##// END OF EJS Templates
merge: make hg.merge() take a context instead of a node...
Martin von Zweigbergk -
r44916:2f290136 default
parent child Browse files
Show More

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

@@ -1,198 +1,198 b''
1 # fetch.py - pull and merge remote changes
1 # fetch.py - pull and merge remote changes
2 #
2 #
3 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
3 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.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 '''pull, update and merge in one command (DEPRECATED)'''
8 '''pull, update and merge in one command (DEPRECATED)'''
9
9
10 from __future__ import absolute_import
10 from __future__ import absolute_import
11
11
12 from mercurial.i18n import _
12 from mercurial.i18n import _
13 from mercurial.node import short
13 from mercurial.node import short
14 from mercurial import (
14 from mercurial import (
15 cmdutil,
15 cmdutil,
16 error,
16 error,
17 exchange,
17 exchange,
18 hg,
18 hg,
19 lock,
19 lock,
20 pycompat,
20 pycompat,
21 registrar,
21 registrar,
22 util,
22 util,
23 )
23 )
24 from mercurial.utils import dateutil
24 from mercurial.utils import dateutil
25
25
26 release = lock.release
26 release = lock.release
27 cmdtable = {}
27 cmdtable = {}
28 command = registrar.command(cmdtable)
28 command = registrar.command(cmdtable)
29 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
29 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
30 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
30 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
31 # be specifying the version(s) of Mercurial they are tested with, or
31 # be specifying the version(s) of Mercurial they are tested with, or
32 # leave the attribute unspecified.
32 # leave the attribute unspecified.
33 testedwith = b'ships-with-hg-core'
33 testedwith = b'ships-with-hg-core'
34
34
35
35
36 @command(
36 @command(
37 b'fetch',
37 b'fetch',
38 [
38 [
39 (
39 (
40 b'r',
40 b'r',
41 b'rev',
41 b'rev',
42 [],
42 [],
43 _(b'a specific revision you would like to pull'),
43 _(b'a specific revision you would like to pull'),
44 _(b'REV'),
44 _(b'REV'),
45 ),
45 ),
46 (b'', b'edit', None, _(b'invoke editor on commit messages')),
46 (b'', b'edit', None, _(b'invoke editor on commit messages')),
47 (b'', b'force-editor', None, _(b'edit commit message (DEPRECATED)')),
47 (b'', b'force-editor', None, _(b'edit commit message (DEPRECATED)')),
48 (b'', b'switch-parent', None, _(b'switch parents when merging')),
48 (b'', b'switch-parent', None, _(b'switch parents when merging')),
49 ]
49 ]
50 + cmdutil.commitopts
50 + cmdutil.commitopts
51 + cmdutil.commitopts2
51 + cmdutil.commitopts2
52 + cmdutil.remoteopts,
52 + cmdutil.remoteopts,
53 _(b'hg fetch [SOURCE]'),
53 _(b'hg fetch [SOURCE]'),
54 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
54 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
55 )
55 )
56 def fetch(ui, repo, source=b'default', **opts):
56 def fetch(ui, repo, source=b'default', **opts):
57 '''pull changes from a remote repository, merge new changes if needed.
57 '''pull changes from a remote repository, merge new changes if needed.
58
58
59 This finds all changes from the repository at the specified path
59 This finds all changes from the repository at the specified path
60 or URL and adds them to the local repository.
60 or URL and adds them to the local repository.
61
61
62 If the pulled changes add a new branch head, the head is
62 If the pulled changes add a new branch head, the head is
63 automatically merged, and the result of the merge is committed.
63 automatically merged, and the result of the merge is committed.
64 Otherwise, the working directory is updated to include the new
64 Otherwise, the working directory is updated to include the new
65 changes.
65 changes.
66
66
67 When a merge is needed, the working directory is first updated to
67 When a merge is needed, the working directory is first updated to
68 the newly pulled changes. Local changes are then merged into the
68 the newly pulled changes. Local changes are then merged into the
69 pulled changes. To switch the merge order, use --switch-parent.
69 pulled changes. To switch the merge order, use --switch-parent.
70
70
71 See :hg:`help dates` for a list of formats valid for -d/--date.
71 See :hg:`help dates` for a list of formats valid for -d/--date.
72
72
73 Returns 0 on success.
73 Returns 0 on success.
74 '''
74 '''
75
75
76 opts = pycompat.byteskwargs(opts)
76 opts = pycompat.byteskwargs(opts)
77 date = opts.get(b'date')
77 date = opts.get(b'date')
78 if date:
78 if date:
79 opts[b'date'] = dateutil.parsedate(date)
79 opts[b'date'] = dateutil.parsedate(date)
80
80
81 parent = repo.dirstate.p1()
81 parent = repo.dirstate.p1()
82 branch = repo.dirstate.branch()
82 branch = repo.dirstate.branch()
83 try:
83 try:
84 branchnode = repo.branchtip(branch)
84 branchnode = repo.branchtip(branch)
85 except error.RepoLookupError:
85 except error.RepoLookupError:
86 branchnode = None
86 branchnode = None
87 if parent != branchnode:
87 if parent != branchnode:
88 raise error.Abort(
88 raise error.Abort(
89 _(b'working directory not at branch tip'),
89 _(b'working directory not at branch tip'),
90 hint=_(b"use 'hg update' to check out branch tip"),
90 hint=_(b"use 'hg update' to check out branch tip"),
91 )
91 )
92
92
93 wlock = lock = None
93 wlock = lock = None
94 try:
94 try:
95 wlock = repo.wlock()
95 wlock = repo.wlock()
96 lock = repo.lock()
96 lock = repo.lock()
97
97
98 cmdutil.bailifchanged(repo)
98 cmdutil.bailifchanged(repo)
99
99
100 bheads = repo.branchheads(branch)
100 bheads = repo.branchheads(branch)
101 bheads = [head for head in bheads if len(repo[head].children()) == 0]
101 bheads = [head for head in bheads if len(repo[head].children()) == 0]
102 if len(bheads) > 1:
102 if len(bheads) > 1:
103 raise error.Abort(
103 raise error.Abort(
104 _(
104 _(
105 b'multiple heads in this branch '
105 b'multiple heads in this branch '
106 b'(use "hg heads ." and "hg merge" to merge)'
106 b'(use "hg heads ." and "hg merge" to merge)'
107 )
107 )
108 )
108 )
109
109
110 other = hg.peer(repo, opts, ui.expandpath(source))
110 other = hg.peer(repo, opts, ui.expandpath(source))
111 ui.status(
111 ui.status(
112 _(b'pulling from %s\n') % util.hidepassword(ui.expandpath(source))
112 _(b'pulling from %s\n') % util.hidepassword(ui.expandpath(source))
113 )
113 )
114 revs = None
114 revs = None
115 if opts[b'rev']:
115 if opts[b'rev']:
116 try:
116 try:
117 revs = [other.lookup(rev) for rev in opts[b'rev']]
117 revs = [other.lookup(rev) for rev in opts[b'rev']]
118 except error.CapabilityError:
118 except error.CapabilityError:
119 err = _(
119 err = _(
120 b"other repository doesn't support revision lookup, "
120 b"other repository doesn't support revision lookup, "
121 b"so a rev cannot be specified."
121 b"so a rev cannot be specified."
122 )
122 )
123 raise error.Abort(err)
123 raise error.Abort(err)
124
124
125 # Are there any changes at all?
125 # Are there any changes at all?
126 modheads = exchange.pull(repo, other, heads=revs).cgresult
126 modheads = exchange.pull(repo, other, heads=revs).cgresult
127 if modheads == 0:
127 if modheads == 0:
128 return 0
128 return 0
129
129
130 # Is this a simple fast-forward along the current branch?
130 # Is this a simple fast-forward along the current branch?
131 newheads = repo.branchheads(branch)
131 newheads = repo.branchheads(branch)
132 newchildren = repo.changelog.nodesbetween([parent], newheads)[2]
132 newchildren = repo.changelog.nodesbetween([parent], newheads)[2]
133 if len(newheads) == 1 and len(newchildren):
133 if len(newheads) == 1 and len(newchildren):
134 if newchildren[0] != parent:
134 if newchildren[0] != parent:
135 return hg.update(repo, newchildren[0])
135 return hg.update(repo, newchildren[0])
136 else:
136 else:
137 return 0
137 return 0
138
138
139 # Are there more than one additional branch heads?
139 # Are there more than one additional branch heads?
140 newchildren = [n for n in newchildren if n != parent]
140 newchildren = [n for n in newchildren if n != parent]
141 newparent = parent
141 newparent = parent
142 if newchildren:
142 if newchildren:
143 newparent = newchildren[0]
143 newparent = newchildren[0]
144 hg.clean(repo, newparent)
144 hg.clean(repo, newparent)
145 newheads = [n for n in newheads if n != newparent]
145 newheads = [n for n in newheads if n != newparent]
146 if len(newheads) > 1:
146 if len(newheads) > 1:
147 ui.status(
147 ui.status(
148 _(
148 _(
149 b'not merging with %d other new branch heads '
149 b'not merging with %d other new branch heads '
150 b'(use "hg heads ." and "hg merge" to merge them)\n'
150 b'(use "hg heads ." and "hg merge" to merge them)\n'
151 )
151 )
152 % (len(newheads) - 1)
152 % (len(newheads) - 1)
153 )
153 )
154 return 1
154 return 1
155
155
156 if not newheads:
156 if not newheads:
157 return 0
157 return 0
158
158
159 # Otherwise, let's merge.
159 # Otherwise, let's merge.
160 err = False
160 err = False
161 if newheads:
161 if newheads:
162 # By default, we consider the repository we're pulling
162 # By default, we consider the repository we're pulling
163 # *from* as authoritative, so we merge our changes into
163 # *from* as authoritative, so we merge our changes into
164 # theirs.
164 # theirs.
165 if opts[b'switch_parent']:
165 if opts[b'switch_parent']:
166 firstparent, secondparent = newparent, newheads[0]
166 firstparent, secondparent = newparent, newheads[0]
167 else:
167 else:
168 firstparent, secondparent = newheads[0], newparent
168 firstparent, secondparent = newheads[0], newparent
169 ui.status(
169 ui.status(
170 _(b'updating to %d:%s\n')
170 _(b'updating to %d:%s\n')
171 % (repo.changelog.rev(firstparent), short(firstparent))
171 % (repo.changelog.rev(firstparent), short(firstparent))
172 )
172 )
173 hg.clean(repo, firstparent)
173 hg.clean(repo, firstparent)
174 p2ctx = repo[secondparent]
174 ui.status(
175 ui.status(
175 _(b'merging with %d:%s\n')
176 _(b'merging with %d:%s\n') % (p2ctx.rev(), short(secondparent))
176 % (repo.changelog.rev(secondparent), short(secondparent))
177 )
177 )
178 err = hg.merge(repo, secondparent, remind=False)
178 err = hg.merge(p2ctx, remind=False)
179
179
180 if not err:
180 if not err:
181 # we don't translate commit messages
181 # we don't translate commit messages
182 message = cmdutil.logmessage(ui, opts) or (
182 message = cmdutil.logmessage(ui, opts) or (
183 b'Automated merge with %s' % util.removeauth(other.url())
183 b'Automated merge with %s' % util.removeauth(other.url())
184 )
184 )
185 editopt = opts.get(b'edit') or opts.get(b'force_editor')
185 editopt = opts.get(b'edit') or opts.get(b'force_editor')
186 editor = cmdutil.getcommiteditor(edit=editopt, editform=b'fetch')
186 editor = cmdutil.getcommiteditor(edit=editopt, editform=b'fetch')
187 n = repo.commit(
187 n = repo.commit(
188 message, opts[b'user'], opts[b'date'], editor=editor
188 message, opts[b'user'], opts[b'date'], editor=editor
189 )
189 )
190 ui.status(
190 ui.status(
191 _(b'new changeset %d:%s merges remote changes with local\n')
191 _(b'new changeset %d:%s merges remote changes with local\n')
192 % (repo.changelog.rev(n), short(n))
192 % (repo.changelog.rev(n), short(n))
193 )
193 )
194
194
195 return err
195 return err
196
196
197 finally:
197 finally:
198 release(lock, wlock)
198 release(lock, wlock)
@@ -1,4295 +1,4295 b''
1 # mq.py - patch queues for mercurial
1 # mq.py - patch queues for mercurial
2 #
2 #
3 # Copyright 2005, 2006 Chris Mason <mason@suse.com>
3 # Copyright 2005, 2006 Chris Mason <mason@suse.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 '''manage a stack of patches
8 '''manage a stack of patches
9
9
10 This extension lets you work with a stack of patches in a Mercurial
10 This extension lets you work with a stack of patches in a Mercurial
11 repository. It manages two stacks of patches - all known patches, and
11 repository. It manages two stacks of patches - all known patches, and
12 applied patches (subset of known patches).
12 applied patches (subset of known patches).
13
13
14 Known patches are represented as patch files in the .hg/patches
14 Known patches are represented as patch files in the .hg/patches
15 directory. Applied patches are both patch files and changesets.
15 directory. Applied patches are both patch files and changesets.
16
16
17 Common tasks (use :hg:`help COMMAND` for more details)::
17 Common tasks (use :hg:`help COMMAND` for more details)::
18
18
19 create new patch qnew
19 create new patch qnew
20 import existing patch qimport
20 import existing patch qimport
21
21
22 print patch series qseries
22 print patch series qseries
23 print applied patches qapplied
23 print applied patches qapplied
24
24
25 add known patch to applied stack qpush
25 add known patch to applied stack qpush
26 remove patch from applied stack qpop
26 remove patch from applied stack qpop
27 refresh contents of top applied patch qrefresh
27 refresh contents of top applied patch qrefresh
28
28
29 By default, mq will automatically use git patches when required to
29 By default, mq will automatically use git patches when required to
30 avoid losing file mode changes, copy records, binary files or empty
30 avoid losing file mode changes, copy records, binary files or empty
31 files creations or deletions. This behavior can be configured with::
31 files creations or deletions. This behavior can be configured with::
32
32
33 [mq]
33 [mq]
34 git = auto/keep/yes/no
34 git = auto/keep/yes/no
35
35
36 If set to 'keep', mq will obey the [diff] section configuration while
36 If set to 'keep', mq will obey the [diff] section configuration while
37 preserving existing git patches upon qrefresh. If set to 'yes' or
37 preserving existing git patches upon qrefresh. If set to 'yes' or
38 'no', mq will override the [diff] section and always generate git or
38 'no', mq will override the [diff] section and always generate git or
39 regular patches, possibly losing data in the second case.
39 regular patches, possibly losing data in the second case.
40
40
41 It may be desirable for mq changesets to be kept in the secret phase (see
41 It may be desirable for mq changesets to be kept in the secret phase (see
42 :hg:`help phases`), which can be enabled with the following setting::
42 :hg:`help phases`), which can be enabled with the following setting::
43
43
44 [mq]
44 [mq]
45 secret = True
45 secret = True
46
46
47 You will by default be managing a patch queue named "patches". You can
47 You will by default be managing a patch queue named "patches". You can
48 create other, independent patch queues with the :hg:`qqueue` command.
48 create other, independent patch queues with the :hg:`qqueue` command.
49
49
50 If the working directory contains uncommitted files, qpush, qpop and
50 If the working directory contains uncommitted files, qpush, qpop and
51 qgoto abort immediately. If -f/--force is used, the changes are
51 qgoto abort immediately. If -f/--force is used, the changes are
52 discarded. Setting::
52 discarded. Setting::
53
53
54 [mq]
54 [mq]
55 keepchanges = True
55 keepchanges = True
56
56
57 make them behave as if --keep-changes were passed, and non-conflicting
57 make them behave as if --keep-changes were passed, and non-conflicting
58 local changes will be tolerated and preserved. If incompatible options
58 local changes will be tolerated and preserved. If incompatible options
59 such as -f/--force or --exact are passed, this setting is ignored.
59 such as -f/--force or --exact are passed, this setting is ignored.
60
60
61 This extension used to provide a strip command. This command now lives
61 This extension used to provide a strip command. This command now lives
62 in the strip extension.
62 in the strip extension.
63 '''
63 '''
64
64
65 from __future__ import absolute_import, print_function
65 from __future__ import absolute_import, print_function
66
66
67 import errno
67 import errno
68 import os
68 import os
69 import re
69 import re
70 import shutil
70 import shutil
71 import sys
71 import sys
72 from mercurial.i18n import _
72 from mercurial.i18n import _
73 from mercurial.node import (
73 from mercurial.node import (
74 bin,
74 bin,
75 hex,
75 hex,
76 nullid,
76 nullid,
77 nullrev,
77 nullrev,
78 short,
78 short,
79 )
79 )
80 from mercurial.pycompat import (
80 from mercurial.pycompat import (
81 delattr,
81 delattr,
82 getattr,
82 getattr,
83 open,
83 open,
84 )
84 )
85 from mercurial import (
85 from mercurial import (
86 cmdutil,
86 cmdutil,
87 commands,
87 commands,
88 dirstateguard,
88 dirstateguard,
89 encoding,
89 encoding,
90 error,
90 error,
91 extensions,
91 extensions,
92 hg,
92 hg,
93 localrepo,
93 localrepo,
94 lock as lockmod,
94 lock as lockmod,
95 logcmdutil,
95 logcmdutil,
96 patch as patchmod,
96 patch as patchmod,
97 phases,
97 phases,
98 pycompat,
98 pycompat,
99 registrar,
99 registrar,
100 revsetlang,
100 revsetlang,
101 scmutil,
101 scmutil,
102 smartset,
102 smartset,
103 subrepoutil,
103 subrepoutil,
104 util,
104 util,
105 vfs as vfsmod,
105 vfs as vfsmod,
106 )
106 )
107 from mercurial.utils import (
107 from mercurial.utils import (
108 dateutil,
108 dateutil,
109 stringutil,
109 stringutil,
110 )
110 )
111
111
112 release = lockmod.release
112 release = lockmod.release
113 seriesopts = [(b's', b'summary', None, _(b'print first line of patch header'))]
113 seriesopts = [(b's', b'summary', None, _(b'print first line of patch header'))]
114
114
115 cmdtable = {}
115 cmdtable = {}
116 command = registrar.command(cmdtable)
116 command = registrar.command(cmdtable)
117 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
117 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
118 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
118 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
119 # be specifying the version(s) of Mercurial they are tested with, or
119 # be specifying the version(s) of Mercurial they are tested with, or
120 # leave the attribute unspecified.
120 # leave the attribute unspecified.
121 testedwith = b'ships-with-hg-core'
121 testedwith = b'ships-with-hg-core'
122
122
123 configtable = {}
123 configtable = {}
124 configitem = registrar.configitem(configtable)
124 configitem = registrar.configitem(configtable)
125
125
126 configitem(
126 configitem(
127 b'mq', b'git', default=b'auto',
127 b'mq', b'git', default=b'auto',
128 )
128 )
129 configitem(
129 configitem(
130 b'mq', b'keepchanges', default=False,
130 b'mq', b'keepchanges', default=False,
131 )
131 )
132 configitem(
132 configitem(
133 b'mq', b'plain', default=False,
133 b'mq', b'plain', default=False,
134 )
134 )
135 configitem(
135 configitem(
136 b'mq', b'secret', default=False,
136 b'mq', b'secret', default=False,
137 )
137 )
138
138
139 # force load strip extension formerly included in mq and import some utility
139 # force load strip extension formerly included in mq and import some utility
140 try:
140 try:
141 stripext = extensions.find(b'strip')
141 stripext = extensions.find(b'strip')
142 except KeyError:
142 except KeyError:
143 # note: load is lazy so we could avoid the try-except,
143 # note: load is lazy so we could avoid the try-except,
144 # but I (marmoute) prefer this explicit code.
144 # but I (marmoute) prefer this explicit code.
145 class dummyui(object):
145 class dummyui(object):
146 def debug(self, msg):
146 def debug(self, msg):
147 pass
147 pass
148
148
149 def log(self, event, msgfmt, *msgargs, **opts):
149 def log(self, event, msgfmt, *msgargs, **opts):
150 pass
150 pass
151
151
152 stripext = extensions.load(dummyui(), b'strip', b'')
152 stripext = extensions.load(dummyui(), b'strip', b'')
153
153
154 strip = stripext.strip
154 strip = stripext.strip
155
155
156
156
157 def checksubstate(repo, baserev=None):
157 def checksubstate(repo, baserev=None):
158 '''return list of subrepos at a different revision than substate.
158 '''return list of subrepos at a different revision than substate.
159 Abort if any subrepos have uncommitted changes.'''
159 Abort if any subrepos have uncommitted changes.'''
160 inclsubs = []
160 inclsubs = []
161 wctx = repo[None]
161 wctx = repo[None]
162 if baserev:
162 if baserev:
163 bctx = repo[baserev]
163 bctx = repo[baserev]
164 else:
164 else:
165 bctx = wctx.p1()
165 bctx = wctx.p1()
166 for s in sorted(wctx.substate):
166 for s in sorted(wctx.substate):
167 wctx.sub(s).bailifchanged(True)
167 wctx.sub(s).bailifchanged(True)
168 if s not in bctx.substate or bctx.sub(s).dirty():
168 if s not in bctx.substate or bctx.sub(s).dirty():
169 inclsubs.append(s)
169 inclsubs.append(s)
170 return inclsubs
170 return inclsubs
171
171
172
172
173 # Patch names looks like unix-file names.
173 # Patch names looks like unix-file names.
174 # They must be joinable with queue directory and result in the patch path.
174 # They must be joinable with queue directory and result in the patch path.
175 normname = util.normpath
175 normname = util.normpath
176
176
177
177
178 class statusentry(object):
178 class statusentry(object):
179 def __init__(self, node, name):
179 def __init__(self, node, name):
180 self.node, self.name = node, name
180 self.node, self.name = node, name
181
181
182 def __bytes__(self):
182 def __bytes__(self):
183 return hex(self.node) + b':' + self.name
183 return hex(self.node) + b':' + self.name
184
184
185 __str__ = encoding.strmethod(__bytes__)
185 __str__ = encoding.strmethod(__bytes__)
186 __repr__ = encoding.strmethod(__bytes__)
186 __repr__ = encoding.strmethod(__bytes__)
187
187
188
188
189 # The order of the headers in 'hg export' HG patches:
189 # The order of the headers in 'hg export' HG patches:
190 HGHEADERS = [
190 HGHEADERS = [
191 # '# HG changeset patch',
191 # '# HG changeset patch',
192 b'# User ',
192 b'# User ',
193 b'# Date ',
193 b'# Date ',
194 b'# ',
194 b'# ',
195 b'# Branch ',
195 b'# Branch ',
196 b'# Node ID ',
196 b'# Node ID ',
197 b'# Parent ', # can occur twice for merges - but that is not relevant for mq
197 b'# Parent ', # can occur twice for merges - but that is not relevant for mq
198 ]
198 ]
199 # The order of headers in plain 'mail style' patches:
199 # The order of headers in plain 'mail style' patches:
200 PLAINHEADERS = {
200 PLAINHEADERS = {
201 b'from': 0,
201 b'from': 0,
202 b'date': 1,
202 b'date': 1,
203 b'subject': 2,
203 b'subject': 2,
204 }
204 }
205
205
206
206
207 def inserthgheader(lines, header, value):
207 def inserthgheader(lines, header, value):
208 """Assuming lines contains a HG patch header, add a header line with value.
208 """Assuming lines contains a HG patch header, add a header line with value.
209 >>> try: inserthgheader([], b'# Date ', b'z')
209 >>> try: inserthgheader([], b'# Date ', b'z')
210 ... except ValueError as inst: print("oops")
210 ... except ValueError as inst: print("oops")
211 oops
211 oops
212 >>> inserthgheader([b'# HG changeset patch'], b'# Date ', b'z')
212 >>> inserthgheader([b'# HG changeset patch'], b'# Date ', b'z')
213 ['# HG changeset patch', '# Date z']
213 ['# HG changeset patch', '# Date z']
214 >>> inserthgheader([b'# HG changeset patch', b''], b'# Date ', b'z')
214 >>> inserthgheader([b'# HG changeset patch', b''], b'# Date ', b'z')
215 ['# HG changeset patch', '# Date z', '']
215 ['# HG changeset patch', '# Date z', '']
216 >>> inserthgheader([b'# HG changeset patch', b'# User y'], b'# Date ', b'z')
216 >>> inserthgheader([b'# HG changeset patch', b'# User y'], b'# Date ', b'z')
217 ['# HG changeset patch', '# User y', '# Date z']
217 ['# HG changeset patch', '# User y', '# Date z']
218 >>> inserthgheader([b'# HG changeset patch', b'# Date x', b'# User y'],
218 >>> inserthgheader([b'# HG changeset patch', b'# Date x', b'# User y'],
219 ... b'# User ', b'z')
219 ... b'# User ', b'z')
220 ['# HG changeset patch', '# Date x', '# User z']
220 ['# HG changeset patch', '# Date x', '# User z']
221 >>> inserthgheader([b'# HG changeset patch', b'# Date y'], b'# Date ', b'z')
221 >>> inserthgheader([b'# HG changeset patch', b'# Date y'], b'# Date ', b'z')
222 ['# HG changeset patch', '# Date z']
222 ['# HG changeset patch', '# Date z']
223 >>> inserthgheader([b'# HG changeset patch', b'', b'# Date y'],
223 >>> inserthgheader([b'# HG changeset patch', b'', b'# Date y'],
224 ... b'# Date ', b'z')
224 ... b'# Date ', b'z')
225 ['# HG changeset patch', '# Date z', '', '# Date y']
225 ['# HG changeset patch', '# Date z', '', '# Date y']
226 >>> inserthgheader([b'# HG changeset patch', b'# Parent y'],
226 >>> inserthgheader([b'# HG changeset patch', b'# Parent y'],
227 ... b'# Date ', b'z')
227 ... b'# Date ', b'z')
228 ['# HG changeset patch', '# Date z', '# Parent y']
228 ['# HG changeset patch', '# Date z', '# Parent y']
229 """
229 """
230 start = lines.index(b'# HG changeset patch') + 1
230 start = lines.index(b'# HG changeset patch') + 1
231 newindex = HGHEADERS.index(header)
231 newindex = HGHEADERS.index(header)
232 bestpos = len(lines)
232 bestpos = len(lines)
233 for i in range(start, len(lines)):
233 for i in range(start, len(lines)):
234 line = lines[i]
234 line = lines[i]
235 if not line.startswith(b'# '):
235 if not line.startswith(b'# '):
236 bestpos = min(bestpos, i)
236 bestpos = min(bestpos, i)
237 break
237 break
238 for lineindex, h in enumerate(HGHEADERS):
238 for lineindex, h in enumerate(HGHEADERS):
239 if line.startswith(h):
239 if line.startswith(h):
240 if lineindex == newindex:
240 if lineindex == newindex:
241 lines[i] = header + value
241 lines[i] = header + value
242 return lines
242 return lines
243 if lineindex > newindex:
243 if lineindex > newindex:
244 bestpos = min(bestpos, i)
244 bestpos = min(bestpos, i)
245 break # next line
245 break # next line
246 lines.insert(bestpos, header + value)
246 lines.insert(bestpos, header + value)
247 return lines
247 return lines
248
248
249
249
250 def insertplainheader(lines, header, value):
250 def insertplainheader(lines, header, value):
251 """For lines containing a plain patch header, add a header line with value.
251 """For lines containing a plain patch header, add a header line with value.
252 >>> insertplainheader([], b'Date', b'z')
252 >>> insertplainheader([], b'Date', b'z')
253 ['Date: z']
253 ['Date: z']
254 >>> insertplainheader([b''], b'Date', b'z')
254 >>> insertplainheader([b''], b'Date', b'z')
255 ['Date: z', '']
255 ['Date: z', '']
256 >>> insertplainheader([b'x'], b'Date', b'z')
256 >>> insertplainheader([b'x'], b'Date', b'z')
257 ['Date: z', '', 'x']
257 ['Date: z', '', 'x']
258 >>> insertplainheader([b'From: y', b'x'], b'Date', b'z')
258 >>> insertplainheader([b'From: y', b'x'], b'Date', b'z')
259 ['From: y', 'Date: z', '', 'x']
259 ['From: y', 'Date: z', '', 'x']
260 >>> insertplainheader([b' date : x', b' from : y', b''], b'From', b'z')
260 >>> insertplainheader([b' date : x', b' from : y', b''], b'From', b'z')
261 [' date : x', 'From: z', '']
261 [' date : x', 'From: z', '']
262 >>> insertplainheader([b'', b'Date: y'], b'Date', b'z')
262 >>> insertplainheader([b'', b'Date: y'], b'Date', b'z')
263 ['Date: z', '', 'Date: y']
263 ['Date: z', '', 'Date: y']
264 >>> insertplainheader([b'foo: bar', b'DATE: z', b'x'], b'From', b'y')
264 >>> insertplainheader([b'foo: bar', b'DATE: z', b'x'], b'From', b'y')
265 ['From: y', 'foo: bar', 'DATE: z', '', 'x']
265 ['From: y', 'foo: bar', 'DATE: z', '', 'x']
266 """
266 """
267 newprio = PLAINHEADERS[header.lower()]
267 newprio = PLAINHEADERS[header.lower()]
268 bestpos = len(lines)
268 bestpos = len(lines)
269 for i, line in enumerate(lines):
269 for i, line in enumerate(lines):
270 if b':' in line:
270 if b':' in line:
271 lheader = line.split(b':', 1)[0].strip().lower()
271 lheader = line.split(b':', 1)[0].strip().lower()
272 lprio = PLAINHEADERS.get(lheader, newprio + 1)
272 lprio = PLAINHEADERS.get(lheader, newprio + 1)
273 if lprio == newprio:
273 if lprio == newprio:
274 lines[i] = b'%s: %s' % (header, value)
274 lines[i] = b'%s: %s' % (header, value)
275 return lines
275 return lines
276 if lprio > newprio and i < bestpos:
276 if lprio > newprio and i < bestpos:
277 bestpos = i
277 bestpos = i
278 else:
278 else:
279 if line:
279 if line:
280 lines.insert(i, b'')
280 lines.insert(i, b'')
281 if i < bestpos:
281 if i < bestpos:
282 bestpos = i
282 bestpos = i
283 break
283 break
284 lines.insert(bestpos, b'%s: %s' % (header, value))
284 lines.insert(bestpos, b'%s: %s' % (header, value))
285 return lines
285 return lines
286
286
287
287
288 class patchheader(object):
288 class patchheader(object):
289 def __init__(self, pf, plainmode=False):
289 def __init__(self, pf, plainmode=False):
290 def eatdiff(lines):
290 def eatdiff(lines):
291 while lines:
291 while lines:
292 l = lines[-1]
292 l = lines[-1]
293 if (
293 if (
294 l.startswith(b"diff -")
294 l.startswith(b"diff -")
295 or l.startswith(b"Index:")
295 or l.startswith(b"Index:")
296 or l.startswith(b"===========")
296 or l.startswith(b"===========")
297 ):
297 ):
298 del lines[-1]
298 del lines[-1]
299 else:
299 else:
300 break
300 break
301
301
302 def eatempty(lines):
302 def eatempty(lines):
303 while lines:
303 while lines:
304 if not lines[-1].strip():
304 if not lines[-1].strip():
305 del lines[-1]
305 del lines[-1]
306 else:
306 else:
307 break
307 break
308
308
309 message = []
309 message = []
310 comments = []
310 comments = []
311 user = None
311 user = None
312 date = None
312 date = None
313 parent = None
313 parent = None
314 format = None
314 format = None
315 subject = None
315 subject = None
316 branch = None
316 branch = None
317 nodeid = None
317 nodeid = None
318 diffstart = 0
318 diffstart = 0
319
319
320 for line in open(pf, b'rb'):
320 for line in open(pf, b'rb'):
321 line = line.rstrip()
321 line = line.rstrip()
322 if line.startswith(b'diff --git') or (
322 if line.startswith(b'diff --git') or (
323 diffstart and line.startswith(b'+++ ')
323 diffstart and line.startswith(b'+++ ')
324 ):
324 ):
325 diffstart = 2
325 diffstart = 2
326 break
326 break
327 diffstart = 0 # reset
327 diffstart = 0 # reset
328 if line.startswith(b"--- "):
328 if line.startswith(b"--- "):
329 diffstart = 1
329 diffstart = 1
330 continue
330 continue
331 elif format == b"hgpatch":
331 elif format == b"hgpatch":
332 # parse values when importing the result of an hg export
332 # parse values when importing the result of an hg export
333 if line.startswith(b"# User "):
333 if line.startswith(b"# User "):
334 user = line[7:]
334 user = line[7:]
335 elif line.startswith(b"# Date "):
335 elif line.startswith(b"# Date "):
336 date = line[7:]
336 date = line[7:]
337 elif line.startswith(b"# Parent "):
337 elif line.startswith(b"# Parent "):
338 parent = line[9:].lstrip() # handle double trailing space
338 parent = line[9:].lstrip() # handle double trailing space
339 elif line.startswith(b"# Branch "):
339 elif line.startswith(b"# Branch "):
340 branch = line[9:]
340 branch = line[9:]
341 elif line.startswith(b"# Node ID "):
341 elif line.startswith(b"# Node ID "):
342 nodeid = line[10:]
342 nodeid = line[10:]
343 elif not line.startswith(b"# ") and line:
343 elif not line.startswith(b"# ") and line:
344 message.append(line)
344 message.append(line)
345 format = None
345 format = None
346 elif line == b'# HG changeset patch':
346 elif line == b'# HG changeset patch':
347 message = []
347 message = []
348 format = b"hgpatch"
348 format = b"hgpatch"
349 elif format != b"tagdone" and (
349 elif format != b"tagdone" and (
350 line.startswith(b"Subject: ") or line.startswith(b"subject: ")
350 line.startswith(b"Subject: ") or line.startswith(b"subject: ")
351 ):
351 ):
352 subject = line[9:]
352 subject = line[9:]
353 format = b"tag"
353 format = b"tag"
354 elif format != b"tagdone" and (
354 elif format != b"tagdone" and (
355 line.startswith(b"From: ") or line.startswith(b"from: ")
355 line.startswith(b"From: ") or line.startswith(b"from: ")
356 ):
356 ):
357 user = line[6:]
357 user = line[6:]
358 format = b"tag"
358 format = b"tag"
359 elif format != b"tagdone" and (
359 elif format != b"tagdone" and (
360 line.startswith(b"Date: ") or line.startswith(b"date: ")
360 line.startswith(b"Date: ") or line.startswith(b"date: ")
361 ):
361 ):
362 date = line[6:]
362 date = line[6:]
363 format = b"tag"
363 format = b"tag"
364 elif format == b"tag" and line == b"":
364 elif format == b"tag" and line == b"":
365 # when looking for tags (subject: from: etc) they
365 # when looking for tags (subject: from: etc) they
366 # end once you find a blank line in the source
366 # end once you find a blank line in the source
367 format = b"tagdone"
367 format = b"tagdone"
368 elif message or line:
368 elif message or line:
369 message.append(line)
369 message.append(line)
370 comments.append(line)
370 comments.append(line)
371
371
372 eatdiff(message)
372 eatdiff(message)
373 eatdiff(comments)
373 eatdiff(comments)
374 # Remember the exact starting line of the patch diffs before consuming
374 # Remember the exact starting line of the patch diffs before consuming
375 # empty lines, for external use by TortoiseHg and others
375 # empty lines, for external use by TortoiseHg and others
376 self.diffstartline = len(comments)
376 self.diffstartline = len(comments)
377 eatempty(message)
377 eatempty(message)
378 eatempty(comments)
378 eatempty(comments)
379
379
380 # make sure message isn't empty
380 # make sure message isn't empty
381 if format and format.startswith(b"tag") and subject:
381 if format and format.startswith(b"tag") and subject:
382 message.insert(0, subject)
382 message.insert(0, subject)
383
383
384 self.message = message
384 self.message = message
385 self.comments = comments
385 self.comments = comments
386 self.user = user
386 self.user = user
387 self.date = date
387 self.date = date
388 self.parent = parent
388 self.parent = parent
389 # nodeid and branch are for external use by TortoiseHg and others
389 # nodeid and branch are for external use by TortoiseHg and others
390 self.nodeid = nodeid
390 self.nodeid = nodeid
391 self.branch = branch
391 self.branch = branch
392 self.haspatch = diffstart > 1
392 self.haspatch = diffstart > 1
393 self.plainmode = (
393 self.plainmode = (
394 plainmode
394 plainmode
395 or b'# HG changeset patch' not in self.comments
395 or b'# HG changeset patch' not in self.comments
396 and any(
396 and any(
397 c.startswith(b'Date: ') or c.startswith(b'From: ')
397 c.startswith(b'Date: ') or c.startswith(b'From: ')
398 for c in self.comments
398 for c in self.comments
399 )
399 )
400 )
400 )
401
401
402 def setuser(self, user):
402 def setuser(self, user):
403 try:
403 try:
404 inserthgheader(self.comments, b'# User ', user)
404 inserthgheader(self.comments, b'# User ', user)
405 except ValueError:
405 except ValueError:
406 if self.plainmode:
406 if self.plainmode:
407 insertplainheader(self.comments, b'From', user)
407 insertplainheader(self.comments, b'From', user)
408 else:
408 else:
409 tmp = [b'# HG changeset patch', b'# User ' + user]
409 tmp = [b'# HG changeset patch', b'# User ' + user]
410 self.comments = tmp + self.comments
410 self.comments = tmp + self.comments
411 self.user = user
411 self.user = user
412
412
413 def setdate(self, date):
413 def setdate(self, date):
414 try:
414 try:
415 inserthgheader(self.comments, b'# Date ', date)
415 inserthgheader(self.comments, b'# Date ', date)
416 except ValueError:
416 except ValueError:
417 if self.plainmode:
417 if self.plainmode:
418 insertplainheader(self.comments, b'Date', date)
418 insertplainheader(self.comments, b'Date', date)
419 else:
419 else:
420 tmp = [b'# HG changeset patch', b'# Date ' + date]
420 tmp = [b'# HG changeset patch', b'# Date ' + date]
421 self.comments = tmp + self.comments
421 self.comments = tmp + self.comments
422 self.date = date
422 self.date = date
423
423
424 def setparent(self, parent):
424 def setparent(self, parent):
425 try:
425 try:
426 inserthgheader(self.comments, b'# Parent ', parent)
426 inserthgheader(self.comments, b'# Parent ', parent)
427 except ValueError:
427 except ValueError:
428 if not self.plainmode:
428 if not self.plainmode:
429 tmp = [b'# HG changeset patch', b'# Parent ' + parent]
429 tmp = [b'# HG changeset patch', b'# Parent ' + parent]
430 self.comments = tmp + self.comments
430 self.comments = tmp + self.comments
431 self.parent = parent
431 self.parent = parent
432
432
433 def setmessage(self, message):
433 def setmessage(self, message):
434 if self.comments:
434 if self.comments:
435 self._delmsg()
435 self._delmsg()
436 self.message = [message]
436 self.message = [message]
437 if message:
437 if message:
438 if self.plainmode and self.comments and self.comments[-1]:
438 if self.plainmode and self.comments and self.comments[-1]:
439 self.comments.append(b'')
439 self.comments.append(b'')
440 self.comments.append(message)
440 self.comments.append(message)
441
441
442 def __bytes__(self):
442 def __bytes__(self):
443 s = b'\n'.join(self.comments).rstrip()
443 s = b'\n'.join(self.comments).rstrip()
444 if not s:
444 if not s:
445 return b''
445 return b''
446 return s + b'\n\n'
446 return s + b'\n\n'
447
447
448 __str__ = encoding.strmethod(__bytes__)
448 __str__ = encoding.strmethod(__bytes__)
449
449
450 def _delmsg(self):
450 def _delmsg(self):
451 '''Remove existing message, keeping the rest of the comments fields.
451 '''Remove existing message, keeping the rest of the comments fields.
452 If comments contains 'subject: ', message will prepend
452 If comments contains 'subject: ', message will prepend
453 the field and a blank line.'''
453 the field and a blank line.'''
454 if self.message:
454 if self.message:
455 subj = b'subject: ' + self.message[0].lower()
455 subj = b'subject: ' + self.message[0].lower()
456 for i in pycompat.xrange(len(self.comments)):
456 for i in pycompat.xrange(len(self.comments)):
457 if subj == self.comments[i].lower():
457 if subj == self.comments[i].lower():
458 del self.comments[i]
458 del self.comments[i]
459 self.message = self.message[2:]
459 self.message = self.message[2:]
460 break
460 break
461 ci = 0
461 ci = 0
462 for mi in self.message:
462 for mi in self.message:
463 while mi != self.comments[ci]:
463 while mi != self.comments[ci]:
464 ci += 1
464 ci += 1
465 del self.comments[ci]
465 del self.comments[ci]
466
466
467
467
468 def newcommit(repo, phase, *args, **kwargs):
468 def newcommit(repo, phase, *args, **kwargs):
469 """helper dedicated to ensure a commit respect mq.secret setting
469 """helper dedicated to ensure a commit respect mq.secret setting
470
470
471 It should be used instead of repo.commit inside the mq source for operation
471 It should be used instead of repo.commit inside the mq source for operation
472 creating new changeset.
472 creating new changeset.
473 """
473 """
474 repo = repo.unfiltered()
474 repo = repo.unfiltered()
475 if phase is None:
475 if phase is None:
476 if repo.ui.configbool(b'mq', b'secret'):
476 if repo.ui.configbool(b'mq', b'secret'):
477 phase = phases.secret
477 phase = phases.secret
478 overrides = {(b'ui', b'allowemptycommit'): True}
478 overrides = {(b'ui', b'allowemptycommit'): True}
479 if phase is not None:
479 if phase is not None:
480 overrides[(b'phases', b'new-commit')] = phase
480 overrides[(b'phases', b'new-commit')] = phase
481 with repo.ui.configoverride(overrides, b'mq'):
481 with repo.ui.configoverride(overrides, b'mq'):
482 repo.ui.setconfig(b'ui', b'allowemptycommit', True)
482 repo.ui.setconfig(b'ui', b'allowemptycommit', True)
483 return repo.commit(*args, **kwargs)
483 return repo.commit(*args, **kwargs)
484
484
485
485
486 class AbortNoCleanup(error.Abort):
486 class AbortNoCleanup(error.Abort):
487 pass
487 pass
488
488
489
489
490 class queue(object):
490 class queue(object):
491 def __init__(self, ui, baseui, path, patchdir=None):
491 def __init__(self, ui, baseui, path, patchdir=None):
492 self.basepath = path
492 self.basepath = path
493 try:
493 try:
494 with open(os.path.join(path, b'patches.queue'), 'rb') as fh:
494 with open(os.path.join(path, b'patches.queue'), 'rb') as fh:
495 cur = fh.read().rstrip()
495 cur = fh.read().rstrip()
496
496
497 if not cur:
497 if not cur:
498 curpath = os.path.join(path, b'patches')
498 curpath = os.path.join(path, b'patches')
499 else:
499 else:
500 curpath = os.path.join(path, b'patches-' + cur)
500 curpath = os.path.join(path, b'patches-' + cur)
501 except IOError:
501 except IOError:
502 curpath = os.path.join(path, b'patches')
502 curpath = os.path.join(path, b'patches')
503 self.path = patchdir or curpath
503 self.path = patchdir or curpath
504 self.opener = vfsmod.vfs(self.path)
504 self.opener = vfsmod.vfs(self.path)
505 self.ui = ui
505 self.ui = ui
506 self.baseui = baseui
506 self.baseui = baseui
507 self.applieddirty = False
507 self.applieddirty = False
508 self.seriesdirty = False
508 self.seriesdirty = False
509 self.added = []
509 self.added = []
510 self.seriespath = b"series"
510 self.seriespath = b"series"
511 self.statuspath = b"status"
511 self.statuspath = b"status"
512 self.guardspath = b"guards"
512 self.guardspath = b"guards"
513 self.activeguards = None
513 self.activeguards = None
514 self.guardsdirty = False
514 self.guardsdirty = False
515 # Handle mq.git as a bool with extended values
515 # Handle mq.git as a bool with extended values
516 gitmode = ui.config(b'mq', b'git').lower()
516 gitmode = ui.config(b'mq', b'git').lower()
517 boolmode = stringutil.parsebool(gitmode)
517 boolmode = stringutil.parsebool(gitmode)
518 if boolmode is not None:
518 if boolmode is not None:
519 if boolmode:
519 if boolmode:
520 gitmode = b'yes'
520 gitmode = b'yes'
521 else:
521 else:
522 gitmode = b'no'
522 gitmode = b'no'
523 self.gitmode = gitmode
523 self.gitmode = gitmode
524 # deprecated config: mq.plain
524 # deprecated config: mq.plain
525 self.plainmode = ui.configbool(b'mq', b'plain')
525 self.plainmode = ui.configbool(b'mq', b'plain')
526 self.checkapplied = True
526 self.checkapplied = True
527
527
528 @util.propertycache
528 @util.propertycache
529 def applied(self):
529 def applied(self):
530 def parselines(lines):
530 def parselines(lines):
531 for l in lines:
531 for l in lines:
532 entry = l.split(b':', 1)
532 entry = l.split(b':', 1)
533 if len(entry) > 1:
533 if len(entry) > 1:
534 n, name = entry
534 n, name = entry
535 yield statusentry(bin(n), name)
535 yield statusentry(bin(n), name)
536 elif l.strip():
536 elif l.strip():
537 self.ui.warn(
537 self.ui.warn(
538 _(b'malformated mq status line: %s\n')
538 _(b'malformated mq status line: %s\n')
539 % stringutil.pprint(entry)
539 % stringutil.pprint(entry)
540 )
540 )
541 # else we ignore empty lines
541 # else we ignore empty lines
542
542
543 try:
543 try:
544 lines = self.opener.read(self.statuspath).splitlines()
544 lines = self.opener.read(self.statuspath).splitlines()
545 return list(parselines(lines))
545 return list(parselines(lines))
546 except IOError as e:
546 except IOError as e:
547 if e.errno == errno.ENOENT:
547 if e.errno == errno.ENOENT:
548 return []
548 return []
549 raise
549 raise
550
550
551 @util.propertycache
551 @util.propertycache
552 def fullseries(self):
552 def fullseries(self):
553 try:
553 try:
554 return self.opener.read(self.seriespath).splitlines()
554 return self.opener.read(self.seriespath).splitlines()
555 except IOError as e:
555 except IOError as e:
556 if e.errno == errno.ENOENT:
556 if e.errno == errno.ENOENT:
557 return []
557 return []
558 raise
558 raise
559
559
560 @util.propertycache
560 @util.propertycache
561 def series(self):
561 def series(self):
562 self.parseseries()
562 self.parseseries()
563 return self.series
563 return self.series
564
564
565 @util.propertycache
565 @util.propertycache
566 def seriesguards(self):
566 def seriesguards(self):
567 self.parseseries()
567 self.parseseries()
568 return self.seriesguards
568 return self.seriesguards
569
569
570 def invalidate(self):
570 def invalidate(self):
571 for a in 'applied fullseries series seriesguards'.split():
571 for a in 'applied fullseries series seriesguards'.split():
572 if a in self.__dict__:
572 if a in self.__dict__:
573 delattr(self, a)
573 delattr(self, a)
574 self.applieddirty = False
574 self.applieddirty = False
575 self.seriesdirty = False
575 self.seriesdirty = False
576 self.guardsdirty = False
576 self.guardsdirty = False
577 self.activeguards = None
577 self.activeguards = None
578
578
579 def diffopts(self, opts=None, patchfn=None, plain=False):
579 def diffopts(self, opts=None, patchfn=None, plain=False):
580 """Return diff options tweaked for this mq use, possibly upgrading to
580 """Return diff options tweaked for this mq use, possibly upgrading to
581 git format, and possibly plain and without lossy options."""
581 git format, and possibly plain and without lossy options."""
582 diffopts = patchmod.difffeatureopts(
582 diffopts = patchmod.difffeatureopts(
583 self.ui,
583 self.ui,
584 opts,
584 opts,
585 git=True,
585 git=True,
586 whitespace=not plain,
586 whitespace=not plain,
587 formatchanging=not plain,
587 formatchanging=not plain,
588 )
588 )
589 if self.gitmode == b'auto':
589 if self.gitmode == b'auto':
590 diffopts.upgrade = True
590 diffopts.upgrade = True
591 elif self.gitmode == b'keep':
591 elif self.gitmode == b'keep':
592 pass
592 pass
593 elif self.gitmode in (b'yes', b'no'):
593 elif self.gitmode in (b'yes', b'no'):
594 diffopts.git = self.gitmode == b'yes'
594 diffopts.git = self.gitmode == b'yes'
595 else:
595 else:
596 raise error.Abort(
596 raise error.Abort(
597 _(b'mq.git option can be auto/keep/yes/no got %s')
597 _(b'mq.git option can be auto/keep/yes/no got %s')
598 % self.gitmode
598 % self.gitmode
599 )
599 )
600 if patchfn:
600 if patchfn:
601 diffopts = self.patchopts(diffopts, patchfn)
601 diffopts = self.patchopts(diffopts, patchfn)
602 return diffopts
602 return diffopts
603
603
604 def patchopts(self, diffopts, *patches):
604 def patchopts(self, diffopts, *patches):
605 """Return a copy of input diff options with git set to true if
605 """Return a copy of input diff options with git set to true if
606 referenced patch is a git patch and should be preserved as such.
606 referenced patch is a git patch and should be preserved as such.
607 """
607 """
608 diffopts = diffopts.copy()
608 diffopts = diffopts.copy()
609 if not diffopts.git and self.gitmode == b'keep':
609 if not diffopts.git and self.gitmode == b'keep':
610 for patchfn in patches:
610 for patchfn in patches:
611 patchf = self.opener(patchfn, b'r')
611 patchf = self.opener(patchfn, b'r')
612 # if the patch was a git patch, refresh it as a git patch
612 # if the patch was a git patch, refresh it as a git patch
613 diffopts.git = any(
613 diffopts.git = any(
614 line.startswith(b'diff --git') for line in patchf
614 line.startswith(b'diff --git') for line in patchf
615 )
615 )
616 patchf.close()
616 patchf.close()
617 return diffopts
617 return diffopts
618
618
619 def join(self, *p):
619 def join(self, *p):
620 return os.path.join(self.path, *p)
620 return os.path.join(self.path, *p)
621
621
622 def findseries(self, patch):
622 def findseries(self, patch):
623 def matchpatch(l):
623 def matchpatch(l):
624 l = l.split(b'#', 1)[0]
624 l = l.split(b'#', 1)[0]
625 return l.strip() == patch
625 return l.strip() == patch
626
626
627 for index, l in enumerate(self.fullseries):
627 for index, l in enumerate(self.fullseries):
628 if matchpatch(l):
628 if matchpatch(l):
629 return index
629 return index
630 return None
630 return None
631
631
632 guard_re = re.compile(br'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
632 guard_re = re.compile(br'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
633
633
634 def parseseries(self):
634 def parseseries(self):
635 self.series = []
635 self.series = []
636 self.seriesguards = []
636 self.seriesguards = []
637 for l in self.fullseries:
637 for l in self.fullseries:
638 h = l.find(b'#')
638 h = l.find(b'#')
639 if h == -1:
639 if h == -1:
640 patch = l
640 patch = l
641 comment = b''
641 comment = b''
642 elif h == 0:
642 elif h == 0:
643 continue
643 continue
644 else:
644 else:
645 patch = l[:h]
645 patch = l[:h]
646 comment = l[h:]
646 comment = l[h:]
647 patch = patch.strip()
647 patch = patch.strip()
648 if patch:
648 if patch:
649 if patch in self.series:
649 if patch in self.series:
650 raise error.Abort(
650 raise error.Abort(
651 _(b'%s appears more than once in %s')
651 _(b'%s appears more than once in %s')
652 % (patch, self.join(self.seriespath))
652 % (patch, self.join(self.seriespath))
653 )
653 )
654 self.series.append(patch)
654 self.series.append(patch)
655 self.seriesguards.append(self.guard_re.findall(comment))
655 self.seriesguards.append(self.guard_re.findall(comment))
656
656
657 def checkguard(self, guard):
657 def checkguard(self, guard):
658 if not guard:
658 if not guard:
659 return _(b'guard cannot be an empty string')
659 return _(b'guard cannot be an empty string')
660 bad_chars = b'# \t\r\n\f'
660 bad_chars = b'# \t\r\n\f'
661 first = guard[0]
661 first = guard[0]
662 if first in b'-+':
662 if first in b'-+':
663 return _(b'guard %r starts with invalid character: %r') % (
663 return _(b'guard %r starts with invalid character: %r') % (
664 guard,
664 guard,
665 first,
665 first,
666 )
666 )
667 for c in bad_chars:
667 for c in bad_chars:
668 if c in guard:
668 if c in guard:
669 return _(b'invalid character in guard %r: %r') % (guard, c)
669 return _(b'invalid character in guard %r: %r') % (guard, c)
670
670
671 def setactive(self, guards):
671 def setactive(self, guards):
672 for guard in guards:
672 for guard in guards:
673 bad = self.checkguard(guard)
673 bad = self.checkguard(guard)
674 if bad:
674 if bad:
675 raise error.Abort(bad)
675 raise error.Abort(bad)
676 guards = sorted(set(guards))
676 guards = sorted(set(guards))
677 self.ui.debug(b'active guards: %s\n' % b' '.join(guards))
677 self.ui.debug(b'active guards: %s\n' % b' '.join(guards))
678 self.activeguards = guards
678 self.activeguards = guards
679 self.guardsdirty = True
679 self.guardsdirty = True
680
680
681 def active(self):
681 def active(self):
682 if self.activeguards is None:
682 if self.activeguards is None:
683 self.activeguards = []
683 self.activeguards = []
684 try:
684 try:
685 guards = self.opener.read(self.guardspath).split()
685 guards = self.opener.read(self.guardspath).split()
686 except IOError as err:
686 except IOError as err:
687 if err.errno != errno.ENOENT:
687 if err.errno != errno.ENOENT:
688 raise
688 raise
689 guards = []
689 guards = []
690 for i, guard in enumerate(guards):
690 for i, guard in enumerate(guards):
691 bad = self.checkguard(guard)
691 bad = self.checkguard(guard)
692 if bad:
692 if bad:
693 self.ui.warn(
693 self.ui.warn(
694 b'%s:%d: %s\n'
694 b'%s:%d: %s\n'
695 % (self.join(self.guardspath), i + 1, bad)
695 % (self.join(self.guardspath), i + 1, bad)
696 )
696 )
697 else:
697 else:
698 self.activeguards.append(guard)
698 self.activeguards.append(guard)
699 return self.activeguards
699 return self.activeguards
700
700
701 def setguards(self, idx, guards):
701 def setguards(self, idx, guards):
702 for g in guards:
702 for g in guards:
703 if len(g) < 2:
703 if len(g) < 2:
704 raise error.Abort(_(b'guard %r too short') % g)
704 raise error.Abort(_(b'guard %r too short') % g)
705 if g[0] not in b'-+':
705 if g[0] not in b'-+':
706 raise error.Abort(_(b'guard %r starts with invalid char') % g)
706 raise error.Abort(_(b'guard %r starts with invalid char') % g)
707 bad = self.checkguard(g[1:])
707 bad = self.checkguard(g[1:])
708 if bad:
708 if bad:
709 raise error.Abort(bad)
709 raise error.Abort(bad)
710 drop = self.guard_re.sub(b'', self.fullseries[idx])
710 drop = self.guard_re.sub(b'', self.fullseries[idx])
711 self.fullseries[idx] = drop + b''.join([b' #' + g for g in guards])
711 self.fullseries[idx] = drop + b''.join([b' #' + g for g in guards])
712 self.parseseries()
712 self.parseseries()
713 self.seriesdirty = True
713 self.seriesdirty = True
714
714
715 def pushable(self, idx):
715 def pushable(self, idx):
716 if isinstance(idx, bytes):
716 if isinstance(idx, bytes):
717 idx = self.series.index(idx)
717 idx = self.series.index(idx)
718 patchguards = self.seriesguards[idx]
718 patchguards = self.seriesguards[idx]
719 if not patchguards:
719 if not patchguards:
720 return True, None
720 return True, None
721 guards = self.active()
721 guards = self.active()
722 exactneg = [
722 exactneg = [
723 g for g in patchguards if g.startswith(b'-') and g[1:] in guards
723 g for g in patchguards if g.startswith(b'-') and g[1:] in guards
724 ]
724 ]
725 if exactneg:
725 if exactneg:
726 return False, stringutil.pprint(exactneg[0])
726 return False, stringutil.pprint(exactneg[0])
727 pos = [g for g in patchguards if g.startswith(b'+')]
727 pos = [g for g in patchguards if g.startswith(b'+')]
728 exactpos = [g for g in pos if g[1:] in guards]
728 exactpos = [g for g in pos if g[1:] in guards]
729 if pos:
729 if pos:
730 if exactpos:
730 if exactpos:
731 return True, stringutil.pprint(exactpos[0])
731 return True, stringutil.pprint(exactpos[0])
732 return False, b' '.join([stringutil.pprint(p) for p in pos])
732 return False, b' '.join([stringutil.pprint(p) for p in pos])
733 return True, b''
733 return True, b''
734
734
735 def explainpushable(self, idx, all_patches=False):
735 def explainpushable(self, idx, all_patches=False):
736 if all_patches:
736 if all_patches:
737 write = self.ui.write
737 write = self.ui.write
738 else:
738 else:
739 write = self.ui.warn
739 write = self.ui.warn
740
740
741 if all_patches or self.ui.verbose:
741 if all_patches or self.ui.verbose:
742 if isinstance(idx, bytes):
742 if isinstance(idx, bytes):
743 idx = self.series.index(idx)
743 idx = self.series.index(idx)
744 pushable, why = self.pushable(idx)
744 pushable, why = self.pushable(idx)
745 if all_patches and pushable:
745 if all_patches and pushable:
746 if why is None:
746 if why is None:
747 write(
747 write(
748 _(b'allowing %s - no guards in effect\n')
748 _(b'allowing %s - no guards in effect\n')
749 % self.series[idx]
749 % self.series[idx]
750 )
750 )
751 else:
751 else:
752 if not why:
752 if not why:
753 write(
753 write(
754 _(b'allowing %s - no matching negative guards\n')
754 _(b'allowing %s - no matching negative guards\n')
755 % self.series[idx]
755 % self.series[idx]
756 )
756 )
757 else:
757 else:
758 write(
758 write(
759 _(b'allowing %s - guarded by %s\n')
759 _(b'allowing %s - guarded by %s\n')
760 % (self.series[idx], why)
760 % (self.series[idx], why)
761 )
761 )
762 if not pushable:
762 if not pushable:
763 if why:
763 if why:
764 write(
764 write(
765 _(b'skipping %s - guarded by %s\n')
765 _(b'skipping %s - guarded by %s\n')
766 % (self.series[idx], why)
766 % (self.series[idx], why)
767 )
767 )
768 else:
768 else:
769 write(
769 write(
770 _(b'skipping %s - no matching guards\n')
770 _(b'skipping %s - no matching guards\n')
771 % self.series[idx]
771 % self.series[idx]
772 )
772 )
773
773
774 def savedirty(self):
774 def savedirty(self):
775 def writelist(items, path):
775 def writelist(items, path):
776 fp = self.opener(path, b'wb')
776 fp = self.opener(path, b'wb')
777 for i in items:
777 for i in items:
778 fp.write(b"%s\n" % i)
778 fp.write(b"%s\n" % i)
779 fp.close()
779 fp.close()
780
780
781 if self.applieddirty:
781 if self.applieddirty:
782 writelist(map(bytes, self.applied), self.statuspath)
782 writelist(map(bytes, self.applied), self.statuspath)
783 self.applieddirty = False
783 self.applieddirty = False
784 if self.seriesdirty:
784 if self.seriesdirty:
785 writelist(self.fullseries, self.seriespath)
785 writelist(self.fullseries, self.seriespath)
786 self.seriesdirty = False
786 self.seriesdirty = False
787 if self.guardsdirty:
787 if self.guardsdirty:
788 writelist(self.activeguards, self.guardspath)
788 writelist(self.activeguards, self.guardspath)
789 self.guardsdirty = False
789 self.guardsdirty = False
790 if self.added:
790 if self.added:
791 qrepo = self.qrepo()
791 qrepo = self.qrepo()
792 if qrepo:
792 if qrepo:
793 qrepo[None].add(f for f in self.added if f not in qrepo[None])
793 qrepo[None].add(f for f in self.added if f not in qrepo[None])
794 self.added = []
794 self.added = []
795
795
796 def removeundo(self, repo):
796 def removeundo(self, repo):
797 undo = repo.sjoin(b'undo')
797 undo = repo.sjoin(b'undo')
798 if not os.path.exists(undo):
798 if not os.path.exists(undo):
799 return
799 return
800 try:
800 try:
801 os.unlink(undo)
801 os.unlink(undo)
802 except OSError as inst:
802 except OSError as inst:
803 self.ui.warn(
803 self.ui.warn(
804 _(b'error removing undo: %s\n') % stringutil.forcebytestr(inst)
804 _(b'error removing undo: %s\n') % stringutil.forcebytestr(inst)
805 )
805 )
806
806
807 def backup(self, repo, files, copy=False):
807 def backup(self, repo, files, copy=False):
808 # backup local changes in --force case
808 # backup local changes in --force case
809 for f in sorted(files):
809 for f in sorted(files):
810 absf = repo.wjoin(f)
810 absf = repo.wjoin(f)
811 if os.path.lexists(absf):
811 if os.path.lexists(absf):
812 absorig = scmutil.backuppath(self.ui, repo, f)
812 absorig = scmutil.backuppath(self.ui, repo, f)
813 self.ui.note(
813 self.ui.note(
814 _(b'saving current version of %s as %s\n')
814 _(b'saving current version of %s as %s\n')
815 % (f, os.path.relpath(absorig))
815 % (f, os.path.relpath(absorig))
816 )
816 )
817
817
818 if copy:
818 if copy:
819 util.copyfile(absf, absorig)
819 util.copyfile(absf, absorig)
820 else:
820 else:
821 util.rename(absf, absorig)
821 util.rename(absf, absorig)
822
822
823 def printdiff(
823 def printdiff(
824 self,
824 self,
825 repo,
825 repo,
826 diffopts,
826 diffopts,
827 node1,
827 node1,
828 node2=None,
828 node2=None,
829 files=None,
829 files=None,
830 fp=None,
830 fp=None,
831 changes=None,
831 changes=None,
832 opts=None,
832 opts=None,
833 ):
833 ):
834 if opts is None:
834 if opts is None:
835 opts = {}
835 opts = {}
836 stat = opts.get(b'stat')
836 stat = opts.get(b'stat')
837 m = scmutil.match(repo[node1], files, opts)
837 m = scmutil.match(repo[node1], files, opts)
838 logcmdutil.diffordiffstat(
838 logcmdutil.diffordiffstat(
839 self.ui, repo, diffopts, node1, node2, m, changes, stat, fp
839 self.ui, repo, diffopts, node1, node2, m, changes, stat, fp
840 )
840 )
841
841
842 def mergeone(self, repo, mergeq, head, patch, rev, diffopts):
842 def mergeone(self, repo, mergeq, head, patch, rev, diffopts):
843 # first try just applying the patch
843 # first try just applying the patch
844 (err, n) = self.apply(
844 (err, n) = self.apply(
845 repo, [patch], update_status=False, strict=True, merge=rev
845 repo, [patch], update_status=False, strict=True, merge=rev
846 )
846 )
847
847
848 if err == 0:
848 if err == 0:
849 return (err, n)
849 return (err, n)
850
850
851 if n is None:
851 if n is None:
852 raise error.Abort(_(b"apply failed for patch %s") % patch)
852 raise error.Abort(_(b"apply failed for patch %s") % patch)
853
853
854 self.ui.warn(_(b"patch didn't work out, merging %s\n") % patch)
854 self.ui.warn(_(b"patch didn't work out, merging %s\n") % patch)
855
855
856 # apply failed, strip away that rev and merge.
856 # apply failed, strip away that rev and merge.
857 hg.clean(repo, head)
857 hg.clean(repo, head)
858 strip(self.ui, repo, [n], update=False, backup=False)
858 strip(self.ui, repo, [n], update=False, backup=False)
859
859
860 ctx = repo[rev]
860 ctx = repo[rev]
861 ret = hg.merge(repo, rev)
861 ret = hg.merge(ctx)
862 if ret:
862 if ret:
863 raise error.Abort(_(b"update returned %d") % ret)
863 raise error.Abort(_(b"update returned %d") % ret)
864 n = newcommit(repo, None, ctx.description(), ctx.user(), force=True)
864 n = newcommit(repo, None, ctx.description(), ctx.user(), force=True)
865 if n is None:
865 if n is None:
866 raise error.Abort(_(b"repo commit failed"))
866 raise error.Abort(_(b"repo commit failed"))
867 try:
867 try:
868 ph = patchheader(mergeq.join(patch), self.plainmode)
868 ph = patchheader(mergeq.join(patch), self.plainmode)
869 except Exception:
869 except Exception:
870 raise error.Abort(_(b"unable to read %s") % patch)
870 raise error.Abort(_(b"unable to read %s") % patch)
871
871
872 diffopts = self.patchopts(diffopts, patch)
872 diffopts = self.patchopts(diffopts, patch)
873 patchf = self.opener(patch, b"w")
873 patchf = self.opener(patch, b"w")
874 comments = bytes(ph)
874 comments = bytes(ph)
875 if comments:
875 if comments:
876 patchf.write(comments)
876 patchf.write(comments)
877 self.printdiff(repo, diffopts, head, n, fp=patchf)
877 self.printdiff(repo, diffopts, head, n, fp=patchf)
878 patchf.close()
878 patchf.close()
879 self.removeundo(repo)
879 self.removeundo(repo)
880 return (0, n)
880 return (0, n)
881
881
882 def qparents(self, repo, rev=None):
882 def qparents(self, repo, rev=None):
883 """return the mq handled parent or p1
883 """return the mq handled parent or p1
884
884
885 In some case where mq get himself in being the parent of a merge the
885 In some case where mq get himself in being the parent of a merge the
886 appropriate parent may be p2.
886 appropriate parent may be p2.
887 (eg: an in progress merge started with mq disabled)
887 (eg: an in progress merge started with mq disabled)
888
888
889 If no parent are managed by mq, p1 is returned.
889 If no parent are managed by mq, p1 is returned.
890 """
890 """
891 if rev is None:
891 if rev is None:
892 (p1, p2) = repo.dirstate.parents()
892 (p1, p2) = repo.dirstate.parents()
893 if p2 == nullid:
893 if p2 == nullid:
894 return p1
894 return p1
895 if not self.applied:
895 if not self.applied:
896 return None
896 return None
897 return self.applied[-1].node
897 return self.applied[-1].node
898 p1, p2 = repo.changelog.parents(rev)
898 p1, p2 = repo.changelog.parents(rev)
899 if p2 != nullid and p2 in [x.node for x in self.applied]:
899 if p2 != nullid and p2 in [x.node for x in self.applied]:
900 return p2
900 return p2
901 return p1
901 return p1
902
902
903 def mergepatch(self, repo, mergeq, series, diffopts):
903 def mergepatch(self, repo, mergeq, series, diffopts):
904 if not self.applied:
904 if not self.applied:
905 # each of the patches merged in will have two parents. This
905 # each of the patches merged in will have two parents. This
906 # can confuse the qrefresh, qdiff, and strip code because it
906 # can confuse the qrefresh, qdiff, and strip code because it
907 # needs to know which parent is actually in the patch queue.
907 # needs to know which parent is actually in the patch queue.
908 # so, we insert a merge marker with only one parent. This way
908 # so, we insert a merge marker with only one parent. This way
909 # the first patch in the queue is never a merge patch
909 # the first patch in the queue is never a merge patch
910 #
910 #
911 pname = b".hg.patches.merge.marker"
911 pname = b".hg.patches.merge.marker"
912 n = newcommit(repo, None, b'[mq]: merge marker', force=True)
912 n = newcommit(repo, None, b'[mq]: merge marker', force=True)
913 self.removeundo(repo)
913 self.removeundo(repo)
914 self.applied.append(statusentry(n, pname))
914 self.applied.append(statusentry(n, pname))
915 self.applieddirty = True
915 self.applieddirty = True
916
916
917 head = self.qparents(repo)
917 head = self.qparents(repo)
918
918
919 for patch in series:
919 for patch in series:
920 patch = mergeq.lookup(patch, strict=True)
920 patch = mergeq.lookup(patch, strict=True)
921 if not patch:
921 if not patch:
922 self.ui.warn(_(b"patch %s does not exist\n") % patch)
922 self.ui.warn(_(b"patch %s does not exist\n") % patch)
923 return (1, None)
923 return (1, None)
924 pushable, reason = self.pushable(patch)
924 pushable, reason = self.pushable(patch)
925 if not pushable:
925 if not pushable:
926 self.explainpushable(patch, all_patches=True)
926 self.explainpushable(patch, all_patches=True)
927 continue
927 continue
928 info = mergeq.isapplied(patch)
928 info = mergeq.isapplied(patch)
929 if not info:
929 if not info:
930 self.ui.warn(_(b"patch %s is not applied\n") % patch)
930 self.ui.warn(_(b"patch %s is not applied\n") % patch)
931 return (1, None)
931 return (1, None)
932 rev = info[1]
932 rev = info[1]
933 err, head = self.mergeone(repo, mergeq, head, patch, rev, diffopts)
933 err, head = self.mergeone(repo, mergeq, head, patch, rev, diffopts)
934 if head:
934 if head:
935 self.applied.append(statusentry(head, patch))
935 self.applied.append(statusentry(head, patch))
936 self.applieddirty = True
936 self.applieddirty = True
937 if err:
937 if err:
938 return (err, head)
938 return (err, head)
939 self.savedirty()
939 self.savedirty()
940 return (0, head)
940 return (0, head)
941
941
942 def patch(self, repo, patchfile):
942 def patch(self, repo, patchfile):
943 '''Apply patchfile to the working directory.
943 '''Apply patchfile to the working directory.
944 patchfile: name of patch file'''
944 patchfile: name of patch file'''
945 files = set()
945 files = set()
946 try:
946 try:
947 fuzz = patchmod.patch(
947 fuzz = patchmod.patch(
948 self.ui, repo, patchfile, strip=1, files=files, eolmode=None
948 self.ui, repo, patchfile, strip=1, files=files, eolmode=None
949 )
949 )
950 return (True, list(files), fuzz)
950 return (True, list(files), fuzz)
951 except Exception as inst:
951 except Exception as inst:
952 self.ui.note(stringutil.forcebytestr(inst) + b'\n')
952 self.ui.note(stringutil.forcebytestr(inst) + b'\n')
953 if not self.ui.verbose:
953 if not self.ui.verbose:
954 self.ui.warn(_(b"patch failed, unable to continue (try -v)\n"))
954 self.ui.warn(_(b"patch failed, unable to continue (try -v)\n"))
955 self.ui.traceback()
955 self.ui.traceback()
956 return (False, list(files), False)
956 return (False, list(files), False)
957
957
958 def apply(
958 def apply(
959 self,
959 self,
960 repo,
960 repo,
961 series,
961 series,
962 list=False,
962 list=False,
963 update_status=True,
963 update_status=True,
964 strict=False,
964 strict=False,
965 patchdir=None,
965 patchdir=None,
966 merge=None,
966 merge=None,
967 all_files=None,
967 all_files=None,
968 tobackup=None,
968 tobackup=None,
969 keepchanges=False,
969 keepchanges=False,
970 ):
970 ):
971 wlock = lock = tr = None
971 wlock = lock = tr = None
972 try:
972 try:
973 wlock = repo.wlock()
973 wlock = repo.wlock()
974 lock = repo.lock()
974 lock = repo.lock()
975 tr = repo.transaction(b"qpush")
975 tr = repo.transaction(b"qpush")
976 try:
976 try:
977 ret = self._apply(
977 ret = self._apply(
978 repo,
978 repo,
979 series,
979 series,
980 list,
980 list,
981 update_status,
981 update_status,
982 strict,
982 strict,
983 patchdir,
983 patchdir,
984 merge,
984 merge,
985 all_files=all_files,
985 all_files=all_files,
986 tobackup=tobackup,
986 tobackup=tobackup,
987 keepchanges=keepchanges,
987 keepchanges=keepchanges,
988 )
988 )
989 tr.close()
989 tr.close()
990 self.savedirty()
990 self.savedirty()
991 return ret
991 return ret
992 except AbortNoCleanup:
992 except AbortNoCleanup:
993 tr.close()
993 tr.close()
994 self.savedirty()
994 self.savedirty()
995 raise
995 raise
996 except: # re-raises
996 except: # re-raises
997 try:
997 try:
998 tr.abort()
998 tr.abort()
999 finally:
999 finally:
1000 self.invalidate()
1000 self.invalidate()
1001 raise
1001 raise
1002 finally:
1002 finally:
1003 release(tr, lock, wlock)
1003 release(tr, lock, wlock)
1004 self.removeundo(repo)
1004 self.removeundo(repo)
1005
1005
1006 def _apply(
1006 def _apply(
1007 self,
1007 self,
1008 repo,
1008 repo,
1009 series,
1009 series,
1010 list=False,
1010 list=False,
1011 update_status=True,
1011 update_status=True,
1012 strict=False,
1012 strict=False,
1013 patchdir=None,
1013 patchdir=None,
1014 merge=None,
1014 merge=None,
1015 all_files=None,
1015 all_files=None,
1016 tobackup=None,
1016 tobackup=None,
1017 keepchanges=False,
1017 keepchanges=False,
1018 ):
1018 ):
1019 """returns (error, hash)
1019 """returns (error, hash)
1020
1020
1021 error = 1 for unable to read, 2 for patch failed, 3 for patch
1021 error = 1 for unable to read, 2 for patch failed, 3 for patch
1022 fuzz. tobackup is None or a set of files to backup before they
1022 fuzz. tobackup is None or a set of files to backup before they
1023 are modified by a patch.
1023 are modified by a patch.
1024 """
1024 """
1025 # TODO unify with commands.py
1025 # TODO unify with commands.py
1026 if not patchdir:
1026 if not patchdir:
1027 patchdir = self.path
1027 patchdir = self.path
1028 err = 0
1028 err = 0
1029 n = None
1029 n = None
1030 for patchname in series:
1030 for patchname in series:
1031 pushable, reason = self.pushable(patchname)
1031 pushable, reason = self.pushable(patchname)
1032 if not pushable:
1032 if not pushable:
1033 self.explainpushable(patchname, all_patches=True)
1033 self.explainpushable(patchname, all_patches=True)
1034 continue
1034 continue
1035 self.ui.status(_(b"applying %s\n") % patchname)
1035 self.ui.status(_(b"applying %s\n") % patchname)
1036 pf = os.path.join(patchdir, patchname)
1036 pf = os.path.join(patchdir, patchname)
1037
1037
1038 try:
1038 try:
1039 ph = patchheader(self.join(patchname), self.plainmode)
1039 ph = patchheader(self.join(patchname), self.plainmode)
1040 except IOError:
1040 except IOError:
1041 self.ui.warn(_(b"unable to read %s\n") % patchname)
1041 self.ui.warn(_(b"unable to read %s\n") % patchname)
1042 err = 1
1042 err = 1
1043 break
1043 break
1044
1044
1045 message = ph.message
1045 message = ph.message
1046 if not message:
1046 if not message:
1047 # The commit message should not be translated
1047 # The commit message should not be translated
1048 message = b"imported patch %s\n" % patchname
1048 message = b"imported patch %s\n" % patchname
1049 else:
1049 else:
1050 if list:
1050 if list:
1051 # The commit message should not be translated
1051 # The commit message should not be translated
1052 message.append(b"\nimported patch %s" % patchname)
1052 message.append(b"\nimported patch %s" % patchname)
1053 message = b'\n'.join(message)
1053 message = b'\n'.join(message)
1054
1054
1055 if ph.haspatch:
1055 if ph.haspatch:
1056 if tobackup:
1056 if tobackup:
1057 touched = patchmod.changedfiles(self.ui, repo, pf)
1057 touched = patchmod.changedfiles(self.ui, repo, pf)
1058 touched = set(touched) & tobackup
1058 touched = set(touched) & tobackup
1059 if touched and keepchanges:
1059 if touched and keepchanges:
1060 raise AbortNoCleanup(
1060 raise AbortNoCleanup(
1061 _(b"conflicting local changes found"),
1061 _(b"conflicting local changes found"),
1062 hint=_(b"did you forget to qrefresh?"),
1062 hint=_(b"did you forget to qrefresh?"),
1063 )
1063 )
1064 self.backup(repo, touched, copy=True)
1064 self.backup(repo, touched, copy=True)
1065 tobackup = tobackup - touched
1065 tobackup = tobackup - touched
1066 (patcherr, files, fuzz) = self.patch(repo, pf)
1066 (patcherr, files, fuzz) = self.patch(repo, pf)
1067 if all_files is not None:
1067 if all_files is not None:
1068 all_files.update(files)
1068 all_files.update(files)
1069 patcherr = not patcherr
1069 patcherr = not patcherr
1070 else:
1070 else:
1071 self.ui.warn(_(b"patch %s is empty\n") % patchname)
1071 self.ui.warn(_(b"patch %s is empty\n") % patchname)
1072 patcherr, files, fuzz = 0, [], 0
1072 patcherr, files, fuzz = 0, [], 0
1073
1073
1074 if merge and files:
1074 if merge and files:
1075 # Mark as removed/merged and update dirstate parent info
1075 # Mark as removed/merged and update dirstate parent info
1076 removed = []
1076 removed = []
1077 merged = []
1077 merged = []
1078 for f in files:
1078 for f in files:
1079 if os.path.lexists(repo.wjoin(f)):
1079 if os.path.lexists(repo.wjoin(f)):
1080 merged.append(f)
1080 merged.append(f)
1081 else:
1081 else:
1082 removed.append(f)
1082 removed.append(f)
1083 with repo.dirstate.parentchange():
1083 with repo.dirstate.parentchange():
1084 for f in removed:
1084 for f in removed:
1085 repo.dirstate.remove(f)
1085 repo.dirstate.remove(f)
1086 for f in merged:
1086 for f in merged:
1087 repo.dirstate.merge(f)
1087 repo.dirstate.merge(f)
1088 p1 = repo.dirstate.p1()
1088 p1 = repo.dirstate.p1()
1089 repo.setparents(p1, merge)
1089 repo.setparents(p1, merge)
1090
1090
1091 if all_files and b'.hgsubstate' in all_files:
1091 if all_files and b'.hgsubstate' in all_files:
1092 wctx = repo[None]
1092 wctx = repo[None]
1093 pctx = repo[b'.']
1093 pctx = repo[b'.']
1094 overwrite = False
1094 overwrite = False
1095 mergedsubstate = subrepoutil.submerge(
1095 mergedsubstate = subrepoutil.submerge(
1096 repo, pctx, wctx, wctx, overwrite
1096 repo, pctx, wctx, wctx, overwrite
1097 )
1097 )
1098 files += mergedsubstate.keys()
1098 files += mergedsubstate.keys()
1099
1099
1100 match = scmutil.matchfiles(repo, files or [])
1100 match = scmutil.matchfiles(repo, files or [])
1101 oldtip = repo.changelog.tip()
1101 oldtip = repo.changelog.tip()
1102 n = newcommit(
1102 n = newcommit(
1103 repo, None, message, ph.user, ph.date, match=match, force=True
1103 repo, None, message, ph.user, ph.date, match=match, force=True
1104 )
1104 )
1105 if repo.changelog.tip() == oldtip:
1105 if repo.changelog.tip() == oldtip:
1106 raise error.Abort(
1106 raise error.Abort(
1107 _(b"qpush exactly duplicates child changeset")
1107 _(b"qpush exactly duplicates child changeset")
1108 )
1108 )
1109 if n is None:
1109 if n is None:
1110 raise error.Abort(_(b"repository commit failed"))
1110 raise error.Abort(_(b"repository commit failed"))
1111
1111
1112 if update_status:
1112 if update_status:
1113 self.applied.append(statusentry(n, patchname))
1113 self.applied.append(statusentry(n, patchname))
1114
1114
1115 if patcherr:
1115 if patcherr:
1116 self.ui.warn(
1116 self.ui.warn(
1117 _(b"patch failed, rejects left in working directory\n")
1117 _(b"patch failed, rejects left in working directory\n")
1118 )
1118 )
1119 err = 2
1119 err = 2
1120 break
1120 break
1121
1121
1122 if fuzz and strict:
1122 if fuzz and strict:
1123 self.ui.warn(_(b"fuzz found when applying patch, stopping\n"))
1123 self.ui.warn(_(b"fuzz found when applying patch, stopping\n"))
1124 err = 3
1124 err = 3
1125 break
1125 break
1126 return (err, n)
1126 return (err, n)
1127
1127
1128 def _cleanup(self, patches, numrevs, keep=False):
1128 def _cleanup(self, patches, numrevs, keep=False):
1129 if not keep:
1129 if not keep:
1130 r = self.qrepo()
1130 r = self.qrepo()
1131 if r:
1131 if r:
1132 r[None].forget(patches)
1132 r[None].forget(patches)
1133 for p in patches:
1133 for p in patches:
1134 try:
1134 try:
1135 os.unlink(self.join(p))
1135 os.unlink(self.join(p))
1136 except OSError as inst:
1136 except OSError as inst:
1137 if inst.errno != errno.ENOENT:
1137 if inst.errno != errno.ENOENT:
1138 raise
1138 raise
1139
1139
1140 qfinished = []
1140 qfinished = []
1141 if numrevs:
1141 if numrevs:
1142 qfinished = self.applied[:numrevs]
1142 qfinished = self.applied[:numrevs]
1143 del self.applied[:numrevs]
1143 del self.applied[:numrevs]
1144 self.applieddirty = True
1144 self.applieddirty = True
1145
1145
1146 unknown = []
1146 unknown = []
1147
1147
1148 sortedseries = []
1148 sortedseries = []
1149 for p in patches:
1149 for p in patches:
1150 idx = self.findseries(p)
1150 idx = self.findseries(p)
1151 if idx is None:
1151 if idx is None:
1152 sortedseries.append((-1, p))
1152 sortedseries.append((-1, p))
1153 else:
1153 else:
1154 sortedseries.append((idx, p))
1154 sortedseries.append((idx, p))
1155
1155
1156 sortedseries.sort(reverse=True)
1156 sortedseries.sort(reverse=True)
1157 for (i, p) in sortedseries:
1157 for (i, p) in sortedseries:
1158 if i != -1:
1158 if i != -1:
1159 del self.fullseries[i]
1159 del self.fullseries[i]
1160 else:
1160 else:
1161 unknown.append(p)
1161 unknown.append(p)
1162
1162
1163 if unknown:
1163 if unknown:
1164 if numrevs:
1164 if numrevs:
1165 rev = dict((entry.name, entry.node) for entry in qfinished)
1165 rev = dict((entry.name, entry.node) for entry in qfinished)
1166 for p in unknown:
1166 for p in unknown:
1167 msg = _(b'revision %s refers to unknown patches: %s\n')
1167 msg = _(b'revision %s refers to unknown patches: %s\n')
1168 self.ui.warn(msg % (short(rev[p]), p))
1168 self.ui.warn(msg % (short(rev[p]), p))
1169 else:
1169 else:
1170 msg = _(b'unknown patches: %s\n')
1170 msg = _(b'unknown patches: %s\n')
1171 raise error.Abort(b''.join(msg % p for p in unknown))
1171 raise error.Abort(b''.join(msg % p for p in unknown))
1172
1172
1173 self.parseseries()
1173 self.parseseries()
1174 self.seriesdirty = True
1174 self.seriesdirty = True
1175 return [entry.node for entry in qfinished]
1175 return [entry.node for entry in qfinished]
1176
1176
1177 def _revpatches(self, repo, revs):
1177 def _revpatches(self, repo, revs):
1178 firstrev = repo[self.applied[0].node].rev()
1178 firstrev = repo[self.applied[0].node].rev()
1179 patches = []
1179 patches = []
1180 for i, rev in enumerate(revs):
1180 for i, rev in enumerate(revs):
1181
1181
1182 if rev < firstrev:
1182 if rev < firstrev:
1183 raise error.Abort(_(b'revision %d is not managed') % rev)
1183 raise error.Abort(_(b'revision %d is not managed') % rev)
1184
1184
1185 ctx = repo[rev]
1185 ctx = repo[rev]
1186 base = self.applied[i].node
1186 base = self.applied[i].node
1187 if ctx.node() != base:
1187 if ctx.node() != base:
1188 msg = _(b'cannot delete revision %d above applied patches')
1188 msg = _(b'cannot delete revision %d above applied patches')
1189 raise error.Abort(msg % rev)
1189 raise error.Abort(msg % rev)
1190
1190
1191 patch = self.applied[i].name
1191 patch = self.applied[i].name
1192 for fmt in (b'[mq]: %s', b'imported patch %s'):
1192 for fmt in (b'[mq]: %s', b'imported patch %s'):
1193 if ctx.description() == fmt % patch:
1193 if ctx.description() == fmt % patch:
1194 msg = _(b'patch %s finalized without changeset message\n')
1194 msg = _(b'patch %s finalized without changeset message\n')
1195 repo.ui.status(msg % patch)
1195 repo.ui.status(msg % patch)
1196 break
1196 break
1197
1197
1198 patches.append(patch)
1198 patches.append(patch)
1199 return patches
1199 return patches
1200
1200
1201 def finish(self, repo, revs):
1201 def finish(self, repo, revs):
1202 # Manually trigger phase computation to ensure phasedefaults is
1202 # Manually trigger phase computation to ensure phasedefaults is
1203 # executed before we remove the patches.
1203 # executed before we remove the patches.
1204 repo._phasecache
1204 repo._phasecache
1205 patches = self._revpatches(repo, sorted(revs))
1205 patches = self._revpatches(repo, sorted(revs))
1206 qfinished = self._cleanup(patches, len(patches))
1206 qfinished = self._cleanup(patches, len(patches))
1207 if qfinished and repo.ui.configbool(b'mq', b'secret'):
1207 if qfinished and repo.ui.configbool(b'mq', b'secret'):
1208 # only use this logic when the secret option is added
1208 # only use this logic when the secret option is added
1209 oldqbase = repo[qfinished[0]]
1209 oldqbase = repo[qfinished[0]]
1210 tphase = phases.newcommitphase(repo.ui)
1210 tphase = phases.newcommitphase(repo.ui)
1211 if oldqbase.phase() > tphase and oldqbase.p1().phase() <= tphase:
1211 if oldqbase.phase() > tphase and oldqbase.p1().phase() <= tphase:
1212 with repo.transaction(b'qfinish') as tr:
1212 with repo.transaction(b'qfinish') as tr:
1213 phases.advanceboundary(repo, tr, tphase, qfinished)
1213 phases.advanceboundary(repo, tr, tphase, qfinished)
1214
1214
1215 def delete(self, repo, patches, opts):
1215 def delete(self, repo, patches, opts):
1216 if not patches and not opts.get(b'rev'):
1216 if not patches and not opts.get(b'rev'):
1217 raise error.Abort(
1217 raise error.Abort(
1218 _(b'qdelete requires at least one revision or patch name')
1218 _(b'qdelete requires at least one revision or patch name')
1219 )
1219 )
1220
1220
1221 realpatches = []
1221 realpatches = []
1222 for patch in patches:
1222 for patch in patches:
1223 patch = self.lookup(patch, strict=True)
1223 patch = self.lookup(patch, strict=True)
1224 info = self.isapplied(patch)
1224 info = self.isapplied(patch)
1225 if info:
1225 if info:
1226 raise error.Abort(_(b"cannot delete applied patch %s") % patch)
1226 raise error.Abort(_(b"cannot delete applied patch %s") % patch)
1227 if patch not in self.series:
1227 if patch not in self.series:
1228 raise error.Abort(_(b"patch %s not in series file") % patch)
1228 raise error.Abort(_(b"patch %s not in series file") % patch)
1229 if patch not in realpatches:
1229 if patch not in realpatches:
1230 realpatches.append(patch)
1230 realpatches.append(patch)
1231
1231
1232 numrevs = 0
1232 numrevs = 0
1233 if opts.get(b'rev'):
1233 if opts.get(b'rev'):
1234 if not self.applied:
1234 if not self.applied:
1235 raise error.Abort(_(b'no patches applied'))
1235 raise error.Abort(_(b'no patches applied'))
1236 revs = scmutil.revrange(repo, opts.get(b'rev'))
1236 revs = scmutil.revrange(repo, opts.get(b'rev'))
1237 revs.sort()
1237 revs.sort()
1238 revpatches = self._revpatches(repo, revs)
1238 revpatches = self._revpatches(repo, revs)
1239 realpatches += revpatches
1239 realpatches += revpatches
1240 numrevs = len(revpatches)
1240 numrevs = len(revpatches)
1241
1241
1242 self._cleanup(realpatches, numrevs, opts.get(b'keep'))
1242 self._cleanup(realpatches, numrevs, opts.get(b'keep'))
1243
1243
1244 def checktoppatch(self, repo):
1244 def checktoppatch(self, repo):
1245 '''check that working directory is at qtip'''
1245 '''check that working directory is at qtip'''
1246 if self.applied:
1246 if self.applied:
1247 top = self.applied[-1].node
1247 top = self.applied[-1].node
1248 patch = self.applied[-1].name
1248 patch = self.applied[-1].name
1249 if repo.dirstate.p1() != top:
1249 if repo.dirstate.p1() != top:
1250 raise error.Abort(_(b"working directory revision is not qtip"))
1250 raise error.Abort(_(b"working directory revision is not qtip"))
1251 return top, patch
1251 return top, patch
1252 return None, None
1252 return None, None
1253
1253
1254 def putsubstate2changes(self, substatestate, changes):
1254 def putsubstate2changes(self, substatestate, changes):
1255 if isinstance(changes, list):
1255 if isinstance(changes, list):
1256 mar = changes[:3]
1256 mar = changes[:3]
1257 else:
1257 else:
1258 mar = (changes.modified, changes.added, changes.removed)
1258 mar = (changes.modified, changes.added, changes.removed)
1259 if any((b'.hgsubstate' in files for files in mar)):
1259 if any((b'.hgsubstate' in files for files in mar)):
1260 return # already listed up
1260 return # already listed up
1261 # not yet listed up
1261 # not yet listed up
1262 if substatestate in b'a?':
1262 if substatestate in b'a?':
1263 mar[1].append(b'.hgsubstate')
1263 mar[1].append(b'.hgsubstate')
1264 elif substatestate in b'r':
1264 elif substatestate in b'r':
1265 mar[2].append(b'.hgsubstate')
1265 mar[2].append(b'.hgsubstate')
1266 else: # modified
1266 else: # modified
1267 mar[0].append(b'.hgsubstate')
1267 mar[0].append(b'.hgsubstate')
1268
1268
1269 def checklocalchanges(self, repo, force=False, refresh=True):
1269 def checklocalchanges(self, repo, force=False, refresh=True):
1270 excsuffix = b''
1270 excsuffix = b''
1271 if refresh:
1271 if refresh:
1272 excsuffix = b', qrefresh first'
1272 excsuffix = b', qrefresh first'
1273 # plain versions for i18n tool to detect them
1273 # plain versions for i18n tool to detect them
1274 _(b"local changes found, qrefresh first")
1274 _(b"local changes found, qrefresh first")
1275 _(b"local changed subrepos found, qrefresh first")
1275 _(b"local changed subrepos found, qrefresh first")
1276
1276
1277 s = repo.status()
1277 s = repo.status()
1278 if not force:
1278 if not force:
1279 cmdutil.checkunfinished(repo)
1279 cmdutil.checkunfinished(repo)
1280 if s.modified or s.added or s.removed or s.deleted:
1280 if s.modified or s.added or s.removed or s.deleted:
1281 _(b"local changes found") # i18n tool detection
1281 _(b"local changes found") # i18n tool detection
1282 raise error.Abort(_(b"local changes found" + excsuffix))
1282 raise error.Abort(_(b"local changes found" + excsuffix))
1283 if checksubstate(repo):
1283 if checksubstate(repo):
1284 _(b"local changed subrepos found") # i18n tool detection
1284 _(b"local changed subrepos found") # i18n tool detection
1285 raise error.Abort(
1285 raise error.Abort(
1286 _(b"local changed subrepos found" + excsuffix)
1286 _(b"local changed subrepos found" + excsuffix)
1287 )
1287 )
1288 else:
1288 else:
1289 cmdutil.checkunfinished(repo, skipmerge=True)
1289 cmdutil.checkunfinished(repo, skipmerge=True)
1290 return s
1290 return s
1291
1291
1292 _reserved = (b'series', b'status', b'guards', b'.', b'..')
1292 _reserved = (b'series', b'status', b'guards', b'.', b'..')
1293
1293
1294 def checkreservedname(self, name):
1294 def checkreservedname(self, name):
1295 if name in self._reserved:
1295 if name in self._reserved:
1296 raise error.Abort(
1296 raise error.Abort(
1297 _(b'"%s" cannot be used as the name of a patch') % name
1297 _(b'"%s" cannot be used as the name of a patch') % name
1298 )
1298 )
1299 if name != name.strip():
1299 if name != name.strip():
1300 # whitespace is stripped by parseseries()
1300 # whitespace is stripped by parseseries()
1301 raise error.Abort(
1301 raise error.Abort(
1302 _(b'patch name cannot begin or end with whitespace')
1302 _(b'patch name cannot begin or end with whitespace')
1303 )
1303 )
1304 for prefix in (b'.hg', b'.mq'):
1304 for prefix in (b'.hg', b'.mq'):
1305 if name.startswith(prefix):
1305 if name.startswith(prefix):
1306 raise error.Abort(
1306 raise error.Abort(
1307 _(b'patch name cannot begin with "%s"') % prefix
1307 _(b'patch name cannot begin with "%s"') % prefix
1308 )
1308 )
1309 for c in (b'#', b':', b'\r', b'\n'):
1309 for c in (b'#', b':', b'\r', b'\n'):
1310 if c in name:
1310 if c in name:
1311 raise error.Abort(
1311 raise error.Abort(
1312 _(b'%r cannot be used in the name of a patch')
1312 _(b'%r cannot be used in the name of a patch')
1313 % pycompat.bytestr(c)
1313 % pycompat.bytestr(c)
1314 )
1314 )
1315
1315
1316 def checkpatchname(self, name, force=False):
1316 def checkpatchname(self, name, force=False):
1317 self.checkreservedname(name)
1317 self.checkreservedname(name)
1318 if not force and os.path.exists(self.join(name)):
1318 if not force and os.path.exists(self.join(name)):
1319 if os.path.isdir(self.join(name)):
1319 if os.path.isdir(self.join(name)):
1320 raise error.Abort(
1320 raise error.Abort(
1321 _(b'"%s" already exists as a directory') % name
1321 _(b'"%s" already exists as a directory') % name
1322 )
1322 )
1323 else:
1323 else:
1324 raise error.Abort(_(b'patch "%s" already exists') % name)
1324 raise error.Abort(_(b'patch "%s" already exists') % name)
1325
1325
1326 def makepatchname(self, title, fallbackname):
1326 def makepatchname(self, title, fallbackname):
1327 """Return a suitable filename for title, adding a suffix to make
1327 """Return a suitable filename for title, adding a suffix to make
1328 it unique in the existing list"""
1328 it unique in the existing list"""
1329 namebase = re.sub(br'[\s\W_]+', b'_', title.lower()).strip(b'_')
1329 namebase = re.sub(br'[\s\W_]+', b'_', title.lower()).strip(b'_')
1330 namebase = namebase[:75] # avoid too long name (issue5117)
1330 namebase = namebase[:75] # avoid too long name (issue5117)
1331 if namebase:
1331 if namebase:
1332 try:
1332 try:
1333 self.checkreservedname(namebase)
1333 self.checkreservedname(namebase)
1334 except error.Abort:
1334 except error.Abort:
1335 namebase = fallbackname
1335 namebase = fallbackname
1336 else:
1336 else:
1337 namebase = fallbackname
1337 namebase = fallbackname
1338 name = namebase
1338 name = namebase
1339 i = 0
1339 i = 0
1340 while True:
1340 while True:
1341 if name not in self.fullseries:
1341 if name not in self.fullseries:
1342 try:
1342 try:
1343 self.checkpatchname(name)
1343 self.checkpatchname(name)
1344 break
1344 break
1345 except error.Abort:
1345 except error.Abort:
1346 pass
1346 pass
1347 i += 1
1347 i += 1
1348 name = b'%s__%d' % (namebase, i)
1348 name = b'%s__%d' % (namebase, i)
1349 return name
1349 return name
1350
1350
1351 def checkkeepchanges(self, keepchanges, force):
1351 def checkkeepchanges(self, keepchanges, force):
1352 if force and keepchanges:
1352 if force and keepchanges:
1353 raise error.Abort(_(b'cannot use both --force and --keep-changes'))
1353 raise error.Abort(_(b'cannot use both --force and --keep-changes'))
1354
1354
1355 def new(self, repo, patchfn, *pats, **opts):
1355 def new(self, repo, patchfn, *pats, **opts):
1356 """options:
1356 """options:
1357 msg: a string or a no-argument function returning a string
1357 msg: a string or a no-argument function returning a string
1358 """
1358 """
1359 opts = pycompat.byteskwargs(opts)
1359 opts = pycompat.byteskwargs(opts)
1360 msg = opts.get(b'msg')
1360 msg = opts.get(b'msg')
1361 edit = opts.get(b'edit')
1361 edit = opts.get(b'edit')
1362 editform = opts.get(b'editform', b'mq.qnew')
1362 editform = opts.get(b'editform', b'mq.qnew')
1363 user = opts.get(b'user')
1363 user = opts.get(b'user')
1364 date = opts.get(b'date')
1364 date = opts.get(b'date')
1365 if date:
1365 if date:
1366 date = dateutil.parsedate(date)
1366 date = dateutil.parsedate(date)
1367 diffopts = self.diffopts({b'git': opts.get(b'git')}, plain=True)
1367 diffopts = self.diffopts({b'git': opts.get(b'git')}, plain=True)
1368 if opts.get(b'checkname', True):
1368 if opts.get(b'checkname', True):
1369 self.checkpatchname(patchfn)
1369 self.checkpatchname(patchfn)
1370 inclsubs = checksubstate(repo)
1370 inclsubs = checksubstate(repo)
1371 if inclsubs:
1371 if inclsubs:
1372 substatestate = repo.dirstate[b'.hgsubstate']
1372 substatestate = repo.dirstate[b'.hgsubstate']
1373 if opts.get(b'include') or opts.get(b'exclude') or pats:
1373 if opts.get(b'include') or opts.get(b'exclude') or pats:
1374 # detect missing files in pats
1374 # detect missing files in pats
1375 def badfn(f, msg):
1375 def badfn(f, msg):
1376 if f != b'.hgsubstate': # .hgsubstate is auto-created
1376 if f != b'.hgsubstate': # .hgsubstate is auto-created
1377 raise error.Abort(b'%s: %s' % (f, msg))
1377 raise error.Abort(b'%s: %s' % (f, msg))
1378
1378
1379 match = scmutil.match(repo[None], pats, opts, badfn=badfn)
1379 match = scmutil.match(repo[None], pats, opts, badfn=badfn)
1380 changes = repo.status(match=match)
1380 changes = repo.status(match=match)
1381 else:
1381 else:
1382 changes = self.checklocalchanges(repo, force=True)
1382 changes = self.checklocalchanges(repo, force=True)
1383 commitfiles = list(inclsubs)
1383 commitfiles = list(inclsubs)
1384 commitfiles.extend(changes.modified)
1384 commitfiles.extend(changes.modified)
1385 commitfiles.extend(changes.added)
1385 commitfiles.extend(changes.added)
1386 commitfiles.extend(changes.removed)
1386 commitfiles.extend(changes.removed)
1387 match = scmutil.matchfiles(repo, commitfiles)
1387 match = scmutil.matchfiles(repo, commitfiles)
1388 if len(repo[None].parents()) > 1:
1388 if len(repo[None].parents()) > 1:
1389 raise error.Abort(_(b'cannot manage merge changesets'))
1389 raise error.Abort(_(b'cannot manage merge changesets'))
1390 self.checktoppatch(repo)
1390 self.checktoppatch(repo)
1391 insert = self.fullseriesend()
1391 insert = self.fullseriesend()
1392 with repo.wlock():
1392 with repo.wlock():
1393 try:
1393 try:
1394 # if patch file write fails, abort early
1394 # if patch file write fails, abort early
1395 p = self.opener(patchfn, b"w")
1395 p = self.opener(patchfn, b"w")
1396 except IOError as e:
1396 except IOError as e:
1397 raise error.Abort(
1397 raise error.Abort(
1398 _(b'cannot write patch "%s": %s')
1398 _(b'cannot write patch "%s": %s')
1399 % (patchfn, encoding.strtolocal(e.strerror))
1399 % (patchfn, encoding.strtolocal(e.strerror))
1400 )
1400 )
1401 try:
1401 try:
1402 defaultmsg = b"[mq]: %s" % patchfn
1402 defaultmsg = b"[mq]: %s" % patchfn
1403 editor = cmdutil.getcommiteditor(editform=editform)
1403 editor = cmdutil.getcommiteditor(editform=editform)
1404 if edit:
1404 if edit:
1405
1405
1406 def finishdesc(desc):
1406 def finishdesc(desc):
1407 if desc.rstrip():
1407 if desc.rstrip():
1408 return desc
1408 return desc
1409 else:
1409 else:
1410 return defaultmsg
1410 return defaultmsg
1411
1411
1412 # i18n: this message is shown in editor with "HG: " prefix
1412 # i18n: this message is shown in editor with "HG: " prefix
1413 extramsg = _(b'Leave message empty to use default message.')
1413 extramsg = _(b'Leave message empty to use default message.')
1414 editor = cmdutil.getcommiteditor(
1414 editor = cmdutil.getcommiteditor(
1415 finishdesc=finishdesc,
1415 finishdesc=finishdesc,
1416 extramsg=extramsg,
1416 extramsg=extramsg,
1417 editform=editform,
1417 editform=editform,
1418 )
1418 )
1419 commitmsg = msg
1419 commitmsg = msg
1420 else:
1420 else:
1421 commitmsg = msg or defaultmsg
1421 commitmsg = msg or defaultmsg
1422
1422
1423 n = newcommit(
1423 n = newcommit(
1424 repo,
1424 repo,
1425 None,
1425 None,
1426 commitmsg,
1426 commitmsg,
1427 user,
1427 user,
1428 date,
1428 date,
1429 match=match,
1429 match=match,
1430 force=True,
1430 force=True,
1431 editor=editor,
1431 editor=editor,
1432 )
1432 )
1433 if n is None:
1433 if n is None:
1434 raise error.Abort(_(b"repo commit failed"))
1434 raise error.Abort(_(b"repo commit failed"))
1435 try:
1435 try:
1436 self.fullseries[insert:insert] = [patchfn]
1436 self.fullseries[insert:insert] = [patchfn]
1437 self.applied.append(statusentry(n, patchfn))
1437 self.applied.append(statusentry(n, patchfn))
1438 self.parseseries()
1438 self.parseseries()
1439 self.seriesdirty = True
1439 self.seriesdirty = True
1440 self.applieddirty = True
1440 self.applieddirty = True
1441 nctx = repo[n]
1441 nctx = repo[n]
1442 ph = patchheader(self.join(patchfn), self.plainmode)
1442 ph = patchheader(self.join(patchfn), self.plainmode)
1443 if user:
1443 if user:
1444 ph.setuser(user)
1444 ph.setuser(user)
1445 if date:
1445 if date:
1446 ph.setdate(b'%d %d' % date)
1446 ph.setdate(b'%d %d' % date)
1447 ph.setparent(hex(nctx.p1().node()))
1447 ph.setparent(hex(nctx.p1().node()))
1448 msg = nctx.description().strip()
1448 msg = nctx.description().strip()
1449 if msg == defaultmsg.strip():
1449 if msg == defaultmsg.strip():
1450 msg = b''
1450 msg = b''
1451 ph.setmessage(msg)
1451 ph.setmessage(msg)
1452 p.write(bytes(ph))
1452 p.write(bytes(ph))
1453 if commitfiles:
1453 if commitfiles:
1454 parent = self.qparents(repo, n)
1454 parent = self.qparents(repo, n)
1455 if inclsubs:
1455 if inclsubs:
1456 self.putsubstate2changes(substatestate, changes)
1456 self.putsubstate2changes(substatestate, changes)
1457 chunks = patchmod.diff(
1457 chunks = patchmod.diff(
1458 repo,
1458 repo,
1459 node1=parent,
1459 node1=parent,
1460 node2=n,
1460 node2=n,
1461 changes=changes,
1461 changes=changes,
1462 opts=diffopts,
1462 opts=diffopts,
1463 )
1463 )
1464 for chunk in chunks:
1464 for chunk in chunks:
1465 p.write(chunk)
1465 p.write(chunk)
1466 p.close()
1466 p.close()
1467 r = self.qrepo()
1467 r = self.qrepo()
1468 if r:
1468 if r:
1469 r[None].add([patchfn])
1469 r[None].add([patchfn])
1470 except: # re-raises
1470 except: # re-raises
1471 repo.rollback()
1471 repo.rollback()
1472 raise
1472 raise
1473 except Exception:
1473 except Exception:
1474 patchpath = self.join(patchfn)
1474 patchpath = self.join(patchfn)
1475 try:
1475 try:
1476 os.unlink(patchpath)
1476 os.unlink(patchpath)
1477 except OSError:
1477 except OSError:
1478 self.ui.warn(_(b'error unlinking %s\n') % patchpath)
1478 self.ui.warn(_(b'error unlinking %s\n') % patchpath)
1479 raise
1479 raise
1480 self.removeundo(repo)
1480 self.removeundo(repo)
1481
1481
1482 def isapplied(self, patch):
1482 def isapplied(self, patch):
1483 """returns (index, rev, patch)"""
1483 """returns (index, rev, patch)"""
1484 for i, a in enumerate(self.applied):
1484 for i, a in enumerate(self.applied):
1485 if a.name == patch:
1485 if a.name == patch:
1486 return (i, a.node, a.name)
1486 return (i, a.node, a.name)
1487 return None
1487 return None
1488
1488
1489 # if the exact patch name does not exist, we try a few
1489 # if the exact patch name does not exist, we try a few
1490 # variations. If strict is passed, we try only #1
1490 # variations. If strict is passed, we try only #1
1491 #
1491 #
1492 # 1) a number (as string) to indicate an offset in the series file
1492 # 1) a number (as string) to indicate an offset in the series file
1493 # 2) a unique substring of the patch name was given
1493 # 2) a unique substring of the patch name was given
1494 # 3) patchname[-+]num to indicate an offset in the series file
1494 # 3) patchname[-+]num to indicate an offset in the series file
1495 def lookup(self, patch, strict=False):
1495 def lookup(self, patch, strict=False):
1496 def partialname(s):
1496 def partialname(s):
1497 if s in self.series:
1497 if s in self.series:
1498 return s
1498 return s
1499 matches = [x for x in self.series if s in x]
1499 matches = [x for x in self.series if s in x]
1500 if len(matches) > 1:
1500 if len(matches) > 1:
1501 self.ui.warn(_(b'patch name "%s" is ambiguous:\n') % s)
1501 self.ui.warn(_(b'patch name "%s" is ambiguous:\n') % s)
1502 for m in matches:
1502 for m in matches:
1503 self.ui.warn(b' %s\n' % m)
1503 self.ui.warn(b' %s\n' % m)
1504 return None
1504 return None
1505 if matches:
1505 if matches:
1506 return matches[0]
1506 return matches[0]
1507 if self.series and self.applied:
1507 if self.series and self.applied:
1508 if s == b'qtip':
1508 if s == b'qtip':
1509 return self.series[self.seriesend(True) - 1]
1509 return self.series[self.seriesend(True) - 1]
1510 if s == b'qbase':
1510 if s == b'qbase':
1511 return self.series[0]
1511 return self.series[0]
1512 return None
1512 return None
1513
1513
1514 if patch in self.series:
1514 if patch in self.series:
1515 return patch
1515 return patch
1516
1516
1517 if not os.path.isfile(self.join(patch)):
1517 if not os.path.isfile(self.join(patch)):
1518 try:
1518 try:
1519 sno = int(patch)
1519 sno = int(patch)
1520 except (ValueError, OverflowError):
1520 except (ValueError, OverflowError):
1521 pass
1521 pass
1522 else:
1522 else:
1523 if -len(self.series) <= sno < len(self.series):
1523 if -len(self.series) <= sno < len(self.series):
1524 return self.series[sno]
1524 return self.series[sno]
1525
1525
1526 if not strict:
1526 if not strict:
1527 res = partialname(patch)
1527 res = partialname(patch)
1528 if res:
1528 if res:
1529 return res
1529 return res
1530 minus = patch.rfind(b'-')
1530 minus = patch.rfind(b'-')
1531 if minus >= 0:
1531 if minus >= 0:
1532 res = partialname(patch[:minus])
1532 res = partialname(patch[:minus])
1533 if res:
1533 if res:
1534 i = self.series.index(res)
1534 i = self.series.index(res)
1535 try:
1535 try:
1536 off = int(patch[minus + 1 :] or 1)
1536 off = int(patch[minus + 1 :] or 1)
1537 except (ValueError, OverflowError):
1537 except (ValueError, OverflowError):
1538 pass
1538 pass
1539 else:
1539 else:
1540 if i - off >= 0:
1540 if i - off >= 0:
1541 return self.series[i - off]
1541 return self.series[i - off]
1542 plus = patch.rfind(b'+')
1542 plus = patch.rfind(b'+')
1543 if plus >= 0:
1543 if plus >= 0:
1544 res = partialname(patch[:plus])
1544 res = partialname(patch[:plus])
1545 if res:
1545 if res:
1546 i = self.series.index(res)
1546 i = self.series.index(res)
1547 try:
1547 try:
1548 off = int(patch[plus + 1 :] or 1)
1548 off = int(patch[plus + 1 :] or 1)
1549 except (ValueError, OverflowError):
1549 except (ValueError, OverflowError):
1550 pass
1550 pass
1551 else:
1551 else:
1552 if i + off < len(self.series):
1552 if i + off < len(self.series):
1553 return self.series[i + off]
1553 return self.series[i + off]
1554 raise error.Abort(_(b"patch %s not in series") % patch)
1554 raise error.Abort(_(b"patch %s not in series") % patch)
1555
1555
1556 def push(
1556 def push(
1557 self,
1557 self,
1558 repo,
1558 repo,
1559 patch=None,
1559 patch=None,
1560 force=False,
1560 force=False,
1561 list=False,
1561 list=False,
1562 mergeq=None,
1562 mergeq=None,
1563 all=False,
1563 all=False,
1564 move=False,
1564 move=False,
1565 exact=False,
1565 exact=False,
1566 nobackup=False,
1566 nobackup=False,
1567 keepchanges=False,
1567 keepchanges=False,
1568 ):
1568 ):
1569 self.checkkeepchanges(keepchanges, force)
1569 self.checkkeepchanges(keepchanges, force)
1570 diffopts = self.diffopts()
1570 diffopts = self.diffopts()
1571 with repo.wlock():
1571 with repo.wlock():
1572 heads = []
1572 heads = []
1573 for hs in repo.branchmap().iterheads():
1573 for hs in repo.branchmap().iterheads():
1574 heads.extend(hs)
1574 heads.extend(hs)
1575 if not heads:
1575 if not heads:
1576 heads = [nullid]
1576 heads = [nullid]
1577 if repo.dirstate.p1() not in heads and not exact:
1577 if repo.dirstate.p1() not in heads and not exact:
1578 self.ui.status(_(b"(working directory not at a head)\n"))
1578 self.ui.status(_(b"(working directory not at a head)\n"))
1579
1579
1580 if not self.series:
1580 if not self.series:
1581 self.ui.warn(_(b'no patches in series\n'))
1581 self.ui.warn(_(b'no patches in series\n'))
1582 return 0
1582 return 0
1583
1583
1584 # Suppose our series file is: A B C and the current 'top'
1584 # Suppose our series file is: A B C and the current 'top'
1585 # patch is B. qpush C should be performed (moving forward)
1585 # patch is B. qpush C should be performed (moving forward)
1586 # qpush B is a NOP (no change) qpush A is an error (can't
1586 # qpush B is a NOP (no change) qpush A is an error (can't
1587 # go backwards with qpush)
1587 # go backwards with qpush)
1588 if patch:
1588 if patch:
1589 patch = self.lookup(patch)
1589 patch = self.lookup(patch)
1590 info = self.isapplied(patch)
1590 info = self.isapplied(patch)
1591 if info and info[0] >= len(self.applied) - 1:
1591 if info and info[0] >= len(self.applied) - 1:
1592 self.ui.warn(
1592 self.ui.warn(
1593 _(b'qpush: %s is already at the top\n') % patch
1593 _(b'qpush: %s is already at the top\n') % patch
1594 )
1594 )
1595 return 0
1595 return 0
1596
1596
1597 pushable, reason = self.pushable(patch)
1597 pushable, reason = self.pushable(patch)
1598 if pushable:
1598 if pushable:
1599 if self.series.index(patch) < self.seriesend():
1599 if self.series.index(patch) < self.seriesend():
1600 raise error.Abort(
1600 raise error.Abort(
1601 _(b"cannot push to a previous patch: %s") % patch
1601 _(b"cannot push to a previous patch: %s") % patch
1602 )
1602 )
1603 else:
1603 else:
1604 if reason:
1604 if reason:
1605 reason = _(b'guarded by %s') % reason
1605 reason = _(b'guarded by %s') % reason
1606 else:
1606 else:
1607 reason = _(b'no matching guards')
1607 reason = _(b'no matching guards')
1608 self.ui.warn(
1608 self.ui.warn(
1609 _(b"cannot push '%s' - %s\n") % (patch, reason)
1609 _(b"cannot push '%s' - %s\n") % (patch, reason)
1610 )
1610 )
1611 return 1
1611 return 1
1612 elif all:
1612 elif all:
1613 patch = self.series[-1]
1613 patch = self.series[-1]
1614 if self.isapplied(patch):
1614 if self.isapplied(patch):
1615 self.ui.warn(_(b'all patches are currently applied\n'))
1615 self.ui.warn(_(b'all patches are currently applied\n'))
1616 return 0
1616 return 0
1617
1617
1618 # Following the above example, starting at 'top' of B:
1618 # Following the above example, starting at 'top' of B:
1619 # qpush should be performed (pushes C), but a subsequent
1619 # qpush should be performed (pushes C), but a subsequent
1620 # qpush without an argument is an error (nothing to
1620 # qpush without an argument is an error (nothing to
1621 # apply). This allows a loop of "...while hg qpush..." to
1621 # apply). This allows a loop of "...while hg qpush..." to
1622 # work as it detects an error when done
1622 # work as it detects an error when done
1623 start = self.seriesend()
1623 start = self.seriesend()
1624 if start == len(self.series):
1624 if start == len(self.series):
1625 self.ui.warn(_(b'patch series already fully applied\n'))
1625 self.ui.warn(_(b'patch series already fully applied\n'))
1626 return 1
1626 return 1
1627 if not force and not keepchanges:
1627 if not force and not keepchanges:
1628 self.checklocalchanges(repo, refresh=self.applied)
1628 self.checklocalchanges(repo, refresh=self.applied)
1629
1629
1630 if exact:
1630 if exact:
1631 if keepchanges:
1631 if keepchanges:
1632 raise error.Abort(
1632 raise error.Abort(
1633 _(b"cannot use --exact and --keep-changes together")
1633 _(b"cannot use --exact and --keep-changes together")
1634 )
1634 )
1635 if move:
1635 if move:
1636 raise error.Abort(
1636 raise error.Abort(
1637 _(b'cannot use --exact and --move together')
1637 _(b'cannot use --exact and --move together')
1638 )
1638 )
1639 if self.applied:
1639 if self.applied:
1640 raise error.Abort(
1640 raise error.Abort(
1641 _(b'cannot push --exact with applied patches')
1641 _(b'cannot push --exact with applied patches')
1642 )
1642 )
1643 root = self.series[start]
1643 root = self.series[start]
1644 target = patchheader(self.join(root), self.plainmode).parent
1644 target = patchheader(self.join(root), self.plainmode).parent
1645 if not target:
1645 if not target:
1646 raise error.Abort(
1646 raise error.Abort(
1647 _(b"%s does not have a parent recorded") % root
1647 _(b"%s does not have a parent recorded") % root
1648 )
1648 )
1649 if not repo[target] == repo[b'.']:
1649 if not repo[target] == repo[b'.']:
1650 hg.update(repo, target)
1650 hg.update(repo, target)
1651
1651
1652 if move:
1652 if move:
1653 if not patch:
1653 if not patch:
1654 raise error.Abort(_(b"please specify the patch to move"))
1654 raise error.Abort(_(b"please specify the patch to move"))
1655 for fullstart, rpn in enumerate(self.fullseries):
1655 for fullstart, rpn in enumerate(self.fullseries):
1656 # strip markers for patch guards
1656 # strip markers for patch guards
1657 if self.guard_re.split(rpn, 1)[0] == self.series[start]:
1657 if self.guard_re.split(rpn, 1)[0] == self.series[start]:
1658 break
1658 break
1659 for i, rpn in enumerate(self.fullseries[fullstart:]):
1659 for i, rpn in enumerate(self.fullseries[fullstart:]):
1660 # strip markers for patch guards
1660 # strip markers for patch guards
1661 if self.guard_re.split(rpn, 1)[0] == patch:
1661 if self.guard_re.split(rpn, 1)[0] == patch:
1662 break
1662 break
1663 index = fullstart + i
1663 index = fullstart + i
1664 assert index < len(self.fullseries)
1664 assert index < len(self.fullseries)
1665 fullpatch = self.fullseries[index]
1665 fullpatch = self.fullseries[index]
1666 del self.fullseries[index]
1666 del self.fullseries[index]
1667 self.fullseries.insert(fullstart, fullpatch)
1667 self.fullseries.insert(fullstart, fullpatch)
1668 self.parseseries()
1668 self.parseseries()
1669 self.seriesdirty = True
1669 self.seriesdirty = True
1670
1670
1671 self.applieddirty = True
1671 self.applieddirty = True
1672 if start > 0:
1672 if start > 0:
1673 self.checktoppatch(repo)
1673 self.checktoppatch(repo)
1674 if not patch:
1674 if not patch:
1675 patch = self.series[start]
1675 patch = self.series[start]
1676 end = start + 1
1676 end = start + 1
1677 else:
1677 else:
1678 end = self.series.index(patch, start) + 1
1678 end = self.series.index(patch, start) + 1
1679
1679
1680 tobackup = set()
1680 tobackup = set()
1681 if (not nobackup and force) or keepchanges:
1681 if (not nobackup and force) or keepchanges:
1682 status = self.checklocalchanges(repo, force=True)
1682 status = self.checklocalchanges(repo, force=True)
1683 if keepchanges:
1683 if keepchanges:
1684 tobackup.update(
1684 tobackup.update(
1685 status.modified
1685 status.modified
1686 + status.added
1686 + status.added
1687 + status.removed
1687 + status.removed
1688 + status.deleted
1688 + status.deleted
1689 )
1689 )
1690 else:
1690 else:
1691 tobackup.update(status.modified + status.added)
1691 tobackup.update(status.modified + status.added)
1692
1692
1693 s = self.series[start:end]
1693 s = self.series[start:end]
1694 all_files = set()
1694 all_files = set()
1695 try:
1695 try:
1696 if mergeq:
1696 if mergeq:
1697 ret = self.mergepatch(repo, mergeq, s, diffopts)
1697 ret = self.mergepatch(repo, mergeq, s, diffopts)
1698 else:
1698 else:
1699 ret = self.apply(
1699 ret = self.apply(
1700 repo,
1700 repo,
1701 s,
1701 s,
1702 list,
1702 list,
1703 all_files=all_files,
1703 all_files=all_files,
1704 tobackup=tobackup,
1704 tobackup=tobackup,
1705 keepchanges=keepchanges,
1705 keepchanges=keepchanges,
1706 )
1706 )
1707 except AbortNoCleanup:
1707 except AbortNoCleanup:
1708 raise
1708 raise
1709 except: # re-raises
1709 except: # re-raises
1710 self.ui.warn(_(b'cleaning up working directory...\n'))
1710 self.ui.warn(_(b'cleaning up working directory...\n'))
1711 cmdutil.revert(
1711 cmdutil.revert(
1712 self.ui,
1712 self.ui,
1713 repo,
1713 repo,
1714 repo[b'.'],
1714 repo[b'.'],
1715 repo.dirstate.parents(),
1715 repo.dirstate.parents(),
1716 no_backup=True,
1716 no_backup=True,
1717 )
1717 )
1718 # only remove unknown files that we know we touched or
1718 # only remove unknown files that we know we touched or
1719 # created while patching
1719 # created while patching
1720 for f in all_files:
1720 for f in all_files:
1721 if f not in repo.dirstate:
1721 if f not in repo.dirstate:
1722 repo.wvfs.unlinkpath(f, ignoremissing=True)
1722 repo.wvfs.unlinkpath(f, ignoremissing=True)
1723 self.ui.warn(_(b'done\n'))
1723 self.ui.warn(_(b'done\n'))
1724 raise
1724 raise
1725
1725
1726 if not self.applied:
1726 if not self.applied:
1727 return ret[0]
1727 return ret[0]
1728 top = self.applied[-1].name
1728 top = self.applied[-1].name
1729 if ret[0] and ret[0] > 1:
1729 if ret[0] and ret[0] > 1:
1730 msg = _(b"errors during apply, please fix and qrefresh %s\n")
1730 msg = _(b"errors during apply, please fix and qrefresh %s\n")
1731 self.ui.write(msg % top)
1731 self.ui.write(msg % top)
1732 else:
1732 else:
1733 self.ui.write(_(b"now at: %s\n") % top)
1733 self.ui.write(_(b"now at: %s\n") % top)
1734 return ret[0]
1734 return ret[0]
1735
1735
1736 def pop(
1736 def pop(
1737 self,
1737 self,
1738 repo,
1738 repo,
1739 patch=None,
1739 patch=None,
1740 force=False,
1740 force=False,
1741 update=True,
1741 update=True,
1742 all=False,
1742 all=False,
1743 nobackup=False,
1743 nobackup=False,
1744 keepchanges=False,
1744 keepchanges=False,
1745 ):
1745 ):
1746 self.checkkeepchanges(keepchanges, force)
1746 self.checkkeepchanges(keepchanges, force)
1747 with repo.wlock():
1747 with repo.wlock():
1748 if patch:
1748 if patch:
1749 # index, rev, patch
1749 # index, rev, patch
1750 info = self.isapplied(patch)
1750 info = self.isapplied(patch)
1751 if not info:
1751 if not info:
1752 patch = self.lookup(patch)
1752 patch = self.lookup(patch)
1753 info = self.isapplied(patch)
1753 info = self.isapplied(patch)
1754 if not info:
1754 if not info:
1755 raise error.Abort(_(b"patch %s is not applied") % patch)
1755 raise error.Abort(_(b"patch %s is not applied") % patch)
1756
1756
1757 if not self.applied:
1757 if not self.applied:
1758 # Allow qpop -a to work repeatedly,
1758 # Allow qpop -a to work repeatedly,
1759 # but not qpop without an argument
1759 # but not qpop without an argument
1760 self.ui.warn(_(b"no patches applied\n"))
1760 self.ui.warn(_(b"no patches applied\n"))
1761 return not all
1761 return not all
1762
1762
1763 if all:
1763 if all:
1764 start = 0
1764 start = 0
1765 elif patch:
1765 elif patch:
1766 start = info[0] + 1
1766 start = info[0] + 1
1767 else:
1767 else:
1768 start = len(self.applied) - 1
1768 start = len(self.applied) - 1
1769
1769
1770 if start >= len(self.applied):
1770 if start >= len(self.applied):
1771 self.ui.warn(_(b"qpop: %s is already at the top\n") % patch)
1771 self.ui.warn(_(b"qpop: %s is already at the top\n") % patch)
1772 return
1772 return
1773
1773
1774 if not update:
1774 if not update:
1775 parents = repo.dirstate.parents()
1775 parents = repo.dirstate.parents()
1776 rr = [x.node for x in self.applied]
1776 rr = [x.node for x in self.applied]
1777 for p in parents:
1777 for p in parents:
1778 if p in rr:
1778 if p in rr:
1779 self.ui.warn(_(b"qpop: forcing dirstate update\n"))
1779 self.ui.warn(_(b"qpop: forcing dirstate update\n"))
1780 update = True
1780 update = True
1781 else:
1781 else:
1782 parents = [p.node() for p in repo[None].parents()]
1782 parents = [p.node() for p in repo[None].parents()]
1783 update = any(
1783 update = any(
1784 entry.node in parents for entry in self.applied[start:]
1784 entry.node in parents for entry in self.applied[start:]
1785 )
1785 )
1786
1786
1787 tobackup = set()
1787 tobackup = set()
1788 if update:
1788 if update:
1789 s = self.checklocalchanges(repo, force=force or keepchanges)
1789 s = self.checklocalchanges(repo, force=force or keepchanges)
1790 if force:
1790 if force:
1791 if not nobackup:
1791 if not nobackup:
1792 tobackup.update(s.modified + s.added)
1792 tobackup.update(s.modified + s.added)
1793 elif keepchanges:
1793 elif keepchanges:
1794 tobackup.update(
1794 tobackup.update(
1795 s.modified + s.added + s.removed + s.deleted
1795 s.modified + s.added + s.removed + s.deleted
1796 )
1796 )
1797
1797
1798 self.applieddirty = True
1798 self.applieddirty = True
1799 end = len(self.applied)
1799 end = len(self.applied)
1800 rev = self.applied[start].node
1800 rev = self.applied[start].node
1801
1801
1802 try:
1802 try:
1803 heads = repo.changelog.heads(rev)
1803 heads = repo.changelog.heads(rev)
1804 except error.LookupError:
1804 except error.LookupError:
1805 node = short(rev)
1805 node = short(rev)
1806 raise error.Abort(_(b'trying to pop unknown node %s') % node)
1806 raise error.Abort(_(b'trying to pop unknown node %s') % node)
1807
1807
1808 if heads != [self.applied[-1].node]:
1808 if heads != [self.applied[-1].node]:
1809 raise error.Abort(
1809 raise error.Abort(
1810 _(
1810 _(
1811 b"popping would remove a revision not "
1811 b"popping would remove a revision not "
1812 b"managed by this patch queue"
1812 b"managed by this patch queue"
1813 )
1813 )
1814 )
1814 )
1815 if not repo[self.applied[-1].node].mutable():
1815 if not repo[self.applied[-1].node].mutable():
1816 raise error.Abort(
1816 raise error.Abort(
1817 _(b"popping would remove a public revision"),
1817 _(b"popping would remove a public revision"),
1818 hint=_(b"see 'hg help phases' for details"),
1818 hint=_(b"see 'hg help phases' for details"),
1819 )
1819 )
1820
1820
1821 # we know there are no local changes, so we can make a simplified
1821 # we know there are no local changes, so we can make a simplified
1822 # form of hg.update.
1822 # form of hg.update.
1823 if update:
1823 if update:
1824 qp = self.qparents(repo, rev)
1824 qp = self.qparents(repo, rev)
1825 ctx = repo[qp]
1825 ctx = repo[qp]
1826 st = repo.status(qp, b'.')
1826 st = repo.status(qp, b'.')
1827 m, a, r, d = st.modified, st.added, st.removed, st.deleted
1827 m, a, r, d = st.modified, st.added, st.removed, st.deleted
1828 if d:
1828 if d:
1829 raise error.Abort(_(b"deletions found between repo revs"))
1829 raise error.Abort(_(b"deletions found between repo revs"))
1830
1830
1831 tobackup = set(a + m + r) & tobackup
1831 tobackup = set(a + m + r) & tobackup
1832 if keepchanges and tobackup:
1832 if keepchanges and tobackup:
1833 raise error.Abort(_(b"local changes found, qrefresh first"))
1833 raise error.Abort(_(b"local changes found, qrefresh first"))
1834 self.backup(repo, tobackup)
1834 self.backup(repo, tobackup)
1835 with repo.dirstate.parentchange():
1835 with repo.dirstate.parentchange():
1836 for f in a:
1836 for f in a:
1837 repo.wvfs.unlinkpath(f, ignoremissing=True)
1837 repo.wvfs.unlinkpath(f, ignoremissing=True)
1838 repo.dirstate.drop(f)
1838 repo.dirstate.drop(f)
1839 for f in m + r:
1839 for f in m + r:
1840 fctx = ctx[f]
1840 fctx = ctx[f]
1841 repo.wwrite(f, fctx.data(), fctx.flags())
1841 repo.wwrite(f, fctx.data(), fctx.flags())
1842 repo.dirstate.normal(f)
1842 repo.dirstate.normal(f)
1843 repo.setparents(qp, nullid)
1843 repo.setparents(qp, nullid)
1844 for patch in reversed(self.applied[start:end]):
1844 for patch in reversed(self.applied[start:end]):
1845 self.ui.status(_(b"popping %s\n") % patch.name)
1845 self.ui.status(_(b"popping %s\n") % patch.name)
1846 del self.applied[start:end]
1846 del self.applied[start:end]
1847 strip(self.ui, repo, [rev], update=False, backup=False)
1847 strip(self.ui, repo, [rev], update=False, backup=False)
1848 for s, state in repo[b'.'].substate.items():
1848 for s, state in repo[b'.'].substate.items():
1849 repo[b'.'].sub(s).get(state)
1849 repo[b'.'].sub(s).get(state)
1850 if self.applied:
1850 if self.applied:
1851 self.ui.write(_(b"now at: %s\n") % self.applied[-1].name)
1851 self.ui.write(_(b"now at: %s\n") % self.applied[-1].name)
1852 else:
1852 else:
1853 self.ui.write(_(b"patch queue now empty\n"))
1853 self.ui.write(_(b"patch queue now empty\n"))
1854
1854
1855 def diff(self, repo, pats, opts):
1855 def diff(self, repo, pats, opts):
1856 top, patch = self.checktoppatch(repo)
1856 top, patch = self.checktoppatch(repo)
1857 if not top:
1857 if not top:
1858 self.ui.write(_(b"no patches applied\n"))
1858 self.ui.write(_(b"no patches applied\n"))
1859 return
1859 return
1860 qp = self.qparents(repo, top)
1860 qp = self.qparents(repo, top)
1861 if opts.get(b'reverse'):
1861 if opts.get(b'reverse'):
1862 node1, node2 = None, qp
1862 node1, node2 = None, qp
1863 else:
1863 else:
1864 node1, node2 = qp, None
1864 node1, node2 = qp, None
1865 diffopts = self.diffopts(opts, patch)
1865 diffopts = self.diffopts(opts, patch)
1866 self.printdiff(repo, diffopts, node1, node2, files=pats, opts=opts)
1866 self.printdiff(repo, diffopts, node1, node2, files=pats, opts=opts)
1867
1867
1868 def refresh(self, repo, pats=None, **opts):
1868 def refresh(self, repo, pats=None, **opts):
1869 opts = pycompat.byteskwargs(opts)
1869 opts = pycompat.byteskwargs(opts)
1870 if not self.applied:
1870 if not self.applied:
1871 self.ui.write(_(b"no patches applied\n"))
1871 self.ui.write(_(b"no patches applied\n"))
1872 return 1
1872 return 1
1873 msg = opts.get(b'msg', b'').rstrip()
1873 msg = opts.get(b'msg', b'').rstrip()
1874 edit = opts.get(b'edit')
1874 edit = opts.get(b'edit')
1875 editform = opts.get(b'editform', b'mq.qrefresh')
1875 editform = opts.get(b'editform', b'mq.qrefresh')
1876 newuser = opts.get(b'user')
1876 newuser = opts.get(b'user')
1877 newdate = opts.get(b'date')
1877 newdate = opts.get(b'date')
1878 if newdate:
1878 if newdate:
1879 newdate = b'%d %d' % dateutil.parsedate(newdate)
1879 newdate = b'%d %d' % dateutil.parsedate(newdate)
1880 wlock = repo.wlock()
1880 wlock = repo.wlock()
1881
1881
1882 try:
1882 try:
1883 self.checktoppatch(repo)
1883 self.checktoppatch(repo)
1884 (top, patchfn) = (self.applied[-1].node, self.applied[-1].name)
1884 (top, patchfn) = (self.applied[-1].node, self.applied[-1].name)
1885 if repo.changelog.heads(top) != [top]:
1885 if repo.changelog.heads(top) != [top]:
1886 raise error.Abort(
1886 raise error.Abort(
1887 _(b"cannot qrefresh a revision with children")
1887 _(b"cannot qrefresh a revision with children")
1888 )
1888 )
1889 if not repo[top].mutable():
1889 if not repo[top].mutable():
1890 raise error.Abort(
1890 raise error.Abort(
1891 _(b"cannot qrefresh public revision"),
1891 _(b"cannot qrefresh public revision"),
1892 hint=_(b"see 'hg help phases' for details"),
1892 hint=_(b"see 'hg help phases' for details"),
1893 )
1893 )
1894
1894
1895 cparents = repo.changelog.parents(top)
1895 cparents = repo.changelog.parents(top)
1896 patchparent = self.qparents(repo, top)
1896 patchparent = self.qparents(repo, top)
1897
1897
1898 inclsubs = checksubstate(repo, patchparent)
1898 inclsubs = checksubstate(repo, patchparent)
1899 if inclsubs:
1899 if inclsubs:
1900 substatestate = repo.dirstate[b'.hgsubstate']
1900 substatestate = repo.dirstate[b'.hgsubstate']
1901
1901
1902 ph = patchheader(self.join(patchfn), self.plainmode)
1902 ph = patchheader(self.join(patchfn), self.plainmode)
1903 diffopts = self.diffopts(
1903 diffopts = self.diffopts(
1904 {b'git': opts.get(b'git')}, patchfn, plain=True
1904 {b'git': opts.get(b'git')}, patchfn, plain=True
1905 )
1905 )
1906 if newuser:
1906 if newuser:
1907 ph.setuser(newuser)
1907 ph.setuser(newuser)
1908 if newdate:
1908 if newdate:
1909 ph.setdate(newdate)
1909 ph.setdate(newdate)
1910 ph.setparent(hex(patchparent))
1910 ph.setparent(hex(patchparent))
1911
1911
1912 # only commit new patch when write is complete
1912 # only commit new patch when write is complete
1913 patchf = self.opener(patchfn, b'w', atomictemp=True)
1913 patchf = self.opener(patchfn, b'w', atomictemp=True)
1914
1914
1915 # update the dirstate in place, strip off the qtip commit
1915 # update the dirstate in place, strip off the qtip commit
1916 # and then commit.
1916 # and then commit.
1917 #
1917 #
1918 # this should really read:
1918 # this should really read:
1919 # st = repo.status(top, patchparent)
1919 # st = repo.status(top, patchparent)
1920 # but we do it backwards to take advantage of manifest/changelog
1920 # but we do it backwards to take advantage of manifest/changelog
1921 # caching against the next repo.status call
1921 # caching against the next repo.status call
1922 st = repo.status(patchparent, top)
1922 st = repo.status(patchparent, top)
1923 mm, aa, dd = st.modified, st.added, st.removed
1923 mm, aa, dd = st.modified, st.added, st.removed
1924 ctx = repo[top]
1924 ctx = repo[top]
1925 aaa = aa[:]
1925 aaa = aa[:]
1926 match1 = scmutil.match(repo[None], pats, opts)
1926 match1 = scmutil.match(repo[None], pats, opts)
1927 # in short mode, we only diff the files included in the
1927 # in short mode, we only diff the files included in the
1928 # patch already plus specified files
1928 # patch already plus specified files
1929 if opts.get(b'short'):
1929 if opts.get(b'short'):
1930 # if amending a patch, we start with existing
1930 # if amending a patch, we start with existing
1931 # files plus specified files - unfiltered
1931 # files plus specified files - unfiltered
1932 match = scmutil.matchfiles(repo, mm + aa + dd + match1.files())
1932 match = scmutil.matchfiles(repo, mm + aa + dd + match1.files())
1933 # filter with include/exclude options
1933 # filter with include/exclude options
1934 match1 = scmutil.match(repo[None], opts=opts)
1934 match1 = scmutil.match(repo[None], opts=opts)
1935 else:
1935 else:
1936 match = scmutil.matchall(repo)
1936 match = scmutil.matchall(repo)
1937 stb = repo.status(match=match)
1937 stb = repo.status(match=match)
1938 m, a, r, d = stb.modified, stb.added, stb.removed, stb.deleted
1938 m, a, r, d = stb.modified, stb.added, stb.removed, stb.deleted
1939 mm = set(mm)
1939 mm = set(mm)
1940 aa = set(aa)
1940 aa = set(aa)
1941 dd = set(dd)
1941 dd = set(dd)
1942
1942
1943 # we might end up with files that were added between
1943 # we might end up with files that were added between
1944 # qtip and the dirstate parent, but then changed in the
1944 # qtip and the dirstate parent, but then changed in the
1945 # local dirstate. in this case, we want them to only
1945 # local dirstate. in this case, we want them to only
1946 # show up in the added section
1946 # show up in the added section
1947 for x in m:
1947 for x in m:
1948 if x not in aa:
1948 if x not in aa:
1949 mm.add(x)
1949 mm.add(x)
1950 # we might end up with files added by the local dirstate that
1950 # we might end up with files added by the local dirstate that
1951 # were deleted by the patch. In this case, they should only
1951 # were deleted by the patch. In this case, they should only
1952 # show up in the changed section.
1952 # show up in the changed section.
1953 for x in a:
1953 for x in a:
1954 if x in dd:
1954 if x in dd:
1955 dd.remove(x)
1955 dd.remove(x)
1956 mm.add(x)
1956 mm.add(x)
1957 else:
1957 else:
1958 aa.add(x)
1958 aa.add(x)
1959 # make sure any files deleted in the local dirstate
1959 # make sure any files deleted in the local dirstate
1960 # are not in the add or change column of the patch
1960 # are not in the add or change column of the patch
1961 forget = []
1961 forget = []
1962 for x in d + r:
1962 for x in d + r:
1963 if x in aa:
1963 if x in aa:
1964 aa.remove(x)
1964 aa.remove(x)
1965 forget.append(x)
1965 forget.append(x)
1966 continue
1966 continue
1967 else:
1967 else:
1968 mm.discard(x)
1968 mm.discard(x)
1969 dd.add(x)
1969 dd.add(x)
1970
1970
1971 m = list(mm)
1971 m = list(mm)
1972 r = list(dd)
1972 r = list(dd)
1973 a = list(aa)
1973 a = list(aa)
1974
1974
1975 # create 'match' that includes the files to be recommitted.
1975 # create 'match' that includes the files to be recommitted.
1976 # apply match1 via repo.status to ensure correct case handling.
1976 # apply match1 via repo.status to ensure correct case handling.
1977 st = repo.status(patchparent, match=match1)
1977 st = repo.status(patchparent, match=match1)
1978 cm, ca, cr, cd = st.modified, st.added, st.removed, st.deleted
1978 cm, ca, cr, cd = st.modified, st.added, st.removed, st.deleted
1979 allmatches = set(cm + ca + cr + cd)
1979 allmatches = set(cm + ca + cr + cd)
1980 refreshchanges = [x.intersection(allmatches) for x in (mm, aa, dd)]
1980 refreshchanges = [x.intersection(allmatches) for x in (mm, aa, dd)]
1981
1981
1982 files = set(inclsubs)
1982 files = set(inclsubs)
1983 for x in refreshchanges:
1983 for x in refreshchanges:
1984 files.update(x)
1984 files.update(x)
1985 match = scmutil.matchfiles(repo, files)
1985 match = scmutil.matchfiles(repo, files)
1986
1986
1987 bmlist = repo[top].bookmarks()
1987 bmlist = repo[top].bookmarks()
1988
1988
1989 dsguard = None
1989 dsguard = None
1990 try:
1990 try:
1991 dsguard = dirstateguard.dirstateguard(repo, b'mq.refresh')
1991 dsguard = dirstateguard.dirstateguard(repo, b'mq.refresh')
1992 if diffopts.git or diffopts.upgrade:
1992 if diffopts.git or diffopts.upgrade:
1993 copies = {}
1993 copies = {}
1994 for dst in a:
1994 for dst in a:
1995 src = repo.dirstate.copied(dst)
1995 src = repo.dirstate.copied(dst)
1996 # during qfold, the source file for copies may
1996 # during qfold, the source file for copies may
1997 # be removed. Treat this as a simple add.
1997 # be removed. Treat this as a simple add.
1998 if src is not None and src in repo.dirstate:
1998 if src is not None and src in repo.dirstate:
1999 copies.setdefault(src, []).append(dst)
1999 copies.setdefault(src, []).append(dst)
2000 repo.dirstate.add(dst)
2000 repo.dirstate.add(dst)
2001 # remember the copies between patchparent and qtip
2001 # remember the copies between patchparent and qtip
2002 for dst in aaa:
2002 for dst in aaa:
2003 src = ctx[dst].copysource()
2003 src = ctx[dst].copysource()
2004 if src:
2004 if src:
2005 copies.setdefault(src, []).extend(
2005 copies.setdefault(src, []).extend(
2006 copies.get(dst, [])
2006 copies.get(dst, [])
2007 )
2007 )
2008 if dst in a:
2008 if dst in a:
2009 copies[src].append(dst)
2009 copies[src].append(dst)
2010 # we can't copy a file created by the patch itself
2010 # we can't copy a file created by the patch itself
2011 if dst in copies:
2011 if dst in copies:
2012 del copies[dst]
2012 del copies[dst]
2013 for src, dsts in pycompat.iteritems(copies):
2013 for src, dsts in pycompat.iteritems(copies):
2014 for dst in dsts:
2014 for dst in dsts:
2015 repo.dirstate.copy(src, dst)
2015 repo.dirstate.copy(src, dst)
2016 else:
2016 else:
2017 for dst in a:
2017 for dst in a:
2018 repo.dirstate.add(dst)
2018 repo.dirstate.add(dst)
2019 # Drop useless copy information
2019 # Drop useless copy information
2020 for f in list(repo.dirstate.copies()):
2020 for f in list(repo.dirstate.copies()):
2021 repo.dirstate.copy(None, f)
2021 repo.dirstate.copy(None, f)
2022 for f in r:
2022 for f in r:
2023 repo.dirstate.remove(f)
2023 repo.dirstate.remove(f)
2024 # if the patch excludes a modified file, mark that
2024 # if the patch excludes a modified file, mark that
2025 # file with mtime=0 so status can see it.
2025 # file with mtime=0 so status can see it.
2026 mm = []
2026 mm = []
2027 for i in pycompat.xrange(len(m) - 1, -1, -1):
2027 for i in pycompat.xrange(len(m) - 1, -1, -1):
2028 if not match1(m[i]):
2028 if not match1(m[i]):
2029 mm.append(m[i])
2029 mm.append(m[i])
2030 del m[i]
2030 del m[i]
2031 for f in m:
2031 for f in m:
2032 repo.dirstate.normal(f)
2032 repo.dirstate.normal(f)
2033 for f in mm:
2033 for f in mm:
2034 repo.dirstate.normallookup(f)
2034 repo.dirstate.normallookup(f)
2035 for f in forget:
2035 for f in forget:
2036 repo.dirstate.drop(f)
2036 repo.dirstate.drop(f)
2037
2037
2038 user = ph.user or ctx.user()
2038 user = ph.user or ctx.user()
2039
2039
2040 oldphase = repo[top].phase()
2040 oldphase = repo[top].phase()
2041
2041
2042 # assumes strip can roll itself back if interrupted
2042 # assumes strip can roll itself back if interrupted
2043 repo.setparents(*cparents)
2043 repo.setparents(*cparents)
2044 self.applied.pop()
2044 self.applied.pop()
2045 self.applieddirty = True
2045 self.applieddirty = True
2046 strip(self.ui, repo, [top], update=False, backup=False)
2046 strip(self.ui, repo, [top], update=False, backup=False)
2047 dsguard.close()
2047 dsguard.close()
2048 finally:
2048 finally:
2049 release(dsguard)
2049 release(dsguard)
2050
2050
2051 try:
2051 try:
2052 # might be nice to attempt to roll back strip after this
2052 # might be nice to attempt to roll back strip after this
2053
2053
2054 defaultmsg = b"[mq]: %s" % patchfn
2054 defaultmsg = b"[mq]: %s" % patchfn
2055 editor = cmdutil.getcommiteditor(editform=editform)
2055 editor = cmdutil.getcommiteditor(editform=editform)
2056 if edit:
2056 if edit:
2057
2057
2058 def finishdesc(desc):
2058 def finishdesc(desc):
2059 if desc.rstrip():
2059 if desc.rstrip():
2060 ph.setmessage(desc)
2060 ph.setmessage(desc)
2061 return desc
2061 return desc
2062 return defaultmsg
2062 return defaultmsg
2063
2063
2064 # i18n: this message is shown in editor with "HG: " prefix
2064 # i18n: this message is shown in editor with "HG: " prefix
2065 extramsg = _(b'Leave message empty to use default message.')
2065 extramsg = _(b'Leave message empty to use default message.')
2066 editor = cmdutil.getcommiteditor(
2066 editor = cmdutil.getcommiteditor(
2067 finishdesc=finishdesc,
2067 finishdesc=finishdesc,
2068 extramsg=extramsg,
2068 extramsg=extramsg,
2069 editform=editform,
2069 editform=editform,
2070 )
2070 )
2071 message = msg or b"\n".join(ph.message)
2071 message = msg or b"\n".join(ph.message)
2072 elif not msg:
2072 elif not msg:
2073 if not ph.message:
2073 if not ph.message:
2074 message = defaultmsg
2074 message = defaultmsg
2075 else:
2075 else:
2076 message = b"\n".join(ph.message)
2076 message = b"\n".join(ph.message)
2077 else:
2077 else:
2078 message = msg
2078 message = msg
2079 ph.setmessage(msg)
2079 ph.setmessage(msg)
2080
2080
2081 # Ensure we create a new changeset in the same phase than
2081 # Ensure we create a new changeset in the same phase than
2082 # the old one.
2082 # the old one.
2083 lock = tr = None
2083 lock = tr = None
2084 try:
2084 try:
2085 lock = repo.lock()
2085 lock = repo.lock()
2086 tr = repo.transaction(b'mq')
2086 tr = repo.transaction(b'mq')
2087 n = newcommit(
2087 n = newcommit(
2088 repo,
2088 repo,
2089 oldphase,
2089 oldphase,
2090 message,
2090 message,
2091 user,
2091 user,
2092 ph.date,
2092 ph.date,
2093 match=match,
2093 match=match,
2094 force=True,
2094 force=True,
2095 editor=editor,
2095 editor=editor,
2096 )
2096 )
2097 # only write patch after a successful commit
2097 # only write patch after a successful commit
2098 c = [list(x) for x in refreshchanges]
2098 c = [list(x) for x in refreshchanges]
2099 if inclsubs:
2099 if inclsubs:
2100 self.putsubstate2changes(substatestate, c)
2100 self.putsubstate2changes(substatestate, c)
2101 chunks = patchmod.diff(
2101 chunks = patchmod.diff(
2102 repo, patchparent, changes=c, opts=diffopts
2102 repo, patchparent, changes=c, opts=diffopts
2103 )
2103 )
2104 comments = bytes(ph)
2104 comments = bytes(ph)
2105 if comments:
2105 if comments:
2106 patchf.write(comments)
2106 patchf.write(comments)
2107 for chunk in chunks:
2107 for chunk in chunks:
2108 patchf.write(chunk)
2108 patchf.write(chunk)
2109 patchf.close()
2109 patchf.close()
2110
2110
2111 marks = repo._bookmarks
2111 marks = repo._bookmarks
2112 marks.applychanges(repo, tr, [(bm, n) for bm in bmlist])
2112 marks.applychanges(repo, tr, [(bm, n) for bm in bmlist])
2113 tr.close()
2113 tr.close()
2114
2114
2115 self.applied.append(statusentry(n, patchfn))
2115 self.applied.append(statusentry(n, patchfn))
2116 finally:
2116 finally:
2117 lockmod.release(tr, lock)
2117 lockmod.release(tr, lock)
2118 except: # re-raises
2118 except: # re-raises
2119 ctx = repo[cparents[0]]
2119 ctx = repo[cparents[0]]
2120 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
2120 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
2121 self.savedirty()
2121 self.savedirty()
2122 self.ui.warn(
2122 self.ui.warn(
2123 _(
2123 _(
2124 b'qrefresh interrupted while patch was popped! '
2124 b'qrefresh interrupted while patch was popped! '
2125 b'(revert --all, qpush to recover)\n'
2125 b'(revert --all, qpush to recover)\n'
2126 )
2126 )
2127 )
2127 )
2128 raise
2128 raise
2129 finally:
2129 finally:
2130 wlock.release()
2130 wlock.release()
2131 self.removeundo(repo)
2131 self.removeundo(repo)
2132
2132
2133 def init(self, repo, create=False):
2133 def init(self, repo, create=False):
2134 if not create and os.path.isdir(self.path):
2134 if not create and os.path.isdir(self.path):
2135 raise error.Abort(_(b"patch queue directory already exists"))
2135 raise error.Abort(_(b"patch queue directory already exists"))
2136 try:
2136 try:
2137 os.mkdir(self.path)
2137 os.mkdir(self.path)
2138 except OSError as inst:
2138 except OSError as inst:
2139 if inst.errno != errno.EEXIST or not create:
2139 if inst.errno != errno.EEXIST or not create:
2140 raise
2140 raise
2141 if create:
2141 if create:
2142 return self.qrepo(create=True)
2142 return self.qrepo(create=True)
2143
2143
2144 def unapplied(self, repo, patch=None):
2144 def unapplied(self, repo, patch=None):
2145 if patch and patch not in self.series:
2145 if patch and patch not in self.series:
2146 raise error.Abort(_(b"patch %s is not in series file") % patch)
2146 raise error.Abort(_(b"patch %s is not in series file") % patch)
2147 if not patch:
2147 if not patch:
2148 start = self.seriesend()
2148 start = self.seriesend()
2149 else:
2149 else:
2150 start = self.series.index(patch) + 1
2150 start = self.series.index(patch) + 1
2151 unapplied = []
2151 unapplied = []
2152 for i in pycompat.xrange(start, len(self.series)):
2152 for i in pycompat.xrange(start, len(self.series)):
2153 pushable, reason = self.pushable(i)
2153 pushable, reason = self.pushable(i)
2154 if pushable:
2154 if pushable:
2155 unapplied.append((i, self.series[i]))
2155 unapplied.append((i, self.series[i]))
2156 self.explainpushable(i)
2156 self.explainpushable(i)
2157 return unapplied
2157 return unapplied
2158
2158
2159 def qseries(
2159 def qseries(
2160 self,
2160 self,
2161 repo,
2161 repo,
2162 missing=None,
2162 missing=None,
2163 start=0,
2163 start=0,
2164 length=None,
2164 length=None,
2165 status=None,
2165 status=None,
2166 summary=False,
2166 summary=False,
2167 ):
2167 ):
2168 def displayname(pfx, patchname, state):
2168 def displayname(pfx, patchname, state):
2169 if pfx:
2169 if pfx:
2170 self.ui.write(pfx)
2170 self.ui.write(pfx)
2171 if summary:
2171 if summary:
2172 ph = patchheader(self.join(patchname), self.plainmode)
2172 ph = patchheader(self.join(patchname), self.plainmode)
2173 if ph.message:
2173 if ph.message:
2174 msg = ph.message[0]
2174 msg = ph.message[0]
2175 else:
2175 else:
2176 msg = b''
2176 msg = b''
2177
2177
2178 if self.ui.formatted():
2178 if self.ui.formatted():
2179 width = self.ui.termwidth() - len(pfx) - len(patchname) - 2
2179 width = self.ui.termwidth() - len(pfx) - len(patchname) - 2
2180 if width > 0:
2180 if width > 0:
2181 msg = stringutil.ellipsis(msg, width)
2181 msg = stringutil.ellipsis(msg, width)
2182 else:
2182 else:
2183 msg = b''
2183 msg = b''
2184 self.ui.write(patchname, label=b'qseries.' + state)
2184 self.ui.write(patchname, label=b'qseries.' + state)
2185 self.ui.write(b': ')
2185 self.ui.write(b': ')
2186 self.ui.write(msg, label=b'qseries.message.' + state)
2186 self.ui.write(msg, label=b'qseries.message.' + state)
2187 else:
2187 else:
2188 self.ui.write(patchname, label=b'qseries.' + state)
2188 self.ui.write(patchname, label=b'qseries.' + state)
2189 self.ui.write(b'\n')
2189 self.ui.write(b'\n')
2190
2190
2191 applied = {p.name for p in self.applied}
2191 applied = {p.name for p in self.applied}
2192 if length is None:
2192 if length is None:
2193 length = len(self.series) - start
2193 length = len(self.series) - start
2194 if not missing:
2194 if not missing:
2195 if self.ui.verbose:
2195 if self.ui.verbose:
2196 idxwidth = len(b"%d" % (start + length - 1))
2196 idxwidth = len(b"%d" % (start + length - 1))
2197 for i in pycompat.xrange(start, start + length):
2197 for i in pycompat.xrange(start, start + length):
2198 patch = self.series[i]
2198 patch = self.series[i]
2199 if patch in applied:
2199 if patch in applied:
2200 char, state = b'A', b'applied'
2200 char, state = b'A', b'applied'
2201 elif self.pushable(i)[0]:
2201 elif self.pushable(i)[0]:
2202 char, state = b'U', b'unapplied'
2202 char, state = b'U', b'unapplied'
2203 else:
2203 else:
2204 char, state = b'G', b'guarded'
2204 char, state = b'G', b'guarded'
2205 pfx = b''
2205 pfx = b''
2206 if self.ui.verbose:
2206 if self.ui.verbose:
2207 pfx = b'%*d %s ' % (idxwidth, i, char)
2207 pfx = b'%*d %s ' % (idxwidth, i, char)
2208 elif status and status != char:
2208 elif status and status != char:
2209 continue
2209 continue
2210 displayname(pfx, patch, state)
2210 displayname(pfx, patch, state)
2211 else:
2211 else:
2212 msng_list = []
2212 msng_list = []
2213 for root, dirs, files in os.walk(self.path):
2213 for root, dirs, files in os.walk(self.path):
2214 d = root[len(self.path) + 1 :]
2214 d = root[len(self.path) + 1 :]
2215 for f in files:
2215 for f in files:
2216 fl = os.path.join(d, f)
2216 fl = os.path.join(d, f)
2217 if (
2217 if (
2218 fl not in self.series
2218 fl not in self.series
2219 and fl
2219 and fl
2220 not in (
2220 not in (
2221 self.statuspath,
2221 self.statuspath,
2222 self.seriespath,
2222 self.seriespath,
2223 self.guardspath,
2223 self.guardspath,
2224 )
2224 )
2225 and not fl.startswith(b'.')
2225 and not fl.startswith(b'.')
2226 ):
2226 ):
2227 msng_list.append(fl)
2227 msng_list.append(fl)
2228 for x in sorted(msng_list):
2228 for x in sorted(msng_list):
2229 pfx = self.ui.verbose and b'D ' or b''
2229 pfx = self.ui.verbose and b'D ' or b''
2230 displayname(pfx, x, b'missing')
2230 displayname(pfx, x, b'missing')
2231
2231
2232 def issaveline(self, l):
2232 def issaveline(self, l):
2233 if l.name == b'.hg.patches.save.line':
2233 if l.name == b'.hg.patches.save.line':
2234 return True
2234 return True
2235
2235
2236 def qrepo(self, create=False):
2236 def qrepo(self, create=False):
2237 ui = self.baseui.copy()
2237 ui = self.baseui.copy()
2238 # copy back attributes set by ui.pager()
2238 # copy back attributes set by ui.pager()
2239 if self.ui.pageractive and not ui.pageractive:
2239 if self.ui.pageractive and not ui.pageractive:
2240 ui.pageractive = self.ui.pageractive
2240 ui.pageractive = self.ui.pageractive
2241 # internal config: ui.formatted
2241 # internal config: ui.formatted
2242 ui.setconfig(
2242 ui.setconfig(
2243 b'ui',
2243 b'ui',
2244 b'formatted',
2244 b'formatted',
2245 self.ui.config(b'ui', b'formatted'),
2245 self.ui.config(b'ui', b'formatted'),
2246 b'mqpager',
2246 b'mqpager',
2247 )
2247 )
2248 ui.setconfig(
2248 ui.setconfig(
2249 b'ui',
2249 b'ui',
2250 b'interactive',
2250 b'interactive',
2251 self.ui.config(b'ui', b'interactive'),
2251 self.ui.config(b'ui', b'interactive'),
2252 b'mqpager',
2252 b'mqpager',
2253 )
2253 )
2254 if create or os.path.isdir(self.join(b".hg")):
2254 if create or os.path.isdir(self.join(b".hg")):
2255 return hg.repository(ui, path=self.path, create=create)
2255 return hg.repository(ui, path=self.path, create=create)
2256
2256
2257 def restore(self, repo, rev, delete=None, qupdate=None):
2257 def restore(self, repo, rev, delete=None, qupdate=None):
2258 desc = repo[rev].description().strip()
2258 desc = repo[rev].description().strip()
2259 lines = desc.splitlines()
2259 lines = desc.splitlines()
2260 datastart = None
2260 datastart = None
2261 series = []
2261 series = []
2262 applied = []
2262 applied = []
2263 qpp = None
2263 qpp = None
2264 for i, line in enumerate(lines):
2264 for i, line in enumerate(lines):
2265 if line == b'Patch Data:':
2265 if line == b'Patch Data:':
2266 datastart = i + 1
2266 datastart = i + 1
2267 elif line.startswith(b'Dirstate:'):
2267 elif line.startswith(b'Dirstate:'):
2268 l = line.rstrip()
2268 l = line.rstrip()
2269 l = l[10:].split(b' ')
2269 l = l[10:].split(b' ')
2270 qpp = [bin(x) for x in l]
2270 qpp = [bin(x) for x in l]
2271 elif datastart is not None:
2271 elif datastart is not None:
2272 l = line.rstrip()
2272 l = line.rstrip()
2273 n, name = l.split(b':', 1)
2273 n, name = l.split(b':', 1)
2274 if n:
2274 if n:
2275 applied.append(statusentry(bin(n), name))
2275 applied.append(statusentry(bin(n), name))
2276 else:
2276 else:
2277 series.append(l)
2277 series.append(l)
2278 if datastart is None:
2278 if datastart is None:
2279 self.ui.warn(_(b"no saved patch data found\n"))
2279 self.ui.warn(_(b"no saved patch data found\n"))
2280 return 1
2280 return 1
2281 self.ui.warn(_(b"restoring status: %s\n") % lines[0])
2281 self.ui.warn(_(b"restoring status: %s\n") % lines[0])
2282 self.fullseries = series
2282 self.fullseries = series
2283 self.applied = applied
2283 self.applied = applied
2284 self.parseseries()
2284 self.parseseries()
2285 self.seriesdirty = True
2285 self.seriesdirty = True
2286 self.applieddirty = True
2286 self.applieddirty = True
2287 heads = repo.changelog.heads()
2287 heads = repo.changelog.heads()
2288 if delete:
2288 if delete:
2289 if rev not in heads:
2289 if rev not in heads:
2290 self.ui.warn(_(b"save entry has children, leaving it alone\n"))
2290 self.ui.warn(_(b"save entry has children, leaving it alone\n"))
2291 else:
2291 else:
2292 self.ui.warn(_(b"removing save entry %s\n") % short(rev))
2292 self.ui.warn(_(b"removing save entry %s\n") % short(rev))
2293 pp = repo.dirstate.parents()
2293 pp = repo.dirstate.parents()
2294 if rev in pp:
2294 if rev in pp:
2295 update = True
2295 update = True
2296 else:
2296 else:
2297 update = False
2297 update = False
2298 strip(self.ui, repo, [rev], update=update, backup=False)
2298 strip(self.ui, repo, [rev], update=update, backup=False)
2299 if qpp:
2299 if qpp:
2300 self.ui.warn(
2300 self.ui.warn(
2301 _(b"saved queue repository parents: %s %s\n")
2301 _(b"saved queue repository parents: %s %s\n")
2302 % (short(qpp[0]), short(qpp[1]))
2302 % (short(qpp[0]), short(qpp[1]))
2303 )
2303 )
2304 if qupdate:
2304 if qupdate:
2305 self.ui.status(_(b"updating queue directory\n"))
2305 self.ui.status(_(b"updating queue directory\n"))
2306 r = self.qrepo()
2306 r = self.qrepo()
2307 if not r:
2307 if not r:
2308 self.ui.warn(_(b"unable to load queue repository\n"))
2308 self.ui.warn(_(b"unable to load queue repository\n"))
2309 return 1
2309 return 1
2310 hg.clean(r, qpp[0])
2310 hg.clean(r, qpp[0])
2311
2311
2312 def save(self, repo, msg=None):
2312 def save(self, repo, msg=None):
2313 if not self.applied:
2313 if not self.applied:
2314 self.ui.warn(_(b"save: no patches applied, exiting\n"))
2314 self.ui.warn(_(b"save: no patches applied, exiting\n"))
2315 return 1
2315 return 1
2316 if self.issaveline(self.applied[-1]):
2316 if self.issaveline(self.applied[-1]):
2317 self.ui.warn(_(b"status is already saved\n"))
2317 self.ui.warn(_(b"status is already saved\n"))
2318 return 1
2318 return 1
2319
2319
2320 if not msg:
2320 if not msg:
2321 msg = _(b"hg patches saved state")
2321 msg = _(b"hg patches saved state")
2322 else:
2322 else:
2323 msg = b"hg patches: " + msg.rstrip(b'\r\n')
2323 msg = b"hg patches: " + msg.rstrip(b'\r\n')
2324 r = self.qrepo()
2324 r = self.qrepo()
2325 if r:
2325 if r:
2326 pp = r.dirstate.parents()
2326 pp = r.dirstate.parents()
2327 msg += b"\nDirstate: %s %s" % (hex(pp[0]), hex(pp[1]))
2327 msg += b"\nDirstate: %s %s" % (hex(pp[0]), hex(pp[1]))
2328 msg += b"\n\nPatch Data:\n"
2328 msg += b"\n\nPatch Data:\n"
2329 msg += b''.join(b'%s\n' % x for x in self.applied)
2329 msg += b''.join(b'%s\n' % x for x in self.applied)
2330 msg += b''.join(b':%s\n' % x for x in self.fullseries)
2330 msg += b''.join(b':%s\n' % x for x in self.fullseries)
2331 n = repo.commit(msg, force=True)
2331 n = repo.commit(msg, force=True)
2332 if not n:
2332 if not n:
2333 self.ui.warn(_(b"repo commit failed\n"))
2333 self.ui.warn(_(b"repo commit failed\n"))
2334 return 1
2334 return 1
2335 self.applied.append(statusentry(n, b'.hg.patches.save.line'))
2335 self.applied.append(statusentry(n, b'.hg.patches.save.line'))
2336 self.applieddirty = True
2336 self.applieddirty = True
2337 self.removeundo(repo)
2337 self.removeundo(repo)
2338
2338
2339 def fullseriesend(self):
2339 def fullseriesend(self):
2340 if self.applied:
2340 if self.applied:
2341 p = self.applied[-1].name
2341 p = self.applied[-1].name
2342 end = self.findseries(p)
2342 end = self.findseries(p)
2343 if end is None:
2343 if end is None:
2344 return len(self.fullseries)
2344 return len(self.fullseries)
2345 return end + 1
2345 return end + 1
2346 return 0
2346 return 0
2347
2347
2348 def seriesend(self, all_patches=False):
2348 def seriesend(self, all_patches=False):
2349 """If all_patches is False, return the index of the next pushable patch
2349 """If all_patches is False, return the index of the next pushable patch
2350 in the series, or the series length. If all_patches is True, return the
2350 in the series, or the series length. If all_patches is True, return the
2351 index of the first patch past the last applied one.
2351 index of the first patch past the last applied one.
2352 """
2352 """
2353 end = 0
2353 end = 0
2354
2354
2355 def nextpatch(start):
2355 def nextpatch(start):
2356 if all_patches or start >= len(self.series):
2356 if all_patches or start >= len(self.series):
2357 return start
2357 return start
2358 for i in pycompat.xrange(start, len(self.series)):
2358 for i in pycompat.xrange(start, len(self.series)):
2359 p, reason = self.pushable(i)
2359 p, reason = self.pushable(i)
2360 if p:
2360 if p:
2361 return i
2361 return i
2362 self.explainpushable(i)
2362 self.explainpushable(i)
2363 return len(self.series)
2363 return len(self.series)
2364
2364
2365 if self.applied:
2365 if self.applied:
2366 p = self.applied[-1].name
2366 p = self.applied[-1].name
2367 try:
2367 try:
2368 end = self.series.index(p)
2368 end = self.series.index(p)
2369 except ValueError:
2369 except ValueError:
2370 return 0
2370 return 0
2371 return nextpatch(end + 1)
2371 return nextpatch(end + 1)
2372 return nextpatch(end)
2372 return nextpatch(end)
2373
2373
2374 def appliedname(self, index):
2374 def appliedname(self, index):
2375 pname = self.applied[index].name
2375 pname = self.applied[index].name
2376 if not self.ui.verbose:
2376 if not self.ui.verbose:
2377 p = pname
2377 p = pname
2378 else:
2378 else:
2379 p = (b"%d" % self.series.index(pname)) + b" " + pname
2379 p = (b"%d" % self.series.index(pname)) + b" " + pname
2380 return p
2380 return p
2381
2381
2382 def qimport(
2382 def qimport(
2383 self,
2383 self,
2384 repo,
2384 repo,
2385 files,
2385 files,
2386 patchname=None,
2386 patchname=None,
2387 rev=None,
2387 rev=None,
2388 existing=None,
2388 existing=None,
2389 force=None,
2389 force=None,
2390 git=False,
2390 git=False,
2391 ):
2391 ):
2392 def checkseries(patchname):
2392 def checkseries(patchname):
2393 if patchname in self.series:
2393 if patchname in self.series:
2394 raise error.Abort(
2394 raise error.Abort(
2395 _(b'patch %s is already in the series file') % patchname
2395 _(b'patch %s is already in the series file') % patchname
2396 )
2396 )
2397
2397
2398 if rev:
2398 if rev:
2399 if files:
2399 if files:
2400 raise error.Abort(
2400 raise error.Abort(
2401 _(b'option "-r" not valid when importing files')
2401 _(b'option "-r" not valid when importing files')
2402 )
2402 )
2403 rev = scmutil.revrange(repo, rev)
2403 rev = scmutil.revrange(repo, rev)
2404 rev.sort(reverse=True)
2404 rev.sort(reverse=True)
2405 elif not files:
2405 elif not files:
2406 raise error.Abort(_(b'no files or revisions specified'))
2406 raise error.Abort(_(b'no files or revisions specified'))
2407 if (len(files) > 1 or len(rev) > 1) and patchname:
2407 if (len(files) > 1 or len(rev) > 1) and patchname:
2408 raise error.Abort(
2408 raise error.Abort(
2409 _(b'option "-n" not valid when importing multiple patches')
2409 _(b'option "-n" not valid when importing multiple patches')
2410 )
2410 )
2411 imported = []
2411 imported = []
2412 if rev:
2412 if rev:
2413 # If mq patches are applied, we can only import revisions
2413 # If mq patches are applied, we can only import revisions
2414 # that form a linear path to qbase.
2414 # that form a linear path to qbase.
2415 # Otherwise, they should form a linear path to a head.
2415 # Otherwise, they should form a linear path to a head.
2416 heads = repo.changelog.heads(repo.changelog.node(rev.first()))
2416 heads = repo.changelog.heads(repo.changelog.node(rev.first()))
2417 if len(heads) > 1:
2417 if len(heads) > 1:
2418 raise error.Abort(
2418 raise error.Abort(
2419 _(b'revision %d is the root of more than one branch')
2419 _(b'revision %d is the root of more than one branch')
2420 % rev.last()
2420 % rev.last()
2421 )
2421 )
2422 if self.applied:
2422 if self.applied:
2423 base = repo.changelog.node(rev.first())
2423 base = repo.changelog.node(rev.first())
2424 if base in [n.node for n in self.applied]:
2424 if base in [n.node for n in self.applied]:
2425 raise error.Abort(
2425 raise error.Abort(
2426 _(b'revision %d is already managed') % rev.first()
2426 _(b'revision %d is already managed') % rev.first()
2427 )
2427 )
2428 if heads != [self.applied[-1].node]:
2428 if heads != [self.applied[-1].node]:
2429 raise error.Abort(
2429 raise error.Abort(
2430 _(b'revision %d is not the parent of the queue')
2430 _(b'revision %d is not the parent of the queue')
2431 % rev.first()
2431 % rev.first()
2432 )
2432 )
2433 base = repo.changelog.rev(self.applied[0].node)
2433 base = repo.changelog.rev(self.applied[0].node)
2434 lastparent = repo.changelog.parentrevs(base)[0]
2434 lastparent = repo.changelog.parentrevs(base)[0]
2435 else:
2435 else:
2436 if heads != [repo.changelog.node(rev.first())]:
2436 if heads != [repo.changelog.node(rev.first())]:
2437 raise error.Abort(
2437 raise error.Abort(
2438 _(b'revision %d has unmanaged children') % rev.first()
2438 _(b'revision %d has unmanaged children') % rev.first()
2439 )
2439 )
2440 lastparent = None
2440 lastparent = None
2441
2441
2442 diffopts = self.diffopts({b'git': git})
2442 diffopts = self.diffopts({b'git': git})
2443 with repo.transaction(b'qimport') as tr:
2443 with repo.transaction(b'qimport') as tr:
2444 for r in rev:
2444 for r in rev:
2445 if not repo[r].mutable():
2445 if not repo[r].mutable():
2446 raise error.Abort(
2446 raise error.Abort(
2447 _(b'revision %d is not mutable') % r,
2447 _(b'revision %d is not mutable') % r,
2448 hint=_(b"see 'hg help phases' " b'for details'),
2448 hint=_(b"see 'hg help phases' " b'for details'),
2449 )
2449 )
2450 p1, p2 = repo.changelog.parentrevs(r)
2450 p1, p2 = repo.changelog.parentrevs(r)
2451 n = repo.changelog.node(r)
2451 n = repo.changelog.node(r)
2452 if p2 != nullrev:
2452 if p2 != nullrev:
2453 raise error.Abort(
2453 raise error.Abort(
2454 _(b'cannot import merge revision %d') % r
2454 _(b'cannot import merge revision %d') % r
2455 )
2455 )
2456 if lastparent and lastparent != r:
2456 if lastparent and lastparent != r:
2457 raise error.Abort(
2457 raise error.Abort(
2458 _(b'revision %d is not the parent of %d')
2458 _(b'revision %d is not the parent of %d')
2459 % (r, lastparent)
2459 % (r, lastparent)
2460 )
2460 )
2461 lastparent = p1
2461 lastparent = p1
2462
2462
2463 if not patchname:
2463 if not patchname:
2464 patchname = self.makepatchname(
2464 patchname = self.makepatchname(
2465 repo[r].description().split(b'\n', 1)[0],
2465 repo[r].description().split(b'\n', 1)[0],
2466 b'%d.diff' % r,
2466 b'%d.diff' % r,
2467 )
2467 )
2468 checkseries(patchname)
2468 checkseries(patchname)
2469 self.checkpatchname(patchname, force)
2469 self.checkpatchname(patchname, force)
2470 self.fullseries.insert(0, patchname)
2470 self.fullseries.insert(0, patchname)
2471
2471
2472 with self.opener(patchname, b"w") as fp:
2472 with self.opener(patchname, b"w") as fp:
2473 cmdutil.exportfile(repo, [n], fp, opts=diffopts)
2473 cmdutil.exportfile(repo, [n], fp, opts=diffopts)
2474
2474
2475 se = statusentry(n, patchname)
2475 se = statusentry(n, patchname)
2476 self.applied.insert(0, se)
2476 self.applied.insert(0, se)
2477
2477
2478 self.added.append(patchname)
2478 self.added.append(patchname)
2479 imported.append(patchname)
2479 imported.append(patchname)
2480 patchname = None
2480 patchname = None
2481 if rev and repo.ui.configbool(b'mq', b'secret'):
2481 if rev and repo.ui.configbool(b'mq', b'secret'):
2482 # if we added anything with --rev, move the secret root
2482 # if we added anything with --rev, move the secret root
2483 phases.retractboundary(repo, tr, phases.secret, [n])
2483 phases.retractboundary(repo, tr, phases.secret, [n])
2484 self.parseseries()
2484 self.parseseries()
2485 self.applieddirty = True
2485 self.applieddirty = True
2486 self.seriesdirty = True
2486 self.seriesdirty = True
2487
2487
2488 for i, filename in enumerate(files):
2488 for i, filename in enumerate(files):
2489 if existing:
2489 if existing:
2490 if filename == b'-':
2490 if filename == b'-':
2491 raise error.Abort(
2491 raise error.Abort(
2492 _(b'-e is incompatible with import from -')
2492 _(b'-e is incompatible with import from -')
2493 )
2493 )
2494 filename = normname(filename)
2494 filename = normname(filename)
2495 self.checkreservedname(filename)
2495 self.checkreservedname(filename)
2496 if util.url(filename).islocal():
2496 if util.url(filename).islocal():
2497 originpath = self.join(filename)
2497 originpath = self.join(filename)
2498 if not os.path.isfile(originpath):
2498 if not os.path.isfile(originpath):
2499 raise error.Abort(
2499 raise error.Abort(
2500 _(b"patch %s does not exist") % filename
2500 _(b"patch %s does not exist") % filename
2501 )
2501 )
2502
2502
2503 if patchname:
2503 if patchname:
2504 self.checkpatchname(patchname, force)
2504 self.checkpatchname(patchname, force)
2505
2505
2506 self.ui.write(
2506 self.ui.write(
2507 _(b'renaming %s to %s\n') % (filename, patchname)
2507 _(b'renaming %s to %s\n') % (filename, patchname)
2508 )
2508 )
2509 util.rename(originpath, self.join(patchname))
2509 util.rename(originpath, self.join(patchname))
2510 else:
2510 else:
2511 patchname = filename
2511 patchname = filename
2512
2512
2513 else:
2513 else:
2514 if filename == b'-' and not patchname:
2514 if filename == b'-' and not patchname:
2515 raise error.Abort(
2515 raise error.Abort(
2516 _(b'need --name to import a patch from -')
2516 _(b'need --name to import a patch from -')
2517 )
2517 )
2518 elif not patchname:
2518 elif not patchname:
2519 patchname = normname(
2519 patchname = normname(
2520 os.path.basename(filename.rstrip(b'/'))
2520 os.path.basename(filename.rstrip(b'/'))
2521 )
2521 )
2522 self.checkpatchname(patchname, force)
2522 self.checkpatchname(patchname, force)
2523 try:
2523 try:
2524 if filename == b'-':
2524 if filename == b'-':
2525 text = self.ui.fin.read()
2525 text = self.ui.fin.read()
2526 else:
2526 else:
2527 fp = hg.openpath(self.ui, filename)
2527 fp = hg.openpath(self.ui, filename)
2528 text = fp.read()
2528 text = fp.read()
2529 fp.close()
2529 fp.close()
2530 except (OSError, IOError):
2530 except (OSError, IOError):
2531 raise error.Abort(_(b"unable to read file %s") % filename)
2531 raise error.Abort(_(b"unable to read file %s") % filename)
2532 patchf = self.opener(patchname, b"w")
2532 patchf = self.opener(patchname, b"w")
2533 patchf.write(text)
2533 patchf.write(text)
2534 patchf.close()
2534 patchf.close()
2535 if not force:
2535 if not force:
2536 checkseries(patchname)
2536 checkseries(patchname)
2537 if patchname not in self.series:
2537 if patchname not in self.series:
2538 index = self.fullseriesend() + i
2538 index = self.fullseriesend() + i
2539 self.fullseries[index:index] = [patchname]
2539 self.fullseries[index:index] = [patchname]
2540 self.parseseries()
2540 self.parseseries()
2541 self.seriesdirty = True
2541 self.seriesdirty = True
2542 self.ui.warn(_(b"adding %s to series file\n") % patchname)
2542 self.ui.warn(_(b"adding %s to series file\n") % patchname)
2543 self.added.append(patchname)
2543 self.added.append(patchname)
2544 imported.append(patchname)
2544 imported.append(patchname)
2545 patchname = None
2545 patchname = None
2546
2546
2547 self.removeundo(repo)
2547 self.removeundo(repo)
2548 return imported
2548 return imported
2549
2549
2550
2550
2551 def fixkeepchangesopts(ui, opts):
2551 def fixkeepchangesopts(ui, opts):
2552 if (
2552 if (
2553 not ui.configbool(b'mq', b'keepchanges')
2553 not ui.configbool(b'mq', b'keepchanges')
2554 or opts.get(b'force')
2554 or opts.get(b'force')
2555 or opts.get(b'exact')
2555 or opts.get(b'exact')
2556 ):
2556 ):
2557 return opts
2557 return opts
2558 opts = dict(opts)
2558 opts = dict(opts)
2559 opts[b'keep_changes'] = True
2559 opts[b'keep_changes'] = True
2560 return opts
2560 return opts
2561
2561
2562
2562
2563 @command(
2563 @command(
2564 b"qdelete|qremove|qrm",
2564 b"qdelete|qremove|qrm",
2565 [
2565 [
2566 (b'k', b'keep', None, _(b'keep patch file')),
2566 (b'k', b'keep', None, _(b'keep patch file')),
2567 (
2567 (
2568 b'r',
2568 b'r',
2569 b'rev',
2569 b'rev',
2570 [],
2570 [],
2571 _(b'stop managing a revision (DEPRECATED)'),
2571 _(b'stop managing a revision (DEPRECATED)'),
2572 _(b'REV'),
2572 _(b'REV'),
2573 ),
2573 ),
2574 ],
2574 ],
2575 _(b'hg qdelete [-k] [PATCH]...'),
2575 _(b'hg qdelete [-k] [PATCH]...'),
2576 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
2576 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
2577 )
2577 )
2578 def delete(ui, repo, *patches, **opts):
2578 def delete(ui, repo, *patches, **opts):
2579 """remove patches from queue
2579 """remove patches from queue
2580
2580
2581 The patches must not be applied, and at least one patch is required. Exact
2581 The patches must not be applied, and at least one patch is required. Exact
2582 patch identifiers must be given. With -k/--keep, the patch files are
2582 patch identifiers must be given. With -k/--keep, the patch files are
2583 preserved in the patch directory.
2583 preserved in the patch directory.
2584
2584
2585 To stop managing a patch and move it into permanent history,
2585 To stop managing a patch and move it into permanent history,
2586 use the :hg:`qfinish` command."""
2586 use the :hg:`qfinish` command."""
2587 q = repo.mq
2587 q = repo.mq
2588 q.delete(repo, patches, pycompat.byteskwargs(opts))
2588 q.delete(repo, patches, pycompat.byteskwargs(opts))
2589 q.savedirty()
2589 q.savedirty()
2590 return 0
2590 return 0
2591
2591
2592
2592
2593 @command(
2593 @command(
2594 b"qapplied",
2594 b"qapplied",
2595 [(b'1', b'last', None, _(b'show only the preceding applied patch'))]
2595 [(b'1', b'last', None, _(b'show only the preceding applied patch'))]
2596 + seriesopts,
2596 + seriesopts,
2597 _(b'hg qapplied [-1] [-s] [PATCH]'),
2597 _(b'hg qapplied [-1] [-s] [PATCH]'),
2598 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
2598 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
2599 )
2599 )
2600 def applied(ui, repo, patch=None, **opts):
2600 def applied(ui, repo, patch=None, **opts):
2601 """print the patches already applied
2601 """print the patches already applied
2602
2602
2603 Returns 0 on success."""
2603 Returns 0 on success."""
2604
2604
2605 q = repo.mq
2605 q = repo.mq
2606 opts = pycompat.byteskwargs(opts)
2606 opts = pycompat.byteskwargs(opts)
2607
2607
2608 if patch:
2608 if patch:
2609 if patch not in q.series:
2609 if patch not in q.series:
2610 raise error.Abort(_(b"patch %s is not in series file") % patch)
2610 raise error.Abort(_(b"patch %s is not in series file") % patch)
2611 end = q.series.index(patch) + 1
2611 end = q.series.index(patch) + 1
2612 else:
2612 else:
2613 end = q.seriesend(True)
2613 end = q.seriesend(True)
2614
2614
2615 if opts.get(b'last') and not end:
2615 if opts.get(b'last') and not end:
2616 ui.write(_(b"no patches applied\n"))
2616 ui.write(_(b"no patches applied\n"))
2617 return 1
2617 return 1
2618 elif opts.get(b'last') and end == 1:
2618 elif opts.get(b'last') and end == 1:
2619 ui.write(_(b"only one patch applied\n"))
2619 ui.write(_(b"only one patch applied\n"))
2620 return 1
2620 return 1
2621 elif opts.get(b'last'):
2621 elif opts.get(b'last'):
2622 start = end - 2
2622 start = end - 2
2623 end = 1
2623 end = 1
2624 else:
2624 else:
2625 start = 0
2625 start = 0
2626
2626
2627 q.qseries(
2627 q.qseries(
2628 repo, length=end, start=start, status=b'A', summary=opts.get(b'summary')
2628 repo, length=end, start=start, status=b'A', summary=opts.get(b'summary')
2629 )
2629 )
2630
2630
2631
2631
2632 @command(
2632 @command(
2633 b"qunapplied",
2633 b"qunapplied",
2634 [(b'1', b'first', None, _(b'show only the first patch'))] + seriesopts,
2634 [(b'1', b'first', None, _(b'show only the first patch'))] + seriesopts,
2635 _(b'hg qunapplied [-1] [-s] [PATCH]'),
2635 _(b'hg qunapplied [-1] [-s] [PATCH]'),
2636 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
2636 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
2637 )
2637 )
2638 def unapplied(ui, repo, patch=None, **opts):
2638 def unapplied(ui, repo, patch=None, **opts):
2639 """print the patches not yet applied
2639 """print the patches not yet applied
2640
2640
2641 Returns 0 on success."""
2641 Returns 0 on success."""
2642
2642
2643 q = repo.mq
2643 q = repo.mq
2644 opts = pycompat.byteskwargs(opts)
2644 opts = pycompat.byteskwargs(opts)
2645 if patch:
2645 if patch:
2646 if patch not in q.series:
2646 if patch not in q.series:
2647 raise error.Abort(_(b"patch %s is not in series file") % patch)
2647 raise error.Abort(_(b"patch %s is not in series file") % patch)
2648 start = q.series.index(patch) + 1
2648 start = q.series.index(patch) + 1
2649 else:
2649 else:
2650 start = q.seriesend(True)
2650 start = q.seriesend(True)
2651
2651
2652 if start == len(q.series) and opts.get(b'first'):
2652 if start == len(q.series) and opts.get(b'first'):
2653 ui.write(_(b"all patches applied\n"))
2653 ui.write(_(b"all patches applied\n"))
2654 return 1
2654 return 1
2655
2655
2656 if opts.get(b'first'):
2656 if opts.get(b'first'):
2657 length = 1
2657 length = 1
2658 else:
2658 else:
2659 length = None
2659 length = None
2660 q.qseries(
2660 q.qseries(
2661 repo,
2661 repo,
2662 start=start,
2662 start=start,
2663 length=length,
2663 length=length,
2664 status=b'U',
2664 status=b'U',
2665 summary=opts.get(b'summary'),
2665 summary=opts.get(b'summary'),
2666 )
2666 )
2667
2667
2668
2668
2669 @command(
2669 @command(
2670 b"qimport",
2670 b"qimport",
2671 [
2671 [
2672 (b'e', b'existing', None, _(b'import file in patch directory')),
2672 (b'e', b'existing', None, _(b'import file in patch directory')),
2673 (b'n', b'name', b'', _(b'name of patch file'), _(b'NAME')),
2673 (b'n', b'name', b'', _(b'name of patch file'), _(b'NAME')),
2674 (b'f', b'force', None, _(b'overwrite existing files')),
2674 (b'f', b'force', None, _(b'overwrite existing files')),
2675 (
2675 (
2676 b'r',
2676 b'r',
2677 b'rev',
2677 b'rev',
2678 [],
2678 [],
2679 _(b'place existing revisions under mq control'),
2679 _(b'place existing revisions under mq control'),
2680 _(b'REV'),
2680 _(b'REV'),
2681 ),
2681 ),
2682 (b'g', b'git', None, _(b'use git extended diff format')),
2682 (b'g', b'git', None, _(b'use git extended diff format')),
2683 (b'P', b'push', None, _(b'qpush after importing')),
2683 (b'P', b'push', None, _(b'qpush after importing')),
2684 ],
2684 ],
2685 _(b'hg qimport [-e] [-n NAME] [-f] [-g] [-P] [-r REV]... [FILE]...'),
2685 _(b'hg qimport [-e] [-n NAME] [-f] [-g] [-P] [-r REV]... [FILE]...'),
2686 helpcategory=command.CATEGORY_IMPORT_EXPORT,
2686 helpcategory=command.CATEGORY_IMPORT_EXPORT,
2687 )
2687 )
2688 def qimport(ui, repo, *filename, **opts):
2688 def qimport(ui, repo, *filename, **opts):
2689 """import a patch or existing changeset
2689 """import a patch or existing changeset
2690
2690
2691 The patch is inserted into the series after the last applied
2691 The patch is inserted into the series after the last applied
2692 patch. If no patches have been applied, qimport prepends the patch
2692 patch. If no patches have been applied, qimport prepends the patch
2693 to the series.
2693 to the series.
2694
2694
2695 The patch will have the same name as its source file unless you
2695 The patch will have the same name as its source file unless you
2696 give it a new one with -n/--name.
2696 give it a new one with -n/--name.
2697
2697
2698 You can register an existing patch inside the patch directory with
2698 You can register an existing patch inside the patch directory with
2699 the -e/--existing flag.
2699 the -e/--existing flag.
2700
2700
2701 With -f/--force, an existing patch of the same name will be
2701 With -f/--force, an existing patch of the same name will be
2702 overwritten.
2702 overwritten.
2703
2703
2704 An existing changeset may be placed under mq control with -r/--rev
2704 An existing changeset may be placed under mq control with -r/--rev
2705 (e.g. qimport --rev . -n patch will place the current revision
2705 (e.g. qimport --rev . -n patch will place the current revision
2706 under mq control). With -g/--git, patches imported with --rev will
2706 under mq control). With -g/--git, patches imported with --rev will
2707 use the git diff format. See the diffs help topic for information
2707 use the git diff format. See the diffs help topic for information
2708 on why this is important for preserving rename/copy information
2708 on why this is important for preserving rename/copy information
2709 and permission changes. Use :hg:`qfinish` to remove changesets
2709 and permission changes. Use :hg:`qfinish` to remove changesets
2710 from mq control.
2710 from mq control.
2711
2711
2712 To import a patch from standard input, pass - as the patch file.
2712 To import a patch from standard input, pass - as the patch file.
2713 When importing from standard input, a patch name must be specified
2713 When importing from standard input, a patch name must be specified
2714 using the --name flag.
2714 using the --name flag.
2715
2715
2716 To import an existing patch while renaming it::
2716 To import an existing patch while renaming it::
2717
2717
2718 hg qimport -e existing-patch -n new-name
2718 hg qimport -e existing-patch -n new-name
2719
2719
2720 Returns 0 if import succeeded.
2720 Returns 0 if import succeeded.
2721 """
2721 """
2722 opts = pycompat.byteskwargs(opts)
2722 opts = pycompat.byteskwargs(opts)
2723 with repo.lock(): # cause this may move phase
2723 with repo.lock(): # cause this may move phase
2724 q = repo.mq
2724 q = repo.mq
2725 try:
2725 try:
2726 imported = q.qimport(
2726 imported = q.qimport(
2727 repo,
2727 repo,
2728 filename,
2728 filename,
2729 patchname=opts.get(b'name'),
2729 patchname=opts.get(b'name'),
2730 existing=opts.get(b'existing'),
2730 existing=opts.get(b'existing'),
2731 force=opts.get(b'force'),
2731 force=opts.get(b'force'),
2732 rev=opts.get(b'rev'),
2732 rev=opts.get(b'rev'),
2733 git=opts.get(b'git'),
2733 git=opts.get(b'git'),
2734 )
2734 )
2735 finally:
2735 finally:
2736 q.savedirty()
2736 q.savedirty()
2737
2737
2738 if imported and opts.get(b'push') and not opts.get(b'rev'):
2738 if imported and opts.get(b'push') and not opts.get(b'rev'):
2739 return q.push(repo, imported[-1])
2739 return q.push(repo, imported[-1])
2740 return 0
2740 return 0
2741
2741
2742
2742
2743 def qinit(ui, repo, create):
2743 def qinit(ui, repo, create):
2744 """initialize a new queue repository
2744 """initialize a new queue repository
2745
2745
2746 This command also creates a series file for ordering patches, and
2746 This command also creates a series file for ordering patches, and
2747 an mq-specific .hgignore file in the queue repository, to exclude
2747 an mq-specific .hgignore file in the queue repository, to exclude
2748 the status and guards files (these contain mostly transient state).
2748 the status and guards files (these contain mostly transient state).
2749
2749
2750 Returns 0 if initialization succeeded."""
2750 Returns 0 if initialization succeeded."""
2751 q = repo.mq
2751 q = repo.mq
2752 r = q.init(repo, create)
2752 r = q.init(repo, create)
2753 q.savedirty()
2753 q.savedirty()
2754 if r:
2754 if r:
2755 if not os.path.exists(r.wjoin(b'.hgignore')):
2755 if not os.path.exists(r.wjoin(b'.hgignore')):
2756 fp = r.wvfs(b'.hgignore', b'w')
2756 fp = r.wvfs(b'.hgignore', b'w')
2757 fp.write(b'^\\.hg\n')
2757 fp.write(b'^\\.hg\n')
2758 fp.write(b'^\\.mq\n')
2758 fp.write(b'^\\.mq\n')
2759 fp.write(b'syntax: glob\n')
2759 fp.write(b'syntax: glob\n')
2760 fp.write(b'status\n')
2760 fp.write(b'status\n')
2761 fp.write(b'guards\n')
2761 fp.write(b'guards\n')
2762 fp.close()
2762 fp.close()
2763 if not os.path.exists(r.wjoin(b'series')):
2763 if not os.path.exists(r.wjoin(b'series')):
2764 r.wvfs(b'series', b'w').close()
2764 r.wvfs(b'series', b'w').close()
2765 r[None].add([b'.hgignore', b'series'])
2765 r[None].add([b'.hgignore', b'series'])
2766 commands.add(ui, r)
2766 commands.add(ui, r)
2767 return 0
2767 return 0
2768
2768
2769
2769
2770 @command(
2770 @command(
2771 b"qinit",
2771 b"qinit",
2772 [(b'c', b'create-repo', None, _(b'create queue repository'))],
2772 [(b'c', b'create-repo', None, _(b'create queue repository'))],
2773 _(b'hg qinit [-c]'),
2773 _(b'hg qinit [-c]'),
2774 helpcategory=command.CATEGORY_REPO_CREATION,
2774 helpcategory=command.CATEGORY_REPO_CREATION,
2775 helpbasic=True,
2775 helpbasic=True,
2776 )
2776 )
2777 def init(ui, repo, **opts):
2777 def init(ui, repo, **opts):
2778 """init a new queue repository (DEPRECATED)
2778 """init a new queue repository (DEPRECATED)
2779
2779
2780 The queue repository is unversioned by default. If
2780 The queue repository is unversioned by default. If
2781 -c/--create-repo is specified, qinit will create a separate nested
2781 -c/--create-repo is specified, qinit will create a separate nested
2782 repository for patches (qinit -c may also be run later to convert
2782 repository for patches (qinit -c may also be run later to convert
2783 an unversioned patch repository into a versioned one). You can use
2783 an unversioned patch repository into a versioned one). You can use
2784 qcommit to commit changes to this queue repository.
2784 qcommit to commit changes to this queue repository.
2785
2785
2786 This command is deprecated. Without -c, it's implied by other relevant
2786 This command is deprecated. Without -c, it's implied by other relevant
2787 commands. With -c, use :hg:`init --mq` instead."""
2787 commands. With -c, use :hg:`init --mq` instead."""
2788 return qinit(ui, repo, create=opts.get('create_repo'))
2788 return qinit(ui, repo, create=opts.get('create_repo'))
2789
2789
2790
2790
2791 @command(
2791 @command(
2792 b"qclone",
2792 b"qclone",
2793 [
2793 [
2794 (b'', b'pull', None, _(b'use pull protocol to copy metadata')),
2794 (b'', b'pull', None, _(b'use pull protocol to copy metadata')),
2795 (
2795 (
2796 b'U',
2796 b'U',
2797 b'noupdate',
2797 b'noupdate',
2798 None,
2798 None,
2799 _(b'do not update the new working directories'),
2799 _(b'do not update the new working directories'),
2800 ),
2800 ),
2801 (
2801 (
2802 b'',
2802 b'',
2803 b'uncompressed',
2803 b'uncompressed',
2804 None,
2804 None,
2805 _(b'use uncompressed transfer (fast over LAN)'),
2805 _(b'use uncompressed transfer (fast over LAN)'),
2806 ),
2806 ),
2807 (
2807 (
2808 b'p',
2808 b'p',
2809 b'patches',
2809 b'patches',
2810 b'',
2810 b'',
2811 _(b'location of source patch repository'),
2811 _(b'location of source patch repository'),
2812 _(b'REPO'),
2812 _(b'REPO'),
2813 ),
2813 ),
2814 ]
2814 ]
2815 + cmdutil.remoteopts,
2815 + cmdutil.remoteopts,
2816 _(b'hg qclone [OPTION]... SOURCE [DEST]'),
2816 _(b'hg qclone [OPTION]... SOURCE [DEST]'),
2817 helpcategory=command.CATEGORY_REPO_CREATION,
2817 helpcategory=command.CATEGORY_REPO_CREATION,
2818 norepo=True,
2818 norepo=True,
2819 )
2819 )
2820 def clone(ui, source, dest=None, **opts):
2820 def clone(ui, source, dest=None, **opts):
2821 '''clone main and patch repository at same time
2821 '''clone main and patch repository at same time
2822
2822
2823 If source is local, destination will have no patches applied. If
2823 If source is local, destination will have no patches applied. If
2824 source is remote, this command can not check if patches are
2824 source is remote, this command can not check if patches are
2825 applied in source, so cannot guarantee that patches are not
2825 applied in source, so cannot guarantee that patches are not
2826 applied in destination. If you clone remote repository, be sure
2826 applied in destination. If you clone remote repository, be sure
2827 before that it has no patches applied.
2827 before that it has no patches applied.
2828
2828
2829 Source patch repository is looked for in <src>/.hg/patches by
2829 Source patch repository is looked for in <src>/.hg/patches by
2830 default. Use -p <url> to change.
2830 default. Use -p <url> to change.
2831
2831
2832 The patch directory must be a nested Mercurial repository, as
2832 The patch directory must be a nested Mercurial repository, as
2833 would be created by :hg:`init --mq`.
2833 would be created by :hg:`init --mq`.
2834
2834
2835 Return 0 on success.
2835 Return 0 on success.
2836 '''
2836 '''
2837 opts = pycompat.byteskwargs(opts)
2837 opts = pycompat.byteskwargs(opts)
2838
2838
2839 def patchdir(repo):
2839 def patchdir(repo):
2840 """compute a patch repo url from a repo object"""
2840 """compute a patch repo url from a repo object"""
2841 url = repo.url()
2841 url = repo.url()
2842 if url.endswith(b'/'):
2842 if url.endswith(b'/'):
2843 url = url[:-1]
2843 url = url[:-1]
2844 return url + b'/.hg/patches'
2844 return url + b'/.hg/patches'
2845
2845
2846 # main repo (destination and sources)
2846 # main repo (destination and sources)
2847 if dest is None:
2847 if dest is None:
2848 dest = hg.defaultdest(source)
2848 dest = hg.defaultdest(source)
2849 sr = hg.peer(ui, opts, ui.expandpath(source))
2849 sr = hg.peer(ui, opts, ui.expandpath(source))
2850
2850
2851 # patches repo (source only)
2851 # patches repo (source only)
2852 if opts.get(b'patches'):
2852 if opts.get(b'patches'):
2853 patchespath = ui.expandpath(opts.get(b'patches'))
2853 patchespath = ui.expandpath(opts.get(b'patches'))
2854 else:
2854 else:
2855 patchespath = patchdir(sr)
2855 patchespath = patchdir(sr)
2856 try:
2856 try:
2857 hg.peer(ui, opts, patchespath)
2857 hg.peer(ui, opts, patchespath)
2858 except error.RepoError:
2858 except error.RepoError:
2859 raise error.Abort(
2859 raise error.Abort(
2860 _(b'versioned patch repository not found (see init --mq)')
2860 _(b'versioned patch repository not found (see init --mq)')
2861 )
2861 )
2862 qbase, destrev = None, None
2862 qbase, destrev = None, None
2863 if sr.local():
2863 if sr.local():
2864 repo = sr.local()
2864 repo = sr.local()
2865 if repo.mq.applied and repo[qbase].phase() != phases.secret:
2865 if repo.mq.applied and repo[qbase].phase() != phases.secret:
2866 qbase = repo.mq.applied[0].node
2866 qbase = repo.mq.applied[0].node
2867 if not hg.islocal(dest):
2867 if not hg.islocal(dest):
2868 heads = set(repo.heads())
2868 heads = set(repo.heads())
2869 destrev = list(heads.difference(repo.heads(qbase)))
2869 destrev = list(heads.difference(repo.heads(qbase)))
2870 destrev.append(repo.changelog.parents(qbase)[0])
2870 destrev.append(repo.changelog.parents(qbase)[0])
2871 elif sr.capable(b'lookup'):
2871 elif sr.capable(b'lookup'):
2872 try:
2872 try:
2873 qbase = sr.lookup(b'qbase')
2873 qbase = sr.lookup(b'qbase')
2874 except error.RepoError:
2874 except error.RepoError:
2875 pass
2875 pass
2876
2876
2877 ui.note(_(b'cloning main repository\n'))
2877 ui.note(_(b'cloning main repository\n'))
2878 sr, dr = hg.clone(
2878 sr, dr = hg.clone(
2879 ui,
2879 ui,
2880 opts,
2880 opts,
2881 sr.url(),
2881 sr.url(),
2882 dest,
2882 dest,
2883 pull=opts.get(b'pull'),
2883 pull=opts.get(b'pull'),
2884 revs=destrev,
2884 revs=destrev,
2885 update=False,
2885 update=False,
2886 stream=opts.get(b'uncompressed'),
2886 stream=opts.get(b'uncompressed'),
2887 )
2887 )
2888
2888
2889 ui.note(_(b'cloning patch repository\n'))
2889 ui.note(_(b'cloning patch repository\n'))
2890 hg.clone(
2890 hg.clone(
2891 ui,
2891 ui,
2892 opts,
2892 opts,
2893 opts.get(b'patches') or patchdir(sr),
2893 opts.get(b'patches') or patchdir(sr),
2894 patchdir(dr),
2894 patchdir(dr),
2895 pull=opts.get(b'pull'),
2895 pull=opts.get(b'pull'),
2896 update=not opts.get(b'noupdate'),
2896 update=not opts.get(b'noupdate'),
2897 stream=opts.get(b'uncompressed'),
2897 stream=opts.get(b'uncompressed'),
2898 )
2898 )
2899
2899
2900 if dr.local():
2900 if dr.local():
2901 repo = dr.local()
2901 repo = dr.local()
2902 if qbase:
2902 if qbase:
2903 ui.note(
2903 ui.note(
2904 _(
2904 _(
2905 b'stripping applied patches from destination '
2905 b'stripping applied patches from destination '
2906 b'repository\n'
2906 b'repository\n'
2907 )
2907 )
2908 )
2908 )
2909 strip(ui, repo, [qbase], update=False, backup=None)
2909 strip(ui, repo, [qbase], update=False, backup=None)
2910 if not opts.get(b'noupdate'):
2910 if not opts.get(b'noupdate'):
2911 ui.note(_(b'updating destination repository\n'))
2911 ui.note(_(b'updating destination repository\n'))
2912 hg.update(repo, repo.changelog.tip())
2912 hg.update(repo, repo.changelog.tip())
2913
2913
2914
2914
2915 @command(
2915 @command(
2916 b"qcommit|qci",
2916 b"qcommit|qci",
2917 commands.table[b"commit|ci"][1],
2917 commands.table[b"commit|ci"][1],
2918 _(b'hg qcommit [OPTION]... [FILE]...'),
2918 _(b'hg qcommit [OPTION]... [FILE]...'),
2919 helpcategory=command.CATEGORY_COMMITTING,
2919 helpcategory=command.CATEGORY_COMMITTING,
2920 inferrepo=True,
2920 inferrepo=True,
2921 )
2921 )
2922 def commit(ui, repo, *pats, **opts):
2922 def commit(ui, repo, *pats, **opts):
2923 """commit changes in the queue repository (DEPRECATED)
2923 """commit changes in the queue repository (DEPRECATED)
2924
2924
2925 This command is deprecated; use :hg:`commit --mq` instead."""
2925 This command is deprecated; use :hg:`commit --mq` instead."""
2926 q = repo.mq
2926 q = repo.mq
2927 r = q.qrepo()
2927 r = q.qrepo()
2928 if not r:
2928 if not r:
2929 raise error.Abort(b'no queue repository')
2929 raise error.Abort(b'no queue repository')
2930 commands.commit(r.ui, r, *pats, **opts)
2930 commands.commit(r.ui, r, *pats, **opts)
2931
2931
2932
2932
2933 @command(
2933 @command(
2934 b"qseries",
2934 b"qseries",
2935 [(b'm', b'missing', None, _(b'print patches not in series')),] + seriesopts,
2935 [(b'm', b'missing', None, _(b'print patches not in series')),] + seriesopts,
2936 _(b'hg qseries [-ms]'),
2936 _(b'hg qseries [-ms]'),
2937 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
2937 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
2938 )
2938 )
2939 def series(ui, repo, **opts):
2939 def series(ui, repo, **opts):
2940 """print the entire series file
2940 """print the entire series file
2941
2941
2942 Returns 0 on success."""
2942 Returns 0 on success."""
2943 repo.mq.qseries(
2943 repo.mq.qseries(
2944 repo, missing=opts.get('missing'), summary=opts.get('summary')
2944 repo, missing=opts.get('missing'), summary=opts.get('summary')
2945 )
2945 )
2946 return 0
2946 return 0
2947
2947
2948
2948
2949 @command(
2949 @command(
2950 b"qtop",
2950 b"qtop",
2951 seriesopts,
2951 seriesopts,
2952 _(b'hg qtop [-s]'),
2952 _(b'hg qtop [-s]'),
2953 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
2953 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
2954 )
2954 )
2955 def top(ui, repo, **opts):
2955 def top(ui, repo, **opts):
2956 """print the name of the current patch
2956 """print the name of the current patch
2957
2957
2958 Returns 0 on success."""
2958 Returns 0 on success."""
2959 q = repo.mq
2959 q = repo.mq
2960 if q.applied:
2960 if q.applied:
2961 t = q.seriesend(True)
2961 t = q.seriesend(True)
2962 else:
2962 else:
2963 t = 0
2963 t = 0
2964
2964
2965 if t:
2965 if t:
2966 q.qseries(
2966 q.qseries(
2967 repo,
2967 repo,
2968 start=t - 1,
2968 start=t - 1,
2969 length=1,
2969 length=1,
2970 status=b'A',
2970 status=b'A',
2971 summary=opts.get('summary'),
2971 summary=opts.get('summary'),
2972 )
2972 )
2973 else:
2973 else:
2974 ui.write(_(b"no patches applied\n"))
2974 ui.write(_(b"no patches applied\n"))
2975 return 1
2975 return 1
2976
2976
2977
2977
2978 @command(
2978 @command(
2979 b"qnext",
2979 b"qnext",
2980 seriesopts,
2980 seriesopts,
2981 _(b'hg qnext [-s]'),
2981 _(b'hg qnext [-s]'),
2982 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
2982 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
2983 )
2983 )
2984 def next(ui, repo, **opts):
2984 def next(ui, repo, **opts):
2985 """print the name of the next pushable patch
2985 """print the name of the next pushable patch
2986
2986
2987 Returns 0 on success."""
2987 Returns 0 on success."""
2988 q = repo.mq
2988 q = repo.mq
2989 end = q.seriesend()
2989 end = q.seriesend()
2990 if end == len(q.series):
2990 if end == len(q.series):
2991 ui.write(_(b"all patches applied\n"))
2991 ui.write(_(b"all patches applied\n"))
2992 return 1
2992 return 1
2993 q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
2993 q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
2994
2994
2995
2995
2996 @command(
2996 @command(
2997 b"qprev",
2997 b"qprev",
2998 seriesopts,
2998 seriesopts,
2999 _(b'hg qprev [-s]'),
2999 _(b'hg qprev [-s]'),
3000 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
3000 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
3001 )
3001 )
3002 def prev(ui, repo, **opts):
3002 def prev(ui, repo, **opts):
3003 """print the name of the preceding applied patch
3003 """print the name of the preceding applied patch
3004
3004
3005 Returns 0 on success."""
3005 Returns 0 on success."""
3006 q = repo.mq
3006 q = repo.mq
3007 l = len(q.applied)
3007 l = len(q.applied)
3008 if l == 1:
3008 if l == 1:
3009 ui.write(_(b"only one patch applied\n"))
3009 ui.write(_(b"only one patch applied\n"))
3010 return 1
3010 return 1
3011 if not l:
3011 if not l:
3012 ui.write(_(b"no patches applied\n"))
3012 ui.write(_(b"no patches applied\n"))
3013 return 1
3013 return 1
3014 idx = q.series.index(q.applied[-2].name)
3014 idx = q.series.index(q.applied[-2].name)
3015 q.qseries(
3015 q.qseries(
3016 repo, start=idx, length=1, status=b'A', summary=opts.get('summary')
3016 repo, start=idx, length=1, status=b'A', summary=opts.get('summary')
3017 )
3017 )
3018
3018
3019
3019
3020 def setupheaderopts(ui, opts):
3020 def setupheaderopts(ui, opts):
3021 if not opts.get(b'user') and opts.get(b'currentuser'):
3021 if not opts.get(b'user') and opts.get(b'currentuser'):
3022 opts[b'user'] = ui.username()
3022 opts[b'user'] = ui.username()
3023 if not opts.get(b'date') and opts.get(b'currentdate'):
3023 if not opts.get(b'date') and opts.get(b'currentdate'):
3024 opts[b'date'] = b"%d %d" % dateutil.makedate()
3024 opts[b'date'] = b"%d %d" % dateutil.makedate()
3025
3025
3026
3026
3027 @command(
3027 @command(
3028 b"qnew",
3028 b"qnew",
3029 [
3029 [
3030 (b'e', b'edit', None, _(b'invoke editor on commit messages')),
3030 (b'e', b'edit', None, _(b'invoke editor on commit messages')),
3031 (b'f', b'force', None, _(b'import uncommitted changes (DEPRECATED)')),
3031 (b'f', b'force', None, _(b'import uncommitted changes (DEPRECATED)')),
3032 (b'g', b'git', None, _(b'use git extended diff format')),
3032 (b'g', b'git', None, _(b'use git extended diff format')),
3033 (b'U', b'currentuser', None, _(b'add "From: <current user>" to patch')),
3033 (b'U', b'currentuser', None, _(b'add "From: <current user>" to patch')),
3034 (b'u', b'user', b'', _(b'add "From: <USER>" to patch'), _(b'USER')),
3034 (b'u', b'user', b'', _(b'add "From: <USER>" to patch'), _(b'USER')),
3035 (b'D', b'currentdate', None, _(b'add "Date: <current date>" to patch')),
3035 (b'D', b'currentdate', None, _(b'add "Date: <current date>" to patch')),
3036 (b'd', b'date', b'', _(b'add "Date: <DATE>" to patch'), _(b'DATE')),
3036 (b'd', b'date', b'', _(b'add "Date: <DATE>" to patch'), _(b'DATE')),
3037 ]
3037 ]
3038 + cmdutil.walkopts
3038 + cmdutil.walkopts
3039 + cmdutil.commitopts,
3039 + cmdutil.commitopts,
3040 _(b'hg qnew [-e] [-m TEXT] [-l FILE] PATCH [FILE]...'),
3040 _(b'hg qnew [-e] [-m TEXT] [-l FILE] PATCH [FILE]...'),
3041 helpcategory=command.CATEGORY_COMMITTING,
3041 helpcategory=command.CATEGORY_COMMITTING,
3042 helpbasic=True,
3042 helpbasic=True,
3043 inferrepo=True,
3043 inferrepo=True,
3044 )
3044 )
3045 def new(ui, repo, patch, *args, **opts):
3045 def new(ui, repo, patch, *args, **opts):
3046 """create a new patch
3046 """create a new patch
3047
3047
3048 qnew creates a new patch on top of the currently-applied patch (if
3048 qnew creates a new patch on top of the currently-applied patch (if
3049 any). The patch will be initialized with any outstanding changes
3049 any). The patch will be initialized with any outstanding changes
3050 in the working directory. You may also use -I/--include,
3050 in the working directory. You may also use -I/--include,
3051 -X/--exclude, and/or a list of files after the patch name to add
3051 -X/--exclude, and/or a list of files after the patch name to add
3052 only changes to matching files to the new patch, leaving the rest
3052 only changes to matching files to the new patch, leaving the rest
3053 as uncommitted modifications.
3053 as uncommitted modifications.
3054
3054
3055 -u/--user and -d/--date can be used to set the (given) user and
3055 -u/--user and -d/--date can be used to set the (given) user and
3056 date, respectively. -U/--currentuser and -D/--currentdate set user
3056 date, respectively. -U/--currentuser and -D/--currentdate set user
3057 to current user and date to current date.
3057 to current user and date to current date.
3058
3058
3059 -e/--edit, -m/--message or -l/--logfile set the patch header as
3059 -e/--edit, -m/--message or -l/--logfile set the patch header as
3060 well as the commit message. If none is specified, the header is
3060 well as the commit message. If none is specified, the header is
3061 empty and the commit message is '[mq]: PATCH'.
3061 empty and the commit message is '[mq]: PATCH'.
3062
3062
3063 Use the -g/--git option to keep the patch in the git extended diff
3063 Use the -g/--git option to keep the patch in the git extended diff
3064 format. Read the diffs help topic for more information on why this
3064 format. Read the diffs help topic for more information on why this
3065 is important for preserving permission changes and copy/rename
3065 is important for preserving permission changes and copy/rename
3066 information.
3066 information.
3067
3067
3068 Returns 0 on successful creation of a new patch.
3068 Returns 0 on successful creation of a new patch.
3069 """
3069 """
3070 opts = pycompat.byteskwargs(opts)
3070 opts = pycompat.byteskwargs(opts)
3071 msg = cmdutil.logmessage(ui, opts)
3071 msg = cmdutil.logmessage(ui, opts)
3072 q = repo.mq
3072 q = repo.mq
3073 opts[b'msg'] = msg
3073 opts[b'msg'] = msg
3074 setupheaderopts(ui, opts)
3074 setupheaderopts(ui, opts)
3075 q.new(repo, patch, *args, **pycompat.strkwargs(opts))
3075 q.new(repo, patch, *args, **pycompat.strkwargs(opts))
3076 q.savedirty()
3076 q.savedirty()
3077 return 0
3077 return 0
3078
3078
3079
3079
3080 @command(
3080 @command(
3081 b"qrefresh",
3081 b"qrefresh",
3082 [
3082 [
3083 (b'e', b'edit', None, _(b'invoke editor on commit messages')),
3083 (b'e', b'edit', None, _(b'invoke editor on commit messages')),
3084 (b'g', b'git', None, _(b'use git extended diff format')),
3084 (b'g', b'git', None, _(b'use git extended diff format')),
3085 (
3085 (
3086 b's',
3086 b's',
3087 b'short',
3087 b'short',
3088 None,
3088 None,
3089 _(b'refresh only files already in the patch and specified files'),
3089 _(b'refresh only files already in the patch and specified files'),
3090 ),
3090 ),
3091 (
3091 (
3092 b'U',
3092 b'U',
3093 b'currentuser',
3093 b'currentuser',
3094 None,
3094 None,
3095 _(b'add/update author field in patch with current user'),
3095 _(b'add/update author field in patch with current user'),
3096 ),
3096 ),
3097 (
3097 (
3098 b'u',
3098 b'u',
3099 b'user',
3099 b'user',
3100 b'',
3100 b'',
3101 _(b'add/update author field in patch with given user'),
3101 _(b'add/update author field in patch with given user'),
3102 _(b'USER'),
3102 _(b'USER'),
3103 ),
3103 ),
3104 (
3104 (
3105 b'D',
3105 b'D',
3106 b'currentdate',
3106 b'currentdate',
3107 None,
3107 None,
3108 _(b'add/update date field in patch with current date'),
3108 _(b'add/update date field in patch with current date'),
3109 ),
3109 ),
3110 (
3110 (
3111 b'd',
3111 b'd',
3112 b'date',
3112 b'date',
3113 b'',
3113 b'',
3114 _(b'add/update date field in patch with given date'),
3114 _(b'add/update date field in patch with given date'),
3115 _(b'DATE'),
3115 _(b'DATE'),
3116 ),
3116 ),
3117 ]
3117 ]
3118 + cmdutil.walkopts
3118 + cmdutil.walkopts
3119 + cmdutil.commitopts,
3119 + cmdutil.commitopts,
3120 _(b'hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...'),
3120 _(b'hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...'),
3121 helpcategory=command.CATEGORY_COMMITTING,
3121 helpcategory=command.CATEGORY_COMMITTING,
3122 helpbasic=True,
3122 helpbasic=True,
3123 inferrepo=True,
3123 inferrepo=True,
3124 )
3124 )
3125 def refresh(ui, repo, *pats, **opts):
3125 def refresh(ui, repo, *pats, **opts):
3126 """update the current patch
3126 """update the current patch
3127
3127
3128 If any file patterns are provided, the refreshed patch will
3128 If any file patterns are provided, the refreshed patch will
3129 contain only the modifications that match those patterns; the
3129 contain only the modifications that match those patterns; the
3130 remaining modifications will remain in the working directory.
3130 remaining modifications will remain in the working directory.
3131
3131
3132 If -s/--short is specified, files currently included in the patch
3132 If -s/--short is specified, files currently included in the patch
3133 will be refreshed just like matched files and remain in the patch.
3133 will be refreshed just like matched files and remain in the patch.
3134
3134
3135 If -e/--edit is specified, Mercurial will start your configured editor for
3135 If -e/--edit is specified, Mercurial will start your configured editor for
3136 you to enter a message. In case qrefresh fails, you will find a backup of
3136 you to enter a message. In case qrefresh fails, you will find a backup of
3137 your message in ``.hg/last-message.txt``.
3137 your message in ``.hg/last-message.txt``.
3138
3138
3139 hg add/remove/copy/rename work as usual, though you might want to
3139 hg add/remove/copy/rename work as usual, though you might want to
3140 use git-style patches (-g/--git or [diff] git=1) to track copies
3140 use git-style patches (-g/--git or [diff] git=1) to track copies
3141 and renames. See the diffs help topic for more information on the
3141 and renames. See the diffs help topic for more information on the
3142 git diff format.
3142 git diff format.
3143
3143
3144 Returns 0 on success.
3144 Returns 0 on success.
3145 """
3145 """
3146 opts = pycompat.byteskwargs(opts)
3146 opts = pycompat.byteskwargs(opts)
3147 q = repo.mq
3147 q = repo.mq
3148 message = cmdutil.logmessage(ui, opts)
3148 message = cmdutil.logmessage(ui, opts)
3149 setupheaderopts(ui, opts)
3149 setupheaderopts(ui, opts)
3150 with repo.wlock():
3150 with repo.wlock():
3151 ret = q.refresh(repo, pats, msg=message, **pycompat.strkwargs(opts))
3151 ret = q.refresh(repo, pats, msg=message, **pycompat.strkwargs(opts))
3152 q.savedirty()
3152 q.savedirty()
3153 return ret
3153 return ret
3154
3154
3155
3155
3156 @command(
3156 @command(
3157 b"qdiff",
3157 b"qdiff",
3158 cmdutil.diffopts + cmdutil.diffopts2 + cmdutil.walkopts,
3158 cmdutil.diffopts + cmdutil.diffopts2 + cmdutil.walkopts,
3159 _(b'hg qdiff [OPTION]... [FILE]...'),
3159 _(b'hg qdiff [OPTION]... [FILE]...'),
3160 helpcategory=command.CATEGORY_FILE_CONTENTS,
3160 helpcategory=command.CATEGORY_FILE_CONTENTS,
3161 helpbasic=True,
3161 helpbasic=True,
3162 inferrepo=True,
3162 inferrepo=True,
3163 )
3163 )
3164 def diff(ui, repo, *pats, **opts):
3164 def diff(ui, repo, *pats, **opts):
3165 """diff of the current patch and subsequent modifications
3165 """diff of the current patch and subsequent modifications
3166
3166
3167 Shows a diff which includes the current patch as well as any
3167 Shows a diff which includes the current patch as well as any
3168 changes which have been made in the working directory since the
3168 changes which have been made in the working directory since the
3169 last refresh (thus showing what the current patch would become
3169 last refresh (thus showing what the current patch would become
3170 after a qrefresh).
3170 after a qrefresh).
3171
3171
3172 Use :hg:`diff` if you only want to see the changes made since the
3172 Use :hg:`diff` if you only want to see the changes made since the
3173 last qrefresh, or :hg:`export qtip` if you want to see changes
3173 last qrefresh, or :hg:`export qtip` if you want to see changes
3174 made by the current patch without including changes made since the
3174 made by the current patch without including changes made since the
3175 qrefresh.
3175 qrefresh.
3176
3176
3177 Returns 0 on success.
3177 Returns 0 on success.
3178 """
3178 """
3179 ui.pager(b'qdiff')
3179 ui.pager(b'qdiff')
3180 repo.mq.diff(repo, pats, pycompat.byteskwargs(opts))
3180 repo.mq.diff(repo, pats, pycompat.byteskwargs(opts))
3181 return 0
3181 return 0
3182
3182
3183
3183
3184 @command(
3184 @command(
3185 b'qfold',
3185 b'qfold',
3186 [
3186 [
3187 (b'e', b'edit', None, _(b'invoke editor on commit messages')),
3187 (b'e', b'edit', None, _(b'invoke editor on commit messages')),
3188 (b'k', b'keep', None, _(b'keep folded patch files')),
3188 (b'k', b'keep', None, _(b'keep folded patch files')),
3189 ]
3189 ]
3190 + cmdutil.commitopts,
3190 + cmdutil.commitopts,
3191 _(b'hg qfold [-e] [-k] [-m TEXT] [-l FILE] PATCH...'),
3191 _(b'hg qfold [-e] [-k] [-m TEXT] [-l FILE] PATCH...'),
3192 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
3192 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
3193 )
3193 )
3194 def fold(ui, repo, *files, **opts):
3194 def fold(ui, repo, *files, **opts):
3195 """fold the named patches into the current patch
3195 """fold the named patches into the current patch
3196
3196
3197 Patches must not yet be applied. Each patch will be successively
3197 Patches must not yet be applied. Each patch will be successively
3198 applied to the current patch in the order given. If all the
3198 applied to the current patch in the order given. If all the
3199 patches apply successfully, the current patch will be refreshed
3199 patches apply successfully, the current patch will be refreshed
3200 with the new cumulative patch, and the folded patches will be
3200 with the new cumulative patch, and the folded patches will be
3201 deleted. With -k/--keep, the folded patch files will not be
3201 deleted. With -k/--keep, the folded patch files will not be
3202 removed afterwards.
3202 removed afterwards.
3203
3203
3204 The header for each folded patch will be concatenated with the
3204 The header for each folded patch will be concatenated with the
3205 current patch header, separated by a line of ``* * *``.
3205 current patch header, separated by a line of ``* * *``.
3206
3206
3207 Returns 0 on success."""
3207 Returns 0 on success."""
3208 opts = pycompat.byteskwargs(opts)
3208 opts = pycompat.byteskwargs(opts)
3209 q = repo.mq
3209 q = repo.mq
3210 if not files:
3210 if not files:
3211 raise error.Abort(_(b'qfold requires at least one patch name'))
3211 raise error.Abort(_(b'qfold requires at least one patch name'))
3212 if not q.checktoppatch(repo)[0]:
3212 if not q.checktoppatch(repo)[0]:
3213 raise error.Abort(_(b'no patches applied'))
3213 raise error.Abort(_(b'no patches applied'))
3214 q.checklocalchanges(repo)
3214 q.checklocalchanges(repo)
3215
3215
3216 message = cmdutil.logmessage(ui, opts)
3216 message = cmdutil.logmessage(ui, opts)
3217
3217
3218 parent = q.lookup(b'qtip')
3218 parent = q.lookup(b'qtip')
3219 patches = []
3219 patches = []
3220 messages = []
3220 messages = []
3221 for f in files:
3221 for f in files:
3222 p = q.lookup(f)
3222 p = q.lookup(f)
3223 if p in patches or p == parent:
3223 if p in patches or p == parent:
3224 ui.warn(_(b'skipping already folded patch %s\n') % p)
3224 ui.warn(_(b'skipping already folded patch %s\n') % p)
3225 if q.isapplied(p):
3225 if q.isapplied(p):
3226 raise error.Abort(
3226 raise error.Abort(
3227 _(b'qfold cannot fold already applied patch %s') % p
3227 _(b'qfold cannot fold already applied patch %s') % p
3228 )
3228 )
3229 patches.append(p)
3229 patches.append(p)
3230
3230
3231 for p in patches:
3231 for p in patches:
3232 if not message:
3232 if not message:
3233 ph = patchheader(q.join(p), q.plainmode)
3233 ph = patchheader(q.join(p), q.plainmode)
3234 if ph.message:
3234 if ph.message:
3235 messages.append(ph.message)
3235 messages.append(ph.message)
3236 pf = q.join(p)
3236 pf = q.join(p)
3237 (patchsuccess, files, fuzz) = q.patch(repo, pf)
3237 (patchsuccess, files, fuzz) = q.patch(repo, pf)
3238 if not patchsuccess:
3238 if not patchsuccess:
3239 raise error.Abort(_(b'error folding patch %s') % p)
3239 raise error.Abort(_(b'error folding patch %s') % p)
3240
3240
3241 if not message:
3241 if not message:
3242 ph = patchheader(q.join(parent), q.plainmode)
3242 ph = patchheader(q.join(parent), q.plainmode)
3243 message = ph.message
3243 message = ph.message
3244 for msg in messages:
3244 for msg in messages:
3245 if msg:
3245 if msg:
3246 if message:
3246 if message:
3247 message.append(b'* * *')
3247 message.append(b'* * *')
3248 message.extend(msg)
3248 message.extend(msg)
3249 message = b'\n'.join(message)
3249 message = b'\n'.join(message)
3250
3250
3251 diffopts = q.patchopts(q.diffopts(), *patches)
3251 diffopts = q.patchopts(q.diffopts(), *patches)
3252 with repo.wlock():
3252 with repo.wlock():
3253 q.refresh(
3253 q.refresh(
3254 repo,
3254 repo,
3255 msg=message,
3255 msg=message,
3256 git=diffopts.git,
3256 git=diffopts.git,
3257 edit=opts.get(b'edit'),
3257 edit=opts.get(b'edit'),
3258 editform=b'mq.qfold',
3258 editform=b'mq.qfold',
3259 )
3259 )
3260 q.delete(repo, patches, opts)
3260 q.delete(repo, patches, opts)
3261 q.savedirty()
3261 q.savedirty()
3262
3262
3263
3263
3264 @command(
3264 @command(
3265 b"qgoto",
3265 b"qgoto",
3266 [
3266 [
3267 (
3267 (
3268 b'',
3268 b'',
3269 b'keep-changes',
3269 b'keep-changes',
3270 None,
3270 None,
3271 _(b'tolerate non-conflicting local changes'),
3271 _(b'tolerate non-conflicting local changes'),
3272 ),
3272 ),
3273 (b'f', b'force', None, _(b'overwrite any local changes')),
3273 (b'f', b'force', None, _(b'overwrite any local changes')),
3274 (b'', b'no-backup', None, _(b'do not save backup copies of files')),
3274 (b'', b'no-backup', None, _(b'do not save backup copies of files')),
3275 ],
3275 ],
3276 _(b'hg qgoto [OPTION]... PATCH'),
3276 _(b'hg qgoto [OPTION]... PATCH'),
3277 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
3277 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
3278 )
3278 )
3279 def goto(ui, repo, patch, **opts):
3279 def goto(ui, repo, patch, **opts):
3280 '''push or pop patches until named patch is at top of stack
3280 '''push or pop patches until named patch is at top of stack
3281
3281
3282 Returns 0 on success.'''
3282 Returns 0 on success.'''
3283 opts = pycompat.byteskwargs(opts)
3283 opts = pycompat.byteskwargs(opts)
3284 opts = fixkeepchangesopts(ui, opts)
3284 opts = fixkeepchangesopts(ui, opts)
3285 q = repo.mq
3285 q = repo.mq
3286 patch = q.lookup(patch)
3286 patch = q.lookup(patch)
3287 nobackup = opts.get(b'no_backup')
3287 nobackup = opts.get(b'no_backup')
3288 keepchanges = opts.get(b'keep_changes')
3288 keepchanges = opts.get(b'keep_changes')
3289 if q.isapplied(patch):
3289 if q.isapplied(patch):
3290 ret = q.pop(
3290 ret = q.pop(
3291 repo,
3291 repo,
3292 patch,
3292 patch,
3293 force=opts.get(b'force'),
3293 force=opts.get(b'force'),
3294 nobackup=nobackup,
3294 nobackup=nobackup,
3295 keepchanges=keepchanges,
3295 keepchanges=keepchanges,
3296 )
3296 )
3297 else:
3297 else:
3298 ret = q.push(
3298 ret = q.push(
3299 repo,
3299 repo,
3300 patch,
3300 patch,
3301 force=opts.get(b'force'),
3301 force=opts.get(b'force'),
3302 nobackup=nobackup,
3302 nobackup=nobackup,
3303 keepchanges=keepchanges,
3303 keepchanges=keepchanges,
3304 )
3304 )
3305 q.savedirty()
3305 q.savedirty()
3306 return ret
3306 return ret
3307
3307
3308
3308
3309 @command(
3309 @command(
3310 b"qguard",
3310 b"qguard",
3311 [
3311 [
3312 (b'l', b'list', None, _(b'list all patches and guards')),
3312 (b'l', b'list', None, _(b'list all patches and guards')),
3313 (b'n', b'none', None, _(b'drop all guards')),
3313 (b'n', b'none', None, _(b'drop all guards')),
3314 ],
3314 ],
3315 _(b'hg qguard [-l] [-n] [PATCH] [-- [+GUARD]... [-GUARD]...]'),
3315 _(b'hg qguard [-l] [-n] [PATCH] [-- [+GUARD]... [-GUARD]...]'),
3316 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
3316 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
3317 )
3317 )
3318 def guard(ui, repo, *args, **opts):
3318 def guard(ui, repo, *args, **opts):
3319 '''set or print guards for a patch
3319 '''set or print guards for a patch
3320
3320
3321 Guards control whether a patch can be pushed. A patch with no
3321 Guards control whether a patch can be pushed. A patch with no
3322 guards is always pushed. A patch with a positive guard ("+foo") is
3322 guards is always pushed. A patch with a positive guard ("+foo") is
3323 pushed only if the :hg:`qselect` command has activated it. A patch with
3323 pushed only if the :hg:`qselect` command has activated it. A patch with
3324 a negative guard ("-foo") is never pushed if the :hg:`qselect` command
3324 a negative guard ("-foo") is never pushed if the :hg:`qselect` command
3325 has activated it.
3325 has activated it.
3326
3326
3327 With no arguments, print the currently active guards.
3327 With no arguments, print the currently active guards.
3328 With arguments, set guards for the named patch.
3328 With arguments, set guards for the named patch.
3329
3329
3330 .. note::
3330 .. note::
3331
3331
3332 Specifying negative guards now requires '--'.
3332 Specifying negative guards now requires '--'.
3333
3333
3334 To set guards on another patch::
3334 To set guards on another patch::
3335
3335
3336 hg qguard other.patch -- +2.6.17 -stable
3336 hg qguard other.patch -- +2.6.17 -stable
3337
3337
3338 Returns 0 on success.
3338 Returns 0 on success.
3339 '''
3339 '''
3340
3340
3341 def status(idx):
3341 def status(idx):
3342 guards = q.seriesguards[idx] or [b'unguarded']
3342 guards = q.seriesguards[idx] or [b'unguarded']
3343 if q.series[idx] in applied:
3343 if q.series[idx] in applied:
3344 state = b'applied'
3344 state = b'applied'
3345 elif q.pushable(idx)[0]:
3345 elif q.pushable(idx)[0]:
3346 state = b'unapplied'
3346 state = b'unapplied'
3347 else:
3347 else:
3348 state = b'guarded'
3348 state = b'guarded'
3349 label = b'qguard.patch qguard.%s qseries.%s' % (state, state)
3349 label = b'qguard.patch qguard.%s qseries.%s' % (state, state)
3350 ui.write(b'%s: ' % ui.label(q.series[idx], label))
3350 ui.write(b'%s: ' % ui.label(q.series[idx], label))
3351
3351
3352 for i, guard in enumerate(guards):
3352 for i, guard in enumerate(guards):
3353 if guard.startswith(b'+'):
3353 if guard.startswith(b'+'):
3354 ui.write(guard, label=b'qguard.positive')
3354 ui.write(guard, label=b'qguard.positive')
3355 elif guard.startswith(b'-'):
3355 elif guard.startswith(b'-'):
3356 ui.write(guard, label=b'qguard.negative')
3356 ui.write(guard, label=b'qguard.negative')
3357 else:
3357 else:
3358 ui.write(guard, label=b'qguard.unguarded')
3358 ui.write(guard, label=b'qguard.unguarded')
3359 if i != len(guards) - 1:
3359 if i != len(guards) - 1:
3360 ui.write(b' ')
3360 ui.write(b' ')
3361 ui.write(b'\n')
3361 ui.write(b'\n')
3362
3362
3363 q = repo.mq
3363 q = repo.mq
3364 applied = set(p.name for p in q.applied)
3364 applied = set(p.name for p in q.applied)
3365 patch = None
3365 patch = None
3366 args = list(args)
3366 args = list(args)
3367 if opts.get('list'):
3367 if opts.get('list'):
3368 if args or opts.get('none'):
3368 if args or opts.get('none'):
3369 raise error.Abort(
3369 raise error.Abort(
3370 _(b'cannot mix -l/--list with options or arguments')
3370 _(b'cannot mix -l/--list with options or arguments')
3371 )
3371 )
3372 for i in pycompat.xrange(len(q.series)):
3372 for i in pycompat.xrange(len(q.series)):
3373 status(i)
3373 status(i)
3374 return
3374 return
3375 if not args or args[0][0:1] in b'-+':
3375 if not args or args[0][0:1] in b'-+':
3376 if not q.applied:
3376 if not q.applied:
3377 raise error.Abort(_(b'no patches applied'))
3377 raise error.Abort(_(b'no patches applied'))
3378 patch = q.applied[-1].name
3378 patch = q.applied[-1].name
3379 if patch is None and args[0][0:1] not in b'-+':
3379 if patch is None and args[0][0:1] not in b'-+':
3380 patch = args.pop(0)
3380 patch = args.pop(0)
3381 if patch is None:
3381 if patch is None:
3382 raise error.Abort(_(b'no patch to work with'))
3382 raise error.Abort(_(b'no patch to work with'))
3383 if args or opts.get('none'):
3383 if args or opts.get('none'):
3384 idx = q.findseries(patch)
3384 idx = q.findseries(patch)
3385 if idx is None:
3385 if idx is None:
3386 raise error.Abort(_(b'no patch named %s') % patch)
3386 raise error.Abort(_(b'no patch named %s') % patch)
3387 q.setguards(idx, args)
3387 q.setguards(idx, args)
3388 q.savedirty()
3388 q.savedirty()
3389 else:
3389 else:
3390 status(q.series.index(q.lookup(patch)))
3390 status(q.series.index(q.lookup(patch)))
3391
3391
3392
3392
3393 @command(
3393 @command(
3394 b"qheader",
3394 b"qheader",
3395 [],
3395 [],
3396 _(b'hg qheader [PATCH]'),
3396 _(b'hg qheader [PATCH]'),
3397 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
3397 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
3398 )
3398 )
3399 def header(ui, repo, patch=None):
3399 def header(ui, repo, patch=None):
3400 """print the header of the topmost or specified patch
3400 """print the header of the topmost or specified patch
3401
3401
3402 Returns 0 on success."""
3402 Returns 0 on success."""
3403 q = repo.mq
3403 q = repo.mq
3404
3404
3405 if patch:
3405 if patch:
3406 patch = q.lookup(patch)
3406 patch = q.lookup(patch)
3407 else:
3407 else:
3408 if not q.applied:
3408 if not q.applied:
3409 ui.write(_(b'no patches applied\n'))
3409 ui.write(_(b'no patches applied\n'))
3410 return 1
3410 return 1
3411 patch = q.lookup(b'qtip')
3411 patch = q.lookup(b'qtip')
3412 ph = patchheader(q.join(patch), q.plainmode)
3412 ph = patchheader(q.join(patch), q.plainmode)
3413
3413
3414 ui.write(b'\n'.join(ph.message) + b'\n')
3414 ui.write(b'\n'.join(ph.message) + b'\n')
3415
3415
3416
3416
3417 def lastsavename(path):
3417 def lastsavename(path):
3418 (directory, base) = os.path.split(path)
3418 (directory, base) = os.path.split(path)
3419 names = os.listdir(directory)
3419 names = os.listdir(directory)
3420 namere = re.compile(b"%s.([0-9]+)" % base)
3420 namere = re.compile(b"%s.([0-9]+)" % base)
3421 maxindex = None
3421 maxindex = None
3422 maxname = None
3422 maxname = None
3423 for f in names:
3423 for f in names:
3424 m = namere.match(f)
3424 m = namere.match(f)
3425 if m:
3425 if m:
3426 index = int(m.group(1))
3426 index = int(m.group(1))
3427 if maxindex is None or index > maxindex:
3427 if maxindex is None or index > maxindex:
3428 maxindex = index
3428 maxindex = index
3429 maxname = f
3429 maxname = f
3430 if maxname:
3430 if maxname:
3431 return (os.path.join(directory, maxname), maxindex)
3431 return (os.path.join(directory, maxname), maxindex)
3432 return (None, None)
3432 return (None, None)
3433
3433
3434
3434
3435 def savename(path):
3435 def savename(path):
3436 (last, index) = lastsavename(path)
3436 (last, index) = lastsavename(path)
3437 if last is None:
3437 if last is None:
3438 index = 0
3438 index = 0
3439 newpath = path + b".%d" % (index + 1)
3439 newpath = path + b".%d" % (index + 1)
3440 return newpath
3440 return newpath
3441
3441
3442
3442
3443 @command(
3443 @command(
3444 b"qpush",
3444 b"qpush",
3445 [
3445 [
3446 (
3446 (
3447 b'',
3447 b'',
3448 b'keep-changes',
3448 b'keep-changes',
3449 None,
3449 None,
3450 _(b'tolerate non-conflicting local changes'),
3450 _(b'tolerate non-conflicting local changes'),
3451 ),
3451 ),
3452 (b'f', b'force', None, _(b'apply on top of local changes')),
3452 (b'f', b'force', None, _(b'apply on top of local changes')),
3453 (
3453 (
3454 b'e',
3454 b'e',
3455 b'exact',
3455 b'exact',
3456 None,
3456 None,
3457 _(b'apply the target patch to its recorded parent'),
3457 _(b'apply the target patch to its recorded parent'),
3458 ),
3458 ),
3459 (b'l', b'list', None, _(b'list patch name in commit text')),
3459 (b'l', b'list', None, _(b'list patch name in commit text')),
3460 (b'a', b'all', None, _(b'apply all patches')),
3460 (b'a', b'all', None, _(b'apply all patches')),
3461 (b'm', b'merge', None, _(b'merge from another queue (DEPRECATED)')),
3461 (b'm', b'merge', None, _(b'merge from another queue (DEPRECATED)')),
3462 (b'n', b'name', b'', _(b'merge queue name (DEPRECATED)'), _(b'NAME')),
3462 (b'n', b'name', b'', _(b'merge queue name (DEPRECATED)'), _(b'NAME')),
3463 (
3463 (
3464 b'',
3464 b'',
3465 b'move',
3465 b'move',
3466 None,
3466 None,
3467 _(b'reorder patch series and apply only the patch'),
3467 _(b'reorder patch series and apply only the patch'),
3468 ),
3468 ),
3469 (b'', b'no-backup', None, _(b'do not save backup copies of files')),
3469 (b'', b'no-backup', None, _(b'do not save backup copies of files')),
3470 ],
3470 ],
3471 _(b'hg qpush [-f] [-l] [-a] [--move] [PATCH | INDEX]'),
3471 _(b'hg qpush [-f] [-l] [-a] [--move] [PATCH | INDEX]'),
3472 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
3472 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
3473 helpbasic=True,
3473 helpbasic=True,
3474 )
3474 )
3475 def push(ui, repo, patch=None, **opts):
3475 def push(ui, repo, patch=None, **opts):
3476 """push the next patch onto the stack
3476 """push the next patch onto the stack
3477
3477
3478 By default, abort if the working directory contains uncommitted
3478 By default, abort if the working directory contains uncommitted
3479 changes. With --keep-changes, abort only if the uncommitted files
3479 changes. With --keep-changes, abort only if the uncommitted files
3480 overlap with patched files. With -f/--force, backup and patch over
3480 overlap with patched files. With -f/--force, backup and patch over
3481 uncommitted changes.
3481 uncommitted changes.
3482
3482
3483 Return 0 on success.
3483 Return 0 on success.
3484 """
3484 """
3485 q = repo.mq
3485 q = repo.mq
3486 mergeq = None
3486 mergeq = None
3487
3487
3488 opts = pycompat.byteskwargs(opts)
3488 opts = pycompat.byteskwargs(opts)
3489 opts = fixkeepchangesopts(ui, opts)
3489 opts = fixkeepchangesopts(ui, opts)
3490 if opts.get(b'merge'):
3490 if opts.get(b'merge'):
3491 if opts.get(b'name'):
3491 if opts.get(b'name'):
3492 newpath = repo.vfs.join(opts.get(b'name'))
3492 newpath = repo.vfs.join(opts.get(b'name'))
3493 else:
3493 else:
3494 newpath, i = lastsavename(q.path)
3494 newpath, i = lastsavename(q.path)
3495 if not newpath:
3495 if not newpath:
3496 ui.warn(_(b"no saved queues found, please use -n\n"))
3496 ui.warn(_(b"no saved queues found, please use -n\n"))
3497 return 1
3497 return 1
3498 mergeq = queue(ui, repo.baseui, repo.path, newpath)
3498 mergeq = queue(ui, repo.baseui, repo.path, newpath)
3499 ui.warn(_(b"merging with queue at: %s\n") % mergeq.path)
3499 ui.warn(_(b"merging with queue at: %s\n") % mergeq.path)
3500 ret = q.push(
3500 ret = q.push(
3501 repo,
3501 repo,
3502 patch,
3502 patch,
3503 force=opts.get(b'force'),
3503 force=opts.get(b'force'),
3504 list=opts.get(b'list'),
3504 list=opts.get(b'list'),
3505 mergeq=mergeq,
3505 mergeq=mergeq,
3506 all=opts.get(b'all'),
3506 all=opts.get(b'all'),
3507 move=opts.get(b'move'),
3507 move=opts.get(b'move'),
3508 exact=opts.get(b'exact'),
3508 exact=opts.get(b'exact'),
3509 nobackup=opts.get(b'no_backup'),
3509 nobackup=opts.get(b'no_backup'),
3510 keepchanges=opts.get(b'keep_changes'),
3510 keepchanges=opts.get(b'keep_changes'),
3511 )
3511 )
3512 return ret
3512 return ret
3513
3513
3514
3514
3515 @command(
3515 @command(
3516 b"qpop",
3516 b"qpop",
3517 [
3517 [
3518 (b'a', b'all', None, _(b'pop all patches')),
3518 (b'a', b'all', None, _(b'pop all patches')),
3519 (b'n', b'name', b'', _(b'queue name to pop (DEPRECATED)'), _(b'NAME')),
3519 (b'n', b'name', b'', _(b'queue name to pop (DEPRECATED)'), _(b'NAME')),
3520 (
3520 (
3521 b'',
3521 b'',
3522 b'keep-changes',
3522 b'keep-changes',
3523 None,
3523 None,
3524 _(b'tolerate non-conflicting local changes'),
3524 _(b'tolerate non-conflicting local changes'),
3525 ),
3525 ),
3526 (b'f', b'force', None, _(b'forget any local changes to patched files')),
3526 (b'f', b'force', None, _(b'forget any local changes to patched files')),
3527 (b'', b'no-backup', None, _(b'do not save backup copies of files')),
3527 (b'', b'no-backup', None, _(b'do not save backup copies of files')),
3528 ],
3528 ],
3529 _(b'hg qpop [-a] [-f] [PATCH | INDEX]'),
3529 _(b'hg qpop [-a] [-f] [PATCH | INDEX]'),
3530 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
3530 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
3531 helpbasic=True,
3531 helpbasic=True,
3532 )
3532 )
3533 def pop(ui, repo, patch=None, **opts):
3533 def pop(ui, repo, patch=None, **opts):
3534 """pop the current patch off the stack
3534 """pop the current patch off the stack
3535
3535
3536 Without argument, pops off the top of the patch stack. If given a
3536 Without argument, pops off the top of the patch stack. If given a
3537 patch name, keeps popping off patches until the named patch is at
3537 patch name, keeps popping off patches until the named patch is at
3538 the top of the stack.
3538 the top of the stack.
3539
3539
3540 By default, abort if the working directory contains uncommitted
3540 By default, abort if the working directory contains uncommitted
3541 changes. With --keep-changes, abort only if the uncommitted files
3541 changes. With --keep-changes, abort only if the uncommitted files
3542 overlap with patched files. With -f/--force, backup and discard
3542 overlap with patched files. With -f/--force, backup and discard
3543 changes made to such files.
3543 changes made to such files.
3544
3544
3545 Return 0 on success.
3545 Return 0 on success.
3546 """
3546 """
3547 opts = pycompat.byteskwargs(opts)
3547 opts = pycompat.byteskwargs(opts)
3548 opts = fixkeepchangesopts(ui, opts)
3548 opts = fixkeepchangesopts(ui, opts)
3549 localupdate = True
3549 localupdate = True
3550 if opts.get(b'name'):
3550 if opts.get(b'name'):
3551 q = queue(ui, repo.baseui, repo.path, repo.vfs.join(opts.get(b'name')))
3551 q = queue(ui, repo.baseui, repo.path, repo.vfs.join(opts.get(b'name')))
3552 ui.warn(_(b'using patch queue: %s\n') % q.path)
3552 ui.warn(_(b'using patch queue: %s\n') % q.path)
3553 localupdate = False
3553 localupdate = False
3554 else:
3554 else:
3555 q = repo.mq
3555 q = repo.mq
3556 ret = q.pop(
3556 ret = q.pop(
3557 repo,
3557 repo,
3558 patch,
3558 patch,
3559 force=opts.get(b'force'),
3559 force=opts.get(b'force'),
3560 update=localupdate,
3560 update=localupdate,
3561 all=opts.get(b'all'),
3561 all=opts.get(b'all'),
3562 nobackup=opts.get(b'no_backup'),
3562 nobackup=opts.get(b'no_backup'),
3563 keepchanges=opts.get(b'keep_changes'),
3563 keepchanges=opts.get(b'keep_changes'),
3564 )
3564 )
3565 q.savedirty()
3565 q.savedirty()
3566 return ret
3566 return ret
3567
3567
3568
3568
3569 @command(
3569 @command(
3570 b"qrename|qmv",
3570 b"qrename|qmv",
3571 [],
3571 [],
3572 _(b'hg qrename PATCH1 [PATCH2]'),
3572 _(b'hg qrename PATCH1 [PATCH2]'),
3573 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
3573 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
3574 )
3574 )
3575 def rename(ui, repo, patch, name=None, **opts):
3575 def rename(ui, repo, patch, name=None, **opts):
3576 """rename a patch
3576 """rename a patch
3577
3577
3578 With one argument, renames the current patch to PATCH1.
3578 With one argument, renames the current patch to PATCH1.
3579 With two arguments, renames PATCH1 to PATCH2.
3579 With two arguments, renames PATCH1 to PATCH2.
3580
3580
3581 Returns 0 on success."""
3581 Returns 0 on success."""
3582 q = repo.mq
3582 q = repo.mq
3583 if not name:
3583 if not name:
3584 name = patch
3584 name = patch
3585 patch = None
3585 patch = None
3586
3586
3587 if patch:
3587 if patch:
3588 patch = q.lookup(patch)
3588 patch = q.lookup(patch)
3589 else:
3589 else:
3590 if not q.applied:
3590 if not q.applied:
3591 ui.write(_(b'no patches applied\n'))
3591 ui.write(_(b'no patches applied\n'))
3592 return
3592 return
3593 patch = q.lookup(b'qtip')
3593 patch = q.lookup(b'qtip')
3594 absdest = q.join(name)
3594 absdest = q.join(name)
3595 if os.path.isdir(absdest):
3595 if os.path.isdir(absdest):
3596 name = normname(os.path.join(name, os.path.basename(patch)))
3596 name = normname(os.path.join(name, os.path.basename(patch)))
3597 absdest = q.join(name)
3597 absdest = q.join(name)
3598 q.checkpatchname(name)
3598 q.checkpatchname(name)
3599
3599
3600 ui.note(_(b'renaming %s to %s\n') % (patch, name))
3600 ui.note(_(b'renaming %s to %s\n') % (patch, name))
3601 i = q.findseries(patch)
3601 i = q.findseries(patch)
3602 guards = q.guard_re.findall(q.fullseries[i])
3602 guards = q.guard_re.findall(q.fullseries[i])
3603 q.fullseries[i] = name + b''.join([b' #' + g for g in guards])
3603 q.fullseries[i] = name + b''.join([b' #' + g for g in guards])
3604 q.parseseries()
3604 q.parseseries()
3605 q.seriesdirty = True
3605 q.seriesdirty = True
3606
3606
3607 info = q.isapplied(patch)
3607 info = q.isapplied(patch)
3608 if info:
3608 if info:
3609 q.applied[info[0]] = statusentry(info[1], name)
3609 q.applied[info[0]] = statusentry(info[1], name)
3610 q.applieddirty = True
3610 q.applieddirty = True
3611
3611
3612 destdir = os.path.dirname(absdest)
3612 destdir = os.path.dirname(absdest)
3613 if not os.path.isdir(destdir):
3613 if not os.path.isdir(destdir):
3614 os.makedirs(destdir)
3614 os.makedirs(destdir)
3615 util.rename(q.join(patch), absdest)
3615 util.rename(q.join(patch), absdest)
3616 r = q.qrepo()
3616 r = q.qrepo()
3617 if r and patch in r.dirstate:
3617 if r and patch in r.dirstate:
3618 wctx = r[None]
3618 wctx = r[None]
3619 with r.wlock():
3619 with r.wlock():
3620 if r.dirstate[patch] == b'a':
3620 if r.dirstate[patch] == b'a':
3621 r.dirstate.drop(patch)
3621 r.dirstate.drop(patch)
3622 r.dirstate.add(name)
3622 r.dirstate.add(name)
3623 else:
3623 else:
3624 wctx.copy(patch, name)
3624 wctx.copy(patch, name)
3625 wctx.forget([patch])
3625 wctx.forget([patch])
3626
3626
3627 q.savedirty()
3627 q.savedirty()
3628
3628
3629
3629
3630 @command(
3630 @command(
3631 b"qrestore",
3631 b"qrestore",
3632 [
3632 [
3633 (b'd', b'delete', None, _(b'delete save entry')),
3633 (b'd', b'delete', None, _(b'delete save entry')),
3634 (b'u', b'update', None, _(b'update queue working directory')),
3634 (b'u', b'update', None, _(b'update queue working directory')),
3635 ],
3635 ],
3636 _(b'hg qrestore [-d] [-u] REV'),
3636 _(b'hg qrestore [-d] [-u] REV'),
3637 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
3637 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
3638 )
3638 )
3639 def restore(ui, repo, rev, **opts):
3639 def restore(ui, repo, rev, **opts):
3640 """restore the queue state saved by a revision (DEPRECATED)
3640 """restore the queue state saved by a revision (DEPRECATED)
3641
3641
3642 This command is deprecated, use :hg:`rebase` instead."""
3642 This command is deprecated, use :hg:`rebase` instead."""
3643 rev = repo.lookup(rev)
3643 rev = repo.lookup(rev)
3644 q = repo.mq
3644 q = repo.mq
3645 q.restore(repo, rev, delete=opts.get('delete'), qupdate=opts.get('update'))
3645 q.restore(repo, rev, delete=opts.get('delete'), qupdate=opts.get('update'))
3646 q.savedirty()
3646 q.savedirty()
3647 return 0
3647 return 0
3648
3648
3649
3649
3650 @command(
3650 @command(
3651 b"qsave",
3651 b"qsave",
3652 [
3652 [
3653 (b'c', b'copy', None, _(b'copy patch directory')),
3653 (b'c', b'copy', None, _(b'copy patch directory')),
3654 (b'n', b'name', b'', _(b'copy directory name'), _(b'NAME')),
3654 (b'n', b'name', b'', _(b'copy directory name'), _(b'NAME')),
3655 (b'e', b'empty', None, _(b'clear queue status file')),
3655 (b'e', b'empty', None, _(b'clear queue status file')),
3656 (b'f', b'force', None, _(b'force copy')),
3656 (b'f', b'force', None, _(b'force copy')),
3657 ]
3657 ]
3658 + cmdutil.commitopts,
3658 + cmdutil.commitopts,
3659 _(b'hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]'),
3659 _(b'hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]'),
3660 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
3660 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
3661 )
3661 )
3662 def save(ui, repo, **opts):
3662 def save(ui, repo, **opts):
3663 """save current queue state (DEPRECATED)
3663 """save current queue state (DEPRECATED)
3664
3664
3665 This command is deprecated, use :hg:`rebase` instead."""
3665 This command is deprecated, use :hg:`rebase` instead."""
3666 q = repo.mq
3666 q = repo.mq
3667 opts = pycompat.byteskwargs(opts)
3667 opts = pycompat.byteskwargs(opts)
3668 message = cmdutil.logmessage(ui, opts)
3668 message = cmdutil.logmessage(ui, opts)
3669 ret = q.save(repo, msg=message)
3669 ret = q.save(repo, msg=message)
3670 if ret:
3670 if ret:
3671 return ret
3671 return ret
3672 q.savedirty() # save to .hg/patches before copying
3672 q.savedirty() # save to .hg/patches before copying
3673 if opts.get(b'copy'):
3673 if opts.get(b'copy'):
3674 path = q.path
3674 path = q.path
3675 if opts.get(b'name'):
3675 if opts.get(b'name'):
3676 newpath = os.path.join(q.basepath, opts.get(b'name'))
3676 newpath = os.path.join(q.basepath, opts.get(b'name'))
3677 if os.path.exists(newpath):
3677 if os.path.exists(newpath):
3678 if not os.path.isdir(newpath):
3678 if not os.path.isdir(newpath):
3679 raise error.Abort(
3679 raise error.Abort(
3680 _(b'destination %s exists and is not a directory')
3680 _(b'destination %s exists and is not a directory')
3681 % newpath
3681 % newpath
3682 )
3682 )
3683 if not opts.get(b'force'):
3683 if not opts.get(b'force'):
3684 raise error.Abort(
3684 raise error.Abort(
3685 _(b'destination %s exists, use -f to force') % newpath
3685 _(b'destination %s exists, use -f to force') % newpath
3686 )
3686 )
3687 else:
3687 else:
3688 newpath = savename(path)
3688 newpath = savename(path)
3689 ui.warn(_(b"copy %s to %s\n") % (path, newpath))
3689 ui.warn(_(b"copy %s to %s\n") % (path, newpath))
3690 util.copyfiles(path, newpath)
3690 util.copyfiles(path, newpath)
3691 if opts.get(b'empty'):
3691 if opts.get(b'empty'):
3692 del q.applied[:]
3692 del q.applied[:]
3693 q.applieddirty = True
3693 q.applieddirty = True
3694 q.savedirty()
3694 q.savedirty()
3695 return 0
3695 return 0
3696
3696
3697
3697
3698 @command(
3698 @command(
3699 b"qselect",
3699 b"qselect",
3700 [
3700 [
3701 (b'n', b'none', None, _(b'disable all guards')),
3701 (b'n', b'none', None, _(b'disable all guards')),
3702 (b's', b'series', None, _(b'list all guards in series file')),
3702 (b's', b'series', None, _(b'list all guards in series file')),
3703 (b'', b'pop', None, _(b'pop to before first guarded applied patch')),
3703 (b'', b'pop', None, _(b'pop to before first guarded applied patch')),
3704 (b'', b'reapply', None, _(b'pop, then reapply patches')),
3704 (b'', b'reapply', None, _(b'pop, then reapply patches')),
3705 ],
3705 ],
3706 _(b'hg qselect [OPTION]... [GUARD]...'),
3706 _(b'hg qselect [OPTION]... [GUARD]...'),
3707 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
3707 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
3708 )
3708 )
3709 def select(ui, repo, *args, **opts):
3709 def select(ui, repo, *args, **opts):
3710 '''set or print guarded patches to push
3710 '''set or print guarded patches to push
3711
3711
3712 Use the :hg:`qguard` command to set or print guards on patch, then use
3712 Use the :hg:`qguard` command to set or print guards on patch, then use
3713 qselect to tell mq which guards to use. A patch will be pushed if
3713 qselect to tell mq which guards to use. A patch will be pushed if
3714 it has no guards or any positive guards match the currently
3714 it has no guards or any positive guards match the currently
3715 selected guard, but will not be pushed if any negative guards
3715 selected guard, but will not be pushed if any negative guards
3716 match the current guard. For example::
3716 match the current guard. For example::
3717
3717
3718 qguard foo.patch -- -stable (negative guard)
3718 qguard foo.patch -- -stable (negative guard)
3719 qguard bar.patch +stable (positive guard)
3719 qguard bar.patch +stable (positive guard)
3720 qselect stable
3720 qselect stable
3721
3721
3722 This activates the "stable" guard. mq will skip foo.patch (because
3722 This activates the "stable" guard. mq will skip foo.patch (because
3723 it has a negative match) but push bar.patch (because it has a
3723 it has a negative match) but push bar.patch (because it has a
3724 positive match).
3724 positive match).
3725
3725
3726 With no arguments, prints the currently active guards.
3726 With no arguments, prints the currently active guards.
3727 With one argument, sets the active guard.
3727 With one argument, sets the active guard.
3728
3728
3729 Use -n/--none to deactivate guards (no other arguments needed).
3729 Use -n/--none to deactivate guards (no other arguments needed).
3730 When no guards are active, patches with positive guards are
3730 When no guards are active, patches with positive guards are
3731 skipped and patches with negative guards are pushed.
3731 skipped and patches with negative guards are pushed.
3732
3732
3733 qselect can change the guards on applied patches. It does not pop
3733 qselect can change the guards on applied patches. It does not pop
3734 guarded patches by default. Use --pop to pop back to the last
3734 guarded patches by default. Use --pop to pop back to the last
3735 applied patch that is not guarded. Use --reapply (which implies
3735 applied patch that is not guarded. Use --reapply (which implies
3736 --pop) to push back to the current patch afterwards, but skip
3736 --pop) to push back to the current patch afterwards, but skip
3737 guarded patches.
3737 guarded patches.
3738
3738
3739 Use -s/--series to print a list of all guards in the series file
3739 Use -s/--series to print a list of all guards in the series file
3740 (no other arguments needed). Use -v for more information.
3740 (no other arguments needed). Use -v for more information.
3741
3741
3742 Returns 0 on success.'''
3742 Returns 0 on success.'''
3743
3743
3744 q = repo.mq
3744 q = repo.mq
3745 opts = pycompat.byteskwargs(opts)
3745 opts = pycompat.byteskwargs(opts)
3746 guards = q.active()
3746 guards = q.active()
3747 pushable = lambda i: q.pushable(q.applied[i].name)[0]
3747 pushable = lambda i: q.pushable(q.applied[i].name)[0]
3748 if args or opts.get(b'none'):
3748 if args or opts.get(b'none'):
3749 old_unapplied = q.unapplied(repo)
3749 old_unapplied = q.unapplied(repo)
3750 old_guarded = [
3750 old_guarded = [
3751 i for i in pycompat.xrange(len(q.applied)) if not pushable(i)
3751 i for i in pycompat.xrange(len(q.applied)) if not pushable(i)
3752 ]
3752 ]
3753 q.setactive(args)
3753 q.setactive(args)
3754 q.savedirty()
3754 q.savedirty()
3755 if not args:
3755 if not args:
3756 ui.status(_(b'guards deactivated\n'))
3756 ui.status(_(b'guards deactivated\n'))
3757 if not opts.get(b'pop') and not opts.get(b'reapply'):
3757 if not opts.get(b'pop') and not opts.get(b'reapply'):
3758 unapplied = q.unapplied(repo)
3758 unapplied = q.unapplied(repo)
3759 guarded = [
3759 guarded = [
3760 i for i in pycompat.xrange(len(q.applied)) if not pushable(i)
3760 i for i in pycompat.xrange(len(q.applied)) if not pushable(i)
3761 ]
3761 ]
3762 if len(unapplied) != len(old_unapplied):
3762 if len(unapplied) != len(old_unapplied):
3763 ui.status(
3763 ui.status(
3764 _(
3764 _(
3765 b'number of unguarded, unapplied patches has '
3765 b'number of unguarded, unapplied patches has '
3766 b'changed from %d to %d\n'
3766 b'changed from %d to %d\n'
3767 )
3767 )
3768 % (len(old_unapplied), len(unapplied))
3768 % (len(old_unapplied), len(unapplied))
3769 )
3769 )
3770 if len(guarded) != len(old_guarded):
3770 if len(guarded) != len(old_guarded):
3771 ui.status(
3771 ui.status(
3772 _(
3772 _(
3773 b'number of guarded, applied patches has changed '
3773 b'number of guarded, applied patches has changed '
3774 b'from %d to %d\n'
3774 b'from %d to %d\n'
3775 )
3775 )
3776 % (len(old_guarded), len(guarded))
3776 % (len(old_guarded), len(guarded))
3777 )
3777 )
3778 elif opts.get(b'series'):
3778 elif opts.get(b'series'):
3779 guards = {}
3779 guards = {}
3780 noguards = 0
3780 noguards = 0
3781 for gs in q.seriesguards:
3781 for gs in q.seriesguards:
3782 if not gs:
3782 if not gs:
3783 noguards += 1
3783 noguards += 1
3784 for g in gs:
3784 for g in gs:
3785 guards.setdefault(g, 0)
3785 guards.setdefault(g, 0)
3786 guards[g] += 1
3786 guards[g] += 1
3787 if ui.verbose:
3787 if ui.verbose:
3788 guards[b'NONE'] = noguards
3788 guards[b'NONE'] = noguards
3789 guards = list(guards.items())
3789 guards = list(guards.items())
3790 guards.sort(key=lambda x: x[0][1:])
3790 guards.sort(key=lambda x: x[0][1:])
3791 if guards:
3791 if guards:
3792 ui.note(_(b'guards in series file:\n'))
3792 ui.note(_(b'guards in series file:\n'))
3793 for guard, count in guards:
3793 for guard, count in guards:
3794 ui.note(b'%2d ' % count)
3794 ui.note(b'%2d ' % count)
3795 ui.write(guard, b'\n')
3795 ui.write(guard, b'\n')
3796 else:
3796 else:
3797 ui.note(_(b'no guards in series file\n'))
3797 ui.note(_(b'no guards in series file\n'))
3798 else:
3798 else:
3799 if guards:
3799 if guards:
3800 ui.note(_(b'active guards:\n'))
3800 ui.note(_(b'active guards:\n'))
3801 for g in guards:
3801 for g in guards:
3802 ui.write(g, b'\n')
3802 ui.write(g, b'\n')
3803 else:
3803 else:
3804 ui.write(_(b'no active guards\n'))
3804 ui.write(_(b'no active guards\n'))
3805 reapply = opts.get(b'reapply') and q.applied and q.applied[-1].name
3805 reapply = opts.get(b'reapply') and q.applied and q.applied[-1].name
3806 popped = False
3806 popped = False
3807 if opts.get(b'pop') or opts.get(b'reapply'):
3807 if opts.get(b'pop') or opts.get(b'reapply'):
3808 for i in pycompat.xrange(len(q.applied)):
3808 for i in pycompat.xrange(len(q.applied)):
3809 if not pushable(i):
3809 if not pushable(i):
3810 ui.status(_(b'popping guarded patches\n'))
3810 ui.status(_(b'popping guarded patches\n'))
3811 popped = True
3811 popped = True
3812 if i == 0:
3812 if i == 0:
3813 q.pop(repo, all=True)
3813 q.pop(repo, all=True)
3814 else:
3814 else:
3815 q.pop(repo, q.applied[i - 1].name)
3815 q.pop(repo, q.applied[i - 1].name)
3816 break
3816 break
3817 if popped:
3817 if popped:
3818 try:
3818 try:
3819 if reapply:
3819 if reapply:
3820 ui.status(_(b'reapplying unguarded patches\n'))
3820 ui.status(_(b'reapplying unguarded patches\n'))
3821 q.push(repo, reapply)
3821 q.push(repo, reapply)
3822 finally:
3822 finally:
3823 q.savedirty()
3823 q.savedirty()
3824
3824
3825
3825
3826 @command(
3826 @command(
3827 b"qfinish",
3827 b"qfinish",
3828 [(b'a', b'applied', None, _(b'finish all applied changesets'))],
3828 [(b'a', b'applied', None, _(b'finish all applied changesets'))],
3829 _(b'hg qfinish [-a] [REV]...'),
3829 _(b'hg qfinish [-a] [REV]...'),
3830 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
3830 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
3831 )
3831 )
3832 def finish(ui, repo, *revrange, **opts):
3832 def finish(ui, repo, *revrange, **opts):
3833 """move applied patches into repository history
3833 """move applied patches into repository history
3834
3834
3835 Finishes the specified revisions (corresponding to applied
3835 Finishes the specified revisions (corresponding to applied
3836 patches) by moving them out of mq control into regular repository
3836 patches) by moving them out of mq control into regular repository
3837 history.
3837 history.
3838
3838
3839 Accepts a revision range or the -a/--applied option. If --applied
3839 Accepts a revision range or the -a/--applied option. If --applied
3840 is specified, all applied mq revisions are removed from mq
3840 is specified, all applied mq revisions are removed from mq
3841 control. Otherwise, the given revisions must be at the base of the
3841 control. Otherwise, the given revisions must be at the base of the
3842 stack of applied patches.
3842 stack of applied patches.
3843
3843
3844 This can be especially useful if your changes have been applied to
3844 This can be especially useful if your changes have been applied to
3845 an upstream repository, or if you are about to push your changes
3845 an upstream repository, or if you are about to push your changes
3846 to upstream.
3846 to upstream.
3847
3847
3848 Returns 0 on success.
3848 Returns 0 on success.
3849 """
3849 """
3850 if not opts.get('applied') and not revrange:
3850 if not opts.get('applied') and not revrange:
3851 raise error.Abort(_(b'no revisions specified'))
3851 raise error.Abort(_(b'no revisions specified'))
3852 elif opts.get('applied'):
3852 elif opts.get('applied'):
3853 revrange = (b'qbase::qtip',) + revrange
3853 revrange = (b'qbase::qtip',) + revrange
3854
3854
3855 q = repo.mq
3855 q = repo.mq
3856 if not q.applied:
3856 if not q.applied:
3857 ui.status(_(b'no patches applied\n'))
3857 ui.status(_(b'no patches applied\n'))
3858 return 0
3858 return 0
3859
3859
3860 revs = scmutil.revrange(repo, revrange)
3860 revs = scmutil.revrange(repo, revrange)
3861 if repo[b'.'].rev() in revs and repo[None].files():
3861 if repo[b'.'].rev() in revs and repo[None].files():
3862 ui.warn(_(b'warning: uncommitted changes in the working directory\n'))
3862 ui.warn(_(b'warning: uncommitted changes in the working directory\n'))
3863 # queue.finish may changes phases but leave the responsibility to lock the
3863 # queue.finish may changes phases but leave the responsibility to lock the
3864 # repo to the caller to avoid deadlock with wlock. This command code is
3864 # repo to the caller to avoid deadlock with wlock. This command code is
3865 # responsibility for this locking.
3865 # responsibility for this locking.
3866 with repo.lock():
3866 with repo.lock():
3867 q.finish(repo, revs)
3867 q.finish(repo, revs)
3868 q.savedirty()
3868 q.savedirty()
3869 return 0
3869 return 0
3870
3870
3871
3871
3872 @command(
3872 @command(
3873 b"qqueue",
3873 b"qqueue",
3874 [
3874 [
3875 (b'l', b'list', False, _(b'list all available queues')),
3875 (b'l', b'list', False, _(b'list all available queues')),
3876 (b'', b'active', False, _(b'print name of active queue')),
3876 (b'', b'active', False, _(b'print name of active queue')),
3877 (b'c', b'create', False, _(b'create new queue')),
3877 (b'c', b'create', False, _(b'create new queue')),
3878 (b'', b'rename', False, _(b'rename active queue')),
3878 (b'', b'rename', False, _(b'rename active queue')),
3879 (b'', b'delete', False, _(b'delete reference to queue')),
3879 (b'', b'delete', False, _(b'delete reference to queue')),
3880 (b'', b'purge', False, _(b'delete queue, and remove patch dir')),
3880 (b'', b'purge', False, _(b'delete queue, and remove patch dir')),
3881 ],
3881 ],
3882 _(b'[OPTION] [QUEUE]'),
3882 _(b'[OPTION] [QUEUE]'),
3883 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
3883 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
3884 )
3884 )
3885 def qqueue(ui, repo, name=None, **opts):
3885 def qqueue(ui, repo, name=None, **opts):
3886 '''manage multiple patch queues
3886 '''manage multiple patch queues
3887
3887
3888 Supports switching between different patch queues, as well as creating
3888 Supports switching between different patch queues, as well as creating
3889 new patch queues and deleting existing ones.
3889 new patch queues and deleting existing ones.
3890
3890
3891 Omitting a queue name or specifying -l/--list will show you the registered
3891 Omitting a queue name or specifying -l/--list will show you the registered
3892 queues - by default the "normal" patches queue is registered. The currently
3892 queues - by default the "normal" patches queue is registered. The currently
3893 active queue will be marked with "(active)". Specifying --active will print
3893 active queue will be marked with "(active)". Specifying --active will print
3894 only the name of the active queue.
3894 only the name of the active queue.
3895
3895
3896 To create a new queue, use -c/--create. The queue is automatically made
3896 To create a new queue, use -c/--create. The queue is automatically made
3897 active, except in the case where there are applied patches from the
3897 active, except in the case where there are applied patches from the
3898 currently active queue in the repository. Then the queue will only be
3898 currently active queue in the repository. Then the queue will only be
3899 created and switching will fail.
3899 created and switching will fail.
3900
3900
3901 To delete an existing queue, use --delete. You cannot delete the currently
3901 To delete an existing queue, use --delete. You cannot delete the currently
3902 active queue.
3902 active queue.
3903
3903
3904 Returns 0 on success.
3904 Returns 0 on success.
3905 '''
3905 '''
3906 q = repo.mq
3906 q = repo.mq
3907 _defaultqueue = b'patches'
3907 _defaultqueue = b'patches'
3908 _allqueues = b'patches.queues'
3908 _allqueues = b'patches.queues'
3909 _activequeue = b'patches.queue'
3909 _activequeue = b'patches.queue'
3910
3910
3911 def _getcurrent():
3911 def _getcurrent():
3912 cur = os.path.basename(q.path)
3912 cur = os.path.basename(q.path)
3913 if cur.startswith(b'patches-'):
3913 if cur.startswith(b'patches-'):
3914 cur = cur[8:]
3914 cur = cur[8:]
3915 return cur
3915 return cur
3916
3916
3917 def _noqueues():
3917 def _noqueues():
3918 try:
3918 try:
3919 fh = repo.vfs(_allqueues, b'r')
3919 fh = repo.vfs(_allqueues, b'r')
3920 fh.close()
3920 fh.close()
3921 except IOError:
3921 except IOError:
3922 return True
3922 return True
3923
3923
3924 return False
3924 return False
3925
3925
3926 def _getqueues():
3926 def _getqueues():
3927 current = _getcurrent()
3927 current = _getcurrent()
3928
3928
3929 try:
3929 try:
3930 fh = repo.vfs(_allqueues, b'r')
3930 fh = repo.vfs(_allqueues, b'r')
3931 queues = [queue.strip() for queue in fh if queue.strip()]
3931 queues = [queue.strip() for queue in fh if queue.strip()]
3932 fh.close()
3932 fh.close()
3933 if current not in queues:
3933 if current not in queues:
3934 queues.append(current)
3934 queues.append(current)
3935 except IOError:
3935 except IOError:
3936 queues = [_defaultqueue]
3936 queues = [_defaultqueue]
3937
3937
3938 return sorted(queues)
3938 return sorted(queues)
3939
3939
3940 def _setactive(name):
3940 def _setactive(name):
3941 if q.applied:
3941 if q.applied:
3942 raise error.Abort(
3942 raise error.Abort(
3943 _(
3943 _(
3944 b'new queue created, but cannot make active '
3944 b'new queue created, but cannot make active '
3945 b'as patches are applied'
3945 b'as patches are applied'
3946 )
3946 )
3947 )
3947 )
3948 _setactivenocheck(name)
3948 _setactivenocheck(name)
3949
3949
3950 def _setactivenocheck(name):
3950 def _setactivenocheck(name):
3951 fh = repo.vfs(_activequeue, b'w')
3951 fh = repo.vfs(_activequeue, b'w')
3952 if name != b'patches':
3952 if name != b'patches':
3953 fh.write(name)
3953 fh.write(name)
3954 fh.close()
3954 fh.close()
3955
3955
3956 def _addqueue(name):
3956 def _addqueue(name):
3957 fh = repo.vfs(_allqueues, b'a')
3957 fh = repo.vfs(_allqueues, b'a')
3958 fh.write(b'%s\n' % (name,))
3958 fh.write(b'%s\n' % (name,))
3959 fh.close()
3959 fh.close()
3960
3960
3961 def _queuedir(name):
3961 def _queuedir(name):
3962 if name == b'patches':
3962 if name == b'patches':
3963 return repo.vfs.join(b'patches')
3963 return repo.vfs.join(b'patches')
3964 else:
3964 else:
3965 return repo.vfs.join(b'patches-' + name)
3965 return repo.vfs.join(b'patches-' + name)
3966
3966
3967 def _validname(name):
3967 def _validname(name):
3968 for n in name:
3968 for n in name:
3969 if n in b':\\/.':
3969 if n in b':\\/.':
3970 return False
3970 return False
3971 return True
3971 return True
3972
3972
3973 def _delete(name):
3973 def _delete(name):
3974 if name not in existing:
3974 if name not in existing:
3975 raise error.Abort(_(b'cannot delete queue that does not exist'))
3975 raise error.Abort(_(b'cannot delete queue that does not exist'))
3976
3976
3977 current = _getcurrent()
3977 current = _getcurrent()
3978
3978
3979 if name == current:
3979 if name == current:
3980 raise error.Abort(_(b'cannot delete currently active queue'))
3980 raise error.Abort(_(b'cannot delete currently active queue'))
3981
3981
3982 fh = repo.vfs(b'patches.queues.new', b'w')
3982 fh = repo.vfs(b'patches.queues.new', b'w')
3983 for queue in existing:
3983 for queue in existing:
3984 if queue == name:
3984 if queue == name:
3985 continue
3985 continue
3986 fh.write(b'%s\n' % (queue,))
3986 fh.write(b'%s\n' % (queue,))
3987 fh.close()
3987 fh.close()
3988 repo.vfs.rename(b'patches.queues.new', _allqueues)
3988 repo.vfs.rename(b'patches.queues.new', _allqueues)
3989
3989
3990 opts = pycompat.byteskwargs(opts)
3990 opts = pycompat.byteskwargs(opts)
3991 if not name or opts.get(b'list') or opts.get(b'active'):
3991 if not name or opts.get(b'list') or opts.get(b'active'):
3992 current = _getcurrent()
3992 current = _getcurrent()
3993 if opts.get(b'active'):
3993 if opts.get(b'active'):
3994 ui.write(b'%s\n' % (current,))
3994 ui.write(b'%s\n' % (current,))
3995 return
3995 return
3996 for queue in _getqueues():
3996 for queue in _getqueues():
3997 ui.write(b'%s' % (queue,))
3997 ui.write(b'%s' % (queue,))
3998 if queue == current and not ui.quiet:
3998 if queue == current and not ui.quiet:
3999 ui.write(_(b' (active)\n'))
3999 ui.write(_(b' (active)\n'))
4000 else:
4000 else:
4001 ui.write(b'\n')
4001 ui.write(b'\n')
4002 return
4002 return
4003
4003
4004 if not _validname(name):
4004 if not _validname(name):
4005 raise error.Abort(
4005 raise error.Abort(
4006 _(b'invalid queue name, may not contain the characters ":\\/."')
4006 _(b'invalid queue name, may not contain the characters ":\\/."')
4007 )
4007 )
4008
4008
4009 with repo.wlock():
4009 with repo.wlock():
4010 existing = _getqueues()
4010 existing = _getqueues()
4011
4011
4012 if opts.get(b'create'):
4012 if opts.get(b'create'):
4013 if name in existing:
4013 if name in existing:
4014 raise error.Abort(_(b'queue "%s" already exists') % name)
4014 raise error.Abort(_(b'queue "%s" already exists') % name)
4015 if _noqueues():
4015 if _noqueues():
4016 _addqueue(_defaultqueue)
4016 _addqueue(_defaultqueue)
4017 _addqueue(name)
4017 _addqueue(name)
4018 _setactive(name)
4018 _setactive(name)
4019 elif opts.get(b'rename'):
4019 elif opts.get(b'rename'):
4020 current = _getcurrent()
4020 current = _getcurrent()
4021 if name == current:
4021 if name == current:
4022 raise error.Abort(
4022 raise error.Abort(
4023 _(b'can\'t rename "%s" to its current name') % name
4023 _(b'can\'t rename "%s" to its current name') % name
4024 )
4024 )
4025 if name in existing:
4025 if name in existing:
4026 raise error.Abort(_(b'queue "%s" already exists') % name)
4026 raise error.Abort(_(b'queue "%s" already exists') % name)
4027
4027
4028 olddir = _queuedir(current)
4028 olddir = _queuedir(current)
4029 newdir = _queuedir(name)
4029 newdir = _queuedir(name)
4030
4030
4031 if os.path.exists(newdir):
4031 if os.path.exists(newdir):
4032 raise error.Abort(
4032 raise error.Abort(
4033 _(b'non-queue directory "%s" already exists') % newdir
4033 _(b'non-queue directory "%s" already exists') % newdir
4034 )
4034 )
4035
4035
4036 fh = repo.vfs(b'patches.queues.new', b'w')
4036 fh = repo.vfs(b'patches.queues.new', b'w')
4037 for queue in existing:
4037 for queue in existing:
4038 if queue == current:
4038 if queue == current:
4039 fh.write(b'%s\n' % (name,))
4039 fh.write(b'%s\n' % (name,))
4040 if os.path.exists(olddir):
4040 if os.path.exists(olddir):
4041 util.rename(olddir, newdir)
4041 util.rename(olddir, newdir)
4042 else:
4042 else:
4043 fh.write(b'%s\n' % (queue,))
4043 fh.write(b'%s\n' % (queue,))
4044 fh.close()
4044 fh.close()
4045 repo.vfs.rename(b'patches.queues.new', _allqueues)
4045 repo.vfs.rename(b'patches.queues.new', _allqueues)
4046 _setactivenocheck(name)
4046 _setactivenocheck(name)
4047 elif opts.get(b'delete'):
4047 elif opts.get(b'delete'):
4048 _delete(name)
4048 _delete(name)
4049 elif opts.get(b'purge'):
4049 elif opts.get(b'purge'):
4050 if name in existing:
4050 if name in existing:
4051 _delete(name)
4051 _delete(name)
4052 qdir = _queuedir(name)
4052 qdir = _queuedir(name)
4053 if os.path.exists(qdir):
4053 if os.path.exists(qdir):
4054 shutil.rmtree(qdir)
4054 shutil.rmtree(qdir)
4055 else:
4055 else:
4056 if name not in existing:
4056 if name not in existing:
4057 raise error.Abort(_(b'use --create to create a new queue'))
4057 raise error.Abort(_(b'use --create to create a new queue'))
4058 _setactive(name)
4058 _setactive(name)
4059
4059
4060
4060
4061 def mqphasedefaults(repo, roots):
4061 def mqphasedefaults(repo, roots):
4062 """callback used to set mq changeset as secret when no phase data exists"""
4062 """callback used to set mq changeset as secret when no phase data exists"""
4063 if repo.mq.applied:
4063 if repo.mq.applied:
4064 if repo.ui.configbool(b'mq', b'secret'):
4064 if repo.ui.configbool(b'mq', b'secret'):
4065 mqphase = phases.secret
4065 mqphase = phases.secret
4066 else:
4066 else:
4067 mqphase = phases.draft
4067 mqphase = phases.draft
4068 qbase = repo[repo.mq.applied[0].node]
4068 qbase = repo[repo.mq.applied[0].node]
4069 roots[mqphase].add(qbase.node())
4069 roots[mqphase].add(qbase.node())
4070 return roots
4070 return roots
4071
4071
4072
4072
4073 def reposetup(ui, repo):
4073 def reposetup(ui, repo):
4074 class mqrepo(repo.__class__):
4074 class mqrepo(repo.__class__):
4075 @localrepo.unfilteredpropertycache
4075 @localrepo.unfilteredpropertycache
4076 def mq(self):
4076 def mq(self):
4077 return queue(self.ui, self.baseui, self.path)
4077 return queue(self.ui, self.baseui, self.path)
4078
4078
4079 def invalidateall(self):
4079 def invalidateall(self):
4080 super(mqrepo, self).invalidateall()
4080 super(mqrepo, self).invalidateall()
4081 if localrepo.hasunfilteredcache(self, 'mq'):
4081 if localrepo.hasunfilteredcache(self, 'mq'):
4082 # recreate mq in case queue path was changed
4082 # recreate mq in case queue path was changed
4083 delattr(self.unfiltered(), 'mq')
4083 delattr(self.unfiltered(), 'mq')
4084
4084
4085 def abortifwdirpatched(self, errmsg, force=False):
4085 def abortifwdirpatched(self, errmsg, force=False):
4086 if self.mq.applied and self.mq.checkapplied and not force:
4086 if self.mq.applied and self.mq.checkapplied and not force:
4087 parents = self.dirstate.parents()
4087 parents = self.dirstate.parents()
4088 patches = [s.node for s in self.mq.applied]
4088 patches = [s.node for s in self.mq.applied]
4089 if any(p in patches for p in parents):
4089 if any(p in patches for p in parents):
4090 raise error.Abort(errmsg)
4090 raise error.Abort(errmsg)
4091
4091
4092 def commit(
4092 def commit(
4093 self,
4093 self,
4094 text=b"",
4094 text=b"",
4095 user=None,
4095 user=None,
4096 date=None,
4096 date=None,
4097 match=None,
4097 match=None,
4098 force=False,
4098 force=False,
4099 editor=False,
4099 editor=False,
4100 extra=None,
4100 extra=None,
4101 ):
4101 ):
4102 if extra is None:
4102 if extra is None:
4103 extra = {}
4103 extra = {}
4104 self.abortifwdirpatched(
4104 self.abortifwdirpatched(
4105 _(b'cannot commit over an applied mq patch'), force
4105 _(b'cannot commit over an applied mq patch'), force
4106 )
4106 )
4107
4107
4108 return super(mqrepo, self).commit(
4108 return super(mqrepo, self).commit(
4109 text, user, date, match, force, editor, extra
4109 text, user, date, match, force, editor, extra
4110 )
4110 )
4111
4111
4112 def checkpush(self, pushop):
4112 def checkpush(self, pushop):
4113 if self.mq.applied and self.mq.checkapplied and not pushop.force:
4113 if self.mq.applied and self.mq.checkapplied and not pushop.force:
4114 outapplied = [e.node for e in self.mq.applied]
4114 outapplied = [e.node for e in self.mq.applied]
4115 if pushop.revs:
4115 if pushop.revs:
4116 # Assume applied patches have no non-patch descendants and
4116 # Assume applied patches have no non-patch descendants and
4117 # are not on remote already. Filtering any changeset not
4117 # are not on remote already. Filtering any changeset not
4118 # pushed.
4118 # pushed.
4119 heads = set(pushop.revs)
4119 heads = set(pushop.revs)
4120 for node in reversed(outapplied):
4120 for node in reversed(outapplied):
4121 if node in heads:
4121 if node in heads:
4122 break
4122 break
4123 else:
4123 else:
4124 outapplied.pop()
4124 outapplied.pop()
4125 # looking for pushed and shared changeset
4125 # looking for pushed and shared changeset
4126 for node in outapplied:
4126 for node in outapplied:
4127 if self[node].phase() < phases.secret:
4127 if self[node].phase() < phases.secret:
4128 raise error.Abort(_(b'source has mq patches applied'))
4128 raise error.Abort(_(b'source has mq patches applied'))
4129 # no non-secret patches pushed
4129 # no non-secret patches pushed
4130 super(mqrepo, self).checkpush(pushop)
4130 super(mqrepo, self).checkpush(pushop)
4131
4131
4132 def _findtags(self):
4132 def _findtags(self):
4133 '''augment tags from base class with patch tags'''
4133 '''augment tags from base class with patch tags'''
4134 result = super(mqrepo, self)._findtags()
4134 result = super(mqrepo, self)._findtags()
4135
4135
4136 q = self.mq
4136 q = self.mq
4137 if not q.applied:
4137 if not q.applied:
4138 return result
4138 return result
4139
4139
4140 mqtags = [(patch.node, patch.name) for patch in q.applied]
4140 mqtags = [(patch.node, patch.name) for patch in q.applied]
4141
4141
4142 try:
4142 try:
4143 # for now ignore filtering business
4143 # for now ignore filtering business
4144 self.unfiltered().changelog.rev(mqtags[-1][0])
4144 self.unfiltered().changelog.rev(mqtags[-1][0])
4145 except error.LookupError:
4145 except error.LookupError:
4146 self.ui.warn(
4146 self.ui.warn(
4147 _(b'mq status file refers to unknown node %s\n')
4147 _(b'mq status file refers to unknown node %s\n')
4148 % short(mqtags[-1][0])
4148 % short(mqtags[-1][0])
4149 )
4149 )
4150 return result
4150 return result
4151
4151
4152 # do not add fake tags for filtered revisions
4152 # do not add fake tags for filtered revisions
4153 included = self.changelog.hasnode
4153 included = self.changelog.hasnode
4154 mqtags = [mqt for mqt in mqtags if included(mqt[0])]
4154 mqtags = [mqt for mqt in mqtags if included(mqt[0])]
4155 if not mqtags:
4155 if not mqtags:
4156 return result
4156 return result
4157
4157
4158 mqtags.append((mqtags[-1][0], b'qtip'))
4158 mqtags.append((mqtags[-1][0], b'qtip'))
4159 mqtags.append((mqtags[0][0], b'qbase'))
4159 mqtags.append((mqtags[0][0], b'qbase'))
4160 mqtags.append((self.changelog.parents(mqtags[0][0])[0], b'qparent'))
4160 mqtags.append((self.changelog.parents(mqtags[0][0])[0], b'qparent'))
4161 tags = result[0]
4161 tags = result[0]
4162 for patch in mqtags:
4162 for patch in mqtags:
4163 if patch[1] in tags:
4163 if patch[1] in tags:
4164 self.ui.warn(
4164 self.ui.warn(
4165 _(b'tag %s overrides mq patch of the same name\n')
4165 _(b'tag %s overrides mq patch of the same name\n')
4166 % patch[1]
4166 % patch[1]
4167 )
4167 )
4168 else:
4168 else:
4169 tags[patch[1]] = patch[0]
4169 tags[patch[1]] = patch[0]
4170
4170
4171 return result
4171 return result
4172
4172
4173 if repo.local():
4173 if repo.local():
4174 repo.__class__ = mqrepo
4174 repo.__class__ = mqrepo
4175
4175
4176 repo._phasedefaults.append(mqphasedefaults)
4176 repo._phasedefaults.append(mqphasedefaults)
4177
4177
4178
4178
4179 def mqimport(orig, ui, repo, *args, **kwargs):
4179 def mqimport(orig, ui, repo, *args, **kwargs):
4180 if util.safehasattr(repo, b'abortifwdirpatched') and not kwargs.get(
4180 if util.safehasattr(repo, b'abortifwdirpatched') and not kwargs.get(
4181 'no_commit', False
4181 'no_commit', False
4182 ):
4182 ):
4183 repo.abortifwdirpatched(
4183 repo.abortifwdirpatched(
4184 _(b'cannot import over an applied patch'), kwargs.get('force')
4184 _(b'cannot import over an applied patch'), kwargs.get('force')
4185 )
4185 )
4186 return orig(ui, repo, *args, **kwargs)
4186 return orig(ui, repo, *args, **kwargs)
4187
4187
4188
4188
4189 def mqinit(orig, ui, *args, **kwargs):
4189 def mqinit(orig, ui, *args, **kwargs):
4190 mq = kwargs.pop('mq', None)
4190 mq = kwargs.pop('mq', None)
4191
4191
4192 if not mq:
4192 if not mq:
4193 return orig(ui, *args, **kwargs)
4193 return orig(ui, *args, **kwargs)
4194
4194
4195 if args:
4195 if args:
4196 repopath = args[0]
4196 repopath = args[0]
4197 if not hg.islocal(repopath):
4197 if not hg.islocal(repopath):
4198 raise error.Abort(
4198 raise error.Abort(
4199 _(b'only a local queue repository may be initialized')
4199 _(b'only a local queue repository may be initialized')
4200 )
4200 )
4201 else:
4201 else:
4202 repopath = cmdutil.findrepo(encoding.getcwd())
4202 repopath = cmdutil.findrepo(encoding.getcwd())
4203 if not repopath:
4203 if not repopath:
4204 raise error.Abort(
4204 raise error.Abort(
4205 _(b'there is no Mercurial repository here (.hg not found)')
4205 _(b'there is no Mercurial repository here (.hg not found)')
4206 )
4206 )
4207 repo = hg.repository(ui, repopath)
4207 repo = hg.repository(ui, repopath)
4208 return qinit(ui, repo, True)
4208 return qinit(ui, repo, True)
4209
4209
4210
4210
4211 def mqcommand(orig, ui, repo, *args, **kwargs):
4211 def mqcommand(orig, ui, repo, *args, **kwargs):
4212 """Add --mq option to operate on patch repository instead of main"""
4212 """Add --mq option to operate on patch repository instead of main"""
4213
4213
4214 # some commands do not like getting unknown options
4214 # some commands do not like getting unknown options
4215 mq = kwargs.pop('mq', None)
4215 mq = kwargs.pop('mq', None)
4216
4216
4217 if not mq:
4217 if not mq:
4218 return orig(ui, repo, *args, **kwargs)
4218 return orig(ui, repo, *args, **kwargs)
4219
4219
4220 q = repo.mq
4220 q = repo.mq
4221 r = q.qrepo()
4221 r = q.qrepo()
4222 if not r:
4222 if not r:
4223 raise error.Abort(_(b'no queue repository'))
4223 raise error.Abort(_(b'no queue repository'))
4224 return orig(r.ui, r, *args, **kwargs)
4224 return orig(r.ui, r, *args, **kwargs)
4225
4225
4226
4226
4227 def summaryhook(ui, repo):
4227 def summaryhook(ui, repo):
4228 q = repo.mq
4228 q = repo.mq
4229 m = []
4229 m = []
4230 a, u = len(q.applied), len(q.unapplied(repo))
4230 a, u = len(q.applied), len(q.unapplied(repo))
4231 if a:
4231 if a:
4232 m.append(ui.label(_(b"%d applied"), b'qseries.applied') % a)
4232 m.append(ui.label(_(b"%d applied"), b'qseries.applied') % a)
4233 if u:
4233 if u:
4234 m.append(ui.label(_(b"%d unapplied"), b'qseries.unapplied') % u)
4234 m.append(ui.label(_(b"%d unapplied"), b'qseries.unapplied') % u)
4235 if m:
4235 if m:
4236 # i18n: column positioning for "hg summary"
4236 # i18n: column positioning for "hg summary"
4237 ui.write(_(b"mq: %s\n") % b', '.join(m))
4237 ui.write(_(b"mq: %s\n") % b', '.join(m))
4238 else:
4238 else:
4239 # i18n: column positioning for "hg summary"
4239 # i18n: column positioning for "hg summary"
4240 ui.note(_(b"mq: (empty queue)\n"))
4240 ui.note(_(b"mq: (empty queue)\n"))
4241
4241
4242
4242
4243 revsetpredicate = registrar.revsetpredicate()
4243 revsetpredicate = registrar.revsetpredicate()
4244
4244
4245
4245
4246 @revsetpredicate(b'mq()')
4246 @revsetpredicate(b'mq()')
4247 def revsetmq(repo, subset, x):
4247 def revsetmq(repo, subset, x):
4248 """Changesets managed by MQ.
4248 """Changesets managed by MQ.
4249 """
4249 """
4250 revsetlang.getargs(x, 0, 0, _(b"mq takes no arguments"))
4250 revsetlang.getargs(x, 0, 0, _(b"mq takes no arguments"))
4251 applied = {repo[r.node].rev() for r in repo.mq.applied}
4251 applied = {repo[r.node].rev() for r in repo.mq.applied}
4252 return smartset.baseset([r for r in subset if r in applied])
4252 return smartset.baseset([r for r in subset if r in applied])
4253
4253
4254
4254
4255 # tell hggettext to extract docstrings from these functions:
4255 # tell hggettext to extract docstrings from these functions:
4256 i18nfunctions = [revsetmq]
4256 i18nfunctions = [revsetmq]
4257
4257
4258
4258
4259 def extsetup(ui):
4259 def extsetup(ui):
4260 # Ensure mq wrappers are called first, regardless of extension load order by
4260 # Ensure mq wrappers are called first, regardless of extension load order by
4261 # NOT wrapping in uisetup() and instead deferring to init stage two here.
4261 # NOT wrapping in uisetup() and instead deferring to init stage two here.
4262 mqopt = [(b'', b'mq', None, _(b"operate on patch repository"))]
4262 mqopt = [(b'', b'mq', None, _(b"operate on patch repository"))]
4263
4263
4264 extensions.wrapcommand(commands.table, b'import', mqimport)
4264 extensions.wrapcommand(commands.table, b'import', mqimport)
4265 cmdutil.summaryhooks.add(b'mq', summaryhook)
4265 cmdutil.summaryhooks.add(b'mq', summaryhook)
4266
4266
4267 entry = extensions.wrapcommand(commands.table, b'init', mqinit)
4267 entry = extensions.wrapcommand(commands.table, b'init', mqinit)
4268 entry[1].extend(mqopt)
4268 entry[1].extend(mqopt)
4269
4269
4270 def dotable(cmdtable):
4270 def dotable(cmdtable):
4271 for cmd, entry in pycompat.iteritems(cmdtable):
4271 for cmd, entry in pycompat.iteritems(cmdtable):
4272 cmd = cmdutil.parsealiases(cmd)[0]
4272 cmd = cmdutil.parsealiases(cmd)[0]
4273 func = entry[0]
4273 func = entry[0]
4274 if func.norepo:
4274 if func.norepo:
4275 continue
4275 continue
4276 entry = extensions.wrapcommand(cmdtable, cmd, mqcommand)
4276 entry = extensions.wrapcommand(cmdtable, cmd, mqcommand)
4277 entry[1].extend(mqopt)
4277 entry[1].extend(mqopt)
4278
4278
4279 dotable(commands.table)
4279 dotable(commands.table)
4280
4280
4281 thismodule = sys.modules["hgext.mq"]
4281 thismodule = sys.modules["hgext.mq"]
4282 for extname, extmodule in extensions.extensions():
4282 for extname, extmodule in extensions.extensions():
4283 if extmodule != thismodule:
4283 if extmodule != thismodule:
4284 dotable(getattr(extmodule, 'cmdtable', {}))
4284 dotable(getattr(extmodule, 'cmdtable', {}))
4285
4285
4286
4286
4287 colortable = {
4287 colortable = {
4288 b'qguard.negative': b'red',
4288 b'qguard.negative': b'red',
4289 b'qguard.positive': b'yellow',
4289 b'qguard.positive': b'yellow',
4290 b'qguard.unguarded': b'green',
4290 b'qguard.unguarded': b'green',
4291 b'qseries.applied': b'blue bold underline',
4291 b'qseries.applied': b'blue bold underline',
4292 b'qseries.guarded': b'black bold',
4292 b'qseries.guarded': b'black bold',
4293 b'qseries.missing': b'red bold',
4293 b'qseries.missing': b'red bold',
4294 b'qseries.unapplied': b'black bold',
4294 b'qseries.unapplied': b'black bold',
4295 }
4295 }
@@ -1,7831 +1,7831 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[b'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'', b'forget', None, _(b'unmark a file as copied')),
2312 (b'', b'forget', None, _(b'unmark a file as copied')),
2313 (b'A', b'after', None, _(b'record a copy that has already occurred')),
2313 (b'A', b'after', None, _(b'record a copy that has already occurred')),
2314 (
2314 (
2315 b'',
2315 b'',
2316 b'at-rev',
2316 b'at-rev',
2317 b'',
2317 b'',
2318 _(b'(un)mark copies in the given revision (EXPERIMENTAL)'),
2318 _(b'(un)mark copies in the given revision (EXPERIMENTAL)'),
2319 _(b'REV'),
2319 _(b'REV'),
2320 ),
2320 ),
2321 (
2321 (
2322 b'f',
2322 b'f',
2323 b'force',
2323 b'force',
2324 None,
2324 None,
2325 _(b'forcibly copy over an existing managed file'),
2325 _(b'forcibly copy over an existing managed file'),
2326 ),
2326 ),
2327 ]
2327 ]
2328 + walkopts
2328 + walkopts
2329 + dryrunopts,
2329 + dryrunopts,
2330 _(b'[OPTION]... SOURCE... DEST'),
2330 _(b'[OPTION]... SOURCE... DEST'),
2331 helpcategory=command.CATEGORY_FILE_CONTENTS,
2331 helpcategory=command.CATEGORY_FILE_CONTENTS,
2332 )
2332 )
2333 def copy(ui, repo, *pats, **opts):
2333 def copy(ui, repo, *pats, **opts):
2334 """mark files as copied for the next commit
2334 """mark files as copied for the next commit
2335
2335
2336 Mark dest as having copies of source files. If dest is a
2336 Mark dest as having copies of source files. If dest is a
2337 directory, copies are put in that directory. If dest is a file,
2337 directory, copies are put in that directory. If dest is a file,
2338 the source must be a single file.
2338 the source must be a single file.
2339
2339
2340 By default, this command copies the contents of files as they
2340 By default, this command copies the contents of files as they
2341 exist in the working directory. If invoked with -A/--after, the
2341 exist in the working directory. If invoked with -A/--after, the
2342 operation is recorded, but no copying is performed.
2342 operation is recorded, but no copying is performed.
2343
2343
2344 To undo marking a file as copied, use --forget. With that option,
2344 To undo marking a file as copied, use --forget. With that option,
2345 all given (positional) arguments are unmarked as copies. The destination
2345 all given (positional) arguments are unmarked as copies. The destination
2346 file(s) will be left in place (still tracked).
2346 file(s) will be left in place (still tracked).
2347
2347
2348 This command takes effect with the next commit by default.
2348 This command takes effect with the next commit by default.
2349
2349
2350 Returns 0 on success, 1 if errors are encountered.
2350 Returns 0 on success, 1 if errors are encountered.
2351 """
2351 """
2352 opts = pycompat.byteskwargs(opts)
2352 opts = pycompat.byteskwargs(opts)
2353 with repo.wlock(False):
2353 with repo.wlock(False):
2354 return cmdutil.copy(ui, repo, pats, opts)
2354 return cmdutil.copy(ui, repo, pats, opts)
2355
2355
2356
2356
2357 @command(
2357 @command(
2358 b'debugcommands',
2358 b'debugcommands',
2359 [],
2359 [],
2360 _(b'[COMMAND]'),
2360 _(b'[COMMAND]'),
2361 helpcategory=command.CATEGORY_HELP,
2361 helpcategory=command.CATEGORY_HELP,
2362 norepo=True,
2362 norepo=True,
2363 )
2363 )
2364 def debugcommands(ui, cmd=b'', *args):
2364 def debugcommands(ui, cmd=b'', *args):
2365 """list all available commands and options"""
2365 """list all available commands and options"""
2366 for cmd, vals in sorted(pycompat.iteritems(table)):
2366 for cmd, vals in sorted(pycompat.iteritems(table)):
2367 cmd = cmd.split(b'|')[0]
2367 cmd = cmd.split(b'|')[0]
2368 opts = b', '.join([i[1] for i in vals[1]])
2368 opts = b', '.join([i[1] for i in vals[1]])
2369 ui.write(b'%s: %s\n' % (cmd, opts))
2369 ui.write(b'%s: %s\n' % (cmd, opts))
2370
2370
2371
2371
2372 @command(
2372 @command(
2373 b'debugcomplete',
2373 b'debugcomplete',
2374 [(b'o', b'options', None, _(b'show the command options'))],
2374 [(b'o', b'options', None, _(b'show the command options'))],
2375 _(b'[-o] CMD'),
2375 _(b'[-o] CMD'),
2376 helpcategory=command.CATEGORY_HELP,
2376 helpcategory=command.CATEGORY_HELP,
2377 norepo=True,
2377 norepo=True,
2378 )
2378 )
2379 def debugcomplete(ui, cmd=b'', **opts):
2379 def debugcomplete(ui, cmd=b'', **opts):
2380 """returns the completion list associated with the given command"""
2380 """returns the completion list associated with the given command"""
2381
2381
2382 if opts.get('options'):
2382 if opts.get('options'):
2383 options = []
2383 options = []
2384 otables = [globalopts]
2384 otables = [globalopts]
2385 if cmd:
2385 if cmd:
2386 aliases, entry = cmdutil.findcmd(cmd, table, False)
2386 aliases, entry = cmdutil.findcmd(cmd, table, False)
2387 otables.append(entry[1])
2387 otables.append(entry[1])
2388 for t in otables:
2388 for t in otables:
2389 for o in t:
2389 for o in t:
2390 if b"(DEPRECATED)" in o[3]:
2390 if b"(DEPRECATED)" in o[3]:
2391 continue
2391 continue
2392 if o[0]:
2392 if o[0]:
2393 options.append(b'-%s' % o[0])
2393 options.append(b'-%s' % o[0])
2394 options.append(b'--%s' % o[1])
2394 options.append(b'--%s' % o[1])
2395 ui.write(b"%s\n" % b"\n".join(options))
2395 ui.write(b"%s\n" % b"\n".join(options))
2396 return
2396 return
2397
2397
2398 cmdlist, unused_allcmds = cmdutil.findpossible(cmd, table)
2398 cmdlist, unused_allcmds = cmdutil.findpossible(cmd, table)
2399 if ui.verbose:
2399 if ui.verbose:
2400 cmdlist = [b' '.join(c[0]) for c in cmdlist.values()]
2400 cmdlist = [b' '.join(c[0]) for c in cmdlist.values()]
2401 ui.write(b"%s\n" % b"\n".join(sorted(cmdlist)))
2401 ui.write(b"%s\n" % b"\n".join(sorted(cmdlist)))
2402
2402
2403
2403
2404 @command(
2404 @command(
2405 b'diff',
2405 b'diff',
2406 [
2406 [
2407 (b'r', b'rev', [], _(b'revision'), _(b'REV')),
2407 (b'r', b'rev', [], _(b'revision'), _(b'REV')),
2408 (b'c', b'change', b'', _(b'change made by revision'), _(b'REV')),
2408 (b'c', b'change', b'', _(b'change made by revision'), _(b'REV')),
2409 ]
2409 ]
2410 + diffopts
2410 + diffopts
2411 + diffopts2
2411 + diffopts2
2412 + walkopts
2412 + walkopts
2413 + subrepoopts,
2413 + subrepoopts,
2414 _(b'[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'),
2414 _(b'[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'),
2415 helpcategory=command.CATEGORY_FILE_CONTENTS,
2415 helpcategory=command.CATEGORY_FILE_CONTENTS,
2416 helpbasic=True,
2416 helpbasic=True,
2417 inferrepo=True,
2417 inferrepo=True,
2418 intents={INTENT_READONLY},
2418 intents={INTENT_READONLY},
2419 )
2419 )
2420 def diff(ui, repo, *pats, **opts):
2420 def diff(ui, repo, *pats, **opts):
2421 """diff repository (or selected files)
2421 """diff repository (or selected files)
2422
2422
2423 Show differences between revisions for the specified files.
2423 Show differences between revisions for the specified files.
2424
2424
2425 Differences between files are shown using the unified diff format.
2425 Differences between files are shown using the unified diff format.
2426
2426
2427 .. note::
2427 .. note::
2428
2428
2429 :hg:`diff` may generate unexpected results for merges, as it will
2429 :hg:`diff` may generate unexpected results for merges, as it will
2430 default to comparing against the working directory's first
2430 default to comparing against the working directory's first
2431 parent changeset if no revisions are specified.
2431 parent changeset if no revisions are specified.
2432
2432
2433 When two revision arguments are given, then changes are shown
2433 When two revision arguments are given, then changes are shown
2434 between those revisions. If only one revision is specified then
2434 between those revisions. If only one revision is specified then
2435 that revision is compared to the working directory, and, when no
2435 that revision is compared to the working directory, and, when no
2436 revisions are specified, the working directory files are compared
2436 revisions are specified, the working directory files are compared
2437 to its first parent.
2437 to its first parent.
2438
2438
2439 Alternatively you can specify -c/--change with a revision to see
2439 Alternatively you can specify -c/--change with a revision to see
2440 the changes in that changeset relative to its first parent.
2440 the changes in that changeset relative to its first parent.
2441
2441
2442 Without the -a/--text option, diff will avoid generating diffs of
2442 Without the -a/--text option, diff will avoid generating diffs of
2443 files it detects as binary. With -a, diff will generate a diff
2443 files it detects as binary. With -a, diff will generate a diff
2444 anyway, probably with undesirable results.
2444 anyway, probably with undesirable results.
2445
2445
2446 Use the -g/--git option to generate diffs in the git extended diff
2446 Use the -g/--git option to generate diffs in the git extended diff
2447 format. For more information, read :hg:`help diffs`.
2447 format. For more information, read :hg:`help diffs`.
2448
2448
2449 .. container:: verbose
2449 .. container:: verbose
2450
2450
2451 Examples:
2451 Examples:
2452
2452
2453 - compare a file in the current working directory to its parent::
2453 - compare a file in the current working directory to its parent::
2454
2454
2455 hg diff foo.c
2455 hg diff foo.c
2456
2456
2457 - compare two historical versions of a directory, with rename info::
2457 - compare two historical versions of a directory, with rename info::
2458
2458
2459 hg diff --git -r 1.0:1.2 lib/
2459 hg diff --git -r 1.0:1.2 lib/
2460
2460
2461 - get change stats relative to the last change on some date::
2461 - get change stats relative to the last change on some date::
2462
2462
2463 hg diff --stat -r "date('may 2')"
2463 hg diff --stat -r "date('may 2')"
2464
2464
2465 - diff all newly-added files that contain a keyword::
2465 - diff all newly-added files that contain a keyword::
2466
2466
2467 hg diff "set:added() and grep(GNU)"
2467 hg diff "set:added() and grep(GNU)"
2468
2468
2469 - compare a revision and its parents::
2469 - compare a revision and its parents::
2470
2470
2471 hg diff -c 9353 # compare against first parent
2471 hg diff -c 9353 # compare against first parent
2472 hg diff -r 9353^:9353 # same using revset syntax
2472 hg diff -r 9353^:9353 # same using revset syntax
2473 hg diff -r 9353^2:9353 # compare against the second parent
2473 hg diff -r 9353^2:9353 # compare against the second parent
2474
2474
2475 Returns 0 on success.
2475 Returns 0 on success.
2476 """
2476 """
2477
2477
2478 opts = pycompat.byteskwargs(opts)
2478 opts = pycompat.byteskwargs(opts)
2479 revs = opts.get(b'rev')
2479 revs = opts.get(b'rev')
2480 change = opts.get(b'change')
2480 change = opts.get(b'change')
2481 stat = opts.get(b'stat')
2481 stat = opts.get(b'stat')
2482 reverse = opts.get(b'reverse')
2482 reverse = opts.get(b'reverse')
2483
2483
2484 if revs and change:
2484 if revs and change:
2485 msg = _(b'cannot specify --rev and --change at the same time')
2485 msg = _(b'cannot specify --rev and --change at the same time')
2486 raise error.Abort(msg)
2486 raise error.Abort(msg)
2487 elif change:
2487 elif change:
2488 repo = scmutil.unhidehashlikerevs(repo, [change], b'nowarn')
2488 repo = scmutil.unhidehashlikerevs(repo, [change], b'nowarn')
2489 ctx2 = scmutil.revsingle(repo, change, None)
2489 ctx2 = scmutil.revsingle(repo, change, None)
2490 ctx1 = ctx2.p1()
2490 ctx1 = ctx2.p1()
2491 else:
2491 else:
2492 repo = scmutil.unhidehashlikerevs(repo, revs, b'nowarn')
2492 repo = scmutil.unhidehashlikerevs(repo, revs, b'nowarn')
2493 ctx1, ctx2 = scmutil.revpair(repo, revs)
2493 ctx1, ctx2 = scmutil.revpair(repo, revs)
2494 node1, node2 = ctx1.node(), ctx2.node()
2494 node1, node2 = ctx1.node(), ctx2.node()
2495
2495
2496 if reverse:
2496 if reverse:
2497 node1, node2 = node2, node1
2497 node1, node2 = node2, node1
2498
2498
2499 diffopts = patch.diffallopts(ui, opts)
2499 diffopts = patch.diffallopts(ui, opts)
2500 m = scmutil.match(ctx2, pats, opts)
2500 m = scmutil.match(ctx2, pats, opts)
2501 m = repo.narrowmatch(m)
2501 m = repo.narrowmatch(m)
2502 ui.pager(b'diff')
2502 ui.pager(b'diff')
2503 logcmdutil.diffordiffstat(
2503 logcmdutil.diffordiffstat(
2504 ui,
2504 ui,
2505 repo,
2505 repo,
2506 diffopts,
2506 diffopts,
2507 node1,
2507 node1,
2508 node2,
2508 node2,
2509 m,
2509 m,
2510 stat=stat,
2510 stat=stat,
2511 listsubrepos=opts.get(b'subrepos'),
2511 listsubrepos=opts.get(b'subrepos'),
2512 root=opts.get(b'root'),
2512 root=opts.get(b'root'),
2513 )
2513 )
2514
2514
2515
2515
2516 @command(
2516 @command(
2517 b'export',
2517 b'export',
2518 [
2518 [
2519 (
2519 (
2520 b'B',
2520 b'B',
2521 b'bookmark',
2521 b'bookmark',
2522 b'',
2522 b'',
2523 _(b'export changes only reachable by given bookmark'),
2523 _(b'export changes only reachable by given bookmark'),
2524 _(b'BOOKMARK'),
2524 _(b'BOOKMARK'),
2525 ),
2525 ),
2526 (
2526 (
2527 b'o',
2527 b'o',
2528 b'output',
2528 b'output',
2529 b'',
2529 b'',
2530 _(b'print output to file with formatted name'),
2530 _(b'print output to file with formatted name'),
2531 _(b'FORMAT'),
2531 _(b'FORMAT'),
2532 ),
2532 ),
2533 (b'', b'switch-parent', None, _(b'diff against the second parent')),
2533 (b'', b'switch-parent', None, _(b'diff against the second parent')),
2534 (b'r', b'rev', [], _(b'revisions to export'), _(b'REV')),
2534 (b'r', b'rev', [], _(b'revisions to export'), _(b'REV')),
2535 ]
2535 ]
2536 + diffopts
2536 + diffopts
2537 + formatteropts,
2537 + formatteropts,
2538 _(b'[OPTION]... [-o OUTFILESPEC] [-r] [REV]...'),
2538 _(b'[OPTION]... [-o OUTFILESPEC] [-r] [REV]...'),
2539 helpcategory=command.CATEGORY_IMPORT_EXPORT,
2539 helpcategory=command.CATEGORY_IMPORT_EXPORT,
2540 helpbasic=True,
2540 helpbasic=True,
2541 intents={INTENT_READONLY},
2541 intents={INTENT_READONLY},
2542 )
2542 )
2543 def export(ui, repo, *changesets, **opts):
2543 def export(ui, repo, *changesets, **opts):
2544 """dump the header and diffs for one or more changesets
2544 """dump the header and diffs for one or more changesets
2545
2545
2546 Print the changeset header and diffs for one or more revisions.
2546 Print the changeset header and diffs for one or more revisions.
2547 If no revision is given, the parent of the working directory is used.
2547 If no revision is given, the parent of the working directory is used.
2548
2548
2549 The information shown in the changeset header is: author, date,
2549 The information shown in the changeset header is: author, date,
2550 branch name (if non-default), changeset hash, parent(s) and commit
2550 branch name (if non-default), changeset hash, parent(s) and commit
2551 comment.
2551 comment.
2552
2552
2553 .. note::
2553 .. note::
2554
2554
2555 :hg:`export` may generate unexpected diff output for merge
2555 :hg:`export` may generate unexpected diff output for merge
2556 changesets, as it will compare the merge changeset against its
2556 changesets, as it will compare the merge changeset against its
2557 first parent only.
2557 first parent only.
2558
2558
2559 Output may be to a file, in which case the name of the file is
2559 Output may be to a file, in which case the name of the file is
2560 given using a template string. See :hg:`help templates`. In addition
2560 given using a template string. See :hg:`help templates`. In addition
2561 to the common template keywords, the following formatting rules are
2561 to the common template keywords, the following formatting rules are
2562 supported:
2562 supported:
2563
2563
2564 :``%%``: literal "%" character
2564 :``%%``: literal "%" character
2565 :``%H``: changeset hash (40 hexadecimal digits)
2565 :``%H``: changeset hash (40 hexadecimal digits)
2566 :``%N``: number of patches being generated
2566 :``%N``: number of patches being generated
2567 :``%R``: changeset revision number
2567 :``%R``: changeset revision number
2568 :``%b``: basename of the exporting repository
2568 :``%b``: basename of the exporting repository
2569 :``%h``: short-form changeset hash (12 hexadecimal digits)
2569 :``%h``: short-form changeset hash (12 hexadecimal digits)
2570 :``%m``: first line of the commit message (only alphanumeric characters)
2570 :``%m``: first line of the commit message (only alphanumeric characters)
2571 :``%n``: zero-padded sequence number, starting at 1
2571 :``%n``: zero-padded sequence number, starting at 1
2572 :``%r``: zero-padded changeset revision number
2572 :``%r``: zero-padded changeset revision number
2573 :``\\``: literal "\\" character
2573 :``\\``: literal "\\" character
2574
2574
2575 Without the -a/--text option, export will avoid generating diffs
2575 Without the -a/--text option, export will avoid generating diffs
2576 of files it detects as binary. With -a, export will generate a
2576 of files it detects as binary. With -a, export will generate a
2577 diff anyway, probably with undesirable results.
2577 diff anyway, probably with undesirable results.
2578
2578
2579 With -B/--bookmark changesets reachable by the given bookmark are
2579 With -B/--bookmark changesets reachable by the given bookmark are
2580 selected.
2580 selected.
2581
2581
2582 Use the -g/--git option to generate diffs in the git extended diff
2582 Use the -g/--git option to generate diffs in the git extended diff
2583 format. See :hg:`help diffs` for more information.
2583 format. See :hg:`help diffs` for more information.
2584
2584
2585 With the --switch-parent option, the diff will be against the
2585 With the --switch-parent option, the diff will be against the
2586 second parent. It can be useful to review a merge.
2586 second parent. It can be useful to review a merge.
2587
2587
2588 .. container:: verbose
2588 .. container:: verbose
2589
2589
2590 Template:
2590 Template:
2591
2591
2592 The following keywords are supported in addition to the common template
2592 The following keywords are supported in addition to the common template
2593 keywords and functions. See also :hg:`help templates`.
2593 keywords and functions. See also :hg:`help templates`.
2594
2594
2595 :diff: String. Diff content.
2595 :diff: String. Diff content.
2596 :parents: List of strings. Parent nodes of the changeset.
2596 :parents: List of strings. Parent nodes of the changeset.
2597
2597
2598 Examples:
2598 Examples:
2599
2599
2600 - use export and import to transplant a bugfix to the current
2600 - use export and import to transplant a bugfix to the current
2601 branch::
2601 branch::
2602
2602
2603 hg export -r 9353 | hg import -
2603 hg export -r 9353 | hg import -
2604
2604
2605 - export all the changesets between two revisions to a file with
2605 - export all the changesets between two revisions to a file with
2606 rename information::
2606 rename information::
2607
2607
2608 hg export --git -r 123:150 > changes.txt
2608 hg export --git -r 123:150 > changes.txt
2609
2609
2610 - split outgoing changes into a series of patches with
2610 - split outgoing changes into a series of patches with
2611 descriptive names::
2611 descriptive names::
2612
2612
2613 hg export -r "outgoing()" -o "%n-%m.patch"
2613 hg export -r "outgoing()" -o "%n-%m.patch"
2614
2614
2615 Returns 0 on success.
2615 Returns 0 on success.
2616 """
2616 """
2617 opts = pycompat.byteskwargs(opts)
2617 opts = pycompat.byteskwargs(opts)
2618 bookmark = opts.get(b'bookmark')
2618 bookmark = opts.get(b'bookmark')
2619 changesets += tuple(opts.get(b'rev', []))
2619 changesets += tuple(opts.get(b'rev', []))
2620
2620
2621 cmdutil.check_at_most_one_arg(opts, b'rev', b'bookmark')
2621 cmdutil.check_at_most_one_arg(opts, b'rev', b'bookmark')
2622
2622
2623 if bookmark:
2623 if bookmark:
2624 if bookmark not in repo._bookmarks:
2624 if bookmark not in repo._bookmarks:
2625 raise error.Abort(_(b"bookmark '%s' not found") % bookmark)
2625 raise error.Abort(_(b"bookmark '%s' not found") % bookmark)
2626
2626
2627 revs = scmutil.bookmarkrevs(repo, bookmark)
2627 revs = scmutil.bookmarkrevs(repo, bookmark)
2628 else:
2628 else:
2629 if not changesets:
2629 if not changesets:
2630 changesets = [b'.']
2630 changesets = [b'.']
2631
2631
2632 repo = scmutil.unhidehashlikerevs(repo, changesets, b'nowarn')
2632 repo = scmutil.unhidehashlikerevs(repo, changesets, b'nowarn')
2633 revs = scmutil.revrange(repo, changesets)
2633 revs = scmutil.revrange(repo, changesets)
2634
2634
2635 if not revs:
2635 if not revs:
2636 raise error.Abort(_(b"export requires at least one changeset"))
2636 raise error.Abort(_(b"export requires at least one changeset"))
2637 if len(revs) > 1:
2637 if len(revs) > 1:
2638 ui.note(_(b'exporting patches:\n'))
2638 ui.note(_(b'exporting patches:\n'))
2639 else:
2639 else:
2640 ui.note(_(b'exporting patch:\n'))
2640 ui.note(_(b'exporting patch:\n'))
2641
2641
2642 fntemplate = opts.get(b'output')
2642 fntemplate = opts.get(b'output')
2643 if cmdutil.isstdiofilename(fntemplate):
2643 if cmdutil.isstdiofilename(fntemplate):
2644 fntemplate = b''
2644 fntemplate = b''
2645
2645
2646 if fntemplate:
2646 if fntemplate:
2647 fm = formatter.nullformatter(ui, b'export', opts)
2647 fm = formatter.nullformatter(ui, b'export', opts)
2648 else:
2648 else:
2649 ui.pager(b'export')
2649 ui.pager(b'export')
2650 fm = ui.formatter(b'export', opts)
2650 fm = ui.formatter(b'export', opts)
2651 with fm:
2651 with fm:
2652 cmdutil.export(
2652 cmdutil.export(
2653 repo,
2653 repo,
2654 revs,
2654 revs,
2655 fm,
2655 fm,
2656 fntemplate=fntemplate,
2656 fntemplate=fntemplate,
2657 switch_parent=opts.get(b'switch_parent'),
2657 switch_parent=opts.get(b'switch_parent'),
2658 opts=patch.diffallopts(ui, opts),
2658 opts=patch.diffallopts(ui, opts),
2659 )
2659 )
2660
2660
2661
2661
2662 @command(
2662 @command(
2663 b'files',
2663 b'files',
2664 [
2664 [
2665 (
2665 (
2666 b'r',
2666 b'r',
2667 b'rev',
2667 b'rev',
2668 b'',
2668 b'',
2669 _(b'search the repository as it is in REV'),
2669 _(b'search the repository as it is in REV'),
2670 _(b'REV'),
2670 _(b'REV'),
2671 ),
2671 ),
2672 (
2672 (
2673 b'0',
2673 b'0',
2674 b'print0',
2674 b'print0',
2675 None,
2675 None,
2676 _(b'end filenames with NUL, for use with xargs'),
2676 _(b'end filenames with NUL, for use with xargs'),
2677 ),
2677 ),
2678 ]
2678 ]
2679 + walkopts
2679 + walkopts
2680 + formatteropts
2680 + formatteropts
2681 + subrepoopts,
2681 + subrepoopts,
2682 _(b'[OPTION]... [FILE]...'),
2682 _(b'[OPTION]... [FILE]...'),
2683 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2683 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2684 intents={INTENT_READONLY},
2684 intents={INTENT_READONLY},
2685 )
2685 )
2686 def files(ui, repo, *pats, **opts):
2686 def files(ui, repo, *pats, **opts):
2687 """list tracked files
2687 """list tracked files
2688
2688
2689 Print files under Mercurial control in the working directory or
2689 Print files under Mercurial control in the working directory or
2690 specified revision for given files (excluding removed files).
2690 specified revision for given files (excluding removed files).
2691 Files can be specified as filenames or filesets.
2691 Files can be specified as filenames or filesets.
2692
2692
2693 If no files are given to match, this command prints the names
2693 If no files are given to match, this command prints the names
2694 of all files under Mercurial control.
2694 of all files under Mercurial control.
2695
2695
2696 .. container:: verbose
2696 .. container:: verbose
2697
2697
2698 Template:
2698 Template:
2699
2699
2700 The following keywords are supported in addition to the common template
2700 The following keywords are supported in addition to the common template
2701 keywords and functions. See also :hg:`help templates`.
2701 keywords and functions. See also :hg:`help templates`.
2702
2702
2703 :flags: String. Character denoting file's symlink and executable bits.
2703 :flags: String. Character denoting file's symlink and executable bits.
2704 :path: String. Repository-absolute path of the file.
2704 :path: String. Repository-absolute path of the file.
2705 :size: Integer. Size of the file in bytes.
2705 :size: Integer. Size of the file in bytes.
2706
2706
2707 Examples:
2707 Examples:
2708
2708
2709 - list all files under the current directory::
2709 - list all files under the current directory::
2710
2710
2711 hg files .
2711 hg files .
2712
2712
2713 - shows sizes and flags for current revision::
2713 - shows sizes and flags for current revision::
2714
2714
2715 hg files -vr .
2715 hg files -vr .
2716
2716
2717 - list all files named README::
2717 - list all files named README::
2718
2718
2719 hg files -I "**/README"
2719 hg files -I "**/README"
2720
2720
2721 - list all binary files::
2721 - list all binary files::
2722
2722
2723 hg files "set:binary()"
2723 hg files "set:binary()"
2724
2724
2725 - find files containing a regular expression::
2725 - find files containing a regular expression::
2726
2726
2727 hg files "set:grep('bob')"
2727 hg files "set:grep('bob')"
2728
2728
2729 - search tracked file contents with xargs and grep::
2729 - search tracked file contents with xargs and grep::
2730
2730
2731 hg files -0 | xargs -0 grep foo
2731 hg files -0 | xargs -0 grep foo
2732
2732
2733 See :hg:`help patterns` and :hg:`help filesets` for more information
2733 See :hg:`help patterns` and :hg:`help filesets` for more information
2734 on specifying file patterns.
2734 on specifying file patterns.
2735
2735
2736 Returns 0 if a match is found, 1 otherwise.
2736 Returns 0 if a match is found, 1 otherwise.
2737
2737
2738 """
2738 """
2739
2739
2740 opts = pycompat.byteskwargs(opts)
2740 opts = pycompat.byteskwargs(opts)
2741 rev = opts.get(b'rev')
2741 rev = opts.get(b'rev')
2742 if rev:
2742 if rev:
2743 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
2743 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
2744 ctx = scmutil.revsingle(repo, rev, None)
2744 ctx = scmutil.revsingle(repo, rev, None)
2745
2745
2746 end = b'\n'
2746 end = b'\n'
2747 if opts.get(b'print0'):
2747 if opts.get(b'print0'):
2748 end = b'\0'
2748 end = b'\0'
2749 fmt = b'%s' + end
2749 fmt = b'%s' + end
2750
2750
2751 m = scmutil.match(ctx, pats, opts)
2751 m = scmutil.match(ctx, pats, opts)
2752 ui.pager(b'files')
2752 ui.pager(b'files')
2753 uipathfn = scmutil.getuipathfn(ctx.repo(), legacyrelativevalue=True)
2753 uipathfn = scmutil.getuipathfn(ctx.repo(), legacyrelativevalue=True)
2754 with ui.formatter(b'files', opts) as fm:
2754 with ui.formatter(b'files', opts) as fm:
2755 return cmdutil.files(
2755 return cmdutil.files(
2756 ui, ctx, m, uipathfn, fm, fmt, opts.get(b'subrepos')
2756 ui, ctx, m, uipathfn, fm, fmt, opts.get(b'subrepos')
2757 )
2757 )
2758
2758
2759
2759
2760 @command(
2760 @command(
2761 b'forget',
2761 b'forget',
2762 [(b'i', b'interactive', None, _(b'use interactive mode')),]
2762 [(b'i', b'interactive', None, _(b'use interactive mode')),]
2763 + walkopts
2763 + walkopts
2764 + dryrunopts,
2764 + dryrunopts,
2765 _(b'[OPTION]... FILE...'),
2765 _(b'[OPTION]... FILE...'),
2766 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2766 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2767 helpbasic=True,
2767 helpbasic=True,
2768 inferrepo=True,
2768 inferrepo=True,
2769 )
2769 )
2770 def forget(ui, repo, *pats, **opts):
2770 def forget(ui, repo, *pats, **opts):
2771 """forget the specified files on the next commit
2771 """forget the specified files on the next commit
2772
2772
2773 Mark the specified files so they will no longer be tracked
2773 Mark the specified files so they will no longer be tracked
2774 after the next commit.
2774 after the next commit.
2775
2775
2776 This only removes files from the current branch, not from the
2776 This only removes files from the current branch, not from the
2777 entire project history, and it does not delete them from the
2777 entire project history, and it does not delete them from the
2778 working directory.
2778 working directory.
2779
2779
2780 To delete the file from the working directory, see :hg:`remove`.
2780 To delete the file from the working directory, see :hg:`remove`.
2781
2781
2782 To undo a forget before the next commit, see :hg:`add`.
2782 To undo a forget before the next commit, see :hg:`add`.
2783
2783
2784 .. container:: verbose
2784 .. container:: verbose
2785
2785
2786 Examples:
2786 Examples:
2787
2787
2788 - forget newly-added binary files::
2788 - forget newly-added binary files::
2789
2789
2790 hg forget "set:added() and binary()"
2790 hg forget "set:added() and binary()"
2791
2791
2792 - forget files that would be excluded by .hgignore::
2792 - forget files that would be excluded by .hgignore::
2793
2793
2794 hg forget "set:hgignore()"
2794 hg forget "set:hgignore()"
2795
2795
2796 Returns 0 on success.
2796 Returns 0 on success.
2797 """
2797 """
2798
2798
2799 opts = pycompat.byteskwargs(opts)
2799 opts = pycompat.byteskwargs(opts)
2800 if not pats:
2800 if not pats:
2801 raise error.Abort(_(b'no files specified'))
2801 raise error.Abort(_(b'no files specified'))
2802
2802
2803 m = scmutil.match(repo[None], pats, opts)
2803 m = scmutil.match(repo[None], pats, opts)
2804 dryrun, interactive = opts.get(b'dry_run'), opts.get(b'interactive')
2804 dryrun, interactive = opts.get(b'dry_run'), opts.get(b'interactive')
2805 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
2805 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
2806 rejected = cmdutil.forget(
2806 rejected = cmdutil.forget(
2807 ui,
2807 ui,
2808 repo,
2808 repo,
2809 m,
2809 m,
2810 prefix=b"",
2810 prefix=b"",
2811 uipathfn=uipathfn,
2811 uipathfn=uipathfn,
2812 explicitonly=False,
2812 explicitonly=False,
2813 dryrun=dryrun,
2813 dryrun=dryrun,
2814 interactive=interactive,
2814 interactive=interactive,
2815 )[0]
2815 )[0]
2816 return rejected and 1 or 0
2816 return rejected and 1 or 0
2817
2817
2818
2818
2819 @command(
2819 @command(
2820 b'graft',
2820 b'graft',
2821 [
2821 [
2822 (b'r', b'rev', [], _(b'revisions to graft'), _(b'REV')),
2822 (b'r', b'rev', [], _(b'revisions to graft'), _(b'REV')),
2823 (
2823 (
2824 b'',
2824 b'',
2825 b'base',
2825 b'base',
2826 b'',
2826 b'',
2827 _(b'base revision when doing the graft merge (ADVANCED)'),
2827 _(b'base revision when doing the graft merge (ADVANCED)'),
2828 _(b'REV'),
2828 _(b'REV'),
2829 ),
2829 ),
2830 (b'c', b'continue', False, _(b'resume interrupted graft')),
2830 (b'c', b'continue', False, _(b'resume interrupted graft')),
2831 (b'', b'stop', False, _(b'stop interrupted graft')),
2831 (b'', b'stop', False, _(b'stop interrupted graft')),
2832 (b'', b'abort', False, _(b'abort interrupted graft')),
2832 (b'', b'abort', False, _(b'abort interrupted graft')),
2833 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
2833 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
2834 (b'', b'log', None, _(b'append graft info to log message')),
2834 (b'', b'log', None, _(b'append graft info to log message')),
2835 (
2835 (
2836 b'',
2836 b'',
2837 b'no-commit',
2837 b'no-commit',
2838 None,
2838 None,
2839 _(b"don't commit, just apply the changes in working directory"),
2839 _(b"don't commit, just apply the changes in working directory"),
2840 ),
2840 ),
2841 (b'f', b'force', False, _(b'force graft')),
2841 (b'f', b'force', False, _(b'force graft')),
2842 (
2842 (
2843 b'D',
2843 b'D',
2844 b'currentdate',
2844 b'currentdate',
2845 False,
2845 False,
2846 _(b'record the current date as commit date'),
2846 _(b'record the current date as commit date'),
2847 ),
2847 ),
2848 (
2848 (
2849 b'U',
2849 b'U',
2850 b'currentuser',
2850 b'currentuser',
2851 False,
2851 False,
2852 _(b'record the current user as committer'),
2852 _(b'record the current user as committer'),
2853 ),
2853 ),
2854 ]
2854 ]
2855 + commitopts2
2855 + commitopts2
2856 + mergetoolopts
2856 + mergetoolopts
2857 + dryrunopts,
2857 + dryrunopts,
2858 _(b'[OPTION]... [-r REV]... REV...'),
2858 _(b'[OPTION]... [-r REV]... REV...'),
2859 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
2859 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
2860 )
2860 )
2861 def graft(ui, repo, *revs, **opts):
2861 def graft(ui, repo, *revs, **opts):
2862 '''copy changes from other branches onto the current branch
2862 '''copy changes from other branches onto the current branch
2863
2863
2864 This command uses Mercurial's merge logic to copy individual
2864 This command uses Mercurial's merge logic to copy individual
2865 changes from other branches without merging branches in the
2865 changes from other branches without merging branches in the
2866 history graph. This is sometimes known as 'backporting' or
2866 history graph. This is sometimes known as 'backporting' or
2867 'cherry-picking'. By default, graft will copy user, date, and
2867 'cherry-picking'. By default, graft will copy user, date, and
2868 description from the source changesets.
2868 description from the source changesets.
2869
2869
2870 Changesets that are ancestors of the current revision, that have
2870 Changesets that are ancestors of the current revision, that have
2871 already been grafted, or that are merges will be skipped.
2871 already been grafted, or that are merges will be skipped.
2872
2872
2873 If --log is specified, log messages will have a comment appended
2873 If --log is specified, log messages will have a comment appended
2874 of the form::
2874 of the form::
2875
2875
2876 (grafted from CHANGESETHASH)
2876 (grafted from CHANGESETHASH)
2877
2877
2878 If --force is specified, revisions will be grafted even if they
2878 If --force is specified, revisions will be grafted even if they
2879 are already ancestors of, or have been grafted to, the destination.
2879 are already ancestors of, or have been grafted to, the destination.
2880 This is useful when the revisions have since been backed out.
2880 This is useful when the revisions have since been backed out.
2881
2881
2882 If a graft merge results in conflicts, the graft process is
2882 If a graft merge results in conflicts, the graft process is
2883 interrupted so that the current merge can be manually resolved.
2883 interrupted so that the current merge can be manually resolved.
2884 Once all conflicts are addressed, the graft process can be
2884 Once all conflicts are addressed, the graft process can be
2885 continued with the -c/--continue option.
2885 continued with the -c/--continue option.
2886
2886
2887 The -c/--continue option reapplies all the earlier options.
2887 The -c/--continue option reapplies all the earlier options.
2888
2888
2889 .. container:: verbose
2889 .. container:: verbose
2890
2890
2891 The --base option exposes more of how graft internally uses merge with a
2891 The --base option exposes more of how graft internally uses merge with a
2892 custom base revision. --base can be used to specify another ancestor than
2892 custom base revision. --base can be used to specify another ancestor than
2893 the first and only parent.
2893 the first and only parent.
2894
2894
2895 The command::
2895 The command::
2896
2896
2897 hg graft -r 345 --base 234
2897 hg graft -r 345 --base 234
2898
2898
2899 is thus pretty much the same as::
2899 is thus pretty much the same as::
2900
2900
2901 hg diff -r 234 -r 345 | hg import
2901 hg diff -r 234 -r 345 | hg import
2902
2902
2903 but using merge to resolve conflicts and track moved files.
2903 but using merge to resolve conflicts and track moved files.
2904
2904
2905 The result of a merge can thus be backported as a single commit by
2905 The result of a merge can thus be backported as a single commit by
2906 specifying one of the merge parents as base, and thus effectively
2906 specifying one of the merge parents as base, and thus effectively
2907 grafting the changes from the other side.
2907 grafting the changes from the other side.
2908
2908
2909 It is also possible to collapse multiple changesets and clean up history
2909 It is also possible to collapse multiple changesets and clean up history
2910 by specifying another ancestor as base, much like rebase --collapse
2910 by specifying another ancestor as base, much like rebase --collapse
2911 --keep.
2911 --keep.
2912
2912
2913 The commit message can be tweaked after the fact using commit --amend .
2913 The commit message can be tweaked after the fact using commit --amend .
2914
2914
2915 For using non-ancestors as the base to backout changes, see the backout
2915 For using non-ancestors as the base to backout changes, see the backout
2916 command and the hidden --parent option.
2916 command and the hidden --parent option.
2917
2917
2918 .. container:: verbose
2918 .. container:: verbose
2919
2919
2920 Examples:
2920 Examples:
2921
2921
2922 - copy a single change to the stable branch and edit its description::
2922 - copy a single change to the stable branch and edit its description::
2923
2923
2924 hg update stable
2924 hg update stable
2925 hg graft --edit 9393
2925 hg graft --edit 9393
2926
2926
2927 - graft a range of changesets with one exception, updating dates::
2927 - graft a range of changesets with one exception, updating dates::
2928
2928
2929 hg graft -D "2085::2093 and not 2091"
2929 hg graft -D "2085::2093 and not 2091"
2930
2930
2931 - continue a graft after resolving conflicts::
2931 - continue a graft after resolving conflicts::
2932
2932
2933 hg graft -c
2933 hg graft -c
2934
2934
2935 - show the source of a grafted changeset::
2935 - show the source of a grafted changeset::
2936
2936
2937 hg log --debug -r .
2937 hg log --debug -r .
2938
2938
2939 - show revisions sorted by date::
2939 - show revisions sorted by date::
2940
2940
2941 hg log -r "sort(all(), date)"
2941 hg log -r "sort(all(), date)"
2942
2942
2943 - backport the result of a merge as a single commit::
2943 - backport the result of a merge as a single commit::
2944
2944
2945 hg graft -r 123 --base 123^
2945 hg graft -r 123 --base 123^
2946
2946
2947 - land a feature branch as one changeset::
2947 - land a feature branch as one changeset::
2948
2948
2949 hg up -cr default
2949 hg up -cr default
2950 hg graft -r featureX --base "ancestor('featureX', 'default')"
2950 hg graft -r featureX --base "ancestor('featureX', 'default')"
2951
2951
2952 See :hg:`help revisions` for more about specifying revisions.
2952 See :hg:`help revisions` for more about specifying revisions.
2953
2953
2954 Returns 0 on successful completion.
2954 Returns 0 on successful completion.
2955 '''
2955 '''
2956 with repo.wlock():
2956 with repo.wlock():
2957 return _dograft(ui, repo, *revs, **opts)
2957 return _dograft(ui, repo, *revs, **opts)
2958
2958
2959
2959
2960 def _dograft(ui, repo, *revs, **opts):
2960 def _dograft(ui, repo, *revs, **opts):
2961 opts = pycompat.byteskwargs(opts)
2961 opts = pycompat.byteskwargs(opts)
2962 if revs and opts.get(b'rev'):
2962 if revs and opts.get(b'rev'):
2963 ui.warn(
2963 ui.warn(
2964 _(
2964 _(
2965 b'warning: inconsistent use of --rev might give unexpected '
2965 b'warning: inconsistent use of --rev might give unexpected '
2966 b'revision ordering!\n'
2966 b'revision ordering!\n'
2967 )
2967 )
2968 )
2968 )
2969
2969
2970 revs = list(revs)
2970 revs = list(revs)
2971 revs.extend(opts.get(b'rev'))
2971 revs.extend(opts.get(b'rev'))
2972 basectx = None
2972 basectx = None
2973 if opts.get(b'base'):
2973 if opts.get(b'base'):
2974 basectx = scmutil.revsingle(repo, opts[b'base'], None)
2974 basectx = scmutil.revsingle(repo, opts[b'base'], None)
2975 # a dict of data to be stored in state file
2975 # a dict of data to be stored in state file
2976 statedata = {}
2976 statedata = {}
2977 # list of new nodes created by ongoing graft
2977 # list of new nodes created by ongoing graft
2978 statedata[b'newnodes'] = []
2978 statedata[b'newnodes'] = []
2979
2979
2980 cmdutil.resolvecommitoptions(ui, opts)
2980 cmdutil.resolvecommitoptions(ui, opts)
2981
2981
2982 editor = cmdutil.getcommiteditor(
2982 editor = cmdutil.getcommiteditor(
2983 editform=b'graft', **pycompat.strkwargs(opts)
2983 editform=b'graft', **pycompat.strkwargs(opts)
2984 )
2984 )
2985
2985
2986 cont = False
2986 cont = False
2987 if opts.get(b'no_commit'):
2987 if opts.get(b'no_commit'):
2988 if opts.get(b'edit'):
2988 if opts.get(b'edit'):
2989 raise error.Abort(
2989 raise error.Abort(
2990 _(b"cannot specify --no-commit and --edit together")
2990 _(b"cannot specify --no-commit and --edit together")
2991 )
2991 )
2992 if opts.get(b'currentuser'):
2992 if opts.get(b'currentuser'):
2993 raise error.Abort(
2993 raise error.Abort(
2994 _(b"cannot specify --no-commit and --currentuser together")
2994 _(b"cannot specify --no-commit and --currentuser together")
2995 )
2995 )
2996 if opts.get(b'currentdate'):
2996 if opts.get(b'currentdate'):
2997 raise error.Abort(
2997 raise error.Abort(
2998 _(b"cannot specify --no-commit and --currentdate together")
2998 _(b"cannot specify --no-commit and --currentdate together")
2999 )
2999 )
3000 if opts.get(b'log'):
3000 if opts.get(b'log'):
3001 raise error.Abort(
3001 raise error.Abort(
3002 _(b"cannot specify --no-commit and --log together")
3002 _(b"cannot specify --no-commit and --log together")
3003 )
3003 )
3004
3004
3005 graftstate = statemod.cmdstate(repo, b'graftstate')
3005 graftstate = statemod.cmdstate(repo, b'graftstate')
3006
3006
3007 if opts.get(b'stop'):
3007 if opts.get(b'stop'):
3008 if opts.get(b'continue'):
3008 if opts.get(b'continue'):
3009 raise error.Abort(
3009 raise error.Abort(
3010 _(b"cannot use '--continue' and '--stop' together")
3010 _(b"cannot use '--continue' and '--stop' together")
3011 )
3011 )
3012 if opts.get(b'abort'):
3012 if opts.get(b'abort'):
3013 raise error.Abort(_(b"cannot use '--abort' and '--stop' together"))
3013 raise error.Abort(_(b"cannot use '--abort' and '--stop' together"))
3014
3014
3015 if any(
3015 if any(
3016 (
3016 (
3017 opts.get(b'edit'),
3017 opts.get(b'edit'),
3018 opts.get(b'log'),
3018 opts.get(b'log'),
3019 opts.get(b'user'),
3019 opts.get(b'user'),
3020 opts.get(b'date'),
3020 opts.get(b'date'),
3021 opts.get(b'currentdate'),
3021 opts.get(b'currentdate'),
3022 opts.get(b'currentuser'),
3022 opts.get(b'currentuser'),
3023 opts.get(b'rev'),
3023 opts.get(b'rev'),
3024 )
3024 )
3025 ):
3025 ):
3026 raise error.Abort(_(b"cannot specify any other flag with '--stop'"))
3026 raise error.Abort(_(b"cannot specify any other flag with '--stop'"))
3027 return _stopgraft(ui, repo, graftstate)
3027 return _stopgraft(ui, repo, graftstate)
3028 elif opts.get(b'abort'):
3028 elif opts.get(b'abort'):
3029 if opts.get(b'continue'):
3029 if opts.get(b'continue'):
3030 raise error.Abort(
3030 raise error.Abort(
3031 _(b"cannot use '--continue' and '--abort' together")
3031 _(b"cannot use '--continue' and '--abort' together")
3032 )
3032 )
3033 if any(
3033 if any(
3034 (
3034 (
3035 opts.get(b'edit'),
3035 opts.get(b'edit'),
3036 opts.get(b'log'),
3036 opts.get(b'log'),
3037 opts.get(b'user'),
3037 opts.get(b'user'),
3038 opts.get(b'date'),
3038 opts.get(b'date'),
3039 opts.get(b'currentdate'),
3039 opts.get(b'currentdate'),
3040 opts.get(b'currentuser'),
3040 opts.get(b'currentuser'),
3041 opts.get(b'rev'),
3041 opts.get(b'rev'),
3042 )
3042 )
3043 ):
3043 ):
3044 raise error.Abort(
3044 raise error.Abort(
3045 _(b"cannot specify any other flag with '--abort'")
3045 _(b"cannot specify any other flag with '--abort'")
3046 )
3046 )
3047
3047
3048 return cmdutil.abortgraft(ui, repo, graftstate)
3048 return cmdutil.abortgraft(ui, repo, graftstate)
3049 elif opts.get(b'continue'):
3049 elif opts.get(b'continue'):
3050 cont = True
3050 cont = True
3051 if revs:
3051 if revs:
3052 raise error.Abort(_(b"can't specify --continue and revisions"))
3052 raise error.Abort(_(b"can't specify --continue and revisions"))
3053 # read in unfinished revisions
3053 # read in unfinished revisions
3054 if graftstate.exists():
3054 if graftstate.exists():
3055 statedata = cmdutil.readgraftstate(repo, graftstate)
3055 statedata = cmdutil.readgraftstate(repo, graftstate)
3056 if statedata.get(b'date'):
3056 if statedata.get(b'date'):
3057 opts[b'date'] = statedata[b'date']
3057 opts[b'date'] = statedata[b'date']
3058 if statedata.get(b'user'):
3058 if statedata.get(b'user'):
3059 opts[b'user'] = statedata[b'user']
3059 opts[b'user'] = statedata[b'user']
3060 if statedata.get(b'log'):
3060 if statedata.get(b'log'):
3061 opts[b'log'] = True
3061 opts[b'log'] = True
3062 if statedata.get(b'no_commit'):
3062 if statedata.get(b'no_commit'):
3063 opts[b'no_commit'] = statedata.get(b'no_commit')
3063 opts[b'no_commit'] = statedata.get(b'no_commit')
3064 nodes = statedata[b'nodes']
3064 nodes = statedata[b'nodes']
3065 revs = [repo[node].rev() for node in nodes]
3065 revs = [repo[node].rev() for node in nodes]
3066 else:
3066 else:
3067 cmdutil.wrongtooltocontinue(repo, _(b'graft'))
3067 cmdutil.wrongtooltocontinue(repo, _(b'graft'))
3068 else:
3068 else:
3069 if not revs:
3069 if not revs:
3070 raise error.Abort(_(b'no revisions specified'))
3070 raise error.Abort(_(b'no revisions specified'))
3071 cmdutil.checkunfinished(repo)
3071 cmdutil.checkunfinished(repo)
3072 cmdutil.bailifchanged(repo)
3072 cmdutil.bailifchanged(repo)
3073 revs = scmutil.revrange(repo, revs)
3073 revs = scmutil.revrange(repo, revs)
3074
3074
3075 skipped = set()
3075 skipped = set()
3076 if basectx is None:
3076 if basectx is None:
3077 # check for merges
3077 # check for merges
3078 for rev in repo.revs(b'%ld and merge()', revs):
3078 for rev in repo.revs(b'%ld and merge()', revs):
3079 ui.warn(_(b'skipping ungraftable merge revision %d\n') % rev)
3079 ui.warn(_(b'skipping ungraftable merge revision %d\n') % rev)
3080 skipped.add(rev)
3080 skipped.add(rev)
3081 revs = [r for r in revs if r not in skipped]
3081 revs = [r for r in revs if r not in skipped]
3082 if not revs:
3082 if not revs:
3083 return -1
3083 return -1
3084 if basectx is not None and len(revs) != 1:
3084 if basectx is not None and len(revs) != 1:
3085 raise error.Abort(_(b'only one revision allowed with --base '))
3085 raise error.Abort(_(b'only one revision allowed with --base '))
3086
3086
3087 # Don't check in the --continue case, in effect retaining --force across
3087 # Don't check in the --continue case, in effect retaining --force across
3088 # --continues. That's because without --force, any revisions we decided to
3088 # --continues. That's because without --force, any revisions we decided to
3089 # skip would have been filtered out here, so they wouldn't have made their
3089 # skip would have been filtered out here, so they wouldn't have made their
3090 # way to the graftstate. With --force, any revisions we would have otherwise
3090 # way to the graftstate. With --force, any revisions we would have otherwise
3091 # skipped would not have been filtered out, and if they hadn't been applied
3091 # skipped would not have been filtered out, and if they hadn't been applied
3092 # already, they'd have been in the graftstate.
3092 # already, they'd have been in the graftstate.
3093 if not (cont or opts.get(b'force')) and basectx is None:
3093 if not (cont or opts.get(b'force')) and basectx is None:
3094 # check for ancestors of dest branch
3094 # check for ancestors of dest branch
3095 ancestors = repo.revs(b'%ld & (::.)', revs)
3095 ancestors = repo.revs(b'%ld & (::.)', revs)
3096 for rev in ancestors:
3096 for rev in ancestors:
3097 ui.warn(_(b'skipping ancestor revision %d:%s\n') % (rev, repo[rev]))
3097 ui.warn(_(b'skipping ancestor revision %d:%s\n') % (rev, repo[rev]))
3098
3098
3099 revs = [r for r in revs if r not in ancestors]
3099 revs = [r for r in revs if r not in ancestors]
3100
3100
3101 if not revs:
3101 if not revs:
3102 return -1
3102 return -1
3103
3103
3104 # analyze revs for earlier grafts
3104 # analyze revs for earlier grafts
3105 ids = {}
3105 ids = {}
3106 for ctx in repo.set(b"%ld", revs):
3106 for ctx in repo.set(b"%ld", revs):
3107 ids[ctx.hex()] = ctx.rev()
3107 ids[ctx.hex()] = ctx.rev()
3108 n = ctx.extra().get(b'source')
3108 n = ctx.extra().get(b'source')
3109 if n:
3109 if n:
3110 ids[n] = ctx.rev()
3110 ids[n] = ctx.rev()
3111
3111
3112 # check ancestors for earlier grafts
3112 # check ancestors for earlier grafts
3113 ui.debug(b'scanning for duplicate grafts\n')
3113 ui.debug(b'scanning for duplicate grafts\n')
3114
3114
3115 # The only changesets we can be sure doesn't contain grafts of any
3115 # The only changesets we can be sure doesn't contain grafts of any
3116 # revs, are the ones that are common ancestors of *all* revs:
3116 # revs, are the ones that are common ancestors of *all* revs:
3117 for rev in repo.revs(b'only(%d,ancestor(%ld))', repo[b'.'].rev(), revs):
3117 for rev in repo.revs(b'only(%d,ancestor(%ld))', repo[b'.'].rev(), revs):
3118 ctx = repo[rev]
3118 ctx = repo[rev]
3119 n = ctx.extra().get(b'source')
3119 n = ctx.extra().get(b'source')
3120 if n in ids:
3120 if n in ids:
3121 try:
3121 try:
3122 r = repo[n].rev()
3122 r = repo[n].rev()
3123 except error.RepoLookupError:
3123 except error.RepoLookupError:
3124 r = None
3124 r = None
3125 if r in revs:
3125 if r in revs:
3126 ui.warn(
3126 ui.warn(
3127 _(
3127 _(
3128 b'skipping revision %d:%s '
3128 b'skipping revision %d:%s '
3129 b'(already grafted to %d:%s)\n'
3129 b'(already grafted to %d:%s)\n'
3130 )
3130 )
3131 % (r, repo[r], rev, ctx)
3131 % (r, repo[r], rev, ctx)
3132 )
3132 )
3133 revs.remove(r)
3133 revs.remove(r)
3134 elif ids[n] in revs:
3134 elif ids[n] in revs:
3135 if r is None:
3135 if r is None:
3136 ui.warn(
3136 ui.warn(
3137 _(
3137 _(
3138 b'skipping already grafted revision %d:%s '
3138 b'skipping already grafted revision %d:%s '
3139 b'(%d:%s also has unknown origin %s)\n'
3139 b'(%d:%s also has unknown origin %s)\n'
3140 )
3140 )
3141 % (ids[n], repo[ids[n]], rev, ctx, n[:12])
3141 % (ids[n], repo[ids[n]], rev, ctx, n[:12])
3142 )
3142 )
3143 else:
3143 else:
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'(%d:%s also has origin %d:%s)\n'
3147 b'(%d:%s also has origin %d:%s)\n'
3148 )
3148 )
3149 % (ids[n], repo[ids[n]], rev, ctx, r, n[:12])
3149 % (ids[n], repo[ids[n]], rev, ctx, r, n[:12])
3150 )
3150 )
3151 revs.remove(ids[n])
3151 revs.remove(ids[n])
3152 elif ctx.hex() in ids:
3152 elif ctx.hex() in ids:
3153 r = ids[ctx.hex()]
3153 r = ids[ctx.hex()]
3154 if r in revs:
3154 if r in revs:
3155 ui.warn(
3155 ui.warn(
3156 _(
3156 _(
3157 b'skipping already grafted revision %d:%s '
3157 b'skipping already grafted revision %d:%s '
3158 b'(was grafted from %d:%s)\n'
3158 b'(was grafted from %d:%s)\n'
3159 )
3159 )
3160 % (r, repo[r], rev, ctx)
3160 % (r, repo[r], rev, ctx)
3161 )
3161 )
3162 revs.remove(r)
3162 revs.remove(r)
3163 if not revs:
3163 if not revs:
3164 return -1
3164 return -1
3165
3165
3166 if opts.get(b'no_commit'):
3166 if opts.get(b'no_commit'):
3167 statedata[b'no_commit'] = True
3167 statedata[b'no_commit'] = True
3168 for pos, ctx in enumerate(repo.set(b"%ld", revs)):
3168 for pos, ctx in enumerate(repo.set(b"%ld", revs)):
3169 desc = b'%d:%s "%s"' % (
3169 desc = b'%d:%s "%s"' % (
3170 ctx.rev(),
3170 ctx.rev(),
3171 ctx,
3171 ctx,
3172 ctx.description().split(b'\n', 1)[0],
3172 ctx.description().split(b'\n', 1)[0],
3173 )
3173 )
3174 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
3174 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
3175 if names:
3175 if names:
3176 desc += b' (%s)' % b' '.join(names)
3176 desc += b' (%s)' % b' '.join(names)
3177 ui.status(_(b'grafting %s\n') % desc)
3177 ui.status(_(b'grafting %s\n') % desc)
3178 if opts.get(b'dry_run'):
3178 if opts.get(b'dry_run'):
3179 continue
3179 continue
3180
3180
3181 source = ctx.extra().get(b'source')
3181 source = ctx.extra().get(b'source')
3182 extra = {}
3182 extra = {}
3183 if source:
3183 if source:
3184 extra[b'source'] = source
3184 extra[b'source'] = source
3185 extra[b'intermediate-source'] = ctx.hex()
3185 extra[b'intermediate-source'] = ctx.hex()
3186 else:
3186 else:
3187 extra[b'source'] = ctx.hex()
3187 extra[b'source'] = ctx.hex()
3188 user = ctx.user()
3188 user = ctx.user()
3189 if opts.get(b'user'):
3189 if opts.get(b'user'):
3190 user = opts[b'user']
3190 user = opts[b'user']
3191 statedata[b'user'] = user
3191 statedata[b'user'] = user
3192 date = ctx.date()
3192 date = ctx.date()
3193 if opts.get(b'date'):
3193 if opts.get(b'date'):
3194 date = opts[b'date']
3194 date = opts[b'date']
3195 statedata[b'date'] = date
3195 statedata[b'date'] = date
3196 message = ctx.description()
3196 message = ctx.description()
3197 if opts.get(b'log'):
3197 if opts.get(b'log'):
3198 message += b'\n(grafted from %s)' % ctx.hex()
3198 message += b'\n(grafted from %s)' % ctx.hex()
3199 statedata[b'log'] = True
3199 statedata[b'log'] = True
3200
3200
3201 # we don't merge the first commit when continuing
3201 # we don't merge the first commit when continuing
3202 if not cont:
3202 if not cont:
3203 # perform the graft merge with p1(rev) as 'ancestor'
3203 # perform the graft merge with p1(rev) as 'ancestor'
3204 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
3204 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
3205 base = ctx.p1() if basectx is None else basectx
3205 base = ctx.p1() if basectx is None else basectx
3206 with ui.configoverride(overrides, b'graft'):
3206 with ui.configoverride(overrides, b'graft'):
3207 stats = mergemod.graft(repo, ctx, base, [b'local', b'graft'])
3207 stats = mergemod.graft(repo, ctx, base, [b'local', b'graft'])
3208 # report any conflicts
3208 # report any conflicts
3209 if stats.unresolvedcount > 0:
3209 if stats.unresolvedcount > 0:
3210 # write out state for --continue
3210 # write out state for --continue
3211 nodes = [repo[rev].hex() for rev in revs[pos:]]
3211 nodes = [repo[rev].hex() for rev in revs[pos:]]
3212 statedata[b'nodes'] = nodes
3212 statedata[b'nodes'] = nodes
3213 stateversion = 1
3213 stateversion = 1
3214 graftstate.save(stateversion, statedata)
3214 graftstate.save(stateversion, statedata)
3215 hint = _(b"use 'hg resolve' and 'hg graft --continue'")
3215 hint = _(b"use 'hg resolve' and 'hg graft --continue'")
3216 raise error.Abort(
3216 raise error.Abort(
3217 _(b"unresolved conflicts, can't continue"), hint=hint
3217 _(b"unresolved conflicts, can't continue"), hint=hint
3218 )
3218 )
3219 else:
3219 else:
3220 cont = False
3220 cont = False
3221
3221
3222 # commit if --no-commit is false
3222 # commit if --no-commit is false
3223 if not opts.get(b'no_commit'):
3223 if not opts.get(b'no_commit'):
3224 node = repo.commit(
3224 node = repo.commit(
3225 text=message, user=user, date=date, extra=extra, editor=editor
3225 text=message, user=user, date=date, extra=extra, editor=editor
3226 )
3226 )
3227 if node is None:
3227 if node is None:
3228 ui.warn(
3228 ui.warn(
3229 _(b'note: graft of %d:%s created no changes to commit\n')
3229 _(b'note: graft of %d:%s created no changes to commit\n')
3230 % (ctx.rev(), ctx)
3230 % (ctx.rev(), ctx)
3231 )
3231 )
3232 # checking that newnodes exist because old state files won't have it
3232 # checking that newnodes exist because old state files won't have it
3233 elif statedata.get(b'newnodes') is not None:
3233 elif statedata.get(b'newnodes') is not None:
3234 statedata[b'newnodes'].append(node)
3234 statedata[b'newnodes'].append(node)
3235
3235
3236 # remove state when we complete successfully
3236 # remove state when we complete successfully
3237 if not opts.get(b'dry_run'):
3237 if not opts.get(b'dry_run'):
3238 graftstate.delete()
3238 graftstate.delete()
3239
3239
3240 return 0
3240 return 0
3241
3241
3242
3242
3243 def _stopgraft(ui, repo, graftstate):
3243 def _stopgraft(ui, repo, graftstate):
3244 """stop the interrupted graft"""
3244 """stop the interrupted graft"""
3245 if not graftstate.exists():
3245 if not graftstate.exists():
3246 raise error.Abort(_(b"no interrupted graft found"))
3246 raise error.Abort(_(b"no interrupted graft found"))
3247 pctx = repo[b'.']
3247 pctx = repo[b'.']
3248 hg.updaterepo(repo, pctx.node(), overwrite=True)
3248 hg.updaterepo(repo, pctx.node(), overwrite=True)
3249 graftstate.delete()
3249 graftstate.delete()
3250 ui.status(_(b"stopped the interrupted graft\n"))
3250 ui.status(_(b"stopped the interrupted graft\n"))
3251 ui.status(_(b"working directory is now at %s\n") % pctx.hex()[:12])
3251 ui.status(_(b"working directory is now at %s\n") % pctx.hex()[:12])
3252 return 0
3252 return 0
3253
3253
3254
3254
3255 statemod.addunfinished(
3255 statemod.addunfinished(
3256 b'graft',
3256 b'graft',
3257 fname=b'graftstate',
3257 fname=b'graftstate',
3258 clearable=True,
3258 clearable=True,
3259 stopflag=True,
3259 stopflag=True,
3260 continueflag=True,
3260 continueflag=True,
3261 abortfunc=cmdutil.hgabortgraft,
3261 abortfunc=cmdutil.hgabortgraft,
3262 cmdhint=_(b"use 'hg graft --continue' or 'hg graft --stop' to stop"),
3262 cmdhint=_(b"use 'hg graft --continue' or 'hg graft --stop' to stop"),
3263 )
3263 )
3264
3264
3265
3265
3266 @command(
3266 @command(
3267 b'grep',
3267 b'grep',
3268 [
3268 [
3269 (b'0', b'print0', None, _(b'end fields with NUL')),
3269 (b'0', b'print0', None, _(b'end fields with NUL')),
3270 (b'', b'all', None, _(b'print all revisions that match (DEPRECATED) ')),
3270 (b'', b'all', None, _(b'print all revisions that match (DEPRECATED) ')),
3271 (
3271 (
3272 b'',
3272 b'',
3273 b'diff',
3273 b'diff',
3274 None,
3274 None,
3275 _(
3275 _(
3276 b'search revision differences for when the pattern was added '
3276 b'search revision differences for when the pattern was added '
3277 b'or removed'
3277 b'or removed'
3278 ),
3278 ),
3279 ),
3279 ),
3280 (b'a', b'text', None, _(b'treat all files as text')),
3280 (b'a', b'text', None, _(b'treat all files as text')),
3281 (
3281 (
3282 b'f',
3282 b'f',
3283 b'follow',
3283 b'follow',
3284 None,
3284 None,
3285 _(
3285 _(
3286 b'follow changeset history,'
3286 b'follow changeset history,'
3287 b' or file history across copies and renames'
3287 b' or file history across copies and renames'
3288 ),
3288 ),
3289 ),
3289 ),
3290 (b'i', b'ignore-case', None, _(b'ignore case when matching')),
3290 (b'i', b'ignore-case', None, _(b'ignore case when matching')),
3291 (
3291 (
3292 b'l',
3292 b'l',
3293 b'files-with-matches',
3293 b'files-with-matches',
3294 None,
3294 None,
3295 _(b'print only filenames and revisions that match'),
3295 _(b'print only filenames and revisions that match'),
3296 ),
3296 ),
3297 (b'n', b'line-number', None, _(b'print matching line numbers')),
3297 (b'n', b'line-number', None, _(b'print matching line numbers')),
3298 (
3298 (
3299 b'r',
3299 b'r',
3300 b'rev',
3300 b'rev',
3301 [],
3301 [],
3302 _(b'search files changed within revision range'),
3302 _(b'search files changed within revision range'),
3303 _(b'REV'),
3303 _(b'REV'),
3304 ),
3304 ),
3305 (
3305 (
3306 b'',
3306 b'',
3307 b'all-files',
3307 b'all-files',
3308 None,
3308 None,
3309 _(
3309 _(
3310 b'include all files in the changeset while grepping (DEPRECATED)'
3310 b'include all files in the changeset while grepping (DEPRECATED)'
3311 ),
3311 ),
3312 ),
3312 ),
3313 (b'u', b'user', None, _(b'list the author (long with -v)')),
3313 (b'u', b'user', None, _(b'list the author (long with -v)')),
3314 (b'd', b'date', None, _(b'list the date (short with -q)')),
3314 (b'd', b'date', None, _(b'list the date (short with -q)')),
3315 ]
3315 ]
3316 + formatteropts
3316 + formatteropts
3317 + walkopts,
3317 + walkopts,
3318 _(b'[--diff] [OPTION]... PATTERN [FILE]...'),
3318 _(b'[--diff] [OPTION]... PATTERN [FILE]...'),
3319 helpcategory=command.CATEGORY_FILE_CONTENTS,
3319 helpcategory=command.CATEGORY_FILE_CONTENTS,
3320 inferrepo=True,
3320 inferrepo=True,
3321 intents={INTENT_READONLY},
3321 intents={INTENT_READONLY},
3322 )
3322 )
3323 def grep(ui, repo, pattern, *pats, **opts):
3323 def grep(ui, repo, pattern, *pats, **opts):
3324 """search for a pattern in specified files
3324 """search for a pattern in specified files
3325
3325
3326 Search the working directory or revision history for a regular
3326 Search the working directory or revision history for a regular
3327 expression in the specified files for the entire repository.
3327 expression in the specified files for the entire repository.
3328
3328
3329 By default, grep searches the repository files in the working
3329 By default, grep searches the repository files in the working
3330 directory and prints the files where it finds a match. To specify
3330 directory and prints the files where it finds a match. To specify
3331 historical revisions instead of the working directory, use the
3331 historical revisions instead of the working directory, use the
3332 --rev flag.
3332 --rev flag.
3333
3333
3334 To search instead historical revision differences that contains a
3334 To search instead historical revision differences that contains a
3335 change in match status ("-" for a match that becomes a non-match,
3335 change in match status ("-" for a match that becomes a non-match,
3336 or "+" for a non-match that becomes a match), use the --diff flag.
3336 or "+" for a non-match that becomes a match), use the --diff flag.
3337
3337
3338 PATTERN can be any Python (roughly Perl-compatible) regular
3338 PATTERN can be any Python (roughly Perl-compatible) regular
3339 expression.
3339 expression.
3340
3340
3341 If no FILEs are specified and the --rev flag isn't supplied, all
3341 If no FILEs are specified and the --rev flag isn't supplied, all
3342 files in the working directory are searched. When using the --rev
3342 files in the working directory are searched. When using the --rev
3343 flag and specifying FILEs, use the --follow argument to also
3343 flag and specifying FILEs, use the --follow argument to also
3344 follow the specified FILEs across renames and copies.
3344 follow the specified FILEs across renames and copies.
3345
3345
3346 .. container:: verbose
3346 .. container:: verbose
3347
3347
3348 Template:
3348 Template:
3349
3349
3350 The following keywords are supported in addition to the common template
3350 The following keywords are supported in addition to the common template
3351 keywords and functions. See also :hg:`help templates`.
3351 keywords and functions. See also :hg:`help templates`.
3352
3352
3353 :change: String. Character denoting insertion ``+`` or removal ``-``.
3353 :change: String. Character denoting insertion ``+`` or removal ``-``.
3354 Available if ``--diff`` is specified.
3354 Available if ``--diff`` is specified.
3355 :lineno: Integer. Line number of the match.
3355 :lineno: Integer. Line number of the match.
3356 :path: String. Repository-absolute path of the file.
3356 :path: String. Repository-absolute path of the file.
3357 :texts: List of text chunks.
3357 :texts: List of text chunks.
3358
3358
3359 And each entry of ``{texts}`` provides the following sub-keywords.
3359 And each entry of ``{texts}`` provides the following sub-keywords.
3360
3360
3361 :matched: Boolean. True if the chunk matches the specified pattern.
3361 :matched: Boolean. True if the chunk matches the specified pattern.
3362 :text: String. Chunk content.
3362 :text: String. Chunk content.
3363
3363
3364 See :hg:`help templates.operators` for the list expansion syntax.
3364 See :hg:`help templates.operators` for the list expansion syntax.
3365
3365
3366 Returns 0 if a match is found, 1 otherwise.
3366 Returns 0 if a match is found, 1 otherwise.
3367
3367
3368 """
3368 """
3369 opts = pycompat.byteskwargs(opts)
3369 opts = pycompat.byteskwargs(opts)
3370 diff = opts.get(b'all') or opts.get(b'diff')
3370 diff = opts.get(b'all') or opts.get(b'diff')
3371 if diff and opts.get(b'all_files'):
3371 if diff and opts.get(b'all_files'):
3372 raise error.Abort(_(b'--diff and --all-files are mutually exclusive'))
3372 raise error.Abort(_(b'--diff and --all-files are mutually exclusive'))
3373 if opts.get(b'all_files') is None and not diff:
3373 if opts.get(b'all_files') is None and not diff:
3374 opts[b'all_files'] = True
3374 opts[b'all_files'] = True
3375 plaingrep = opts.get(b'all_files') and not opts.get(b'rev')
3375 plaingrep = opts.get(b'all_files') and not opts.get(b'rev')
3376 all_files = opts.get(b'all_files')
3376 all_files = opts.get(b'all_files')
3377 if plaingrep:
3377 if plaingrep:
3378 opts[b'rev'] = [b'wdir()']
3378 opts[b'rev'] = [b'wdir()']
3379
3379
3380 reflags = re.M
3380 reflags = re.M
3381 if opts.get(b'ignore_case'):
3381 if opts.get(b'ignore_case'):
3382 reflags |= re.I
3382 reflags |= re.I
3383 try:
3383 try:
3384 regexp = util.re.compile(pattern, reflags)
3384 regexp = util.re.compile(pattern, reflags)
3385 except re.error as inst:
3385 except re.error as inst:
3386 ui.warn(
3386 ui.warn(
3387 _(b"grep: invalid match pattern: %s\n") % pycompat.bytestr(inst)
3387 _(b"grep: invalid match pattern: %s\n") % pycompat.bytestr(inst)
3388 )
3388 )
3389 return 1
3389 return 1
3390 sep, eol = b':', b'\n'
3390 sep, eol = b':', b'\n'
3391 if opts.get(b'print0'):
3391 if opts.get(b'print0'):
3392 sep = eol = b'\0'
3392 sep = eol = b'\0'
3393
3393
3394 getfile = util.lrucachefunc(repo.file)
3394 getfile = util.lrucachefunc(repo.file)
3395
3395
3396 def matchlines(body):
3396 def matchlines(body):
3397 begin = 0
3397 begin = 0
3398 linenum = 0
3398 linenum = 0
3399 while begin < len(body):
3399 while begin < len(body):
3400 match = regexp.search(body, begin)
3400 match = regexp.search(body, begin)
3401 if not match:
3401 if not match:
3402 break
3402 break
3403 mstart, mend = match.span()
3403 mstart, mend = match.span()
3404 linenum += body.count(b'\n', begin, mstart) + 1
3404 linenum += body.count(b'\n', begin, mstart) + 1
3405 lstart = body.rfind(b'\n', begin, mstart) + 1 or begin
3405 lstart = body.rfind(b'\n', begin, mstart) + 1 or begin
3406 begin = body.find(b'\n', mend) + 1 or len(body) + 1
3406 begin = body.find(b'\n', mend) + 1 or len(body) + 1
3407 lend = begin - 1
3407 lend = begin - 1
3408 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
3408 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
3409
3409
3410 class linestate(object):
3410 class linestate(object):
3411 def __init__(self, line, linenum, colstart, colend):
3411 def __init__(self, line, linenum, colstart, colend):
3412 self.line = line
3412 self.line = line
3413 self.linenum = linenum
3413 self.linenum = linenum
3414 self.colstart = colstart
3414 self.colstart = colstart
3415 self.colend = colend
3415 self.colend = colend
3416
3416
3417 def __hash__(self):
3417 def __hash__(self):
3418 return hash((self.linenum, self.line))
3418 return hash((self.linenum, self.line))
3419
3419
3420 def __eq__(self, other):
3420 def __eq__(self, other):
3421 return self.line == other.line
3421 return self.line == other.line
3422
3422
3423 def findpos(self):
3423 def findpos(self):
3424 """Iterate all (start, end) indices of matches"""
3424 """Iterate all (start, end) indices of matches"""
3425 yield self.colstart, self.colend
3425 yield self.colstart, self.colend
3426 p = self.colend
3426 p = self.colend
3427 while p < len(self.line):
3427 while p < len(self.line):
3428 m = regexp.search(self.line, p)
3428 m = regexp.search(self.line, p)
3429 if not m:
3429 if not m:
3430 break
3430 break
3431 yield m.span()
3431 yield m.span()
3432 p = m.end()
3432 p = m.end()
3433
3433
3434 matches = {}
3434 matches = {}
3435 copies = {}
3435 copies = {}
3436
3436
3437 def grepbody(fn, rev, body):
3437 def grepbody(fn, rev, body):
3438 matches[rev].setdefault(fn, [])
3438 matches[rev].setdefault(fn, [])
3439 m = matches[rev][fn]
3439 m = matches[rev][fn]
3440 if body is None:
3440 if body is None:
3441 return
3441 return
3442
3442
3443 for lnum, cstart, cend, line in matchlines(body):
3443 for lnum, cstart, cend, line in matchlines(body):
3444 s = linestate(line, lnum, cstart, cend)
3444 s = linestate(line, lnum, cstart, cend)
3445 m.append(s)
3445 m.append(s)
3446
3446
3447 def difflinestates(a, b):
3447 def difflinestates(a, b):
3448 sm = difflib.SequenceMatcher(None, a, b)
3448 sm = difflib.SequenceMatcher(None, a, b)
3449 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
3449 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
3450 if tag == 'insert':
3450 if tag == 'insert':
3451 for i in pycompat.xrange(blo, bhi):
3451 for i in pycompat.xrange(blo, bhi):
3452 yield (b'+', b[i])
3452 yield (b'+', b[i])
3453 elif tag == 'delete':
3453 elif tag == 'delete':
3454 for i in pycompat.xrange(alo, ahi):
3454 for i in pycompat.xrange(alo, ahi):
3455 yield (b'-', a[i])
3455 yield (b'-', a[i])
3456 elif tag == 'replace':
3456 elif tag == 'replace':
3457 for i in pycompat.xrange(alo, ahi):
3457 for i in pycompat.xrange(alo, ahi):
3458 yield (b'-', a[i])
3458 yield (b'-', a[i])
3459 for i in pycompat.xrange(blo, bhi):
3459 for i in pycompat.xrange(blo, bhi):
3460 yield (b'+', b[i])
3460 yield (b'+', b[i])
3461
3461
3462 uipathfn = scmutil.getuipathfn(repo)
3462 uipathfn = scmutil.getuipathfn(repo)
3463
3463
3464 def display(fm, fn, ctx, pstates, states):
3464 def display(fm, fn, ctx, pstates, states):
3465 rev = scmutil.intrev(ctx)
3465 rev = scmutil.intrev(ctx)
3466 if fm.isplain():
3466 if fm.isplain():
3467 formatuser = ui.shortuser
3467 formatuser = ui.shortuser
3468 else:
3468 else:
3469 formatuser = pycompat.bytestr
3469 formatuser = pycompat.bytestr
3470 if ui.quiet:
3470 if ui.quiet:
3471 datefmt = b'%Y-%m-%d'
3471 datefmt = b'%Y-%m-%d'
3472 else:
3472 else:
3473 datefmt = b'%a %b %d %H:%M:%S %Y %1%2'
3473 datefmt = b'%a %b %d %H:%M:%S %Y %1%2'
3474 found = False
3474 found = False
3475
3475
3476 @util.cachefunc
3476 @util.cachefunc
3477 def binary():
3477 def binary():
3478 flog = getfile(fn)
3478 flog = getfile(fn)
3479 try:
3479 try:
3480 return stringutil.binary(flog.read(ctx.filenode(fn)))
3480 return stringutil.binary(flog.read(ctx.filenode(fn)))
3481 except error.WdirUnsupported:
3481 except error.WdirUnsupported:
3482 return ctx[fn].isbinary()
3482 return ctx[fn].isbinary()
3483
3483
3484 fieldnamemap = {b'linenumber': b'lineno'}
3484 fieldnamemap = {b'linenumber': b'lineno'}
3485 if diff:
3485 if diff:
3486 iter = difflinestates(pstates, states)
3486 iter = difflinestates(pstates, states)
3487 else:
3487 else:
3488 iter = [(b'', l) for l in states]
3488 iter = [(b'', l) for l in states]
3489 for change, l in iter:
3489 for change, l in iter:
3490 fm.startitem()
3490 fm.startitem()
3491 fm.context(ctx=ctx)
3491 fm.context(ctx=ctx)
3492 fm.data(node=fm.hexfunc(scmutil.binnode(ctx)), path=fn)
3492 fm.data(node=fm.hexfunc(scmutil.binnode(ctx)), path=fn)
3493 fm.plain(uipathfn(fn), label=b'grep.filename')
3493 fm.plain(uipathfn(fn), label=b'grep.filename')
3494
3494
3495 cols = [
3495 cols = [
3496 (b'rev', b'%d', rev, not plaingrep, b''),
3496 (b'rev', b'%d', rev, not plaingrep, b''),
3497 (
3497 (
3498 b'linenumber',
3498 b'linenumber',
3499 b'%d',
3499 b'%d',
3500 l.linenum,
3500 l.linenum,
3501 opts.get(b'line_number'),
3501 opts.get(b'line_number'),
3502 b'',
3502 b'',
3503 ),
3503 ),
3504 ]
3504 ]
3505 if diff:
3505 if diff:
3506 cols.append(
3506 cols.append(
3507 (
3507 (
3508 b'change',
3508 b'change',
3509 b'%s',
3509 b'%s',
3510 change,
3510 change,
3511 True,
3511 True,
3512 b'grep.inserted '
3512 b'grep.inserted '
3513 if change == b'+'
3513 if change == b'+'
3514 else b'grep.deleted ',
3514 else b'grep.deleted ',
3515 )
3515 )
3516 )
3516 )
3517 cols.extend(
3517 cols.extend(
3518 [
3518 [
3519 (
3519 (
3520 b'user',
3520 b'user',
3521 b'%s',
3521 b'%s',
3522 formatuser(ctx.user()),
3522 formatuser(ctx.user()),
3523 opts.get(b'user'),
3523 opts.get(b'user'),
3524 b'',
3524 b'',
3525 ),
3525 ),
3526 (
3526 (
3527 b'date',
3527 b'date',
3528 b'%s',
3528 b'%s',
3529 fm.formatdate(ctx.date(), datefmt),
3529 fm.formatdate(ctx.date(), datefmt),
3530 opts.get(b'date'),
3530 opts.get(b'date'),
3531 b'',
3531 b'',
3532 ),
3532 ),
3533 ]
3533 ]
3534 )
3534 )
3535 for name, fmt, data, cond, extra_label in cols:
3535 for name, fmt, data, cond, extra_label in cols:
3536 if cond:
3536 if cond:
3537 fm.plain(sep, label=b'grep.sep')
3537 fm.plain(sep, label=b'grep.sep')
3538 field = fieldnamemap.get(name, name)
3538 field = fieldnamemap.get(name, name)
3539 label = extra_label + (b'grep.%s' % name)
3539 label = extra_label + (b'grep.%s' % name)
3540 fm.condwrite(cond, field, fmt, data, label=label)
3540 fm.condwrite(cond, field, fmt, data, label=label)
3541 if not opts.get(b'files_with_matches'):
3541 if not opts.get(b'files_with_matches'):
3542 fm.plain(sep, label=b'grep.sep')
3542 fm.plain(sep, label=b'grep.sep')
3543 if not opts.get(b'text') and binary():
3543 if not opts.get(b'text') and binary():
3544 fm.plain(_(b" Binary file matches"))
3544 fm.plain(_(b" Binary file matches"))
3545 else:
3545 else:
3546 displaymatches(fm.nested(b'texts', tmpl=b'{text}'), l)
3546 displaymatches(fm.nested(b'texts', tmpl=b'{text}'), l)
3547 fm.plain(eol)
3547 fm.plain(eol)
3548 found = True
3548 found = True
3549 if opts.get(b'files_with_matches'):
3549 if opts.get(b'files_with_matches'):
3550 break
3550 break
3551 return found
3551 return found
3552
3552
3553 def displaymatches(fm, l):
3553 def displaymatches(fm, l):
3554 p = 0
3554 p = 0
3555 for s, e in l.findpos():
3555 for s, e in l.findpos():
3556 if p < s:
3556 if p < s:
3557 fm.startitem()
3557 fm.startitem()
3558 fm.write(b'text', b'%s', l.line[p:s])
3558 fm.write(b'text', b'%s', l.line[p:s])
3559 fm.data(matched=False)
3559 fm.data(matched=False)
3560 fm.startitem()
3560 fm.startitem()
3561 fm.write(b'text', b'%s', l.line[s:e], label=b'grep.match')
3561 fm.write(b'text', b'%s', l.line[s:e], label=b'grep.match')
3562 fm.data(matched=True)
3562 fm.data(matched=True)
3563 p = e
3563 p = e
3564 if p < len(l.line):
3564 if p < len(l.line):
3565 fm.startitem()
3565 fm.startitem()
3566 fm.write(b'text', b'%s', l.line[p:])
3566 fm.write(b'text', b'%s', l.line[p:])
3567 fm.data(matched=False)
3567 fm.data(matched=False)
3568 fm.end()
3568 fm.end()
3569
3569
3570 skip = set()
3570 skip = set()
3571 revfiles = {}
3571 revfiles = {}
3572 match = scmutil.match(repo[None], pats, opts)
3572 match = scmutil.match(repo[None], pats, opts)
3573 found = False
3573 found = False
3574 follow = opts.get(b'follow')
3574 follow = opts.get(b'follow')
3575
3575
3576 getrenamed = scmutil.getrenamedfn(repo)
3576 getrenamed = scmutil.getrenamedfn(repo)
3577
3577
3578 def get_file_content(filename, filelog, filenode, context, revision):
3578 def get_file_content(filename, filelog, filenode, context, revision):
3579 try:
3579 try:
3580 content = filelog.read(filenode)
3580 content = filelog.read(filenode)
3581 except error.WdirUnsupported:
3581 except error.WdirUnsupported:
3582 content = context[filename].data()
3582 content = context[filename].data()
3583 except error.CensoredNodeError:
3583 except error.CensoredNodeError:
3584 content = None
3584 content = None
3585 ui.warn(
3585 ui.warn(
3586 _(b'cannot search in censored file: %(filename)s:%(revnum)s\n')
3586 _(b'cannot search in censored file: %(filename)s:%(revnum)s\n')
3587 % {b'filename': filename, b'revnum': pycompat.bytestr(revision)}
3587 % {b'filename': filename, b'revnum': pycompat.bytestr(revision)}
3588 )
3588 )
3589 return content
3589 return content
3590
3590
3591 def prep(ctx, fns):
3591 def prep(ctx, fns):
3592 rev = ctx.rev()
3592 rev = ctx.rev()
3593 pctx = ctx.p1()
3593 pctx = ctx.p1()
3594 parent = pctx.rev()
3594 parent = pctx.rev()
3595 matches.setdefault(rev, {})
3595 matches.setdefault(rev, {})
3596 matches.setdefault(parent, {})
3596 matches.setdefault(parent, {})
3597 files = revfiles.setdefault(rev, [])
3597 files = revfiles.setdefault(rev, [])
3598 for fn in fns:
3598 for fn in fns:
3599 flog = getfile(fn)
3599 flog = getfile(fn)
3600 try:
3600 try:
3601 fnode = ctx.filenode(fn)
3601 fnode = ctx.filenode(fn)
3602 except error.LookupError:
3602 except error.LookupError:
3603 continue
3603 continue
3604
3604
3605 copy = None
3605 copy = None
3606 if follow:
3606 if follow:
3607 copy = getrenamed(fn, rev)
3607 copy = getrenamed(fn, rev)
3608 if copy:
3608 if copy:
3609 copies.setdefault(rev, {})[fn] = copy
3609 copies.setdefault(rev, {})[fn] = copy
3610 if fn in skip:
3610 if fn in skip:
3611 skip.add(copy)
3611 skip.add(copy)
3612 if fn in skip:
3612 if fn in skip:
3613 continue
3613 continue
3614 files.append(fn)
3614 files.append(fn)
3615
3615
3616 if fn not in matches[rev]:
3616 if fn not in matches[rev]:
3617 content = get_file_content(fn, flog, fnode, ctx, rev)
3617 content = get_file_content(fn, flog, fnode, ctx, rev)
3618 grepbody(fn, rev, content)
3618 grepbody(fn, rev, content)
3619
3619
3620 pfn = copy or fn
3620 pfn = copy or fn
3621 if pfn not in matches[parent]:
3621 if pfn not in matches[parent]:
3622 try:
3622 try:
3623 pfnode = pctx.filenode(pfn)
3623 pfnode = pctx.filenode(pfn)
3624 pcontent = get_file_content(pfn, flog, pfnode, pctx, parent)
3624 pcontent = get_file_content(pfn, flog, pfnode, pctx, parent)
3625 grepbody(pfn, parent, pcontent)
3625 grepbody(pfn, parent, pcontent)
3626 except error.LookupError:
3626 except error.LookupError:
3627 pass
3627 pass
3628
3628
3629 ui.pager(b'grep')
3629 ui.pager(b'grep')
3630 fm = ui.formatter(b'grep', opts)
3630 fm = ui.formatter(b'grep', opts)
3631 for ctx in cmdutil.walkchangerevs(repo, match, opts, prep):
3631 for ctx in cmdutil.walkchangerevs(repo, match, opts, prep):
3632 rev = ctx.rev()
3632 rev = ctx.rev()
3633 parent = ctx.p1().rev()
3633 parent = ctx.p1().rev()
3634 for fn in sorted(revfiles.get(rev, [])):
3634 for fn in sorted(revfiles.get(rev, [])):
3635 states = matches[rev][fn]
3635 states = matches[rev][fn]
3636 copy = copies.get(rev, {}).get(fn)
3636 copy = copies.get(rev, {}).get(fn)
3637 if fn in skip:
3637 if fn in skip:
3638 if copy:
3638 if copy:
3639 skip.add(copy)
3639 skip.add(copy)
3640 continue
3640 continue
3641 pstates = matches.get(parent, {}).get(copy or fn, [])
3641 pstates = matches.get(parent, {}).get(copy or fn, [])
3642 if pstates or states:
3642 if pstates or states:
3643 r = display(fm, fn, ctx, pstates, states)
3643 r = display(fm, fn, ctx, pstates, states)
3644 found = found or r
3644 found = found or r
3645 if r and not diff and not all_files:
3645 if r and not diff and not all_files:
3646 skip.add(fn)
3646 skip.add(fn)
3647 if copy:
3647 if copy:
3648 skip.add(copy)
3648 skip.add(copy)
3649 del revfiles[rev]
3649 del revfiles[rev]
3650 # We will keep the matches dict for the duration of the window
3650 # We will keep the matches dict for the duration of the window
3651 # clear the matches dict once the window is over
3651 # clear the matches dict once the window is over
3652 if not revfiles:
3652 if not revfiles:
3653 matches.clear()
3653 matches.clear()
3654 fm.end()
3654 fm.end()
3655
3655
3656 return not found
3656 return not found
3657
3657
3658
3658
3659 @command(
3659 @command(
3660 b'heads',
3660 b'heads',
3661 [
3661 [
3662 (
3662 (
3663 b'r',
3663 b'r',
3664 b'rev',
3664 b'rev',
3665 b'',
3665 b'',
3666 _(b'show only heads which are descendants of STARTREV'),
3666 _(b'show only heads which are descendants of STARTREV'),
3667 _(b'STARTREV'),
3667 _(b'STARTREV'),
3668 ),
3668 ),
3669 (b't', b'topo', False, _(b'show topological heads only')),
3669 (b't', b'topo', False, _(b'show topological heads only')),
3670 (
3670 (
3671 b'a',
3671 b'a',
3672 b'active',
3672 b'active',
3673 False,
3673 False,
3674 _(b'show active branchheads only (DEPRECATED)'),
3674 _(b'show active branchheads only (DEPRECATED)'),
3675 ),
3675 ),
3676 (b'c', b'closed', False, _(b'show normal and closed branch heads')),
3676 (b'c', b'closed', False, _(b'show normal and closed branch heads')),
3677 ]
3677 ]
3678 + templateopts,
3678 + templateopts,
3679 _(b'[-ct] [-r STARTREV] [REV]...'),
3679 _(b'[-ct] [-r STARTREV] [REV]...'),
3680 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3680 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3681 intents={INTENT_READONLY},
3681 intents={INTENT_READONLY},
3682 )
3682 )
3683 def heads(ui, repo, *branchrevs, **opts):
3683 def heads(ui, repo, *branchrevs, **opts):
3684 """show branch heads
3684 """show branch heads
3685
3685
3686 With no arguments, show all open branch heads in the repository.
3686 With no arguments, show all open branch heads in the repository.
3687 Branch heads are changesets that have no descendants on the
3687 Branch heads are changesets that have no descendants on the
3688 same branch. They are where development generally takes place and
3688 same branch. They are where development generally takes place and
3689 are the usual targets for update and merge operations.
3689 are the usual targets for update and merge operations.
3690
3690
3691 If one or more REVs are given, only open branch heads on the
3691 If one or more REVs are given, only open branch heads on the
3692 branches associated with the specified changesets are shown. This
3692 branches associated with the specified changesets are shown. This
3693 means that you can use :hg:`heads .` to see the heads on the
3693 means that you can use :hg:`heads .` to see the heads on the
3694 currently checked-out branch.
3694 currently checked-out branch.
3695
3695
3696 If -c/--closed is specified, also show branch heads marked closed
3696 If -c/--closed is specified, also show branch heads marked closed
3697 (see :hg:`commit --close-branch`).
3697 (see :hg:`commit --close-branch`).
3698
3698
3699 If STARTREV is specified, only those heads that are descendants of
3699 If STARTREV is specified, only those heads that are descendants of
3700 STARTREV will be displayed.
3700 STARTREV will be displayed.
3701
3701
3702 If -t/--topo is specified, named branch mechanics will be ignored and only
3702 If -t/--topo is specified, named branch mechanics will be ignored and only
3703 topological heads (changesets with no children) will be shown.
3703 topological heads (changesets with no children) will be shown.
3704
3704
3705 Returns 0 if matching heads are found, 1 if not.
3705 Returns 0 if matching heads are found, 1 if not.
3706 """
3706 """
3707
3707
3708 opts = pycompat.byteskwargs(opts)
3708 opts = pycompat.byteskwargs(opts)
3709 start = None
3709 start = None
3710 rev = opts.get(b'rev')
3710 rev = opts.get(b'rev')
3711 if rev:
3711 if rev:
3712 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
3712 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
3713 start = scmutil.revsingle(repo, rev, None).node()
3713 start = scmutil.revsingle(repo, rev, None).node()
3714
3714
3715 if opts.get(b'topo'):
3715 if opts.get(b'topo'):
3716 heads = [repo[h] for h in repo.heads(start)]
3716 heads = [repo[h] for h in repo.heads(start)]
3717 else:
3717 else:
3718 heads = []
3718 heads = []
3719 for branch in repo.branchmap():
3719 for branch in repo.branchmap():
3720 heads += repo.branchheads(branch, start, opts.get(b'closed'))
3720 heads += repo.branchheads(branch, start, opts.get(b'closed'))
3721 heads = [repo[h] for h in heads]
3721 heads = [repo[h] for h in heads]
3722
3722
3723 if branchrevs:
3723 if branchrevs:
3724 branches = set(
3724 branches = set(
3725 repo[r].branch() for r in scmutil.revrange(repo, branchrevs)
3725 repo[r].branch() for r in scmutil.revrange(repo, branchrevs)
3726 )
3726 )
3727 heads = [h for h in heads if h.branch() in branches]
3727 heads = [h for h in heads if h.branch() in branches]
3728
3728
3729 if opts.get(b'active') and branchrevs:
3729 if opts.get(b'active') and branchrevs:
3730 dagheads = repo.heads(start)
3730 dagheads = repo.heads(start)
3731 heads = [h for h in heads if h.node() in dagheads]
3731 heads = [h for h in heads if h.node() in dagheads]
3732
3732
3733 if branchrevs:
3733 if branchrevs:
3734 haveheads = set(h.branch() for h in heads)
3734 haveheads = set(h.branch() for h in heads)
3735 if branches - haveheads:
3735 if branches - haveheads:
3736 headless = b', '.join(b for b in branches - haveheads)
3736 headless = b', '.join(b for b in branches - haveheads)
3737 msg = _(b'no open branch heads found on branches %s')
3737 msg = _(b'no open branch heads found on branches %s')
3738 if opts.get(b'rev'):
3738 if opts.get(b'rev'):
3739 msg += _(b' (started at %s)') % opts[b'rev']
3739 msg += _(b' (started at %s)') % opts[b'rev']
3740 ui.warn((msg + b'\n') % headless)
3740 ui.warn((msg + b'\n') % headless)
3741
3741
3742 if not heads:
3742 if not heads:
3743 return 1
3743 return 1
3744
3744
3745 ui.pager(b'heads')
3745 ui.pager(b'heads')
3746 heads = sorted(heads, key=lambda x: -(x.rev()))
3746 heads = sorted(heads, key=lambda x: -(x.rev()))
3747 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
3747 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
3748 for ctx in heads:
3748 for ctx in heads:
3749 displayer.show(ctx)
3749 displayer.show(ctx)
3750 displayer.close()
3750 displayer.close()
3751
3751
3752
3752
3753 @command(
3753 @command(
3754 b'help',
3754 b'help',
3755 [
3755 [
3756 (b'e', b'extension', None, _(b'show only help for extensions')),
3756 (b'e', b'extension', None, _(b'show only help for extensions')),
3757 (b'c', b'command', None, _(b'show only help for commands')),
3757 (b'c', b'command', None, _(b'show only help for commands')),
3758 (b'k', b'keyword', None, _(b'show topics matching keyword')),
3758 (b'k', b'keyword', None, _(b'show topics matching keyword')),
3759 (
3759 (
3760 b's',
3760 b's',
3761 b'system',
3761 b'system',
3762 [],
3762 [],
3763 _(b'show help for specific platform(s)'),
3763 _(b'show help for specific platform(s)'),
3764 _(b'PLATFORM'),
3764 _(b'PLATFORM'),
3765 ),
3765 ),
3766 ],
3766 ],
3767 _(b'[-eck] [-s PLATFORM] [TOPIC]'),
3767 _(b'[-eck] [-s PLATFORM] [TOPIC]'),
3768 helpcategory=command.CATEGORY_HELP,
3768 helpcategory=command.CATEGORY_HELP,
3769 norepo=True,
3769 norepo=True,
3770 intents={INTENT_READONLY},
3770 intents={INTENT_READONLY},
3771 )
3771 )
3772 def help_(ui, name=None, **opts):
3772 def help_(ui, name=None, **opts):
3773 """show help for a given topic or a help overview
3773 """show help for a given topic or a help overview
3774
3774
3775 With no arguments, print a list of commands with short help messages.
3775 With no arguments, print a list of commands with short help messages.
3776
3776
3777 Given a topic, extension, or command name, print help for that
3777 Given a topic, extension, or command name, print help for that
3778 topic.
3778 topic.
3779
3779
3780 Returns 0 if successful.
3780 Returns 0 if successful.
3781 """
3781 """
3782
3782
3783 keep = opts.get('system') or []
3783 keep = opts.get('system') or []
3784 if len(keep) == 0:
3784 if len(keep) == 0:
3785 if pycompat.sysplatform.startswith(b'win'):
3785 if pycompat.sysplatform.startswith(b'win'):
3786 keep.append(b'windows')
3786 keep.append(b'windows')
3787 elif pycompat.sysplatform == b'OpenVMS':
3787 elif pycompat.sysplatform == b'OpenVMS':
3788 keep.append(b'vms')
3788 keep.append(b'vms')
3789 elif pycompat.sysplatform == b'plan9':
3789 elif pycompat.sysplatform == b'plan9':
3790 keep.append(b'plan9')
3790 keep.append(b'plan9')
3791 else:
3791 else:
3792 keep.append(b'unix')
3792 keep.append(b'unix')
3793 keep.append(pycompat.sysplatform.lower())
3793 keep.append(pycompat.sysplatform.lower())
3794 if ui.verbose:
3794 if ui.verbose:
3795 keep.append(b'verbose')
3795 keep.append(b'verbose')
3796
3796
3797 commands = sys.modules[__name__]
3797 commands = sys.modules[__name__]
3798 formatted = help.formattedhelp(ui, commands, name, keep=keep, **opts)
3798 formatted = help.formattedhelp(ui, commands, name, keep=keep, **opts)
3799 ui.pager(b'help')
3799 ui.pager(b'help')
3800 ui.write(formatted)
3800 ui.write(formatted)
3801
3801
3802
3802
3803 @command(
3803 @command(
3804 b'identify|id',
3804 b'identify|id',
3805 [
3805 [
3806 (b'r', b'rev', b'', _(b'identify the specified revision'), _(b'REV')),
3806 (b'r', b'rev', b'', _(b'identify the specified revision'), _(b'REV')),
3807 (b'n', b'num', None, _(b'show local revision number')),
3807 (b'n', b'num', None, _(b'show local revision number')),
3808 (b'i', b'id', None, _(b'show global revision id')),
3808 (b'i', b'id', None, _(b'show global revision id')),
3809 (b'b', b'branch', None, _(b'show branch')),
3809 (b'b', b'branch', None, _(b'show branch')),
3810 (b't', b'tags', None, _(b'show tags')),
3810 (b't', b'tags', None, _(b'show tags')),
3811 (b'B', b'bookmarks', None, _(b'show bookmarks')),
3811 (b'B', b'bookmarks', None, _(b'show bookmarks')),
3812 ]
3812 ]
3813 + remoteopts
3813 + remoteopts
3814 + formatteropts,
3814 + formatteropts,
3815 _(b'[-nibtB] [-r REV] [SOURCE]'),
3815 _(b'[-nibtB] [-r REV] [SOURCE]'),
3816 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3816 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3817 optionalrepo=True,
3817 optionalrepo=True,
3818 intents={INTENT_READONLY},
3818 intents={INTENT_READONLY},
3819 )
3819 )
3820 def identify(
3820 def identify(
3821 ui,
3821 ui,
3822 repo,
3822 repo,
3823 source=None,
3823 source=None,
3824 rev=None,
3824 rev=None,
3825 num=None,
3825 num=None,
3826 id=None,
3826 id=None,
3827 branch=None,
3827 branch=None,
3828 tags=None,
3828 tags=None,
3829 bookmarks=None,
3829 bookmarks=None,
3830 **opts
3830 **opts
3831 ):
3831 ):
3832 """identify the working directory or specified revision
3832 """identify the working directory or specified revision
3833
3833
3834 Print a summary identifying the repository state at REV using one or
3834 Print a summary identifying the repository state at REV using one or
3835 two parent hash identifiers, followed by a "+" if the working
3835 two parent hash identifiers, followed by a "+" if the working
3836 directory has uncommitted changes, the branch name (if not default),
3836 directory has uncommitted changes, the branch name (if not default),
3837 a list of tags, and a list of bookmarks.
3837 a list of tags, and a list of bookmarks.
3838
3838
3839 When REV is not given, print a summary of the current state of the
3839 When REV is not given, print a summary of the current state of the
3840 repository including the working directory. Specify -r. to get information
3840 repository including the working directory. Specify -r. to get information
3841 of the working directory parent without scanning uncommitted changes.
3841 of the working directory parent without scanning uncommitted changes.
3842
3842
3843 Specifying a path to a repository root or Mercurial bundle will
3843 Specifying a path to a repository root or Mercurial bundle will
3844 cause lookup to operate on that repository/bundle.
3844 cause lookup to operate on that repository/bundle.
3845
3845
3846 .. container:: verbose
3846 .. container:: verbose
3847
3847
3848 Template:
3848 Template:
3849
3849
3850 The following keywords are supported in addition to the common template
3850 The following keywords are supported in addition to the common template
3851 keywords and functions. See also :hg:`help templates`.
3851 keywords and functions. See also :hg:`help templates`.
3852
3852
3853 :dirty: String. Character ``+`` denoting if the working directory has
3853 :dirty: String. Character ``+`` denoting if the working directory has
3854 uncommitted changes.
3854 uncommitted changes.
3855 :id: String. One or two nodes, optionally followed by ``+``.
3855 :id: String. One or two nodes, optionally followed by ``+``.
3856 :parents: List of strings. Parent nodes of the changeset.
3856 :parents: List of strings. Parent nodes of the changeset.
3857
3857
3858 Examples:
3858 Examples:
3859
3859
3860 - generate a build identifier for the working directory::
3860 - generate a build identifier for the working directory::
3861
3861
3862 hg id --id > build-id.dat
3862 hg id --id > build-id.dat
3863
3863
3864 - find the revision corresponding to a tag::
3864 - find the revision corresponding to a tag::
3865
3865
3866 hg id -n -r 1.3
3866 hg id -n -r 1.3
3867
3867
3868 - check the most recent revision of a remote repository::
3868 - check the most recent revision of a remote repository::
3869
3869
3870 hg id -r tip https://www.mercurial-scm.org/repo/hg/
3870 hg id -r tip https://www.mercurial-scm.org/repo/hg/
3871
3871
3872 See :hg:`log` for generating more information about specific revisions,
3872 See :hg:`log` for generating more information about specific revisions,
3873 including full hash identifiers.
3873 including full hash identifiers.
3874
3874
3875 Returns 0 if successful.
3875 Returns 0 if successful.
3876 """
3876 """
3877
3877
3878 opts = pycompat.byteskwargs(opts)
3878 opts = pycompat.byteskwargs(opts)
3879 if not repo and not source:
3879 if not repo and not source:
3880 raise error.Abort(
3880 raise error.Abort(
3881 _(b"there is no Mercurial repository here (.hg not found)")
3881 _(b"there is no Mercurial repository here (.hg not found)")
3882 )
3882 )
3883
3883
3884 default = not (num or id or branch or tags or bookmarks)
3884 default = not (num or id or branch or tags or bookmarks)
3885 output = []
3885 output = []
3886 revs = []
3886 revs = []
3887
3887
3888 if source:
3888 if source:
3889 source, branches = hg.parseurl(ui.expandpath(source))
3889 source, branches = hg.parseurl(ui.expandpath(source))
3890 peer = hg.peer(repo or ui, opts, source) # only pass ui when no repo
3890 peer = hg.peer(repo or ui, opts, source) # only pass ui when no repo
3891 repo = peer.local()
3891 repo = peer.local()
3892 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
3892 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
3893
3893
3894 fm = ui.formatter(b'identify', opts)
3894 fm = ui.formatter(b'identify', opts)
3895 fm.startitem()
3895 fm.startitem()
3896
3896
3897 if not repo:
3897 if not repo:
3898 if num or branch or tags:
3898 if num or branch or tags:
3899 raise error.Abort(
3899 raise error.Abort(
3900 _(b"can't query remote revision number, branch, or tags")
3900 _(b"can't query remote revision number, branch, or tags")
3901 )
3901 )
3902 if not rev and revs:
3902 if not rev and revs:
3903 rev = revs[0]
3903 rev = revs[0]
3904 if not rev:
3904 if not rev:
3905 rev = b"tip"
3905 rev = b"tip"
3906
3906
3907 remoterev = peer.lookup(rev)
3907 remoterev = peer.lookup(rev)
3908 hexrev = fm.hexfunc(remoterev)
3908 hexrev = fm.hexfunc(remoterev)
3909 if default or id:
3909 if default or id:
3910 output = [hexrev]
3910 output = [hexrev]
3911 fm.data(id=hexrev)
3911 fm.data(id=hexrev)
3912
3912
3913 @util.cachefunc
3913 @util.cachefunc
3914 def getbms():
3914 def getbms():
3915 bms = []
3915 bms = []
3916
3916
3917 if b'bookmarks' in peer.listkeys(b'namespaces'):
3917 if b'bookmarks' in peer.listkeys(b'namespaces'):
3918 hexremoterev = hex(remoterev)
3918 hexremoterev = hex(remoterev)
3919 bms = [
3919 bms = [
3920 bm
3920 bm
3921 for bm, bmr in pycompat.iteritems(
3921 for bm, bmr in pycompat.iteritems(
3922 peer.listkeys(b'bookmarks')
3922 peer.listkeys(b'bookmarks')
3923 )
3923 )
3924 if bmr == hexremoterev
3924 if bmr == hexremoterev
3925 ]
3925 ]
3926
3926
3927 return sorted(bms)
3927 return sorted(bms)
3928
3928
3929 if fm.isplain():
3929 if fm.isplain():
3930 if bookmarks:
3930 if bookmarks:
3931 output.extend(getbms())
3931 output.extend(getbms())
3932 elif default and not ui.quiet:
3932 elif default and not ui.quiet:
3933 # multiple bookmarks for a single parent separated by '/'
3933 # multiple bookmarks for a single parent separated by '/'
3934 bm = b'/'.join(getbms())
3934 bm = b'/'.join(getbms())
3935 if bm:
3935 if bm:
3936 output.append(bm)
3936 output.append(bm)
3937 else:
3937 else:
3938 fm.data(node=hex(remoterev))
3938 fm.data(node=hex(remoterev))
3939 if bookmarks or b'bookmarks' in fm.datahint():
3939 if bookmarks or b'bookmarks' in fm.datahint():
3940 fm.data(bookmarks=fm.formatlist(getbms(), name=b'bookmark'))
3940 fm.data(bookmarks=fm.formatlist(getbms(), name=b'bookmark'))
3941 else:
3941 else:
3942 if rev:
3942 if rev:
3943 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
3943 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
3944 ctx = scmutil.revsingle(repo, rev, None)
3944 ctx = scmutil.revsingle(repo, rev, None)
3945
3945
3946 if ctx.rev() is None:
3946 if ctx.rev() is None:
3947 ctx = repo[None]
3947 ctx = repo[None]
3948 parents = ctx.parents()
3948 parents = ctx.parents()
3949 taglist = []
3949 taglist = []
3950 for p in parents:
3950 for p in parents:
3951 taglist.extend(p.tags())
3951 taglist.extend(p.tags())
3952
3952
3953 dirty = b""
3953 dirty = b""
3954 if ctx.dirty(missing=True, merge=False, branch=False):
3954 if ctx.dirty(missing=True, merge=False, branch=False):
3955 dirty = b'+'
3955 dirty = b'+'
3956 fm.data(dirty=dirty)
3956 fm.data(dirty=dirty)
3957
3957
3958 hexoutput = [fm.hexfunc(p.node()) for p in parents]
3958 hexoutput = [fm.hexfunc(p.node()) for p in parents]
3959 if default or id:
3959 if default or id:
3960 output = [b"%s%s" % (b'+'.join(hexoutput), dirty)]
3960 output = [b"%s%s" % (b'+'.join(hexoutput), dirty)]
3961 fm.data(id=b"%s%s" % (b'+'.join(hexoutput), dirty))
3961 fm.data(id=b"%s%s" % (b'+'.join(hexoutput), dirty))
3962
3962
3963 if num:
3963 if num:
3964 numoutput = [b"%d" % p.rev() for p in parents]
3964 numoutput = [b"%d" % p.rev() for p in parents]
3965 output.append(b"%s%s" % (b'+'.join(numoutput), dirty))
3965 output.append(b"%s%s" % (b'+'.join(numoutput), dirty))
3966
3966
3967 fm.data(
3967 fm.data(
3968 parents=fm.formatlist(
3968 parents=fm.formatlist(
3969 [fm.hexfunc(p.node()) for p in parents], name=b'node'
3969 [fm.hexfunc(p.node()) for p in parents], name=b'node'
3970 )
3970 )
3971 )
3971 )
3972 else:
3972 else:
3973 hexoutput = fm.hexfunc(ctx.node())
3973 hexoutput = fm.hexfunc(ctx.node())
3974 if default or id:
3974 if default or id:
3975 output = [hexoutput]
3975 output = [hexoutput]
3976 fm.data(id=hexoutput)
3976 fm.data(id=hexoutput)
3977
3977
3978 if num:
3978 if num:
3979 output.append(pycompat.bytestr(ctx.rev()))
3979 output.append(pycompat.bytestr(ctx.rev()))
3980 taglist = ctx.tags()
3980 taglist = ctx.tags()
3981
3981
3982 if default and not ui.quiet:
3982 if default and not ui.quiet:
3983 b = ctx.branch()
3983 b = ctx.branch()
3984 if b != b'default':
3984 if b != b'default':
3985 output.append(b"(%s)" % b)
3985 output.append(b"(%s)" % b)
3986
3986
3987 # multiple tags for a single parent separated by '/'
3987 # multiple tags for a single parent separated by '/'
3988 t = b'/'.join(taglist)
3988 t = b'/'.join(taglist)
3989 if t:
3989 if t:
3990 output.append(t)
3990 output.append(t)
3991
3991
3992 # multiple bookmarks for a single parent separated by '/'
3992 # multiple bookmarks for a single parent separated by '/'
3993 bm = b'/'.join(ctx.bookmarks())
3993 bm = b'/'.join(ctx.bookmarks())
3994 if bm:
3994 if bm:
3995 output.append(bm)
3995 output.append(bm)
3996 else:
3996 else:
3997 if branch:
3997 if branch:
3998 output.append(ctx.branch())
3998 output.append(ctx.branch())
3999
3999
4000 if tags:
4000 if tags:
4001 output.extend(taglist)
4001 output.extend(taglist)
4002
4002
4003 if bookmarks:
4003 if bookmarks:
4004 output.extend(ctx.bookmarks())
4004 output.extend(ctx.bookmarks())
4005
4005
4006 fm.data(node=ctx.hex())
4006 fm.data(node=ctx.hex())
4007 fm.data(branch=ctx.branch())
4007 fm.data(branch=ctx.branch())
4008 fm.data(tags=fm.formatlist(taglist, name=b'tag', sep=b':'))
4008 fm.data(tags=fm.formatlist(taglist, name=b'tag', sep=b':'))
4009 fm.data(bookmarks=fm.formatlist(ctx.bookmarks(), name=b'bookmark'))
4009 fm.data(bookmarks=fm.formatlist(ctx.bookmarks(), name=b'bookmark'))
4010 fm.context(ctx=ctx)
4010 fm.context(ctx=ctx)
4011
4011
4012 fm.plain(b"%s\n" % b' '.join(output))
4012 fm.plain(b"%s\n" % b' '.join(output))
4013 fm.end()
4013 fm.end()
4014
4014
4015
4015
4016 @command(
4016 @command(
4017 b'import|patch',
4017 b'import|patch',
4018 [
4018 [
4019 (
4019 (
4020 b'p',
4020 b'p',
4021 b'strip',
4021 b'strip',
4022 1,
4022 1,
4023 _(
4023 _(
4024 b'directory strip option for patch. This has the same '
4024 b'directory strip option for patch. This has the same '
4025 b'meaning as the corresponding patch option'
4025 b'meaning as the corresponding patch option'
4026 ),
4026 ),
4027 _(b'NUM'),
4027 _(b'NUM'),
4028 ),
4028 ),
4029 (b'b', b'base', b'', _(b'base path (DEPRECATED)'), _(b'PATH')),
4029 (b'b', b'base', b'', _(b'base path (DEPRECATED)'), _(b'PATH')),
4030 (b'', b'secret', None, _(b'use the secret phase for committing')),
4030 (b'', b'secret', None, _(b'use the secret phase for committing')),
4031 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
4031 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
4032 (
4032 (
4033 b'f',
4033 b'f',
4034 b'force',
4034 b'force',
4035 None,
4035 None,
4036 _(b'skip check for outstanding uncommitted changes (DEPRECATED)'),
4036 _(b'skip check for outstanding uncommitted changes (DEPRECATED)'),
4037 ),
4037 ),
4038 (
4038 (
4039 b'',
4039 b'',
4040 b'no-commit',
4040 b'no-commit',
4041 None,
4041 None,
4042 _(b"don't commit, just update the working directory"),
4042 _(b"don't commit, just update the working directory"),
4043 ),
4043 ),
4044 (
4044 (
4045 b'',
4045 b'',
4046 b'bypass',
4046 b'bypass',
4047 None,
4047 None,
4048 _(b"apply patch without touching the working directory"),
4048 _(b"apply patch without touching the working directory"),
4049 ),
4049 ),
4050 (b'', b'partial', None, _(b'commit even if some hunks fail')),
4050 (b'', b'partial', None, _(b'commit even if some hunks fail')),
4051 (b'', b'exact', None, _(b'abort if patch would apply lossily')),
4051 (b'', b'exact', None, _(b'abort if patch would apply lossily')),
4052 (b'', b'prefix', b'', _(b'apply patch to subdirectory'), _(b'DIR')),
4052 (b'', b'prefix', b'', _(b'apply patch to subdirectory'), _(b'DIR')),
4053 (
4053 (
4054 b'',
4054 b'',
4055 b'import-branch',
4055 b'import-branch',
4056 None,
4056 None,
4057 _(b'use any branch information in patch (implied by --exact)'),
4057 _(b'use any branch information in patch (implied by --exact)'),
4058 ),
4058 ),
4059 ]
4059 ]
4060 + commitopts
4060 + commitopts
4061 + commitopts2
4061 + commitopts2
4062 + similarityopts,
4062 + similarityopts,
4063 _(b'[OPTION]... PATCH...'),
4063 _(b'[OPTION]... PATCH...'),
4064 helpcategory=command.CATEGORY_IMPORT_EXPORT,
4064 helpcategory=command.CATEGORY_IMPORT_EXPORT,
4065 )
4065 )
4066 def import_(ui, repo, patch1=None, *patches, **opts):
4066 def import_(ui, repo, patch1=None, *patches, **opts):
4067 """import an ordered set of patches
4067 """import an ordered set of patches
4068
4068
4069 Import a list of patches and commit them individually (unless
4069 Import a list of patches and commit them individually (unless
4070 --no-commit is specified).
4070 --no-commit is specified).
4071
4071
4072 To read a patch from standard input (stdin), use "-" as the patch
4072 To read a patch from standard input (stdin), use "-" as the patch
4073 name. If a URL is specified, the patch will be downloaded from
4073 name. If a URL is specified, the patch will be downloaded from
4074 there.
4074 there.
4075
4075
4076 Import first applies changes to the working directory (unless
4076 Import first applies changes to the working directory (unless
4077 --bypass is specified), import will abort if there are outstanding
4077 --bypass is specified), import will abort if there are outstanding
4078 changes.
4078 changes.
4079
4079
4080 Use --bypass to apply and commit patches directly to the
4080 Use --bypass to apply and commit patches directly to the
4081 repository, without affecting the working directory. Without
4081 repository, without affecting the working directory. Without
4082 --exact, patches will be applied on top of the working directory
4082 --exact, patches will be applied on top of the working directory
4083 parent revision.
4083 parent revision.
4084
4084
4085 You can import a patch straight from a mail message. Even patches
4085 You can import a patch straight from a mail message. Even patches
4086 as attachments work (to use the body part, it must have type
4086 as attachments work (to use the body part, it must have type
4087 text/plain or text/x-patch). From and Subject headers of email
4087 text/plain or text/x-patch). From and Subject headers of email
4088 message are used as default committer and commit message. All
4088 message are used as default committer and commit message. All
4089 text/plain body parts before first diff are added to the commit
4089 text/plain body parts before first diff are added to the commit
4090 message.
4090 message.
4091
4091
4092 If the imported patch was generated by :hg:`export`, user and
4092 If the imported patch was generated by :hg:`export`, user and
4093 description from patch override values from message headers and
4093 description from patch override values from message headers and
4094 body. Values given on command line with -m/--message and -u/--user
4094 body. Values given on command line with -m/--message and -u/--user
4095 override these.
4095 override these.
4096
4096
4097 If --exact is specified, import will set the working directory to
4097 If --exact is specified, import will set the working directory to
4098 the parent of each patch before applying it, and will abort if the
4098 the parent of each patch before applying it, and will abort if the
4099 resulting changeset has a different ID than the one recorded in
4099 resulting changeset has a different ID than the one recorded in
4100 the patch. This will guard against various ways that portable
4100 the patch. This will guard against various ways that portable
4101 patch formats and mail systems might fail to transfer Mercurial
4101 patch formats and mail systems might fail to transfer Mercurial
4102 data or metadata. See :hg:`bundle` for lossless transmission.
4102 data or metadata. See :hg:`bundle` for lossless transmission.
4103
4103
4104 Use --partial to ensure a changeset will be created from the patch
4104 Use --partial to ensure a changeset will be created from the patch
4105 even if some hunks fail to apply. Hunks that fail to apply will be
4105 even if some hunks fail to apply. Hunks that fail to apply will be
4106 written to a <target-file>.rej file. Conflicts can then be resolved
4106 written to a <target-file>.rej file. Conflicts can then be resolved
4107 by hand before :hg:`commit --amend` is run to update the created
4107 by hand before :hg:`commit --amend` is run to update the created
4108 changeset. This flag exists to let people import patches that
4108 changeset. This flag exists to let people import patches that
4109 partially apply without losing the associated metadata (author,
4109 partially apply without losing the associated metadata (author,
4110 date, description, ...).
4110 date, description, ...).
4111
4111
4112 .. note::
4112 .. note::
4113
4113
4114 When no hunks apply cleanly, :hg:`import --partial` will create
4114 When no hunks apply cleanly, :hg:`import --partial` will create
4115 an empty changeset, importing only the patch metadata.
4115 an empty changeset, importing only the patch metadata.
4116
4116
4117 With -s/--similarity, hg will attempt to discover renames and
4117 With -s/--similarity, hg will attempt to discover renames and
4118 copies in the patch in the same way as :hg:`addremove`.
4118 copies in the patch in the same way as :hg:`addremove`.
4119
4119
4120 It is possible to use external patch programs to perform the patch
4120 It is possible to use external patch programs to perform the patch
4121 by setting the ``ui.patch`` configuration option. For the default
4121 by setting the ``ui.patch`` configuration option. For the default
4122 internal tool, the fuzz can also be configured via ``patch.fuzz``.
4122 internal tool, the fuzz can also be configured via ``patch.fuzz``.
4123 See :hg:`help config` for more information about configuration
4123 See :hg:`help config` for more information about configuration
4124 files and how to use these options.
4124 files and how to use these options.
4125
4125
4126 See :hg:`help dates` for a list of formats valid for -d/--date.
4126 See :hg:`help dates` for a list of formats valid for -d/--date.
4127
4127
4128 .. container:: verbose
4128 .. container:: verbose
4129
4129
4130 Examples:
4130 Examples:
4131
4131
4132 - import a traditional patch from a website and detect renames::
4132 - import a traditional patch from a website and detect renames::
4133
4133
4134 hg import -s 80 http://example.com/bugfix.patch
4134 hg import -s 80 http://example.com/bugfix.patch
4135
4135
4136 - import a changeset from an hgweb server::
4136 - import a changeset from an hgweb server::
4137
4137
4138 hg import https://www.mercurial-scm.org/repo/hg/rev/5ca8c111e9aa
4138 hg import https://www.mercurial-scm.org/repo/hg/rev/5ca8c111e9aa
4139
4139
4140 - import all the patches in an Unix-style mbox::
4140 - import all the patches in an Unix-style mbox::
4141
4141
4142 hg import incoming-patches.mbox
4142 hg import incoming-patches.mbox
4143
4143
4144 - import patches from stdin::
4144 - import patches from stdin::
4145
4145
4146 hg import -
4146 hg import -
4147
4147
4148 - attempt to exactly restore an exported changeset (not always
4148 - attempt to exactly restore an exported changeset (not always
4149 possible)::
4149 possible)::
4150
4150
4151 hg import --exact proposed-fix.patch
4151 hg import --exact proposed-fix.patch
4152
4152
4153 - use an external tool to apply a patch which is too fuzzy for
4153 - use an external tool to apply a patch which is too fuzzy for
4154 the default internal tool.
4154 the default internal tool.
4155
4155
4156 hg import --config ui.patch="patch --merge" fuzzy.patch
4156 hg import --config ui.patch="patch --merge" fuzzy.patch
4157
4157
4158 - change the default fuzzing from 2 to a less strict 7
4158 - change the default fuzzing from 2 to a less strict 7
4159
4159
4160 hg import --config ui.fuzz=7 fuzz.patch
4160 hg import --config ui.fuzz=7 fuzz.patch
4161
4161
4162 Returns 0 on success, 1 on partial success (see --partial).
4162 Returns 0 on success, 1 on partial success (see --partial).
4163 """
4163 """
4164
4164
4165 opts = pycompat.byteskwargs(opts)
4165 opts = pycompat.byteskwargs(opts)
4166 if not patch1:
4166 if not patch1:
4167 raise error.Abort(_(b'need at least one patch to import'))
4167 raise error.Abort(_(b'need at least one patch to import'))
4168
4168
4169 patches = (patch1,) + patches
4169 patches = (patch1,) + patches
4170
4170
4171 date = opts.get(b'date')
4171 date = opts.get(b'date')
4172 if date:
4172 if date:
4173 opts[b'date'] = dateutil.parsedate(date)
4173 opts[b'date'] = dateutil.parsedate(date)
4174
4174
4175 exact = opts.get(b'exact')
4175 exact = opts.get(b'exact')
4176 update = not opts.get(b'bypass')
4176 update = not opts.get(b'bypass')
4177 if not update and opts.get(b'no_commit'):
4177 if not update and opts.get(b'no_commit'):
4178 raise error.Abort(_(b'cannot use --no-commit with --bypass'))
4178 raise error.Abort(_(b'cannot use --no-commit with --bypass'))
4179 if opts.get(b'secret') and opts.get(b'no_commit'):
4179 if opts.get(b'secret') and opts.get(b'no_commit'):
4180 raise error.Abort(_(b'cannot use --no-commit with --secret'))
4180 raise error.Abort(_(b'cannot use --no-commit with --secret'))
4181 try:
4181 try:
4182 sim = float(opts.get(b'similarity') or 0)
4182 sim = float(opts.get(b'similarity') or 0)
4183 except ValueError:
4183 except ValueError:
4184 raise error.Abort(_(b'similarity must be a number'))
4184 raise error.Abort(_(b'similarity must be a number'))
4185 if sim < 0 or sim > 100:
4185 if sim < 0 or sim > 100:
4186 raise error.Abort(_(b'similarity must be between 0 and 100'))
4186 raise error.Abort(_(b'similarity must be between 0 and 100'))
4187 if sim and not update:
4187 if sim and not update:
4188 raise error.Abort(_(b'cannot use --similarity with --bypass'))
4188 raise error.Abort(_(b'cannot use --similarity with --bypass'))
4189 if exact:
4189 if exact:
4190 if opts.get(b'edit'):
4190 if opts.get(b'edit'):
4191 raise error.Abort(_(b'cannot use --exact with --edit'))
4191 raise error.Abort(_(b'cannot use --exact with --edit'))
4192 if opts.get(b'prefix'):
4192 if opts.get(b'prefix'):
4193 raise error.Abort(_(b'cannot use --exact with --prefix'))
4193 raise error.Abort(_(b'cannot use --exact with --prefix'))
4194
4194
4195 base = opts[b"base"]
4195 base = opts[b"base"]
4196 msgs = []
4196 msgs = []
4197 ret = 0
4197 ret = 0
4198
4198
4199 with repo.wlock():
4199 with repo.wlock():
4200 if update:
4200 if update:
4201 cmdutil.checkunfinished(repo)
4201 cmdutil.checkunfinished(repo)
4202 if exact or not opts.get(b'force'):
4202 if exact or not opts.get(b'force'):
4203 cmdutil.bailifchanged(repo)
4203 cmdutil.bailifchanged(repo)
4204
4204
4205 if not opts.get(b'no_commit'):
4205 if not opts.get(b'no_commit'):
4206 lock = repo.lock
4206 lock = repo.lock
4207 tr = lambda: repo.transaction(b'import')
4207 tr = lambda: repo.transaction(b'import')
4208 dsguard = util.nullcontextmanager
4208 dsguard = util.nullcontextmanager
4209 else:
4209 else:
4210 lock = util.nullcontextmanager
4210 lock = util.nullcontextmanager
4211 tr = util.nullcontextmanager
4211 tr = util.nullcontextmanager
4212 dsguard = lambda: dirstateguard.dirstateguard(repo, b'import')
4212 dsguard = lambda: dirstateguard.dirstateguard(repo, b'import')
4213 with lock(), tr(), dsguard():
4213 with lock(), tr(), dsguard():
4214 parents = repo[None].parents()
4214 parents = repo[None].parents()
4215 for patchurl in patches:
4215 for patchurl in patches:
4216 if patchurl == b'-':
4216 if patchurl == b'-':
4217 ui.status(_(b'applying patch from stdin\n'))
4217 ui.status(_(b'applying patch from stdin\n'))
4218 patchfile = ui.fin
4218 patchfile = ui.fin
4219 patchurl = b'stdin' # for error message
4219 patchurl = b'stdin' # for error message
4220 else:
4220 else:
4221 patchurl = os.path.join(base, patchurl)
4221 patchurl = os.path.join(base, patchurl)
4222 ui.status(_(b'applying %s\n') % patchurl)
4222 ui.status(_(b'applying %s\n') % patchurl)
4223 patchfile = hg.openpath(ui, patchurl, sendaccept=False)
4223 patchfile = hg.openpath(ui, patchurl, sendaccept=False)
4224
4224
4225 haspatch = False
4225 haspatch = False
4226 for hunk in patch.split(patchfile):
4226 for hunk in patch.split(patchfile):
4227 with patch.extract(ui, hunk) as patchdata:
4227 with patch.extract(ui, hunk) as patchdata:
4228 msg, node, rej = cmdutil.tryimportone(
4228 msg, node, rej = cmdutil.tryimportone(
4229 ui, repo, patchdata, parents, opts, msgs, hg.clean
4229 ui, repo, patchdata, parents, opts, msgs, hg.clean
4230 )
4230 )
4231 if msg:
4231 if msg:
4232 haspatch = True
4232 haspatch = True
4233 ui.note(msg + b'\n')
4233 ui.note(msg + b'\n')
4234 if update or exact:
4234 if update or exact:
4235 parents = repo[None].parents()
4235 parents = repo[None].parents()
4236 else:
4236 else:
4237 parents = [repo[node]]
4237 parents = [repo[node]]
4238 if rej:
4238 if rej:
4239 ui.write_err(_(b"patch applied partially\n"))
4239 ui.write_err(_(b"patch applied partially\n"))
4240 ui.write_err(
4240 ui.write_err(
4241 _(
4241 _(
4242 b"(fix the .rej files and run "
4242 b"(fix the .rej files and run "
4243 b"`hg commit --amend`)\n"
4243 b"`hg commit --amend`)\n"
4244 )
4244 )
4245 )
4245 )
4246 ret = 1
4246 ret = 1
4247 break
4247 break
4248
4248
4249 if not haspatch:
4249 if not haspatch:
4250 raise error.Abort(_(b'%s: no diffs found') % patchurl)
4250 raise error.Abort(_(b'%s: no diffs found') % patchurl)
4251
4251
4252 if msgs:
4252 if msgs:
4253 repo.savecommitmessage(b'\n* * *\n'.join(msgs))
4253 repo.savecommitmessage(b'\n* * *\n'.join(msgs))
4254 return ret
4254 return ret
4255
4255
4256
4256
4257 @command(
4257 @command(
4258 b'incoming|in',
4258 b'incoming|in',
4259 [
4259 [
4260 (
4260 (
4261 b'f',
4261 b'f',
4262 b'force',
4262 b'force',
4263 None,
4263 None,
4264 _(b'run even if remote repository is unrelated'),
4264 _(b'run even if remote repository is unrelated'),
4265 ),
4265 ),
4266 (b'n', b'newest-first', None, _(b'show newest record first')),
4266 (b'n', b'newest-first', None, _(b'show newest record first')),
4267 (b'', b'bundle', b'', _(b'file to store the bundles into'), _(b'FILE')),
4267 (b'', b'bundle', b'', _(b'file to store the bundles into'), _(b'FILE')),
4268 (
4268 (
4269 b'r',
4269 b'r',
4270 b'rev',
4270 b'rev',
4271 [],
4271 [],
4272 _(b'a remote changeset intended to be added'),
4272 _(b'a remote changeset intended to be added'),
4273 _(b'REV'),
4273 _(b'REV'),
4274 ),
4274 ),
4275 (b'B', b'bookmarks', False, _(b"compare bookmarks")),
4275 (b'B', b'bookmarks', False, _(b"compare bookmarks")),
4276 (
4276 (
4277 b'b',
4277 b'b',
4278 b'branch',
4278 b'branch',
4279 [],
4279 [],
4280 _(b'a specific branch you would like to pull'),
4280 _(b'a specific branch you would like to pull'),
4281 _(b'BRANCH'),
4281 _(b'BRANCH'),
4282 ),
4282 ),
4283 ]
4283 ]
4284 + logopts
4284 + logopts
4285 + remoteopts
4285 + remoteopts
4286 + subrepoopts,
4286 + subrepoopts,
4287 _(b'[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'),
4287 _(b'[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'),
4288 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4288 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4289 )
4289 )
4290 def incoming(ui, repo, source=b"default", **opts):
4290 def incoming(ui, repo, source=b"default", **opts):
4291 """show new changesets found in source
4291 """show new changesets found in source
4292
4292
4293 Show new changesets found in the specified path/URL or the default
4293 Show new changesets found in the specified path/URL or the default
4294 pull location. These are the changesets that would have been pulled
4294 pull location. These are the changesets that would have been pulled
4295 by :hg:`pull` at the time you issued this command.
4295 by :hg:`pull` at the time you issued this command.
4296
4296
4297 See pull for valid source format details.
4297 See pull for valid source format details.
4298
4298
4299 .. container:: verbose
4299 .. container:: verbose
4300
4300
4301 With -B/--bookmarks, the result of bookmark comparison between
4301 With -B/--bookmarks, the result of bookmark comparison between
4302 local and remote repositories is displayed. With -v/--verbose,
4302 local and remote repositories is displayed. With -v/--verbose,
4303 status is also displayed for each bookmark like below::
4303 status is also displayed for each bookmark like below::
4304
4304
4305 BM1 01234567890a added
4305 BM1 01234567890a added
4306 BM2 1234567890ab advanced
4306 BM2 1234567890ab advanced
4307 BM3 234567890abc diverged
4307 BM3 234567890abc diverged
4308 BM4 34567890abcd changed
4308 BM4 34567890abcd changed
4309
4309
4310 The action taken locally when pulling depends on the
4310 The action taken locally when pulling depends on the
4311 status of each bookmark:
4311 status of each bookmark:
4312
4312
4313 :``added``: pull will create it
4313 :``added``: pull will create it
4314 :``advanced``: pull will update it
4314 :``advanced``: pull will update it
4315 :``diverged``: pull will create a divergent bookmark
4315 :``diverged``: pull will create a divergent bookmark
4316 :``changed``: result depends on remote changesets
4316 :``changed``: result depends on remote changesets
4317
4317
4318 From the point of view of pulling behavior, bookmark
4318 From the point of view of pulling behavior, bookmark
4319 existing only in the remote repository are treated as ``added``,
4319 existing only in the remote repository are treated as ``added``,
4320 even if it is in fact locally deleted.
4320 even if it is in fact locally deleted.
4321
4321
4322 .. container:: verbose
4322 .. container:: verbose
4323
4323
4324 For remote repository, using --bundle avoids downloading the
4324 For remote repository, using --bundle avoids downloading the
4325 changesets twice if the incoming is followed by a pull.
4325 changesets twice if the incoming is followed by a pull.
4326
4326
4327 Examples:
4327 Examples:
4328
4328
4329 - show incoming changes with patches and full description::
4329 - show incoming changes with patches and full description::
4330
4330
4331 hg incoming -vp
4331 hg incoming -vp
4332
4332
4333 - show incoming changes excluding merges, store a bundle::
4333 - show incoming changes excluding merges, store a bundle::
4334
4334
4335 hg in -vpM --bundle incoming.hg
4335 hg in -vpM --bundle incoming.hg
4336 hg pull incoming.hg
4336 hg pull incoming.hg
4337
4337
4338 - briefly list changes inside a bundle::
4338 - briefly list changes inside a bundle::
4339
4339
4340 hg in changes.hg -T "{desc|firstline}\\n"
4340 hg in changes.hg -T "{desc|firstline}\\n"
4341
4341
4342 Returns 0 if there are incoming changes, 1 otherwise.
4342 Returns 0 if there are incoming changes, 1 otherwise.
4343 """
4343 """
4344 opts = pycompat.byteskwargs(opts)
4344 opts = pycompat.byteskwargs(opts)
4345 if opts.get(b'graph'):
4345 if opts.get(b'graph'):
4346 logcmdutil.checkunsupportedgraphflags([], opts)
4346 logcmdutil.checkunsupportedgraphflags([], opts)
4347
4347
4348 def display(other, chlist, displayer):
4348 def display(other, chlist, displayer):
4349 revdag = logcmdutil.graphrevs(other, chlist, opts)
4349 revdag = logcmdutil.graphrevs(other, chlist, opts)
4350 logcmdutil.displaygraph(
4350 logcmdutil.displaygraph(
4351 ui, repo, revdag, displayer, graphmod.asciiedges
4351 ui, repo, revdag, displayer, graphmod.asciiedges
4352 )
4352 )
4353
4353
4354 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
4354 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
4355 return 0
4355 return 0
4356
4356
4357 if opts.get(b'bundle') and opts.get(b'subrepos'):
4357 if opts.get(b'bundle') and opts.get(b'subrepos'):
4358 raise error.Abort(_(b'cannot combine --bundle and --subrepos'))
4358 raise error.Abort(_(b'cannot combine --bundle and --subrepos'))
4359
4359
4360 if opts.get(b'bookmarks'):
4360 if opts.get(b'bookmarks'):
4361 source, branches = hg.parseurl(
4361 source, branches = hg.parseurl(
4362 ui.expandpath(source), opts.get(b'branch')
4362 ui.expandpath(source), opts.get(b'branch')
4363 )
4363 )
4364 other = hg.peer(repo, opts, source)
4364 other = hg.peer(repo, opts, source)
4365 if b'bookmarks' not in other.listkeys(b'namespaces'):
4365 if b'bookmarks' not in other.listkeys(b'namespaces'):
4366 ui.warn(_(b"remote doesn't support bookmarks\n"))
4366 ui.warn(_(b"remote doesn't support bookmarks\n"))
4367 return 0
4367 return 0
4368 ui.pager(b'incoming')
4368 ui.pager(b'incoming')
4369 ui.status(_(b'comparing with %s\n') % util.hidepassword(source))
4369 ui.status(_(b'comparing with %s\n') % util.hidepassword(source))
4370 return bookmarks.incoming(ui, repo, other)
4370 return bookmarks.incoming(ui, repo, other)
4371
4371
4372 repo._subtoppath = ui.expandpath(source)
4372 repo._subtoppath = ui.expandpath(source)
4373 try:
4373 try:
4374 return hg.incoming(ui, repo, source, opts)
4374 return hg.incoming(ui, repo, source, opts)
4375 finally:
4375 finally:
4376 del repo._subtoppath
4376 del repo._subtoppath
4377
4377
4378
4378
4379 @command(
4379 @command(
4380 b'init',
4380 b'init',
4381 remoteopts,
4381 remoteopts,
4382 _(b'[-e CMD] [--remotecmd CMD] [DEST]'),
4382 _(b'[-e CMD] [--remotecmd CMD] [DEST]'),
4383 helpcategory=command.CATEGORY_REPO_CREATION,
4383 helpcategory=command.CATEGORY_REPO_CREATION,
4384 helpbasic=True,
4384 helpbasic=True,
4385 norepo=True,
4385 norepo=True,
4386 )
4386 )
4387 def init(ui, dest=b".", **opts):
4387 def init(ui, dest=b".", **opts):
4388 """create a new repository in the given directory
4388 """create a new repository in the given directory
4389
4389
4390 Initialize a new repository in the given directory. If the given
4390 Initialize a new repository in the given directory. If the given
4391 directory does not exist, it will be created.
4391 directory does not exist, it will be created.
4392
4392
4393 If no directory is given, the current directory is used.
4393 If no directory is given, the current directory is used.
4394
4394
4395 It is possible to specify an ``ssh://`` URL as the destination.
4395 It is possible to specify an ``ssh://`` URL as the destination.
4396 See :hg:`help urls` for more information.
4396 See :hg:`help urls` for more information.
4397
4397
4398 Returns 0 on success.
4398 Returns 0 on success.
4399 """
4399 """
4400 opts = pycompat.byteskwargs(opts)
4400 opts = pycompat.byteskwargs(opts)
4401 hg.peer(ui, opts, ui.expandpath(dest), create=True)
4401 hg.peer(ui, opts, ui.expandpath(dest), create=True)
4402
4402
4403
4403
4404 @command(
4404 @command(
4405 b'locate',
4405 b'locate',
4406 [
4406 [
4407 (
4407 (
4408 b'r',
4408 b'r',
4409 b'rev',
4409 b'rev',
4410 b'',
4410 b'',
4411 _(b'search the repository as it is in REV'),
4411 _(b'search the repository as it is in REV'),
4412 _(b'REV'),
4412 _(b'REV'),
4413 ),
4413 ),
4414 (
4414 (
4415 b'0',
4415 b'0',
4416 b'print0',
4416 b'print0',
4417 None,
4417 None,
4418 _(b'end filenames with NUL, for use with xargs'),
4418 _(b'end filenames with NUL, for use with xargs'),
4419 ),
4419 ),
4420 (
4420 (
4421 b'f',
4421 b'f',
4422 b'fullpath',
4422 b'fullpath',
4423 None,
4423 None,
4424 _(b'print complete paths from the filesystem root'),
4424 _(b'print complete paths from the filesystem root'),
4425 ),
4425 ),
4426 ]
4426 ]
4427 + walkopts,
4427 + walkopts,
4428 _(b'[OPTION]... [PATTERN]...'),
4428 _(b'[OPTION]... [PATTERN]...'),
4429 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
4429 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
4430 )
4430 )
4431 def locate(ui, repo, *pats, **opts):
4431 def locate(ui, repo, *pats, **opts):
4432 """locate files matching specific patterns (DEPRECATED)
4432 """locate files matching specific patterns (DEPRECATED)
4433
4433
4434 Print files under Mercurial control in the working directory whose
4434 Print files under Mercurial control in the working directory whose
4435 names match the given patterns.
4435 names match the given patterns.
4436
4436
4437 By default, this command searches all directories in the working
4437 By default, this command searches all directories in the working
4438 directory. To search just the current directory and its
4438 directory. To search just the current directory and its
4439 subdirectories, use "--include .".
4439 subdirectories, use "--include .".
4440
4440
4441 If no patterns are given to match, this command prints the names
4441 If no patterns are given to match, this command prints the names
4442 of all files under Mercurial control in the working directory.
4442 of all files under Mercurial control in the working directory.
4443
4443
4444 If you want to feed the output of this command into the "xargs"
4444 If you want to feed the output of this command into the "xargs"
4445 command, use the -0 option to both this command and "xargs". This
4445 command, use the -0 option to both this command and "xargs". This
4446 will avoid the problem of "xargs" treating single filenames that
4446 will avoid the problem of "xargs" treating single filenames that
4447 contain whitespace as multiple filenames.
4447 contain whitespace as multiple filenames.
4448
4448
4449 See :hg:`help files` for a more versatile command.
4449 See :hg:`help files` for a more versatile command.
4450
4450
4451 Returns 0 if a match is found, 1 otherwise.
4451 Returns 0 if a match is found, 1 otherwise.
4452 """
4452 """
4453 opts = pycompat.byteskwargs(opts)
4453 opts = pycompat.byteskwargs(opts)
4454 if opts.get(b'print0'):
4454 if opts.get(b'print0'):
4455 end = b'\0'
4455 end = b'\0'
4456 else:
4456 else:
4457 end = b'\n'
4457 end = b'\n'
4458 ctx = scmutil.revsingle(repo, opts.get(b'rev'), None)
4458 ctx = scmutil.revsingle(repo, opts.get(b'rev'), None)
4459
4459
4460 ret = 1
4460 ret = 1
4461 m = scmutil.match(
4461 m = scmutil.match(
4462 ctx, pats, opts, default=b'relglob', badfn=lambda x, y: False
4462 ctx, pats, opts, default=b'relglob', badfn=lambda x, y: False
4463 )
4463 )
4464
4464
4465 ui.pager(b'locate')
4465 ui.pager(b'locate')
4466 if ctx.rev() is None:
4466 if ctx.rev() is None:
4467 # When run on the working copy, "locate" includes removed files, so
4467 # When run on the working copy, "locate" includes removed files, so
4468 # we get the list of files from the dirstate.
4468 # we get the list of files from the dirstate.
4469 filesgen = sorted(repo.dirstate.matches(m))
4469 filesgen = sorted(repo.dirstate.matches(m))
4470 else:
4470 else:
4471 filesgen = ctx.matches(m)
4471 filesgen = ctx.matches(m)
4472 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=bool(pats))
4472 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=bool(pats))
4473 for abs in filesgen:
4473 for abs in filesgen:
4474 if opts.get(b'fullpath'):
4474 if opts.get(b'fullpath'):
4475 ui.write(repo.wjoin(abs), end)
4475 ui.write(repo.wjoin(abs), end)
4476 else:
4476 else:
4477 ui.write(uipathfn(abs), end)
4477 ui.write(uipathfn(abs), end)
4478 ret = 0
4478 ret = 0
4479
4479
4480 return ret
4480 return ret
4481
4481
4482
4482
4483 @command(
4483 @command(
4484 b'log|history',
4484 b'log|history',
4485 [
4485 [
4486 (
4486 (
4487 b'f',
4487 b'f',
4488 b'follow',
4488 b'follow',
4489 None,
4489 None,
4490 _(
4490 _(
4491 b'follow changeset history, or file history across copies and renames'
4491 b'follow changeset history, or file history across copies and renames'
4492 ),
4492 ),
4493 ),
4493 ),
4494 (
4494 (
4495 b'',
4495 b'',
4496 b'follow-first',
4496 b'follow-first',
4497 None,
4497 None,
4498 _(b'only follow the first parent of merge changesets (DEPRECATED)'),
4498 _(b'only follow the first parent of merge changesets (DEPRECATED)'),
4499 ),
4499 ),
4500 (
4500 (
4501 b'd',
4501 b'd',
4502 b'date',
4502 b'date',
4503 b'',
4503 b'',
4504 _(b'show revisions matching date spec'),
4504 _(b'show revisions matching date spec'),
4505 _(b'DATE'),
4505 _(b'DATE'),
4506 ),
4506 ),
4507 (b'C', b'copies', None, _(b'show copied files')),
4507 (b'C', b'copies', None, _(b'show copied files')),
4508 (
4508 (
4509 b'k',
4509 b'k',
4510 b'keyword',
4510 b'keyword',
4511 [],
4511 [],
4512 _(b'do case-insensitive search for a given text'),
4512 _(b'do case-insensitive search for a given text'),
4513 _(b'TEXT'),
4513 _(b'TEXT'),
4514 ),
4514 ),
4515 (
4515 (
4516 b'r',
4516 b'r',
4517 b'rev',
4517 b'rev',
4518 [],
4518 [],
4519 _(b'show the specified revision or revset'),
4519 _(b'show the specified revision or revset'),
4520 _(b'REV'),
4520 _(b'REV'),
4521 ),
4521 ),
4522 (
4522 (
4523 b'L',
4523 b'L',
4524 b'line-range',
4524 b'line-range',
4525 [],
4525 [],
4526 _(b'follow line range of specified file (EXPERIMENTAL)'),
4526 _(b'follow line range of specified file (EXPERIMENTAL)'),
4527 _(b'FILE,RANGE'),
4527 _(b'FILE,RANGE'),
4528 ),
4528 ),
4529 (
4529 (
4530 b'',
4530 b'',
4531 b'removed',
4531 b'removed',
4532 None,
4532 None,
4533 _(b'include revisions where files were removed'),
4533 _(b'include revisions where files were removed'),
4534 ),
4534 ),
4535 (
4535 (
4536 b'm',
4536 b'm',
4537 b'only-merges',
4537 b'only-merges',
4538 None,
4538 None,
4539 _(b'show only merges (DEPRECATED) (use -r "merge()" instead)'),
4539 _(b'show only merges (DEPRECATED) (use -r "merge()" instead)'),
4540 ),
4540 ),
4541 (b'u', b'user', [], _(b'revisions committed by user'), _(b'USER')),
4541 (b'u', b'user', [], _(b'revisions committed by user'), _(b'USER')),
4542 (
4542 (
4543 b'',
4543 b'',
4544 b'only-branch',
4544 b'only-branch',
4545 [],
4545 [],
4546 _(
4546 _(
4547 b'show only changesets within the given named branch (DEPRECATED)'
4547 b'show only changesets within the given named branch (DEPRECATED)'
4548 ),
4548 ),
4549 _(b'BRANCH'),
4549 _(b'BRANCH'),
4550 ),
4550 ),
4551 (
4551 (
4552 b'b',
4552 b'b',
4553 b'branch',
4553 b'branch',
4554 [],
4554 [],
4555 _(b'show changesets within the given named branch'),
4555 _(b'show changesets within the given named branch'),
4556 _(b'BRANCH'),
4556 _(b'BRANCH'),
4557 ),
4557 ),
4558 (
4558 (
4559 b'P',
4559 b'P',
4560 b'prune',
4560 b'prune',
4561 [],
4561 [],
4562 _(b'do not display revision or any of its ancestors'),
4562 _(b'do not display revision or any of its ancestors'),
4563 _(b'REV'),
4563 _(b'REV'),
4564 ),
4564 ),
4565 ]
4565 ]
4566 + logopts
4566 + logopts
4567 + walkopts,
4567 + walkopts,
4568 _(b'[OPTION]... [FILE]'),
4568 _(b'[OPTION]... [FILE]'),
4569 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
4569 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
4570 helpbasic=True,
4570 helpbasic=True,
4571 inferrepo=True,
4571 inferrepo=True,
4572 intents={INTENT_READONLY},
4572 intents={INTENT_READONLY},
4573 )
4573 )
4574 def log(ui, repo, *pats, **opts):
4574 def log(ui, repo, *pats, **opts):
4575 """show revision history of entire repository or files
4575 """show revision history of entire repository or files
4576
4576
4577 Print the revision history of the specified files or the entire
4577 Print the revision history of the specified files or the entire
4578 project.
4578 project.
4579
4579
4580 If no revision range is specified, the default is ``tip:0`` unless
4580 If no revision range is specified, the default is ``tip:0`` unless
4581 --follow is set, in which case the working directory parent is
4581 --follow is set, in which case the working directory parent is
4582 used as the starting revision.
4582 used as the starting revision.
4583
4583
4584 File history is shown without following rename or copy history of
4584 File history is shown without following rename or copy history of
4585 files. Use -f/--follow with a filename to follow history across
4585 files. Use -f/--follow with a filename to follow history across
4586 renames and copies. --follow without a filename will only show
4586 renames and copies. --follow without a filename will only show
4587 ancestors of the starting revision.
4587 ancestors of the starting revision.
4588
4588
4589 By default this command prints revision number and changeset id,
4589 By default this command prints revision number and changeset id,
4590 tags, non-trivial parents, user, date and time, and a summary for
4590 tags, non-trivial parents, user, date and time, and a summary for
4591 each commit. When the -v/--verbose switch is used, the list of
4591 each commit. When the -v/--verbose switch is used, the list of
4592 changed files and full commit message are shown.
4592 changed files and full commit message are shown.
4593
4593
4594 With --graph the revisions are shown as an ASCII art DAG with the most
4594 With --graph the revisions are shown as an ASCII art DAG with the most
4595 recent changeset at the top.
4595 recent changeset at the top.
4596 'o' is a changeset, '@' is a working directory parent, '_' closes a branch,
4596 'o' is a changeset, '@' is a working directory parent, '_' closes a branch,
4597 'x' is obsolete, '*' is unstable, and '+' represents a fork where the
4597 'x' is obsolete, '*' is unstable, and '+' represents a fork where the
4598 changeset from the lines below is a parent of the 'o' merge on the same
4598 changeset from the lines below is a parent of the 'o' merge on the same
4599 line.
4599 line.
4600 Paths in the DAG are represented with '|', '/' and so forth. ':' in place
4600 Paths in the DAG are represented with '|', '/' and so forth. ':' in place
4601 of a '|' indicates one or more revisions in a path are omitted.
4601 of a '|' indicates one or more revisions in a path are omitted.
4602
4602
4603 .. container:: verbose
4603 .. container:: verbose
4604
4604
4605 Use -L/--line-range FILE,M:N options to follow the history of lines
4605 Use -L/--line-range FILE,M:N options to follow the history of lines
4606 from M to N in FILE. With -p/--patch only diff hunks affecting
4606 from M to N in FILE. With -p/--patch only diff hunks affecting
4607 specified line range will be shown. This option requires --follow;
4607 specified line range will be shown. This option requires --follow;
4608 it can be specified multiple times. Currently, this option is not
4608 it can be specified multiple times. Currently, this option is not
4609 compatible with --graph. This option is experimental.
4609 compatible with --graph. This option is experimental.
4610
4610
4611 .. note::
4611 .. note::
4612
4612
4613 :hg:`log --patch` may generate unexpected diff output for merge
4613 :hg:`log --patch` may generate unexpected diff output for merge
4614 changesets, as it will only compare the merge changeset against
4614 changesets, as it will only compare the merge changeset against
4615 its first parent. Also, only files different from BOTH parents
4615 its first parent. Also, only files different from BOTH parents
4616 will appear in files:.
4616 will appear in files:.
4617
4617
4618 .. note::
4618 .. note::
4619
4619
4620 For performance reasons, :hg:`log FILE` may omit duplicate changes
4620 For performance reasons, :hg:`log FILE` may omit duplicate changes
4621 made on branches and will not show removals or mode changes. To
4621 made on branches and will not show removals or mode changes. To
4622 see all such changes, use the --removed switch.
4622 see all such changes, use the --removed switch.
4623
4623
4624 .. container:: verbose
4624 .. container:: verbose
4625
4625
4626 .. note::
4626 .. note::
4627
4627
4628 The history resulting from -L/--line-range options depends on diff
4628 The history resulting from -L/--line-range options depends on diff
4629 options; for instance if white-spaces are ignored, respective changes
4629 options; for instance if white-spaces are ignored, respective changes
4630 with only white-spaces in specified line range will not be listed.
4630 with only white-spaces in specified line range will not be listed.
4631
4631
4632 .. container:: verbose
4632 .. container:: verbose
4633
4633
4634 Some examples:
4634 Some examples:
4635
4635
4636 - changesets with full descriptions and file lists::
4636 - changesets with full descriptions and file lists::
4637
4637
4638 hg log -v
4638 hg log -v
4639
4639
4640 - changesets ancestral to the working directory::
4640 - changesets ancestral to the working directory::
4641
4641
4642 hg log -f
4642 hg log -f
4643
4643
4644 - last 10 commits on the current branch::
4644 - last 10 commits on the current branch::
4645
4645
4646 hg log -l 10 -b .
4646 hg log -l 10 -b .
4647
4647
4648 - changesets showing all modifications of a file, including removals::
4648 - changesets showing all modifications of a file, including removals::
4649
4649
4650 hg log --removed file.c
4650 hg log --removed file.c
4651
4651
4652 - all changesets that touch a directory, with diffs, excluding merges::
4652 - all changesets that touch a directory, with diffs, excluding merges::
4653
4653
4654 hg log -Mp lib/
4654 hg log -Mp lib/
4655
4655
4656 - all revision numbers that match a keyword::
4656 - all revision numbers that match a keyword::
4657
4657
4658 hg log -k bug --template "{rev}\\n"
4658 hg log -k bug --template "{rev}\\n"
4659
4659
4660 - the full hash identifier of the working directory parent::
4660 - the full hash identifier of the working directory parent::
4661
4661
4662 hg log -r . --template "{node}\\n"
4662 hg log -r . --template "{node}\\n"
4663
4663
4664 - list available log templates::
4664 - list available log templates::
4665
4665
4666 hg log -T list
4666 hg log -T list
4667
4667
4668 - check if a given changeset is included in a tagged release::
4668 - check if a given changeset is included in a tagged release::
4669
4669
4670 hg log -r "a21ccf and ancestor(1.9)"
4670 hg log -r "a21ccf and ancestor(1.9)"
4671
4671
4672 - find all changesets by some user in a date range::
4672 - find all changesets by some user in a date range::
4673
4673
4674 hg log -k alice -d "may 2008 to jul 2008"
4674 hg log -k alice -d "may 2008 to jul 2008"
4675
4675
4676 - summary of all changesets after the last tag::
4676 - summary of all changesets after the last tag::
4677
4677
4678 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
4678 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
4679
4679
4680 - changesets touching lines 13 to 23 for file.c::
4680 - changesets touching lines 13 to 23 for file.c::
4681
4681
4682 hg log -L file.c,13:23
4682 hg log -L file.c,13:23
4683
4683
4684 - changesets touching lines 13 to 23 for file.c and lines 2 to 6 of
4684 - changesets touching lines 13 to 23 for file.c and lines 2 to 6 of
4685 main.c with patch::
4685 main.c with patch::
4686
4686
4687 hg log -L file.c,13:23 -L main.c,2:6 -p
4687 hg log -L file.c,13:23 -L main.c,2:6 -p
4688
4688
4689 See :hg:`help dates` for a list of formats valid for -d/--date.
4689 See :hg:`help dates` for a list of formats valid for -d/--date.
4690
4690
4691 See :hg:`help revisions` for more about specifying and ordering
4691 See :hg:`help revisions` for more about specifying and ordering
4692 revisions.
4692 revisions.
4693
4693
4694 See :hg:`help templates` for more about pre-packaged styles and
4694 See :hg:`help templates` for more about pre-packaged styles and
4695 specifying custom templates. The default template used by the log
4695 specifying custom templates. The default template used by the log
4696 command can be customized via the ``ui.logtemplate`` configuration
4696 command can be customized via the ``ui.logtemplate`` configuration
4697 setting.
4697 setting.
4698
4698
4699 Returns 0 on success.
4699 Returns 0 on success.
4700
4700
4701 """
4701 """
4702 opts = pycompat.byteskwargs(opts)
4702 opts = pycompat.byteskwargs(opts)
4703 linerange = opts.get(b'line_range')
4703 linerange = opts.get(b'line_range')
4704
4704
4705 if linerange and not opts.get(b'follow'):
4705 if linerange and not opts.get(b'follow'):
4706 raise error.Abort(_(b'--line-range requires --follow'))
4706 raise error.Abort(_(b'--line-range requires --follow'))
4707
4707
4708 if linerange and pats:
4708 if linerange and pats:
4709 # TODO: take pats as patterns with no line-range filter
4709 # TODO: take pats as patterns with no line-range filter
4710 raise error.Abort(
4710 raise error.Abort(
4711 _(b'FILE arguments are not compatible with --line-range option')
4711 _(b'FILE arguments are not compatible with --line-range option')
4712 )
4712 )
4713
4713
4714 repo = scmutil.unhidehashlikerevs(repo, opts.get(b'rev'), b'nowarn')
4714 repo = scmutil.unhidehashlikerevs(repo, opts.get(b'rev'), b'nowarn')
4715 revs, differ = logcmdutil.getrevs(repo, pats, opts)
4715 revs, differ = logcmdutil.getrevs(repo, pats, opts)
4716 if linerange:
4716 if linerange:
4717 # TODO: should follow file history from logcmdutil._initialrevs(),
4717 # TODO: should follow file history from logcmdutil._initialrevs(),
4718 # then filter the result by logcmdutil._makerevset() and --limit
4718 # then filter the result by logcmdutil._makerevset() and --limit
4719 revs, differ = logcmdutil.getlinerangerevs(repo, revs, opts)
4719 revs, differ = logcmdutil.getlinerangerevs(repo, revs, opts)
4720
4720
4721 getcopies = None
4721 getcopies = None
4722 if opts.get(b'copies'):
4722 if opts.get(b'copies'):
4723 endrev = None
4723 endrev = None
4724 if revs:
4724 if revs:
4725 endrev = revs.max() + 1
4725 endrev = revs.max() + 1
4726 getcopies = scmutil.getcopiesfn(repo, endrev=endrev)
4726 getcopies = scmutil.getcopiesfn(repo, endrev=endrev)
4727
4727
4728 ui.pager(b'log')
4728 ui.pager(b'log')
4729 displayer = logcmdutil.changesetdisplayer(
4729 displayer = logcmdutil.changesetdisplayer(
4730 ui, repo, opts, differ, buffered=True
4730 ui, repo, opts, differ, buffered=True
4731 )
4731 )
4732 if opts.get(b'graph'):
4732 if opts.get(b'graph'):
4733 displayfn = logcmdutil.displaygraphrevs
4733 displayfn = logcmdutil.displaygraphrevs
4734 else:
4734 else:
4735 displayfn = logcmdutil.displayrevs
4735 displayfn = logcmdutil.displayrevs
4736 displayfn(ui, repo, revs, displayer, getcopies)
4736 displayfn(ui, repo, revs, displayer, getcopies)
4737
4737
4738
4738
4739 @command(
4739 @command(
4740 b'manifest',
4740 b'manifest',
4741 [
4741 [
4742 (b'r', b'rev', b'', _(b'revision to display'), _(b'REV')),
4742 (b'r', b'rev', b'', _(b'revision to display'), _(b'REV')),
4743 (b'', b'all', False, _(b"list files from all revisions")),
4743 (b'', b'all', False, _(b"list files from all revisions")),
4744 ]
4744 ]
4745 + formatteropts,
4745 + formatteropts,
4746 _(b'[-r REV]'),
4746 _(b'[-r REV]'),
4747 helpcategory=command.CATEGORY_MAINTENANCE,
4747 helpcategory=command.CATEGORY_MAINTENANCE,
4748 intents={INTENT_READONLY},
4748 intents={INTENT_READONLY},
4749 )
4749 )
4750 def manifest(ui, repo, node=None, rev=None, **opts):
4750 def manifest(ui, repo, node=None, rev=None, **opts):
4751 """output the current or given revision of the project manifest
4751 """output the current or given revision of the project manifest
4752
4752
4753 Print a list of version controlled files for the given revision.
4753 Print a list of version controlled files for the given revision.
4754 If no revision is given, the first parent of the working directory
4754 If no revision is given, the first parent of the working directory
4755 is used, or the null revision if no revision is checked out.
4755 is used, or the null revision if no revision is checked out.
4756
4756
4757 With -v, print file permissions, symlink and executable bits.
4757 With -v, print file permissions, symlink and executable bits.
4758 With --debug, print file revision hashes.
4758 With --debug, print file revision hashes.
4759
4759
4760 If option --all is specified, the list of all files from all revisions
4760 If option --all is specified, the list of all files from all revisions
4761 is printed. This includes deleted and renamed files.
4761 is printed. This includes deleted and renamed files.
4762
4762
4763 Returns 0 on success.
4763 Returns 0 on success.
4764 """
4764 """
4765 opts = pycompat.byteskwargs(opts)
4765 opts = pycompat.byteskwargs(opts)
4766 fm = ui.formatter(b'manifest', opts)
4766 fm = ui.formatter(b'manifest', opts)
4767
4767
4768 if opts.get(b'all'):
4768 if opts.get(b'all'):
4769 if rev or node:
4769 if rev or node:
4770 raise error.Abort(_(b"can't specify a revision with --all"))
4770 raise error.Abort(_(b"can't specify a revision with --all"))
4771
4771
4772 res = set()
4772 res = set()
4773 for rev in repo:
4773 for rev in repo:
4774 ctx = repo[rev]
4774 ctx = repo[rev]
4775 res |= set(ctx.files())
4775 res |= set(ctx.files())
4776
4776
4777 ui.pager(b'manifest')
4777 ui.pager(b'manifest')
4778 for f in sorted(res):
4778 for f in sorted(res):
4779 fm.startitem()
4779 fm.startitem()
4780 fm.write(b"path", b'%s\n', f)
4780 fm.write(b"path", b'%s\n', f)
4781 fm.end()
4781 fm.end()
4782 return
4782 return
4783
4783
4784 if rev and node:
4784 if rev and node:
4785 raise error.Abort(_(b"please specify just one revision"))
4785 raise error.Abort(_(b"please specify just one revision"))
4786
4786
4787 if not node:
4787 if not node:
4788 node = rev
4788 node = rev
4789
4789
4790 char = {b'l': b'@', b'x': b'*', b'': b'', b't': b'd'}
4790 char = {b'l': b'@', b'x': b'*', b'': b'', b't': b'd'}
4791 mode = {b'l': b'644', b'x': b'755', b'': b'644', b't': b'755'}
4791 mode = {b'l': b'644', b'x': b'755', b'': b'644', b't': b'755'}
4792 if node:
4792 if node:
4793 repo = scmutil.unhidehashlikerevs(repo, [node], b'nowarn')
4793 repo = scmutil.unhidehashlikerevs(repo, [node], b'nowarn')
4794 ctx = scmutil.revsingle(repo, node)
4794 ctx = scmutil.revsingle(repo, node)
4795 mf = ctx.manifest()
4795 mf = ctx.manifest()
4796 ui.pager(b'manifest')
4796 ui.pager(b'manifest')
4797 for f in ctx:
4797 for f in ctx:
4798 fm.startitem()
4798 fm.startitem()
4799 fm.context(ctx=ctx)
4799 fm.context(ctx=ctx)
4800 fl = ctx[f].flags()
4800 fl = ctx[f].flags()
4801 fm.condwrite(ui.debugflag, b'hash', b'%s ', hex(mf[f]))
4801 fm.condwrite(ui.debugflag, b'hash', b'%s ', hex(mf[f]))
4802 fm.condwrite(ui.verbose, b'mode type', b'%s %1s ', mode[fl], char[fl])
4802 fm.condwrite(ui.verbose, b'mode type', b'%s %1s ', mode[fl], char[fl])
4803 fm.write(b'path', b'%s\n', f)
4803 fm.write(b'path', b'%s\n', f)
4804 fm.end()
4804 fm.end()
4805
4805
4806
4806
4807 @command(
4807 @command(
4808 b'merge',
4808 b'merge',
4809 [
4809 [
4810 (
4810 (
4811 b'f',
4811 b'f',
4812 b'force',
4812 b'force',
4813 None,
4813 None,
4814 _(b'force a merge including outstanding changes (DEPRECATED)'),
4814 _(b'force a merge including outstanding changes (DEPRECATED)'),
4815 ),
4815 ),
4816 (b'r', b'rev', b'', _(b'revision to merge'), _(b'REV')),
4816 (b'r', b'rev', b'', _(b'revision to merge'), _(b'REV')),
4817 (
4817 (
4818 b'P',
4818 b'P',
4819 b'preview',
4819 b'preview',
4820 None,
4820 None,
4821 _(b'review revisions to merge (no merge is performed)'),
4821 _(b'review revisions to merge (no merge is performed)'),
4822 ),
4822 ),
4823 (b'', b'abort', None, _(b'abort the ongoing merge')),
4823 (b'', b'abort', None, _(b'abort the ongoing merge')),
4824 ]
4824 ]
4825 + mergetoolopts,
4825 + mergetoolopts,
4826 _(b'[-P] [[-r] REV]'),
4826 _(b'[-P] [[-r] REV]'),
4827 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
4827 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
4828 helpbasic=True,
4828 helpbasic=True,
4829 )
4829 )
4830 def merge(ui, repo, node=None, **opts):
4830 def merge(ui, repo, node=None, **opts):
4831 """merge another revision into working directory
4831 """merge another revision into working directory
4832
4832
4833 The current working directory is updated with all changes made in
4833 The current working directory is updated with all changes made in
4834 the requested revision since the last common predecessor revision.
4834 the requested revision since the last common predecessor revision.
4835
4835
4836 Files that changed between either parent are marked as changed for
4836 Files that changed between either parent are marked as changed for
4837 the next commit and a commit must be performed before any further
4837 the next commit and a commit must be performed before any further
4838 updates to the repository are allowed. The next commit will have
4838 updates to the repository are allowed. The next commit will have
4839 two parents.
4839 two parents.
4840
4840
4841 ``--tool`` can be used to specify the merge tool used for file
4841 ``--tool`` can be used to specify the merge tool used for file
4842 merges. It overrides the HGMERGE environment variable and your
4842 merges. It overrides the HGMERGE environment variable and your
4843 configuration files. See :hg:`help merge-tools` for options.
4843 configuration files. See :hg:`help merge-tools` for options.
4844
4844
4845 If no revision is specified, the working directory's parent is a
4845 If no revision is specified, the working directory's parent is a
4846 head revision, and the current branch contains exactly one other
4846 head revision, and the current branch contains exactly one other
4847 head, the other head is merged with by default. Otherwise, an
4847 head, the other head is merged with by default. Otherwise, an
4848 explicit revision with which to merge must be provided.
4848 explicit revision with which to merge must be provided.
4849
4849
4850 See :hg:`help resolve` for information on handling file conflicts.
4850 See :hg:`help resolve` for information on handling file conflicts.
4851
4851
4852 To undo an uncommitted merge, use :hg:`merge --abort` which
4852 To undo an uncommitted merge, use :hg:`merge --abort` which
4853 will check out a clean copy of the original merge parent, losing
4853 will check out a clean copy of the original merge parent, losing
4854 all changes.
4854 all changes.
4855
4855
4856 Returns 0 on success, 1 if there are unresolved files.
4856 Returns 0 on success, 1 if there are unresolved files.
4857 """
4857 """
4858
4858
4859 opts = pycompat.byteskwargs(opts)
4859 opts = pycompat.byteskwargs(opts)
4860 abort = opts.get(b'abort')
4860 abort = opts.get(b'abort')
4861 if abort and repo.dirstate.p2() == nullid:
4861 if abort and repo.dirstate.p2() == nullid:
4862 cmdutil.wrongtooltocontinue(repo, _(b'merge'))
4862 cmdutil.wrongtooltocontinue(repo, _(b'merge'))
4863 cmdutil.check_incompatible_arguments(opts, b'abort', [b'rev', b'preview'])
4863 cmdutil.check_incompatible_arguments(opts, b'abort', [b'rev', b'preview'])
4864 if abort:
4864 if abort:
4865 state = cmdutil.getunfinishedstate(repo)
4865 state = cmdutil.getunfinishedstate(repo)
4866 if state and state._opname != b'merge':
4866 if state and state._opname != b'merge':
4867 raise error.Abort(
4867 raise error.Abort(
4868 _(b'cannot abort merge with %s in progress') % (state._opname),
4868 _(b'cannot abort merge with %s in progress') % (state._opname),
4869 hint=state.hint(),
4869 hint=state.hint(),
4870 )
4870 )
4871 if node:
4871 if node:
4872 raise error.Abort(_(b"cannot specify a node with --abort"))
4872 raise error.Abort(_(b"cannot specify a node with --abort"))
4873 return hg.abortmerge(repo.ui, repo)
4873 return hg.abortmerge(repo.ui, repo)
4874
4874
4875 if opts.get(b'rev') and node:
4875 if opts.get(b'rev') and node:
4876 raise error.Abort(_(b"please specify just one revision"))
4876 raise error.Abort(_(b"please specify just one revision"))
4877 if not node:
4877 if not node:
4878 node = opts.get(b'rev')
4878 node = opts.get(b'rev')
4879
4879
4880 if node:
4880 if node:
4881 node = scmutil.revsingle(repo, node).node()
4881 ctx = scmutil.revsingle(repo, node)
4882 else:
4882 else:
4883 if ui.configbool(b'commands', b'merge.require-rev'):
4883 if ui.configbool(b'commands', b'merge.require-rev'):
4884 raise error.Abort(
4884 raise error.Abort(
4885 _(
4885 _(
4886 b'configuration requires specifying revision to merge '
4886 b'configuration requires specifying revision to merge '
4887 b'with'
4887 b'with'
4888 )
4888 )
4889 )
4889 )
4890 node = repo[destutil.destmerge(repo)].node()
4890 ctx = repo[destutil.destmerge(repo)]
4891
4891
4892 if node is None:
4892 if ctx.node() is None:
4893 raise error.Abort(_(b'merging with the working copy has no effect'))
4893 raise error.Abort(_(b'merging with the working copy has no effect'))
4894
4894
4895 if opts.get(b'preview'):
4895 if opts.get(b'preview'):
4896 # find nodes that are ancestors of p2 but not of p1
4896 # find nodes that are ancestors of p2 but not of p1
4897 p1 = repo[b'.'].node()
4897 p1 = repo[b'.'].node()
4898 p2 = node
4898 p2 = ctx.node()
4899 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
4899 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
4900
4900
4901 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
4901 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
4902 for node in nodes:
4902 for node in nodes:
4903 displayer.show(repo[node])
4903 displayer.show(repo[node])
4904 displayer.close()
4904 displayer.close()
4905 return 0
4905 return 0
4906
4906
4907 # ui.forcemerge is an internal variable, do not document
4907 # ui.forcemerge is an internal variable, do not document
4908 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
4908 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
4909 with ui.configoverride(overrides, b'merge'):
4909 with ui.configoverride(overrides, b'merge'):
4910 force = opts.get(b'force')
4910 force = opts.get(b'force')
4911 labels = [b'working copy', b'merge rev']
4911 labels = [b'working copy', b'merge rev']
4912 return hg.merge(repo, node, force=force, labels=labels)
4912 return hg.merge(ctx, force=force, labels=labels)
4913
4913
4914
4914
4915 statemod.addunfinished(
4915 statemod.addunfinished(
4916 b'merge',
4916 b'merge',
4917 fname=None,
4917 fname=None,
4918 clearable=True,
4918 clearable=True,
4919 allowcommit=True,
4919 allowcommit=True,
4920 cmdmsg=_(b'outstanding uncommitted merge'),
4920 cmdmsg=_(b'outstanding uncommitted merge'),
4921 abortfunc=hg.abortmerge,
4921 abortfunc=hg.abortmerge,
4922 statushint=_(
4922 statushint=_(
4923 b'To continue: hg commit\nTo abort: hg merge --abort'
4923 b'To continue: hg commit\nTo abort: hg merge --abort'
4924 ),
4924 ),
4925 cmdhint=_(b"use 'hg commit' or 'hg merge --abort'"),
4925 cmdhint=_(b"use 'hg commit' or 'hg merge --abort'"),
4926 )
4926 )
4927
4927
4928
4928
4929 @command(
4929 @command(
4930 b'outgoing|out',
4930 b'outgoing|out',
4931 [
4931 [
4932 (
4932 (
4933 b'f',
4933 b'f',
4934 b'force',
4934 b'force',
4935 None,
4935 None,
4936 _(b'run even when the destination is unrelated'),
4936 _(b'run even when the destination is unrelated'),
4937 ),
4937 ),
4938 (
4938 (
4939 b'r',
4939 b'r',
4940 b'rev',
4940 b'rev',
4941 [],
4941 [],
4942 _(b'a changeset intended to be included in the destination'),
4942 _(b'a changeset intended to be included in the destination'),
4943 _(b'REV'),
4943 _(b'REV'),
4944 ),
4944 ),
4945 (b'n', b'newest-first', None, _(b'show newest record first')),
4945 (b'n', b'newest-first', None, _(b'show newest record first')),
4946 (b'B', b'bookmarks', False, _(b'compare bookmarks')),
4946 (b'B', b'bookmarks', False, _(b'compare bookmarks')),
4947 (
4947 (
4948 b'b',
4948 b'b',
4949 b'branch',
4949 b'branch',
4950 [],
4950 [],
4951 _(b'a specific branch you would like to push'),
4951 _(b'a specific branch you would like to push'),
4952 _(b'BRANCH'),
4952 _(b'BRANCH'),
4953 ),
4953 ),
4954 ]
4954 ]
4955 + logopts
4955 + logopts
4956 + remoteopts
4956 + remoteopts
4957 + subrepoopts,
4957 + subrepoopts,
4958 _(b'[-M] [-p] [-n] [-f] [-r REV]... [DEST]'),
4958 _(b'[-M] [-p] [-n] [-f] [-r REV]... [DEST]'),
4959 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4959 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4960 )
4960 )
4961 def outgoing(ui, repo, dest=None, **opts):
4961 def outgoing(ui, repo, dest=None, **opts):
4962 """show changesets not found in the destination
4962 """show changesets not found in the destination
4963
4963
4964 Show changesets not found in the specified destination repository
4964 Show changesets not found in the specified destination repository
4965 or the default push location. These are the changesets that would
4965 or the default push location. These are the changesets that would
4966 be pushed if a push was requested.
4966 be pushed if a push was requested.
4967
4967
4968 See pull for details of valid destination formats.
4968 See pull for details of valid destination formats.
4969
4969
4970 .. container:: verbose
4970 .. container:: verbose
4971
4971
4972 With -B/--bookmarks, the result of bookmark comparison between
4972 With -B/--bookmarks, the result of bookmark comparison between
4973 local and remote repositories is displayed. With -v/--verbose,
4973 local and remote repositories is displayed. With -v/--verbose,
4974 status is also displayed for each bookmark like below::
4974 status is also displayed for each bookmark like below::
4975
4975
4976 BM1 01234567890a added
4976 BM1 01234567890a added
4977 BM2 deleted
4977 BM2 deleted
4978 BM3 234567890abc advanced
4978 BM3 234567890abc advanced
4979 BM4 34567890abcd diverged
4979 BM4 34567890abcd diverged
4980 BM5 4567890abcde changed
4980 BM5 4567890abcde changed
4981
4981
4982 The action taken when pushing depends on the
4982 The action taken when pushing depends on the
4983 status of each bookmark:
4983 status of each bookmark:
4984
4984
4985 :``added``: push with ``-B`` will create it
4985 :``added``: push with ``-B`` will create it
4986 :``deleted``: push with ``-B`` will delete it
4986 :``deleted``: push with ``-B`` will delete it
4987 :``advanced``: push will update it
4987 :``advanced``: push will update it
4988 :``diverged``: push with ``-B`` will update it
4988 :``diverged``: push with ``-B`` will update it
4989 :``changed``: push with ``-B`` will update it
4989 :``changed``: push with ``-B`` will update it
4990
4990
4991 From the point of view of pushing behavior, bookmarks
4991 From the point of view of pushing behavior, bookmarks
4992 existing only in the remote repository are treated as
4992 existing only in the remote repository are treated as
4993 ``deleted``, even if it is in fact added remotely.
4993 ``deleted``, even if it is in fact added remotely.
4994
4994
4995 Returns 0 if there are outgoing changes, 1 otherwise.
4995 Returns 0 if there are outgoing changes, 1 otherwise.
4996 """
4996 """
4997 # hg._outgoing() needs to re-resolve the path in order to handle #branch
4997 # hg._outgoing() needs to re-resolve the path in order to handle #branch
4998 # style URLs, so don't overwrite dest.
4998 # style URLs, so don't overwrite dest.
4999 path = ui.paths.getpath(dest, default=(b'default-push', b'default'))
4999 path = ui.paths.getpath(dest, default=(b'default-push', b'default'))
5000 if not path:
5000 if not path:
5001 raise error.Abort(
5001 raise error.Abort(
5002 _(b'default repository not configured!'),
5002 _(b'default repository not configured!'),
5003 hint=_(b"see 'hg help config.paths'"),
5003 hint=_(b"see 'hg help config.paths'"),
5004 )
5004 )
5005
5005
5006 opts = pycompat.byteskwargs(opts)
5006 opts = pycompat.byteskwargs(opts)
5007 if opts.get(b'graph'):
5007 if opts.get(b'graph'):
5008 logcmdutil.checkunsupportedgraphflags([], opts)
5008 logcmdutil.checkunsupportedgraphflags([], opts)
5009 o, other = hg._outgoing(ui, repo, dest, opts)
5009 o, other = hg._outgoing(ui, repo, dest, opts)
5010 if not o:
5010 if not o:
5011 cmdutil.outgoinghooks(ui, repo, other, opts, o)
5011 cmdutil.outgoinghooks(ui, repo, other, opts, o)
5012 return
5012 return
5013
5013
5014 revdag = logcmdutil.graphrevs(repo, o, opts)
5014 revdag = logcmdutil.graphrevs(repo, o, opts)
5015 ui.pager(b'outgoing')
5015 ui.pager(b'outgoing')
5016 displayer = logcmdutil.changesetdisplayer(ui, repo, opts, buffered=True)
5016 displayer = logcmdutil.changesetdisplayer(ui, repo, opts, buffered=True)
5017 logcmdutil.displaygraph(
5017 logcmdutil.displaygraph(
5018 ui, repo, revdag, displayer, graphmod.asciiedges
5018 ui, repo, revdag, displayer, graphmod.asciiedges
5019 )
5019 )
5020 cmdutil.outgoinghooks(ui, repo, other, opts, o)
5020 cmdutil.outgoinghooks(ui, repo, other, opts, o)
5021 return 0
5021 return 0
5022
5022
5023 if opts.get(b'bookmarks'):
5023 if opts.get(b'bookmarks'):
5024 dest = path.pushloc or path.loc
5024 dest = path.pushloc or path.loc
5025 other = hg.peer(repo, opts, dest)
5025 other = hg.peer(repo, opts, dest)
5026 if b'bookmarks' not in other.listkeys(b'namespaces'):
5026 if b'bookmarks' not in other.listkeys(b'namespaces'):
5027 ui.warn(_(b"remote doesn't support bookmarks\n"))
5027 ui.warn(_(b"remote doesn't support bookmarks\n"))
5028 return 0
5028 return 0
5029 ui.status(_(b'comparing with %s\n') % util.hidepassword(dest))
5029 ui.status(_(b'comparing with %s\n') % util.hidepassword(dest))
5030 ui.pager(b'outgoing')
5030 ui.pager(b'outgoing')
5031 return bookmarks.outgoing(ui, repo, other)
5031 return bookmarks.outgoing(ui, repo, other)
5032
5032
5033 repo._subtoppath = path.pushloc or path.loc
5033 repo._subtoppath = path.pushloc or path.loc
5034 try:
5034 try:
5035 return hg.outgoing(ui, repo, dest, opts)
5035 return hg.outgoing(ui, repo, dest, opts)
5036 finally:
5036 finally:
5037 del repo._subtoppath
5037 del repo._subtoppath
5038
5038
5039
5039
5040 @command(
5040 @command(
5041 b'parents',
5041 b'parents',
5042 [
5042 [
5043 (
5043 (
5044 b'r',
5044 b'r',
5045 b'rev',
5045 b'rev',
5046 b'',
5046 b'',
5047 _(b'show parents of the specified revision'),
5047 _(b'show parents of the specified revision'),
5048 _(b'REV'),
5048 _(b'REV'),
5049 ),
5049 ),
5050 ]
5050 ]
5051 + templateopts,
5051 + templateopts,
5052 _(b'[-r REV] [FILE]'),
5052 _(b'[-r REV] [FILE]'),
5053 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
5053 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
5054 inferrepo=True,
5054 inferrepo=True,
5055 )
5055 )
5056 def parents(ui, repo, file_=None, **opts):
5056 def parents(ui, repo, file_=None, **opts):
5057 """show the parents of the working directory or revision (DEPRECATED)
5057 """show the parents of the working directory or revision (DEPRECATED)
5058
5058
5059 Print the working directory's parent revisions. If a revision is
5059 Print the working directory's parent revisions. If a revision is
5060 given via -r/--rev, the parent of that revision will be printed.
5060 given via -r/--rev, the parent of that revision will be printed.
5061 If a file argument is given, the revision in which the file was
5061 If a file argument is given, the revision in which the file was
5062 last changed (before the working directory revision or the
5062 last changed (before the working directory revision or the
5063 argument to --rev if given) is printed.
5063 argument to --rev if given) is printed.
5064
5064
5065 This command is equivalent to::
5065 This command is equivalent to::
5066
5066
5067 hg log -r "p1()+p2()" or
5067 hg log -r "p1()+p2()" or
5068 hg log -r "p1(REV)+p2(REV)" or
5068 hg log -r "p1(REV)+p2(REV)" or
5069 hg log -r "max(::p1() and file(FILE))+max(::p2() and file(FILE))" or
5069 hg log -r "max(::p1() and file(FILE))+max(::p2() and file(FILE))" or
5070 hg log -r "max(::p1(REV) and file(FILE))+max(::p2(REV) and file(FILE))"
5070 hg log -r "max(::p1(REV) and file(FILE))+max(::p2(REV) and file(FILE))"
5071
5071
5072 See :hg:`summary` and :hg:`help revsets` for related information.
5072 See :hg:`summary` and :hg:`help revsets` for related information.
5073
5073
5074 Returns 0 on success.
5074 Returns 0 on success.
5075 """
5075 """
5076
5076
5077 opts = pycompat.byteskwargs(opts)
5077 opts = pycompat.byteskwargs(opts)
5078 rev = opts.get(b'rev')
5078 rev = opts.get(b'rev')
5079 if rev:
5079 if rev:
5080 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
5080 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
5081 ctx = scmutil.revsingle(repo, rev, None)
5081 ctx = scmutil.revsingle(repo, rev, None)
5082
5082
5083 if file_:
5083 if file_:
5084 m = scmutil.match(ctx, (file_,), opts)
5084 m = scmutil.match(ctx, (file_,), opts)
5085 if m.anypats() or len(m.files()) != 1:
5085 if m.anypats() or len(m.files()) != 1:
5086 raise error.Abort(_(b'can only specify an explicit filename'))
5086 raise error.Abort(_(b'can only specify an explicit filename'))
5087 file_ = m.files()[0]
5087 file_ = m.files()[0]
5088 filenodes = []
5088 filenodes = []
5089 for cp in ctx.parents():
5089 for cp in ctx.parents():
5090 if not cp:
5090 if not cp:
5091 continue
5091 continue
5092 try:
5092 try:
5093 filenodes.append(cp.filenode(file_))
5093 filenodes.append(cp.filenode(file_))
5094 except error.LookupError:
5094 except error.LookupError:
5095 pass
5095 pass
5096 if not filenodes:
5096 if not filenodes:
5097 raise error.Abort(_(b"'%s' not found in manifest!") % file_)
5097 raise error.Abort(_(b"'%s' not found in manifest!") % file_)
5098 p = []
5098 p = []
5099 for fn in filenodes:
5099 for fn in filenodes:
5100 fctx = repo.filectx(file_, fileid=fn)
5100 fctx = repo.filectx(file_, fileid=fn)
5101 p.append(fctx.node())
5101 p.append(fctx.node())
5102 else:
5102 else:
5103 p = [cp.node() for cp in ctx.parents()]
5103 p = [cp.node() for cp in ctx.parents()]
5104
5104
5105 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
5105 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
5106 for n in p:
5106 for n in p:
5107 if n != nullid:
5107 if n != nullid:
5108 displayer.show(repo[n])
5108 displayer.show(repo[n])
5109 displayer.close()
5109 displayer.close()
5110
5110
5111
5111
5112 @command(
5112 @command(
5113 b'paths',
5113 b'paths',
5114 formatteropts,
5114 formatteropts,
5115 _(b'[NAME]'),
5115 _(b'[NAME]'),
5116 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5116 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5117 optionalrepo=True,
5117 optionalrepo=True,
5118 intents={INTENT_READONLY},
5118 intents={INTENT_READONLY},
5119 )
5119 )
5120 def paths(ui, repo, search=None, **opts):
5120 def paths(ui, repo, search=None, **opts):
5121 """show aliases for remote repositories
5121 """show aliases for remote repositories
5122
5122
5123 Show definition of symbolic path name NAME. If no name is given,
5123 Show definition of symbolic path name NAME. If no name is given,
5124 show definition of all available names.
5124 show definition of all available names.
5125
5125
5126 Option -q/--quiet suppresses all output when searching for NAME
5126 Option -q/--quiet suppresses all output when searching for NAME
5127 and shows only the path names when listing all definitions.
5127 and shows only the path names when listing all definitions.
5128
5128
5129 Path names are defined in the [paths] section of your
5129 Path names are defined in the [paths] section of your
5130 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
5130 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
5131 repository, ``.hg/hgrc`` is used, too.
5131 repository, ``.hg/hgrc`` is used, too.
5132
5132
5133 The path names ``default`` and ``default-push`` have a special
5133 The path names ``default`` and ``default-push`` have a special
5134 meaning. When performing a push or pull operation, they are used
5134 meaning. When performing a push or pull operation, they are used
5135 as fallbacks if no location is specified on the command-line.
5135 as fallbacks if no location is specified on the command-line.
5136 When ``default-push`` is set, it will be used for push and
5136 When ``default-push`` is set, it will be used for push and
5137 ``default`` will be used for pull; otherwise ``default`` is used
5137 ``default`` will be used for pull; otherwise ``default`` is used
5138 as the fallback for both. When cloning a repository, the clone
5138 as the fallback for both. When cloning a repository, the clone
5139 source is written as ``default`` in ``.hg/hgrc``.
5139 source is written as ``default`` in ``.hg/hgrc``.
5140
5140
5141 .. note::
5141 .. note::
5142
5142
5143 ``default`` and ``default-push`` apply to all inbound (e.g.
5143 ``default`` and ``default-push`` apply to all inbound (e.g.
5144 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email`
5144 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email`
5145 and :hg:`bundle`) operations.
5145 and :hg:`bundle`) operations.
5146
5146
5147 See :hg:`help urls` for more information.
5147 See :hg:`help urls` for more information.
5148
5148
5149 .. container:: verbose
5149 .. container:: verbose
5150
5150
5151 Template:
5151 Template:
5152
5152
5153 The following keywords are supported. See also :hg:`help templates`.
5153 The following keywords are supported. See also :hg:`help templates`.
5154
5154
5155 :name: String. Symbolic name of the path alias.
5155 :name: String. Symbolic name of the path alias.
5156 :pushurl: String. URL for push operations.
5156 :pushurl: String. URL for push operations.
5157 :url: String. URL or directory path for the other operations.
5157 :url: String. URL or directory path for the other operations.
5158
5158
5159 Returns 0 on success.
5159 Returns 0 on success.
5160 """
5160 """
5161
5161
5162 opts = pycompat.byteskwargs(opts)
5162 opts = pycompat.byteskwargs(opts)
5163 ui.pager(b'paths')
5163 ui.pager(b'paths')
5164 if search:
5164 if search:
5165 pathitems = [
5165 pathitems = [
5166 (name, path)
5166 (name, path)
5167 for name, path in pycompat.iteritems(ui.paths)
5167 for name, path in pycompat.iteritems(ui.paths)
5168 if name == search
5168 if name == search
5169 ]
5169 ]
5170 else:
5170 else:
5171 pathitems = sorted(pycompat.iteritems(ui.paths))
5171 pathitems = sorted(pycompat.iteritems(ui.paths))
5172
5172
5173 fm = ui.formatter(b'paths', opts)
5173 fm = ui.formatter(b'paths', opts)
5174 if fm.isplain():
5174 if fm.isplain():
5175 hidepassword = util.hidepassword
5175 hidepassword = util.hidepassword
5176 else:
5176 else:
5177 hidepassword = bytes
5177 hidepassword = bytes
5178 if ui.quiet:
5178 if ui.quiet:
5179 namefmt = b'%s\n'
5179 namefmt = b'%s\n'
5180 else:
5180 else:
5181 namefmt = b'%s = '
5181 namefmt = b'%s = '
5182 showsubopts = not search and not ui.quiet
5182 showsubopts = not search and not ui.quiet
5183
5183
5184 for name, path in pathitems:
5184 for name, path in pathitems:
5185 fm.startitem()
5185 fm.startitem()
5186 fm.condwrite(not search, b'name', namefmt, name)
5186 fm.condwrite(not search, b'name', namefmt, name)
5187 fm.condwrite(not ui.quiet, b'url', b'%s\n', hidepassword(path.rawloc))
5187 fm.condwrite(not ui.quiet, b'url', b'%s\n', hidepassword(path.rawloc))
5188 for subopt, value in sorted(path.suboptions.items()):
5188 for subopt, value in sorted(path.suboptions.items()):
5189 assert subopt not in (b'name', b'url')
5189 assert subopt not in (b'name', b'url')
5190 if showsubopts:
5190 if showsubopts:
5191 fm.plain(b'%s:%s = ' % (name, subopt))
5191 fm.plain(b'%s:%s = ' % (name, subopt))
5192 fm.condwrite(showsubopts, subopt, b'%s\n', value)
5192 fm.condwrite(showsubopts, subopt, b'%s\n', value)
5193
5193
5194 fm.end()
5194 fm.end()
5195
5195
5196 if search and not pathitems:
5196 if search and not pathitems:
5197 if not ui.quiet:
5197 if not ui.quiet:
5198 ui.warn(_(b"not found!\n"))
5198 ui.warn(_(b"not found!\n"))
5199 return 1
5199 return 1
5200 else:
5200 else:
5201 return 0
5201 return 0
5202
5202
5203
5203
5204 @command(
5204 @command(
5205 b'phase',
5205 b'phase',
5206 [
5206 [
5207 (b'p', b'public', False, _(b'set changeset phase to public')),
5207 (b'p', b'public', False, _(b'set changeset phase to public')),
5208 (b'd', b'draft', False, _(b'set changeset phase to draft')),
5208 (b'd', b'draft', False, _(b'set changeset phase to draft')),
5209 (b's', b'secret', False, _(b'set changeset phase to secret')),
5209 (b's', b'secret', False, _(b'set changeset phase to secret')),
5210 (b'f', b'force', False, _(b'allow to move boundary backward')),
5210 (b'f', b'force', False, _(b'allow to move boundary backward')),
5211 (b'r', b'rev', [], _(b'target revision'), _(b'REV')),
5211 (b'r', b'rev', [], _(b'target revision'), _(b'REV')),
5212 ],
5212 ],
5213 _(b'[-p|-d|-s] [-f] [-r] [REV...]'),
5213 _(b'[-p|-d|-s] [-f] [-r] [REV...]'),
5214 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
5214 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
5215 )
5215 )
5216 def phase(ui, repo, *revs, **opts):
5216 def phase(ui, repo, *revs, **opts):
5217 """set or show the current phase name
5217 """set or show the current phase name
5218
5218
5219 With no argument, show the phase name of the current revision(s).
5219 With no argument, show the phase name of the current revision(s).
5220
5220
5221 With one of -p/--public, -d/--draft or -s/--secret, change the
5221 With one of -p/--public, -d/--draft or -s/--secret, change the
5222 phase value of the specified revisions.
5222 phase value of the specified revisions.
5223
5223
5224 Unless -f/--force is specified, :hg:`phase` won't move changesets from a
5224 Unless -f/--force is specified, :hg:`phase` won't move changesets from a
5225 lower phase to a higher phase. Phases are ordered as follows::
5225 lower phase to a higher phase. Phases are ordered as follows::
5226
5226
5227 public < draft < secret
5227 public < draft < secret
5228
5228
5229 Returns 0 on success, 1 if some phases could not be changed.
5229 Returns 0 on success, 1 if some phases could not be changed.
5230
5230
5231 (For more information about the phases concept, see :hg:`help phases`.)
5231 (For more information about the phases concept, see :hg:`help phases`.)
5232 """
5232 """
5233 opts = pycompat.byteskwargs(opts)
5233 opts = pycompat.byteskwargs(opts)
5234 # search for a unique phase argument
5234 # search for a unique phase argument
5235 targetphase = None
5235 targetphase = None
5236 for idx, name in enumerate(phases.cmdphasenames):
5236 for idx, name in enumerate(phases.cmdphasenames):
5237 if opts[name]:
5237 if opts[name]:
5238 if targetphase is not None:
5238 if targetphase is not None:
5239 raise error.Abort(_(b'only one phase can be specified'))
5239 raise error.Abort(_(b'only one phase can be specified'))
5240 targetphase = idx
5240 targetphase = idx
5241
5241
5242 # look for specified revision
5242 # look for specified revision
5243 revs = list(revs)
5243 revs = list(revs)
5244 revs.extend(opts[b'rev'])
5244 revs.extend(opts[b'rev'])
5245 if not revs:
5245 if not revs:
5246 # display both parents as the second parent phase can influence
5246 # display both parents as the second parent phase can influence
5247 # the phase of a merge commit
5247 # the phase of a merge commit
5248 revs = [c.rev() for c in repo[None].parents()]
5248 revs = [c.rev() for c in repo[None].parents()]
5249
5249
5250 revs = scmutil.revrange(repo, revs)
5250 revs = scmutil.revrange(repo, revs)
5251
5251
5252 ret = 0
5252 ret = 0
5253 if targetphase is None:
5253 if targetphase is None:
5254 # display
5254 # display
5255 for r in revs:
5255 for r in revs:
5256 ctx = repo[r]
5256 ctx = repo[r]
5257 ui.write(b'%i: %s\n' % (ctx.rev(), ctx.phasestr()))
5257 ui.write(b'%i: %s\n' % (ctx.rev(), ctx.phasestr()))
5258 else:
5258 else:
5259 with repo.lock(), repo.transaction(b"phase") as tr:
5259 with repo.lock(), repo.transaction(b"phase") as tr:
5260 # set phase
5260 # set phase
5261 if not revs:
5261 if not revs:
5262 raise error.Abort(_(b'empty revision set'))
5262 raise error.Abort(_(b'empty revision set'))
5263 nodes = [repo[r].node() for r in revs]
5263 nodes = [repo[r].node() for r in revs]
5264 # moving revision from public to draft may hide them
5264 # moving revision from public to draft may hide them
5265 # We have to check result on an unfiltered repository
5265 # We have to check result on an unfiltered repository
5266 unfi = repo.unfiltered()
5266 unfi = repo.unfiltered()
5267 getphase = unfi._phasecache.phase
5267 getphase = unfi._phasecache.phase
5268 olddata = [getphase(unfi, r) for r in unfi]
5268 olddata = [getphase(unfi, r) for r in unfi]
5269 phases.advanceboundary(repo, tr, targetphase, nodes)
5269 phases.advanceboundary(repo, tr, targetphase, nodes)
5270 if opts[b'force']:
5270 if opts[b'force']:
5271 phases.retractboundary(repo, tr, targetphase, nodes)
5271 phases.retractboundary(repo, tr, targetphase, nodes)
5272 getphase = unfi._phasecache.phase
5272 getphase = unfi._phasecache.phase
5273 newdata = [getphase(unfi, r) for r in unfi]
5273 newdata = [getphase(unfi, r) for r in unfi]
5274 changes = sum(newdata[r] != olddata[r] for r in unfi)
5274 changes = sum(newdata[r] != olddata[r] for r in unfi)
5275 cl = unfi.changelog
5275 cl = unfi.changelog
5276 rejected = [n for n in nodes if newdata[cl.rev(n)] < targetphase]
5276 rejected = [n for n in nodes if newdata[cl.rev(n)] < targetphase]
5277 if rejected:
5277 if rejected:
5278 ui.warn(
5278 ui.warn(
5279 _(
5279 _(
5280 b'cannot move %i changesets to a higher '
5280 b'cannot move %i changesets to a higher '
5281 b'phase, use --force\n'
5281 b'phase, use --force\n'
5282 )
5282 )
5283 % len(rejected)
5283 % len(rejected)
5284 )
5284 )
5285 ret = 1
5285 ret = 1
5286 if changes:
5286 if changes:
5287 msg = _(b'phase changed for %i changesets\n') % changes
5287 msg = _(b'phase changed for %i changesets\n') % changes
5288 if ret:
5288 if ret:
5289 ui.status(msg)
5289 ui.status(msg)
5290 else:
5290 else:
5291 ui.note(msg)
5291 ui.note(msg)
5292 else:
5292 else:
5293 ui.warn(_(b'no phases changed\n'))
5293 ui.warn(_(b'no phases changed\n'))
5294 return ret
5294 return ret
5295
5295
5296
5296
5297 def postincoming(ui, repo, modheads, optupdate, checkout, brev):
5297 def postincoming(ui, repo, modheads, optupdate, checkout, brev):
5298 """Run after a changegroup has been added via pull/unbundle
5298 """Run after a changegroup has been added via pull/unbundle
5299
5299
5300 This takes arguments below:
5300 This takes arguments below:
5301
5301
5302 :modheads: change of heads by pull/unbundle
5302 :modheads: change of heads by pull/unbundle
5303 :optupdate: updating working directory is needed or not
5303 :optupdate: updating working directory is needed or not
5304 :checkout: update destination revision (or None to default destination)
5304 :checkout: update destination revision (or None to default destination)
5305 :brev: a name, which might be a bookmark to be activated after updating
5305 :brev: a name, which might be a bookmark to be activated after updating
5306 """
5306 """
5307 if modheads == 0:
5307 if modheads == 0:
5308 return
5308 return
5309 if optupdate:
5309 if optupdate:
5310 try:
5310 try:
5311 return hg.updatetotally(ui, repo, checkout, brev)
5311 return hg.updatetotally(ui, repo, checkout, brev)
5312 except error.UpdateAbort as inst:
5312 except error.UpdateAbort as inst:
5313 msg = _(b"not updating: %s") % stringutil.forcebytestr(inst)
5313 msg = _(b"not updating: %s") % stringutil.forcebytestr(inst)
5314 hint = inst.hint
5314 hint = inst.hint
5315 raise error.UpdateAbort(msg, hint=hint)
5315 raise error.UpdateAbort(msg, hint=hint)
5316 if modheads is not None and modheads > 1:
5316 if modheads is not None and modheads > 1:
5317 currentbranchheads = len(repo.branchheads())
5317 currentbranchheads = len(repo.branchheads())
5318 if currentbranchheads == modheads:
5318 if currentbranchheads == modheads:
5319 ui.status(
5319 ui.status(
5320 _(b"(run 'hg heads' to see heads, 'hg merge' to merge)\n")
5320 _(b"(run 'hg heads' to see heads, 'hg merge' to merge)\n")
5321 )
5321 )
5322 elif currentbranchheads > 1:
5322 elif currentbranchheads > 1:
5323 ui.status(
5323 ui.status(
5324 _(b"(run 'hg heads .' to see heads, 'hg merge' to merge)\n")
5324 _(b"(run 'hg heads .' to see heads, 'hg merge' to merge)\n")
5325 )
5325 )
5326 else:
5326 else:
5327 ui.status(_(b"(run 'hg heads' to see heads)\n"))
5327 ui.status(_(b"(run 'hg heads' to see heads)\n"))
5328 elif not ui.configbool(b'commands', b'update.requiredest'):
5328 elif not ui.configbool(b'commands', b'update.requiredest'):
5329 ui.status(_(b"(run 'hg update' to get a working copy)\n"))
5329 ui.status(_(b"(run 'hg update' to get a working copy)\n"))
5330
5330
5331
5331
5332 @command(
5332 @command(
5333 b'pull',
5333 b'pull',
5334 [
5334 [
5335 (
5335 (
5336 b'u',
5336 b'u',
5337 b'update',
5337 b'update',
5338 None,
5338 None,
5339 _(b'update to new branch head if new descendants were pulled'),
5339 _(b'update to new branch head if new descendants were pulled'),
5340 ),
5340 ),
5341 (
5341 (
5342 b'f',
5342 b'f',
5343 b'force',
5343 b'force',
5344 None,
5344 None,
5345 _(b'run even when remote repository is unrelated'),
5345 _(b'run even when remote repository is unrelated'),
5346 ),
5346 ),
5347 (
5347 (
5348 b'r',
5348 b'r',
5349 b'rev',
5349 b'rev',
5350 [],
5350 [],
5351 _(b'a remote changeset intended to be added'),
5351 _(b'a remote changeset intended to be added'),
5352 _(b'REV'),
5352 _(b'REV'),
5353 ),
5353 ),
5354 (b'B', b'bookmark', [], _(b"bookmark to pull"), _(b'BOOKMARK')),
5354 (b'B', b'bookmark', [], _(b"bookmark to pull"), _(b'BOOKMARK')),
5355 (
5355 (
5356 b'b',
5356 b'b',
5357 b'branch',
5357 b'branch',
5358 [],
5358 [],
5359 _(b'a specific branch you would like to pull'),
5359 _(b'a specific branch you would like to pull'),
5360 _(b'BRANCH'),
5360 _(b'BRANCH'),
5361 ),
5361 ),
5362 ]
5362 ]
5363 + remoteopts,
5363 + remoteopts,
5364 _(b'[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'),
5364 _(b'[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'),
5365 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5365 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5366 helpbasic=True,
5366 helpbasic=True,
5367 )
5367 )
5368 def pull(ui, repo, source=b"default", **opts):
5368 def pull(ui, repo, source=b"default", **opts):
5369 """pull changes from the specified source
5369 """pull changes from the specified source
5370
5370
5371 Pull changes from a remote repository to a local one.
5371 Pull changes from a remote repository to a local one.
5372
5372
5373 This finds all changes from the repository at the specified path
5373 This finds all changes from the repository at the specified path
5374 or URL and adds them to a local repository (the current one unless
5374 or URL and adds them to a local repository (the current one unless
5375 -R is specified). By default, this does not update the copy of the
5375 -R is specified). By default, this does not update the copy of the
5376 project in the working directory.
5376 project in the working directory.
5377
5377
5378 When cloning from servers that support it, Mercurial may fetch
5378 When cloning from servers that support it, Mercurial may fetch
5379 pre-generated data. When this is done, hooks operating on incoming
5379 pre-generated data. When this is done, hooks operating on incoming
5380 changesets and changegroups may fire more than once, once for each
5380 changesets and changegroups may fire more than once, once for each
5381 pre-generated bundle and as well as for any additional remaining
5381 pre-generated bundle and as well as for any additional remaining
5382 data. See :hg:`help -e clonebundles` for more.
5382 data. See :hg:`help -e clonebundles` for more.
5383
5383
5384 Use :hg:`incoming` if you want to see what would have been added
5384 Use :hg:`incoming` if you want to see what would have been added
5385 by a pull at the time you issued this command. If you then decide
5385 by a pull at the time you issued this command. If you then decide
5386 to add those changes to the repository, you should use :hg:`pull
5386 to add those changes to the repository, you should use :hg:`pull
5387 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
5387 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
5388
5388
5389 If SOURCE is omitted, the 'default' path will be used.
5389 If SOURCE is omitted, the 'default' path will be used.
5390 See :hg:`help urls` for more information.
5390 See :hg:`help urls` for more information.
5391
5391
5392 Specifying bookmark as ``.`` is equivalent to specifying the active
5392 Specifying bookmark as ``.`` is equivalent to specifying the active
5393 bookmark's name.
5393 bookmark's name.
5394
5394
5395 Returns 0 on success, 1 if an update had unresolved files.
5395 Returns 0 on success, 1 if an update had unresolved files.
5396 """
5396 """
5397
5397
5398 opts = pycompat.byteskwargs(opts)
5398 opts = pycompat.byteskwargs(opts)
5399 if ui.configbool(b'commands', b'update.requiredest') and opts.get(
5399 if ui.configbool(b'commands', b'update.requiredest') and opts.get(
5400 b'update'
5400 b'update'
5401 ):
5401 ):
5402 msg = _(b'update destination required by configuration')
5402 msg = _(b'update destination required by configuration')
5403 hint = _(b'use hg pull followed by hg update DEST')
5403 hint = _(b'use hg pull followed by hg update DEST')
5404 raise error.Abort(msg, hint=hint)
5404 raise error.Abort(msg, hint=hint)
5405
5405
5406 source, branches = hg.parseurl(ui.expandpath(source), opts.get(b'branch'))
5406 source, branches = hg.parseurl(ui.expandpath(source), opts.get(b'branch'))
5407 ui.status(_(b'pulling from %s\n') % util.hidepassword(source))
5407 ui.status(_(b'pulling from %s\n') % util.hidepassword(source))
5408 other = hg.peer(repo, opts, source)
5408 other = hg.peer(repo, opts, source)
5409 try:
5409 try:
5410 revs, checkout = hg.addbranchrevs(
5410 revs, checkout = hg.addbranchrevs(
5411 repo, other, branches, opts.get(b'rev')
5411 repo, other, branches, opts.get(b'rev')
5412 )
5412 )
5413
5413
5414 pullopargs = {}
5414 pullopargs = {}
5415
5415
5416 nodes = None
5416 nodes = None
5417 if opts.get(b'bookmark') or revs:
5417 if opts.get(b'bookmark') or revs:
5418 # The list of bookmark used here is the same used to actually update
5418 # The list of bookmark used here is the same used to actually update
5419 # the bookmark names, to avoid the race from issue 4689 and we do
5419 # the bookmark names, to avoid the race from issue 4689 and we do
5420 # all lookup and bookmark queries in one go so they see the same
5420 # all lookup and bookmark queries in one go so they see the same
5421 # version of the server state (issue 4700).
5421 # version of the server state (issue 4700).
5422 nodes = []
5422 nodes = []
5423 fnodes = []
5423 fnodes = []
5424 revs = revs or []
5424 revs = revs or []
5425 if revs and not other.capable(b'lookup'):
5425 if revs and not other.capable(b'lookup'):
5426 err = _(
5426 err = _(
5427 b"other repository doesn't support revision lookup, "
5427 b"other repository doesn't support revision lookup, "
5428 b"so a rev cannot be specified."
5428 b"so a rev cannot be specified."
5429 )
5429 )
5430 raise error.Abort(err)
5430 raise error.Abort(err)
5431 with other.commandexecutor() as e:
5431 with other.commandexecutor() as e:
5432 fremotebookmarks = e.callcommand(
5432 fremotebookmarks = e.callcommand(
5433 b'listkeys', {b'namespace': b'bookmarks'}
5433 b'listkeys', {b'namespace': b'bookmarks'}
5434 )
5434 )
5435 for r in revs:
5435 for r in revs:
5436 fnodes.append(e.callcommand(b'lookup', {b'key': r}))
5436 fnodes.append(e.callcommand(b'lookup', {b'key': r}))
5437 remotebookmarks = fremotebookmarks.result()
5437 remotebookmarks = fremotebookmarks.result()
5438 remotebookmarks = bookmarks.unhexlifybookmarks(remotebookmarks)
5438 remotebookmarks = bookmarks.unhexlifybookmarks(remotebookmarks)
5439 pullopargs[b'remotebookmarks'] = remotebookmarks
5439 pullopargs[b'remotebookmarks'] = remotebookmarks
5440 for b in opts.get(b'bookmark', []):
5440 for b in opts.get(b'bookmark', []):
5441 b = repo._bookmarks.expandname(b)
5441 b = repo._bookmarks.expandname(b)
5442 if b not in remotebookmarks:
5442 if b not in remotebookmarks:
5443 raise error.Abort(_(b'remote bookmark %s not found!') % b)
5443 raise error.Abort(_(b'remote bookmark %s not found!') % b)
5444 nodes.append(remotebookmarks[b])
5444 nodes.append(remotebookmarks[b])
5445 for i, rev in enumerate(revs):
5445 for i, rev in enumerate(revs):
5446 node = fnodes[i].result()
5446 node = fnodes[i].result()
5447 nodes.append(node)
5447 nodes.append(node)
5448 if rev == checkout:
5448 if rev == checkout:
5449 checkout = node
5449 checkout = node
5450
5450
5451 wlock = util.nullcontextmanager()
5451 wlock = util.nullcontextmanager()
5452 if opts.get(b'update'):
5452 if opts.get(b'update'):
5453 wlock = repo.wlock()
5453 wlock = repo.wlock()
5454 with wlock:
5454 with wlock:
5455 pullopargs.update(opts.get(b'opargs', {}))
5455 pullopargs.update(opts.get(b'opargs', {}))
5456 modheads = exchange.pull(
5456 modheads = exchange.pull(
5457 repo,
5457 repo,
5458 other,
5458 other,
5459 heads=nodes,
5459 heads=nodes,
5460 force=opts.get(b'force'),
5460 force=opts.get(b'force'),
5461 bookmarks=opts.get(b'bookmark', ()),
5461 bookmarks=opts.get(b'bookmark', ()),
5462 opargs=pullopargs,
5462 opargs=pullopargs,
5463 ).cgresult
5463 ).cgresult
5464
5464
5465 # brev is a name, which might be a bookmark to be activated at
5465 # brev is a name, which might be a bookmark to be activated at
5466 # the end of the update. In other words, it is an explicit
5466 # the end of the update. In other words, it is an explicit
5467 # destination of the update
5467 # destination of the update
5468 brev = None
5468 brev = None
5469
5469
5470 if checkout:
5470 if checkout:
5471 checkout = repo.unfiltered().changelog.rev(checkout)
5471 checkout = repo.unfiltered().changelog.rev(checkout)
5472
5472
5473 # order below depends on implementation of
5473 # order below depends on implementation of
5474 # hg.addbranchrevs(). opts['bookmark'] is ignored,
5474 # hg.addbranchrevs(). opts['bookmark'] is ignored,
5475 # because 'checkout' is determined without it.
5475 # because 'checkout' is determined without it.
5476 if opts.get(b'rev'):
5476 if opts.get(b'rev'):
5477 brev = opts[b'rev'][0]
5477 brev = opts[b'rev'][0]
5478 elif opts.get(b'branch'):
5478 elif opts.get(b'branch'):
5479 brev = opts[b'branch'][0]
5479 brev = opts[b'branch'][0]
5480 else:
5480 else:
5481 brev = branches[0]
5481 brev = branches[0]
5482 repo._subtoppath = source
5482 repo._subtoppath = source
5483 try:
5483 try:
5484 ret = postincoming(
5484 ret = postincoming(
5485 ui, repo, modheads, opts.get(b'update'), checkout, brev
5485 ui, repo, modheads, opts.get(b'update'), checkout, brev
5486 )
5486 )
5487 except error.FilteredRepoLookupError as exc:
5487 except error.FilteredRepoLookupError as exc:
5488 msg = _(b'cannot update to target: %s') % exc.args[0]
5488 msg = _(b'cannot update to target: %s') % exc.args[0]
5489 exc.args = (msg,) + exc.args[1:]
5489 exc.args = (msg,) + exc.args[1:]
5490 raise
5490 raise
5491 finally:
5491 finally:
5492 del repo._subtoppath
5492 del repo._subtoppath
5493
5493
5494 finally:
5494 finally:
5495 other.close()
5495 other.close()
5496 return ret
5496 return ret
5497
5497
5498
5498
5499 @command(
5499 @command(
5500 b'push',
5500 b'push',
5501 [
5501 [
5502 (b'f', b'force', None, _(b'force push')),
5502 (b'f', b'force', None, _(b'force push')),
5503 (
5503 (
5504 b'r',
5504 b'r',
5505 b'rev',
5505 b'rev',
5506 [],
5506 [],
5507 _(b'a changeset intended to be included in the destination'),
5507 _(b'a changeset intended to be included in the destination'),
5508 _(b'REV'),
5508 _(b'REV'),
5509 ),
5509 ),
5510 (b'B', b'bookmark', [], _(b"bookmark to push"), _(b'BOOKMARK')),
5510 (b'B', b'bookmark', [], _(b"bookmark to push"), _(b'BOOKMARK')),
5511 (
5511 (
5512 b'b',
5512 b'b',
5513 b'branch',
5513 b'branch',
5514 [],
5514 [],
5515 _(b'a specific branch you would like to push'),
5515 _(b'a specific branch you would like to push'),
5516 _(b'BRANCH'),
5516 _(b'BRANCH'),
5517 ),
5517 ),
5518 (b'', b'new-branch', False, _(b'allow pushing a new branch')),
5518 (b'', b'new-branch', False, _(b'allow pushing a new branch')),
5519 (
5519 (
5520 b'',
5520 b'',
5521 b'pushvars',
5521 b'pushvars',
5522 [],
5522 [],
5523 _(b'variables that can be sent to server (ADVANCED)'),
5523 _(b'variables that can be sent to server (ADVANCED)'),
5524 ),
5524 ),
5525 (
5525 (
5526 b'',
5526 b'',
5527 b'publish',
5527 b'publish',
5528 False,
5528 False,
5529 _(b'push the changeset as public (EXPERIMENTAL)'),
5529 _(b'push the changeset as public (EXPERIMENTAL)'),
5530 ),
5530 ),
5531 ]
5531 ]
5532 + remoteopts,
5532 + remoteopts,
5533 _(b'[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'),
5533 _(b'[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'),
5534 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5534 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5535 helpbasic=True,
5535 helpbasic=True,
5536 )
5536 )
5537 def push(ui, repo, dest=None, **opts):
5537 def push(ui, repo, dest=None, **opts):
5538 """push changes to the specified destination
5538 """push changes to the specified destination
5539
5539
5540 Push changesets from the local repository to the specified
5540 Push changesets from the local repository to the specified
5541 destination.
5541 destination.
5542
5542
5543 This operation is symmetrical to pull: it is identical to a pull
5543 This operation is symmetrical to pull: it is identical to a pull
5544 in the destination repository from the current one.
5544 in the destination repository from the current one.
5545
5545
5546 By default, push will not allow creation of new heads at the
5546 By default, push will not allow creation of new heads at the
5547 destination, since multiple heads would make it unclear which head
5547 destination, since multiple heads would make it unclear which head
5548 to use. In this situation, it is recommended to pull and merge
5548 to use. In this situation, it is recommended to pull and merge
5549 before pushing.
5549 before pushing.
5550
5550
5551 Use --new-branch if you want to allow push to create a new named
5551 Use --new-branch if you want to allow push to create a new named
5552 branch that is not present at the destination. This allows you to
5552 branch that is not present at the destination. This allows you to
5553 only create a new branch without forcing other changes.
5553 only create a new branch without forcing other changes.
5554
5554
5555 .. note::
5555 .. note::
5556
5556
5557 Extra care should be taken with the -f/--force option,
5557 Extra care should be taken with the -f/--force option,
5558 which will push all new heads on all branches, an action which will
5558 which will push all new heads on all branches, an action which will
5559 almost always cause confusion for collaborators.
5559 almost always cause confusion for collaborators.
5560
5560
5561 If -r/--rev is used, the specified revision and all its ancestors
5561 If -r/--rev is used, the specified revision and all its ancestors
5562 will be pushed to the remote repository.
5562 will be pushed to the remote repository.
5563
5563
5564 If -B/--bookmark is used, the specified bookmarked revision, its
5564 If -B/--bookmark is used, the specified bookmarked revision, its
5565 ancestors, and the bookmark will be pushed to the remote
5565 ancestors, and the bookmark will be pushed to the remote
5566 repository. Specifying ``.`` is equivalent to specifying the active
5566 repository. Specifying ``.`` is equivalent to specifying the active
5567 bookmark's name.
5567 bookmark's name.
5568
5568
5569 Please see :hg:`help urls` for important details about ``ssh://``
5569 Please see :hg:`help urls` for important details about ``ssh://``
5570 URLs. If DESTINATION is omitted, a default path will be used.
5570 URLs. If DESTINATION is omitted, a default path will be used.
5571
5571
5572 .. container:: verbose
5572 .. container:: verbose
5573
5573
5574 The --pushvars option sends strings to the server that become
5574 The --pushvars option sends strings to the server that become
5575 environment variables prepended with ``HG_USERVAR_``. For example,
5575 environment variables prepended with ``HG_USERVAR_``. For example,
5576 ``--pushvars ENABLE_FEATURE=true``, provides the server side hooks with
5576 ``--pushvars ENABLE_FEATURE=true``, provides the server side hooks with
5577 ``HG_USERVAR_ENABLE_FEATURE=true`` as part of their environment.
5577 ``HG_USERVAR_ENABLE_FEATURE=true`` as part of their environment.
5578
5578
5579 pushvars can provide for user-overridable hooks as well as set debug
5579 pushvars can provide for user-overridable hooks as well as set debug
5580 levels. One example is having a hook that blocks commits containing
5580 levels. One example is having a hook that blocks commits containing
5581 conflict markers, but enables the user to override the hook if the file
5581 conflict markers, but enables the user to override the hook if the file
5582 is using conflict markers for testing purposes or the file format has
5582 is using conflict markers for testing purposes or the file format has
5583 strings that look like conflict markers.
5583 strings that look like conflict markers.
5584
5584
5585 By default, servers will ignore `--pushvars`. To enable it add the
5585 By default, servers will ignore `--pushvars`. To enable it add the
5586 following to your configuration file::
5586 following to your configuration file::
5587
5587
5588 [push]
5588 [push]
5589 pushvars.server = true
5589 pushvars.server = true
5590
5590
5591 Returns 0 if push was successful, 1 if nothing to push.
5591 Returns 0 if push was successful, 1 if nothing to push.
5592 """
5592 """
5593
5593
5594 opts = pycompat.byteskwargs(opts)
5594 opts = pycompat.byteskwargs(opts)
5595 if opts.get(b'bookmark'):
5595 if opts.get(b'bookmark'):
5596 ui.setconfig(b'bookmarks', b'pushing', opts[b'bookmark'], b'push')
5596 ui.setconfig(b'bookmarks', b'pushing', opts[b'bookmark'], b'push')
5597 for b in opts[b'bookmark']:
5597 for b in opts[b'bookmark']:
5598 # translate -B options to -r so changesets get pushed
5598 # translate -B options to -r so changesets get pushed
5599 b = repo._bookmarks.expandname(b)
5599 b = repo._bookmarks.expandname(b)
5600 if b in repo._bookmarks:
5600 if b in repo._bookmarks:
5601 opts.setdefault(b'rev', []).append(b)
5601 opts.setdefault(b'rev', []).append(b)
5602 else:
5602 else:
5603 # if we try to push a deleted bookmark, translate it to null
5603 # if we try to push a deleted bookmark, translate it to null
5604 # this lets simultaneous -r, -b options continue working
5604 # this lets simultaneous -r, -b options continue working
5605 opts.setdefault(b'rev', []).append(b"null")
5605 opts.setdefault(b'rev', []).append(b"null")
5606
5606
5607 path = ui.paths.getpath(dest, default=(b'default-push', b'default'))
5607 path = ui.paths.getpath(dest, default=(b'default-push', b'default'))
5608 if not path:
5608 if not path:
5609 raise error.Abort(
5609 raise error.Abort(
5610 _(b'default repository not configured!'),
5610 _(b'default repository not configured!'),
5611 hint=_(b"see 'hg help config.paths'"),
5611 hint=_(b"see 'hg help config.paths'"),
5612 )
5612 )
5613 dest = path.pushloc or path.loc
5613 dest = path.pushloc or path.loc
5614 branches = (path.branch, opts.get(b'branch') or [])
5614 branches = (path.branch, opts.get(b'branch') or [])
5615 ui.status(_(b'pushing to %s\n') % util.hidepassword(dest))
5615 ui.status(_(b'pushing to %s\n') % util.hidepassword(dest))
5616 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get(b'rev'))
5616 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get(b'rev'))
5617 other = hg.peer(repo, opts, dest)
5617 other = hg.peer(repo, opts, dest)
5618
5618
5619 if revs:
5619 if revs:
5620 revs = [repo[r].node() for r in scmutil.revrange(repo, revs)]
5620 revs = [repo[r].node() for r in scmutil.revrange(repo, revs)]
5621 if not revs:
5621 if not revs:
5622 raise error.Abort(
5622 raise error.Abort(
5623 _(b"specified revisions evaluate to an empty set"),
5623 _(b"specified revisions evaluate to an empty set"),
5624 hint=_(b"use different revision arguments"),
5624 hint=_(b"use different revision arguments"),
5625 )
5625 )
5626 elif path.pushrev:
5626 elif path.pushrev:
5627 # It doesn't make any sense to specify ancestor revisions. So limit
5627 # It doesn't make any sense to specify ancestor revisions. So limit
5628 # to DAG heads to make discovery simpler.
5628 # to DAG heads to make discovery simpler.
5629 expr = revsetlang.formatspec(b'heads(%r)', path.pushrev)
5629 expr = revsetlang.formatspec(b'heads(%r)', path.pushrev)
5630 revs = scmutil.revrange(repo, [expr])
5630 revs = scmutil.revrange(repo, [expr])
5631 revs = [repo[rev].node() for rev in revs]
5631 revs = [repo[rev].node() for rev in revs]
5632 if not revs:
5632 if not revs:
5633 raise error.Abort(
5633 raise error.Abort(
5634 _(b'default push revset for path evaluates to an empty set')
5634 _(b'default push revset for path evaluates to an empty set')
5635 )
5635 )
5636 elif ui.configbool(b'commands', b'push.require-revs'):
5636 elif ui.configbool(b'commands', b'push.require-revs'):
5637 raise error.Abort(
5637 raise error.Abort(
5638 _(b'no revisions specified to push'),
5638 _(b'no revisions specified to push'),
5639 hint=_(b'did you mean "hg push -r ."?'),
5639 hint=_(b'did you mean "hg push -r ."?'),
5640 )
5640 )
5641
5641
5642 repo._subtoppath = dest
5642 repo._subtoppath = dest
5643 try:
5643 try:
5644 # push subrepos depth-first for coherent ordering
5644 # push subrepos depth-first for coherent ordering
5645 c = repo[b'.']
5645 c = repo[b'.']
5646 subs = c.substate # only repos that are committed
5646 subs = c.substate # only repos that are committed
5647 for s in sorted(subs):
5647 for s in sorted(subs):
5648 result = c.sub(s).push(opts)
5648 result = c.sub(s).push(opts)
5649 if result == 0:
5649 if result == 0:
5650 return not result
5650 return not result
5651 finally:
5651 finally:
5652 del repo._subtoppath
5652 del repo._subtoppath
5653
5653
5654 opargs = dict(opts.get(b'opargs', {})) # copy opargs since we may mutate it
5654 opargs = dict(opts.get(b'opargs', {})) # copy opargs since we may mutate it
5655 opargs.setdefault(b'pushvars', []).extend(opts.get(b'pushvars', []))
5655 opargs.setdefault(b'pushvars', []).extend(opts.get(b'pushvars', []))
5656
5656
5657 pushop = exchange.push(
5657 pushop = exchange.push(
5658 repo,
5658 repo,
5659 other,
5659 other,
5660 opts.get(b'force'),
5660 opts.get(b'force'),
5661 revs=revs,
5661 revs=revs,
5662 newbranch=opts.get(b'new_branch'),
5662 newbranch=opts.get(b'new_branch'),
5663 bookmarks=opts.get(b'bookmark', ()),
5663 bookmarks=opts.get(b'bookmark', ()),
5664 publish=opts.get(b'publish'),
5664 publish=opts.get(b'publish'),
5665 opargs=opargs,
5665 opargs=opargs,
5666 )
5666 )
5667
5667
5668 result = not pushop.cgresult
5668 result = not pushop.cgresult
5669
5669
5670 if pushop.bkresult is not None:
5670 if pushop.bkresult is not None:
5671 if pushop.bkresult == 2:
5671 if pushop.bkresult == 2:
5672 result = 2
5672 result = 2
5673 elif not result and pushop.bkresult:
5673 elif not result and pushop.bkresult:
5674 result = 2
5674 result = 2
5675
5675
5676 return result
5676 return result
5677
5677
5678
5678
5679 @command(
5679 @command(
5680 b'recover',
5680 b'recover',
5681 [(b'', b'verify', False, b"run `hg verify` after successful recover"),],
5681 [(b'', b'verify', False, b"run `hg verify` after successful recover"),],
5682 helpcategory=command.CATEGORY_MAINTENANCE,
5682 helpcategory=command.CATEGORY_MAINTENANCE,
5683 )
5683 )
5684 def recover(ui, repo, **opts):
5684 def recover(ui, repo, **opts):
5685 """roll back an interrupted transaction
5685 """roll back an interrupted transaction
5686
5686
5687 Recover from an interrupted commit or pull.
5687 Recover from an interrupted commit or pull.
5688
5688
5689 This command tries to fix the repository status after an
5689 This command tries to fix the repository status after an
5690 interrupted operation. It should only be necessary when Mercurial
5690 interrupted operation. It should only be necessary when Mercurial
5691 suggests it.
5691 suggests it.
5692
5692
5693 Returns 0 if successful, 1 if nothing to recover or verify fails.
5693 Returns 0 if successful, 1 if nothing to recover or verify fails.
5694 """
5694 """
5695 ret = repo.recover()
5695 ret = repo.recover()
5696 if ret:
5696 if ret:
5697 if opts['verify']:
5697 if opts['verify']:
5698 return hg.verify(repo)
5698 return hg.verify(repo)
5699 else:
5699 else:
5700 msg = _(
5700 msg = _(
5701 b"(verify step skipped, run `hg verify` to check your "
5701 b"(verify step skipped, run `hg verify` to check your "
5702 b"repository content)\n"
5702 b"repository content)\n"
5703 )
5703 )
5704 ui.warn(msg)
5704 ui.warn(msg)
5705 return 0
5705 return 0
5706 return 1
5706 return 1
5707
5707
5708
5708
5709 @command(
5709 @command(
5710 b'remove|rm',
5710 b'remove|rm',
5711 [
5711 [
5712 (b'A', b'after', None, _(b'record delete for missing files')),
5712 (b'A', b'after', None, _(b'record delete for missing files')),
5713 (b'f', b'force', None, _(b'forget added files, delete modified files')),
5713 (b'f', b'force', None, _(b'forget added files, delete modified files')),
5714 ]
5714 ]
5715 + subrepoopts
5715 + subrepoopts
5716 + walkopts
5716 + walkopts
5717 + dryrunopts,
5717 + dryrunopts,
5718 _(b'[OPTION]... FILE...'),
5718 _(b'[OPTION]... FILE...'),
5719 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5719 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5720 helpbasic=True,
5720 helpbasic=True,
5721 inferrepo=True,
5721 inferrepo=True,
5722 )
5722 )
5723 def remove(ui, repo, *pats, **opts):
5723 def remove(ui, repo, *pats, **opts):
5724 """remove the specified files on the next commit
5724 """remove the specified files on the next commit
5725
5725
5726 Schedule the indicated files for removal from the current branch.
5726 Schedule the indicated files for removal from the current branch.
5727
5727
5728 This command schedules the files to be removed at the next commit.
5728 This command schedules the files to be removed at the next commit.
5729 To undo a remove before that, see :hg:`revert`. To undo added
5729 To undo a remove before that, see :hg:`revert`. To undo added
5730 files, see :hg:`forget`.
5730 files, see :hg:`forget`.
5731
5731
5732 .. container:: verbose
5732 .. container:: verbose
5733
5733
5734 -A/--after can be used to remove only files that have already
5734 -A/--after can be used to remove only files that have already
5735 been deleted, -f/--force can be used to force deletion, and -Af
5735 been deleted, -f/--force can be used to force deletion, and -Af
5736 can be used to remove files from the next revision without
5736 can be used to remove files from the next revision without
5737 deleting them from the working directory.
5737 deleting them from the working directory.
5738
5738
5739 The following table details the behavior of remove for different
5739 The following table details the behavior of remove for different
5740 file states (columns) and option combinations (rows). The file
5740 file states (columns) and option combinations (rows). The file
5741 states are Added [A], Clean [C], Modified [M] and Missing [!]
5741 states are Added [A], Clean [C], Modified [M] and Missing [!]
5742 (as reported by :hg:`status`). The actions are Warn, Remove
5742 (as reported by :hg:`status`). The actions are Warn, Remove
5743 (from branch) and Delete (from disk):
5743 (from branch) and Delete (from disk):
5744
5744
5745 ========= == == == ==
5745 ========= == == == ==
5746 opt/state A C M !
5746 opt/state A C M !
5747 ========= == == == ==
5747 ========= == == == ==
5748 none W RD W R
5748 none W RD W R
5749 -f R RD RD R
5749 -f R RD RD R
5750 -A W W W R
5750 -A W W W R
5751 -Af R R R R
5751 -Af R R R R
5752 ========= == == == ==
5752 ========= == == == ==
5753
5753
5754 .. note::
5754 .. note::
5755
5755
5756 :hg:`remove` never deletes files in Added [A] state from the
5756 :hg:`remove` never deletes files in Added [A] state from the
5757 working directory, not even if ``--force`` is specified.
5757 working directory, not even if ``--force`` is specified.
5758
5758
5759 Returns 0 on success, 1 if any warnings encountered.
5759 Returns 0 on success, 1 if any warnings encountered.
5760 """
5760 """
5761
5761
5762 opts = pycompat.byteskwargs(opts)
5762 opts = pycompat.byteskwargs(opts)
5763 after, force = opts.get(b'after'), opts.get(b'force')
5763 after, force = opts.get(b'after'), opts.get(b'force')
5764 dryrun = opts.get(b'dry_run')
5764 dryrun = opts.get(b'dry_run')
5765 if not pats and not after:
5765 if not pats and not after:
5766 raise error.Abort(_(b'no files specified'))
5766 raise error.Abort(_(b'no files specified'))
5767
5767
5768 m = scmutil.match(repo[None], pats, opts)
5768 m = scmutil.match(repo[None], pats, opts)
5769 subrepos = opts.get(b'subrepos')
5769 subrepos = opts.get(b'subrepos')
5770 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
5770 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
5771 return cmdutil.remove(
5771 return cmdutil.remove(
5772 ui, repo, m, b"", uipathfn, after, force, subrepos, dryrun=dryrun
5772 ui, repo, m, b"", uipathfn, after, force, subrepos, dryrun=dryrun
5773 )
5773 )
5774
5774
5775
5775
5776 @command(
5776 @command(
5777 b'rename|move|mv',
5777 b'rename|move|mv',
5778 [
5778 [
5779 (b'A', b'after', None, _(b'record a rename that has already occurred')),
5779 (b'A', b'after', None, _(b'record a rename that has already occurred')),
5780 (
5780 (
5781 b'f',
5781 b'f',
5782 b'force',
5782 b'force',
5783 None,
5783 None,
5784 _(b'forcibly move over an existing managed file'),
5784 _(b'forcibly move over an existing managed file'),
5785 ),
5785 ),
5786 ]
5786 ]
5787 + walkopts
5787 + walkopts
5788 + dryrunopts,
5788 + dryrunopts,
5789 _(b'[OPTION]... SOURCE... DEST'),
5789 _(b'[OPTION]... SOURCE... DEST'),
5790 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5790 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5791 )
5791 )
5792 def rename(ui, repo, *pats, **opts):
5792 def rename(ui, repo, *pats, **opts):
5793 """rename files; equivalent of copy + remove
5793 """rename files; equivalent of copy + remove
5794
5794
5795 Mark dest as copies of sources; mark sources for deletion. If dest
5795 Mark dest as copies of sources; mark sources for deletion. If dest
5796 is a directory, copies are put in that directory. If dest is a
5796 is a directory, copies are put in that directory. If dest is a
5797 file, there can only be one source.
5797 file, there can only be one source.
5798
5798
5799 By default, this command copies the contents of files as they
5799 By default, this command copies the contents of files as they
5800 exist in the working directory. If invoked with -A/--after, the
5800 exist in the working directory. If invoked with -A/--after, the
5801 operation is recorded, but no copying is performed.
5801 operation is recorded, but no copying is performed.
5802
5802
5803 This command takes effect at the next commit. To undo a rename
5803 This command takes effect at the next commit. To undo a rename
5804 before that, see :hg:`revert`.
5804 before that, see :hg:`revert`.
5805
5805
5806 Returns 0 on success, 1 if errors are encountered.
5806 Returns 0 on success, 1 if errors are encountered.
5807 """
5807 """
5808 opts = pycompat.byteskwargs(opts)
5808 opts = pycompat.byteskwargs(opts)
5809 with repo.wlock(False):
5809 with repo.wlock(False):
5810 return cmdutil.copy(ui, repo, pats, opts, rename=True)
5810 return cmdutil.copy(ui, repo, pats, opts, rename=True)
5811
5811
5812
5812
5813 @command(
5813 @command(
5814 b'resolve',
5814 b'resolve',
5815 [
5815 [
5816 (b'a', b'all', None, _(b'select all unresolved files')),
5816 (b'a', b'all', None, _(b'select all unresolved files')),
5817 (b'l', b'list', None, _(b'list state of files needing merge')),
5817 (b'l', b'list', None, _(b'list state of files needing merge')),
5818 (b'm', b'mark', None, _(b'mark files as resolved')),
5818 (b'm', b'mark', None, _(b'mark files as resolved')),
5819 (b'u', b'unmark', None, _(b'mark files as unresolved')),
5819 (b'u', b'unmark', None, _(b'mark files as unresolved')),
5820 (b'n', b'no-status', None, _(b'hide status prefix')),
5820 (b'n', b'no-status', None, _(b'hide status prefix')),
5821 (b'', b're-merge', None, _(b're-merge files')),
5821 (b'', b're-merge', None, _(b're-merge files')),
5822 ]
5822 ]
5823 + mergetoolopts
5823 + mergetoolopts
5824 + walkopts
5824 + walkopts
5825 + formatteropts,
5825 + formatteropts,
5826 _(b'[OPTION]... [FILE]...'),
5826 _(b'[OPTION]... [FILE]...'),
5827 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5827 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5828 inferrepo=True,
5828 inferrepo=True,
5829 )
5829 )
5830 def resolve(ui, repo, *pats, **opts):
5830 def resolve(ui, repo, *pats, **opts):
5831 """redo merges or set/view the merge status of files
5831 """redo merges or set/view the merge status of files
5832
5832
5833 Merges with unresolved conflicts are often the result of
5833 Merges with unresolved conflicts are often the result of
5834 non-interactive merging using the ``internal:merge`` configuration
5834 non-interactive merging using the ``internal:merge`` configuration
5835 setting, or a command-line merge tool like ``diff3``. The resolve
5835 setting, or a command-line merge tool like ``diff3``. The resolve
5836 command is used to manage the files involved in a merge, after
5836 command is used to manage the files involved in a merge, after
5837 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
5837 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
5838 working directory must have two parents). See :hg:`help
5838 working directory must have two parents). See :hg:`help
5839 merge-tools` for information on configuring merge tools.
5839 merge-tools` for information on configuring merge tools.
5840
5840
5841 The resolve command can be used in the following ways:
5841 The resolve command can be used in the following ways:
5842
5842
5843 - :hg:`resolve [--re-merge] [--tool TOOL] FILE...`: attempt to re-merge
5843 - :hg:`resolve [--re-merge] [--tool TOOL] FILE...`: attempt to re-merge
5844 the specified files, discarding any previous merge attempts. Re-merging
5844 the specified files, discarding any previous merge attempts. Re-merging
5845 is not performed for files already marked as resolved. Use ``--all/-a``
5845 is not performed for files already marked as resolved. Use ``--all/-a``
5846 to select all unresolved files. ``--tool`` can be used to specify
5846 to select all unresolved files. ``--tool`` can be used to specify
5847 the merge tool used for the given files. It overrides the HGMERGE
5847 the merge tool used for the given files. It overrides the HGMERGE
5848 environment variable and your configuration files. Previous file
5848 environment variable and your configuration files. Previous file
5849 contents are saved with a ``.orig`` suffix.
5849 contents are saved with a ``.orig`` suffix.
5850
5850
5851 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
5851 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
5852 (e.g. after having manually fixed-up the files). The default is
5852 (e.g. after having manually fixed-up the files). The default is
5853 to mark all unresolved files.
5853 to mark all unresolved files.
5854
5854
5855 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
5855 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
5856 default is to mark all resolved files.
5856 default is to mark all resolved files.
5857
5857
5858 - :hg:`resolve -l`: list files which had or still have conflicts.
5858 - :hg:`resolve -l`: list files which had or still have conflicts.
5859 In the printed list, ``U`` = unresolved and ``R`` = resolved.
5859 In the printed list, ``U`` = unresolved and ``R`` = resolved.
5860 You can use ``set:unresolved()`` or ``set:resolved()`` to filter
5860 You can use ``set:unresolved()`` or ``set:resolved()`` to filter
5861 the list. See :hg:`help filesets` for details.
5861 the list. See :hg:`help filesets` for details.
5862
5862
5863 .. note::
5863 .. note::
5864
5864
5865 Mercurial will not let you commit files with unresolved merge
5865 Mercurial will not let you commit files with unresolved merge
5866 conflicts. You must use :hg:`resolve -m ...` before you can
5866 conflicts. You must use :hg:`resolve -m ...` before you can
5867 commit after a conflicting merge.
5867 commit after a conflicting merge.
5868
5868
5869 .. container:: verbose
5869 .. container:: verbose
5870
5870
5871 Template:
5871 Template:
5872
5872
5873 The following keywords are supported in addition to the common template
5873 The following keywords are supported in addition to the common template
5874 keywords and functions. See also :hg:`help templates`.
5874 keywords and functions. See also :hg:`help templates`.
5875
5875
5876 :mergestatus: String. Character denoting merge conflicts, ``U`` or ``R``.
5876 :mergestatus: String. Character denoting merge conflicts, ``U`` or ``R``.
5877 :path: String. Repository-absolute path of the file.
5877 :path: String. Repository-absolute path of the file.
5878
5878
5879 Returns 0 on success, 1 if any files fail a resolve attempt.
5879 Returns 0 on success, 1 if any files fail a resolve attempt.
5880 """
5880 """
5881
5881
5882 opts = pycompat.byteskwargs(opts)
5882 opts = pycompat.byteskwargs(opts)
5883 confirm = ui.configbool(b'commands', b'resolve.confirm')
5883 confirm = ui.configbool(b'commands', b'resolve.confirm')
5884 flaglist = b'all mark unmark list no_status re_merge'.split()
5884 flaglist = b'all mark unmark list no_status re_merge'.split()
5885 all, mark, unmark, show, nostatus, remerge = [opts.get(o) for o in flaglist]
5885 all, mark, unmark, show, nostatus, remerge = [opts.get(o) for o in flaglist]
5886
5886
5887 actioncount = len(list(filter(None, [show, mark, unmark, remerge])))
5887 actioncount = len(list(filter(None, [show, mark, unmark, remerge])))
5888 if actioncount > 1:
5888 if actioncount > 1:
5889 raise error.Abort(_(b"too many actions specified"))
5889 raise error.Abort(_(b"too many actions specified"))
5890 elif actioncount == 0 and ui.configbool(
5890 elif actioncount == 0 and ui.configbool(
5891 b'commands', b'resolve.explicit-re-merge'
5891 b'commands', b'resolve.explicit-re-merge'
5892 ):
5892 ):
5893 hint = _(b'use --mark, --unmark, --list or --re-merge')
5893 hint = _(b'use --mark, --unmark, --list or --re-merge')
5894 raise error.Abort(_(b'no action specified'), hint=hint)
5894 raise error.Abort(_(b'no action specified'), hint=hint)
5895 if pats and all:
5895 if pats and all:
5896 raise error.Abort(_(b"can't specify --all and patterns"))
5896 raise error.Abort(_(b"can't specify --all and patterns"))
5897 if not (all or pats or show or mark or unmark):
5897 if not (all or pats or show or mark or unmark):
5898 raise error.Abort(
5898 raise error.Abort(
5899 _(b'no files or directories specified'),
5899 _(b'no files or directories specified'),
5900 hint=b'use --all to re-merge all unresolved files',
5900 hint=b'use --all to re-merge all unresolved files',
5901 )
5901 )
5902
5902
5903 if confirm:
5903 if confirm:
5904 if all:
5904 if all:
5905 if ui.promptchoice(
5905 if ui.promptchoice(
5906 _(b're-merge all unresolved files (yn)?$$ &Yes $$ &No')
5906 _(b're-merge all unresolved files (yn)?$$ &Yes $$ &No')
5907 ):
5907 ):
5908 raise error.Abort(_(b'user quit'))
5908 raise error.Abort(_(b'user quit'))
5909 if mark and not pats:
5909 if mark and not pats:
5910 if ui.promptchoice(
5910 if ui.promptchoice(
5911 _(
5911 _(
5912 b'mark all unresolved files as resolved (yn)?'
5912 b'mark all unresolved files as resolved (yn)?'
5913 b'$$ &Yes $$ &No'
5913 b'$$ &Yes $$ &No'
5914 )
5914 )
5915 ):
5915 ):
5916 raise error.Abort(_(b'user quit'))
5916 raise error.Abort(_(b'user quit'))
5917 if unmark and not pats:
5917 if unmark and not pats:
5918 if ui.promptchoice(
5918 if ui.promptchoice(
5919 _(
5919 _(
5920 b'mark all resolved files as unresolved (yn)?'
5920 b'mark all resolved files as unresolved (yn)?'
5921 b'$$ &Yes $$ &No'
5921 b'$$ &Yes $$ &No'
5922 )
5922 )
5923 ):
5923 ):
5924 raise error.Abort(_(b'user quit'))
5924 raise error.Abort(_(b'user quit'))
5925
5925
5926 uipathfn = scmutil.getuipathfn(repo)
5926 uipathfn = scmutil.getuipathfn(repo)
5927
5927
5928 if show:
5928 if show:
5929 ui.pager(b'resolve')
5929 ui.pager(b'resolve')
5930 fm = ui.formatter(b'resolve', opts)
5930 fm = ui.formatter(b'resolve', opts)
5931 ms = mergemod.mergestate.read(repo)
5931 ms = mergemod.mergestate.read(repo)
5932 wctx = repo[None]
5932 wctx = repo[None]
5933 m = scmutil.match(wctx, pats, opts)
5933 m = scmutil.match(wctx, pats, opts)
5934
5934
5935 # Labels and keys based on merge state. Unresolved path conflicts show
5935 # Labels and keys based on merge state. Unresolved path conflicts show
5936 # as 'P'. Resolved path conflicts show as 'R', the same as normal
5936 # as 'P'. Resolved path conflicts show as 'R', the same as normal
5937 # resolved conflicts.
5937 # resolved conflicts.
5938 mergestateinfo = {
5938 mergestateinfo = {
5939 mergemod.MERGE_RECORD_UNRESOLVED: (b'resolve.unresolved', b'U'),
5939 mergemod.MERGE_RECORD_UNRESOLVED: (b'resolve.unresolved', b'U'),
5940 mergemod.MERGE_RECORD_RESOLVED: (b'resolve.resolved', b'R'),
5940 mergemod.MERGE_RECORD_RESOLVED: (b'resolve.resolved', b'R'),
5941 mergemod.MERGE_RECORD_UNRESOLVED_PATH: (
5941 mergemod.MERGE_RECORD_UNRESOLVED_PATH: (
5942 b'resolve.unresolved',
5942 b'resolve.unresolved',
5943 b'P',
5943 b'P',
5944 ),
5944 ),
5945 mergemod.MERGE_RECORD_RESOLVED_PATH: (b'resolve.resolved', b'R'),
5945 mergemod.MERGE_RECORD_RESOLVED_PATH: (b'resolve.resolved', b'R'),
5946 mergemod.MERGE_RECORD_DRIVER_RESOLVED: (
5946 mergemod.MERGE_RECORD_DRIVER_RESOLVED: (
5947 b'resolve.driverresolved',
5947 b'resolve.driverresolved',
5948 b'D',
5948 b'D',
5949 ),
5949 ),
5950 }
5950 }
5951
5951
5952 for f in ms:
5952 for f in ms:
5953 if not m(f):
5953 if not m(f):
5954 continue
5954 continue
5955
5955
5956 label, key = mergestateinfo[ms[f]]
5956 label, key = mergestateinfo[ms[f]]
5957 fm.startitem()
5957 fm.startitem()
5958 fm.context(ctx=wctx)
5958 fm.context(ctx=wctx)
5959 fm.condwrite(not nostatus, b'mergestatus', b'%s ', key, label=label)
5959 fm.condwrite(not nostatus, b'mergestatus', b'%s ', key, label=label)
5960 fm.data(path=f)
5960 fm.data(path=f)
5961 fm.plain(b'%s\n' % uipathfn(f), label=label)
5961 fm.plain(b'%s\n' % uipathfn(f), label=label)
5962 fm.end()
5962 fm.end()
5963 return 0
5963 return 0
5964
5964
5965 with repo.wlock():
5965 with repo.wlock():
5966 ms = mergemod.mergestate.read(repo)
5966 ms = mergemod.mergestate.read(repo)
5967
5967
5968 if not (ms.active() or repo.dirstate.p2() != nullid):
5968 if not (ms.active() or repo.dirstate.p2() != nullid):
5969 raise error.Abort(
5969 raise error.Abort(
5970 _(b'resolve command not applicable when not merging')
5970 _(b'resolve command not applicable when not merging')
5971 )
5971 )
5972
5972
5973 wctx = repo[None]
5973 wctx = repo[None]
5974
5974
5975 if (
5975 if (
5976 ms.mergedriver
5976 ms.mergedriver
5977 and ms.mdstate() == mergemod.MERGE_DRIVER_STATE_UNMARKED
5977 and ms.mdstate() == mergemod.MERGE_DRIVER_STATE_UNMARKED
5978 ):
5978 ):
5979 proceed = mergemod.driverpreprocess(repo, ms, wctx)
5979 proceed = mergemod.driverpreprocess(repo, ms, wctx)
5980 ms.commit()
5980 ms.commit()
5981 # allow mark and unmark to go through
5981 # allow mark and unmark to go through
5982 if not mark and not unmark and not proceed:
5982 if not mark and not unmark and not proceed:
5983 return 1
5983 return 1
5984
5984
5985 m = scmutil.match(wctx, pats, opts)
5985 m = scmutil.match(wctx, pats, opts)
5986 ret = 0
5986 ret = 0
5987 didwork = False
5987 didwork = False
5988 runconclude = False
5988 runconclude = False
5989
5989
5990 tocomplete = []
5990 tocomplete = []
5991 hasconflictmarkers = []
5991 hasconflictmarkers = []
5992 if mark:
5992 if mark:
5993 markcheck = ui.config(b'commands', b'resolve.mark-check')
5993 markcheck = ui.config(b'commands', b'resolve.mark-check')
5994 if markcheck not in [b'warn', b'abort']:
5994 if markcheck not in [b'warn', b'abort']:
5995 # Treat all invalid / unrecognized values as 'none'.
5995 # Treat all invalid / unrecognized values as 'none'.
5996 markcheck = False
5996 markcheck = False
5997 for f in ms:
5997 for f in ms:
5998 if not m(f):
5998 if not m(f):
5999 continue
5999 continue
6000
6000
6001 didwork = True
6001 didwork = True
6002
6002
6003 # don't let driver-resolved files be marked, and run the conclude
6003 # don't let driver-resolved files be marked, and run the conclude
6004 # step if asked to resolve
6004 # step if asked to resolve
6005 if ms[f] == mergemod.MERGE_RECORD_DRIVER_RESOLVED:
6005 if ms[f] == mergemod.MERGE_RECORD_DRIVER_RESOLVED:
6006 exact = m.exact(f)
6006 exact = m.exact(f)
6007 if mark:
6007 if mark:
6008 if exact:
6008 if exact:
6009 ui.warn(
6009 ui.warn(
6010 _(b'not marking %s as it is driver-resolved\n')
6010 _(b'not marking %s as it is driver-resolved\n')
6011 % uipathfn(f)
6011 % uipathfn(f)
6012 )
6012 )
6013 elif unmark:
6013 elif unmark:
6014 if exact:
6014 if exact:
6015 ui.warn(
6015 ui.warn(
6016 _(b'not unmarking %s as it is driver-resolved\n')
6016 _(b'not unmarking %s as it is driver-resolved\n')
6017 % uipathfn(f)
6017 % uipathfn(f)
6018 )
6018 )
6019 else:
6019 else:
6020 runconclude = True
6020 runconclude = True
6021 continue
6021 continue
6022
6022
6023 # path conflicts must be resolved manually
6023 # path conflicts must be resolved manually
6024 if ms[f] in (
6024 if ms[f] in (
6025 mergemod.MERGE_RECORD_UNRESOLVED_PATH,
6025 mergemod.MERGE_RECORD_UNRESOLVED_PATH,
6026 mergemod.MERGE_RECORD_RESOLVED_PATH,
6026 mergemod.MERGE_RECORD_RESOLVED_PATH,
6027 ):
6027 ):
6028 if mark:
6028 if mark:
6029 ms.mark(f, mergemod.MERGE_RECORD_RESOLVED_PATH)
6029 ms.mark(f, mergemod.MERGE_RECORD_RESOLVED_PATH)
6030 elif unmark:
6030 elif unmark:
6031 ms.mark(f, mergemod.MERGE_RECORD_UNRESOLVED_PATH)
6031 ms.mark(f, mergemod.MERGE_RECORD_UNRESOLVED_PATH)
6032 elif ms[f] == mergemod.MERGE_RECORD_UNRESOLVED_PATH:
6032 elif ms[f] == mergemod.MERGE_RECORD_UNRESOLVED_PATH:
6033 ui.warn(
6033 ui.warn(
6034 _(b'%s: path conflict must be resolved manually\n')
6034 _(b'%s: path conflict must be resolved manually\n')
6035 % uipathfn(f)
6035 % uipathfn(f)
6036 )
6036 )
6037 continue
6037 continue
6038
6038
6039 if mark:
6039 if mark:
6040 if markcheck:
6040 if markcheck:
6041 fdata = repo.wvfs.tryread(f)
6041 fdata = repo.wvfs.tryread(f)
6042 if (
6042 if (
6043 filemerge.hasconflictmarkers(fdata)
6043 filemerge.hasconflictmarkers(fdata)
6044 and ms[f] != mergemod.MERGE_RECORD_RESOLVED
6044 and ms[f] != mergemod.MERGE_RECORD_RESOLVED
6045 ):
6045 ):
6046 hasconflictmarkers.append(f)
6046 hasconflictmarkers.append(f)
6047 ms.mark(f, mergemod.MERGE_RECORD_RESOLVED)
6047 ms.mark(f, mergemod.MERGE_RECORD_RESOLVED)
6048 elif unmark:
6048 elif unmark:
6049 ms.mark(f, mergemod.MERGE_RECORD_UNRESOLVED)
6049 ms.mark(f, mergemod.MERGE_RECORD_UNRESOLVED)
6050 else:
6050 else:
6051 # backup pre-resolve (merge uses .orig for its own purposes)
6051 # backup pre-resolve (merge uses .orig for its own purposes)
6052 a = repo.wjoin(f)
6052 a = repo.wjoin(f)
6053 try:
6053 try:
6054 util.copyfile(a, a + b".resolve")
6054 util.copyfile(a, a + b".resolve")
6055 except (IOError, OSError) as inst:
6055 except (IOError, OSError) as inst:
6056 if inst.errno != errno.ENOENT:
6056 if inst.errno != errno.ENOENT:
6057 raise
6057 raise
6058
6058
6059 try:
6059 try:
6060 # preresolve file
6060 # preresolve file
6061 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
6061 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
6062 with ui.configoverride(overrides, b'resolve'):
6062 with ui.configoverride(overrides, b'resolve'):
6063 complete, r = ms.preresolve(f, wctx)
6063 complete, r = ms.preresolve(f, wctx)
6064 if not complete:
6064 if not complete:
6065 tocomplete.append(f)
6065 tocomplete.append(f)
6066 elif r:
6066 elif r:
6067 ret = 1
6067 ret = 1
6068 finally:
6068 finally:
6069 ms.commit()
6069 ms.commit()
6070
6070
6071 # replace filemerge's .orig file with our resolve file, but only
6071 # replace filemerge's .orig file with our resolve file, but only
6072 # for merges that are complete
6072 # for merges that are complete
6073 if complete:
6073 if complete:
6074 try:
6074 try:
6075 util.rename(
6075 util.rename(
6076 a + b".resolve", scmutil.backuppath(ui, repo, f)
6076 a + b".resolve", scmutil.backuppath(ui, repo, f)
6077 )
6077 )
6078 except OSError as inst:
6078 except OSError as inst:
6079 if inst.errno != errno.ENOENT:
6079 if inst.errno != errno.ENOENT:
6080 raise
6080 raise
6081
6081
6082 if hasconflictmarkers:
6082 if hasconflictmarkers:
6083 ui.warn(
6083 ui.warn(
6084 _(
6084 _(
6085 b'warning: the following files still have conflict '
6085 b'warning: the following files still have conflict '
6086 b'markers:\n'
6086 b'markers:\n'
6087 )
6087 )
6088 + b''.join(
6088 + b''.join(
6089 b' ' + uipathfn(f) + b'\n' for f in hasconflictmarkers
6089 b' ' + uipathfn(f) + b'\n' for f in hasconflictmarkers
6090 )
6090 )
6091 )
6091 )
6092 if markcheck == b'abort' and not all and not pats:
6092 if markcheck == b'abort' and not all and not pats:
6093 raise error.Abort(
6093 raise error.Abort(
6094 _(b'conflict markers detected'),
6094 _(b'conflict markers detected'),
6095 hint=_(b'use --all to mark anyway'),
6095 hint=_(b'use --all to mark anyway'),
6096 )
6096 )
6097
6097
6098 for f in tocomplete:
6098 for f in tocomplete:
6099 try:
6099 try:
6100 # resolve file
6100 # resolve file
6101 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
6101 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
6102 with ui.configoverride(overrides, b'resolve'):
6102 with ui.configoverride(overrides, b'resolve'):
6103 r = ms.resolve(f, wctx)
6103 r = ms.resolve(f, wctx)
6104 if r:
6104 if r:
6105 ret = 1
6105 ret = 1
6106 finally:
6106 finally:
6107 ms.commit()
6107 ms.commit()
6108
6108
6109 # replace filemerge's .orig file with our resolve file
6109 # replace filemerge's .orig file with our resolve file
6110 a = repo.wjoin(f)
6110 a = repo.wjoin(f)
6111 try:
6111 try:
6112 util.rename(a + b".resolve", scmutil.backuppath(ui, repo, f))
6112 util.rename(a + b".resolve", scmutil.backuppath(ui, repo, f))
6113 except OSError as inst:
6113 except OSError as inst:
6114 if inst.errno != errno.ENOENT:
6114 if inst.errno != errno.ENOENT:
6115 raise
6115 raise
6116
6116
6117 ms.commit()
6117 ms.commit()
6118 ms.recordactions()
6118 ms.recordactions()
6119
6119
6120 if not didwork and pats:
6120 if not didwork and pats:
6121 hint = None
6121 hint = None
6122 if not any([p for p in pats if p.find(b':') >= 0]):
6122 if not any([p for p in pats if p.find(b':') >= 0]):
6123 pats = [b'path:%s' % p for p in pats]
6123 pats = [b'path:%s' % p for p in pats]
6124 m = scmutil.match(wctx, pats, opts)
6124 m = scmutil.match(wctx, pats, opts)
6125 for f in ms:
6125 for f in ms:
6126 if not m(f):
6126 if not m(f):
6127 continue
6127 continue
6128
6128
6129 def flag(o):
6129 def flag(o):
6130 if o == b're_merge':
6130 if o == b're_merge':
6131 return b'--re-merge '
6131 return b'--re-merge '
6132 return b'-%s ' % o[0:1]
6132 return b'-%s ' % o[0:1]
6133
6133
6134 flags = b''.join([flag(o) for o in flaglist if opts.get(o)])
6134 flags = b''.join([flag(o) for o in flaglist if opts.get(o)])
6135 hint = _(b"(try: hg resolve %s%s)\n") % (
6135 hint = _(b"(try: hg resolve %s%s)\n") % (
6136 flags,
6136 flags,
6137 b' '.join(pats),
6137 b' '.join(pats),
6138 )
6138 )
6139 break
6139 break
6140 ui.warn(_(b"arguments do not match paths that need resolving\n"))
6140 ui.warn(_(b"arguments do not match paths that need resolving\n"))
6141 if hint:
6141 if hint:
6142 ui.warn(hint)
6142 ui.warn(hint)
6143 elif ms.mergedriver and ms.mdstate() != b's':
6143 elif ms.mergedriver and ms.mdstate() != b's':
6144 # run conclude step when either a driver-resolved file is requested
6144 # run conclude step when either a driver-resolved file is requested
6145 # or there are no driver-resolved files
6145 # or there are no driver-resolved files
6146 # we can't use 'ret' to determine whether any files are unresolved
6146 # we can't use 'ret' to determine whether any files are unresolved
6147 # because we might not have tried to resolve some
6147 # because we might not have tried to resolve some
6148 if (runconclude or not list(ms.driverresolved())) and not list(
6148 if (runconclude or not list(ms.driverresolved())) and not list(
6149 ms.unresolved()
6149 ms.unresolved()
6150 ):
6150 ):
6151 proceed = mergemod.driverconclude(repo, ms, wctx)
6151 proceed = mergemod.driverconclude(repo, ms, wctx)
6152 ms.commit()
6152 ms.commit()
6153 if not proceed:
6153 if not proceed:
6154 return 1
6154 return 1
6155
6155
6156 # Nudge users into finishing an unfinished operation
6156 # Nudge users into finishing an unfinished operation
6157 unresolvedf = list(ms.unresolved())
6157 unresolvedf = list(ms.unresolved())
6158 driverresolvedf = list(ms.driverresolved())
6158 driverresolvedf = list(ms.driverresolved())
6159 if not unresolvedf and not driverresolvedf:
6159 if not unresolvedf and not driverresolvedf:
6160 ui.status(_(b'(no more unresolved files)\n'))
6160 ui.status(_(b'(no more unresolved files)\n'))
6161 cmdutil.checkafterresolved(repo)
6161 cmdutil.checkafterresolved(repo)
6162 elif not unresolvedf:
6162 elif not unresolvedf:
6163 ui.status(
6163 ui.status(
6164 _(
6164 _(
6165 b'(no more unresolved files -- '
6165 b'(no more unresolved files -- '
6166 b'run "hg resolve --all" to conclude)\n'
6166 b'run "hg resolve --all" to conclude)\n'
6167 )
6167 )
6168 )
6168 )
6169
6169
6170 return ret
6170 return ret
6171
6171
6172
6172
6173 @command(
6173 @command(
6174 b'revert',
6174 b'revert',
6175 [
6175 [
6176 (b'a', b'all', None, _(b'revert all changes when no arguments given')),
6176 (b'a', b'all', None, _(b'revert all changes when no arguments given')),
6177 (b'd', b'date', b'', _(b'tipmost revision matching date'), _(b'DATE')),
6177 (b'd', b'date', b'', _(b'tipmost revision matching date'), _(b'DATE')),
6178 (b'r', b'rev', b'', _(b'revert to the specified revision'), _(b'REV')),
6178 (b'r', b'rev', b'', _(b'revert to the specified revision'), _(b'REV')),
6179 (b'C', b'no-backup', None, _(b'do not save backup copies of files')),
6179 (b'C', b'no-backup', None, _(b'do not save backup copies of files')),
6180 (b'i', b'interactive', None, _(b'interactively select the changes')),
6180 (b'i', b'interactive', None, _(b'interactively select the changes')),
6181 ]
6181 ]
6182 + walkopts
6182 + walkopts
6183 + dryrunopts,
6183 + dryrunopts,
6184 _(b'[OPTION]... [-r REV] [NAME]...'),
6184 _(b'[OPTION]... [-r REV] [NAME]...'),
6185 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6185 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6186 )
6186 )
6187 def revert(ui, repo, *pats, **opts):
6187 def revert(ui, repo, *pats, **opts):
6188 """restore files to their checkout state
6188 """restore files to their checkout state
6189
6189
6190 .. note::
6190 .. note::
6191
6191
6192 To check out earlier revisions, you should use :hg:`update REV`.
6192 To check out earlier revisions, you should use :hg:`update REV`.
6193 To cancel an uncommitted merge (and lose your changes),
6193 To cancel an uncommitted merge (and lose your changes),
6194 use :hg:`merge --abort`.
6194 use :hg:`merge --abort`.
6195
6195
6196 With no revision specified, revert the specified files or directories
6196 With no revision specified, revert the specified files or directories
6197 to the contents they had in the parent of the working directory.
6197 to the contents they had in the parent of the working directory.
6198 This restores the contents of files to an unmodified
6198 This restores the contents of files to an unmodified
6199 state and unschedules adds, removes, copies, and renames. If the
6199 state and unschedules adds, removes, copies, and renames. If the
6200 working directory has two parents, you must explicitly specify a
6200 working directory has two parents, you must explicitly specify a
6201 revision.
6201 revision.
6202
6202
6203 Using the -r/--rev or -d/--date options, revert the given files or
6203 Using the -r/--rev or -d/--date options, revert the given files or
6204 directories to their states as of a specific revision. Because
6204 directories to their states as of a specific revision. Because
6205 revert does not change the working directory parents, this will
6205 revert does not change the working directory parents, this will
6206 cause these files to appear modified. This can be helpful to "back
6206 cause these files to appear modified. This can be helpful to "back
6207 out" some or all of an earlier change. See :hg:`backout` for a
6207 out" some or all of an earlier change. See :hg:`backout` for a
6208 related method.
6208 related method.
6209
6209
6210 Modified files are saved with a .orig suffix before reverting.
6210 Modified files are saved with a .orig suffix before reverting.
6211 To disable these backups, use --no-backup. It is possible to store
6211 To disable these backups, use --no-backup. It is possible to store
6212 the backup files in a custom directory relative to the root of the
6212 the backup files in a custom directory relative to the root of the
6213 repository by setting the ``ui.origbackuppath`` configuration
6213 repository by setting the ``ui.origbackuppath`` configuration
6214 option.
6214 option.
6215
6215
6216 See :hg:`help dates` for a list of formats valid for -d/--date.
6216 See :hg:`help dates` for a list of formats valid for -d/--date.
6217
6217
6218 See :hg:`help backout` for a way to reverse the effect of an
6218 See :hg:`help backout` for a way to reverse the effect of an
6219 earlier changeset.
6219 earlier changeset.
6220
6220
6221 Returns 0 on success.
6221 Returns 0 on success.
6222 """
6222 """
6223
6223
6224 opts = pycompat.byteskwargs(opts)
6224 opts = pycompat.byteskwargs(opts)
6225 if opts.get(b"date"):
6225 if opts.get(b"date"):
6226 if opts.get(b"rev"):
6226 if opts.get(b"rev"):
6227 raise error.Abort(_(b"you can't specify a revision and a date"))
6227 raise error.Abort(_(b"you can't specify a revision and a date"))
6228 opts[b"rev"] = cmdutil.finddate(ui, repo, opts[b"date"])
6228 opts[b"rev"] = cmdutil.finddate(ui, repo, opts[b"date"])
6229
6229
6230 parent, p2 = repo.dirstate.parents()
6230 parent, p2 = repo.dirstate.parents()
6231 if not opts.get(b'rev') and p2 != nullid:
6231 if not opts.get(b'rev') and p2 != nullid:
6232 # revert after merge is a trap for new users (issue2915)
6232 # revert after merge is a trap for new users (issue2915)
6233 raise error.Abort(
6233 raise error.Abort(
6234 _(b'uncommitted merge with no revision specified'),
6234 _(b'uncommitted merge with no revision specified'),
6235 hint=_(b"use 'hg update' or see 'hg help revert'"),
6235 hint=_(b"use 'hg update' or see 'hg help revert'"),
6236 )
6236 )
6237
6237
6238 rev = opts.get(b'rev')
6238 rev = opts.get(b'rev')
6239 if rev:
6239 if rev:
6240 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
6240 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
6241 ctx = scmutil.revsingle(repo, rev)
6241 ctx = scmutil.revsingle(repo, rev)
6242
6242
6243 if not (
6243 if not (
6244 pats
6244 pats
6245 or opts.get(b'include')
6245 or opts.get(b'include')
6246 or opts.get(b'exclude')
6246 or opts.get(b'exclude')
6247 or opts.get(b'all')
6247 or opts.get(b'all')
6248 or opts.get(b'interactive')
6248 or opts.get(b'interactive')
6249 ):
6249 ):
6250 msg = _(b"no files or directories specified")
6250 msg = _(b"no files or directories specified")
6251 if p2 != nullid:
6251 if p2 != nullid:
6252 hint = _(
6252 hint = _(
6253 b"uncommitted merge, use --all to discard all changes,"
6253 b"uncommitted merge, use --all to discard all changes,"
6254 b" or 'hg update -C .' to abort the merge"
6254 b" or 'hg update -C .' to abort the merge"
6255 )
6255 )
6256 raise error.Abort(msg, hint=hint)
6256 raise error.Abort(msg, hint=hint)
6257 dirty = any(repo.status())
6257 dirty = any(repo.status())
6258 node = ctx.node()
6258 node = ctx.node()
6259 if node != parent:
6259 if node != parent:
6260 if dirty:
6260 if dirty:
6261 hint = (
6261 hint = (
6262 _(
6262 _(
6263 b"uncommitted changes, use --all to discard all"
6263 b"uncommitted changes, use --all to discard all"
6264 b" changes, or 'hg update %d' to update"
6264 b" changes, or 'hg update %d' to update"
6265 )
6265 )
6266 % ctx.rev()
6266 % ctx.rev()
6267 )
6267 )
6268 else:
6268 else:
6269 hint = (
6269 hint = (
6270 _(
6270 _(
6271 b"use --all to revert all files,"
6271 b"use --all to revert all files,"
6272 b" or 'hg update %d' to update"
6272 b" or 'hg update %d' to update"
6273 )
6273 )
6274 % ctx.rev()
6274 % ctx.rev()
6275 )
6275 )
6276 elif dirty:
6276 elif dirty:
6277 hint = _(b"uncommitted changes, use --all to discard all changes")
6277 hint = _(b"uncommitted changes, use --all to discard all changes")
6278 else:
6278 else:
6279 hint = _(b"use --all to revert all files")
6279 hint = _(b"use --all to revert all files")
6280 raise error.Abort(msg, hint=hint)
6280 raise error.Abort(msg, hint=hint)
6281
6281
6282 return cmdutil.revert(
6282 return cmdutil.revert(
6283 ui, repo, ctx, (parent, p2), *pats, **pycompat.strkwargs(opts)
6283 ui, repo, ctx, (parent, p2), *pats, **pycompat.strkwargs(opts)
6284 )
6284 )
6285
6285
6286
6286
6287 @command(
6287 @command(
6288 b'rollback',
6288 b'rollback',
6289 dryrunopts + [(b'f', b'force', False, _(b'ignore safety measures'))],
6289 dryrunopts + [(b'f', b'force', False, _(b'ignore safety measures'))],
6290 helpcategory=command.CATEGORY_MAINTENANCE,
6290 helpcategory=command.CATEGORY_MAINTENANCE,
6291 )
6291 )
6292 def rollback(ui, repo, **opts):
6292 def rollback(ui, repo, **opts):
6293 """roll back the last transaction (DANGEROUS) (DEPRECATED)
6293 """roll back the last transaction (DANGEROUS) (DEPRECATED)
6294
6294
6295 Please use :hg:`commit --amend` instead of rollback to correct
6295 Please use :hg:`commit --amend` instead of rollback to correct
6296 mistakes in the last commit.
6296 mistakes in the last commit.
6297
6297
6298 This command should be used with care. There is only one level of
6298 This command should be used with care. There is only one level of
6299 rollback, and there is no way to undo a rollback. It will also
6299 rollback, and there is no way to undo a rollback. It will also
6300 restore the dirstate at the time of the last transaction, losing
6300 restore the dirstate at the time of the last transaction, losing
6301 any dirstate changes since that time. This command does not alter
6301 any dirstate changes since that time. This command does not alter
6302 the working directory.
6302 the working directory.
6303
6303
6304 Transactions are used to encapsulate the effects of all commands
6304 Transactions are used to encapsulate the effects of all commands
6305 that create new changesets or propagate existing changesets into a
6305 that create new changesets or propagate existing changesets into a
6306 repository.
6306 repository.
6307
6307
6308 .. container:: verbose
6308 .. container:: verbose
6309
6309
6310 For example, the following commands are transactional, and their
6310 For example, the following commands are transactional, and their
6311 effects can be rolled back:
6311 effects can be rolled back:
6312
6312
6313 - commit
6313 - commit
6314 - import
6314 - import
6315 - pull
6315 - pull
6316 - push (with this repository as the destination)
6316 - push (with this repository as the destination)
6317 - unbundle
6317 - unbundle
6318
6318
6319 To avoid permanent data loss, rollback will refuse to rollback a
6319 To avoid permanent data loss, rollback will refuse to rollback a
6320 commit transaction if it isn't checked out. Use --force to
6320 commit transaction if it isn't checked out. Use --force to
6321 override this protection.
6321 override this protection.
6322
6322
6323 The rollback command can be entirely disabled by setting the
6323 The rollback command can be entirely disabled by setting the
6324 ``ui.rollback`` configuration setting to false. If you're here
6324 ``ui.rollback`` configuration setting to false. If you're here
6325 because you want to use rollback and it's disabled, you can
6325 because you want to use rollback and it's disabled, you can
6326 re-enable the command by setting ``ui.rollback`` to true.
6326 re-enable the command by setting ``ui.rollback`` to true.
6327
6327
6328 This command is not intended for use on public repositories. Once
6328 This command is not intended for use on public repositories. Once
6329 changes are visible for pull by other users, rolling a transaction
6329 changes are visible for pull by other users, rolling a transaction
6330 back locally is ineffective (someone else may already have pulled
6330 back locally is ineffective (someone else may already have pulled
6331 the changes). Furthermore, a race is possible with readers of the
6331 the changes). Furthermore, a race is possible with readers of the
6332 repository; for example an in-progress pull from the repository
6332 repository; for example an in-progress pull from the repository
6333 may fail if a rollback is performed.
6333 may fail if a rollback is performed.
6334
6334
6335 Returns 0 on success, 1 if no rollback data is available.
6335 Returns 0 on success, 1 if no rollback data is available.
6336 """
6336 """
6337 if not ui.configbool(b'ui', b'rollback'):
6337 if not ui.configbool(b'ui', b'rollback'):
6338 raise error.Abort(
6338 raise error.Abort(
6339 _(b'rollback is disabled because it is unsafe'),
6339 _(b'rollback is disabled because it is unsafe'),
6340 hint=b'see `hg help -v rollback` for information',
6340 hint=b'see `hg help -v rollback` for information',
6341 )
6341 )
6342 return repo.rollback(dryrun=opts.get('dry_run'), force=opts.get('force'))
6342 return repo.rollback(dryrun=opts.get('dry_run'), force=opts.get('force'))
6343
6343
6344
6344
6345 @command(
6345 @command(
6346 b'root',
6346 b'root',
6347 [] + formatteropts,
6347 [] + formatteropts,
6348 intents={INTENT_READONLY},
6348 intents={INTENT_READONLY},
6349 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6349 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6350 )
6350 )
6351 def root(ui, repo, **opts):
6351 def root(ui, repo, **opts):
6352 """print the root (top) of the current working directory
6352 """print the root (top) of the current working directory
6353
6353
6354 Print the root directory of the current repository.
6354 Print the root directory of the current repository.
6355
6355
6356 .. container:: verbose
6356 .. container:: verbose
6357
6357
6358 Template:
6358 Template:
6359
6359
6360 The following keywords are supported in addition to the common template
6360 The following keywords are supported in addition to the common template
6361 keywords and functions. See also :hg:`help templates`.
6361 keywords and functions. See also :hg:`help templates`.
6362
6362
6363 :hgpath: String. Path to the .hg directory.
6363 :hgpath: String. Path to the .hg directory.
6364 :storepath: String. Path to the directory holding versioned data.
6364 :storepath: String. Path to the directory holding versioned data.
6365
6365
6366 Returns 0 on success.
6366 Returns 0 on success.
6367 """
6367 """
6368 opts = pycompat.byteskwargs(opts)
6368 opts = pycompat.byteskwargs(opts)
6369 with ui.formatter(b'root', opts) as fm:
6369 with ui.formatter(b'root', opts) as fm:
6370 fm.startitem()
6370 fm.startitem()
6371 fm.write(b'reporoot', b'%s\n', repo.root)
6371 fm.write(b'reporoot', b'%s\n', repo.root)
6372 fm.data(hgpath=repo.path, storepath=repo.spath)
6372 fm.data(hgpath=repo.path, storepath=repo.spath)
6373
6373
6374
6374
6375 @command(
6375 @command(
6376 b'serve',
6376 b'serve',
6377 [
6377 [
6378 (
6378 (
6379 b'A',
6379 b'A',
6380 b'accesslog',
6380 b'accesslog',
6381 b'',
6381 b'',
6382 _(b'name of access log file to write to'),
6382 _(b'name of access log file to write to'),
6383 _(b'FILE'),
6383 _(b'FILE'),
6384 ),
6384 ),
6385 (b'd', b'daemon', None, _(b'run server in background')),
6385 (b'd', b'daemon', None, _(b'run server in background')),
6386 (b'', b'daemon-postexec', [], _(b'used internally by daemon mode')),
6386 (b'', b'daemon-postexec', [], _(b'used internally by daemon mode')),
6387 (
6387 (
6388 b'E',
6388 b'E',
6389 b'errorlog',
6389 b'errorlog',
6390 b'',
6390 b'',
6391 _(b'name of error log file to write to'),
6391 _(b'name of error log file to write to'),
6392 _(b'FILE'),
6392 _(b'FILE'),
6393 ),
6393 ),
6394 # use string type, then we can check if something was passed
6394 # use string type, then we can check if something was passed
6395 (
6395 (
6396 b'p',
6396 b'p',
6397 b'port',
6397 b'port',
6398 b'',
6398 b'',
6399 _(b'port to listen on (default: 8000)'),
6399 _(b'port to listen on (default: 8000)'),
6400 _(b'PORT'),
6400 _(b'PORT'),
6401 ),
6401 ),
6402 (
6402 (
6403 b'a',
6403 b'a',
6404 b'address',
6404 b'address',
6405 b'',
6405 b'',
6406 _(b'address to listen on (default: all interfaces)'),
6406 _(b'address to listen on (default: all interfaces)'),
6407 _(b'ADDR'),
6407 _(b'ADDR'),
6408 ),
6408 ),
6409 (
6409 (
6410 b'',
6410 b'',
6411 b'prefix',
6411 b'prefix',
6412 b'',
6412 b'',
6413 _(b'prefix path to serve from (default: server root)'),
6413 _(b'prefix path to serve from (default: server root)'),
6414 _(b'PREFIX'),
6414 _(b'PREFIX'),
6415 ),
6415 ),
6416 (
6416 (
6417 b'n',
6417 b'n',
6418 b'name',
6418 b'name',
6419 b'',
6419 b'',
6420 _(b'name to show in web pages (default: working directory)'),
6420 _(b'name to show in web pages (default: working directory)'),
6421 _(b'NAME'),
6421 _(b'NAME'),
6422 ),
6422 ),
6423 (
6423 (
6424 b'',
6424 b'',
6425 b'web-conf',
6425 b'web-conf',
6426 b'',
6426 b'',
6427 _(b"name of the hgweb config file (see 'hg help hgweb')"),
6427 _(b"name of the hgweb config file (see 'hg help hgweb')"),
6428 _(b'FILE'),
6428 _(b'FILE'),
6429 ),
6429 ),
6430 (
6430 (
6431 b'',
6431 b'',
6432 b'webdir-conf',
6432 b'webdir-conf',
6433 b'',
6433 b'',
6434 _(b'name of the hgweb config file (DEPRECATED)'),
6434 _(b'name of the hgweb config file (DEPRECATED)'),
6435 _(b'FILE'),
6435 _(b'FILE'),
6436 ),
6436 ),
6437 (
6437 (
6438 b'',
6438 b'',
6439 b'pid-file',
6439 b'pid-file',
6440 b'',
6440 b'',
6441 _(b'name of file to write process ID to'),
6441 _(b'name of file to write process ID to'),
6442 _(b'FILE'),
6442 _(b'FILE'),
6443 ),
6443 ),
6444 (b'', b'stdio', None, _(b'for remote clients (ADVANCED)')),
6444 (b'', b'stdio', None, _(b'for remote clients (ADVANCED)')),
6445 (
6445 (
6446 b'',
6446 b'',
6447 b'cmdserver',
6447 b'cmdserver',
6448 b'',
6448 b'',
6449 _(b'for remote clients (ADVANCED)'),
6449 _(b'for remote clients (ADVANCED)'),
6450 _(b'MODE'),
6450 _(b'MODE'),
6451 ),
6451 ),
6452 (b't', b'templates', b'', _(b'web templates to use'), _(b'TEMPLATE')),
6452 (b't', b'templates', b'', _(b'web templates to use'), _(b'TEMPLATE')),
6453 (b'', b'style', b'', _(b'template style to use'), _(b'STYLE')),
6453 (b'', b'style', b'', _(b'template style to use'), _(b'STYLE')),
6454 (b'6', b'ipv6', None, _(b'use IPv6 in addition to IPv4')),
6454 (b'6', b'ipv6', None, _(b'use IPv6 in addition to IPv4')),
6455 (b'', b'certificate', b'', _(b'SSL certificate file'), _(b'FILE')),
6455 (b'', b'certificate', b'', _(b'SSL certificate file'), _(b'FILE')),
6456 (b'', b'print-url', None, _(b'start and print only the URL')),
6456 (b'', b'print-url', None, _(b'start and print only the URL')),
6457 ]
6457 ]
6458 + subrepoopts,
6458 + subrepoopts,
6459 _(b'[OPTION]...'),
6459 _(b'[OPTION]...'),
6460 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
6460 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
6461 helpbasic=True,
6461 helpbasic=True,
6462 optionalrepo=True,
6462 optionalrepo=True,
6463 )
6463 )
6464 def serve(ui, repo, **opts):
6464 def serve(ui, repo, **opts):
6465 """start stand-alone webserver
6465 """start stand-alone webserver
6466
6466
6467 Start a local HTTP repository browser and pull server. You can use
6467 Start a local HTTP repository browser and pull server. You can use
6468 this for ad-hoc sharing and browsing of repositories. It is
6468 this for ad-hoc sharing and browsing of repositories. It is
6469 recommended to use a real web server to serve a repository for
6469 recommended to use a real web server to serve a repository for
6470 longer periods of time.
6470 longer periods of time.
6471
6471
6472 Please note that the server does not implement access control.
6472 Please note that the server does not implement access control.
6473 This means that, by default, anybody can read from the server and
6473 This means that, by default, anybody can read from the server and
6474 nobody can write to it by default. Set the ``web.allow-push``
6474 nobody can write to it by default. Set the ``web.allow-push``
6475 option to ``*`` to allow everybody to push to the server. You
6475 option to ``*`` to allow everybody to push to the server. You
6476 should use a real web server if you need to authenticate users.
6476 should use a real web server if you need to authenticate users.
6477
6477
6478 By default, the server logs accesses to stdout and errors to
6478 By default, the server logs accesses to stdout and errors to
6479 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
6479 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
6480 files.
6480 files.
6481
6481
6482 To have the server choose a free port number to listen on, specify
6482 To have the server choose a free port number to listen on, specify
6483 a port number of 0; in this case, the server will print the port
6483 a port number of 0; in this case, the server will print the port
6484 number it uses.
6484 number it uses.
6485
6485
6486 Returns 0 on success.
6486 Returns 0 on success.
6487 """
6487 """
6488
6488
6489 opts = pycompat.byteskwargs(opts)
6489 opts = pycompat.byteskwargs(opts)
6490 if opts[b"stdio"] and opts[b"cmdserver"]:
6490 if opts[b"stdio"] and opts[b"cmdserver"]:
6491 raise error.Abort(_(b"cannot use --stdio with --cmdserver"))
6491 raise error.Abort(_(b"cannot use --stdio with --cmdserver"))
6492 if opts[b"print_url"] and ui.verbose:
6492 if opts[b"print_url"] and ui.verbose:
6493 raise error.Abort(_(b"cannot use --print-url with --verbose"))
6493 raise error.Abort(_(b"cannot use --print-url with --verbose"))
6494
6494
6495 if opts[b"stdio"]:
6495 if opts[b"stdio"]:
6496 if repo is None:
6496 if repo is None:
6497 raise error.RepoError(
6497 raise error.RepoError(
6498 _(b"there is no Mercurial repository here (.hg not found)")
6498 _(b"there is no Mercurial repository here (.hg not found)")
6499 )
6499 )
6500 s = wireprotoserver.sshserver(ui, repo)
6500 s = wireprotoserver.sshserver(ui, repo)
6501 s.serve_forever()
6501 s.serve_forever()
6502
6502
6503 service = server.createservice(ui, repo, opts)
6503 service = server.createservice(ui, repo, opts)
6504 return server.runservice(opts, initfn=service.init, runfn=service.run)
6504 return server.runservice(opts, initfn=service.init, runfn=service.run)
6505
6505
6506
6506
6507 @command(
6507 @command(
6508 b'shelve',
6508 b'shelve',
6509 [
6509 [
6510 (
6510 (
6511 b'A',
6511 b'A',
6512 b'addremove',
6512 b'addremove',
6513 None,
6513 None,
6514 _(b'mark new/missing files as added/removed before shelving'),
6514 _(b'mark new/missing files as added/removed before shelving'),
6515 ),
6515 ),
6516 (b'u', b'unknown', None, _(b'store unknown files in the shelve')),
6516 (b'u', b'unknown', None, _(b'store unknown files in the shelve')),
6517 (b'', b'cleanup', None, _(b'delete all shelved changes')),
6517 (b'', b'cleanup', None, _(b'delete all shelved changes')),
6518 (
6518 (
6519 b'',
6519 b'',
6520 b'date',
6520 b'date',
6521 b'',
6521 b'',
6522 _(b'shelve with the specified commit date'),
6522 _(b'shelve with the specified commit date'),
6523 _(b'DATE'),
6523 _(b'DATE'),
6524 ),
6524 ),
6525 (b'd', b'delete', None, _(b'delete the named shelved change(s)')),
6525 (b'd', b'delete', None, _(b'delete the named shelved change(s)')),
6526 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
6526 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
6527 (
6527 (
6528 b'k',
6528 b'k',
6529 b'keep',
6529 b'keep',
6530 False,
6530 False,
6531 _(b'shelve, but keep changes in the working directory'),
6531 _(b'shelve, but keep changes in the working directory'),
6532 ),
6532 ),
6533 (b'l', b'list', None, _(b'list current shelves')),
6533 (b'l', b'list', None, _(b'list current shelves')),
6534 (b'm', b'message', b'', _(b'use text as shelve message'), _(b'TEXT')),
6534 (b'm', b'message', b'', _(b'use text as shelve message'), _(b'TEXT')),
6535 (
6535 (
6536 b'n',
6536 b'n',
6537 b'name',
6537 b'name',
6538 b'',
6538 b'',
6539 _(b'use the given name for the shelved commit'),
6539 _(b'use the given name for the shelved commit'),
6540 _(b'NAME'),
6540 _(b'NAME'),
6541 ),
6541 ),
6542 (
6542 (
6543 b'p',
6543 b'p',
6544 b'patch',
6544 b'patch',
6545 None,
6545 None,
6546 _(
6546 _(
6547 b'output patches for changes (provide the names of the shelved '
6547 b'output patches for changes (provide the names of the shelved '
6548 b'changes as positional arguments)'
6548 b'changes as positional arguments)'
6549 ),
6549 ),
6550 ),
6550 ),
6551 (b'i', b'interactive', None, _(b'interactive mode')),
6551 (b'i', b'interactive', None, _(b'interactive mode')),
6552 (
6552 (
6553 b'',
6553 b'',
6554 b'stat',
6554 b'stat',
6555 None,
6555 None,
6556 _(
6556 _(
6557 b'output diffstat-style summary of changes (provide the names of '
6557 b'output diffstat-style summary of changes (provide the names of '
6558 b'the shelved changes as positional arguments)'
6558 b'the shelved changes as positional arguments)'
6559 ),
6559 ),
6560 ),
6560 ),
6561 ]
6561 ]
6562 + cmdutil.walkopts,
6562 + cmdutil.walkopts,
6563 _(b'hg shelve [OPTION]... [FILE]...'),
6563 _(b'hg shelve [OPTION]... [FILE]...'),
6564 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6564 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6565 )
6565 )
6566 def shelve(ui, repo, *pats, **opts):
6566 def shelve(ui, repo, *pats, **opts):
6567 '''save and set aside changes from the working directory
6567 '''save and set aside changes from the working directory
6568
6568
6569 Shelving takes files that "hg status" reports as not clean, saves
6569 Shelving takes files that "hg status" reports as not clean, saves
6570 the modifications to a bundle (a shelved change), and reverts the
6570 the modifications to a bundle (a shelved change), and reverts the
6571 files so that their state in the working directory becomes clean.
6571 files so that their state in the working directory becomes clean.
6572
6572
6573 To restore these changes to the working directory, using "hg
6573 To restore these changes to the working directory, using "hg
6574 unshelve"; this will work even if you switch to a different
6574 unshelve"; this will work even if you switch to a different
6575 commit.
6575 commit.
6576
6576
6577 When no files are specified, "hg shelve" saves all not-clean
6577 When no files are specified, "hg shelve" saves all not-clean
6578 files. If specific files or directories are named, only changes to
6578 files. If specific files or directories are named, only changes to
6579 those files are shelved.
6579 those files are shelved.
6580
6580
6581 In bare shelve (when no files are specified, without interactive,
6581 In bare shelve (when no files are specified, without interactive,
6582 include and exclude option), shelving remembers information if the
6582 include and exclude option), shelving remembers information if the
6583 working directory was on newly created branch, in other words working
6583 working directory was on newly created branch, in other words working
6584 directory was on different branch than its first parent. In this
6584 directory was on different branch than its first parent. In this
6585 situation unshelving restores branch information to the working directory.
6585 situation unshelving restores branch information to the working directory.
6586
6586
6587 Each shelved change has a name that makes it easier to find later.
6587 Each shelved change has a name that makes it easier to find later.
6588 The name of a shelved change defaults to being based on the active
6588 The name of a shelved change defaults to being based on the active
6589 bookmark, or if there is no active bookmark, the current named
6589 bookmark, or if there is no active bookmark, the current named
6590 branch. To specify a different name, use ``--name``.
6590 branch. To specify a different name, use ``--name``.
6591
6591
6592 To see a list of existing shelved changes, use the ``--list``
6592 To see a list of existing shelved changes, use the ``--list``
6593 option. For each shelved change, this will print its name, age,
6593 option. For each shelved change, this will print its name, age,
6594 and description; use ``--patch`` or ``--stat`` for more details.
6594 and description; use ``--patch`` or ``--stat`` for more details.
6595
6595
6596 To delete specific shelved changes, use ``--delete``. To delete
6596 To delete specific shelved changes, use ``--delete``. To delete
6597 all shelved changes, use ``--cleanup``.
6597 all shelved changes, use ``--cleanup``.
6598 '''
6598 '''
6599 opts = pycompat.byteskwargs(opts)
6599 opts = pycompat.byteskwargs(opts)
6600 allowables = [
6600 allowables = [
6601 (b'addremove', {b'create'}), # 'create' is pseudo action
6601 (b'addremove', {b'create'}), # 'create' is pseudo action
6602 (b'unknown', {b'create'}),
6602 (b'unknown', {b'create'}),
6603 (b'cleanup', {b'cleanup'}),
6603 (b'cleanup', {b'cleanup'}),
6604 # ('date', {'create'}), # ignored for passing '--date "0 0"' in tests
6604 # ('date', {'create'}), # ignored for passing '--date "0 0"' in tests
6605 (b'delete', {b'delete'}),
6605 (b'delete', {b'delete'}),
6606 (b'edit', {b'create'}),
6606 (b'edit', {b'create'}),
6607 (b'keep', {b'create'}),
6607 (b'keep', {b'create'}),
6608 (b'list', {b'list'}),
6608 (b'list', {b'list'}),
6609 (b'message', {b'create'}),
6609 (b'message', {b'create'}),
6610 (b'name', {b'create'}),
6610 (b'name', {b'create'}),
6611 (b'patch', {b'patch', b'list'}),
6611 (b'patch', {b'patch', b'list'}),
6612 (b'stat', {b'stat', b'list'}),
6612 (b'stat', {b'stat', b'list'}),
6613 ]
6613 ]
6614
6614
6615 def checkopt(opt):
6615 def checkopt(opt):
6616 if opts.get(opt):
6616 if opts.get(opt):
6617 for i, allowable in allowables:
6617 for i, allowable in allowables:
6618 if opts[i] and opt not in allowable:
6618 if opts[i] and opt not in allowable:
6619 raise error.Abort(
6619 raise error.Abort(
6620 _(
6620 _(
6621 b"options '--%s' and '--%s' may not be "
6621 b"options '--%s' and '--%s' may not be "
6622 b"used together"
6622 b"used together"
6623 )
6623 )
6624 % (opt, i)
6624 % (opt, i)
6625 )
6625 )
6626 return True
6626 return True
6627
6627
6628 if checkopt(b'cleanup'):
6628 if checkopt(b'cleanup'):
6629 if pats:
6629 if pats:
6630 raise error.Abort(_(b"cannot specify names when using '--cleanup'"))
6630 raise error.Abort(_(b"cannot specify names when using '--cleanup'"))
6631 return shelvemod.cleanupcmd(ui, repo)
6631 return shelvemod.cleanupcmd(ui, repo)
6632 elif checkopt(b'delete'):
6632 elif checkopt(b'delete'):
6633 return shelvemod.deletecmd(ui, repo, pats)
6633 return shelvemod.deletecmd(ui, repo, pats)
6634 elif checkopt(b'list'):
6634 elif checkopt(b'list'):
6635 return shelvemod.listcmd(ui, repo, pats, opts)
6635 return shelvemod.listcmd(ui, repo, pats, opts)
6636 elif checkopt(b'patch') or checkopt(b'stat'):
6636 elif checkopt(b'patch') or checkopt(b'stat'):
6637 return shelvemod.patchcmds(ui, repo, pats, opts)
6637 return shelvemod.patchcmds(ui, repo, pats, opts)
6638 else:
6638 else:
6639 return shelvemod.createcmd(ui, repo, pats, opts)
6639 return shelvemod.createcmd(ui, repo, pats, opts)
6640
6640
6641
6641
6642 _NOTTERSE = b'nothing'
6642 _NOTTERSE = b'nothing'
6643
6643
6644
6644
6645 @command(
6645 @command(
6646 b'status|st',
6646 b'status|st',
6647 [
6647 [
6648 (b'A', b'all', None, _(b'show status of all files')),
6648 (b'A', b'all', None, _(b'show status of all files')),
6649 (b'm', b'modified', None, _(b'show only modified files')),
6649 (b'm', b'modified', None, _(b'show only modified files')),
6650 (b'a', b'added', None, _(b'show only added files')),
6650 (b'a', b'added', None, _(b'show only added files')),
6651 (b'r', b'removed', None, _(b'show only removed files')),
6651 (b'r', b'removed', None, _(b'show only removed files')),
6652 (b'd', b'deleted', None, _(b'show only deleted (but tracked) files')),
6652 (b'd', b'deleted', None, _(b'show only deleted (but tracked) files')),
6653 (b'c', b'clean', None, _(b'show only files without changes')),
6653 (b'c', b'clean', None, _(b'show only files without changes')),
6654 (b'u', b'unknown', None, _(b'show only unknown (not tracked) files')),
6654 (b'u', b'unknown', None, _(b'show only unknown (not tracked) files')),
6655 (b'i', b'ignored', None, _(b'show only ignored files')),
6655 (b'i', b'ignored', None, _(b'show only ignored files')),
6656 (b'n', b'no-status', None, _(b'hide status prefix')),
6656 (b'n', b'no-status', None, _(b'hide status prefix')),
6657 (b't', b'terse', _NOTTERSE, _(b'show the terse output (EXPERIMENTAL)')),
6657 (b't', b'terse', _NOTTERSE, _(b'show the terse output (EXPERIMENTAL)')),
6658 (
6658 (
6659 b'C',
6659 b'C',
6660 b'copies',
6660 b'copies',
6661 None,
6661 None,
6662 _(b'show source of copied files (DEFAULT: ui.statuscopies)'),
6662 _(b'show source of copied files (DEFAULT: ui.statuscopies)'),
6663 ),
6663 ),
6664 (
6664 (
6665 b'0',
6665 b'0',
6666 b'print0',
6666 b'print0',
6667 None,
6667 None,
6668 _(b'end filenames with NUL, for use with xargs'),
6668 _(b'end filenames with NUL, for use with xargs'),
6669 ),
6669 ),
6670 (b'', b'rev', [], _(b'show difference from revision'), _(b'REV')),
6670 (b'', b'rev', [], _(b'show difference from revision'), _(b'REV')),
6671 (
6671 (
6672 b'',
6672 b'',
6673 b'change',
6673 b'change',
6674 b'',
6674 b'',
6675 _(b'list the changed files of a revision'),
6675 _(b'list the changed files of a revision'),
6676 _(b'REV'),
6676 _(b'REV'),
6677 ),
6677 ),
6678 ]
6678 ]
6679 + walkopts
6679 + walkopts
6680 + subrepoopts
6680 + subrepoopts
6681 + formatteropts,
6681 + formatteropts,
6682 _(b'[OPTION]... [FILE]...'),
6682 _(b'[OPTION]... [FILE]...'),
6683 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6683 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6684 helpbasic=True,
6684 helpbasic=True,
6685 inferrepo=True,
6685 inferrepo=True,
6686 intents={INTENT_READONLY},
6686 intents={INTENT_READONLY},
6687 )
6687 )
6688 def status(ui, repo, *pats, **opts):
6688 def status(ui, repo, *pats, **opts):
6689 """show changed files in the working directory
6689 """show changed files in the working directory
6690
6690
6691 Show status of files in the repository. If names are given, only
6691 Show status of files in the repository. If names are given, only
6692 files that match are shown. Files that are clean or ignored or
6692 files that match are shown. Files that are clean or ignored or
6693 the source of a copy/move operation, are not listed unless
6693 the source of a copy/move operation, are not listed unless
6694 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
6694 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
6695 Unless options described with "show only ..." are given, the
6695 Unless options described with "show only ..." are given, the
6696 options -mardu are used.
6696 options -mardu are used.
6697
6697
6698 Option -q/--quiet hides untracked (unknown and ignored) files
6698 Option -q/--quiet hides untracked (unknown and ignored) files
6699 unless explicitly requested with -u/--unknown or -i/--ignored.
6699 unless explicitly requested with -u/--unknown or -i/--ignored.
6700
6700
6701 .. note::
6701 .. note::
6702
6702
6703 :hg:`status` may appear to disagree with diff if permissions have
6703 :hg:`status` may appear to disagree with diff if permissions have
6704 changed or a merge has occurred. The standard diff format does
6704 changed or a merge has occurred. The standard diff format does
6705 not report permission changes and diff only reports changes
6705 not report permission changes and diff only reports changes
6706 relative to one merge parent.
6706 relative to one merge parent.
6707
6707
6708 If one revision is given, it is used as the base revision.
6708 If one revision is given, it is used as the base revision.
6709 If two revisions are given, the differences between them are
6709 If two revisions are given, the differences between them are
6710 shown. The --change option can also be used as a shortcut to list
6710 shown. The --change option can also be used as a shortcut to list
6711 the changed files of a revision from its first parent.
6711 the changed files of a revision from its first parent.
6712
6712
6713 The codes used to show the status of files are::
6713 The codes used to show the status of files are::
6714
6714
6715 M = modified
6715 M = modified
6716 A = added
6716 A = added
6717 R = removed
6717 R = removed
6718 C = clean
6718 C = clean
6719 ! = missing (deleted by non-hg command, but still tracked)
6719 ! = missing (deleted by non-hg command, but still tracked)
6720 ? = not tracked
6720 ? = not tracked
6721 I = ignored
6721 I = ignored
6722 = origin of the previous file (with --copies)
6722 = origin of the previous file (with --copies)
6723
6723
6724 .. container:: verbose
6724 .. container:: verbose
6725
6725
6726 The -t/--terse option abbreviates the output by showing only the directory
6726 The -t/--terse option abbreviates the output by showing only the directory
6727 name if all the files in it share the same status. The option takes an
6727 name if all the files in it share the same status. The option takes an
6728 argument indicating the statuses to abbreviate: 'm' for 'modified', 'a'
6728 argument indicating the statuses to abbreviate: 'm' for 'modified', 'a'
6729 for 'added', 'r' for 'removed', 'd' for 'deleted', 'u' for 'unknown', 'i'
6729 for 'added', 'r' for 'removed', 'd' for 'deleted', 'u' for 'unknown', 'i'
6730 for 'ignored' and 'c' for clean.
6730 for 'ignored' and 'c' for clean.
6731
6731
6732 It abbreviates only those statuses which are passed. Note that clean and
6732 It abbreviates only those statuses which are passed. Note that clean and
6733 ignored files are not displayed with '--terse ic' unless the -c/--clean
6733 ignored files are not displayed with '--terse ic' unless the -c/--clean
6734 and -i/--ignored options are also used.
6734 and -i/--ignored options are also used.
6735
6735
6736 The -v/--verbose option shows information when the repository is in an
6736 The -v/--verbose option shows information when the repository is in an
6737 unfinished merge, shelve, rebase state etc. You can have this behavior
6737 unfinished merge, shelve, rebase state etc. You can have this behavior
6738 turned on by default by enabling the ``commands.status.verbose`` option.
6738 turned on by default by enabling the ``commands.status.verbose`` option.
6739
6739
6740 You can skip displaying some of these states by setting
6740 You can skip displaying some of these states by setting
6741 ``commands.status.skipstates`` to one or more of: 'bisect', 'graft',
6741 ``commands.status.skipstates`` to one or more of: 'bisect', 'graft',
6742 'histedit', 'merge', 'rebase', or 'unshelve'.
6742 'histedit', 'merge', 'rebase', or 'unshelve'.
6743
6743
6744 Template:
6744 Template:
6745
6745
6746 The following keywords are supported in addition to the common template
6746 The following keywords are supported in addition to the common template
6747 keywords and functions. See also :hg:`help templates`.
6747 keywords and functions. See also :hg:`help templates`.
6748
6748
6749 :path: String. Repository-absolute path of the file.
6749 :path: String. Repository-absolute path of the file.
6750 :source: String. Repository-absolute path of the file originated from.
6750 :source: String. Repository-absolute path of the file originated from.
6751 Available if ``--copies`` is specified.
6751 Available if ``--copies`` is specified.
6752 :status: String. Character denoting file's status.
6752 :status: String. Character denoting file's status.
6753
6753
6754 Examples:
6754 Examples:
6755
6755
6756 - show changes in the working directory relative to a
6756 - show changes in the working directory relative to a
6757 changeset::
6757 changeset::
6758
6758
6759 hg status --rev 9353
6759 hg status --rev 9353
6760
6760
6761 - show changes in the working directory relative to the
6761 - show changes in the working directory relative to the
6762 current directory (see :hg:`help patterns` for more information)::
6762 current directory (see :hg:`help patterns` for more information)::
6763
6763
6764 hg status re:
6764 hg status re:
6765
6765
6766 - show all changes including copies in an existing changeset::
6766 - show all changes including copies in an existing changeset::
6767
6767
6768 hg status --copies --change 9353
6768 hg status --copies --change 9353
6769
6769
6770 - get a NUL separated list of added files, suitable for xargs::
6770 - get a NUL separated list of added files, suitable for xargs::
6771
6771
6772 hg status -an0
6772 hg status -an0
6773
6773
6774 - show more information about the repository status, abbreviating
6774 - show more information about the repository status, abbreviating
6775 added, removed, modified, deleted, and untracked paths::
6775 added, removed, modified, deleted, and untracked paths::
6776
6776
6777 hg status -v -t mardu
6777 hg status -v -t mardu
6778
6778
6779 Returns 0 on success.
6779 Returns 0 on success.
6780
6780
6781 """
6781 """
6782
6782
6783 opts = pycompat.byteskwargs(opts)
6783 opts = pycompat.byteskwargs(opts)
6784 revs = opts.get(b'rev')
6784 revs = opts.get(b'rev')
6785 change = opts.get(b'change')
6785 change = opts.get(b'change')
6786 terse = opts.get(b'terse')
6786 terse = opts.get(b'terse')
6787 if terse is _NOTTERSE:
6787 if terse is _NOTTERSE:
6788 if revs:
6788 if revs:
6789 terse = b''
6789 terse = b''
6790 else:
6790 else:
6791 terse = ui.config(b'commands', b'status.terse')
6791 terse = ui.config(b'commands', b'status.terse')
6792
6792
6793 if revs and change:
6793 if revs and change:
6794 msg = _(b'cannot specify --rev and --change at the same time')
6794 msg = _(b'cannot specify --rev and --change at the same time')
6795 raise error.Abort(msg)
6795 raise error.Abort(msg)
6796 elif revs and terse:
6796 elif revs and terse:
6797 msg = _(b'cannot use --terse with --rev')
6797 msg = _(b'cannot use --terse with --rev')
6798 raise error.Abort(msg)
6798 raise error.Abort(msg)
6799 elif change:
6799 elif change:
6800 repo = scmutil.unhidehashlikerevs(repo, [change], b'nowarn')
6800 repo = scmutil.unhidehashlikerevs(repo, [change], b'nowarn')
6801 ctx2 = scmutil.revsingle(repo, change, None)
6801 ctx2 = scmutil.revsingle(repo, change, None)
6802 ctx1 = ctx2.p1()
6802 ctx1 = ctx2.p1()
6803 else:
6803 else:
6804 repo = scmutil.unhidehashlikerevs(repo, revs, b'nowarn')
6804 repo = scmutil.unhidehashlikerevs(repo, revs, b'nowarn')
6805 ctx1, ctx2 = scmutil.revpair(repo, revs)
6805 ctx1, ctx2 = scmutil.revpair(repo, revs)
6806
6806
6807 forcerelativevalue = None
6807 forcerelativevalue = None
6808 if ui.hasconfig(b'commands', b'status.relative'):
6808 if ui.hasconfig(b'commands', b'status.relative'):
6809 forcerelativevalue = ui.configbool(b'commands', b'status.relative')
6809 forcerelativevalue = ui.configbool(b'commands', b'status.relative')
6810 uipathfn = scmutil.getuipathfn(
6810 uipathfn = scmutil.getuipathfn(
6811 repo,
6811 repo,
6812 legacyrelativevalue=bool(pats),
6812 legacyrelativevalue=bool(pats),
6813 forcerelativevalue=forcerelativevalue,
6813 forcerelativevalue=forcerelativevalue,
6814 )
6814 )
6815
6815
6816 if opts.get(b'print0'):
6816 if opts.get(b'print0'):
6817 end = b'\0'
6817 end = b'\0'
6818 else:
6818 else:
6819 end = b'\n'
6819 end = b'\n'
6820 states = b'modified added removed deleted unknown ignored clean'.split()
6820 states = b'modified added removed deleted unknown ignored clean'.split()
6821 show = [k for k in states if opts.get(k)]
6821 show = [k for k in states if opts.get(k)]
6822 if opts.get(b'all'):
6822 if opts.get(b'all'):
6823 show += ui.quiet and (states[:4] + [b'clean']) or states
6823 show += ui.quiet and (states[:4] + [b'clean']) or states
6824
6824
6825 if not show:
6825 if not show:
6826 if ui.quiet:
6826 if ui.quiet:
6827 show = states[:4]
6827 show = states[:4]
6828 else:
6828 else:
6829 show = states[:5]
6829 show = states[:5]
6830
6830
6831 m = scmutil.match(ctx2, pats, opts)
6831 m = scmutil.match(ctx2, pats, opts)
6832 if terse:
6832 if terse:
6833 # we need to compute clean and unknown to terse
6833 # we need to compute clean and unknown to terse
6834 stat = repo.status(
6834 stat = repo.status(
6835 ctx1.node(),
6835 ctx1.node(),
6836 ctx2.node(),
6836 ctx2.node(),
6837 m,
6837 m,
6838 b'ignored' in show or b'i' in terse,
6838 b'ignored' in show or b'i' in terse,
6839 clean=True,
6839 clean=True,
6840 unknown=True,
6840 unknown=True,
6841 listsubrepos=opts.get(b'subrepos'),
6841 listsubrepos=opts.get(b'subrepos'),
6842 )
6842 )
6843
6843
6844 stat = cmdutil.tersedir(stat, terse)
6844 stat = cmdutil.tersedir(stat, terse)
6845 else:
6845 else:
6846 stat = repo.status(
6846 stat = repo.status(
6847 ctx1.node(),
6847 ctx1.node(),
6848 ctx2.node(),
6848 ctx2.node(),
6849 m,
6849 m,
6850 b'ignored' in show,
6850 b'ignored' in show,
6851 b'clean' in show,
6851 b'clean' in show,
6852 b'unknown' in show,
6852 b'unknown' in show,
6853 opts.get(b'subrepos'),
6853 opts.get(b'subrepos'),
6854 )
6854 )
6855
6855
6856 changestates = zip(
6856 changestates = zip(
6857 states,
6857 states,
6858 pycompat.iterbytestr(b'MAR!?IC'),
6858 pycompat.iterbytestr(b'MAR!?IC'),
6859 [getattr(stat, s.decode('utf8')) for s in states],
6859 [getattr(stat, s.decode('utf8')) for s in states],
6860 )
6860 )
6861
6861
6862 copy = {}
6862 copy = {}
6863 if (
6863 if (
6864 opts.get(b'all')
6864 opts.get(b'all')
6865 or opts.get(b'copies')
6865 or opts.get(b'copies')
6866 or ui.configbool(b'ui', b'statuscopies')
6866 or ui.configbool(b'ui', b'statuscopies')
6867 ) and not opts.get(b'no_status'):
6867 ) and not opts.get(b'no_status'):
6868 copy = copies.pathcopies(ctx1, ctx2, m)
6868 copy = copies.pathcopies(ctx1, ctx2, m)
6869
6869
6870 morestatus = None
6870 morestatus = None
6871 if (
6871 if (
6872 ui.verbose or ui.configbool(b'commands', b'status.verbose')
6872 ui.verbose or ui.configbool(b'commands', b'status.verbose')
6873 ) and not ui.plain():
6873 ) and not ui.plain():
6874 morestatus = cmdutil.readmorestatus(repo)
6874 morestatus = cmdutil.readmorestatus(repo)
6875
6875
6876 ui.pager(b'status')
6876 ui.pager(b'status')
6877 fm = ui.formatter(b'status', opts)
6877 fm = ui.formatter(b'status', opts)
6878 fmt = b'%s' + end
6878 fmt = b'%s' + end
6879 showchar = not opts.get(b'no_status')
6879 showchar = not opts.get(b'no_status')
6880
6880
6881 for state, char, files in changestates:
6881 for state, char, files in changestates:
6882 if state in show:
6882 if state in show:
6883 label = b'status.' + state
6883 label = b'status.' + state
6884 for f in files:
6884 for f in files:
6885 fm.startitem()
6885 fm.startitem()
6886 fm.context(ctx=ctx2)
6886 fm.context(ctx=ctx2)
6887 fm.data(itemtype=b'file', path=f)
6887 fm.data(itemtype=b'file', path=f)
6888 fm.condwrite(showchar, b'status', b'%s ', char, label=label)
6888 fm.condwrite(showchar, b'status', b'%s ', char, label=label)
6889 fm.plain(fmt % uipathfn(f), label=label)
6889 fm.plain(fmt % uipathfn(f), label=label)
6890 if f in copy:
6890 if f in copy:
6891 fm.data(source=copy[f])
6891 fm.data(source=copy[f])
6892 fm.plain(
6892 fm.plain(
6893 (b' %s' + end) % uipathfn(copy[f]),
6893 (b' %s' + end) % uipathfn(copy[f]),
6894 label=b'status.copied',
6894 label=b'status.copied',
6895 )
6895 )
6896 if morestatus:
6896 if morestatus:
6897 morestatus.formatfile(f, fm)
6897 morestatus.formatfile(f, fm)
6898
6898
6899 if morestatus:
6899 if morestatus:
6900 morestatus.formatfooter(fm)
6900 morestatus.formatfooter(fm)
6901 fm.end()
6901 fm.end()
6902
6902
6903
6903
6904 @command(
6904 @command(
6905 b'summary|sum',
6905 b'summary|sum',
6906 [(b'', b'remote', None, _(b'check for push and pull'))],
6906 [(b'', b'remote', None, _(b'check for push and pull'))],
6907 b'[--remote]',
6907 b'[--remote]',
6908 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6908 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6909 helpbasic=True,
6909 helpbasic=True,
6910 intents={INTENT_READONLY},
6910 intents={INTENT_READONLY},
6911 )
6911 )
6912 def summary(ui, repo, **opts):
6912 def summary(ui, repo, **opts):
6913 """summarize working directory state
6913 """summarize working directory state
6914
6914
6915 This generates a brief summary of the working directory state,
6915 This generates a brief summary of the working directory state,
6916 including parents, branch, commit status, phase and available updates.
6916 including parents, branch, commit status, phase and available updates.
6917
6917
6918 With the --remote option, this will check the default paths for
6918 With the --remote option, this will check the default paths for
6919 incoming and outgoing changes. This can be time-consuming.
6919 incoming and outgoing changes. This can be time-consuming.
6920
6920
6921 Returns 0 on success.
6921 Returns 0 on success.
6922 """
6922 """
6923
6923
6924 opts = pycompat.byteskwargs(opts)
6924 opts = pycompat.byteskwargs(opts)
6925 ui.pager(b'summary')
6925 ui.pager(b'summary')
6926 ctx = repo[None]
6926 ctx = repo[None]
6927 parents = ctx.parents()
6927 parents = ctx.parents()
6928 pnode = parents[0].node()
6928 pnode = parents[0].node()
6929 marks = []
6929 marks = []
6930
6930
6931 try:
6931 try:
6932 ms = mergemod.mergestate.read(repo)
6932 ms = mergemod.mergestate.read(repo)
6933 except error.UnsupportedMergeRecords as e:
6933 except error.UnsupportedMergeRecords as e:
6934 s = b' '.join(e.recordtypes)
6934 s = b' '.join(e.recordtypes)
6935 ui.warn(
6935 ui.warn(
6936 _(b'warning: merge state has unsupported record types: %s\n') % s
6936 _(b'warning: merge state has unsupported record types: %s\n') % s
6937 )
6937 )
6938 unresolved = []
6938 unresolved = []
6939 else:
6939 else:
6940 unresolved = list(ms.unresolved())
6940 unresolved = list(ms.unresolved())
6941
6941
6942 for p in parents:
6942 for p in parents:
6943 # label with log.changeset (instead of log.parent) since this
6943 # label with log.changeset (instead of log.parent) since this
6944 # shows a working directory parent *changeset*:
6944 # shows a working directory parent *changeset*:
6945 # i18n: column positioning for "hg summary"
6945 # i18n: column positioning for "hg summary"
6946 ui.write(
6946 ui.write(
6947 _(b'parent: %d:%s ') % (p.rev(), p),
6947 _(b'parent: %d:%s ') % (p.rev(), p),
6948 label=logcmdutil.changesetlabels(p),
6948 label=logcmdutil.changesetlabels(p),
6949 )
6949 )
6950 ui.write(b' '.join(p.tags()), label=b'log.tag')
6950 ui.write(b' '.join(p.tags()), label=b'log.tag')
6951 if p.bookmarks():
6951 if p.bookmarks():
6952 marks.extend(p.bookmarks())
6952 marks.extend(p.bookmarks())
6953 if p.rev() == -1:
6953 if p.rev() == -1:
6954 if not len(repo):
6954 if not len(repo):
6955 ui.write(_(b' (empty repository)'))
6955 ui.write(_(b' (empty repository)'))
6956 else:
6956 else:
6957 ui.write(_(b' (no revision checked out)'))
6957 ui.write(_(b' (no revision checked out)'))
6958 if p.obsolete():
6958 if p.obsolete():
6959 ui.write(_(b' (obsolete)'))
6959 ui.write(_(b' (obsolete)'))
6960 if p.isunstable():
6960 if p.isunstable():
6961 instabilities = (
6961 instabilities = (
6962 ui.label(instability, b'trouble.%s' % instability)
6962 ui.label(instability, b'trouble.%s' % instability)
6963 for instability in p.instabilities()
6963 for instability in p.instabilities()
6964 )
6964 )
6965 ui.write(b' (' + b', '.join(instabilities) + b')')
6965 ui.write(b' (' + b', '.join(instabilities) + b')')
6966 ui.write(b'\n')
6966 ui.write(b'\n')
6967 if p.description():
6967 if p.description():
6968 ui.status(
6968 ui.status(
6969 b' ' + p.description().splitlines()[0].strip() + b'\n',
6969 b' ' + p.description().splitlines()[0].strip() + b'\n',
6970 label=b'log.summary',
6970 label=b'log.summary',
6971 )
6971 )
6972
6972
6973 branch = ctx.branch()
6973 branch = ctx.branch()
6974 bheads = repo.branchheads(branch)
6974 bheads = repo.branchheads(branch)
6975 # i18n: column positioning for "hg summary"
6975 # i18n: column positioning for "hg summary"
6976 m = _(b'branch: %s\n') % branch
6976 m = _(b'branch: %s\n') % branch
6977 if branch != b'default':
6977 if branch != b'default':
6978 ui.write(m, label=b'log.branch')
6978 ui.write(m, label=b'log.branch')
6979 else:
6979 else:
6980 ui.status(m, label=b'log.branch')
6980 ui.status(m, label=b'log.branch')
6981
6981
6982 if marks:
6982 if marks:
6983 active = repo._activebookmark
6983 active = repo._activebookmark
6984 # i18n: column positioning for "hg summary"
6984 # i18n: column positioning for "hg summary"
6985 ui.write(_(b'bookmarks:'), label=b'log.bookmark')
6985 ui.write(_(b'bookmarks:'), label=b'log.bookmark')
6986 if active is not None:
6986 if active is not None:
6987 if active in marks:
6987 if active in marks:
6988 ui.write(b' *' + active, label=bookmarks.activebookmarklabel)
6988 ui.write(b' *' + active, label=bookmarks.activebookmarklabel)
6989 marks.remove(active)
6989 marks.remove(active)
6990 else:
6990 else:
6991 ui.write(b' [%s]' % active, label=bookmarks.activebookmarklabel)
6991 ui.write(b' [%s]' % active, label=bookmarks.activebookmarklabel)
6992 for m in marks:
6992 for m in marks:
6993 ui.write(b' ' + m, label=b'log.bookmark')
6993 ui.write(b' ' + m, label=b'log.bookmark')
6994 ui.write(b'\n', label=b'log.bookmark')
6994 ui.write(b'\n', label=b'log.bookmark')
6995
6995
6996 status = repo.status(unknown=True)
6996 status = repo.status(unknown=True)
6997
6997
6998 c = repo.dirstate.copies()
6998 c = repo.dirstate.copies()
6999 copied, renamed = [], []
6999 copied, renamed = [], []
7000 for d, s in pycompat.iteritems(c):
7000 for d, s in pycompat.iteritems(c):
7001 if s in status.removed:
7001 if s in status.removed:
7002 status.removed.remove(s)
7002 status.removed.remove(s)
7003 renamed.append(d)
7003 renamed.append(d)
7004 else:
7004 else:
7005 copied.append(d)
7005 copied.append(d)
7006 if d in status.added:
7006 if d in status.added:
7007 status.added.remove(d)
7007 status.added.remove(d)
7008
7008
7009 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
7009 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
7010
7010
7011 labels = [
7011 labels = [
7012 (ui.label(_(b'%d modified'), b'status.modified'), status.modified),
7012 (ui.label(_(b'%d modified'), b'status.modified'), status.modified),
7013 (ui.label(_(b'%d added'), b'status.added'), status.added),
7013 (ui.label(_(b'%d added'), b'status.added'), status.added),
7014 (ui.label(_(b'%d removed'), b'status.removed'), status.removed),
7014 (ui.label(_(b'%d removed'), b'status.removed'), status.removed),
7015 (ui.label(_(b'%d renamed'), b'status.copied'), renamed),
7015 (ui.label(_(b'%d renamed'), b'status.copied'), renamed),
7016 (ui.label(_(b'%d copied'), b'status.copied'), copied),
7016 (ui.label(_(b'%d copied'), b'status.copied'), copied),
7017 (ui.label(_(b'%d deleted'), b'status.deleted'), status.deleted),
7017 (ui.label(_(b'%d deleted'), b'status.deleted'), status.deleted),
7018 (ui.label(_(b'%d unknown'), b'status.unknown'), status.unknown),
7018 (ui.label(_(b'%d unknown'), b'status.unknown'), status.unknown),
7019 (ui.label(_(b'%d unresolved'), b'resolve.unresolved'), unresolved),
7019 (ui.label(_(b'%d unresolved'), b'resolve.unresolved'), unresolved),
7020 (ui.label(_(b'%d subrepos'), b'status.modified'), subs),
7020 (ui.label(_(b'%d subrepos'), b'status.modified'), subs),
7021 ]
7021 ]
7022 t = []
7022 t = []
7023 for l, s in labels:
7023 for l, s in labels:
7024 if s:
7024 if s:
7025 t.append(l % len(s))
7025 t.append(l % len(s))
7026
7026
7027 t = b', '.join(t)
7027 t = b', '.join(t)
7028 cleanworkdir = False
7028 cleanworkdir = False
7029
7029
7030 if repo.vfs.exists(b'graftstate'):
7030 if repo.vfs.exists(b'graftstate'):
7031 t += _(b' (graft in progress)')
7031 t += _(b' (graft in progress)')
7032 if repo.vfs.exists(b'updatestate'):
7032 if repo.vfs.exists(b'updatestate'):
7033 t += _(b' (interrupted update)')
7033 t += _(b' (interrupted update)')
7034 elif len(parents) > 1:
7034 elif len(parents) > 1:
7035 t += _(b' (merge)')
7035 t += _(b' (merge)')
7036 elif branch != parents[0].branch():
7036 elif branch != parents[0].branch():
7037 t += _(b' (new branch)')
7037 t += _(b' (new branch)')
7038 elif parents[0].closesbranch() and pnode in repo.branchheads(
7038 elif parents[0].closesbranch() and pnode in repo.branchheads(
7039 branch, closed=True
7039 branch, closed=True
7040 ):
7040 ):
7041 t += _(b' (head closed)')
7041 t += _(b' (head closed)')
7042 elif not (
7042 elif not (
7043 status.modified
7043 status.modified
7044 or status.added
7044 or status.added
7045 or status.removed
7045 or status.removed
7046 or renamed
7046 or renamed
7047 or copied
7047 or copied
7048 or subs
7048 or subs
7049 ):
7049 ):
7050 t += _(b' (clean)')
7050 t += _(b' (clean)')
7051 cleanworkdir = True
7051 cleanworkdir = True
7052 elif pnode not in bheads:
7052 elif pnode not in bheads:
7053 t += _(b' (new branch head)')
7053 t += _(b' (new branch head)')
7054
7054
7055 if parents:
7055 if parents:
7056 pendingphase = max(p.phase() for p in parents)
7056 pendingphase = max(p.phase() for p in parents)
7057 else:
7057 else:
7058 pendingphase = phases.public
7058 pendingphase = phases.public
7059
7059
7060 if pendingphase > phases.newcommitphase(ui):
7060 if pendingphase > phases.newcommitphase(ui):
7061 t += b' (%s)' % phases.phasenames[pendingphase]
7061 t += b' (%s)' % phases.phasenames[pendingphase]
7062
7062
7063 if cleanworkdir:
7063 if cleanworkdir:
7064 # i18n: column positioning for "hg summary"
7064 # i18n: column positioning for "hg summary"
7065 ui.status(_(b'commit: %s\n') % t.strip())
7065 ui.status(_(b'commit: %s\n') % t.strip())
7066 else:
7066 else:
7067 # i18n: column positioning for "hg summary"
7067 # i18n: column positioning for "hg summary"
7068 ui.write(_(b'commit: %s\n') % t.strip())
7068 ui.write(_(b'commit: %s\n') % t.strip())
7069
7069
7070 # all ancestors of branch heads - all ancestors of parent = new csets
7070 # all ancestors of branch heads - all ancestors of parent = new csets
7071 new = len(
7071 new = len(
7072 repo.changelog.findmissing([pctx.node() for pctx in parents], bheads)
7072 repo.changelog.findmissing([pctx.node() for pctx in parents], bheads)
7073 )
7073 )
7074
7074
7075 if new == 0:
7075 if new == 0:
7076 # i18n: column positioning for "hg summary"
7076 # i18n: column positioning for "hg summary"
7077 ui.status(_(b'update: (current)\n'))
7077 ui.status(_(b'update: (current)\n'))
7078 elif pnode not in bheads:
7078 elif pnode not in bheads:
7079 # i18n: column positioning for "hg summary"
7079 # i18n: column positioning for "hg summary"
7080 ui.write(_(b'update: %d new changesets (update)\n') % new)
7080 ui.write(_(b'update: %d new changesets (update)\n') % new)
7081 else:
7081 else:
7082 # i18n: column positioning for "hg summary"
7082 # i18n: column positioning for "hg summary"
7083 ui.write(
7083 ui.write(
7084 _(b'update: %d new changesets, %d branch heads (merge)\n')
7084 _(b'update: %d new changesets, %d branch heads (merge)\n')
7085 % (new, len(bheads))
7085 % (new, len(bheads))
7086 )
7086 )
7087
7087
7088 t = []
7088 t = []
7089 draft = len(repo.revs(b'draft()'))
7089 draft = len(repo.revs(b'draft()'))
7090 if draft:
7090 if draft:
7091 t.append(_(b'%d draft') % draft)
7091 t.append(_(b'%d draft') % draft)
7092 secret = len(repo.revs(b'secret()'))
7092 secret = len(repo.revs(b'secret()'))
7093 if secret:
7093 if secret:
7094 t.append(_(b'%d secret') % secret)
7094 t.append(_(b'%d secret') % secret)
7095
7095
7096 if draft or secret:
7096 if draft or secret:
7097 ui.status(_(b'phases: %s\n') % b', '.join(t))
7097 ui.status(_(b'phases: %s\n') % b', '.join(t))
7098
7098
7099 if obsolete.isenabled(repo, obsolete.createmarkersopt):
7099 if obsolete.isenabled(repo, obsolete.createmarkersopt):
7100 for trouble in (b"orphan", b"contentdivergent", b"phasedivergent"):
7100 for trouble in (b"orphan", b"contentdivergent", b"phasedivergent"):
7101 numtrouble = len(repo.revs(trouble + b"()"))
7101 numtrouble = len(repo.revs(trouble + b"()"))
7102 # We write all the possibilities to ease translation
7102 # We write all the possibilities to ease translation
7103 troublemsg = {
7103 troublemsg = {
7104 b"orphan": _(b"orphan: %d changesets"),
7104 b"orphan": _(b"orphan: %d changesets"),
7105 b"contentdivergent": _(b"content-divergent: %d changesets"),
7105 b"contentdivergent": _(b"content-divergent: %d changesets"),
7106 b"phasedivergent": _(b"phase-divergent: %d changesets"),
7106 b"phasedivergent": _(b"phase-divergent: %d changesets"),
7107 }
7107 }
7108 if numtrouble > 0:
7108 if numtrouble > 0:
7109 ui.status(troublemsg[trouble] % numtrouble + b"\n")
7109 ui.status(troublemsg[trouble] % numtrouble + b"\n")
7110
7110
7111 cmdutil.summaryhooks(ui, repo)
7111 cmdutil.summaryhooks(ui, repo)
7112
7112
7113 if opts.get(b'remote'):
7113 if opts.get(b'remote'):
7114 needsincoming, needsoutgoing = True, True
7114 needsincoming, needsoutgoing = True, True
7115 else:
7115 else:
7116 needsincoming, needsoutgoing = False, False
7116 needsincoming, needsoutgoing = False, False
7117 for i, o in cmdutil.summaryremotehooks(ui, repo, opts, None):
7117 for i, o in cmdutil.summaryremotehooks(ui, repo, opts, None):
7118 if i:
7118 if i:
7119 needsincoming = True
7119 needsincoming = True
7120 if o:
7120 if o:
7121 needsoutgoing = True
7121 needsoutgoing = True
7122 if not needsincoming and not needsoutgoing:
7122 if not needsincoming and not needsoutgoing:
7123 return
7123 return
7124
7124
7125 def getincoming():
7125 def getincoming():
7126 source, branches = hg.parseurl(ui.expandpath(b'default'))
7126 source, branches = hg.parseurl(ui.expandpath(b'default'))
7127 sbranch = branches[0]
7127 sbranch = branches[0]
7128 try:
7128 try:
7129 other = hg.peer(repo, {}, source)
7129 other = hg.peer(repo, {}, source)
7130 except error.RepoError:
7130 except error.RepoError:
7131 if opts.get(b'remote'):
7131 if opts.get(b'remote'):
7132 raise
7132 raise
7133 return source, sbranch, None, None, None
7133 return source, sbranch, None, None, None
7134 revs, checkout = hg.addbranchrevs(repo, other, branches, None)
7134 revs, checkout = hg.addbranchrevs(repo, other, branches, None)
7135 if revs:
7135 if revs:
7136 revs = [other.lookup(rev) for rev in revs]
7136 revs = [other.lookup(rev) for rev in revs]
7137 ui.debug(b'comparing with %s\n' % util.hidepassword(source))
7137 ui.debug(b'comparing with %s\n' % util.hidepassword(source))
7138 repo.ui.pushbuffer()
7138 repo.ui.pushbuffer()
7139 commoninc = discovery.findcommonincoming(repo, other, heads=revs)
7139 commoninc = discovery.findcommonincoming(repo, other, heads=revs)
7140 repo.ui.popbuffer()
7140 repo.ui.popbuffer()
7141 return source, sbranch, other, commoninc, commoninc[1]
7141 return source, sbranch, other, commoninc, commoninc[1]
7142
7142
7143 if needsincoming:
7143 if needsincoming:
7144 source, sbranch, sother, commoninc, incoming = getincoming()
7144 source, sbranch, sother, commoninc, incoming = getincoming()
7145 else:
7145 else:
7146 source = sbranch = sother = commoninc = incoming = None
7146 source = sbranch = sother = commoninc = incoming = None
7147
7147
7148 def getoutgoing():
7148 def getoutgoing():
7149 dest, branches = hg.parseurl(ui.expandpath(b'default-push', b'default'))
7149 dest, branches = hg.parseurl(ui.expandpath(b'default-push', b'default'))
7150 dbranch = branches[0]
7150 dbranch = branches[0]
7151 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
7151 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
7152 if source != dest:
7152 if source != dest:
7153 try:
7153 try:
7154 dother = hg.peer(repo, {}, dest)
7154 dother = hg.peer(repo, {}, dest)
7155 except error.RepoError:
7155 except error.RepoError:
7156 if opts.get(b'remote'):
7156 if opts.get(b'remote'):
7157 raise
7157 raise
7158 return dest, dbranch, None, None
7158 return dest, dbranch, None, None
7159 ui.debug(b'comparing with %s\n' % util.hidepassword(dest))
7159 ui.debug(b'comparing with %s\n' % util.hidepassword(dest))
7160 elif sother is None:
7160 elif sother is None:
7161 # there is no explicit destination peer, but source one is invalid
7161 # there is no explicit destination peer, but source one is invalid
7162 return dest, dbranch, None, None
7162 return dest, dbranch, None, None
7163 else:
7163 else:
7164 dother = sother
7164 dother = sother
7165 if source != dest or (sbranch is not None and sbranch != dbranch):
7165 if source != dest or (sbranch is not None and sbranch != dbranch):
7166 common = None
7166 common = None
7167 else:
7167 else:
7168 common = commoninc
7168 common = commoninc
7169 if revs:
7169 if revs:
7170 revs = [repo.lookup(rev) for rev in revs]
7170 revs = [repo.lookup(rev) for rev in revs]
7171 repo.ui.pushbuffer()
7171 repo.ui.pushbuffer()
7172 outgoing = discovery.findcommonoutgoing(
7172 outgoing = discovery.findcommonoutgoing(
7173 repo, dother, onlyheads=revs, commoninc=common
7173 repo, dother, onlyheads=revs, commoninc=common
7174 )
7174 )
7175 repo.ui.popbuffer()
7175 repo.ui.popbuffer()
7176 return dest, dbranch, dother, outgoing
7176 return dest, dbranch, dother, outgoing
7177
7177
7178 if needsoutgoing:
7178 if needsoutgoing:
7179 dest, dbranch, dother, outgoing = getoutgoing()
7179 dest, dbranch, dother, outgoing = getoutgoing()
7180 else:
7180 else:
7181 dest = dbranch = dother = outgoing = None
7181 dest = dbranch = dother = outgoing = None
7182
7182
7183 if opts.get(b'remote'):
7183 if opts.get(b'remote'):
7184 t = []
7184 t = []
7185 if incoming:
7185 if incoming:
7186 t.append(_(b'1 or more incoming'))
7186 t.append(_(b'1 or more incoming'))
7187 o = outgoing.missing
7187 o = outgoing.missing
7188 if o:
7188 if o:
7189 t.append(_(b'%d outgoing') % len(o))
7189 t.append(_(b'%d outgoing') % len(o))
7190 other = dother or sother
7190 other = dother or sother
7191 if b'bookmarks' in other.listkeys(b'namespaces'):
7191 if b'bookmarks' in other.listkeys(b'namespaces'):
7192 counts = bookmarks.summary(repo, other)
7192 counts = bookmarks.summary(repo, other)
7193 if counts[0] > 0:
7193 if counts[0] > 0:
7194 t.append(_(b'%d incoming bookmarks') % counts[0])
7194 t.append(_(b'%d incoming bookmarks') % counts[0])
7195 if counts[1] > 0:
7195 if counts[1] > 0:
7196 t.append(_(b'%d outgoing bookmarks') % counts[1])
7196 t.append(_(b'%d outgoing bookmarks') % counts[1])
7197
7197
7198 if t:
7198 if t:
7199 # i18n: column positioning for "hg summary"
7199 # i18n: column positioning for "hg summary"
7200 ui.write(_(b'remote: %s\n') % (b', '.join(t)))
7200 ui.write(_(b'remote: %s\n') % (b', '.join(t)))
7201 else:
7201 else:
7202 # i18n: column positioning for "hg summary"
7202 # i18n: column positioning for "hg summary"
7203 ui.status(_(b'remote: (synced)\n'))
7203 ui.status(_(b'remote: (synced)\n'))
7204
7204
7205 cmdutil.summaryremotehooks(
7205 cmdutil.summaryremotehooks(
7206 ui,
7206 ui,
7207 repo,
7207 repo,
7208 opts,
7208 opts,
7209 (
7209 (
7210 (source, sbranch, sother, commoninc),
7210 (source, sbranch, sother, commoninc),
7211 (dest, dbranch, dother, outgoing),
7211 (dest, dbranch, dother, outgoing),
7212 ),
7212 ),
7213 )
7213 )
7214
7214
7215
7215
7216 @command(
7216 @command(
7217 b'tag',
7217 b'tag',
7218 [
7218 [
7219 (b'f', b'force', None, _(b'force tag')),
7219 (b'f', b'force', None, _(b'force tag')),
7220 (b'l', b'local', None, _(b'make the tag local')),
7220 (b'l', b'local', None, _(b'make the tag local')),
7221 (b'r', b'rev', b'', _(b'revision to tag'), _(b'REV')),
7221 (b'r', b'rev', b'', _(b'revision to tag'), _(b'REV')),
7222 (b'', b'remove', None, _(b'remove a tag')),
7222 (b'', b'remove', None, _(b'remove a tag')),
7223 # -l/--local is already there, commitopts cannot be used
7223 # -l/--local is already there, commitopts cannot be used
7224 (b'e', b'edit', None, _(b'invoke editor on commit messages')),
7224 (b'e', b'edit', None, _(b'invoke editor on commit messages')),
7225 (b'm', b'message', b'', _(b'use text as commit message'), _(b'TEXT')),
7225 (b'm', b'message', b'', _(b'use text as commit message'), _(b'TEXT')),
7226 ]
7226 ]
7227 + commitopts2,
7227 + commitopts2,
7228 _(b'[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'),
7228 _(b'[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'),
7229 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
7229 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
7230 )
7230 )
7231 def tag(ui, repo, name1, *names, **opts):
7231 def tag(ui, repo, name1, *names, **opts):
7232 """add one or more tags for the current or given revision
7232 """add one or more tags for the current or given revision
7233
7233
7234 Name a particular revision using <name>.
7234 Name a particular revision using <name>.
7235
7235
7236 Tags are used to name particular revisions of the repository and are
7236 Tags are used to name particular revisions of the repository and are
7237 very useful to compare different revisions, to go back to significant
7237 very useful to compare different revisions, to go back to significant
7238 earlier versions or to mark branch points as releases, etc. Changing
7238 earlier versions or to mark branch points as releases, etc. Changing
7239 an existing tag is normally disallowed; use -f/--force to override.
7239 an existing tag is normally disallowed; use -f/--force to override.
7240
7240
7241 If no revision is given, the parent of the working directory is
7241 If no revision is given, the parent of the working directory is
7242 used.
7242 used.
7243
7243
7244 To facilitate version control, distribution, and merging of tags,
7244 To facilitate version control, distribution, and merging of tags,
7245 they are stored as a file named ".hgtags" which is managed similarly
7245 they are stored as a file named ".hgtags" which is managed similarly
7246 to other project files and can be hand-edited if necessary. This
7246 to other project files and can be hand-edited if necessary. This
7247 also means that tagging creates a new commit. The file
7247 also means that tagging creates a new commit. The file
7248 ".hg/localtags" is used for local tags (not shared among
7248 ".hg/localtags" is used for local tags (not shared among
7249 repositories).
7249 repositories).
7250
7250
7251 Tag commits are usually made at the head of a branch. If the parent
7251 Tag commits are usually made at the head of a branch. If the parent
7252 of the working directory is not a branch head, :hg:`tag` aborts; use
7252 of the working directory is not a branch head, :hg:`tag` aborts; use
7253 -f/--force to force the tag commit to be based on a non-head
7253 -f/--force to force the tag commit to be based on a non-head
7254 changeset.
7254 changeset.
7255
7255
7256 See :hg:`help dates` for a list of formats valid for -d/--date.
7256 See :hg:`help dates` for a list of formats valid for -d/--date.
7257
7257
7258 Since tag names have priority over branch names during revision
7258 Since tag names have priority over branch names during revision
7259 lookup, using an existing branch name as a tag name is discouraged.
7259 lookup, using an existing branch name as a tag name is discouraged.
7260
7260
7261 Returns 0 on success.
7261 Returns 0 on success.
7262 """
7262 """
7263 opts = pycompat.byteskwargs(opts)
7263 opts = pycompat.byteskwargs(opts)
7264 with repo.wlock(), repo.lock():
7264 with repo.wlock(), repo.lock():
7265 rev_ = b"."
7265 rev_ = b"."
7266 names = [t.strip() for t in (name1,) + names]
7266 names = [t.strip() for t in (name1,) + names]
7267 if len(names) != len(set(names)):
7267 if len(names) != len(set(names)):
7268 raise error.Abort(_(b'tag names must be unique'))
7268 raise error.Abort(_(b'tag names must be unique'))
7269 for n in names:
7269 for n in names:
7270 scmutil.checknewlabel(repo, n, b'tag')
7270 scmutil.checknewlabel(repo, n, b'tag')
7271 if not n:
7271 if not n:
7272 raise error.Abort(
7272 raise error.Abort(
7273 _(b'tag names cannot consist entirely of whitespace')
7273 _(b'tag names cannot consist entirely of whitespace')
7274 )
7274 )
7275 if opts.get(b'rev') and opts.get(b'remove'):
7275 if opts.get(b'rev') and opts.get(b'remove'):
7276 raise error.Abort(_(b"--rev and --remove are incompatible"))
7276 raise error.Abort(_(b"--rev and --remove are incompatible"))
7277 if opts.get(b'rev'):
7277 if opts.get(b'rev'):
7278 rev_ = opts[b'rev']
7278 rev_ = opts[b'rev']
7279 message = opts.get(b'message')
7279 message = opts.get(b'message')
7280 if opts.get(b'remove'):
7280 if opts.get(b'remove'):
7281 if opts.get(b'local'):
7281 if opts.get(b'local'):
7282 expectedtype = b'local'
7282 expectedtype = b'local'
7283 else:
7283 else:
7284 expectedtype = b'global'
7284 expectedtype = b'global'
7285
7285
7286 for n in names:
7286 for n in names:
7287 if repo.tagtype(n) == b'global':
7287 if repo.tagtype(n) == b'global':
7288 alltags = tagsmod.findglobaltags(ui, repo)
7288 alltags = tagsmod.findglobaltags(ui, repo)
7289 if alltags[n][0] == nullid:
7289 if alltags[n][0] == nullid:
7290 raise error.Abort(_(b"tag '%s' is already removed") % n)
7290 raise error.Abort(_(b"tag '%s' is already removed") % n)
7291 if not repo.tagtype(n):
7291 if not repo.tagtype(n):
7292 raise error.Abort(_(b"tag '%s' does not exist") % n)
7292 raise error.Abort(_(b"tag '%s' does not exist") % n)
7293 if repo.tagtype(n) != expectedtype:
7293 if repo.tagtype(n) != expectedtype:
7294 if expectedtype == b'global':
7294 if expectedtype == b'global':
7295 raise error.Abort(
7295 raise error.Abort(
7296 _(b"tag '%s' is not a global tag") % n
7296 _(b"tag '%s' is not a global tag") % n
7297 )
7297 )
7298 else:
7298 else:
7299 raise error.Abort(_(b"tag '%s' is not a local tag") % n)
7299 raise error.Abort(_(b"tag '%s' is not a local tag") % n)
7300 rev_ = b'null'
7300 rev_ = b'null'
7301 if not message:
7301 if not message:
7302 # we don't translate commit messages
7302 # we don't translate commit messages
7303 message = b'Removed tag %s' % b', '.join(names)
7303 message = b'Removed tag %s' % b', '.join(names)
7304 elif not opts.get(b'force'):
7304 elif not opts.get(b'force'):
7305 for n in names:
7305 for n in names:
7306 if n in repo.tags():
7306 if n in repo.tags():
7307 raise error.Abort(
7307 raise error.Abort(
7308 _(b"tag '%s' already exists (use -f to force)") % n
7308 _(b"tag '%s' already exists (use -f to force)") % n
7309 )
7309 )
7310 if not opts.get(b'local'):
7310 if not opts.get(b'local'):
7311 p1, p2 = repo.dirstate.parents()
7311 p1, p2 = repo.dirstate.parents()
7312 if p2 != nullid:
7312 if p2 != nullid:
7313 raise error.Abort(_(b'uncommitted merge'))
7313 raise error.Abort(_(b'uncommitted merge'))
7314 bheads = repo.branchheads()
7314 bheads = repo.branchheads()
7315 if not opts.get(b'force') and bheads and p1 not in bheads:
7315 if not opts.get(b'force') and bheads and p1 not in bheads:
7316 raise error.Abort(
7316 raise error.Abort(
7317 _(
7317 _(
7318 b'working directory is not at a branch head '
7318 b'working directory is not at a branch head '
7319 b'(use -f to force)'
7319 b'(use -f to force)'
7320 )
7320 )
7321 )
7321 )
7322 node = scmutil.revsingle(repo, rev_).node()
7322 node = scmutil.revsingle(repo, rev_).node()
7323
7323
7324 if not message:
7324 if not message:
7325 # we don't translate commit messages
7325 # we don't translate commit messages
7326 message = b'Added tag %s for changeset %s' % (
7326 message = b'Added tag %s for changeset %s' % (
7327 b', '.join(names),
7327 b', '.join(names),
7328 short(node),
7328 short(node),
7329 )
7329 )
7330
7330
7331 date = opts.get(b'date')
7331 date = opts.get(b'date')
7332 if date:
7332 if date:
7333 date = dateutil.parsedate(date)
7333 date = dateutil.parsedate(date)
7334
7334
7335 if opts.get(b'remove'):
7335 if opts.get(b'remove'):
7336 editform = b'tag.remove'
7336 editform = b'tag.remove'
7337 else:
7337 else:
7338 editform = b'tag.add'
7338 editform = b'tag.add'
7339 editor = cmdutil.getcommiteditor(
7339 editor = cmdutil.getcommiteditor(
7340 editform=editform, **pycompat.strkwargs(opts)
7340 editform=editform, **pycompat.strkwargs(opts)
7341 )
7341 )
7342
7342
7343 # don't allow tagging the null rev
7343 # don't allow tagging the null rev
7344 if (
7344 if (
7345 not opts.get(b'remove')
7345 not opts.get(b'remove')
7346 and scmutil.revsingle(repo, rev_).rev() == nullrev
7346 and scmutil.revsingle(repo, rev_).rev() == nullrev
7347 ):
7347 ):
7348 raise error.Abort(_(b"cannot tag null revision"))
7348 raise error.Abort(_(b"cannot tag null revision"))
7349
7349
7350 tagsmod.tag(
7350 tagsmod.tag(
7351 repo,
7351 repo,
7352 names,
7352 names,
7353 node,
7353 node,
7354 message,
7354 message,
7355 opts.get(b'local'),
7355 opts.get(b'local'),
7356 opts.get(b'user'),
7356 opts.get(b'user'),
7357 date,
7357 date,
7358 editor=editor,
7358 editor=editor,
7359 )
7359 )
7360
7360
7361
7361
7362 @command(
7362 @command(
7363 b'tags',
7363 b'tags',
7364 formatteropts,
7364 formatteropts,
7365 b'',
7365 b'',
7366 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
7366 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
7367 intents={INTENT_READONLY},
7367 intents={INTENT_READONLY},
7368 )
7368 )
7369 def tags(ui, repo, **opts):
7369 def tags(ui, repo, **opts):
7370 """list repository tags
7370 """list repository tags
7371
7371
7372 This lists both regular and local tags. When the -v/--verbose
7372 This lists both regular and local tags. When the -v/--verbose
7373 switch is used, a third column "local" is printed for local tags.
7373 switch is used, a third column "local" is printed for local tags.
7374 When the -q/--quiet switch is used, only the tag name is printed.
7374 When the -q/--quiet switch is used, only the tag name is printed.
7375
7375
7376 .. container:: verbose
7376 .. container:: verbose
7377
7377
7378 Template:
7378 Template:
7379
7379
7380 The following keywords are supported in addition to the common template
7380 The following keywords are supported in addition to the common template
7381 keywords and functions such as ``{tag}``. See also
7381 keywords and functions such as ``{tag}``. See also
7382 :hg:`help templates`.
7382 :hg:`help templates`.
7383
7383
7384 :type: String. ``local`` for local tags.
7384 :type: String. ``local`` for local tags.
7385
7385
7386 Returns 0 on success.
7386 Returns 0 on success.
7387 """
7387 """
7388
7388
7389 opts = pycompat.byteskwargs(opts)
7389 opts = pycompat.byteskwargs(opts)
7390 ui.pager(b'tags')
7390 ui.pager(b'tags')
7391 fm = ui.formatter(b'tags', opts)
7391 fm = ui.formatter(b'tags', opts)
7392 hexfunc = fm.hexfunc
7392 hexfunc = fm.hexfunc
7393
7393
7394 for t, n in reversed(repo.tagslist()):
7394 for t, n in reversed(repo.tagslist()):
7395 hn = hexfunc(n)
7395 hn = hexfunc(n)
7396 label = b'tags.normal'
7396 label = b'tags.normal'
7397 tagtype = b''
7397 tagtype = b''
7398 if repo.tagtype(t) == b'local':
7398 if repo.tagtype(t) == b'local':
7399 label = b'tags.local'
7399 label = b'tags.local'
7400 tagtype = b'local'
7400 tagtype = b'local'
7401
7401
7402 fm.startitem()
7402 fm.startitem()
7403 fm.context(repo=repo)
7403 fm.context(repo=repo)
7404 fm.write(b'tag', b'%s', t, label=label)
7404 fm.write(b'tag', b'%s', t, label=label)
7405 fmt = b" " * (30 - encoding.colwidth(t)) + b' %5d:%s'
7405 fmt = b" " * (30 - encoding.colwidth(t)) + b' %5d:%s'
7406 fm.condwrite(
7406 fm.condwrite(
7407 not ui.quiet,
7407 not ui.quiet,
7408 b'rev node',
7408 b'rev node',
7409 fmt,
7409 fmt,
7410 repo.changelog.rev(n),
7410 repo.changelog.rev(n),
7411 hn,
7411 hn,
7412 label=label,
7412 label=label,
7413 )
7413 )
7414 fm.condwrite(
7414 fm.condwrite(
7415 ui.verbose and tagtype, b'type', b' %s', tagtype, label=label
7415 ui.verbose and tagtype, b'type', b' %s', tagtype, label=label
7416 )
7416 )
7417 fm.plain(b'\n')
7417 fm.plain(b'\n')
7418 fm.end()
7418 fm.end()
7419
7419
7420
7420
7421 @command(
7421 @command(
7422 b'tip',
7422 b'tip',
7423 [
7423 [
7424 (b'p', b'patch', None, _(b'show patch')),
7424 (b'p', b'patch', None, _(b'show patch')),
7425 (b'g', b'git', None, _(b'use git extended diff format')),
7425 (b'g', b'git', None, _(b'use git extended diff format')),
7426 ]
7426 ]
7427 + templateopts,
7427 + templateopts,
7428 _(b'[-p] [-g]'),
7428 _(b'[-p] [-g]'),
7429 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
7429 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
7430 )
7430 )
7431 def tip(ui, repo, **opts):
7431 def tip(ui, repo, **opts):
7432 """show the tip revision (DEPRECATED)
7432 """show the tip revision (DEPRECATED)
7433
7433
7434 The tip revision (usually just called the tip) is the changeset
7434 The tip revision (usually just called the tip) is the changeset
7435 most recently added to the repository (and therefore the most
7435 most recently added to the repository (and therefore the most
7436 recently changed head).
7436 recently changed head).
7437
7437
7438 If you have just made a commit, that commit will be the tip. If
7438 If you have just made a commit, that commit will be the tip. If
7439 you have just pulled changes from another repository, the tip of
7439 you have just pulled changes from another repository, the tip of
7440 that repository becomes the current tip. The "tip" tag is special
7440 that repository becomes the current tip. The "tip" tag is special
7441 and cannot be renamed or assigned to a different changeset.
7441 and cannot be renamed or assigned to a different changeset.
7442
7442
7443 This command is deprecated, please use :hg:`heads` instead.
7443 This command is deprecated, please use :hg:`heads` instead.
7444
7444
7445 Returns 0 on success.
7445 Returns 0 on success.
7446 """
7446 """
7447 opts = pycompat.byteskwargs(opts)
7447 opts = pycompat.byteskwargs(opts)
7448 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
7448 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
7449 displayer.show(repo[b'tip'])
7449 displayer.show(repo[b'tip'])
7450 displayer.close()
7450 displayer.close()
7451
7451
7452
7452
7453 @command(
7453 @command(
7454 b'unbundle',
7454 b'unbundle',
7455 [
7455 [
7456 (
7456 (
7457 b'u',
7457 b'u',
7458 b'update',
7458 b'update',
7459 None,
7459 None,
7460 _(b'update to new branch head if changesets were unbundled'),
7460 _(b'update to new branch head if changesets were unbundled'),
7461 )
7461 )
7462 ],
7462 ],
7463 _(b'[-u] FILE...'),
7463 _(b'[-u] FILE...'),
7464 helpcategory=command.CATEGORY_IMPORT_EXPORT,
7464 helpcategory=command.CATEGORY_IMPORT_EXPORT,
7465 )
7465 )
7466 def unbundle(ui, repo, fname1, *fnames, **opts):
7466 def unbundle(ui, repo, fname1, *fnames, **opts):
7467 """apply one or more bundle files
7467 """apply one or more bundle files
7468
7468
7469 Apply one or more bundle files generated by :hg:`bundle`.
7469 Apply one or more bundle files generated by :hg:`bundle`.
7470
7470
7471 Returns 0 on success, 1 if an update has unresolved files.
7471 Returns 0 on success, 1 if an update has unresolved files.
7472 """
7472 """
7473 fnames = (fname1,) + fnames
7473 fnames = (fname1,) + fnames
7474
7474
7475 with repo.lock():
7475 with repo.lock():
7476 for fname in fnames:
7476 for fname in fnames:
7477 f = hg.openpath(ui, fname)
7477 f = hg.openpath(ui, fname)
7478 gen = exchange.readbundle(ui, f, fname)
7478 gen = exchange.readbundle(ui, f, fname)
7479 if isinstance(gen, streamclone.streamcloneapplier):
7479 if isinstance(gen, streamclone.streamcloneapplier):
7480 raise error.Abort(
7480 raise error.Abort(
7481 _(
7481 _(
7482 b'packed bundles cannot be applied with '
7482 b'packed bundles cannot be applied with '
7483 b'"hg unbundle"'
7483 b'"hg unbundle"'
7484 ),
7484 ),
7485 hint=_(b'use "hg debugapplystreamclonebundle"'),
7485 hint=_(b'use "hg debugapplystreamclonebundle"'),
7486 )
7486 )
7487 url = b'bundle:' + fname
7487 url = b'bundle:' + fname
7488 try:
7488 try:
7489 txnname = b'unbundle'
7489 txnname = b'unbundle'
7490 if not isinstance(gen, bundle2.unbundle20):
7490 if not isinstance(gen, bundle2.unbundle20):
7491 txnname = b'unbundle\n%s' % util.hidepassword(url)
7491 txnname = b'unbundle\n%s' % util.hidepassword(url)
7492 with repo.transaction(txnname) as tr:
7492 with repo.transaction(txnname) as tr:
7493 op = bundle2.applybundle(
7493 op = bundle2.applybundle(
7494 repo, gen, tr, source=b'unbundle', url=url
7494 repo, gen, tr, source=b'unbundle', url=url
7495 )
7495 )
7496 except error.BundleUnknownFeatureError as exc:
7496 except error.BundleUnknownFeatureError as exc:
7497 raise error.Abort(
7497 raise error.Abort(
7498 _(b'%s: unknown bundle feature, %s') % (fname, exc),
7498 _(b'%s: unknown bundle feature, %s') % (fname, exc),
7499 hint=_(
7499 hint=_(
7500 b"see https://mercurial-scm.org/"
7500 b"see https://mercurial-scm.org/"
7501 b"wiki/BundleFeature for more "
7501 b"wiki/BundleFeature for more "
7502 b"information"
7502 b"information"
7503 ),
7503 ),
7504 )
7504 )
7505 modheads = bundle2.combinechangegroupresults(op)
7505 modheads = bundle2.combinechangegroupresults(op)
7506
7506
7507 return postincoming(ui, repo, modheads, opts.get('update'), None, None)
7507 return postincoming(ui, repo, modheads, opts.get('update'), None, None)
7508
7508
7509
7509
7510 @command(
7510 @command(
7511 b'unshelve',
7511 b'unshelve',
7512 [
7512 [
7513 (b'a', b'abort', None, _(b'abort an incomplete unshelve operation')),
7513 (b'a', b'abort', None, _(b'abort an incomplete unshelve operation')),
7514 (
7514 (
7515 b'c',
7515 b'c',
7516 b'continue',
7516 b'continue',
7517 None,
7517 None,
7518 _(b'continue an incomplete unshelve operation'),
7518 _(b'continue an incomplete unshelve operation'),
7519 ),
7519 ),
7520 (b'i', b'interactive', None, _(b'use interactive mode (EXPERIMENTAL)')),
7520 (b'i', b'interactive', None, _(b'use interactive mode (EXPERIMENTAL)')),
7521 (b'k', b'keep', None, _(b'keep shelve after unshelving')),
7521 (b'k', b'keep', None, _(b'keep shelve after unshelving')),
7522 (
7522 (
7523 b'n',
7523 b'n',
7524 b'name',
7524 b'name',
7525 b'',
7525 b'',
7526 _(b'restore shelved change with given name'),
7526 _(b'restore shelved change with given name'),
7527 _(b'NAME'),
7527 _(b'NAME'),
7528 ),
7528 ),
7529 (b't', b'tool', b'', _(b'specify merge tool')),
7529 (b't', b'tool', b'', _(b'specify merge tool')),
7530 (
7530 (
7531 b'',
7531 b'',
7532 b'date',
7532 b'date',
7533 b'',
7533 b'',
7534 _(b'set date for temporary commits (DEPRECATED)'),
7534 _(b'set date for temporary commits (DEPRECATED)'),
7535 _(b'DATE'),
7535 _(b'DATE'),
7536 ),
7536 ),
7537 ],
7537 ],
7538 _(b'hg unshelve [OPTION]... [[-n] SHELVED]'),
7538 _(b'hg unshelve [OPTION]... [[-n] SHELVED]'),
7539 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
7539 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
7540 )
7540 )
7541 def unshelve(ui, repo, *shelved, **opts):
7541 def unshelve(ui, repo, *shelved, **opts):
7542 """restore a shelved change to the working directory
7542 """restore a shelved change to the working directory
7543
7543
7544 This command accepts an optional name of a shelved change to
7544 This command accepts an optional name of a shelved change to
7545 restore. If none is given, the most recent shelved change is used.
7545 restore. If none is given, the most recent shelved change is used.
7546
7546
7547 If a shelved change is applied successfully, the bundle that
7547 If a shelved change is applied successfully, the bundle that
7548 contains the shelved changes is moved to a backup location
7548 contains the shelved changes is moved to a backup location
7549 (.hg/shelve-backup).
7549 (.hg/shelve-backup).
7550
7550
7551 Since you can restore a shelved change on top of an arbitrary
7551 Since you can restore a shelved change on top of an arbitrary
7552 commit, it is possible that unshelving will result in a conflict
7552 commit, it is possible that unshelving will result in a conflict
7553 between your changes and the commits you are unshelving onto. If
7553 between your changes and the commits you are unshelving onto. If
7554 this occurs, you must resolve the conflict, then use
7554 this occurs, you must resolve the conflict, then use
7555 ``--continue`` to complete the unshelve operation. (The bundle
7555 ``--continue`` to complete the unshelve operation. (The bundle
7556 will not be moved until you successfully complete the unshelve.)
7556 will not be moved until you successfully complete the unshelve.)
7557
7557
7558 (Alternatively, you can use ``--abort`` to abandon an unshelve
7558 (Alternatively, you can use ``--abort`` to abandon an unshelve
7559 that causes a conflict. This reverts the unshelved changes, and
7559 that causes a conflict. This reverts the unshelved changes, and
7560 leaves the bundle in place.)
7560 leaves the bundle in place.)
7561
7561
7562 If bare shelved change (without interactive, include and exclude
7562 If bare shelved change (without interactive, include and exclude
7563 option) was done on newly created branch it would restore branch
7563 option) was done on newly created branch it would restore branch
7564 information to the working directory.
7564 information to the working directory.
7565
7565
7566 After a successful unshelve, the shelved changes are stored in a
7566 After a successful unshelve, the shelved changes are stored in a
7567 backup directory. Only the N most recent backups are kept. N
7567 backup directory. Only the N most recent backups are kept. N
7568 defaults to 10 but can be overridden using the ``shelve.maxbackups``
7568 defaults to 10 but can be overridden using the ``shelve.maxbackups``
7569 configuration option.
7569 configuration option.
7570
7570
7571 .. container:: verbose
7571 .. container:: verbose
7572
7572
7573 Timestamp in seconds is used to decide order of backups. More
7573 Timestamp in seconds is used to decide order of backups. More
7574 than ``maxbackups`` backups are kept, if same timestamp
7574 than ``maxbackups`` backups are kept, if same timestamp
7575 prevents from deciding exact order of them, for safety.
7575 prevents from deciding exact order of them, for safety.
7576
7576
7577 Selected changes can be unshelved with ``--interactive`` flag.
7577 Selected changes can be unshelved with ``--interactive`` flag.
7578 The working directory is updated with the selected changes, and
7578 The working directory is updated with the selected changes, and
7579 only the unselected changes remain shelved.
7579 only the unselected changes remain shelved.
7580 Note: The whole shelve is applied to working directory first before
7580 Note: The whole shelve is applied to working directory first before
7581 running interactively. So, this will bring up all the conflicts between
7581 running interactively. So, this will bring up all the conflicts between
7582 working directory and the shelve, irrespective of which changes will be
7582 working directory and the shelve, irrespective of which changes will be
7583 unshelved.
7583 unshelved.
7584 """
7584 """
7585 with repo.wlock():
7585 with repo.wlock():
7586 return shelvemod.dounshelve(ui, repo, *shelved, **opts)
7586 return shelvemod.dounshelve(ui, repo, *shelved, **opts)
7587
7587
7588
7588
7589 statemod.addunfinished(
7589 statemod.addunfinished(
7590 b'unshelve',
7590 b'unshelve',
7591 fname=b'shelvedstate',
7591 fname=b'shelvedstate',
7592 continueflag=True,
7592 continueflag=True,
7593 abortfunc=shelvemod.hgabortunshelve,
7593 abortfunc=shelvemod.hgabortunshelve,
7594 continuefunc=shelvemod.hgcontinueunshelve,
7594 continuefunc=shelvemod.hgcontinueunshelve,
7595 cmdmsg=_(b'unshelve already in progress'),
7595 cmdmsg=_(b'unshelve already in progress'),
7596 )
7596 )
7597
7597
7598
7598
7599 @command(
7599 @command(
7600 b'update|up|checkout|co',
7600 b'update|up|checkout|co',
7601 [
7601 [
7602 (b'C', b'clean', None, _(b'discard uncommitted changes (no backup)')),
7602 (b'C', b'clean', None, _(b'discard uncommitted changes (no backup)')),
7603 (b'c', b'check', None, _(b'require clean working directory')),
7603 (b'c', b'check', None, _(b'require clean working directory')),
7604 (b'm', b'merge', None, _(b'merge uncommitted changes')),
7604 (b'm', b'merge', None, _(b'merge uncommitted changes')),
7605 (b'd', b'date', b'', _(b'tipmost revision matching date'), _(b'DATE')),
7605 (b'd', b'date', b'', _(b'tipmost revision matching date'), _(b'DATE')),
7606 (b'r', b'rev', b'', _(b'revision'), _(b'REV')),
7606 (b'r', b'rev', b'', _(b'revision'), _(b'REV')),
7607 ]
7607 ]
7608 + mergetoolopts,
7608 + mergetoolopts,
7609 _(b'[-C|-c|-m] [-d DATE] [[-r] REV]'),
7609 _(b'[-C|-c|-m] [-d DATE] [[-r] REV]'),
7610 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
7610 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
7611 helpbasic=True,
7611 helpbasic=True,
7612 )
7612 )
7613 def update(ui, repo, node=None, **opts):
7613 def update(ui, repo, node=None, **opts):
7614 """update working directory (or switch revisions)
7614 """update working directory (or switch revisions)
7615
7615
7616 Update the repository's working directory to the specified
7616 Update the repository's working directory to the specified
7617 changeset. If no changeset is specified, update to the tip of the
7617 changeset. If no changeset is specified, update to the tip of the
7618 current named branch and move the active bookmark (see :hg:`help
7618 current named branch and move the active bookmark (see :hg:`help
7619 bookmarks`).
7619 bookmarks`).
7620
7620
7621 Update sets the working directory's parent revision to the specified
7621 Update sets the working directory's parent revision to the specified
7622 changeset (see :hg:`help parents`).
7622 changeset (see :hg:`help parents`).
7623
7623
7624 If the changeset is not a descendant or ancestor of the working
7624 If the changeset is not a descendant or ancestor of the working
7625 directory's parent and there are uncommitted changes, the update is
7625 directory's parent and there are uncommitted changes, the update is
7626 aborted. With the -c/--check option, the working directory is checked
7626 aborted. With the -c/--check option, the working directory is checked
7627 for uncommitted changes; if none are found, the working directory is
7627 for uncommitted changes; if none are found, the working directory is
7628 updated to the specified changeset.
7628 updated to the specified changeset.
7629
7629
7630 .. container:: verbose
7630 .. container:: verbose
7631
7631
7632 The -C/--clean, -c/--check, and -m/--merge options control what
7632 The -C/--clean, -c/--check, and -m/--merge options control what
7633 happens if the working directory contains uncommitted changes.
7633 happens if the working directory contains uncommitted changes.
7634 At most of one of them can be specified.
7634 At most of one of them can be specified.
7635
7635
7636 1. If no option is specified, and if
7636 1. If no option is specified, and if
7637 the requested changeset is an ancestor or descendant of
7637 the requested changeset is an ancestor or descendant of
7638 the working directory's parent, the uncommitted changes
7638 the working directory's parent, the uncommitted changes
7639 are merged into the requested changeset and the merged
7639 are merged into the requested changeset and the merged
7640 result is left uncommitted. If the requested changeset is
7640 result is left uncommitted. If the requested changeset is
7641 not an ancestor or descendant (that is, it is on another
7641 not an ancestor or descendant (that is, it is on another
7642 branch), the update is aborted and the uncommitted changes
7642 branch), the update is aborted and the uncommitted changes
7643 are preserved.
7643 are preserved.
7644
7644
7645 2. With the -m/--merge option, the update is allowed even if the
7645 2. With the -m/--merge option, the update is allowed even if the
7646 requested changeset is not an ancestor or descendant of
7646 requested changeset is not an ancestor or descendant of
7647 the working directory's parent.
7647 the working directory's parent.
7648
7648
7649 3. With the -c/--check option, the update is aborted and the
7649 3. With the -c/--check option, the update is aborted and the
7650 uncommitted changes are preserved.
7650 uncommitted changes are preserved.
7651
7651
7652 4. With the -C/--clean option, uncommitted changes are discarded and
7652 4. With the -C/--clean option, uncommitted changes are discarded and
7653 the working directory is updated to the requested changeset.
7653 the working directory is updated to the requested changeset.
7654
7654
7655 To cancel an uncommitted merge (and lose your changes), use
7655 To cancel an uncommitted merge (and lose your changes), use
7656 :hg:`merge --abort`.
7656 :hg:`merge --abort`.
7657
7657
7658 Use null as the changeset to remove the working directory (like
7658 Use null as the changeset to remove the working directory (like
7659 :hg:`clone -U`).
7659 :hg:`clone -U`).
7660
7660
7661 If you want to revert just one file to an older revision, use
7661 If you want to revert just one file to an older revision, use
7662 :hg:`revert [-r REV] NAME`.
7662 :hg:`revert [-r REV] NAME`.
7663
7663
7664 See :hg:`help dates` for a list of formats valid for -d/--date.
7664 See :hg:`help dates` for a list of formats valid for -d/--date.
7665
7665
7666 Returns 0 on success, 1 if there are unresolved files.
7666 Returns 0 on success, 1 if there are unresolved files.
7667 """
7667 """
7668 cmdutil.check_at_most_one_arg(opts, b'clean', b'check', b'merge')
7668 cmdutil.check_at_most_one_arg(opts, b'clean', b'check', b'merge')
7669 rev = opts.get('rev')
7669 rev = opts.get('rev')
7670 date = opts.get('date')
7670 date = opts.get('date')
7671 clean = opts.get('clean')
7671 clean = opts.get('clean')
7672 check = opts.get('check')
7672 check = opts.get('check')
7673 merge = opts.get('merge')
7673 merge = opts.get('merge')
7674 if rev and node:
7674 if rev and node:
7675 raise error.Abort(_(b"please specify just one revision"))
7675 raise error.Abort(_(b"please specify just one revision"))
7676
7676
7677 if ui.configbool(b'commands', b'update.requiredest'):
7677 if ui.configbool(b'commands', b'update.requiredest'):
7678 if not node and not rev and not date:
7678 if not node and not rev and not date:
7679 raise error.Abort(
7679 raise error.Abort(
7680 _(b'you must specify a destination'),
7680 _(b'you must specify a destination'),
7681 hint=_(b'for example: hg update ".::"'),
7681 hint=_(b'for example: hg update ".::"'),
7682 )
7682 )
7683
7683
7684 if rev is None or rev == b'':
7684 if rev is None or rev == b'':
7685 rev = node
7685 rev = node
7686
7686
7687 if date and rev is not None:
7687 if date and rev is not None:
7688 raise error.Abort(_(b"you can't specify a revision and a date"))
7688 raise error.Abort(_(b"you can't specify a revision and a date"))
7689
7689
7690 updatecheck = None
7690 updatecheck = None
7691 if check:
7691 if check:
7692 updatecheck = b'abort'
7692 updatecheck = b'abort'
7693 elif merge:
7693 elif merge:
7694 updatecheck = b'none'
7694 updatecheck = b'none'
7695
7695
7696 with repo.wlock():
7696 with repo.wlock():
7697 cmdutil.clearunfinished(repo)
7697 cmdutil.clearunfinished(repo)
7698 if date:
7698 if date:
7699 rev = cmdutil.finddate(ui, repo, date)
7699 rev = cmdutil.finddate(ui, repo, date)
7700
7700
7701 # if we defined a bookmark, we have to remember the original name
7701 # if we defined a bookmark, we have to remember the original name
7702 brev = rev
7702 brev = rev
7703 if rev:
7703 if rev:
7704 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
7704 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
7705 ctx = scmutil.revsingle(repo, rev, default=None)
7705 ctx = scmutil.revsingle(repo, rev, default=None)
7706 rev = ctx.rev()
7706 rev = ctx.rev()
7707 hidden = ctx.hidden()
7707 hidden = ctx.hidden()
7708 overrides = {(b'ui', b'forcemerge'): opts.get('tool', b'')}
7708 overrides = {(b'ui', b'forcemerge'): opts.get('tool', b'')}
7709 with ui.configoverride(overrides, b'update'):
7709 with ui.configoverride(overrides, b'update'):
7710 ret = hg.updatetotally(
7710 ret = hg.updatetotally(
7711 ui, repo, rev, brev, clean=clean, updatecheck=updatecheck
7711 ui, repo, rev, brev, clean=clean, updatecheck=updatecheck
7712 )
7712 )
7713 if hidden:
7713 if hidden:
7714 ctxstr = ctx.hex()[:12]
7714 ctxstr = ctx.hex()[:12]
7715 ui.warn(_(b"updated to hidden changeset %s\n") % ctxstr)
7715 ui.warn(_(b"updated to hidden changeset %s\n") % ctxstr)
7716
7716
7717 if ctx.obsolete():
7717 if ctx.obsolete():
7718 obsfatemsg = obsutil._getfilteredreason(repo, ctxstr, ctx)
7718 obsfatemsg = obsutil._getfilteredreason(repo, ctxstr, ctx)
7719 ui.warn(b"(%s)\n" % obsfatemsg)
7719 ui.warn(b"(%s)\n" % obsfatemsg)
7720 return ret
7720 return ret
7721
7721
7722
7722
7723 @command(
7723 @command(
7724 b'verify',
7724 b'verify',
7725 [(b'', b'full', False, b'perform more checks (EXPERIMENTAL)')],
7725 [(b'', b'full', False, b'perform more checks (EXPERIMENTAL)')],
7726 helpcategory=command.CATEGORY_MAINTENANCE,
7726 helpcategory=command.CATEGORY_MAINTENANCE,
7727 )
7727 )
7728 def verify(ui, repo, **opts):
7728 def verify(ui, repo, **opts):
7729 """verify the integrity of the repository
7729 """verify the integrity of the repository
7730
7730
7731 Verify the integrity of the current repository.
7731 Verify the integrity of the current repository.
7732
7732
7733 This will perform an extensive check of the repository's
7733 This will perform an extensive check of the repository's
7734 integrity, validating the hashes and checksums of each entry in
7734 integrity, validating the hashes and checksums of each entry in
7735 the changelog, manifest, and tracked files, as well as the
7735 the changelog, manifest, and tracked files, as well as the
7736 integrity of their crosslinks and indices.
7736 integrity of their crosslinks and indices.
7737
7737
7738 Please see https://mercurial-scm.org/wiki/RepositoryCorruption
7738 Please see https://mercurial-scm.org/wiki/RepositoryCorruption
7739 for more information about recovery from corruption of the
7739 for more information about recovery from corruption of the
7740 repository.
7740 repository.
7741
7741
7742 Returns 0 on success, 1 if errors are encountered.
7742 Returns 0 on success, 1 if errors are encountered.
7743 """
7743 """
7744 opts = pycompat.byteskwargs(opts)
7744 opts = pycompat.byteskwargs(opts)
7745
7745
7746 level = None
7746 level = None
7747 if opts[b'full']:
7747 if opts[b'full']:
7748 level = verifymod.VERIFY_FULL
7748 level = verifymod.VERIFY_FULL
7749 return hg.verify(repo, level)
7749 return hg.verify(repo, level)
7750
7750
7751
7751
7752 @command(
7752 @command(
7753 b'version',
7753 b'version',
7754 [] + formatteropts,
7754 [] + formatteropts,
7755 helpcategory=command.CATEGORY_HELP,
7755 helpcategory=command.CATEGORY_HELP,
7756 norepo=True,
7756 norepo=True,
7757 intents={INTENT_READONLY},
7757 intents={INTENT_READONLY},
7758 )
7758 )
7759 def version_(ui, **opts):
7759 def version_(ui, **opts):
7760 """output version and copyright information
7760 """output version and copyright information
7761
7761
7762 .. container:: verbose
7762 .. container:: verbose
7763
7763
7764 Template:
7764 Template:
7765
7765
7766 The following keywords are supported. See also :hg:`help templates`.
7766 The following keywords are supported. See also :hg:`help templates`.
7767
7767
7768 :extensions: List of extensions.
7768 :extensions: List of extensions.
7769 :ver: String. Version number.
7769 :ver: String. Version number.
7770
7770
7771 And each entry of ``{extensions}`` provides the following sub-keywords
7771 And each entry of ``{extensions}`` provides the following sub-keywords
7772 in addition to ``{ver}``.
7772 in addition to ``{ver}``.
7773
7773
7774 :bundled: Boolean. True if included in the release.
7774 :bundled: Boolean. True if included in the release.
7775 :name: String. Extension name.
7775 :name: String. Extension name.
7776 """
7776 """
7777 opts = pycompat.byteskwargs(opts)
7777 opts = pycompat.byteskwargs(opts)
7778 if ui.verbose:
7778 if ui.verbose:
7779 ui.pager(b'version')
7779 ui.pager(b'version')
7780 fm = ui.formatter(b"version", opts)
7780 fm = ui.formatter(b"version", opts)
7781 fm.startitem()
7781 fm.startitem()
7782 fm.write(
7782 fm.write(
7783 b"ver", _(b"Mercurial Distributed SCM (version %s)\n"), util.version()
7783 b"ver", _(b"Mercurial Distributed SCM (version %s)\n"), util.version()
7784 )
7784 )
7785 license = _(
7785 license = _(
7786 b"(see https://mercurial-scm.org for more information)\n"
7786 b"(see https://mercurial-scm.org for more information)\n"
7787 b"\nCopyright (C) 2005-2020 Matt Mackall and others\n"
7787 b"\nCopyright (C) 2005-2020 Matt Mackall and others\n"
7788 b"This is free software; see the source for copying conditions. "
7788 b"This is free software; see the source for copying conditions. "
7789 b"There is NO\nwarranty; "
7789 b"There is NO\nwarranty; "
7790 b"not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
7790 b"not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
7791 )
7791 )
7792 if not ui.quiet:
7792 if not ui.quiet:
7793 fm.plain(license)
7793 fm.plain(license)
7794
7794
7795 if ui.verbose:
7795 if ui.verbose:
7796 fm.plain(_(b"\nEnabled extensions:\n\n"))
7796 fm.plain(_(b"\nEnabled extensions:\n\n"))
7797 # format names and versions into columns
7797 # format names and versions into columns
7798 names = []
7798 names = []
7799 vers = []
7799 vers = []
7800 isinternals = []
7800 isinternals = []
7801 for name, module in extensions.extensions():
7801 for name, module in extensions.extensions():
7802 names.append(name)
7802 names.append(name)
7803 vers.append(extensions.moduleversion(module) or None)
7803 vers.append(extensions.moduleversion(module) or None)
7804 isinternals.append(extensions.ismoduleinternal(module))
7804 isinternals.append(extensions.ismoduleinternal(module))
7805 fn = fm.nested(b"extensions", tmpl=b'{name}\n')
7805 fn = fm.nested(b"extensions", tmpl=b'{name}\n')
7806 if names:
7806 if names:
7807 namefmt = b" %%-%ds " % max(len(n) for n in names)
7807 namefmt = b" %%-%ds " % max(len(n) for n in names)
7808 places = [_(b"external"), _(b"internal")]
7808 places = [_(b"external"), _(b"internal")]
7809 for n, v, p in zip(names, vers, isinternals):
7809 for n, v, p in zip(names, vers, isinternals):
7810 fn.startitem()
7810 fn.startitem()
7811 fn.condwrite(ui.verbose, b"name", namefmt, n)
7811 fn.condwrite(ui.verbose, b"name", namefmt, n)
7812 if ui.verbose:
7812 if ui.verbose:
7813 fn.plain(b"%s " % places[p])
7813 fn.plain(b"%s " % places[p])
7814 fn.data(bundled=p)
7814 fn.data(bundled=p)
7815 fn.condwrite(ui.verbose and v, b"ver", b"%s", v)
7815 fn.condwrite(ui.verbose and v, b"ver", b"%s", v)
7816 if ui.verbose:
7816 if ui.verbose:
7817 fn.plain(b"\n")
7817 fn.plain(b"\n")
7818 fn.end()
7818 fn.end()
7819 fm.end()
7819 fm.end()
7820
7820
7821
7821
7822 def loadcmdtable(ui, name, cmdtable):
7822 def loadcmdtable(ui, name, cmdtable):
7823 """Load command functions from specified cmdtable
7823 """Load command functions from specified cmdtable
7824 """
7824 """
7825 overrides = [cmd for cmd in cmdtable if cmd in table]
7825 overrides = [cmd for cmd in cmdtable if cmd in table]
7826 if overrides:
7826 if overrides:
7827 ui.warn(
7827 ui.warn(
7828 _(b"extension '%s' overrides commands: %s\n")
7828 _(b"extension '%s' overrides commands: %s\n")
7829 % (name, b" ".join(overrides))
7829 % (name, b" ".join(overrides))
7830 )
7830 )
7831 table.update(cmdtable)
7831 table.update(cmdtable)
@@ -1,1442 +1,1443 b''
1 # hg.py - repository classes for mercurial
1 # hg.py - repository classes for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
4 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 from __future__ import absolute_import
9 from __future__ import absolute_import
10
10
11 import errno
11 import errno
12 import os
12 import os
13 import shutil
13 import shutil
14 import stat
14 import stat
15
15
16 from .i18n import _
16 from .i18n import _
17 from .node import nullid
17 from .node import nullid
18 from .pycompat import getattr
18 from .pycompat import getattr
19
19
20 from . import (
20 from . import (
21 bookmarks,
21 bookmarks,
22 bundlerepo,
22 bundlerepo,
23 cacheutil,
23 cacheutil,
24 cmdutil,
24 cmdutil,
25 destutil,
25 destutil,
26 discovery,
26 discovery,
27 error,
27 error,
28 exchange,
28 exchange,
29 extensions,
29 extensions,
30 httppeer,
30 httppeer,
31 localrepo,
31 localrepo,
32 lock,
32 lock,
33 logcmdutil,
33 logcmdutil,
34 logexchange,
34 logexchange,
35 merge as mergemod,
35 merge as mergemod,
36 narrowspec,
36 narrowspec,
37 node,
37 node,
38 phases,
38 phases,
39 pycompat,
39 pycompat,
40 scmutil,
40 scmutil,
41 sshpeer,
41 sshpeer,
42 statichttprepo,
42 statichttprepo,
43 ui as uimod,
43 ui as uimod,
44 unionrepo,
44 unionrepo,
45 url,
45 url,
46 util,
46 util,
47 verify as verifymod,
47 verify as verifymod,
48 vfs as vfsmod,
48 vfs as vfsmod,
49 )
49 )
50 from .utils import hashutil
50 from .utils import hashutil
51 from .interfaces import repository as repositorymod
51 from .interfaces import repository as repositorymod
52
52
53 release = lock.release
53 release = lock.release
54
54
55 # shared features
55 # shared features
56 sharedbookmarks = b'bookmarks'
56 sharedbookmarks = b'bookmarks'
57
57
58
58
59 def _local(path):
59 def _local(path):
60 path = util.expandpath(util.urllocalpath(path))
60 path = util.expandpath(util.urllocalpath(path))
61
61
62 try:
62 try:
63 isfile = os.path.isfile(path)
63 isfile = os.path.isfile(path)
64 # Python 2 raises TypeError, Python 3 ValueError.
64 # Python 2 raises TypeError, Python 3 ValueError.
65 except (TypeError, ValueError) as e:
65 except (TypeError, ValueError) as e:
66 raise error.Abort(
66 raise error.Abort(
67 _(b'invalid path %s: %s') % (path, pycompat.bytestr(e))
67 _(b'invalid path %s: %s') % (path, pycompat.bytestr(e))
68 )
68 )
69
69
70 return isfile and bundlerepo or localrepo
70 return isfile and bundlerepo or localrepo
71
71
72
72
73 def addbranchrevs(lrepo, other, branches, revs):
73 def addbranchrevs(lrepo, other, branches, revs):
74 peer = other.peer() # a courtesy to callers using a localrepo for other
74 peer = other.peer() # a courtesy to callers using a localrepo for other
75 hashbranch, branches = branches
75 hashbranch, branches = branches
76 if not hashbranch and not branches:
76 if not hashbranch and not branches:
77 x = revs or None
77 x = revs or None
78 if revs:
78 if revs:
79 y = revs[0]
79 y = revs[0]
80 else:
80 else:
81 y = None
81 y = None
82 return x, y
82 return x, y
83 if revs:
83 if revs:
84 revs = list(revs)
84 revs = list(revs)
85 else:
85 else:
86 revs = []
86 revs = []
87
87
88 if not peer.capable(b'branchmap'):
88 if not peer.capable(b'branchmap'):
89 if branches:
89 if branches:
90 raise error.Abort(_(b"remote branch lookup not supported"))
90 raise error.Abort(_(b"remote branch lookup not supported"))
91 revs.append(hashbranch)
91 revs.append(hashbranch)
92 return revs, revs[0]
92 return revs, revs[0]
93
93
94 with peer.commandexecutor() as e:
94 with peer.commandexecutor() as e:
95 branchmap = e.callcommand(b'branchmap', {}).result()
95 branchmap = e.callcommand(b'branchmap', {}).result()
96
96
97 def primary(branch):
97 def primary(branch):
98 if branch == b'.':
98 if branch == b'.':
99 if not lrepo:
99 if not lrepo:
100 raise error.Abort(_(b"dirstate branch not accessible"))
100 raise error.Abort(_(b"dirstate branch not accessible"))
101 branch = lrepo.dirstate.branch()
101 branch = lrepo.dirstate.branch()
102 if branch in branchmap:
102 if branch in branchmap:
103 revs.extend(node.hex(r) for r in reversed(branchmap[branch]))
103 revs.extend(node.hex(r) for r in reversed(branchmap[branch]))
104 return True
104 return True
105 else:
105 else:
106 return False
106 return False
107
107
108 for branch in branches:
108 for branch in branches:
109 if not primary(branch):
109 if not primary(branch):
110 raise error.RepoLookupError(_(b"unknown branch '%s'") % branch)
110 raise error.RepoLookupError(_(b"unknown branch '%s'") % branch)
111 if hashbranch:
111 if hashbranch:
112 if not primary(hashbranch):
112 if not primary(hashbranch):
113 revs.append(hashbranch)
113 revs.append(hashbranch)
114 return revs, revs[0]
114 return revs, revs[0]
115
115
116
116
117 def parseurl(path, branches=None):
117 def parseurl(path, branches=None):
118 '''parse url#branch, returning (url, (branch, branches))'''
118 '''parse url#branch, returning (url, (branch, branches))'''
119
119
120 u = util.url(path)
120 u = util.url(path)
121 branch = None
121 branch = None
122 if u.fragment:
122 if u.fragment:
123 branch = u.fragment
123 branch = u.fragment
124 u.fragment = None
124 u.fragment = None
125 return bytes(u), (branch, branches or [])
125 return bytes(u), (branch, branches or [])
126
126
127
127
128 schemes = {
128 schemes = {
129 b'bundle': bundlerepo,
129 b'bundle': bundlerepo,
130 b'union': unionrepo,
130 b'union': unionrepo,
131 b'file': _local,
131 b'file': _local,
132 b'http': httppeer,
132 b'http': httppeer,
133 b'https': httppeer,
133 b'https': httppeer,
134 b'ssh': sshpeer,
134 b'ssh': sshpeer,
135 b'static-http': statichttprepo,
135 b'static-http': statichttprepo,
136 }
136 }
137
137
138
138
139 def _peerlookup(path):
139 def _peerlookup(path):
140 u = util.url(path)
140 u = util.url(path)
141 scheme = u.scheme or b'file'
141 scheme = u.scheme or b'file'
142 thing = schemes.get(scheme) or schemes[b'file']
142 thing = schemes.get(scheme) or schemes[b'file']
143 try:
143 try:
144 return thing(path)
144 return thing(path)
145 except TypeError:
145 except TypeError:
146 # we can't test callable(thing) because 'thing' can be an unloaded
146 # we can't test callable(thing) because 'thing' can be an unloaded
147 # module that implements __call__
147 # module that implements __call__
148 if not util.safehasattr(thing, b'instance'):
148 if not util.safehasattr(thing, b'instance'):
149 raise
149 raise
150 return thing
150 return thing
151
151
152
152
153 def islocal(repo):
153 def islocal(repo):
154 '''return true if repo (or path pointing to repo) is local'''
154 '''return true if repo (or path pointing to repo) is local'''
155 if isinstance(repo, bytes):
155 if isinstance(repo, bytes):
156 try:
156 try:
157 return _peerlookup(repo).islocal(repo)
157 return _peerlookup(repo).islocal(repo)
158 except AttributeError:
158 except AttributeError:
159 return False
159 return False
160 return repo.local()
160 return repo.local()
161
161
162
162
163 def openpath(ui, path, sendaccept=True):
163 def openpath(ui, path, sendaccept=True):
164 '''open path with open if local, url.open if remote'''
164 '''open path with open if local, url.open if remote'''
165 pathurl = util.url(path, parsequery=False, parsefragment=False)
165 pathurl = util.url(path, parsequery=False, parsefragment=False)
166 if pathurl.islocal():
166 if pathurl.islocal():
167 return util.posixfile(pathurl.localpath(), b'rb')
167 return util.posixfile(pathurl.localpath(), b'rb')
168 else:
168 else:
169 return url.open(ui, path, sendaccept=sendaccept)
169 return url.open(ui, path, sendaccept=sendaccept)
170
170
171
171
172 # a list of (ui, repo) functions called for wire peer initialization
172 # a list of (ui, repo) functions called for wire peer initialization
173 wirepeersetupfuncs = []
173 wirepeersetupfuncs = []
174
174
175
175
176 def _peerorrepo(
176 def _peerorrepo(
177 ui, path, create=False, presetupfuncs=None, intents=None, createopts=None
177 ui, path, create=False, presetupfuncs=None, intents=None, createopts=None
178 ):
178 ):
179 """return a repository object for the specified path"""
179 """return a repository object for the specified path"""
180 obj = _peerlookup(path).instance(
180 obj = _peerlookup(path).instance(
181 ui, path, create, intents=intents, createopts=createopts
181 ui, path, create, intents=intents, createopts=createopts
182 )
182 )
183 ui = getattr(obj, "ui", ui)
183 ui = getattr(obj, "ui", ui)
184 for f in presetupfuncs or []:
184 for f in presetupfuncs or []:
185 f(ui, obj)
185 f(ui, obj)
186 ui.log(b'extension', b'- executing reposetup hooks\n')
186 ui.log(b'extension', b'- executing reposetup hooks\n')
187 with util.timedcm('all reposetup') as allreposetupstats:
187 with util.timedcm('all reposetup') as allreposetupstats:
188 for name, module in extensions.extensions(ui):
188 for name, module in extensions.extensions(ui):
189 ui.log(b'extension', b' - running reposetup for %s\n', name)
189 ui.log(b'extension', b' - running reposetup for %s\n', name)
190 hook = getattr(module, 'reposetup', None)
190 hook = getattr(module, 'reposetup', None)
191 if hook:
191 if hook:
192 with util.timedcm('reposetup %r', name) as stats:
192 with util.timedcm('reposetup %r', name) as stats:
193 hook(ui, obj)
193 hook(ui, obj)
194 ui.log(
194 ui.log(
195 b'extension', b' > reposetup for %s took %s\n', name, stats
195 b'extension', b' > reposetup for %s took %s\n', name, stats
196 )
196 )
197 ui.log(b'extension', b'> all reposetup took %s\n', allreposetupstats)
197 ui.log(b'extension', b'> all reposetup took %s\n', allreposetupstats)
198 if not obj.local():
198 if not obj.local():
199 for f in wirepeersetupfuncs:
199 for f in wirepeersetupfuncs:
200 f(ui, obj)
200 f(ui, obj)
201 return obj
201 return obj
202
202
203
203
204 def repository(
204 def repository(
205 ui,
205 ui,
206 path=b'',
206 path=b'',
207 create=False,
207 create=False,
208 presetupfuncs=None,
208 presetupfuncs=None,
209 intents=None,
209 intents=None,
210 createopts=None,
210 createopts=None,
211 ):
211 ):
212 """return a repository object for the specified path"""
212 """return a repository object for the specified path"""
213 peer = _peerorrepo(
213 peer = _peerorrepo(
214 ui,
214 ui,
215 path,
215 path,
216 create,
216 create,
217 presetupfuncs=presetupfuncs,
217 presetupfuncs=presetupfuncs,
218 intents=intents,
218 intents=intents,
219 createopts=createopts,
219 createopts=createopts,
220 )
220 )
221 repo = peer.local()
221 repo = peer.local()
222 if not repo:
222 if not repo:
223 raise error.Abort(
223 raise error.Abort(
224 _(b"repository '%s' is not local") % (path or peer.url())
224 _(b"repository '%s' is not local") % (path or peer.url())
225 )
225 )
226 return repo.filtered(b'visible')
226 return repo.filtered(b'visible')
227
227
228
228
229 def peer(uiorrepo, opts, path, create=False, intents=None, createopts=None):
229 def peer(uiorrepo, opts, path, create=False, intents=None, createopts=None):
230 '''return a repository peer for the specified path'''
230 '''return a repository peer for the specified path'''
231 rui = remoteui(uiorrepo, opts)
231 rui = remoteui(uiorrepo, opts)
232 return _peerorrepo(
232 return _peerorrepo(
233 rui, path, create, intents=intents, createopts=createopts
233 rui, path, create, intents=intents, createopts=createopts
234 ).peer()
234 ).peer()
235
235
236
236
237 def defaultdest(source):
237 def defaultdest(source):
238 '''return default destination of clone if none is given
238 '''return default destination of clone if none is given
239
239
240 >>> defaultdest(b'foo')
240 >>> defaultdest(b'foo')
241 'foo'
241 'foo'
242 >>> defaultdest(b'/foo/bar')
242 >>> defaultdest(b'/foo/bar')
243 'bar'
243 'bar'
244 >>> defaultdest(b'/')
244 >>> defaultdest(b'/')
245 ''
245 ''
246 >>> defaultdest(b'')
246 >>> defaultdest(b'')
247 ''
247 ''
248 >>> defaultdest(b'http://example.org/')
248 >>> defaultdest(b'http://example.org/')
249 ''
249 ''
250 >>> defaultdest(b'http://example.org/foo/')
250 >>> defaultdest(b'http://example.org/foo/')
251 'foo'
251 'foo'
252 '''
252 '''
253 path = util.url(source).path
253 path = util.url(source).path
254 if not path:
254 if not path:
255 return b''
255 return b''
256 return os.path.basename(os.path.normpath(path))
256 return os.path.basename(os.path.normpath(path))
257
257
258
258
259 def sharedreposource(repo):
259 def sharedreposource(repo):
260 """Returns repository object for source repository of a shared repo.
260 """Returns repository object for source repository of a shared repo.
261
261
262 If repo is not a shared repository, returns None.
262 If repo is not a shared repository, returns None.
263 """
263 """
264 if repo.sharedpath == repo.path:
264 if repo.sharedpath == repo.path:
265 return None
265 return None
266
266
267 if util.safehasattr(repo, b'srcrepo') and repo.srcrepo:
267 if util.safehasattr(repo, b'srcrepo') and repo.srcrepo:
268 return repo.srcrepo
268 return repo.srcrepo
269
269
270 # the sharedpath always ends in the .hg; we want the path to the repo
270 # the sharedpath always ends in the .hg; we want the path to the repo
271 source = repo.vfs.split(repo.sharedpath)[0]
271 source = repo.vfs.split(repo.sharedpath)[0]
272 srcurl, branches = parseurl(source)
272 srcurl, branches = parseurl(source)
273 srcrepo = repository(repo.ui, srcurl)
273 srcrepo = repository(repo.ui, srcurl)
274 repo.srcrepo = srcrepo
274 repo.srcrepo = srcrepo
275 return srcrepo
275 return srcrepo
276
276
277
277
278 def share(
278 def share(
279 ui,
279 ui,
280 source,
280 source,
281 dest=None,
281 dest=None,
282 update=True,
282 update=True,
283 bookmarks=True,
283 bookmarks=True,
284 defaultpath=None,
284 defaultpath=None,
285 relative=False,
285 relative=False,
286 ):
286 ):
287 '''create a shared repository'''
287 '''create a shared repository'''
288
288
289 if not islocal(source):
289 if not islocal(source):
290 raise error.Abort(_(b'can only share local repositories'))
290 raise error.Abort(_(b'can only share local repositories'))
291
291
292 if not dest:
292 if not dest:
293 dest = defaultdest(source)
293 dest = defaultdest(source)
294 else:
294 else:
295 dest = ui.expandpath(dest)
295 dest = ui.expandpath(dest)
296
296
297 if isinstance(source, bytes):
297 if isinstance(source, bytes):
298 origsource = ui.expandpath(source)
298 origsource = ui.expandpath(source)
299 source, branches = parseurl(origsource)
299 source, branches = parseurl(origsource)
300 srcrepo = repository(ui, source)
300 srcrepo = repository(ui, source)
301 rev, checkout = addbranchrevs(srcrepo, srcrepo, branches, None)
301 rev, checkout = addbranchrevs(srcrepo, srcrepo, branches, None)
302 else:
302 else:
303 srcrepo = source.local()
303 srcrepo = source.local()
304 checkout = None
304 checkout = None
305
305
306 shareditems = set()
306 shareditems = set()
307 if bookmarks:
307 if bookmarks:
308 shareditems.add(sharedbookmarks)
308 shareditems.add(sharedbookmarks)
309
309
310 r = repository(
310 r = repository(
311 ui,
311 ui,
312 dest,
312 dest,
313 create=True,
313 create=True,
314 createopts={
314 createopts={
315 b'sharedrepo': srcrepo,
315 b'sharedrepo': srcrepo,
316 b'sharedrelative': relative,
316 b'sharedrelative': relative,
317 b'shareditems': shareditems,
317 b'shareditems': shareditems,
318 },
318 },
319 )
319 )
320
320
321 postshare(srcrepo, r, defaultpath=defaultpath)
321 postshare(srcrepo, r, defaultpath=defaultpath)
322 r = repository(ui, dest)
322 r = repository(ui, dest)
323 _postshareupdate(r, update, checkout=checkout)
323 _postshareupdate(r, update, checkout=checkout)
324 return r
324 return r
325
325
326
326
327 def unshare(ui, repo):
327 def unshare(ui, repo):
328 """convert a shared repository to a normal one
328 """convert a shared repository to a normal one
329
329
330 Copy the store data to the repo and remove the sharedpath data.
330 Copy the store data to the repo and remove the sharedpath data.
331
331
332 Returns a new repository object representing the unshared repository.
332 Returns a new repository object representing the unshared repository.
333
333
334 The passed repository object is not usable after this function is
334 The passed repository object is not usable after this function is
335 called.
335 called.
336 """
336 """
337
337
338 with repo.lock():
338 with repo.lock():
339 # we use locks here because if we race with commit, we
339 # we use locks here because if we race with commit, we
340 # can end up with extra data in the cloned revlogs that's
340 # can end up with extra data in the cloned revlogs that's
341 # not pointed to by changesets, thus causing verify to
341 # not pointed to by changesets, thus causing verify to
342 # fail
342 # fail
343 destlock = copystore(ui, repo, repo.path)
343 destlock = copystore(ui, repo, repo.path)
344 with destlock or util.nullcontextmanager():
344 with destlock or util.nullcontextmanager():
345
345
346 sharefile = repo.vfs.join(b'sharedpath')
346 sharefile = repo.vfs.join(b'sharedpath')
347 util.rename(sharefile, sharefile + b'.old')
347 util.rename(sharefile, sharefile + b'.old')
348
348
349 repo.requirements.discard(b'shared')
349 repo.requirements.discard(b'shared')
350 repo.requirements.discard(b'relshared')
350 repo.requirements.discard(b'relshared')
351 repo._writerequirements()
351 repo._writerequirements()
352
352
353 # Removing share changes some fundamental properties of the repo instance.
353 # Removing share changes some fundamental properties of the repo instance.
354 # So we instantiate a new repo object and operate on it rather than
354 # So we instantiate a new repo object and operate on it rather than
355 # try to keep the existing repo usable.
355 # try to keep the existing repo usable.
356 newrepo = repository(repo.baseui, repo.root, create=False)
356 newrepo = repository(repo.baseui, repo.root, create=False)
357
357
358 # TODO: figure out how to access subrepos that exist, but were previously
358 # TODO: figure out how to access subrepos that exist, but were previously
359 # removed from .hgsub
359 # removed from .hgsub
360 c = newrepo[b'.']
360 c = newrepo[b'.']
361 subs = c.substate
361 subs = c.substate
362 for s in sorted(subs):
362 for s in sorted(subs):
363 c.sub(s).unshare()
363 c.sub(s).unshare()
364
364
365 localrepo.poisonrepository(repo)
365 localrepo.poisonrepository(repo)
366
366
367 return newrepo
367 return newrepo
368
368
369
369
370 def postshare(sourcerepo, destrepo, defaultpath=None):
370 def postshare(sourcerepo, destrepo, defaultpath=None):
371 """Called after a new shared repo is created.
371 """Called after a new shared repo is created.
372
372
373 The new repo only has a requirements file and pointer to the source.
373 The new repo only has a requirements file and pointer to the source.
374 This function configures additional shared data.
374 This function configures additional shared data.
375
375
376 Extensions can wrap this function and write additional entries to
376 Extensions can wrap this function and write additional entries to
377 destrepo/.hg/shared to indicate additional pieces of data to be shared.
377 destrepo/.hg/shared to indicate additional pieces of data to be shared.
378 """
378 """
379 default = defaultpath or sourcerepo.ui.config(b'paths', b'default')
379 default = defaultpath or sourcerepo.ui.config(b'paths', b'default')
380 if default:
380 if default:
381 template = b'[paths]\ndefault = %s\n'
381 template = b'[paths]\ndefault = %s\n'
382 destrepo.vfs.write(b'hgrc', util.tonativeeol(template % default))
382 destrepo.vfs.write(b'hgrc', util.tonativeeol(template % default))
383 if repositorymod.NARROW_REQUIREMENT in sourcerepo.requirements:
383 if repositorymod.NARROW_REQUIREMENT in sourcerepo.requirements:
384 with destrepo.wlock():
384 with destrepo.wlock():
385 narrowspec.copytoworkingcopy(destrepo)
385 narrowspec.copytoworkingcopy(destrepo)
386
386
387
387
388 def _postshareupdate(repo, update, checkout=None):
388 def _postshareupdate(repo, update, checkout=None):
389 """Maybe perform a working directory update after a shared repo is created.
389 """Maybe perform a working directory update after a shared repo is created.
390
390
391 ``update`` can be a boolean or a revision to update to.
391 ``update`` can be a boolean or a revision to update to.
392 """
392 """
393 if not update:
393 if not update:
394 return
394 return
395
395
396 repo.ui.status(_(b"updating working directory\n"))
396 repo.ui.status(_(b"updating working directory\n"))
397 if update is not True:
397 if update is not True:
398 checkout = update
398 checkout = update
399 for test in (checkout, b'default', b'tip'):
399 for test in (checkout, b'default', b'tip'):
400 if test is None:
400 if test is None:
401 continue
401 continue
402 try:
402 try:
403 uprev = repo.lookup(test)
403 uprev = repo.lookup(test)
404 break
404 break
405 except error.RepoLookupError:
405 except error.RepoLookupError:
406 continue
406 continue
407 _update(repo, uprev)
407 _update(repo, uprev)
408
408
409
409
410 def copystore(ui, srcrepo, destpath):
410 def copystore(ui, srcrepo, destpath):
411 '''copy files from store of srcrepo in destpath
411 '''copy files from store of srcrepo in destpath
412
412
413 returns destlock
413 returns destlock
414 '''
414 '''
415 destlock = None
415 destlock = None
416 try:
416 try:
417 hardlink = None
417 hardlink = None
418 topic = _(b'linking') if hardlink else _(b'copying')
418 topic = _(b'linking') if hardlink else _(b'copying')
419 with ui.makeprogress(topic, unit=_(b'files')) as progress:
419 with ui.makeprogress(topic, unit=_(b'files')) as progress:
420 num = 0
420 num = 0
421 srcpublishing = srcrepo.publishing()
421 srcpublishing = srcrepo.publishing()
422 srcvfs = vfsmod.vfs(srcrepo.sharedpath)
422 srcvfs = vfsmod.vfs(srcrepo.sharedpath)
423 dstvfs = vfsmod.vfs(destpath)
423 dstvfs = vfsmod.vfs(destpath)
424 for f in srcrepo.store.copylist():
424 for f in srcrepo.store.copylist():
425 if srcpublishing and f.endswith(b'phaseroots'):
425 if srcpublishing and f.endswith(b'phaseroots'):
426 continue
426 continue
427 dstbase = os.path.dirname(f)
427 dstbase = os.path.dirname(f)
428 if dstbase and not dstvfs.exists(dstbase):
428 if dstbase and not dstvfs.exists(dstbase):
429 dstvfs.mkdir(dstbase)
429 dstvfs.mkdir(dstbase)
430 if srcvfs.exists(f):
430 if srcvfs.exists(f):
431 if f.endswith(b'data'):
431 if f.endswith(b'data'):
432 # 'dstbase' may be empty (e.g. revlog format 0)
432 # 'dstbase' may be empty (e.g. revlog format 0)
433 lockfile = os.path.join(dstbase, b"lock")
433 lockfile = os.path.join(dstbase, b"lock")
434 # lock to avoid premature writing to the target
434 # lock to avoid premature writing to the target
435 destlock = lock.lock(dstvfs, lockfile)
435 destlock = lock.lock(dstvfs, lockfile)
436 hardlink, n = util.copyfiles(
436 hardlink, n = util.copyfiles(
437 srcvfs.join(f), dstvfs.join(f), hardlink, progress
437 srcvfs.join(f), dstvfs.join(f), hardlink, progress
438 )
438 )
439 num += n
439 num += n
440 if hardlink:
440 if hardlink:
441 ui.debug(b"linked %d files\n" % num)
441 ui.debug(b"linked %d files\n" % num)
442 else:
442 else:
443 ui.debug(b"copied %d files\n" % num)
443 ui.debug(b"copied %d files\n" % num)
444 return destlock
444 return destlock
445 except: # re-raises
445 except: # re-raises
446 release(destlock)
446 release(destlock)
447 raise
447 raise
448
448
449
449
450 def clonewithshare(
450 def clonewithshare(
451 ui,
451 ui,
452 peeropts,
452 peeropts,
453 sharepath,
453 sharepath,
454 source,
454 source,
455 srcpeer,
455 srcpeer,
456 dest,
456 dest,
457 pull=False,
457 pull=False,
458 rev=None,
458 rev=None,
459 update=True,
459 update=True,
460 stream=False,
460 stream=False,
461 ):
461 ):
462 """Perform a clone using a shared repo.
462 """Perform a clone using a shared repo.
463
463
464 The store for the repository will be located at <sharepath>/.hg. The
464 The store for the repository will be located at <sharepath>/.hg. The
465 specified revisions will be cloned or pulled from "source". A shared repo
465 specified revisions will be cloned or pulled from "source". A shared repo
466 will be created at "dest" and a working copy will be created if "update" is
466 will be created at "dest" and a working copy will be created if "update" is
467 True.
467 True.
468 """
468 """
469 revs = None
469 revs = None
470 if rev:
470 if rev:
471 if not srcpeer.capable(b'lookup'):
471 if not srcpeer.capable(b'lookup'):
472 raise error.Abort(
472 raise error.Abort(
473 _(
473 _(
474 b"src repository does not support "
474 b"src repository does not support "
475 b"revision lookup and so doesn't "
475 b"revision lookup and so doesn't "
476 b"support clone by revision"
476 b"support clone by revision"
477 )
477 )
478 )
478 )
479
479
480 # TODO this is batchable.
480 # TODO this is batchable.
481 remoterevs = []
481 remoterevs = []
482 for r in rev:
482 for r in rev:
483 with srcpeer.commandexecutor() as e:
483 with srcpeer.commandexecutor() as e:
484 remoterevs.append(
484 remoterevs.append(
485 e.callcommand(b'lookup', {b'key': r,}).result()
485 e.callcommand(b'lookup', {b'key': r,}).result()
486 )
486 )
487 revs = remoterevs
487 revs = remoterevs
488
488
489 # Obtain a lock before checking for or cloning the pooled repo otherwise
489 # Obtain a lock before checking for or cloning the pooled repo otherwise
490 # 2 clients may race creating or populating it.
490 # 2 clients may race creating or populating it.
491 pooldir = os.path.dirname(sharepath)
491 pooldir = os.path.dirname(sharepath)
492 # lock class requires the directory to exist.
492 # lock class requires the directory to exist.
493 try:
493 try:
494 util.makedir(pooldir, False)
494 util.makedir(pooldir, False)
495 except OSError as e:
495 except OSError as e:
496 if e.errno != errno.EEXIST:
496 if e.errno != errno.EEXIST:
497 raise
497 raise
498
498
499 poolvfs = vfsmod.vfs(pooldir)
499 poolvfs = vfsmod.vfs(pooldir)
500 basename = os.path.basename(sharepath)
500 basename = os.path.basename(sharepath)
501
501
502 with lock.lock(poolvfs, b'%s.lock' % basename):
502 with lock.lock(poolvfs, b'%s.lock' % basename):
503 if os.path.exists(sharepath):
503 if os.path.exists(sharepath):
504 ui.status(
504 ui.status(
505 _(b'(sharing from existing pooled repository %s)\n') % basename
505 _(b'(sharing from existing pooled repository %s)\n') % basename
506 )
506 )
507 else:
507 else:
508 ui.status(
508 ui.status(
509 _(b'(sharing from new pooled repository %s)\n') % basename
509 _(b'(sharing from new pooled repository %s)\n') % basename
510 )
510 )
511 # Always use pull mode because hardlinks in share mode don't work
511 # Always use pull mode because hardlinks in share mode don't work
512 # well. Never update because working copies aren't necessary in
512 # well. Never update because working copies aren't necessary in
513 # share mode.
513 # share mode.
514 clone(
514 clone(
515 ui,
515 ui,
516 peeropts,
516 peeropts,
517 source,
517 source,
518 dest=sharepath,
518 dest=sharepath,
519 pull=True,
519 pull=True,
520 revs=rev,
520 revs=rev,
521 update=False,
521 update=False,
522 stream=stream,
522 stream=stream,
523 )
523 )
524
524
525 # Resolve the value to put in [paths] section for the source.
525 # Resolve the value to put in [paths] section for the source.
526 if islocal(source):
526 if islocal(source):
527 defaultpath = os.path.abspath(util.urllocalpath(source))
527 defaultpath = os.path.abspath(util.urllocalpath(source))
528 else:
528 else:
529 defaultpath = source
529 defaultpath = source
530
530
531 sharerepo = repository(ui, path=sharepath)
531 sharerepo = repository(ui, path=sharepath)
532 destrepo = share(
532 destrepo = share(
533 ui,
533 ui,
534 sharerepo,
534 sharerepo,
535 dest=dest,
535 dest=dest,
536 update=False,
536 update=False,
537 bookmarks=False,
537 bookmarks=False,
538 defaultpath=defaultpath,
538 defaultpath=defaultpath,
539 )
539 )
540
540
541 # We need to perform a pull against the dest repo to fetch bookmarks
541 # We need to perform a pull against the dest repo to fetch bookmarks
542 # and other non-store data that isn't shared by default. In the case of
542 # and other non-store data that isn't shared by default. In the case of
543 # non-existing shared repo, this means we pull from the remote twice. This
543 # non-existing shared repo, this means we pull from the remote twice. This
544 # is a bit weird. But at the time it was implemented, there wasn't an easy
544 # is a bit weird. But at the time it was implemented, there wasn't an easy
545 # way to pull just non-changegroup data.
545 # way to pull just non-changegroup data.
546 exchange.pull(destrepo, srcpeer, heads=revs)
546 exchange.pull(destrepo, srcpeer, heads=revs)
547
547
548 _postshareupdate(destrepo, update)
548 _postshareupdate(destrepo, update)
549
549
550 return srcpeer, peer(ui, peeropts, dest)
550 return srcpeer, peer(ui, peeropts, dest)
551
551
552
552
553 # Recomputing branch cache might be slow on big repos,
553 # Recomputing branch cache might be slow on big repos,
554 # so just copy it
554 # so just copy it
555 def _copycache(srcrepo, dstcachedir, fname):
555 def _copycache(srcrepo, dstcachedir, fname):
556 """copy a cache from srcrepo to destcachedir (if it exists)"""
556 """copy a cache from srcrepo to destcachedir (if it exists)"""
557 srcbranchcache = srcrepo.vfs.join(b'cache/%s' % fname)
557 srcbranchcache = srcrepo.vfs.join(b'cache/%s' % fname)
558 dstbranchcache = os.path.join(dstcachedir, fname)
558 dstbranchcache = os.path.join(dstcachedir, fname)
559 if os.path.exists(srcbranchcache):
559 if os.path.exists(srcbranchcache):
560 if not os.path.exists(dstcachedir):
560 if not os.path.exists(dstcachedir):
561 os.mkdir(dstcachedir)
561 os.mkdir(dstcachedir)
562 util.copyfile(srcbranchcache, dstbranchcache)
562 util.copyfile(srcbranchcache, dstbranchcache)
563
563
564
564
565 def clone(
565 def clone(
566 ui,
566 ui,
567 peeropts,
567 peeropts,
568 source,
568 source,
569 dest=None,
569 dest=None,
570 pull=False,
570 pull=False,
571 revs=None,
571 revs=None,
572 update=True,
572 update=True,
573 stream=False,
573 stream=False,
574 branch=None,
574 branch=None,
575 shareopts=None,
575 shareopts=None,
576 storeincludepats=None,
576 storeincludepats=None,
577 storeexcludepats=None,
577 storeexcludepats=None,
578 depth=None,
578 depth=None,
579 ):
579 ):
580 """Make a copy of an existing repository.
580 """Make a copy of an existing repository.
581
581
582 Create a copy of an existing repository in a new directory. The
582 Create a copy of an existing repository in a new directory. The
583 source and destination are URLs, as passed to the repository
583 source and destination are URLs, as passed to the repository
584 function. Returns a pair of repository peers, the source and
584 function. Returns a pair of repository peers, the source and
585 newly created destination.
585 newly created destination.
586
586
587 The location of the source is added to the new repository's
587 The location of the source is added to the new repository's
588 .hg/hgrc file, as the default to be used for future pulls and
588 .hg/hgrc file, as the default to be used for future pulls and
589 pushes.
589 pushes.
590
590
591 If an exception is raised, the partly cloned/updated destination
591 If an exception is raised, the partly cloned/updated destination
592 repository will be deleted.
592 repository will be deleted.
593
593
594 Arguments:
594 Arguments:
595
595
596 source: repository object or URL
596 source: repository object or URL
597
597
598 dest: URL of destination repository to create (defaults to base
598 dest: URL of destination repository to create (defaults to base
599 name of source repository)
599 name of source repository)
600
600
601 pull: always pull from source repository, even in local case or if the
601 pull: always pull from source repository, even in local case or if the
602 server prefers streaming
602 server prefers streaming
603
603
604 stream: stream raw data uncompressed from repository (fast over
604 stream: stream raw data uncompressed from repository (fast over
605 LAN, slow over WAN)
605 LAN, slow over WAN)
606
606
607 revs: revision to clone up to (implies pull=True)
607 revs: revision to clone up to (implies pull=True)
608
608
609 update: update working directory after clone completes, if
609 update: update working directory after clone completes, if
610 destination is local repository (True means update to default rev,
610 destination is local repository (True means update to default rev,
611 anything else is treated as a revision)
611 anything else is treated as a revision)
612
612
613 branch: branches to clone
613 branch: branches to clone
614
614
615 shareopts: dict of options to control auto sharing behavior. The "pool" key
615 shareopts: dict of options to control auto sharing behavior. The "pool" key
616 activates auto sharing mode and defines the directory for stores. The
616 activates auto sharing mode and defines the directory for stores. The
617 "mode" key determines how to construct the directory name of the shared
617 "mode" key determines how to construct the directory name of the shared
618 repository. "identity" means the name is derived from the node of the first
618 repository. "identity" means the name is derived from the node of the first
619 changeset in the repository. "remote" means the name is derived from the
619 changeset in the repository. "remote" means the name is derived from the
620 remote's path/URL. Defaults to "identity."
620 remote's path/URL. Defaults to "identity."
621
621
622 storeincludepats and storeexcludepats: sets of file patterns to include and
622 storeincludepats and storeexcludepats: sets of file patterns to include and
623 exclude in the repository copy, respectively. If not defined, all files
623 exclude in the repository copy, respectively. If not defined, all files
624 will be included (a "full" clone). Otherwise a "narrow" clone containing
624 will be included (a "full" clone). Otherwise a "narrow" clone containing
625 only the requested files will be performed. If ``storeincludepats`` is not
625 only the requested files will be performed. If ``storeincludepats`` is not
626 defined but ``storeexcludepats`` is, ``storeincludepats`` is assumed to be
626 defined but ``storeexcludepats`` is, ``storeincludepats`` is assumed to be
627 ``path:.``. If both are empty sets, no files will be cloned.
627 ``path:.``. If both are empty sets, no files will be cloned.
628 """
628 """
629
629
630 if isinstance(source, bytes):
630 if isinstance(source, bytes):
631 origsource = ui.expandpath(source)
631 origsource = ui.expandpath(source)
632 source, branches = parseurl(origsource, branch)
632 source, branches = parseurl(origsource, branch)
633 srcpeer = peer(ui, peeropts, source)
633 srcpeer = peer(ui, peeropts, source)
634 else:
634 else:
635 srcpeer = source.peer() # in case we were called with a localrepo
635 srcpeer = source.peer() # in case we were called with a localrepo
636 branches = (None, branch or [])
636 branches = (None, branch or [])
637 origsource = source = srcpeer.url()
637 origsource = source = srcpeer.url()
638 revs, checkout = addbranchrevs(srcpeer, srcpeer, branches, revs)
638 revs, checkout = addbranchrevs(srcpeer, srcpeer, branches, revs)
639
639
640 if dest is None:
640 if dest is None:
641 dest = defaultdest(source)
641 dest = defaultdest(source)
642 if dest:
642 if dest:
643 ui.status(_(b"destination directory: %s\n") % dest)
643 ui.status(_(b"destination directory: %s\n") % dest)
644 else:
644 else:
645 dest = ui.expandpath(dest)
645 dest = ui.expandpath(dest)
646
646
647 dest = util.urllocalpath(dest)
647 dest = util.urllocalpath(dest)
648 source = util.urllocalpath(source)
648 source = util.urllocalpath(source)
649
649
650 if not dest:
650 if not dest:
651 raise error.Abort(_(b"empty destination path is not valid"))
651 raise error.Abort(_(b"empty destination path is not valid"))
652
652
653 destvfs = vfsmod.vfs(dest, expandpath=True)
653 destvfs = vfsmod.vfs(dest, expandpath=True)
654 if destvfs.lexists():
654 if destvfs.lexists():
655 if not destvfs.isdir():
655 if not destvfs.isdir():
656 raise error.Abort(_(b"destination '%s' already exists") % dest)
656 raise error.Abort(_(b"destination '%s' already exists") % dest)
657 elif destvfs.listdir():
657 elif destvfs.listdir():
658 raise error.Abort(_(b"destination '%s' is not empty") % dest)
658 raise error.Abort(_(b"destination '%s' is not empty") % dest)
659
659
660 createopts = {}
660 createopts = {}
661 narrow = False
661 narrow = False
662
662
663 if storeincludepats is not None:
663 if storeincludepats is not None:
664 narrowspec.validatepatterns(storeincludepats)
664 narrowspec.validatepatterns(storeincludepats)
665 narrow = True
665 narrow = True
666
666
667 if storeexcludepats is not None:
667 if storeexcludepats is not None:
668 narrowspec.validatepatterns(storeexcludepats)
668 narrowspec.validatepatterns(storeexcludepats)
669 narrow = True
669 narrow = True
670
670
671 if narrow:
671 if narrow:
672 # Include everything by default if only exclusion patterns defined.
672 # Include everything by default if only exclusion patterns defined.
673 if storeexcludepats and not storeincludepats:
673 if storeexcludepats and not storeincludepats:
674 storeincludepats = {b'path:.'}
674 storeincludepats = {b'path:.'}
675
675
676 createopts[b'narrowfiles'] = True
676 createopts[b'narrowfiles'] = True
677
677
678 if depth:
678 if depth:
679 createopts[b'shallowfilestore'] = True
679 createopts[b'shallowfilestore'] = True
680
680
681 if srcpeer.capable(b'lfs-serve'):
681 if srcpeer.capable(b'lfs-serve'):
682 # Repository creation honors the config if it disabled the extension, so
682 # Repository creation honors the config if it disabled the extension, so
683 # we can't just announce that lfs will be enabled. This check avoids
683 # we can't just announce that lfs will be enabled. This check avoids
684 # saying that lfs will be enabled, and then saying it's an unknown
684 # saying that lfs will be enabled, and then saying it's an unknown
685 # feature. The lfs creation option is set in either case so that a
685 # feature. The lfs creation option is set in either case so that a
686 # requirement is added. If the extension is explicitly disabled but the
686 # requirement is added. If the extension is explicitly disabled but the
687 # requirement is set, the clone aborts early, before transferring any
687 # requirement is set, the clone aborts early, before transferring any
688 # data.
688 # data.
689 createopts[b'lfs'] = True
689 createopts[b'lfs'] = True
690
690
691 if extensions.disabledext(b'lfs'):
691 if extensions.disabledext(b'lfs'):
692 ui.status(
692 ui.status(
693 _(
693 _(
694 b'(remote is using large file support (lfs), but it is '
694 b'(remote is using large file support (lfs), but it is '
695 b'explicitly disabled in the local configuration)\n'
695 b'explicitly disabled in the local configuration)\n'
696 )
696 )
697 )
697 )
698 else:
698 else:
699 ui.status(
699 ui.status(
700 _(
700 _(
701 b'(remote is using large file support (lfs); lfs will '
701 b'(remote is using large file support (lfs); lfs will '
702 b'be enabled for this repository)\n'
702 b'be enabled for this repository)\n'
703 )
703 )
704 )
704 )
705
705
706 shareopts = shareopts or {}
706 shareopts = shareopts or {}
707 sharepool = shareopts.get(b'pool')
707 sharepool = shareopts.get(b'pool')
708 sharenamemode = shareopts.get(b'mode')
708 sharenamemode = shareopts.get(b'mode')
709 if sharepool and islocal(dest):
709 if sharepool and islocal(dest):
710 sharepath = None
710 sharepath = None
711 if sharenamemode == b'identity':
711 if sharenamemode == b'identity':
712 # Resolve the name from the initial changeset in the remote
712 # Resolve the name from the initial changeset in the remote
713 # repository. This returns nullid when the remote is empty. It
713 # repository. This returns nullid when the remote is empty. It
714 # raises RepoLookupError if revision 0 is filtered or otherwise
714 # raises RepoLookupError if revision 0 is filtered or otherwise
715 # not available. If we fail to resolve, sharing is not enabled.
715 # not available. If we fail to resolve, sharing is not enabled.
716 try:
716 try:
717 with srcpeer.commandexecutor() as e:
717 with srcpeer.commandexecutor() as e:
718 rootnode = e.callcommand(
718 rootnode = e.callcommand(
719 b'lookup', {b'key': b'0',}
719 b'lookup', {b'key': b'0',}
720 ).result()
720 ).result()
721
721
722 if rootnode != node.nullid:
722 if rootnode != node.nullid:
723 sharepath = os.path.join(sharepool, node.hex(rootnode))
723 sharepath = os.path.join(sharepool, node.hex(rootnode))
724 else:
724 else:
725 ui.status(
725 ui.status(
726 _(
726 _(
727 b'(not using pooled storage: '
727 b'(not using pooled storage: '
728 b'remote appears to be empty)\n'
728 b'remote appears to be empty)\n'
729 )
729 )
730 )
730 )
731 except error.RepoLookupError:
731 except error.RepoLookupError:
732 ui.status(
732 ui.status(
733 _(
733 _(
734 b'(not using pooled storage: '
734 b'(not using pooled storage: '
735 b'unable to resolve identity of remote)\n'
735 b'unable to resolve identity of remote)\n'
736 )
736 )
737 )
737 )
738 elif sharenamemode == b'remote':
738 elif sharenamemode == b'remote':
739 sharepath = os.path.join(
739 sharepath = os.path.join(
740 sharepool, node.hex(hashutil.sha1(source).digest())
740 sharepool, node.hex(hashutil.sha1(source).digest())
741 )
741 )
742 else:
742 else:
743 raise error.Abort(
743 raise error.Abort(
744 _(b'unknown share naming mode: %s') % sharenamemode
744 _(b'unknown share naming mode: %s') % sharenamemode
745 )
745 )
746
746
747 # TODO this is a somewhat arbitrary restriction.
747 # TODO this is a somewhat arbitrary restriction.
748 if narrow:
748 if narrow:
749 ui.status(_(b'(pooled storage not supported for narrow clones)\n'))
749 ui.status(_(b'(pooled storage not supported for narrow clones)\n'))
750 sharepath = None
750 sharepath = None
751
751
752 if sharepath:
752 if sharepath:
753 return clonewithshare(
753 return clonewithshare(
754 ui,
754 ui,
755 peeropts,
755 peeropts,
756 sharepath,
756 sharepath,
757 source,
757 source,
758 srcpeer,
758 srcpeer,
759 dest,
759 dest,
760 pull=pull,
760 pull=pull,
761 rev=revs,
761 rev=revs,
762 update=update,
762 update=update,
763 stream=stream,
763 stream=stream,
764 )
764 )
765
765
766 srclock = destlock = cleandir = None
766 srclock = destlock = cleandir = None
767 srcrepo = srcpeer.local()
767 srcrepo = srcpeer.local()
768 try:
768 try:
769 abspath = origsource
769 abspath = origsource
770 if islocal(origsource):
770 if islocal(origsource):
771 abspath = os.path.abspath(util.urllocalpath(origsource))
771 abspath = os.path.abspath(util.urllocalpath(origsource))
772
772
773 if islocal(dest):
773 if islocal(dest):
774 cleandir = dest
774 cleandir = dest
775
775
776 copy = False
776 copy = False
777 if (
777 if (
778 srcrepo
778 srcrepo
779 and srcrepo.cancopy()
779 and srcrepo.cancopy()
780 and islocal(dest)
780 and islocal(dest)
781 and not phases.hassecret(srcrepo)
781 and not phases.hassecret(srcrepo)
782 ):
782 ):
783 copy = not pull and not revs
783 copy = not pull and not revs
784
784
785 # TODO this is a somewhat arbitrary restriction.
785 # TODO this is a somewhat arbitrary restriction.
786 if narrow:
786 if narrow:
787 copy = False
787 copy = False
788
788
789 if copy:
789 if copy:
790 try:
790 try:
791 # we use a lock here because if we race with commit, we
791 # we use a lock here because if we race with commit, we
792 # can end up with extra data in the cloned revlogs that's
792 # can end up with extra data in the cloned revlogs that's
793 # not pointed to by changesets, thus causing verify to
793 # not pointed to by changesets, thus causing verify to
794 # fail
794 # fail
795 srclock = srcrepo.lock(wait=False)
795 srclock = srcrepo.lock(wait=False)
796 except error.LockError:
796 except error.LockError:
797 copy = False
797 copy = False
798
798
799 if copy:
799 if copy:
800 srcrepo.hook(b'preoutgoing', throw=True, source=b'clone')
800 srcrepo.hook(b'preoutgoing', throw=True, source=b'clone')
801 hgdir = os.path.realpath(os.path.join(dest, b".hg"))
801 hgdir = os.path.realpath(os.path.join(dest, b".hg"))
802 if not os.path.exists(dest):
802 if not os.path.exists(dest):
803 util.makedirs(dest)
803 util.makedirs(dest)
804 else:
804 else:
805 # only clean up directories we create ourselves
805 # only clean up directories we create ourselves
806 cleandir = hgdir
806 cleandir = hgdir
807 try:
807 try:
808 destpath = hgdir
808 destpath = hgdir
809 util.makedir(destpath, notindexed=True)
809 util.makedir(destpath, notindexed=True)
810 except OSError as inst:
810 except OSError as inst:
811 if inst.errno == errno.EEXIST:
811 if inst.errno == errno.EEXIST:
812 cleandir = None
812 cleandir = None
813 raise error.Abort(
813 raise error.Abort(
814 _(b"destination '%s' already exists") % dest
814 _(b"destination '%s' already exists") % dest
815 )
815 )
816 raise
816 raise
817
817
818 destlock = copystore(ui, srcrepo, destpath)
818 destlock = copystore(ui, srcrepo, destpath)
819 # copy bookmarks over
819 # copy bookmarks over
820 srcbookmarks = srcrepo.vfs.join(b'bookmarks')
820 srcbookmarks = srcrepo.vfs.join(b'bookmarks')
821 dstbookmarks = os.path.join(destpath, b'bookmarks')
821 dstbookmarks = os.path.join(destpath, b'bookmarks')
822 if os.path.exists(srcbookmarks):
822 if os.path.exists(srcbookmarks):
823 util.copyfile(srcbookmarks, dstbookmarks)
823 util.copyfile(srcbookmarks, dstbookmarks)
824
824
825 dstcachedir = os.path.join(destpath, b'cache')
825 dstcachedir = os.path.join(destpath, b'cache')
826 for cache in cacheutil.cachetocopy(srcrepo):
826 for cache in cacheutil.cachetocopy(srcrepo):
827 _copycache(srcrepo, dstcachedir, cache)
827 _copycache(srcrepo, dstcachedir, cache)
828
828
829 # we need to re-init the repo after manually copying the data
829 # we need to re-init the repo after manually copying the data
830 # into it
830 # into it
831 destpeer = peer(srcrepo, peeropts, dest)
831 destpeer = peer(srcrepo, peeropts, dest)
832 srcrepo.hook(
832 srcrepo.hook(
833 b'outgoing', source=b'clone', node=node.hex(node.nullid)
833 b'outgoing', source=b'clone', node=node.hex(node.nullid)
834 )
834 )
835 else:
835 else:
836 try:
836 try:
837 # only pass ui when no srcrepo
837 # only pass ui when no srcrepo
838 destpeer = peer(
838 destpeer = peer(
839 srcrepo or ui,
839 srcrepo or ui,
840 peeropts,
840 peeropts,
841 dest,
841 dest,
842 create=True,
842 create=True,
843 createopts=createopts,
843 createopts=createopts,
844 )
844 )
845 except OSError as inst:
845 except OSError as inst:
846 if inst.errno == errno.EEXIST:
846 if inst.errno == errno.EEXIST:
847 cleandir = None
847 cleandir = None
848 raise error.Abort(
848 raise error.Abort(
849 _(b"destination '%s' already exists") % dest
849 _(b"destination '%s' already exists") % dest
850 )
850 )
851 raise
851 raise
852
852
853 if revs:
853 if revs:
854 if not srcpeer.capable(b'lookup'):
854 if not srcpeer.capable(b'lookup'):
855 raise error.Abort(
855 raise error.Abort(
856 _(
856 _(
857 b"src repository does not support "
857 b"src repository does not support "
858 b"revision lookup and so doesn't "
858 b"revision lookup and so doesn't "
859 b"support clone by revision"
859 b"support clone by revision"
860 )
860 )
861 )
861 )
862
862
863 # TODO this is batchable.
863 # TODO this is batchable.
864 remoterevs = []
864 remoterevs = []
865 for rev in revs:
865 for rev in revs:
866 with srcpeer.commandexecutor() as e:
866 with srcpeer.commandexecutor() as e:
867 remoterevs.append(
867 remoterevs.append(
868 e.callcommand(b'lookup', {b'key': rev,}).result()
868 e.callcommand(b'lookup', {b'key': rev,}).result()
869 )
869 )
870 revs = remoterevs
870 revs = remoterevs
871
871
872 checkout = revs[0]
872 checkout = revs[0]
873 else:
873 else:
874 revs = None
874 revs = None
875 local = destpeer.local()
875 local = destpeer.local()
876 if local:
876 if local:
877 if narrow:
877 if narrow:
878 with local.wlock(), local.lock():
878 with local.wlock(), local.lock():
879 local.setnarrowpats(storeincludepats, storeexcludepats)
879 local.setnarrowpats(storeincludepats, storeexcludepats)
880 narrowspec.copytoworkingcopy(local)
880 narrowspec.copytoworkingcopy(local)
881
881
882 u = util.url(abspath)
882 u = util.url(abspath)
883 defaulturl = bytes(u)
883 defaulturl = bytes(u)
884 local.ui.setconfig(b'paths', b'default', defaulturl, b'clone')
884 local.ui.setconfig(b'paths', b'default', defaulturl, b'clone')
885 if not stream:
885 if not stream:
886 if pull:
886 if pull:
887 stream = False
887 stream = False
888 else:
888 else:
889 stream = None
889 stream = None
890 # internal config: ui.quietbookmarkmove
890 # internal config: ui.quietbookmarkmove
891 overrides = {(b'ui', b'quietbookmarkmove'): True}
891 overrides = {(b'ui', b'quietbookmarkmove'): True}
892 with local.ui.configoverride(overrides, b'clone'):
892 with local.ui.configoverride(overrides, b'clone'):
893 exchange.pull(
893 exchange.pull(
894 local,
894 local,
895 srcpeer,
895 srcpeer,
896 revs,
896 revs,
897 streamclonerequested=stream,
897 streamclonerequested=stream,
898 includepats=storeincludepats,
898 includepats=storeincludepats,
899 excludepats=storeexcludepats,
899 excludepats=storeexcludepats,
900 depth=depth,
900 depth=depth,
901 )
901 )
902 elif srcrepo:
902 elif srcrepo:
903 # TODO lift restriction once exchange.push() accepts narrow
903 # TODO lift restriction once exchange.push() accepts narrow
904 # push.
904 # push.
905 if narrow:
905 if narrow:
906 raise error.Abort(
906 raise error.Abort(
907 _(
907 _(
908 b'narrow clone not available for '
908 b'narrow clone not available for '
909 b'remote destinations'
909 b'remote destinations'
910 )
910 )
911 )
911 )
912
912
913 exchange.push(
913 exchange.push(
914 srcrepo,
914 srcrepo,
915 destpeer,
915 destpeer,
916 revs=revs,
916 revs=revs,
917 bookmarks=srcrepo._bookmarks.keys(),
917 bookmarks=srcrepo._bookmarks.keys(),
918 )
918 )
919 else:
919 else:
920 raise error.Abort(
920 raise error.Abort(
921 _(b"clone from remote to remote not supported")
921 _(b"clone from remote to remote not supported")
922 )
922 )
923
923
924 cleandir = None
924 cleandir = None
925
925
926 destrepo = destpeer.local()
926 destrepo = destpeer.local()
927 if destrepo:
927 if destrepo:
928 template = uimod.samplehgrcs[b'cloned']
928 template = uimod.samplehgrcs[b'cloned']
929 u = util.url(abspath)
929 u = util.url(abspath)
930 u.passwd = None
930 u.passwd = None
931 defaulturl = bytes(u)
931 defaulturl = bytes(u)
932 destrepo.vfs.write(b'hgrc', util.tonativeeol(template % defaulturl))
932 destrepo.vfs.write(b'hgrc', util.tonativeeol(template % defaulturl))
933 destrepo.ui.setconfig(b'paths', b'default', defaulturl, b'clone')
933 destrepo.ui.setconfig(b'paths', b'default', defaulturl, b'clone')
934
934
935 if ui.configbool(b'experimental', b'remotenames'):
935 if ui.configbool(b'experimental', b'remotenames'):
936 logexchange.pullremotenames(destrepo, srcpeer)
936 logexchange.pullremotenames(destrepo, srcpeer)
937
937
938 if update:
938 if update:
939 if update is not True:
939 if update is not True:
940 with srcpeer.commandexecutor() as e:
940 with srcpeer.commandexecutor() as e:
941 checkout = e.callcommand(
941 checkout = e.callcommand(
942 b'lookup', {b'key': update,}
942 b'lookup', {b'key': update,}
943 ).result()
943 ).result()
944
944
945 uprev = None
945 uprev = None
946 status = None
946 status = None
947 if checkout is not None:
947 if checkout is not None:
948 # Some extensions (at least hg-git and hg-subversion) have
948 # Some extensions (at least hg-git and hg-subversion) have
949 # a peer.lookup() implementation that returns a name instead
949 # a peer.lookup() implementation that returns a name instead
950 # of a nodeid. We work around it here until we've figured
950 # of a nodeid. We work around it here until we've figured
951 # out a better solution.
951 # out a better solution.
952 if len(checkout) == 20 and checkout in destrepo:
952 if len(checkout) == 20 and checkout in destrepo:
953 uprev = checkout
953 uprev = checkout
954 elif scmutil.isrevsymbol(destrepo, checkout):
954 elif scmutil.isrevsymbol(destrepo, checkout):
955 uprev = scmutil.revsymbol(destrepo, checkout).node()
955 uprev = scmutil.revsymbol(destrepo, checkout).node()
956 else:
956 else:
957 if update is not True:
957 if update is not True:
958 try:
958 try:
959 uprev = destrepo.lookup(update)
959 uprev = destrepo.lookup(update)
960 except error.RepoLookupError:
960 except error.RepoLookupError:
961 pass
961 pass
962 if uprev is None:
962 if uprev is None:
963 try:
963 try:
964 uprev = destrepo._bookmarks[b'@']
964 uprev = destrepo._bookmarks[b'@']
965 update = b'@'
965 update = b'@'
966 bn = destrepo[uprev].branch()
966 bn = destrepo[uprev].branch()
967 if bn == b'default':
967 if bn == b'default':
968 status = _(b"updating to bookmark @\n")
968 status = _(b"updating to bookmark @\n")
969 else:
969 else:
970 status = (
970 status = (
971 _(b"updating to bookmark @ on branch %s\n") % bn
971 _(b"updating to bookmark @ on branch %s\n") % bn
972 )
972 )
973 except KeyError:
973 except KeyError:
974 try:
974 try:
975 uprev = destrepo.branchtip(b'default')
975 uprev = destrepo.branchtip(b'default')
976 except error.RepoLookupError:
976 except error.RepoLookupError:
977 uprev = destrepo.lookup(b'tip')
977 uprev = destrepo.lookup(b'tip')
978 if not status:
978 if not status:
979 bn = destrepo[uprev].branch()
979 bn = destrepo[uprev].branch()
980 status = _(b"updating to branch %s\n") % bn
980 status = _(b"updating to branch %s\n") % bn
981 destrepo.ui.status(status)
981 destrepo.ui.status(status)
982 _update(destrepo, uprev)
982 _update(destrepo, uprev)
983 if update in destrepo._bookmarks:
983 if update in destrepo._bookmarks:
984 bookmarks.activate(destrepo, update)
984 bookmarks.activate(destrepo, update)
985 finally:
985 finally:
986 release(srclock, destlock)
986 release(srclock, destlock)
987 if cleandir is not None:
987 if cleandir is not None:
988 shutil.rmtree(cleandir, True)
988 shutil.rmtree(cleandir, True)
989 if srcpeer is not None:
989 if srcpeer is not None:
990 srcpeer.close()
990 srcpeer.close()
991 return srcpeer, destpeer
991 return srcpeer, destpeer
992
992
993
993
994 def _showstats(repo, stats, quietempty=False):
994 def _showstats(repo, stats, quietempty=False):
995 if quietempty and stats.isempty():
995 if quietempty and stats.isempty():
996 return
996 return
997 repo.ui.status(
997 repo.ui.status(
998 _(
998 _(
999 b"%d files updated, %d files merged, "
999 b"%d files updated, %d files merged, "
1000 b"%d files removed, %d files unresolved\n"
1000 b"%d files removed, %d files unresolved\n"
1001 )
1001 )
1002 % (
1002 % (
1003 stats.updatedcount,
1003 stats.updatedcount,
1004 stats.mergedcount,
1004 stats.mergedcount,
1005 stats.removedcount,
1005 stats.removedcount,
1006 stats.unresolvedcount,
1006 stats.unresolvedcount,
1007 )
1007 )
1008 )
1008 )
1009
1009
1010
1010
1011 def updaterepo(repo, node, overwrite, updatecheck=None):
1011 def updaterepo(repo, node, overwrite, updatecheck=None):
1012 """Update the working directory to node.
1012 """Update the working directory to node.
1013
1013
1014 When overwrite is set, changes are clobbered, merged else
1014 When overwrite is set, changes are clobbered, merged else
1015
1015
1016 returns stats (see pydoc mercurial.merge.applyupdates)"""
1016 returns stats (see pydoc mercurial.merge.applyupdates)"""
1017 return mergemod.update(
1017 return mergemod.update(
1018 repo,
1018 repo,
1019 node,
1019 node,
1020 branchmerge=False,
1020 branchmerge=False,
1021 force=overwrite,
1021 force=overwrite,
1022 labels=[b'working copy', b'destination'],
1022 labels=[b'working copy', b'destination'],
1023 updatecheck=updatecheck,
1023 updatecheck=updatecheck,
1024 )
1024 )
1025
1025
1026
1026
1027 def update(repo, node, quietempty=False, updatecheck=None):
1027 def update(repo, node, quietempty=False, updatecheck=None):
1028 """update the working directory to node"""
1028 """update the working directory to node"""
1029 stats = updaterepo(repo, node, False, updatecheck=updatecheck)
1029 stats = updaterepo(repo, node, False, updatecheck=updatecheck)
1030 _showstats(repo, stats, quietempty)
1030 _showstats(repo, stats, quietempty)
1031 if stats.unresolvedcount:
1031 if stats.unresolvedcount:
1032 repo.ui.status(_(b"use 'hg resolve' to retry unresolved file merges\n"))
1032 repo.ui.status(_(b"use 'hg resolve' to retry unresolved file merges\n"))
1033 return stats.unresolvedcount > 0
1033 return stats.unresolvedcount > 0
1034
1034
1035
1035
1036 # naming conflict in clone()
1036 # naming conflict in clone()
1037 _update = update
1037 _update = update
1038
1038
1039
1039
1040 def clean(repo, node, show_stats=True, quietempty=False):
1040 def clean(repo, node, show_stats=True, quietempty=False):
1041 """forcibly switch the working directory to node, clobbering changes"""
1041 """forcibly switch the working directory to node, clobbering changes"""
1042 stats = updaterepo(repo, node, True)
1042 stats = updaterepo(repo, node, True)
1043 assert stats.unresolvedcount == 0
1043 assert stats.unresolvedcount == 0
1044 if show_stats:
1044 if show_stats:
1045 _showstats(repo, stats, quietempty)
1045 _showstats(repo, stats, quietempty)
1046
1046
1047
1047
1048 # naming conflict in updatetotally()
1048 # naming conflict in updatetotally()
1049 _clean = clean
1049 _clean = clean
1050
1050
1051 _VALID_UPDATECHECKS = {
1051 _VALID_UPDATECHECKS = {
1052 mergemod.UPDATECHECK_ABORT,
1052 mergemod.UPDATECHECK_ABORT,
1053 mergemod.UPDATECHECK_NONE,
1053 mergemod.UPDATECHECK_NONE,
1054 mergemod.UPDATECHECK_LINEAR,
1054 mergemod.UPDATECHECK_LINEAR,
1055 mergemod.UPDATECHECK_NO_CONFLICT,
1055 mergemod.UPDATECHECK_NO_CONFLICT,
1056 }
1056 }
1057
1057
1058
1058
1059 def updatetotally(ui, repo, checkout, brev, clean=False, updatecheck=None):
1059 def updatetotally(ui, repo, checkout, brev, clean=False, updatecheck=None):
1060 """Update the working directory with extra care for non-file components
1060 """Update the working directory with extra care for non-file components
1061
1061
1062 This takes care of non-file components below:
1062 This takes care of non-file components below:
1063
1063
1064 :bookmark: might be advanced or (in)activated
1064 :bookmark: might be advanced or (in)activated
1065
1065
1066 This takes arguments below:
1066 This takes arguments below:
1067
1067
1068 :checkout: to which revision the working directory is updated
1068 :checkout: to which revision the working directory is updated
1069 :brev: a name, which might be a bookmark to be activated after updating
1069 :brev: a name, which might be a bookmark to be activated after updating
1070 :clean: whether changes in the working directory can be discarded
1070 :clean: whether changes in the working directory can be discarded
1071 :updatecheck: how to deal with a dirty working directory
1071 :updatecheck: how to deal with a dirty working directory
1072
1072
1073 Valid values for updatecheck are the UPDATECHECK_* constants
1073 Valid values for updatecheck are the UPDATECHECK_* constants
1074 defined in the merge module. Passing `None` will result in using the
1074 defined in the merge module. Passing `None` will result in using the
1075 configured default.
1075 configured default.
1076
1076
1077 * ABORT: abort if the working directory is dirty
1077 * ABORT: abort if the working directory is dirty
1078 * NONE: don't check (merge working directory changes into destination)
1078 * NONE: don't check (merge working directory changes into destination)
1079 * LINEAR: check that update is linear before merging working directory
1079 * LINEAR: check that update is linear before merging working directory
1080 changes into destination
1080 changes into destination
1081 * NO_CONFLICT: check that the update does not result in file merges
1081 * NO_CONFLICT: check that the update does not result in file merges
1082
1082
1083 This returns whether conflict is detected at updating or not.
1083 This returns whether conflict is detected at updating or not.
1084 """
1084 """
1085 if updatecheck is None:
1085 if updatecheck is None:
1086 updatecheck = ui.config(b'commands', b'update.check')
1086 updatecheck = ui.config(b'commands', b'update.check')
1087 if updatecheck not in _VALID_UPDATECHECKS:
1087 if updatecheck not in _VALID_UPDATECHECKS:
1088 # If not configured, or invalid value configured
1088 # If not configured, or invalid value configured
1089 updatecheck = mergemod.UPDATECHECK_LINEAR
1089 updatecheck = mergemod.UPDATECHECK_LINEAR
1090 if updatecheck not in _VALID_UPDATECHECKS:
1090 if updatecheck not in _VALID_UPDATECHECKS:
1091 raise ValueError(
1091 raise ValueError(
1092 r'Invalid updatecheck value %r (can accept %r)'
1092 r'Invalid updatecheck value %r (can accept %r)'
1093 % (updatecheck, _VALID_UPDATECHECKS)
1093 % (updatecheck, _VALID_UPDATECHECKS)
1094 )
1094 )
1095 with repo.wlock():
1095 with repo.wlock():
1096 movemarkfrom = None
1096 movemarkfrom = None
1097 warndest = False
1097 warndest = False
1098 if checkout is None:
1098 if checkout is None:
1099 updata = destutil.destupdate(repo, clean=clean)
1099 updata = destutil.destupdate(repo, clean=clean)
1100 checkout, movemarkfrom, brev = updata
1100 checkout, movemarkfrom, brev = updata
1101 warndest = True
1101 warndest = True
1102
1102
1103 if clean:
1103 if clean:
1104 ret = _clean(repo, checkout)
1104 ret = _clean(repo, checkout)
1105 else:
1105 else:
1106 if updatecheck == mergemod.UPDATECHECK_ABORT:
1106 if updatecheck == mergemod.UPDATECHECK_ABORT:
1107 cmdutil.bailifchanged(repo, merge=False)
1107 cmdutil.bailifchanged(repo, merge=False)
1108 updatecheck = mergemod.UPDATECHECK_NONE
1108 updatecheck = mergemod.UPDATECHECK_NONE
1109 ret = _update(repo, checkout, updatecheck=updatecheck)
1109 ret = _update(repo, checkout, updatecheck=updatecheck)
1110
1110
1111 if not ret and movemarkfrom:
1111 if not ret and movemarkfrom:
1112 if movemarkfrom == repo[b'.'].node():
1112 if movemarkfrom == repo[b'.'].node():
1113 pass # no-op update
1113 pass # no-op update
1114 elif bookmarks.update(repo, [movemarkfrom], repo[b'.'].node()):
1114 elif bookmarks.update(repo, [movemarkfrom], repo[b'.'].node()):
1115 b = ui.label(repo._activebookmark, b'bookmarks.active')
1115 b = ui.label(repo._activebookmark, b'bookmarks.active')
1116 ui.status(_(b"updating bookmark %s\n") % b)
1116 ui.status(_(b"updating bookmark %s\n") % b)
1117 else:
1117 else:
1118 # this can happen with a non-linear update
1118 # this can happen with a non-linear update
1119 b = ui.label(repo._activebookmark, b'bookmarks')
1119 b = ui.label(repo._activebookmark, b'bookmarks')
1120 ui.status(_(b"(leaving bookmark %s)\n") % b)
1120 ui.status(_(b"(leaving bookmark %s)\n") % b)
1121 bookmarks.deactivate(repo)
1121 bookmarks.deactivate(repo)
1122 elif brev in repo._bookmarks:
1122 elif brev in repo._bookmarks:
1123 if brev != repo._activebookmark:
1123 if brev != repo._activebookmark:
1124 b = ui.label(brev, b'bookmarks.active')
1124 b = ui.label(brev, b'bookmarks.active')
1125 ui.status(_(b"(activating bookmark %s)\n") % b)
1125 ui.status(_(b"(activating bookmark %s)\n") % b)
1126 bookmarks.activate(repo, brev)
1126 bookmarks.activate(repo, brev)
1127 elif brev:
1127 elif brev:
1128 if repo._activebookmark:
1128 if repo._activebookmark:
1129 b = ui.label(repo._activebookmark, b'bookmarks')
1129 b = ui.label(repo._activebookmark, b'bookmarks')
1130 ui.status(_(b"(leaving bookmark %s)\n") % b)
1130 ui.status(_(b"(leaving bookmark %s)\n") % b)
1131 bookmarks.deactivate(repo)
1131 bookmarks.deactivate(repo)
1132
1132
1133 if warndest:
1133 if warndest:
1134 destutil.statusotherdests(ui, repo)
1134 destutil.statusotherdests(ui, repo)
1135
1135
1136 return ret
1136 return ret
1137
1137
1138
1138
1139 def merge(
1139 def merge(
1140 repo, node, force=False, remind=True, labels=None,
1140 ctx, force=False, remind=True, labels=None,
1141 ):
1141 ):
1142 """Branch merge with node, resolving changes. Return true if any
1142 """Branch merge with node, resolving changes. Return true if any
1143 unresolved conflicts."""
1143 unresolved conflicts."""
1144 stats = mergemod.merge(repo[node], force=force, labels=labels)
1144 repo = ctx.repo()
1145 stats = mergemod.merge(ctx, force=force, labels=labels)
1145 _showstats(repo, stats)
1146 _showstats(repo, stats)
1146 if stats.unresolvedcount:
1147 if stats.unresolvedcount:
1147 repo.ui.status(
1148 repo.ui.status(
1148 _(
1149 _(
1149 b"use 'hg resolve' to retry unresolved file merges "
1150 b"use 'hg resolve' to retry unresolved file merges "
1150 b"or 'hg merge --abort' to abandon\n"
1151 b"or 'hg merge --abort' to abandon\n"
1151 )
1152 )
1152 )
1153 )
1153 elif remind:
1154 elif remind:
1154 repo.ui.status(_(b"(branch merge, don't forget to commit)\n"))
1155 repo.ui.status(_(b"(branch merge, don't forget to commit)\n"))
1155 return stats.unresolvedcount > 0
1156 return stats.unresolvedcount > 0
1156
1157
1157
1158
1158 def abortmerge(ui, repo):
1159 def abortmerge(ui, repo):
1159 ms = mergemod.mergestate.read(repo)
1160 ms = mergemod.mergestate.read(repo)
1160 if ms.active():
1161 if ms.active():
1161 # there were conflicts
1162 # there were conflicts
1162 node = ms.localctx.hex()
1163 node = ms.localctx.hex()
1163 else:
1164 else:
1164 # there were no conficts, mergestate was not stored
1165 # there were no conficts, mergestate was not stored
1165 node = repo[b'.'].hex()
1166 node = repo[b'.'].hex()
1166
1167
1167 repo.ui.status(_(b"aborting the merge, updating back to %s\n") % node[:12])
1168 repo.ui.status(_(b"aborting the merge, updating back to %s\n") % node[:12])
1168 stats = mergemod.clean_update(repo[node])
1169 stats = mergemod.clean_update(repo[node])
1169 assert stats.unresolvedcount == 0
1170 assert stats.unresolvedcount == 0
1170 _showstats(repo, stats)
1171 _showstats(repo, stats)
1171
1172
1172
1173
1173 def _incoming(
1174 def _incoming(
1174 displaychlist, subreporecurse, ui, repo, source, opts, buffered=False
1175 displaychlist, subreporecurse, ui, repo, source, opts, buffered=False
1175 ):
1176 ):
1176 """
1177 """
1177 Helper for incoming / gincoming.
1178 Helper for incoming / gincoming.
1178 displaychlist gets called with
1179 displaychlist gets called with
1179 (remoterepo, incomingchangesetlist, displayer) parameters,
1180 (remoterepo, incomingchangesetlist, displayer) parameters,
1180 and is supposed to contain only code that can't be unified.
1181 and is supposed to contain only code that can't be unified.
1181 """
1182 """
1182 source, branches = parseurl(ui.expandpath(source), opts.get(b'branch'))
1183 source, branches = parseurl(ui.expandpath(source), opts.get(b'branch'))
1183 other = peer(repo, opts, source)
1184 other = peer(repo, opts, source)
1184 ui.status(_(b'comparing with %s\n') % util.hidepassword(source))
1185 ui.status(_(b'comparing with %s\n') % util.hidepassword(source))
1185 revs, checkout = addbranchrevs(repo, other, branches, opts.get(b'rev'))
1186 revs, checkout = addbranchrevs(repo, other, branches, opts.get(b'rev'))
1186
1187
1187 if revs:
1188 if revs:
1188 revs = [other.lookup(rev) for rev in revs]
1189 revs = [other.lookup(rev) for rev in revs]
1189 other, chlist, cleanupfn = bundlerepo.getremotechanges(
1190 other, chlist, cleanupfn = bundlerepo.getremotechanges(
1190 ui, repo, other, revs, opts[b"bundle"], opts[b"force"]
1191 ui, repo, other, revs, opts[b"bundle"], opts[b"force"]
1191 )
1192 )
1192 try:
1193 try:
1193 if not chlist:
1194 if not chlist:
1194 ui.status(_(b"no changes found\n"))
1195 ui.status(_(b"no changes found\n"))
1195 return subreporecurse()
1196 return subreporecurse()
1196 ui.pager(b'incoming')
1197 ui.pager(b'incoming')
1197 displayer = logcmdutil.changesetdisplayer(
1198 displayer = logcmdutil.changesetdisplayer(
1198 ui, other, opts, buffered=buffered
1199 ui, other, opts, buffered=buffered
1199 )
1200 )
1200 displaychlist(other, chlist, displayer)
1201 displaychlist(other, chlist, displayer)
1201 displayer.close()
1202 displayer.close()
1202 finally:
1203 finally:
1203 cleanupfn()
1204 cleanupfn()
1204 subreporecurse()
1205 subreporecurse()
1205 return 0 # exit code is zero since we found incoming changes
1206 return 0 # exit code is zero since we found incoming changes
1206
1207
1207
1208
1208 def incoming(ui, repo, source, opts):
1209 def incoming(ui, repo, source, opts):
1209 def subreporecurse():
1210 def subreporecurse():
1210 ret = 1
1211 ret = 1
1211 if opts.get(b'subrepos'):
1212 if opts.get(b'subrepos'):
1212 ctx = repo[None]
1213 ctx = repo[None]
1213 for subpath in sorted(ctx.substate):
1214 for subpath in sorted(ctx.substate):
1214 sub = ctx.sub(subpath)
1215 sub = ctx.sub(subpath)
1215 ret = min(ret, sub.incoming(ui, source, opts))
1216 ret = min(ret, sub.incoming(ui, source, opts))
1216 return ret
1217 return ret
1217
1218
1218 def display(other, chlist, displayer):
1219 def display(other, chlist, displayer):
1219 limit = logcmdutil.getlimit(opts)
1220 limit = logcmdutil.getlimit(opts)
1220 if opts.get(b'newest_first'):
1221 if opts.get(b'newest_first'):
1221 chlist.reverse()
1222 chlist.reverse()
1222 count = 0
1223 count = 0
1223 for n in chlist:
1224 for n in chlist:
1224 if limit is not None and count >= limit:
1225 if limit is not None and count >= limit:
1225 break
1226 break
1226 parents = [p for p in other.changelog.parents(n) if p != nullid]
1227 parents = [p for p in other.changelog.parents(n) if p != nullid]
1227 if opts.get(b'no_merges') and len(parents) == 2:
1228 if opts.get(b'no_merges') and len(parents) == 2:
1228 continue
1229 continue
1229 count += 1
1230 count += 1
1230 displayer.show(other[n])
1231 displayer.show(other[n])
1231
1232
1232 return _incoming(display, subreporecurse, ui, repo, source, opts)
1233 return _incoming(display, subreporecurse, ui, repo, source, opts)
1233
1234
1234
1235
1235 def _outgoing(ui, repo, dest, opts):
1236 def _outgoing(ui, repo, dest, opts):
1236 path = ui.paths.getpath(dest, default=(b'default-push', b'default'))
1237 path = ui.paths.getpath(dest, default=(b'default-push', b'default'))
1237 if not path:
1238 if not path:
1238 raise error.Abort(
1239 raise error.Abort(
1239 _(b'default repository not configured!'),
1240 _(b'default repository not configured!'),
1240 hint=_(b"see 'hg help config.paths'"),
1241 hint=_(b"see 'hg help config.paths'"),
1241 )
1242 )
1242 dest = path.pushloc or path.loc
1243 dest = path.pushloc or path.loc
1243 branches = path.branch, opts.get(b'branch') or []
1244 branches = path.branch, opts.get(b'branch') or []
1244
1245
1245 ui.status(_(b'comparing with %s\n') % util.hidepassword(dest))
1246 ui.status(_(b'comparing with %s\n') % util.hidepassword(dest))
1246 revs, checkout = addbranchrevs(repo, repo, branches, opts.get(b'rev'))
1247 revs, checkout = addbranchrevs(repo, repo, branches, opts.get(b'rev'))
1247 if revs:
1248 if revs:
1248 revs = [repo[rev].node() for rev in scmutil.revrange(repo, revs)]
1249 revs = [repo[rev].node() for rev in scmutil.revrange(repo, revs)]
1249
1250
1250 other = peer(repo, opts, dest)
1251 other = peer(repo, opts, dest)
1251 outgoing = discovery.findcommonoutgoing(
1252 outgoing = discovery.findcommonoutgoing(
1252 repo, other, revs, force=opts.get(b'force')
1253 repo, other, revs, force=opts.get(b'force')
1253 )
1254 )
1254 o = outgoing.missing
1255 o = outgoing.missing
1255 if not o:
1256 if not o:
1256 scmutil.nochangesfound(repo.ui, repo, outgoing.excluded)
1257 scmutil.nochangesfound(repo.ui, repo, outgoing.excluded)
1257 return o, other
1258 return o, other
1258
1259
1259
1260
1260 def outgoing(ui, repo, dest, opts):
1261 def outgoing(ui, repo, dest, opts):
1261 def recurse():
1262 def recurse():
1262 ret = 1
1263 ret = 1
1263 if opts.get(b'subrepos'):
1264 if opts.get(b'subrepos'):
1264 ctx = repo[None]
1265 ctx = repo[None]
1265 for subpath in sorted(ctx.substate):
1266 for subpath in sorted(ctx.substate):
1266 sub = ctx.sub(subpath)
1267 sub = ctx.sub(subpath)
1267 ret = min(ret, sub.outgoing(ui, dest, opts))
1268 ret = min(ret, sub.outgoing(ui, dest, opts))
1268 return ret
1269 return ret
1269
1270
1270 limit = logcmdutil.getlimit(opts)
1271 limit = logcmdutil.getlimit(opts)
1271 o, other = _outgoing(ui, repo, dest, opts)
1272 o, other = _outgoing(ui, repo, dest, opts)
1272 if not o:
1273 if not o:
1273 cmdutil.outgoinghooks(ui, repo, other, opts, o)
1274 cmdutil.outgoinghooks(ui, repo, other, opts, o)
1274 return recurse()
1275 return recurse()
1275
1276
1276 if opts.get(b'newest_first'):
1277 if opts.get(b'newest_first'):
1277 o.reverse()
1278 o.reverse()
1278 ui.pager(b'outgoing')
1279 ui.pager(b'outgoing')
1279 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
1280 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
1280 count = 0
1281 count = 0
1281 for n in o:
1282 for n in o:
1282 if limit is not None and count >= limit:
1283 if limit is not None and count >= limit:
1283 break
1284 break
1284 parents = [p for p in repo.changelog.parents(n) if p != nullid]
1285 parents = [p for p in repo.changelog.parents(n) if p != nullid]
1285 if opts.get(b'no_merges') and len(parents) == 2:
1286 if opts.get(b'no_merges') and len(parents) == 2:
1286 continue
1287 continue
1287 count += 1
1288 count += 1
1288 displayer.show(repo[n])
1289 displayer.show(repo[n])
1289 displayer.close()
1290 displayer.close()
1290 cmdutil.outgoinghooks(ui, repo, other, opts, o)
1291 cmdutil.outgoinghooks(ui, repo, other, opts, o)
1291 recurse()
1292 recurse()
1292 return 0 # exit code is zero since we found outgoing changes
1293 return 0 # exit code is zero since we found outgoing changes
1293
1294
1294
1295
1295 def verify(repo, level=None):
1296 def verify(repo, level=None):
1296 """verify the consistency of a repository"""
1297 """verify the consistency of a repository"""
1297 ret = verifymod.verify(repo, level=level)
1298 ret = verifymod.verify(repo, level=level)
1298
1299
1299 # Broken subrepo references in hidden csets don't seem worth worrying about,
1300 # Broken subrepo references in hidden csets don't seem worth worrying about,
1300 # since they can't be pushed/pulled, and --hidden can be used if they are a
1301 # since they can't be pushed/pulled, and --hidden can be used if they are a
1301 # concern.
1302 # concern.
1302
1303
1303 # pathto() is needed for -R case
1304 # pathto() is needed for -R case
1304 revs = repo.revs(
1305 revs = repo.revs(
1305 b"filelog(%s)", util.pathto(repo.root, repo.getcwd(), b'.hgsubstate')
1306 b"filelog(%s)", util.pathto(repo.root, repo.getcwd(), b'.hgsubstate')
1306 )
1307 )
1307
1308
1308 if revs:
1309 if revs:
1309 repo.ui.status(_(b'checking subrepo links\n'))
1310 repo.ui.status(_(b'checking subrepo links\n'))
1310 for rev in revs:
1311 for rev in revs:
1311 ctx = repo[rev]
1312 ctx = repo[rev]
1312 try:
1313 try:
1313 for subpath in ctx.substate:
1314 for subpath in ctx.substate:
1314 try:
1315 try:
1315 ret = (
1316 ret = (
1316 ctx.sub(subpath, allowcreate=False).verify() or ret
1317 ctx.sub(subpath, allowcreate=False).verify() or ret
1317 )
1318 )
1318 except error.RepoError as e:
1319 except error.RepoError as e:
1319 repo.ui.warn(b'%d: %s\n' % (rev, e))
1320 repo.ui.warn(b'%d: %s\n' % (rev, e))
1320 except Exception:
1321 except Exception:
1321 repo.ui.warn(
1322 repo.ui.warn(
1322 _(b'.hgsubstate is corrupt in revision %s\n')
1323 _(b'.hgsubstate is corrupt in revision %s\n')
1323 % node.short(ctx.node())
1324 % node.short(ctx.node())
1324 )
1325 )
1325
1326
1326 return ret
1327 return ret
1327
1328
1328
1329
1329 def remoteui(src, opts):
1330 def remoteui(src, opts):
1330 """build a remote ui from ui or repo and opts"""
1331 """build a remote ui from ui or repo and opts"""
1331 if util.safehasattr(src, b'baseui'): # looks like a repository
1332 if util.safehasattr(src, b'baseui'): # looks like a repository
1332 dst = src.baseui.copy() # drop repo-specific config
1333 dst = src.baseui.copy() # drop repo-specific config
1333 src = src.ui # copy target options from repo
1334 src = src.ui # copy target options from repo
1334 else: # assume it's a global ui object
1335 else: # assume it's a global ui object
1335 dst = src.copy() # keep all global options
1336 dst = src.copy() # keep all global options
1336
1337
1337 # copy ssh-specific options
1338 # copy ssh-specific options
1338 for o in b'ssh', b'remotecmd':
1339 for o in b'ssh', b'remotecmd':
1339 v = opts.get(o) or src.config(b'ui', o)
1340 v = opts.get(o) or src.config(b'ui', o)
1340 if v:
1341 if v:
1341 dst.setconfig(b"ui", o, v, b'copied')
1342 dst.setconfig(b"ui", o, v, b'copied')
1342
1343
1343 # copy bundle-specific options
1344 # copy bundle-specific options
1344 r = src.config(b'bundle', b'mainreporoot')
1345 r = src.config(b'bundle', b'mainreporoot')
1345 if r:
1346 if r:
1346 dst.setconfig(b'bundle', b'mainreporoot', r, b'copied')
1347 dst.setconfig(b'bundle', b'mainreporoot', r, b'copied')
1347
1348
1348 # copy selected local settings to the remote ui
1349 # copy selected local settings to the remote ui
1349 for sect in (b'auth', b'hostfingerprints', b'hostsecurity', b'http_proxy'):
1350 for sect in (b'auth', b'hostfingerprints', b'hostsecurity', b'http_proxy'):
1350 for key, val in src.configitems(sect):
1351 for key, val in src.configitems(sect):
1351 dst.setconfig(sect, key, val, b'copied')
1352 dst.setconfig(sect, key, val, b'copied')
1352 v = src.config(b'web', b'cacerts')
1353 v = src.config(b'web', b'cacerts')
1353 if v:
1354 if v:
1354 dst.setconfig(b'web', b'cacerts', util.expandpath(v), b'copied')
1355 dst.setconfig(b'web', b'cacerts', util.expandpath(v), b'copied')
1355
1356
1356 return dst
1357 return dst
1357
1358
1358
1359
1359 # Files of interest
1360 # Files of interest
1360 # Used to check if the repository has changed looking at mtime and size of
1361 # Used to check if the repository has changed looking at mtime and size of
1361 # these files.
1362 # these files.
1362 foi = [
1363 foi = [
1363 (b'spath', b'00changelog.i'),
1364 (b'spath', b'00changelog.i'),
1364 (b'spath', b'phaseroots'), # ! phase can change content at the same size
1365 (b'spath', b'phaseroots'), # ! phase can change content at the same size
1365 (b'spath', b'obsstore'),
1366 (b'spath', b'obsstore'),
1366 (b'path', b'bookmarks'), # ! bookmark can change content at the same size
1367 (b'path', b'bookmarks'), # ! bookmark can change content at the same size
1367 ]
1368 ]
1368
1369
1369
1370
1370 class cachedlocalrepo(object):
1371 class cachedlocalrepo(object):
1371 """Holds a localrepository that can be cached and reused."""
1372 """Holds a localrepository that can be cached and reused."""
1372
1373
1373 def __init__(self, repo):
1374 def __init__(self, repo):
1374 """Create a new cached repo from an existing repo.
1375 """Create a new cached repo from an existing repo.
1375
1376
1376 We assume the passed in repo was recently created. If the
1377 We assume the passed in repo was recently created. If the
1377 repo has changed between when it was created and when it was
1378 repo has changed between when it was created and when it was
1378 turned into a cache, it may not refresh properly.
1379 turned into a cache, it may not refresh properly.
1379 """
1380 """
1380 assert isinstance(repo, localrepo.localrepository)
1381 assert isinstance(repo, localrepo.localrepository)
1381 self._repo = repo
1382 self._repo = repo
1382 self._state, self.mtime = self._repostate()
1383 self._state, self.mtime = self._repostate()
1383 self._filtername = repo.filtername
1384 self._filtername = repo.filtername
1384
1385
1385 def fetch(self):
1386 def fetch(self):
1386 """Refresh (if necessary) and return a repository.
1387 """Refresh (if necessary) and return a repository.
1387
1388
1388 If the cached instance is out of date, it will be recreated
1389 If the cached instance is out of date, it will be recreated
1389 automatically and returned.
1390 automatically and returned.
1390
1391
1391 Returns a tuple of the repo and a boolean indicating whether a new
1392 Returns a tuple of the repo and a boolean indicating whether a new
1392 repo instance was created.
1393 repo instance was created.
1393 """
1394 """
1394 # We compare the mtimes and sizes of some well-known files to
1395 # We compare the mtimes and sizes of some well-known files to
1395 # determine if the repo changed. This is not precise, as mtimes
1396 # determine if the repo changed. This is not precise, as mtimes
1396 # are susceptible to clock skew and imprecise filesystems and
1397 # are susceptible to clock skew and imprecise filesystems and
1397 # file content can change while maintaining the same size.
1398 # file content can change while maintaining the same size.
1398
1399
1399 state, mtime = self._repostate()
1400 state, mtime = self._repostate()
1400 if state == self._state:
1401 if state == self._state:
1401 return self._repo, False
1402 return self._repo, False
1402
1403
1403 repo = repository(self._repo.baseui, self._repo.url())
1404 repo = repository(self._repo.baseui, self._repo.url())
1404 if self._filtername:
1405 if self._filtername:
1405 self._repo = repo.filtered(self._filtername)
1406 self._repo = repo.filtered(self._filtername)
1406 else:
1407 else:
1407 self._repo = repo.unfiltered()
1408 self._repo = repo.unfiltered()
1408 self._state = state
1409 self._state = state
1409 self.mtime = mtime
1410 self.mtime = mtime
1410
1411
1411 return self._repo, True
1412 return self._repo, True
1412
1413
1413 def _repostate(self):
1414 def _repostate(self):
1414 state = []
1415 state = []
1415 maxmtime = -1
1416 maxmtime = -1
1416 for attr, fname in foi:
1417 for attr, fname in foi:
1417 prefix = getattr(self._repo, attr)
1418 prefix = getattr(self._repo, attr)
1418 p = os.path.join(prefix, fname)
1419 p = os.path.join(prefix, fname)
1419 try:
1420 try:
1420 st = os.stat(p)
1421 st = os.stat(p)
1421 except OSError:
1422 except OSError:
1422 st = os.stat(prefix)
1423 st = os.stat(prefix)
1423 state.append((st[stat.ST_MTIME], st.st_size))
1424 state.append((st[stat.ST_MTIME], st.st_size))
1424 maxmtime = max(maxmtime, st[stat.ST_MTIME])
1425 maxmtime = max(maxmtime, st[stat.ST_MTIME])
1425
1426
1426 return tuple(state), maxmtime
1427 return tuple(state), maxmtime
1427
1428
1428 def copy(self):
1429 def copy(self):
1429 """Obtain a copy of this class instance.
1430 """Obtain a copy of this class instance.
1430
1431
1431 A new localrepository instance is obtained. The new instance should be
1432 A new localrepository instance is obtained. The new instance should be
1432 completely independent of the original.
1433 completely independent of the original.
1433 """
1434 """
1434 repo = repository(self._repo.baseui, self._repo.origroot)
1435 repo = repository(self._repo.baseui, self._repo.origroot)
1435 if self._filtername:
1436 if self._filtername:
1436 repo = repo.filtered(self._filtername)
1437 repo = repo.filtered(self._filtername)
1437 else:
1438 else:
1438 repo = repo.unfiltered()
1439 repo = repo.unfiltered()
1439 c = cachedlocalrepo(repo)
1440 c = cachedlocalrepo(repo)
1440 c._state = self._state
1441 c._state = self._state
1441 c.mtime = self.mtime
1442 c.mtime = self.mtime
1442 return c
1443 return c
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