##// END OF EJS Templates
cmdutil: make walkchangerevs() call prepare with matcher instead of filenames...
Yuya Nishihara -
r46225:3a024d7c default
parent child Browse files
Show More
@@ -1,260 +1,259 b''
1 # churn.py - create a graph of revisions count grouped by template
1 # churn.py - create a graph of revisions count grouped by template
2 #
2 #
3 # Copyright 2006 Josef "Jeff" Sipek <jeffpc@josefsipek.net>
3 # Copyright 2006 Josef "Jeff" Sipek <jeffpc@josefsipek.net>
4 # Copyright 2008 Alexander Solovyov <piranha@piranha.org.ua>
4 # Copyright 2008 Alexander Solovyov <piranha@piranha.org.ua>
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 '''command to display statistics about repository history'''
9 '''command to display statistics about repository history'''
10
10
11 from __future__ import absolute_import, division
11 from __future__ import absolute_import, division
12
12
13 import datetime
13 import datetime
14 import os
14 import os
15 import time
15 import time
16
16
17 from mercurial.i18n import _
17 from mercurial.i18n import _
18 from mercurial.pycompat import open
18 from mercurial.pycompat import open
19 from mercurial import (
19 from mercurial import (
20 cmdutil,
20 cmdutil,
21 encoding,
21 encoding,
22 logcmdutil,
22 logcmdutil,
23 patch,
23 patch,
24 pycompat,
24 pycompat,
25 registrar,
25 registrar,
26 scmutil,
26 scmutil,
27 )
27 )
28 from mercurial.utils import dateutil
28 from mercurial.utils import dateutil
29
29
30 cmdtable = {}
30 cmdtable = {}
31 command = registrar.command(cmdtable)
31 command = registrar.command(cmdtable)
32 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
32 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
33 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
33 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
34 # be specifying the version(s) of Mercurial they are tested with, or
34 # be specifying the version(s) of Mercurial they are tested with, or
35 # leave the attribute unspecified.
35 # leave the attribute unspecified.
36 testedwith = b'ships-with-hg-core'
36 testedwith = b'ships-with-hg-core'
37
37
38
38
39 def changedlines(ui, repo, ctx1, ctx2, fns):
39 def changedlines(ui, repo, ctx1, ctx2, fmatch):
40 added, removed = 0, 0
40 added, removed = 0, 0
41 fmatch = scmutil.matchfiles(repo, fns)
42 diff = b''.join(patch.diff(repo, ctx1.node(), ctx2.node(), fmatch))
41 diff = b''.join(patch.diff(repo, ctx1.node(), ctx2.node(), fmatch))
43 for l in diff.split(b'\n'):
42 for l in diff.split(b'\n'):
44 if l.startswith(b"+") and not l.startswith(b"+++ "):
43 if l.startswith(b"+") and not l.startswith(b"+++ "):
45 added += 1
44 added += 1
46 elif l.startswith(b"-") and not l.startswith(b"--- "):
45 elif l.startswith(b"-") and not l.startswith(b"--- "):
47 removed += 1
46 removed += 1
48 return (added, removed)
47 return (added, removed)
49
48
50
49
51 def countrate(ui, repo, amap, *pats, **opts):
50 def countrate(ui, repo, amap, *pats, **opts):
52 """Calculate stats"""
51 """Calculate stats"""
53 opts = pycompat.byteskwargs(opts)
52 opts = pycompat.byteskwargs(opts)
54 if opts.get(b'dateformat'):
53 if opts.get(b'dateformat'):
55
54
56 def getkey(ctx):
55 def getkey(ctx):
57 t, tz = ctx.date()
56 t, tz = ctx.date()
58 date = datetime.datetime(*time.gmtime(float(t) - tz)[:6])
57 date = datetime.datetime(*time.gmtime(float(t) - tz)[:6])
59 return encoding.strtolocal(
58 return encoding.strtolocal(
60 date.strftime(encoding.strfromlocal(opts[b'dateformat']))
59 date.strftime(encoding.strfromlocal(opts[b'dateformat']))
61 )
60 )
62
61
63 else:
62 else:
64 tmpl = opts.get(b'oldtemplate') or opts.get(b'template')
63 tmpl = opts.get(b'oldtemplate') or opts.get(b'template')
65 tmpl = logcmdutil.maketemplater(ui, repo, tmpl)
64 tmpl = logcmdutil.maketemplater(ui, repo, tmpl)
66
65
67 def getkey(ctx):
66 def getkey(ctx):
68 ui.pushbuffer()
67 ui.pushbuffer()
69 tmpl.show(ctx)
68 tmpl.show(ctx)
70 return ui.popbuffer()
69 return ui.popbuffer()
71
70
72 progress = ui.makeprogress(
71 progress = ui.makeprogress(
73 _(b'analyzing'), unit=_(b'revisions'), total=len(repo)
72 _(b'analyzing'), unit=_(b'revisions'), total=len(repo)
74 )
73 )
75 rate = {}
74 rate = {}
76 df = False
75 df = False
77 if opts.get(b'date'):
76 if opts.get(b'date'):
78 df = dateutil.matchdate(opts[b'date'])
77 df = dateutil.matchdate(opts[b'date'])
79
78
80 m = scmutil.match(repo[None], pats, opts)
79 m = scmutil.match(repo[None], pats, opts)
81
80
82 def prep(ctx, fns):
81 def prep(ctx, fmatch):
83 rev = ctx.rev()
82 rev = ctx.rev()
84 if df and not df(ctx.date()[0]): # doesn't match date format
83 if df and not df(ctx.date()[0]): # doesn't match date format
85 return
84 return
86
85
87 key = getkey(ctx).strip()
86 key = getkey(ctx).strip()
88 key = amap.get(key, key) # alias remap
87 key = amap.get(key, key) # alias remap
89 if opts.get(b'changesets'):
88 if opts.get(b'changesets'):
90 rate[key] = (rate.get(key, (0,))[0] + 1, 0)
89 rate[key] = (rate.get(key, (0,))[0] + 1, 0)
91 else:
90 else:
92 parents = ctx.parents()
91 parents = ctx.parents()
93 if len(parents) > 1:
92 if len(parents) > 1:
94 ui.note(_(b'revision %d is a merge, ignoring...\n') % (rev,))
93 ui.note(_(b'revision %d is a merge, ignoring...\n') % (rev,))
95 return
94 return
96
95
97 ctx1 = parents[0]
96 ctx1 = parents[0]
98 lines = changedlines(ui, repo, ctx1, ctx, fns)
97 lines = changedlines(ui, repo, ctx1, ctx, fmatch)
99 rate[key] = [r + l for r, l in zip(rate.get(key, (0, 0)), lines)]
98 rate[key] = [r + l for r, l in zip(rate.get(key, (0, 0)), lines)]
100
99
101 progress.increment()
100 progress.increment()
102
101
103 for ctx in cmdutil.walkchangerevs(repo, m, opts, prep):
102 for ctx in cmdutil.walkchangerevs(repo, m, opts, prep):
104 continue
103 continue
105
104
106 progress.complete()
105 progress.complete()
107
106
108 return rate
107 return rate
109
108
110
109
111 @command(
110 @command(
112 b'churn',
111 b'churn',
113 [
112 [
114 (
113 (
115 b'r',
114 b'r',
116 b'rev',
115 b'rev',
117 [],
116 [],
118 _(b'count rate for the specified revision or revset'),
117 _(b'count rate for the specified revision or revset'),
119 _(b'REV'),
118 _(b'REV'),
120 ),
119 ),
121 (
120 (
122 b'd',
121 b'd',
123 b'date',
122 b'date',
124 b'',
123 b'',
125 _(b'count rate for revisions matching date spec'),
124 _(b'count rate for revisions matching date spec'),
126 _(b'DATE'),
125 _(b'DATE'),
127 ),
126 ),
128 (
127 (
129 b't',
128 b't',
130 b'oldtemplate',
129 b'oldtemplate',
131 b'',
130 b'',
132 _(b'template to group changesets (DEPRECATED)'),
131 _(b'template to group changesets (DEPRECATED)'),
133 _(b'TEMPLATE'),
132 _(b'TEMPLATE'),
134 ),
133 ),
135 (
134 (
136 b'T',
135 b'T',
137 b'template',
136 b'template',
138 b'{author|email}',
137 b'{author|email}',
139 _(b'template to group changesets'),
138 _(b'template to group changesets'),
140 _(b'TEMPLATE'),
139 _(b'TEMPLATE'),
141 ),
140 ),
142 (
141 (
143 b'f',
142 b'f',
144 b'dateformat',
143 b'dateformat',
145 b'',
144 b'',
146 _(b'strftime-compatible format for grouping by date'),
145 _(b'strftime-compatible format for grouping by date'),
147 _(b'FORMAT'),
146 _(b'FORMAT'),
148 ),
147 ),
149 (b'c', b'changesets', False, _(b'count rate by number of changesets')),
148 (b'c', b'changesets', False, _(b'count rate by number of changesets')),
150 (b's', b'sort', False, _(b'sort by key (default: sort by count)')),
149 (b's', b'sort', False, _(b'sort by key (default: sort by count)')),
151 (b'', b'diffstat', False, _(b'display added/removed lines separately')),
150 (b'', b'diffstat', False, _(b'display added/removed lines separately')),
152 (b'', b'aliases', b'', _(b'file with email aliases'), _(b'FILE')),
151 (b'', b'aliases', b'', _(b'file with email aliases'), _(b'FILE')),
153 ]
152 ]
154 + cmdutil.walkopts,
153 + cmdutil.walkopts,
155 _(b"hg churn [-d DATE] [-r REV] [--aliases FILE] [FILE]"),
154 _(b"hg churn [-d DATE] [-r REV] [--aliases FILE] [FILE]"),
156 helpcategory=command.CATEGORY_MAINTENANCE,
155 helpcategory=command.CATEGORY_MAINTENANCE,
157 inferrepo=True,
156 inferrepo=True,
158 )
157 )
159 def churn(ui, repo, *pats, **opts):
158 def churn(ui, repo, *pats, **opts):
160 '''histogram of changes to the repository
159 '''histogram of changes to the repository
161
160
162 This command will display a histogram representing the number
161 This command will display a histogram representing the number
163 of changed lines or revisions, grouped according to the given
162 of changed lines or revisions, grouped according to the given
164 template. The default template will group changes by author.
163 template. The default template will group changes by author.
165 The --dateformat option may be used to group the results by
164 The --dateformat option may be used to group the results by
166 date instead.
165 date instead.
167
166
168 Statistics are based on the number of changed lines, or
167 Statistics are based on the number of changed lines, or
169 alternatively the number of matching revisions if the
168 alternatively the number of matching revisions if the
170 --changesets option is specified.
169 --changesets option is specified.
171
170
172 Examples::
171 Examples::
173
172
174 # display count of changed lines for every committer
173 # display count of changed lines for every committer
175 hg churn -T "{author|email}"
174 hg churn -T "{author|email}"
176
175
177 # display daily activity graph
176 # display daily activity graph
178 hg churn -f "%H" -s -c
177 hg churn -f "%H" -s -c
179
178
180 # display activity of developers by month
179 # display activity of developers by month
181 hg churn -f "%Y-%m" -s -c
180 hg churn -f "%Y-%m" -s -c
182
181
183 # display count of lines changed in every year
182 # display count of lines changed in every year
184 hg churn -f "%Y" -s
183 hg churn -f "%Y" -s
185
184
186 # display count of lines changed in a time range
185 # display count of lines changed in a time range
187 hg churn -d "2020-04 to 2020-09"
186 hg churn -d "2020-04 to 2020-09"
188
187
189 It is possible to map alternate email addresses to a main address
188 It is possible to map alternate email addresses to a main address
190 by providing a file using the following format::
189 by providing a file using the following format::
191
190
192 <alias email> = <actual email>
191 <alias email> = <actual email>
193
192
194 Such a file may be specified with the --aliases option, otherwise
193 Such a file may be specified with the --aliases option, otherwise
195 a .hgchurn file will be looked for in the working directory root.
194 a .hgchurn file will be looked for in the working directory root.
196 Aliases will be split from the rightmost "=".
195 Aliases will be split from the rightmost "=".
197 '''
196 '''
198
197
199 def pad(s, l):
198 def pad(s, l):
200 return s + b" " * (l - encoding.colwidth(s))
199 return s + b" " * (l - encoding.colwidth(s))
201
200
202 amap = {}
201 amap = {}
203 aliases = opts.get('aliases')
202 aliases = opts.get('aliases')
204 if not aliases and os.path.exists(repo.wjoin(b'.hgchurn')):
203 if not aliases and os.path.exists(repo.wjoin(b'.hgchurn')):
205 aliases = repo.wjoin(b'.hgchurn')
204 aliases = repo.wjoin(b'.hgchurn')
206 if aliases:
205 if aliases:
207 for l in open(aliases, b"rb"):
206 for l in open(aliases, b"rb"):
208 try:
207 try:
209 alias, actual = l.rsplit(b'=' in l and b'=' or None, 1)
208 alias, actual = l.rsplit(b'=' in l and b'=' or None, 1)
210 amap[alias.strip()] = actual.strip()
209 amap[alias.strip()] = actual.strip()
211 except ValueError:
210 except ValueError:
212 l = l.strip()
211 l = l.strip()
213 if l:
212 if l:
214 ui.warn(_(b"skipping malformed alias: %s\n") % l)
213 ui.warn(_(b"skipping malformed alias: %s\n") % l)
215 continue
214 continue
216
215
217 rate = list(countrate(ui, repo, amap, *pats, **opts).items())
216 rate = list(countrate(ui, repo, amap, *pats, **opts).items())
218 if not rate:
217 if not rate:
219 return
218 return
220
219
221 if opts.get('sort'):
220 if opts.get('sort'):
222 rate.sort()
221 rate.sort()
223 else:
222 else:
224 rate.sort(key=lambda x: (-sum(x[1]), x))
223 rate.sort(key=lambda x: (-sum(x[1]), x))
225
224
226 # Be careful not to have a zero maxcount (issue833)
225 # Be careful not to have a zero maxcount (issue833)
227 maxcount = float(max(sum(v) for k, v in rate)) or 1.0
226 maxcount = float(max(sum(v) for k, v in rate)) or 1.0
228 maxname = max(len(k) for k, v in rate)
227 maxname = max(len(k) for k, v in rate)
229
228
230 ttywidth = ui.termwidth()
229 ttywidth = ui.termwidth()
231 ui.debug(b"assuming %i character terminal\n" % ttywidth)
230 ui.debug(b"assuming %i character terminal\n" % ttywidth)
232 width = ttywidth - maxname - 2 - 2 - 2
231 width = ttywidth - maxname - 2 - 2 - 2
233
232
234 if opts.get('diffstat'):
233 if opts.get('diffstat'):
235 width -= 15
234 width -= 15
236
235
237 def format(name, diffstat):
236 def format(name, diffstat):
238 added, removed = diffstat
237 added, removed = diffstat
239 return b"%s %15s %s%s\n" % (
238 return b"%s %15s %s%s\n" % (
240 pad(name, maxname),
239 pad(name, maxname),
241 b'+%d/-%d' % (added, removed),
240 b'+%d/-%d' % (added, removed),
242 ui.label(b'+' * charnum(added), b'diffstat.inserted'),
241 ui.label(b'+' * charnum(added), b'diffstat.inserted'),
243 ui.label(b'-' * charnum(removed), b'diffstat.deleted'),
242 ui.label(b'-' * charnum(removed), b'diffstat.deleted'),
244 )
243 )
245
244
246 else:
245 else:
247 width -= 6
246 width -= 6
248
247
249 def format(name, count):
248 def format(name, count):
250 return b"%s %6d %s\n" % (
249 return b"%s %6d %s\n" % (
251 pad(name, maxname),
250 pad(name, maxname),
252 sum(count),
251 sum(count),
253 b'*' * charnum(sum(count)),
252 b'*' * charnum(sum(count)),
254 )
253 )
255
254
256 def charnum(count):
255 def charnum(count):
257 return int(count * width // maxcount)
256 return int(count * width // maxcount)
258
257
259 for name, count in rate:
258 for name, count in rate:
260 ui.write(format(name, count))
259 ui.write(format(name, count))
@@ -1,4216 +1,4216 b''
1 # cmdutil.py - help for command processing in mercurial
1 # cmdutil.py - help for command processing in mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import copy as copymod
10 import copy as copymod
11 import errno
11 import errno
12 import os
12 import os
13 import re
13 import re
14
14
15 from .i18n import _
15 from .i18n import _
16 from .node import (
16 from .node import (
17 hex,
17 hex,
18 nullid,
18 nullid,
19 nullrev,
19 nullrev,
20 short,
20 short,
21 )
21 )
22 from .pycompat import (
22 from .pycompat import (
23 getattr,
23 getattr,
24 open,
24 open,
25 setattr,
25 setattr,
26 )
26 )
27 from .thirdparty import attr
27 from .thirdparty import attr
28
28
29 from . import (
29 from . import (
30 bookmarks,
30 bookmarks,
31 changelog,
31 changelog,
32 copies,
32 copies,
33 crecord as crecordmod,
33 crecord as crecordmod,
34 dirstateguard,
34 dirstateguard,
35 encoding,
35 encoding,
36 error,
36 error,
37 formatter,
37 formatter,
38 logcmdutil,
38 logcmdutil,
39 match as matchmod,
39 match as matchmod,
40 merge as mergemod,
40 merge as mergemod,
41 mergestate as mergestatemod,
41 mergestate as mergestatemod,
42 mergeutil,
42 mergeutil,
43 obsolete,
43 obsolete,
44 patch,
44 patch,
45 pathutil,
45 pathutil,
46 phases,
46 phases,
47 pycompat,
47 pycompat,
48 repair,
48 repair,
49 revlog,
49 revlog,
50 rewriteutil,
50 rewriteutil,
51 scmutil,
51 scmutil,
52 smartset,
52 smartset,
53 state as statemod,
53 state as statemod,
54 subrepoutil,
54 subrepoutil,
55 templatekw,
55 templatekw,
56 templater,
56 templater,
57 util,
57 util,
58 vfs as vfsmod,
58 vfs as vfsmod,
59 )
59 )
60
60
61 from .utils import (
61 from .utils import (
62 dateutil,
62 dateutil,
63 stringutil,
63 stringutil,
64 )
64 )
65
65
66 if pycompat.TYPE_CHECKING:
66 if pycompat.TYPE_CHECKING:
67 from typing import (
67 from typing import (
68 Any,
68 Any,
69 Dict,
69 Dict,
70 )
70 )
71
71
72 for t in (Any, Dict):
72 for t in (Any, Dict):
73 assert t
73 assert t
74
74
75 stringio = util.stringio
75 stringio = util.stringio
76
76
77 # templates of common command options
77 # templates of common command options
78
78
79 dryrunopts = [
79 dryrunopts = [
80 (b'n', b'dry-run', None, _(b'do not perform actions, just print output')),
80 (b'n', b'dry-run', None, _(b'do not perform actions, just print output')),
81 ]
81 ]
82
82
83 confirmopts = [
83 confirmopts = [
84 (b'', b'confirm', None, _(b'ask before applying actions')),
84 (b'', b'confirm', None, _(b'ask before applying actions')),
85 ]
85 ]
86
86
87 remoteopts = [
87 remoteopts = [
88 (b'e', b'ssh', b'', _(b'specify ssh command to use'), _(b'CMD')),
88 (b'e', b'ssh', b'', _(b'specify ssh command to use'), _(b'CMD')),
89 (
89 (
90 b'',
90 b'',
91 b'remotecmd',
91 b'remotecmd',
92 b'',
92 b'',
93 _(b'specify hg command to run on the remote side'),
93 _(b'specify hg command to run on the remote side'),
94 _(b'CMD'),
94 _(b'CMD'),
95 ),
95 ),
96 (
96 (
97 b'',
97 b'',
98 b'insecure',
98 b'insecure',
99 None,
99 None,
100 _(b'do not verify server certificate (ignoring web.cacerts config)'),
100 _(b'do not verify server certificate (ignoring web.cacerts config)'),
101 ),
101 ),
102 ]
102 ]
103
103
104 walkopts = [
104 walkopts = [
105 (
105 (
106 b'I',
106 b'I',
107 b'include',
107 b'include',
108 [],
108 [],
109 _(b'include names matching the given patterns'),
109 _(b'include names matching the given patterns'),
110 _(b'PATTERN'),
110 _(b'PATTERN'),
111 ),
111 ),
112 (
112 (
113 b'X',
113 b'X',
114 b'exclude',
114 b'exclude',
115 [],
115 [],
116 _(b'exclude names matching the given patterns'),
116 _(b'exclude names matching the given patterns'),
117 _(b'PATTERN'),
117 _(b'PATTERN'),
118 ),
118 ),
119 ]
119 ]
120
120
121 commitopts = [
121 commitopts = [
122 (b'm', b'message', b'', _(b'use text as commit message'), _(b'TEXT')),
122 (b'm', b'message', b'', _(b'use text as commit message'), _(b'TEXT')),
123 (b'l', b'logfile', b'', _(b'read commit message from file'), _(b'FILE')),
123 (b'l', b'logfile', b'', _(b'read commit message from file'), _(b'FILE')),
124 ]
124 ]
125
125
126 commitopts2 = [
126 commitopts2 = [
127 (
127 (
128 b'd',
128 b'd',
129 b'date',
129 b'date',
130 b'',
130 b'',
131 _(b'record the specified date as commit date'),
131 _(b'record the specified date as commit date'),
132 _(b'DATE'),
132 _(b'DATE'),
133 ),
133 ),
134 (
134 (
135 b'u',
135 b'u',
136 b'user',
136 b'user',
137 b'',
137 b'',
138 _(b'record the specified user as committer'),
138 _(b'record the specified user as committer'),
139 _(b'USER'),
139 _(b'USER'),
140 ),
140 ),
141 ]
141 ]
142
142
143 commitopts3 = [
143 commitopts3 = [
144 (b'D', b'currentdate', None, _(b'record the current date as commit date')),
144 (b'D', b'currentdate', None, _(b'record the current date as commit date')),
145 (b'U', b'currentuser', None, _(b'record the current user as committer')),
145 (b'U', b'currentuser', None, _(b'record the current user as committer')),
146 ]
146 ]
147
147
148 formatteropts = [
148 formatteropts = [
149 (b'T', b'template', b'', _(b'display with template'), _(b'TEMPLATE')),
149 (b'T', b'template', b'', _(b'display with template'), _(b'TEMPLATE')),
150 ]
150 ]
151
151
152 templateopts = [
152 templateopts = [
153 (
153 (
154 b'',
154 b'',
155 b'style',
155 b'style',
156 b'',
156 b'',
157 _(b'display using template map file (DEPRECATED)'),
157 _(b'display using template map file (DEPRECATED)'),
158 _(b'STYLE'),
158 _(b'STYLE'),
159 ),
159 ),
160 (b'T', b'template', b'', _(b'display with template'), _(b'TEMPLATE')),
160 (b'T', b'template', b'', _(b'display with template'), _(b'TEMPLATE')),
161 ]
161 ]
162
162
163 logopts = [
163 logopts = [
164 (b'p', b'patch', None, _(b'show patch')),
164 (b'p', b'patch', None, _(b'show patch')),
165 (b'g', b'git', None, _(b'use git extended diff format')),
165 (b'g', b'git', None, _(b'use git extended diff format')),
166 (b'l', b'limit', b'', _(b'limit number of changes displayed'), _(b'NUM')),
166 (b'l', b'limit', b'', _(b'limit number of changes displayed'), _(b'NUM')),
167 (b'M', b'no-merges', None, _(b'do not show merges')),
167 (b'M', b'no-merges', None, _(b'do not show merges')),
168 (b'', b'stat', None, _(b'output diffstat-style summary of changes')),
168 (b'', b'stat', None, _(b'output diffstat-style summary of changes')),
169 (b'G', b'graph', None, _(b"show the revision DAG")),
169 (b'G', b'graph', None, _(b"show the revision DAG")),
170 ] + templateopts
170 ] + templateopts
171
171
172 diffopts = [
172 diffopts = [
173 (b'a', b'text', None, _(b'treat all files as text')),
173 (b'a', b'text', None, _(b'treat all files as text')),
174 (
174 (
175 b'g',
175 b'g',
176 b'git',
176 b'git',
177 None,
177 None,
178 _(b'use git extended diff format (DEFAULT: diff.git)'),
178 _(b'use git extended diff format (DEFAULT: diff.git)'),
179 ),
179 ),
180 (b'', b'binary', None, _(b'generate binary diffs in git mode (default)')),
180 (b'', b'binary', None, _(b'generate binary diffs in git mode (default)')),
181 (b'', b'nodates', None, _(b'omit dates from diff headers')),
181 (b'', b'nodates', None, _(b'omit dates from diff headers')),
182 ]
182 ]
183
183
184 diffwsopts = [
184 diffwsopts = [
185 (
185 (
186 b'w',
186 b'w',
187 b'ignore-all-space',
187 b'ignore-all-space',
188 None,
188 None,
189 _(b'ignore white space when comparing lines'),
189 _(b'ignore white space when comparing lines'),
190 ),
190 ),
191 (
191 (
192 b'b',
192 b'b',
193 b'ignore-space-change',
193 b'ignore-space-change',
194 None,
194 None,
195 _(b'ignore changes in the amount of white space'),
195 _(b'ignore changes in the amount of white space'),
196 ),
196 ),
197 (
197 (
198 b'B',
198 b'B',
199 b'ignore-blank-lines',
199 b'ignore-blank-lines',
200 None,
200 None,
201 _(b'ignore changes whose lines are all blank'),
201 _(b'ignore changes whose lines are all blank'),
202 ),
202 ),
203 (
203 (
204 b'Z',
204 b'Z',
205 b'ignore-space-at-eol',
205 b'ignore-space-at-eol',
206 None,
206 None,
207 _(b'ignore changes in whitespace at EOL'),
207 _(b'ignore changes in whitespace at EOL'),
208 ),
208 ),
209 ]
209 ]
210
210
211 diffopts2 = (
211 diffopts2 = (
212 [
212 [
213 (b'', b'noprefix', None, _(b'omit a/ and b/ prefixes from filenames')),
213 (b'', b'noprefix', None, _(b'omit a/ and b/ prefixes from filenames')),
214 (
214 (
215 b'p',
215 b'p',
216 b'show-function',
216 b'show-function',
217 None,
217 None,
218 _(
218 _(
219 b'show which function each change is in (DEFAULT: diff.showfunc)'
219 b'show which function each change is in (DEFAULT: diff.showfunc)'
220 ),
220 ),
221 ),
221 ),
222 (b'', b'reverse', None, _(b'produce a diff that undoes the changes')),
222 (b'', b'reverse', None, _(b'produce a diff that undoes the changes')),
223 ]
223 ]
224 + diffwsopts
224 + diffwsopts
225 + [
225 + [
226 (
226 (
227 b'U',
227 b'U',
228 b'unified',
228 b'unified',
229 b'',
229 b'',
230 _(b'number of lines of context to show'),
230 _(b'number of lines of context to show'),
231 _(b'NUM'),
231 _(b'NUM'),
232 ),
232 ),
233 (b'', b'stat', None, _(b'output diffstat-style summary of changes')),
233 (b'', b'stat', None, _(b'output diffstat-style summary of changes')),
234 (
234 (
235 b'',
235 b'',
236 b'root',
236 b'root',
237 b'',
237 b'',
238 _(b'produce diffs relative to subdirectory'),
238 _(b'produce diffs relative to subdirectory'),
239 _(b'DIR'),
239 _(b'DIR'),
240 ),
240 ),
241 ]
241 ]
242 )
242 )
243
243
244 mergetoolopts = [
244 mergetoolopts = [
245 (b't', b'tool', b'', _(b'specify merge tool'), _(b'TOOL')),
245 (b't', b'tool', b'', _(b'specify merge tool'), _(b'TOOL')),
246 ]
246 ]
247
247
248 similarityopts = [
248 similarityopts = [
249 (
249 (
250 b's',
250 b's',
251 b'similarity',
251 b'similarity',
252 b'',
252 b'',
253 _(b'guess renamed files by similarity (0<=s<=100)'),
253 _(b'guess renamed files by similarity (0<=s<=100)'),
254 _(b'SIMILARITY'),
254 _(b'SIMILARITY'),
255 )
255 )
256 ]
256 ]
257
257
258 subrepoopts = [(b'S', b'subrepos', None, _(b'recurse into subrepositories'))]
258 subrepoopts = [(b'S', b'subrepos', None, _(b'recurse into subrepositories'))]
259
259
260 debugrevlogopts = [
260 debugrevlogopts = [
261 (b'c', b'changelog', False, _(b'open changelog')),
261 (b'c', b'changelog', False, _(b'open changelog')),
262 (b'm', b'manifest', False, _(b'open manifest')),
262 (b'm', b'manifest', False, _(b'open manifest')),
263 (b'', b'dir', b'', _(b'open directory manifest')),
263 (b'', b'dir', b'', _(b'open directory manifest')),
264 ]
264 ]
265
265
266 # special string such that everything below this line will be ingored in the
266 # special string such that everything below this line will be ingored in the
267 # editor text
267 # editor text
268 _linebelow = b"^HG: ------------------------ >8 ------------------------$"
268 _linebelow = b"^HG: ------------------------ >8 ------------------------$"
269
269
270
270
271 def check_at_most_one_arg(opts, *args):
271 def check_at_most_one_arg(opts, *args):
272 """abort if more than one of the arguments are in opts
272 """abort if more than one of the arguments are in opts
273
273
274 Returns the unique argument or None if none of them were specified.
274 Returns the unique argument or None if none of them were specified.
275 """
275 """
276
276
277 def to_display(name):
277 def to_display(name):
278 return pycompat.sysbytes(name).replace(b'_', b'-')
278 return pycompat.sysbytes(name).replace(b'_', b'-')
279
279
280 previous = None
280 previous = None
281 for x in args:
281 for x in args:
282 if opts.get(x):
282 if opts.get(x):
283 if previous:
283 if previous:
284 raise error.Abort(
284 raise error.Abort(
285 _(b'cannot specify both --%s and --%s')
285 _(b'cannot specify both --%s and --%s')
286 % (to_display(previous), to_display(x))
286 % (to_display(previous), to_display(x))
287 )
287 )
288 previous = x
288 previous = x
289 return previous
289 return previous
290
290
291
291
292 def check_incompatible_arguments(opts, first, others):
292 def check_incompatible_arguments(opts, first, others):
293 """abort if the first argument is given along with any of the others
293 """abort if the first argument is given along with any of the others
294
294
295 Unlike check_at_most_one_arg(), `others` are not mutually exclusive
295 Unlike check_at_most_one_arg(), `others` are not mutually exclusive
296 among themselves, and they're passed as a single collection.
296 among themselves, and they're passed as a single collection.
297 """
297 """
298 for other in others:
298 for other in others:
299 check_at_most_one_arg(opts, first, other)
299 check_at_most_one_arg(opts, first, other)
300
300
301
301
302 def resolvecommitoptions(ui, opts):
302 def resolvecommitoptions(ui, opts):
303 """modify commit options dict to handle related options
303 """modify commit options dict to handle related options
304
304
305 The return value indicates that ``rewrite.update-timestamp`` is the reason
305 The return value indicates that ``rewrite.update-timestamp`` is the reason
306 the ``date`` option is set.
306 the ``date`` option is set.
307 """
307 """
308 check_at_most_one_arg(opts, b'date', b'currentdate')
308 check_at_most_one_arg(opts, b'date', b'currentdate')
309 check_at_most_one_arg(opts, b'user', b'currentuser')
309 check_at_most_one_arg(opts, b'user', b'currentuser')
310
310
311 datemaydiffer = False # date-only change should be ignored?
311 datemaydiffer = False # date-only change should be ignored?
312
312
313 if opts.get(b'currentdate'):
313 if opts.get(b'currentdate'):
314 opts[b'date'] = b'%d %d' % dateutil.makedate()
314 opts[b'date'] = b'%d %d' % dateutil.makedate()
315 elif (
315 elif (
316 not opts.get(b'date')
316 not opts.get(b'date')
317 and ui.configbool(b'rewrite', b'update-timestamp')
317 and ui.configbool(b'rewrite', b'update-timestamp')
318 and opts.get(b'currentdate') is None
318 and opts.get(b'currentdate') is None
319 ):
319 ):
320 opts[b'date'] = b'%d %d' % dateutil.makedate()
320 opts[b'date'] = b'%d %d' % dateutil.makedate()
321 datemaydiffer = True
321 datemaydiffer = True
322
322
323 if opts.get(b'currentuser'):
323 if opts.get(b'currentuser'):
324 opts[b'user'] = ui.username()
324 opts[b'user'] = ui.username()
325
325
326 return datemaydiffer
326 return datemaydiffer
327
327
328
328
329 def checknotesize(ui, opts):
329 def checknotesize(ui, opts):
330 """ make sure note is of valid format """
330 """ make sure note is of valid format """
331
331
332 note = opts.get(b'note')
332 note = opts.get(b'note')
333 if not note:
333 if not note:
334 return
334 return
335
335
336 if len(note) > 255:
336 if len(note) > 255:
337 raise error.Abort(_(b"cannot store a note of more than 255 bytes"))
337 raise error.Abort(_(b"cannot store a note of more than 255 bytes"))
338 if b'\n' in note:
338 if b'\n' in note:
339 raise error.Abort(_(b"note cannot contain a newline"))
339 raise error.Abort(_(b"note cannot contain a newline"))
340
340
341
341
342 def ishunk(x):
342 def ishunk(x):
343 hunkclasses = (crecordmod.uihunk, patch.recordhunk)
343 hunkclasses = (crecordmod.uihunk, patch.recordhunk)
344 return isinstance(x, hunkclasses)
344 return isinstance(x, hunkclasses)
345
345
346
346
347 def newandmodified(chunks, originalchunks):
347 def newandmodified(chunks, originalchunks):
348 newlyaddedandmodifiedfiles = set()
348 newlyaddedandmodifiedfiles = set()
349 alsorestore = set()
349 alsorestore = set()
350 for chunk in chunks:
350 for chunk in chunks:
351 if (
351 if (
352 ishunk(chunk)
352 ishunk(chunk)
353 and chunk.header.isnewfile()
353 and chunk.header.isnewfile()
354 and chunk not in originalchunks
354 and chunk not in originalchunks
355 ):
355 ):
356 newlyaddedandmodifiedfiles.add(chunk.header.filename())
356 newlyaddedandmodifiedfiles.add(chunk.header.filename())
357 alsorestore.update(
357 alsorestore.update(
358 set(chunk.header.files()) - {chunk.header.filename()}
358 set(chunk.header.files()) - {chunk.header.filename()}
359 )
359 )
360 return newlyaddedandmodifiedfiles, alsorestore
360 return newlyaddedandmodifiedfiles, alsorestore
361
361
362
362
363 def parsealiases(cmd):
363 def parsealiases(cmd):
364 return cmd.split(b"|")
364 return cmd.split(b"|")
365
365
366
366
367 def setupwrapcolorwrite(ui):
367 def setupwrapcolorwrite(ui):
368 # wrap ui.write so diff output can be labeled/colorized
368 # wrap ui.write so diff output can be labeled/colorized
369 def wrapwrite(orig, *args, **kw):
369 def wrapwrite(orig, *args, **kw):
370 label = kw.pop('label', b'')
370 label = kw.pop('label', b'')
371 for chunk, l in patch.difflabel(lambda: args):
371 for chunk, l in patch.difflabel(lambda: args):
372 orig(chunk, label=label + l)
372 orig(chunk, label=label + l)
373
373
374 oldwrite = ui.write
374 oldwrite = ui.write
375
375
376 def wrap(*args, **kwargs):
376 def wrap(*args, **kwargs):
377 return wrapwrite(oldwrite, *args, **kwargs)
377 return wrapwrite(oldwrite, *args, **kwargs)
378
378
379 setattr(ui, 'write', wrap)
379 setattr(ui, 'write', wrap)
380 return oldwrite
380 return oldwrite
381
381
382
382
383 def filterchunks(ui, originalhunks, usecurses, testfile, match, operation=None):
383 def filterchunks(ui, originalhunks, usecurses, testfile, match, operation=None):
384 try:
384 try:
385 if usecurses:
385 if usecurses:
386 if testfile:
386 if testfile:
387 recordfn = crecordmod.testdecorator(
387 recordfn = crecordmod.testdecorator(
388 testfile, crecordmod.testchunkselector
388 testfile, crecordmod.testchunkselector
389 )
389 )
390 else:
390 else:
391 recordfn = crecordmod.chunkselector
391 recordfn = crecordmod.chunkselector
392
392
393 return crecordmod.filterpatch(
393 return crecordmod.filterpatch(
394 ui, originalhunks, recordfn, operation
394 ui, originalhunks, recordfn, operation
395 )
395 )
396 except crecordmod.fallbackerror as e:
396 except crecordmod.fallbackerror as e:
397 ui.warn(b'%s\n' % e)
397 ui.warn(b'%s\n' % e)
398 ui.warn(_(b'falling back to text mode\n'))
398 ui.warn(_(b'falling back to text mode\n'))
399
399
400 return patch.filterpatch(ui, originalhunks, match, operation)
400 return patch.filterpatch(ui, originalhunks, match, operation)
401
401
402
402
403 def recordfilter(ui, originalhunks, match, operation=None):
403 def recordfilter(ui, originalhunks, match, operation=None):
404 """ Prompts the user to filter the originalhunks and return a list of
404 """ Prompts the user to filter the originalhunks and return a list of
405 selected hunks.
405 selected hunks.
406 *operation* is used for to build ui messages to indicate the user what
406 *operation* is used for to build ui messages to indicate the user what
407 kind of filtering they are doing: reverting, committing, shelving, etc.
407 kind of filtering they are doing: reverting, committing, shelving, etc.
408 (see patch.filterpatch).
408 (see patch.filterpatch).
409 """
409 """
410 usecurses = crecordmod.checkcurses(ui)
410 usecurses = crecordmod.checkcurses(ui)
411 testfile = ui.config(b'experimental', b'crecordtest')
411 testfile = ui.config(b'experimental', b'crecordtest')
412 oldwrite = setupwrapcolorwrite(ui)
412 oldwrite = setupwrapcolorwrite(ui)
413 try:
413 try:
414 newchunks, newopts = filterchunks(
414 newchunks, newopts = filterchunks(
415 ui, originalhunks, usecurses, testfile, match, operation
415 ui, originalhunks, usecurses, testfile, match, operation
416 )
416 )
417 finally:
417 finally:
418 ui.write = oldwrite
418 ui.write = oldwrite
419 return newchunks, newopts
419 return newchunks, newopts
420
420
421
421
422 def dorecord(
422 def dorecord(
423 ui, repo, commitfunc, cmdsuggest, backupall, filterfn, *pats, **opts
423 ui, repo, commitfunc, cmdsuggest, backupall, filterfn, *pats, **opts
424 ):
424 ):
425 opts = pycompat.byteskwargs(opts)
425 opts = pycompat.byteskwargs(opts)
426 if not ui.interactive():
426 if not ui.interactive():
427 if cmdsuggest:
427 if cmdsuggest:
428 msg = _(b'running non-interactively, use %s instead') % cmdsuggest
428 msg = _(b'running non-interactively, use %s instead') % cmdsuggest
429 else:
429 else:
430 msg = _(b'running non-interactively')
430 msg = _(b'running non-interactively')
431 raise error.Abort(msg)
431 raise error.Abort(msg)
432
432
433 # make sure username is set before going interactive
433 # make sure username is set before going interactive
434 if not opts.get(b'user'):
434 if not opts.get(b'user'):
435 ui.username() # raise exception, username not provided
435 ui.username() # raise exception, username not provided
436
436
437 def recordfunc(ui, repo, message, match, opts):
437 def recordfunc(ui, repo, message, match, opts):
438 """This is generic record driver.
438 """This is generic record driver.
439
439
440 Its job is to interactively filter local changes, and
440 Its job is to interactively filter local changes, and
441 accordingly prepare working directory into a state in which the
441 accordingly prepare working directory into a state in which the
442 job can be delegated to a non-interactive commit command such as
442 job can be delegated to a non-interactive commit command such as
443 'commit' or 'qrefresh'.
443 'commit' or 'qrefresh'.
444
444
445 After the actual job is done by non-interactive command, the
445 After the actual job is done by non-interactive command, the
446 working directory is restored to its original state.
446 working directory is restored to its original state.
447
447
448 In the end we'll record interesting changes, and everything else
448 In the end we'll record interesting changes, and everything else
449 will be left in place, so the user can continue working.
449 will be left in place, so the user can continue working.
450 """
450 """
451 if not opts.get(b'interactive-unshelve'):
451 if not opts.get(b'interactive-unshelve'):
452 checkunfinished(repo, commit=True)
452 checkunfinished(repo, commit=True)
453 wctx = repo[None]
453 wctx = repo[None]
454 merge = len(wctx.parents()) > 1
454 merge = len(wctx.parents()) > 1
455 if merge:
455 if merge:
456 raise error.Abort(
456 raise error.Abort(
457 _(
457 _(
458 b'cannot partially commit a merge '
458 b'cannot partially commit a merge '
459 b'(use "hg commit" instead)'
459 b'(use "hg commit" instead)'
460 )
460 )
461 )
461 )
462
462
463 def fail(f, msg):
463 def fail(f, msg):
464 raise error.Abort(b'%s: %s' % (f, msg))
464 raise error.Abort(b'%s: %s' % (f, msg))
465
465
466 force = opts.get(b'force')
466 force = opts.get(b'force')
467 if not force:
467 if not force:
468 match = matchmod.badmatch(match, fail)
468 match = matchmod.badmatch(match, fail)
469
469
470 status = repo.status(match=match)
470 status = repo.status(match=match)
471
471
472 overrides = {(b'ui', b'commitsubrepos'): True}
472 overrides = {(b'ui', b'commitsubrepos'): True}
473
473
474 with repo.ui.configoverride(overrides, b'record'):
474 with repo.ui.configoverride(overrides, b'record'):
475 # subrepoutil.precommit() modifies the status
475 # subrepoutil.precommit() modifies the status
476 tmpstatus = scmutil.status(
476 tmpstatus = scmutil.status(
477 copymod.copy(status.modified),
477 copymod.copy(status.modified),
478 copymod.copy(status.added),
478 copymod.copy(status.added),
479 copymod.copy(status.removed),
479 copymod.copy(status.removed),
480 copymod.copy(status.deleted),
480 copymod.copy(status.deleted),
481 copymod.copy(status.unknown),
481 copymod.copy(status.unknown),
482 copymod.copy(status.ignored),
482 copymod.copy(status.ignored),
483 copymod.copy(status.clean), # pytype: disable=wrong-arg-count
483 copymod.copy(status.clean), # pytype: disable=wrong-arg-count
484 )
484 )
485
485
486 # Force allows -X subrepo to skip the subrepo.
486 # Force allows -X subrepo to skip the subrepo.
487 subs, commitsubs, newstate = subrepoutil.precommit(
487 subs, commitsubs, newstate = subrepoutil.precommit(
488 repo.ui, wctx, tmpstatus, match, force=True
488 repo.ui, wctx, tmpstatus, match, force=True
489 )
489 )
490 for s in subs:
490 for s in subs:
491 if s in commitsubs:
491 if s in commitsubs:
492 dirtyreason = wctx.sub(s).dirtyreason(True)
492 dirtyreason = wctx.sub(s).dirtyreason(True)
493 raise error.Abort(dirtyreason)
493 raise error.Abort(dirtyreason)
494
494
495 if not force:
495 if not force:
496 repo.checkcommitpatterns(wctx, match, status, fail)
496 repo.checkcommitpatterns(wctx, match, status, fail)
497 diffopts = patch.difffeatureopts(
497 diffopts = patch.difffeatureopts(
498 ui,
498 ui,
499 opts=opts,
499 opts=opts,
500 whitespace=True,
500 whitespace=True,
501 section=b'commands',
501 section=b'commands',
502 configprefix=b'commit.interactive.',
502 configprefix=b'commit.interactive.',
503 )
503 )
504 diffopts.nodates = True
504 diffopts.nodates = True
505 diffopts.git = True
505 diffopts.git = True
506 diffopts.showfunc = True
506 diffopts.showfunc = True
507 originaldiff = patch.diff(repo, changes=status, opts=diffopts)
507 originaldiff = patch.diff(repo, changes=status, opts=diffopts)
508 originalchunks = patch.parsepatch(originaldiff)
508 originalchunks = patch.parsepatch(originaldiff)
509 match = scmutil.match(repo[None], pats)
509 match = scmutil.match(repo[None], pats)
510
510
511 # 1. filter patch, since we are intending to apply subset of it
511 # 1. filter patch, since we are intending to apply subset of it
512 try:
512 try:
513 chunks, newopts = filterfn(ui, originalchunks, match)
513 chunks, newopts = filterfn(ui, originalchunks, match)
514 except error.PatchError as err:
514 except error.PatchError as err:
515 raise error.Abort(_(b'error parsing patch: %s') % err)
515 raise error.Abort(_(b'error parsing patch: %s') % err)
516 opts.update(newopts)
516 opts.update(newopts)
517
517
518 # We need to keep a backup of files that have been newly added and
518 # We need to keep a backup of files that have been newly added and
519 # modified during the recording process because there is a previous
519 # modified during the recording process because there is a previous
520 # version without the edit in the workdir. We also will need to restore
520 # version without the edit in the workdir. We also will need to restore
521 # files that were the sources of renames so that the patch application
521 # files that were the sources of renames so that the patch application
522 # works.
522 # works.
523 newlyaddedandmodifiedfiles, alsorestore = newandmodified(
523 newlyaddedandmodifiedfiles, alsorestore = newandmodified(
524 chunks, originalchunks
524 chunks, originalchunks
525 )
525 )
526 contenders = set()
526 contenders = set()
527 for h in chunks:
527 for h in chunks:
528 try:
528 try:
529 contenders.update(set(h.files()))
529 contenders.update(set(h.files()))
530 except AttributeError:
530 except AttributeError:
531 pass
531 pass
532
532
533 changed = status.modified + status.added + status.removed
533 changed = status.modified + status.added + status.removed
534 newfiles = [f for f in changed if f in contenders]
534 newfiles = [f for f in changed if f in contenders]
535 if not newfiles:
535 if not newfiles:
536 ui.status(_(b'no changes to record\n'))
536 ui.status(_(b'no changes to record\n'))
537 return 0
537 return 0
538
538
539 modified = set(status.modified)
539 modified = set(status.modified)
540
540
541 # 2. backup changed files, so we can restore them in the end
541 # 2. backup changed files, so we can restore them in the end
542
542
543 if backupall:
543 if backupall:
544 tobackup = changed
544 tobackup = changed
545 else:
545 else:
546 tobackup = [
546 tobackup = [
547 f
547 f
548 for f in newfiles
548 for f in newfiles
549 if f in modified or f in newlyaddedandmodifiedfiles
549 if f in modified or f in newlyaddedandmodifiedfiles
550 ]
550 ]
551 backups = {}
551 backups = {}
552 if tobackup:
552 if tobackup:
553 backupdir = repo.vfs.join(b'record-backups')
553 backupdir = repo.vfs.join(b'record-backups')
554 try:
554 try:
555 os.mkdir(backupdir)
555 os.mkdir(backupdir)
556 except OSError as err:
556 except OSError as err:
557 if err.errno != errno.EEXIST:
557 if err.errno != errno.EEXIST:
558 raise
558 raise
559 try:
559 try:
560 # backup continues
560 # backup continues
561 for f in tobackup:
561 for f in tobackup:
562 fd, tmpname = pycompat.mkstemp(
562 fd, tmpname = pycompat.mkstemp(
563 prefix=f.replace(b'/', b'_') + b'.', dir=backupdir
563 prefix=f.replace(b'/', b'_') + b'.', dir=backupdir
564 )
564 )
565 os.close(fd)
565 os.close(fd)
566 ui.debug(b'backup %r as %r\n' % (f, tmpname))
566 ui.debug(b'backup %r as %r\n' % (f, tmpname))
567 util.copyfile(repo.wjoin(f), tmpname, copystat=True)
567 util.copyfile(repo.wjoin(f), tmpname, copystat=True)
568 backups[f] = tmpname
568 backups[f] = tmpname
569
569
570 fp = stringio()
570 fp = stringio()
571 for c in chunks:
571 for c in chunks:
572 fname = c.filename()
572 fname = c.filename()
573 if fname in backups:
573 if fname in backups:
574 c.write(fp)
574 c.write(fp)
575 dopatch = fp.tell()
575 dopatch = fp.tell()
576 fp.seek(0)
576 fp.seek(0)
577
577
578 # 2.5 optionally review / modify patch in text editor
578 # 2.5 optionally review / modify patch in text editor
579 if opts.get(b'review', False):
579 if opts.get(b'review', False):
580 patchtext = (
580 patchtext = (
581 crecordmod.diffhelptext
581 crecordmod.diffhelptext
582 + crecordmod.patchhelptext
582 + crecordmod.patchhelptext
583 + fp.read()
583 + fp.read()
584 )
584 )
585 reviewedpatch = ui.edit(
585 reviewedpatch = ui.edit(
586 patchtext, b"", action=b"diff", repopath=repo.path
586 patchtext, b"", action=b"diff", repopath=repo.path
587 )
587 )
588 fp.truncate(0)
588 fp.truncate(0)
589 fp.write(reviewedpatch)
589 fp.write(reviewedpatch)
590 fp.seek(0)
590 fp.seek(0)
591
591
592 [os.unlink(repo.wjoin(c)) for c in newlyaddedandmodifiedfiles]
592 [os.unlink(repo.wjoin(c)) for c in newlyaddedandmodifiedfiles]
593 # 3a. apply filtered patch to clean repo (clean)
593 # 3a. apply filtered patch to clean repo (clean)
594 if backups:
594 if backups:
595 m = scmutil.matchfiles(repo, set(backups.keys()) | alsorestore)
595 m = scmutil.matchfiles(repo, set(backups.keys()) | alsorestore)
596 mergemod.revert_to(repo[b'.'], matcher=m)
596 mergemod.revert_to(repo[b'.'], matcher=m)
597
597
598 # 3b. (apply)
598 # 3b. (apply)
599 if dopatch:
599 if dopatch:
600 try:
600 try:
601 ui.debug(b'applying patch\n')
601 ui.debug(b'applying patch\n')
602 ui.debug(fp.getvalue())
602 ui.debug(fp.getvalue())
603 patch.internalpatch(ui, repo, fp, 1, eolmode=None)
603 patch.internalpatch(ui, repo, fp, 1, eolmode=None)
604 except error.PatchError as err:
604 except error.PatchError as err:
605 raise error.Abort(pycompat.bytestr(err))
605 raise error.Abort(pycompat.bytestr(err))
606 del fp
606 del fp
607
607
608 # 4. We prepared working directory according to filtered
608 # 4. We prepared working directory according to filtered
609 # patch. Now is the time to delegate the job to
609 # patch. Now is the time to delegate the job to
610 # commit/qrefresh or the like!
610 # commit/qrefresh or the like!
611
611
612 # Make all of the pathnames absolute.
612 # Make all of the pathnames absolute.
613 newfiles = [repo.wjoin(nf) for nf in newfiles]
613 newfiles = [repo.wjoin(nf) for nf in newfiles]
614 return commitfunc(ui, repo, *newfiles, **pycompat.strkwargs(opts))
614 return commitfunc(ui, repo, *newfiles, **pycompat.strkwargs(opts))
615 finally:
615 finally:
616 # 5. finally restore backed-up files
616 # 5. finally restore backed-up files
617 try:
617 try:
618 dirstate = repo.dirstate
618 dirstate = repo.dirstate
619 for realname, tmpname in pycompat.iteritems(backups):
619 for realname, tmpname in pycompat.iteritems(backups):
620 ui.debug(b'restoring %r to %r\n' % (tmpname, realname))
620 ui.debug(b'restoring %r to %r\n' % (tmpname, realname))
621
621
622 if dirstate[realname] == b'n':
622 if dirstate[realname] == b'n':
623 # without normallookup, restoring timestamp
623 # without normallookup, restoring timestamp
624 # may cause partially committed files
624 # may cause partially committed files
625 # to be treated as unmodified
625 # to be treated as unmodified
626 dirstate.normallookup(realname)
626 dirstate.normallookup(realname)
627
627
628 # copystat=True here and above are a hack to trick any
628 # copystat=True here and above are a hack to trick any
629 # editors that have f open that we haven't modified them.
629 # editors that have f open that we haven't modified them.
630 #
630 #
631 # Also note that this racy as an editor could notice the
631 # Also note that this racy as an editor could notice the
632 # file's mtime before we've finished writing it.
632 # file's mtime before we've finished writing it.
633 util.copyfile(tmpname, repo.wjoin(realname), copystat=True)
633 util.copyfile(tmpname, repo.wjoin(realname), copystat=True)
634 os.unlink(tmpname)
634 os.unlink(tmpname)
635 if tobackup:
635 if tobackup:
636 os.rmdir(backupdir)
636 os.rmdir(backupdir)
637 except OSError:
637 except OSError:
638 pass
638 pass
639
639
640 def recordinwlock(ui, repo, message, match, opts):
640 def recordinwlock(ui, repo, message, match, opts):
641 with repo.wlock():
641 with repo.wlock():
642 return recordfunc(ui, repo, message, match, opts)
642 return recordfunc(ui, repo, message, match, opts)
643
643
644 return commit(ui, repo, recordinwlock, pats, opts)
644 return commit(ui, repo, recordinwlock, pats, opts)
645
645
646
646
647 class dirnode(object):
647 class dirnode(object):
648 """
648 """
649 Represent a directory in user working copy with information required for
649 Represent a directory in user working copy with information required for
650 the purpose of tersing its status.
650 the purpose of tersing its status.
651
651
652 path is the path to the directory, without a trailing '/'
652 path is the path to the directory, without a trailing '/'
653
653
654 statuses is a set of statuses of all files in this directory (this includes
654 statuses is a set of statuses of all files in this directory (this includes
655 all the files in all the subdirectories too)
655 all the files in all the subdirectories too)
656
656
657 files is a list of files which are direct child of this directory
657 files is a list of files which are direct child of this directory
658
658
659 subdirs is a dictionary of sub-directory name as the key and it's own
659 subdirs is a dictionary of sub-directory name as the key and it's own
660 dirnode object as the value
660 dirnode object as the value
661 """
661 """
662
662
663 def __init__(self, dirpath):
663 def __init__(self, dirpath):
664 self.path = dirpath
664 self.path = dirpath
665 self.statuses = set()
665 self.statuses = set()
666 self.files = []
666 self.files = []
667 self.subdirs = {}
667 self.subdirs = {}
668
668
669 def _addfileindir(self, filename, status):
669 def _addfileindir(self, filename, status):
670 """Add a file in this directory as a direct child."""
670 """Add a file in this directory as a direct child."""
671 self.files.append((filename, status))
671 self.files.append((filename, status))
672
672
673 def addfile(self, filename, status):
673 def addfile(self, filename, status):
674 """
674 """
675 Add a file to this directory or to its direct parent directory.
675 Add a file to this directory or to its direct parent directory.
676
676
677 If the file is not direct child of this directory, we traverse to the
677 If the file is not direct child of this directory, we traverse to the
678 directory of which this file is a direct child of and add the file
678 directory of which this file is a direct child of and add the file
679 there.
679 there.
680 """
680 """
681
681
682 # the filename contains a path separator, it means it's not the direct
682 # the filename contains a path separator, it means it's not the direct
683 # child of this directory
683 # child of this directory
684 if b'/' in filename:
684 if b'/' in filename:
685 subdir, filep = filename.split(b'/', 1)
685 subdir, filep = filename.split(b'/', 1)
686
686
687 # does the dirnode object for subdir exists
687 # does the dirnode object for subdir exists
688 if subdir not in self.subdirs:
688 if subdir not in self.subdirs:
689 subdirpath = pathutil.join(self.path, subdir)
689 subdirpath = pathutil.join(self.path, subdir)
690 self.subdirs[subdir] = dirnode(subdirpath)
690 self.subdirs[subdir] = dirnode(subdirpath)
691
691
692 # try adding the file in subdir
692 # try adding the file in subdir
693 self.subdirs[subdir].addfile(filep, status)
693 self.subdirs[subdir].addfile(filep, status)
694
694
695 else:
695 else:
696 self._addfileindir(filename, status)
696 self._addfileindir(filename, status)
697
697
698 if status not in self.statuses:
698 if status not in self.statuses:
699 self.statuses.add(status)
699 self.statuses.add(status)
700
700
701 def iterfilepaths(self):
701 def iterfilepaths(self):
702 """Yield (status, path) for files directly under this directory."""
702 """Yield (status, path) for files directly under this directory."""
703 for f, st in self.files:
703 for f, st in self.files:
704 yield st, pathutil.join(self.path, f)
704 yield st, pathutil.join(self.path, f)
705
705
706 def tersewalk(self, terseargs):
706 def tersewalk(self, terseargs):
707 """
707 """
708 Yield (status, path) obtained by processing the status of this
708 Yield (status, path) obtained by processing the status of this
709 dirnode.
709 dirnode.
710
710
711 terseargs is the string of arguments passed by the user with `--terse`
711 terseargs is the string of arguments passed by the user with `--terse`
712 flag.
712 flag.
713
713
714 Following are the cases which can happen:
714 Following are the cases which can happen:
715
715
716 1) All the files in the directory (including all the files in its
716 1) All the files in the directory (including all the files in its
717 subdirectories) share the same status and the user has asked us to terse
717 subdirectories) share the same status and the user has asked us to terse
718 that status. -> yield (status, dirpath). dirpath will end in '/'.
718 that status. -> yield (status, dirpath). dirpath will end in '/'.
719
719
720 2) Otherwise, we do following:
720 2) Otherwise, we do following:
721
721
722 a) Yield (status, filepath) for all the files which are in this
722 a) Yield (status, filepath) for all the files which are in this
723 directory (only the ones in this directory, not the subdirs)
723 directory (only the ones in this directory, not the subdirs)
724
724
725 b) Recurse the function on all the subdirectories of this
725 b) Recurse the function on all the subdirectories of this
726 directory
726 directory
727 """
727 """
728
728
729 if len(self.statuses) == 1:
729 if len(self.statuses) == 1:
730 onlyst = self.statuses.pop()
730 onlyst = self.statuses.pop()
731
731
732 # Making sure we terse only when the status abbreviation is
732 # Making sure we terse only when the status abbreviation is
733 # passed as terse argument
733 # passed as terse argument
734 if onlyst in terseargs:
734 if onlyst in terseargs:
735 yield onlyst, self.path + b'/'
735 yield onlyst, self.path + b'/'
736 return
736 return
737
737
738 # add the files to status list
738 # add the files to status list
739 for st, fpath in self.iterfilepaths():
739 for st, fpath in self.iterfilepaths():
740 yield st, fpath
740 yield st, fpath
741
741
742 # recurse on the subdirs
742 # recurse on the subdirs
743 for dirobj in self.subdirs.values():
743 for dirobj in self.subdirs.values():
744 for st, fpath in dirobj.tersewalk(terseargs):
744 for st, fpath in dirobj.tersewalk(terseargs):
745 yield st, fpath
745 yield st, fpath
746
746
747
747
748 def tersedir(statuslist, terseargs):
748 def tersedir(statuslist, terseargs):
749 """
749 """
750 Terse the status if all the files in a directory shares the same status.
750 Terse the status if all the files in a directory shares the same status.
751
751
752 statuslist is scmutil.status() object which contains a list of files for
752 statuslist is scmutil.status() object which contains a list of files for
753 each status.
753 each status.
754 terseargs is string which is passed by the user as the argument to `--terse`
754 terseargs is string which is passed by the user as the argument to `--terse`
755 flag.
755 flag.
756
756
757 The function makes a tree of objects of dirnode class, and at each node it
757 The function makes a tree of objects of dirnode class, and at each node it
758 stores the information required to know whether we can terse a certain
758 stores the information required to know whether we can terse a certain
759 directory or not.
759 directory or not.
760 """
760 """
761 # the order matters here as that is used to produce final list
761 # the order matters here as that is used to produce final list
762 allst = (b'm', b'a', b'r', b'd', b'u', b'i', b'c')
762 allst = (b'm', b'a', b'r', b'd', b'u', b'i', b'c')
763
763
764 # checking the argument validity
764 # checking the argument validity
765 for s in pycompat.bytestr(terseargs):
765 for s in pycompat.bytestr(terseargs):
766 if s not in allst:
766 if s not in allst:
767 raise error.Abort(_(b"'%s' not recognized") % s)
767 raise error.Abort(_(b"'%s' not recognized") % s)
768
768
769 # creating a dirnode object for the root of the repo
769 # creating a dirnode object for the root of the repo
770 rootobj = dirnode(b'')
770 rootobj = dirnode(b'')
771 pstatus = (
771 pstatus = (
772 b'modified',
772 b'modified',
773 b'added',
773 b'added',
774 b'deleted',
774 b'deleted',
775 b'clean',
775 b'clean',
776 b'unknown',
776 b'unknown',
777 b'ignored',
777 b'ignored',
778 b'removed',
778 b'removed',
779 )
779 )
780
780
781 tersedict = {}
781 tersedict = {}
782 for attrname in pstatus:
782 for attrname in pstatus:
783 statuschar = attrname[0:1]
783 statuschar = attrname[0:1]
784 for f in getattr(statuslist, attrname):
784 for f in getattr(statuslist, attrname):
785 rootobj.addfile(f, statuschar)
785 rootobj.addfile(f, statuschar)
786 tersedict[statuschar] = []
786 tersedict[statuschar] = []
787
787
788 # we won't be tersing the root dir, so add files in it
788 # we won't be tersing the root dir, so add files in it
789 for st, fpath in rootobj.iterfilepaths():
789 for st, fpath in rootobj.iterfilepaths():
790 tersedict[st].append(fpath)
790 tersedict[st].append(fpath)
791
791
792 # process each sub-directory and build tersedict
792 # process each sub-directory and build tersedict
793 for subdir in rootobj.subdirs.values():
793 for subdir in rootobj.subdirs.values():
794 for st, f in subdir.tersewalk(terseargs):
794 for st, f in subdir.tersewalk(terseargs):
795 tersedict[st].append(f)
795 tersedict[st].append(f)
796
796
797 tersedlist = []
797 tersedlist = []
798 for st in allst:
798 for st in allst:
799 tersedict[st].sort()
799 tersedict[st].sort()
800 tersedlist.append(tersedict[st])
800 tersedlist.append(tersedict[st])
801
801
802 return scmutil.status(*tersedlist)
802 return scmutil.status(*tersedlist)
803
803
804
804
805 def _commentlines(raw):
805 def _commentlines(raw):
806 '''Surround lineswith a comment char and a new line'''
806 '''Surround lineswith a comment char and a new line'''
807 lines = raw.splitlines()
807 lines = raw.splitlines()
808 commentedlines = [b'# %s' % line for line in lines]
808 commentedlines = [b'# %s' % line for line in lines]
809 return b'\n'.join(commentedlines) + b'\n'
809 return b'\n'.join(commentedlines) + b'\n'
810
810
811
811
812 @attr.s(frozen=True)
812 @attr.s(frozen=True)
813 class morestatus(object):
813 class morestatus(object):
814 reporoot = attr.ib()
814 reporoot = attr.ib()
815 unfinishedop = attr.ib()
815 unfinishedop = attr.ib()
816 unfinishedmsg = attr.ib()
816 unfinishedmsg = attr.ib()
817 activemerge = attr.ib()
817 activemerge = attr.ib()
818 unresolvedpaths = attr.ib()
818 unresolvedpaths = attr.ib()
819 _formattedpaths = attr.ib(init=False, default=set())
819 _formattedpaths = attr.ib(init=False, default=set())
820 _label = b'status.morestatus'
820 _label = b'status.morestatus'
821
821
822 def formatfile(self, path, fm):
822 def formatfile(self, path, fm):
823 self._formattedpaths.add(path)
823 self._formattedpaths.add(path)
824 if self.activemerge and path in self.unresolvedpaths:
824 if self.activemerge and path in self.unresolvedpaths:
825 fm.data(unresolved=True)
825 fm.data(unresolved=True)
826
826
827 def formatfooter(self, fm):
827 def formatfooter(self, fm):
828 if self.unfinishedop or self.unfinishedmsg:
828 if self.unfinishedop or self.unfinishedmsg:
829 fm.startitem()
829 fm.startitem()
830 fm.data(itemtype=b'morestatus')
830 fm.data(itemtype=b'morestatus')
831
831
832 if self.unfinishedop:
832 if self.unfinishedop:
833 fm.data(unfinished=self.unfinishedop)
833 fm.data(unfinished=self.unfinishedop)
834 statemsg = (
834 statemsg = (
835 _(b'The repository is in an unfinished *%s* state.')
835 _(b'The repository is in an unfinished *%s* state.')
836 % self.unfinishedop
836 % self.unfinishedop
837 )
837 )
838 fm.plain(b'%s\n' % _commentlines(statemsg), label=self._label)
838 fm.plain(b'%s\n' % _commentlines(statemsg), label=self._label)
839 if self.unfinishedmsg:
839 if self.unfinishedmsg:
840 fm.data(unfinishedmsg=self.unfinishedmsg)
840 fm.data(unfinishedmsg=self.unfinishedmsg)
841
841
842 # May also start new data items.
842 # May also start new data items.
843 self._formatconflicts(fm)
843 self._formatconflicts(fm)
844
844
845 if self.unfinishedmsg:
845 if self.unfinishedmsg:
846 fm.plain(
846 fm.plain(
847 b'%s\n' % _commentlines(self.unfinishedmsg), label=self._label
847 b'%s\n' % _commentlines(self.unfinishedmsg), label=self._label
848 )
848 )
849
849
850 def _formatconflicts(self, fm):
850 def _formatconflicts(self, fm):
851 if not self.activemerge:
851 if not self.activemerge:
852 return
852 return
853
853
854 if self.unresolvedpaths:
854 if self.unresolvedpaths:
855 mergeliststr = b'\n'.join(
855 mergeliststr = b'\n'.join(
856 [
856 [
857 b' %s'
857 b' %s'
858 % util.pathto(self.reporoot, encoding.getcwd(), path)
858 % util.pathto(self.reporoot, encoding.getcwd(), path)
859 for path in self.unresolvedpaths
859 for path in self.unresolvedpaths
860 ]
860 ]
861 )
861 )
862 msg = (
862 msg = (
863 _(
863 _(
864 '''Unresolved merge conflicts:
864 '''Unresolved merge conflicts:
865
865
866 %s
866 %s
867
867
868 To mark files as resolved: hg resolve --mark FILE'''
868 To mark files as resolved: hg resolve --mark FILE'''
869 )
869 )
870 % mergeliststr
870 % mergeliststr
871 )
871 )
872
872
873 # If any paths with unresolved conflicts were not previously
873 # If any paths with unresolved conflicts were not previously
874 # formatted, output them now.
874 # formatted, output them now.
875 for f in self.unresolvedpaths:
875 for f in self.unresolvedpaths:
876 if f in self._formattedpaths:
876 if f in self._formattedpaths:
877 # Already output.
877 # Already output.
878 continue
878 continue
879 fm.startitem()
879 fm.startitem()
880 # We can't claim to know the status of the file - it may just
880 # We can't claim to know the status of the file - it may just
881 # have been in one of the states that were not requested for
881 # have been in one of the states that were not requested for
882 # display, so it could be anything.
882 # display, so it could be anything.
883 fm.data(itemtype=b'file', path=f, unresolved=True)
883 fm.data(itemtype=b'file', path=f, unresolved=True)
884
884
885 else:
885 else:
886 msg = _(b'No unresolved merge conflicts.')
886 msg = _(b'No unresolved merge conflicts.')
887
887
888 fm.plain(b'%s\n' % _commentlines(msg), label=self._label)
888 fm.plain(b'%s\n' % _commentlines(msg), label=self._label)
889
889
890
890
891 def readmorestatus(repo):
891 def readmorestatus(repo):
892 """Returns a morestatus object if the repo has unfinished state."""
892 """Returns a morestatus object if the repo has unfinished state."""
893 statetuple = statemod.getrepostate(repo)
893 statetuple = statemod.getrepostate(repo)
894 mergestate = mergestatemod.mergestate.read(repo)
894 mergestate = mergestatemod.mergestate.read(repo)
895 activemerge = mergestate.active()
895 activemerge = mergestate.active()
896 if not statetuple and not activemerge:
896 if not statetuple and not activemerge:
897 return None
897 return None
898
898
899 unfinishedop = unfinishedmsg = unresolved = None
899 unfinishedop = unfinishedmsg = unresolved = None
900 if statetuple:
900 if statetuple:
901 unfinishedop, unfinishedmsg = statetuple
901 unfinishedop, unfinishedmsg = statetuple
902 if activemerge:
902 if activemerge:
903 unresolved = sorted(mergestate.unresolved())
903 unresolved = sorted(mergestate.unresolved())
904 return morestatus(
904 return morestatus(
905 repo.root, unfinishedop, unfinishedmsg, activemerge, unresolved
905 repo.root, unfinishedop, unfinishedmsg, activemerge, unresolved
906 )
906 )
907
907
908
908
909 def findpossible(cmd, table, strict=False):
909 def findpossible(cmd, table, strict=False):
910 """
910 """
911 Return cmd -> (aliases, command table entry)
911 Return cmd -> (aliases, command table entry)
912 for each matching command.
912 for each matching command.
913 Return debug commands (or their aliases) only if no normal command matches.
913 Return debug commands (or their aliases) only if no normal command matches.
914 """
914 """
915 choice = {}
915 choice = {}
916 debugchoice = {}
916 debugchoice = {}
917
917
918 if cmd in table:
918 if cmd in table:
919 # short-circuit exact matches, "log" alias beats "log|history"
919 # short-circuit exact matches, "log" alias beats "log|history"
920 keys = [cmd]
920 keys = [cmd]
921 else:
921 else:
922 keys = table.keys()
922 keys = table.keys()
923
923
924 allcmds = []
924 allcmds = []
925 for e in keys:
925 for e in keys:
926 aliases = parsealiases(e)
926 aliases = parsealiases(e)
927 allcmds.extend(aliases)
927 allcmds.extend(aliases)
928 found = None
928 found = None
929 if cmd in aliases:
929 if cmd in aliases:
930 found = cmd
930 found = cmd
931 elif not strict:
931 elif not strict:
932 for a in aliases:
932 for a in aliases:
933 if a.startswith(cmd):
933 if a.startswith(cmd):
934 found = a
934 found = a
935 break
935 break
936 if found is not None:
936 if found is not None:
937 if aliases[0].startswith(b"debug") or found.startswith(b"debug"):
937 if aliases[0].startswith(b"debug") or found.startswith(b"debug"):
938 debugchoice[found] = (aliases, table[e])
938 debugchoice[found] = (aliases, table[e])
939 else:
939 else:
940 choice[found] = (aliases, table[e])
940 choice[found] = (aliases, table[e])
941
941
942 if not choice and debugchoice:
942 if not choice and debugchoice:
943 choice = debugchoice
943 choice = debugchoice
944
944
945 return choice, allcmds
945 return choice, allcmds
946
946
947
947
948 def findcmd(cmd, table, strict=True):
948 def findcmd(cmd, table, strict=True):
949 """Return (aliases, command table entry) for command string."""
949 """Return (aliases, command table entry) for command string."""
950 choice, allcmds = findpossible(cmd, table, strict)
950 choice, allcmds = findpossible(cmd, table, strict)
951
951
952 if cmd in choice:
952 if cmd in choice:
953 return choice[cmd]
953 return choice[cmd]
954
954
955 if len(choice) > 1:
955 if len(choice) > 1:
956 clist = sorted(choice)
956 clist = sorted(choice)
957 raise error.AmbiguousCommand(cmd, clist)
957 raise error.AmbiguousCommand(cmd, clist)
958
958
959 if choice:
959 if choice:
960 return list(choice.values())[0]
960 return list(choice.values())[0]
961
961
962 raise error.UnknownCommand(cmd, allcmds)
962 raise error.UnknownCommand(cmd, allcmds)
963
963
964
964
965 def changebranch(ui, repo, revs, label, opts):
965 def changebranch(ui, repo, revs, label, opts):
966 """ Change the branch name of given revs to label """
966 """ Change the branch name of given revs to label """
967
967
968 with repo.wlock(), repo.lock(), repo.transaction(b'branches'):
968 with repo.wlock(), repo.lock(), repo.transaction(b'branches'):
969 # abort in case of uncommitted merge or dirty wdir
969 # abort in case of uncommitted merge or dirty wdir
970 bailifchanged(repo)
970 bailifchanged(repo)
971 revs = scmutil.revrange(repo, revs)
971 revs = scmutil.revrange(repo, revs)
972 if not revs:
972 if not revs:
973 raise error.Abort(b"empty revision set")
973 raise error.Abort(b"empty revision set")
974 roots = repo.revs(b'roots(%ld)', revs)
974 roots = repo.revs(b'roots(%ld)', revs)
975 if len(roots) > 1:
975 if len(roots) > 1:
976 raise error.Abort(
976 raise error.Abort(
977 _(b"cannot change branch of non-linear revisions")
977 _(b"cannot change branch of non-linear revisions")
978 )
978 )
979 rewriteutil.precheck(repo, revs, b'change branch of')
979 rewriteutil.precheck(repo, revs, b'change branch of')
980
980
981 root = repo[roots.first()]
981 root = repo[roots.first()]
982 rpb = {parent.branch() for parent in root.parents()}
982 rpb = {parent.branch() for parent in root.parents()}
983 if (
983 if (
984 not opts.get(b'force')
984 not opts.get(b'force')
985 and label not in rpb
985 and label not in rpb
986 and label in repo.branchmap()
986 and label in repo.branchmap()
987 ):
987 ):
988 raise error.Abort(_(b"a branch of the same name already exists"))
988 raise error.Abort(_(b"a branch of the same name already exists"))
989
989
990 if repo.revs(b'obsolete() and %ld', revs):
990 if repo.revs(b'obsolete() and %ld', revs):
991 raise error.Abort(
991 raise error.Abort(
992 _(b"cannot change branch of a obsolete changeset")
992 _(b"cannot change branch of a obsolete changeset")
993 )
993 )
994
994
995 # make sure only topological heads
995 # make sure only topological heads
996 if repo.revs(b'heads(%ld) - head()', revs):
996 if repo.revs(b'heads(%ld) - head()', revs):
997 raise error.Abort(_(b"cannot change branch in middle of a stack"))
997 raise error.Abort(_(b"cannot change branch in middle of a stack"))
998
998
999 replacements = {}
999 replacements = {}
1000 # avoid import cycle mercurial.cmdutil -> mercurial.context ->
1000 # avoid import cycle mercurial.cmdutil -> mercurial.context ->
1001 # mercurial.subrepo -> mercurial.cmdutil
1001 # mercurial.subrepo -> mercurial.cmdutil
1002 from . import context
1002 from . import context
1003
1003
1004 for rev in revs:
1004 for rev in revs:
1005 ctx = repo[rev]
1005 ctx = repo[rev]
1006 oldbranch = ctx.branch()
1006 oldbranch = ctx.branch()
1007 # check if ctx has same branch
1007 # check if ctx has same branch
1008 if oldbranch == label:
1008 if oldbranch == label:
1009 continue
1009 continue
1010
1010
1011 def filectxfn(repo, newctx, path):
1011 def filectxfn(repo, newctx, path):
1012 try:
1012 try:
1013 return ctx[path]
1013 return ctx[path]
1014 except error.ManifestLookupError:
1014 except error.ManifestLookupError:
1015 return None
1015 return None
1016
1016
1017 ui.debug(
1017 ui.debug(
1018 b"changing branch of '%s' from '%s' to '%s'\n"
1018 b"changing branch of '%s' from '%s' to '%s'\n"
1019 % (hex(ctx.node()), oldbranch, label)
1019 % (hex(ctx.node()), oldbranch, label)
1020 )
1020 )
1021 extra = ctx.extra()
1021 extra = ctx.extra()
1022 extra[b'branch_change'] = hex(ctx.node())
1022 extra[b'branch_change'] = hex(ctx.node())
1023 # While changing branch of set of linear commits, make sure that
1023 # While changing branch of set of linear commits, make sure that
1024 # we base our commits on new parent rather than old parent which
1024 # we base our commits on new parent rather than old parent which
1025 # was obsoleted while changing the branch
1025 # was obsoleted while changing the branch
1026 p1 = ctx.p1().node()
1026 p1 = ctx.p1().node()
1027 p2 = ctx.p2().node()
1027 p2 = ctx.p2().node()
1028 if p1 in replacements:
1028 if p1 in replacements:
1029 p1 = replacements[p1][0]
1029 p1 = replacements[p1][0]
1030 if p2 in replacements:
1030 if p2 in replacements:
1031 p2 = replacements[p2][0]
1031 p2 = replacements[p2][0]
1032
1032
1033 mc = context.memctx(
1033 mc = context.memctx(
1034 repo,
1034 repo,
1035 (p1, p2),
1035 (p1, p2),
1036 ctx.description(),
1036 ctx.description(),
1037 ctx.files(),
1037 ctx.files(),
1038 filectxfn,
1038 filectxfn,
1039 user=ctx.user(),
1039 user=ctx.user(),
1040 date=ctx.date(),
1040 date=ctx.date(),
1041 extra=extra,
1041 extra=extra,
1042 branch=label,
1042 branch=label,
1043 )
1043 )
1044
1044
1045 newnode = repo.commitctx(mc)
1045 newnode = repo.commitctx(mc)
1046 replacements[ctx.node()] = (newnode,)
1046 replacements[ctx.node()] = (newnode,)
1047 ui.debug(b'new node id is %s\n' % hex(newnode))
1047 ui.debug(b'new node id is %s\n' % hex(newnode))
1048
1048
1049 # create obsmarkers and move bookmarks
1049 # create obsmarkers and move bookmarks
1050 scmutil.cleanupnodes(
1050 scmutil.cleanupnodes(
1051 repo, replacements, b'branch-change', fixphase=True
1051 repo, replacements, b'branch-change', fixphase=True
1052 )
1052 )
1053
1053
1054 # move the working copy too
1054 # move the working copy too
1055 wctx = repo[None]
1055 wctx = repo[None]
1056 # in-progress merge is a bit too complex for now.
1056 # in-progress merge is a bit too complex for now.
1057 if len(wctx.parents()) == 1:
1057 if len(wctx.parents()) == 1:
1058 newid = replacements.get(wctx.p1().node())
1058 newid = replacements.get(wctx.p1().node())
1059 if newid is not None:
1059 if newid is not None:
1060 # avoid import cycle mercurial.cmdutil -> mercurial.hg ->
1060 # avoid import cycle mercurial.cmdutil -> mercurial.hg ->
1061 # mercurial.cmdutil
1061 # mercurial.cmdutil
1062 from . import hg
1062 from . import hg
1063
1063
1064 hg.update(repo, newid[0], quietempty=True)
1064 hg.update(repo, newid[0], quietempty=True)
1065
1065
1066 ui.status(_(b"changed branch on %d changesets\n") % len(replacements))
1066 ui.status(_(b"changed branch on %d changesets\n") % len(replacements))
1067
1067
1068
1068
1069 def findrepo(p):
1069 def findrepo(p):
1070 while not os.path.isdir(os.path.join(p, b".hg")):
1070 while not os.path.isdir(os.path.join(p, b".hg")):
1071 oldp, p = p, os.path.dirname(p)
1071 oldp, p = p, os.path.dirname(p)
1072 if p == oldp:
1072 if p == oldp:
1073 return None
1073 return None
1074
1074
1075 return p
1075 return p
1076
1076
1077
1077
1078 def bailifchanged(repo, merge=True, hint=None):
1078 def bailifchanged(repo, merge=True, hint=None):
1079 """ enforce the precondition that working directory must be clean.
1079 """ enforce the precondition that working directory must be clean.
1080
1080
1081 'merge' can be set to false if a pending uncommitted merge should be
1081 'merge' can be set to false if a pending uncommitted merge should be
1082 ignored (such as when 'update --check' runs).
1082 ignored (such as when 'update --check' runs).
1083
1083
1084 'hint' is the usual hint given to Abort exception.
1084 'hint' is the usual hint given to Abort exception.
1085 """
1085 """
1086
1086
1087 if merge and repo.dirstate.p2() != nullid:
1087 if merge and repo.dirstate.p2() != nullid:
1088 raise error.Abort(_(b'outstanding uncommitted merge'), hint=hint)
1088 raise error.Abort(_(b'outstanding uncommitted merge'), hint=hint)
1089 st = repo.status()
1089 st = repo.status()
1090 if st.modified or st.added or st.removed or st.deleted:
1090 if st.modified or st.added or st.removed or st.deleted:
1091 raise error.Abort(_(b'uncommitted changes'), hint=hint)
1091 raise error.Abort(_(b'uncommitted changes'), hint=hint)
1092 ctx = repo[None]
1092 ctx = repo[None]
1093 for s in sorted(ctx.substate):
1093 for s in sorted(ctx.substate):
1094 ctx.sub(s).bailifchanged(hint=hint)
1094 ctx.sub(s).bailifchanged(hint=hint)
1095
1095
1096
1096
1097 def logmessage(ui, opts):
1097 def logmessage(ui, opts):
1098 """ get the log message according to -m and -l option """
1098 """ get the log message according to -m and -l option """
1099
1099
1100 check_at_most_one_arg(opts, b'message', b'logfile')
1100 check_at_most_one_arg(opts, b'message', b'logfile')
1101
1101
1102 message = opts.get(b'message')
1102 message = opts.get(b'message')
1103 logfile = opts.get(b'logfile')
1103 logfile = opts.get(b'logfile')
1104
1104
1105 if not message and logfile:
1105 if not message and logfile:
1106 try:
1106 try:
1107 if isstdiofilename(logfile):
1107 if isstdiofilename(logfile):
1108 message = ui.fin.read()
1108 message = ui.fin.read()
1109 else:
1109 else:
1110 message = b'\n'.join(util.readfile(logfile).splitlines())
1110 message = b'\n'.join(util.readfile(logfile).splitlines())
1111 except IOError as inst:
1111 except IOError as inst:
1112 raise error.Abort(
1112 raise error.Abort(
1113 _(b"can't read commit message '%s': %s")
1113 _(b"can't read commit message '%s': %s")
1114 % (logfile, encoding.strtolocal(inst.strerror))
1114 % (logfile, encoding.strtolocal(inst.strerror))
1115 )
1115 )
1116 return message
1116 return message
1117
1117
1118
1118
1119 def mergeeditform(ctxorbool, baseformname):
1119 def mergeeditform(ctxorbool, baseformname):
1120 """return appropriate editform name (referencing a committemplate)
1120 """return appropriate editform name (referencing a committemplate)
1121
1121
1122 'ctxorbool' is either a ctx to be committed, or a bool indicating whether
1122 'ctxorbool' is either a ctx to be committed, or a bool indicating whether
1123 merging is committed.
1123 merging is committed.
1124
1124
1125 This returns baseformname with '.merge' appended if it is a merge,
1125 This returns baseformname with '.merge' appended if it is a merge,
1126 otherwise '.normal' is appended.
1126 otherwise '.normal' is appended.
1127 """
1127 """
1128 if isinstance(ctxorbool, bool):
1128 if isinstance(ctxorbool, bool):
1129 if ctxorbool:
1129 if ctxorbool:
1130 return baseformname + b".merge"
1130 return baseformname + b".merge"
1131 elif len(ctxorbool.parents()) > 1:
1131 elif len(ctxorbool.parents()) > 1:
1132 return baseformname + b".merge"
1132 return baseformname + b".merge"
1133
1133
1134 return baseformname + b".normal"
1134 return baseformname + b".normal"
1135
1135
1136
1136
1137 def getcommiteditor(
1137 def getcommiteditor(
1138 edit=False, finishdesc=None, extramsg=None, editform=b'', **opts
1138 edit=False, finishdesc=None, extramsg=None, editform=b'', **opts
1139 ):
1139 ):
1140 """get appropriate commit message editor according to '--edit' option
1140 """get appropriate commit message editor according to '--edit' option
1141
1141
1142 'finishdesc' is a function to be called with edited commit message
1142 'finishdesc' is a function to be called with edited commit message
1143 (= 'description' of the new changeset) just after editing, but
1143 (= 'description' of the new changeset) just after editing, but
1144 before checking empty-ness. It should return actual text to be
1144 before checking empty-ness. It should return actual text to be
1145 stored into history. This allows to change description before
1145 stored into history. This allows to change description before
1146 storing.
1146 storing.
1147
1147
1148 'extramsg' is a extra message to be shown in the editor instead of
1148 'extramsg' is a extra message to be shown in the editor instead of
1149 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
1149 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
1150 is automatically added.
1150 is automatically added.
1151
1151
1152 'editform' is a dot-separated list of names, to distinguish
1152 'editform' is a dot-separated list of names, to distinguish
1153 the purpose of commit text editing.
1153 the purpose of commit text editing.
1154
1154
1155 'getcommiteditor' returns 'commitforceeditor' regardless of
1155 'getcommiteditor' returns 'commitforceeditor' regardless of
1156 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
1156 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
1157 they are specific for usage in MQ.
1157 they are specific for usage in MQ.
1158 """
1158 """
1159 if edit or finishdesc or extramsg:
1159 if edit or finishdesc or extramsg:
1160 return lambda r, c, s: commitforceeditor(
1160 return lambda r, c, s: commitforceeditor(
1161 r, c, s, finishdesc=finishdesc, extramsg=extramsg, editform=editform
1161 r, c, s, finishdesc=finishdesc, extramsg=extramsg, editform=editform
1162 )
1162 )
1163 elif editform:
1163 elif editform:
1164 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
1164 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
1165 else:
1165 else:
1166 return commiteditor
1166 return commiteditor
1167
1167
1168
1168
1169 def _escapecommandtemplate(tmpl):
1169 def _escapecommandtemplate(tmpl):
1170 parts = []
1170 parts = []
1171 for typ, start, end in templater.scantemplate(tmpl, raw=True):
1171 for typ, start, end in templater.scantemplate(tmpl, raw=True):
1172 if typ == b'string':
1172 if typ == b'string':
1173 parts.append(stringutil.escapestr(tmpl[start:end]))
1173 parts.append(stringutil.escapestr(tmpl[start:end]))
1174 else:
1174 else:
1175 parts.append(tmpl[start:end])
1175 parts.append(tmpl[start:end])
1176 return b''.join(parts)
1176 return b''.join(parts)
1177
1177
1178
1178
1179 def rendercommandtemplate(ui, tmpl, props):
1179 def rendercommandtemplate(ui, tmpl, props):
1180 r"""Expand a literal template 'tmpl' in a way suitable for command line
1180 r"""Expand a literal template 'tmpl' in a way suitable for command line
1181
1181
1182 '\' in outermost string is not taken as an escape character because it
1182 '\' in outermost string is not taken as an escape character because it
1183 is a directory separator on Windows.
1183 is a directory separator on Windows.
1184
1184
1185 >>> from . import ui as uimod
1185 >>> from . import ui as uimod
1186 >>> ui = uimod.ui()
1186 >>> ui = uimod.ui()
1187 >>> rendercommandtemplate(ui, b'c:\\{path}', {b'path': b'foo'})
1187 >>> rendercommandtemplate(ui, b'c:\\{path}', {b'path': b'foo'})
1188 'c:\\foo'
1188 'c:\\foo'
1189 >>> rendercommandtemplate(ui, b'{"c:\\{path}"}', {'path': b'foo'})
1189 >>> rendercommandtemplate(ui, b'{"c:\\{path}"}', {'path': b'foo'})
1190 'c:{path}'
1190 'c:{path}'
1191 """
1191 """
1192 if not tmpl:
1192 if not tmpl:
1193 return tmpl
1193 return tmpl
1194 t = formatter.maketemplater(ui, _escapecommandtemplate(tmpl))
1194 t = formatter.maketemplater(ui, _escapecommandtemplate(tmpl))
1195 return t.renderdefault(props)
1195 return t.renderdefault(props)
1196
1196
1197
1197
1198 def rendertemplate(ctx, tmpl, props=None):
1198 def rendertemplate(ctx, tmpl, props=None):
1199 """Expand a literal template 'tmpl' byte-string against one changeset
1199 """Expand a literal template 'tmpl' byte-string against one changeset
1200
1200
1201 Each props item must be a stringify-able value or a callable returning
1201 Each props item must be a stringify-able value or a callable returning
1202 such value, i.e. no bare list nor dict should be passed.
1202 such value, i.e. no bare list nor dict should be passed.
1203 """
1203 """
1204 repo = ctx.repo()
1204 repo = ctx.repo()
1205 tres = formatter.templateresources(repo.ui, repo)
1205 tres = formatter.templateresources(repo.ui, repo)
1206 t = formatter.maketemplater(
1206 t = formatter.maketemplater(
1207 repo.ui, tmpl, defaults=templatekw.keywords, resources=tres
1207 repo.ui, tmpl, defaults=templatekw.keywords, resources=tres
1208 )
1208 )
1209 mapping = {b'ctx': ctx}
1209 mapping = {b'ctx': ctx}
1210 if props:
1210 if props:
1211 mapping.update(props)
1211 mapping.update(props)
1212 return t.renderdefault(mapping)
1212 return t.renderdefault(mapping)
1213
1213
1214
1214
1215 def _buildfntemplate(pat, total=None, seqno=None, revwidth=None, pathname=None):
1215 def _buildfntemplate(pat, total=None, seqno=None, revwidth=None, pathname=None):
1216 r"""Convert old-style filename format string to template string
1216 r"""Convert old-style filename format string to template string
1217
1217
1218 >>> _buildfntemplate(b'foo-%b-%n.patch', seqno=0)
1218 >>> _buildfntemplate(b'foo-%b-%n.patch', seqno=0)
1219 'foo-{reporoot|basename}-{seqno}.patch'
1219 'foo-{reporoot|basename}-{seqno}.patch'
1220 >>> _buildfntemplate(b'%R{tags % "{tag}"}%H')
1220 >>> _buildfntemplate(b'%R{tags % "{tag}"}%H')
1221 '{rev}{tags % "{tag}"}{node}'
1221 '{rev}{tags % "{tag}"}{node}'
1222
1222
1223 '\' in outermost strings has to be escaped because it is a directory
1223 '\' in outermost strings has to be escaped because it is a directory
1224 separator on Windows:
1224 separator on Windows:
1225
1225
1226 >>> _buildfntemplate(b'c:\\tmp\\%R\\%n.patch', seqno=0)
1226 >>> _buildfntemplate(b'c:\\tmp\\%R\\%n.patch', seqno=0)
1227 'c:\\\\tmp\\\\{rev}\\\\{seqno}.patch'
1227 'c:\\\\tmp\\\\{rev}\\\\{seqno}.patch'
1228 >>> _buildfntemplate(b'\\\\foo\\bar.patch')
1228 >>> _buildfntemplate(b'\\\\foo\\bar.patch')
1229 '\\\\\\\\foo\\\\bar.patch'
1229 '\\\\\\\\foo\\\\bar.patch'
1230 >>> _buildfntemplate(b'\\{tags % "{tag}"}')
1230 >>> _buildfntemplate(b'\\{tags % "{tag}"}')
1231 '\\\\{tags % "{tag}"}'
1231 '\\\\{tags % "{tag}"}'
1232
1232
1233 but inner strings follow the template rules (i.e. '\' is taken as an
1233 but inner strings follow the template rules (i.e. '\' is taken as an
1234 escape character):
1234 escape character):
1235
1235
1236 >>> _buildfntemplate(br'{"c:\tmp"}', seqno=0)
1236 >>> _buildfntemplate(br'{"c:\tmp"}', seqno=0)
1237 '{"c:\\tmp"}'
1237 '{"c:\\tmp"}'
1238 """
1238 """
1239 expander = {
1239 expander = {
1240 b'H': b'{node}',
1240 b'H': b'{node}',
1241 b'R': b'{rev}',
1241 b'R': b'{rev}',
1242 b'h': b'{node|short}',
1242 b'h': b'{node|short}',
1243 b'm': br'{sub(r"[^\w]", "_", desc|firstline)}',
1243 b'm': br'{sub(r"[^\w]", "_", desc|firstline)}',
1244 b'r': b'{if(revwidth, pad(rev, revwidth, "0", left=True), rev)}',
1244 b'r': b'{if(revwidth, pad(rev, revwidth, "0", left=True), rev)}',
1245 b'%': b'%',
1245 b'%': b'%',
1246 b'b': b'{reporoot|basename}',
1246 b'b': b'{reporoot|basename}',
1247 }
1247 }
1248 if total is not None:
1248 if total is not None:
1249 expander[b'N'] = b'{total}'
1249 expander[b'N'] = b'{total}'
1250 if seqno is not None:
1250 if seqno is not None:
1251 expander[b'n'] = b'{seqno}'
1251 expander[b'n'] = b'{seqno}'
1252 if total is not None and seqno is not None:
1252 if total is not None and seqno is not None:
1253 expander[b'n'] = b'{pad(seqno, total|stringify|count, "0", left=True)}'
1253 expander[b'n'] = b'{pad(seqno, total|stringify|count, "0", left=True)}'
1254 if pathname is not None:
1254 if pathname is not None:
1255 expander[b's'] = b'{pathname|basename}'
1255 expander[b's'] = b'{pathname|basename}'
1256 expander[b'd'] = b'{if(pathname|dirname, pathname|dirname, ".")}'
1256 expander[b'd'] = b'{if(pathname|dirname, pathname|dirname, ".")}'
1257 expander[b'p'] = b'{pathname}'
1257 expander[b'p'] = b'{pathname}'
1258
1258
1259 newname = []
1259 newname = []
1260 for typ, start, end in templater.scantemplate(pat, raw=True):
1260 for typ, start, end in templater.scantemplate(pat, raw=True):
1261 if typ != b'string':
1261 if typ != b'string':
1262 newname.append(pat[start:end])
1262 newname.append(pat[start:end])
1263 continue
1263 continue
1264 i = start
1264 i = start
1265 while i < end:
1265 while i < end:
1266 n = pat.find(b'%', i, end)
1266 n = pat.find(b'%', i, end)
1267 if n < 0:
1267 if n < 0:
1268 newname.append(stringutil.escapestr(pat[i:end]))
1268 newname.append(stringutil.escapestr(pat[i:end]))
1269 break
1269 break
1270 newname.append(stringutil.escapestr(pat[i:n]))
1270 newname.append(stringutil.escapestr(pat[i:n]))
1271 if n + 2 > end:
1271 if n + 2 > end:
1272 raise error.Abort(
1272 raise error.Abort(
1273 _(b"incomplete format spec in output filename")
1273 _(b"incomplete format spec in output filename")
1274 )
1274 )
1275 c = pat[n + 1 : n + 2]
1275 c = pat[n + 1 : n + 2]
1276 i = n + 2
1276 i = n + 2
1277 try:
1277 try:
1278 newname.append(expander[c])
1278 newname.append(expander[c])
1279 except KeyError:
1279 except KeyError:
1280 raise error.Abort(
1280 raise error.Abort(
1281 _(b"invalid format spec '%%%s' in output filename") % c
1281 _(b"invalid format spec '%%%s' in output filename") % c
1282 )
1282 )
1283 return b''.join(newname)
1283 return b''.join(newname)
1284
1284
1285
1285
1286 def makefilename(ctx, pat, **props):
1286 def makefilename(ctx, pat, **props):
1287 if not pat:
1287 if not pat:
1288 return pat
1288 return pat
1289 tmpl = _buildfntemplate(pat, **props)
1289 tmpl = _buildfntemplate(pat, **props)
1290 # BUG: alias expansion shouldn't be made against template fragments
1290 # BUG: alias expansion shouldn't be made against template fragments
1291 # rewritten from %-format strings, but we have no easy way to partially
1291 # rewritten from %-format strings, but we have no easy way to partially
1292 # disable the expansion.
1292 # disable the expansion.
1293 return rendertemplate(ctx, tmpl, pycompat.byteskwargs(props))
1293 return rendertemplate(ctx, tmpl, pycompat.byteskwargs(props))
1294
1294
1295
1295
1296 def isstdiofilename(pat):
1296 def isstdiofilename(pat):
1297 """True if the given pat looks like a filename denoting stdin/stdout"""
1297 """True if the given pat looks like a filename denoting stdin/stdout"""
1298 return not pat or pat == b'-'
1298 return not pat or pat == b'-'
1299
1299
1300
1300
1301 class _unclosablefile(object):
1301 class _unclosablefile(object):
1302 def __init__(self, fp):
1302 def __init__(self, fp):
1303 self._fp = fp
1303 self._fp = fp
1304
1304
1305 def close(self):
1305 def close(self):
1306 pass
1306 pass
1307
1307
1308 def __iter__(self):
1308 def __iter__(self):
1309 return iter(self._fp)
1309 return iter(self._fp)
1310
1310
1311 def __getattr__(self, attr):
1311 def __getattr__(self, attr):
1312 return getattr(self._fp, attr)
1312 return getattr(self._fp, attr)
1313
1313
1314 def __enter__(self):
1314 def __enter__(self):
1315 return self
1315 return self
1316
1316
1317 def __exit__(self, exc_type, exc_value, exc_tb):
1317 def __exit__(self, exc_type, exc_value, exc_tb):
1318 pass
1318 pass
1319
1319
1320
1320
1321 def makefileobj(ctx, pat, mode=b'wb', **props):
1321 def makefileobj(ctx, pat, mode=b'wb', **props):
1322 writable = mode not in (b'r', b'rb')
1322 writable = mode not in (b'r', b'rb')
1323
1323
1324 if isstdiofilename(pat):
1324 if isstdiofilename(pat):
1325 repo = ctx.repo()
1325 repo = ctx.repo()
1326 if writable:
1326 if writable:
1327 fp = repo.ui.fout
1327 fp = repo.ui.fout
1328 else:
1328 else:
1329 fp = repo.ui.fin
1329 fp = repo.ui.fin
1330 return _unclosablefile(fp)
1330 return _unclosablefile(fp)
1331 fn = makefilename(ctx, pat, **props)
1331 fn = makefilename(ctx, pat, **props)
1332 return open(fn, mode)
1332 return open(fn, mode)
1333
1333
1334
1334
1335 def openstorage(repo, cmd, file_, opts, returnrevlog=False):
1335 def openstorage(repo, cmd, file_, opts, returnrevlog=False):
1336 """opens the changelog, manifest, a filelog or a given revlog"""
1336 """opens the changelog, manifest, a filelog or a given revlog"""
1337 cl = opts[b'changelog']
1337 cl = opts[b'changelog']
1338 mf = opts[b'manifest']
1338 mf = opts[b'manifest']
1339 dir = opts[b'dir']
1339 dir = opts[b'dir']
1340 msg = None
1340 msg = None
1341 if cl and mf:
1341 if cl and mf:
1342 msg = _(b'cannot specify --changelog and --manifest at the same time')
1342 msg = _(b'cannot specify --changelog and --manifest at the same time')
1343 elif cl and dir:
1343 elif cl and dir:
1344 msg = _(b'cannot specify --changelog and --dir at the same time')
1344 msg = _(b'cannot specify --changelog and --dir at the same time')
1345 elif cl or mf or dir:
1345 elif cl or mf or dir:
1346 if file_:
1346 if file_:
1347 msg = _(b'cannot specify filename with --changelog or --manifest')
1347 msg = _(b'cannot specify filename with --changelog or --manifest')
1348 elif not repo:
1348 elif not repo:
1349 msg = _(
1349 msg = _(
1350 b'cannot specify --changelog or --manifest or --dir '
1350 b'cannot specify --changelog or --manifest or --dir '
1351 b'without a repository'
1351 b'without a repository'
1352 )
1352 )
1353 if msg:
1353 if msg:
1354 raise error.Abort(msg)
1354 raise error.Abort(msg)
1355
1355
1356 r = None
1356 r = None
1357 if repo:
1357 if repo:
1358 if cl:
1358 if cl:
1359 r = repo.unfiltered().changelog
1359 r = repo.unfiltered().changelog
1360 elif dir:
1360 elif dir:
1361 if not scmutil.istreemanifest(repo):
1361 if not scmutil.istreemanifest(repo):
1362 raise error.Abort(
1362 raise error.Abort(
1363 _(
1363 _(
1364 b"--dir can only be used on repos with "
1364 b"--dir can only be used on repos with "
1365 b"treemanifest enabled"
1365 b"treemanifest enabled"
1366 )
1366 )
1367 )
1367 )
1368 if not dir.endswith(b'/'):
1368 if not dir.endswith(b'/'):
1369 dir = dir + b'/'
1369 dir = dir + b'/'
1370 dirlog = repo.manifestlog.getstorage(dir)
1370 dirlog = repo.manifestlog.getstorage(dir)
1371 if len(dirlog):
1371 if len(dirlog):
1372 r = dirlog
1372 r = dirlog
1373 elif mf:
1373 elif mf:
1374 r = repo.manifestlog.getstorage(b'')
1374 r = repo.manifestlog.getstorage(b'')
1375 elif file_:
1375 elif file_:
1376 filelog = repo.file(file_)
1376 filelog = repo.file(file_)
1377 if len(filelog):
1377 if len(filelog):
1378 r = filelog
1378 r = filelog
1379
1379
1380 # Not all storage may be revlogs. If requested, try to return an actual
1380 # Not all storage may be revlogs. If requested, try to return an actual
1381 # revlog instance.
1381 # revlog instance.
1382 if returnrevlog:
1382 if returnrevlog:
1383 if isinstance(r, revlog.revlog):
1383 if isinstance(r, revlog.revlog):
1384 pass
1384 pass
1385 elif util.safehasattr(r, b'_revlog'):
1385 elif util.safehasattr(r, b'_revlog'):
1386 r = r._revlog # pytype: disable=attribute-error
1386 r = r._revlog # pytype: disable=attribute-error
1387 elif r is not None:
1387 elif r is not None:
1388 raise error.Abort(_(b'%r does not appear to be a revlog') % r)
1388 raise error.Abort(_(b'%r does not appear to be a revlog') % r)
1389
1389
1390 if not r:
1390 if not r:
1391 if not returnrevlog:
1391 if not returnrevlog:
1392 raise error.Abort(_(b'cannot give path to non-revlog'))
1392 raise error.Abort(_(b'cannot give path to non-revlog'))
1393
1393
1394 if not file_:
1394 if not file_:
1395 raise error.CommandError(cmd, _(b'invalid arguments'))
1395 raise error.CommandError(cmd, _(b'invalid arguments'))
1396 if not os.path.isfile(file_):
1396 if not os.path.isfile(file_):
1397 raise error.Abort(_(b"revlog '%s' not found") % file_)
1397 raise error.Abort(_(b"revlog '%s' not found") % file_)
1398 r = revlog.revlog(
1398 r = revlog.revlog(
1399 vfsmod.vfs(encoding.getcwd(), audit=False), file_[:-2] + b".i"
1399 vfsmod.vfs(encoding.getcwd(), audit=False), file_[:-2] + b".i"
1400 )
1400 )
1401 return r
1401 return r
1402
1402
1403
1403
1404 def openrevlog(repo, cmd, file_, opts):
1404 def openrevlog(repo, cmd, file_, opts):
1405 """Obtain a revlog backing storage of an item.
1405 """Obtain a revlog backing storage of an item.
1406
1406
1407 This is similar to ``openstorage()`` except it always returns a revlog.
1407 This is similar to ``openstorage()`` except it always returns a revlog.
1408
1408
1409 In most cases, a caller cares about the main storage object - not the
1409 In most cases, a caller cares about the main storage object - not the
1410 revlog backing it. Therefore, this function should only be used by code
1410 revlog backing it. Therefore, this function should only be used by code
1411 that needs to examine low-level revlog implementation details. e.g. debug
1411 that needs to examine low-level revlog implementation details. e.g. debug
1412 commands.
1412 commands.
1413 """
1413 """
1414 return openstorage(repo, cmd, file_, opts, returnrevlog=True)
1414 return openstorage(repo, cmd, file_, opts, returnrevlog=True)
1415
1415
1416
1416
1417 def copy(ui, repo, pats, opts, rename=False):
1417 def copy(ui, repo, pats, opts, rename=False):
1418 check_incompatible_arguments(opts, b'forget', [b'dry_run'])
1418 check_incompatible_arguments(opts, b'forget', [b'dry_run'])
1419
1419
1420 # called with the repo lock held
1420 # called with the repo lock held
1421 #
1421 #
1422 # hgsep => pathname that uses "/" to separate directories
1422 # hgsep => pathname that uses "/" to separate directories
1423 # ossep => pathname that uses os.sep to separate directories
1423 # ossep => pathname that uses os.sep to separate directories
1424 cwd = repo.getcwd()
1424 cwd = repo.getcwd()
1425 targets = {}
1425 targets = {}
1426 forget = opts.get(b"forget")
1426 forget = opts.get(b"forget")
1427 after = opts.get(b"after")
1427 after = opts.get(b"after")
1428 dryrun = opts.get(b"dry_run")
1428 dryrun = opts.get(b"dry_run")
1429 rev = opts.get(b'at_rev')
1429 rev = opts.get(b'at_rev')
1430 if rev:
1430 if rev:
1431 if not forget and not after:
1431 if not forget and not after:
1432 # TODO: Remove this restriction and make it also create the copy
1432 # TODO: Remove this restriction and make it also create the copy
1433 # targets (and remove the rename source if rename==True).
1433 # targets (and remove the rename source if rename==True).
1434 raise error.Abort(_(b'--at-rev requires --after'))
1434 raise error.Abort(_(b'--at-rev requires --after'))
1435 ctx = scmutil.revsingle(repo, rev)
1435 ctx = scmutil.revsingle(repo, rev)
1436 if len(ctx.parents()) > 1:
1436 if len(ctx.parents()) > 1:
1437 raise error.Abort(_(b'cannot mark/unmark copy in merge commit'))
1437 raise error.Abort(_(b'cannot mark/unmark copy in merge commit'))
1438 else:
1438 else:
1439 ctx = repo[None]
1439 ctx = repo[None]
1440
1440
1441 pctx = ctx.p1()
1441 pctx = ctx.p1()
1442
1442
1443 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
1443 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
1444
1444
1445 if forget:
1445 if forget:
1446 if ctx.rev() is None:
1446 if ctx.rev() is None:
1447 new_ctx = ctx
1447 new_ctx = ctx
1448 else:
1448 else:
1449 if len(ctx.parents()) > 1:
1449 if len(ctx.parents()) > 1:
1450 raise error.Abort(_(b'cannot unmark copy in merge commit'))
1450 raise error.Abort(_(b'cannot unmark copy in merge commit'))
1451 # avoid cycle context -> subrepo -> cmdutil
1451 # avoid cycle context -> subrepo -> cmdutil
1452 from . import context
1452 from . import context
1453
1453
1454 rewriteutil.precheck(repo, [ctx.rev()], b'uncopy')
1454 rewriteutil.precheck(repo, [ctx.rev()], b'uncopy')
1455 new_ctx = context.overlayworkingctx(repo)
1455 new_ctx = context.overlayworkingctx(repo)
1456 new_ctx.setbase(ctx.p1())
1456 new_ctx.setbase(ctx.p1())
1457 mergemod.graft(repo, ctx, wctx=new_ctx)
1457 mergemod.graft(repo, ctx, wctx=new_ctx)
1458
1458
1459 match = scmutil.match(ctx, pats, opts)
1459 match = scmutil.match(ctx, pats, opts)
1460
1460
1461 current_copies = ctx.p1copies()
1461 current_copies = ctx.p1copies()
1462 current_copies.update(ctx.p2copies())
1462 current_copies.update(ctx.p2copies())
1463
1463
1464 uipathfn = scmutil.getuipathfn(repo)
1464 uipathfn = scmutil.getuipathfn(repo)
1465 for f in ctx.walk(match):
1465 for f in ctx.walk(match):
1466 if f in current_copies:
1466 if f in current_copies:
1467 new_ctx[f].markcopied(None)
1467 new_ctx[f].markcopied(None)
1468 elif match.exact(f):
1468 elif match.exact(f):
1469 ui.warn(
1469 ui.warn(
1470 _(
1470 _(
1471 b'%s: not unmarking as copy - file is not marked as copied\n'
1471 b'%s: not unmarking as copy - file is not marked as copied\n'
1472 )
1472 )
1473 % uipathfn(f)
1473 % uipathfn(f)
1474 )
1474 )
1475
1475
1476 if ctx.rev() is not None:
1476 if ctx.rev() is not None:
1477 with repo.lock():
1477 with repo.lock():
1478 mem_ctx = new_ctx.tomemctx_for_amend(ctx)
1478 mem_ctx = new_ctx.tomemctx_for_amend(ctx)
1479 new_node = mem_ctx.commit()
1479 new_node = mem_ctx.commit()
1480
1480
1481 if repo.dirstate.p1() == ctx.node():
1481 if repo.dirstate.p1() == ctx.node():
1482 with repo.dirstate.parentchange():
1482 with repo.dirstate.parentchange():
1483 scmutil.movedirstate(repo, repo[new_node])
1483 scmutil.movedirstate(repo, repo[new_node])
1484 replacements = {ctx.node(): [new_node]}
1484 replacements = {ctx.node(): [new_node]}
1485 scmutil.cleanupnodes(
1485 scmutil.cleanupnodes(
1486 repo, replacements, b'uncopy', fixphase=True
1486 repo, replacements, b'uncopy', fixphase=True
1487 )
1487 )
1488
1488
1489 return
1489 return
1490
1490
1491 pats = scmutil.expandpats(pats)
1491 pats = scmutil.expandpats(pats)
1492 if not pats:
1492 if not pats:
1493 raise error.Abort(_(b'no source or destination specified'))
1493 raise error.Abort(_(b'no source or destination specified'))
1494 if len(pats) == 1:
1494 if len(pats) == 1:
1495 raise error.Abort(_(b'no destination specified'))
1495 raise error.Abort(_(b'no destination specified'))
1496 dest = pats.pop()
1496 dest = pats.pop()
1497
1497
1498 def walkpat(pat):
1498 def walkpat(pat):
1499 srcs = []
1499 srcs = []
1500 # TODO: Inline and simplify the non-working-copy version of this code
1500 # TODO: Inline and simplify the non-working-copy version of this code
1501 # since it shares very little with the working-copy version of it.
1501 # since it shares very little with the working-copy version of it.
1502 ctx_to_walk = ctx if ctx.rev() is None else pctx
1502 ctx_to_walk = ctx if ctx.rev() is None else pctx
1503 m = scmutil.match(ctx_to_walk, [pat], opts, globbed=True)
1503 m = scmutil.match(ctx_to_walk, [pat], opts, globbed=True)
1504 for abs in ctx_to_walk.walk(m):
1504 for abs in ctx_to_walk.walk(m):
1505 rel = uipathfn(abs)
1505 rel = uipathfn(abs)
1506 exact = m.exact(abs)
1506 exact = m.exact(abs)
1507 if abs not in ctx:
1507 if abs not in ctx:
1508 if abs in pctx:
1508 if abs in pctx:
1509 if not after:
1509 if not after:
1510 if exact:
1510 if exact:
1511 ui.warn(
1511 ui.warn(
1512 _(
1512 _(
1513 b'%s: not copying - file has been marked '
1513 b'%s: not copying - file has been marked '
1514 b'for remove\n'
1514 b'for remove\n'
1515 )
1515 )
1516 % rel
1516 % rel
1517 )
1517 )
1518 continue
1518 continue
1519 else:
1519 else:
1520 if exact:
1520 if exact:
1521 ui.warn(
1521 ui.warn(
1522 _(b'%s: not copying - file is not managed\n') % rel
1522 _(b'%s: not copying - file is not managed\n') % rel
1523 )
1523 )
1524 continue
1524 continue
1525
1525
1526 # abs: hgsep
1526 # abs: hgsep
1527 # rel: ossep
1527 # rel: ossep
1528 srcs.append((abs, rel, exact))
1528 srcs.append((abs, rel, exact))
1529 return srcs
1529 return srcs
1530
1530
1531 if ctx.rev() is not None:
1531 if ctx.rev() is not None:
1532 rewriteutil.precheck(repo, [ctx.rev()], b'uncopy')
1532 rewriteutil.precheck(repo, [ctx.rev()], b'uncopy')
1533 absdest = pathutil.canonpath(repo.root, cwd, dest)
1533 absdest = pathutil.canonpath(repo.root, cwd, dest)
1534 if ctx.hasdir(absdest):
1534 if ctx.hasdir(absdest):
1535 raise error.Abort(
1535 raise error.Abort(
1536 _(b'%s: --at-rev does not support a directory as destination')
1536 _(b'%s: --at-rev does not support a directory as destination')
1537 % uipathfn(absdest)
1537 % uipathfn(absdest)
1538 )
1538 )
1539 if absdest not in ctx:
1539 if absdest not in ctx:
1540 raise error.Abort(
1540 raise error.Abort(
1541 _(b'%s: copy destination does not exist in %s')
1541 _(b'%s: copy destination does not exist in %s')
1542 % (uipathfn(absdest), ctx)
1542 % (uipathfn(absdest), ctx)
1543 )
1543 )
1544
1544
1545 # avoid cycle context -> subrepo -> cmdutil
1545 # avoid cycle context -> subrepo -> cmdutil
1546 from . import context
1546 from . import context
1547
1547
1548 copylist = []
1548 copylist = []
1549 for pat in pats:
1549 for pat in pats:
1550 srcs = walkpat(pat)
1550 srcs = walkpat(pat)
1551 if not srcs:
1551 if not srcs:
1552 continue
1552 continue
1553 for abs, rel, exact in srcs:
1553 for abs, rel, exact in srcs:
1554 copylist.append(abs)
1554 copylist.append(abs)
1555
1555
1556 if not copylist:
1556 if not copylist:
1557 raise error.Abort(_(b'no files to copy'))
1557 raise error.Abort(_(b'no files to copy'))
1558 # TODO: Add support for `hg cp --at-rev . foo bar dir` and
1558 # TODO: Add support for `hg cp --at-rev . foo bar dir` and
1559 # `hg cp --at-rev . dir1 dir2`, preferably unifying the code with the
1559 # `hg cp --at-rev . dir1 dir2`, preferably unifying the code with the
1560 # existing functions below.
1560 # existing functions below.
1561 if len(copylist) != 1:
1561 if len(copylist) != 1:
1562 raise error.Abort(_(b'--at-rev requires a single source'))
1562 raise error.Abort(_(b'--at-rev requires a single source'))
1563
1563
1564 new_ctx = context.overlayworkingctx(repo)
1564 new_ctx = context.overlayworkingctx(repo)
1565 new_ctx.setbase(ctx.p1())
1565 new_ctx.setbase(ctx.p1())
1566 mergemod.graft(repo, ctx, wctx=new_ctx)
1566 mergemod.graft(repo, ctx, wctx=new_ctx)
1567
1567
1568 new_ctx.markcopied(absdest, copylist[0])
1568 new_ctx.markcopied(absdest, copylist[0])
1569
1569
1570 with repo.lock():
1570 with repo.lock():
1571 mem_ctx = new_ctx.tomemctx_for_amend(ctx)
1571 mem_ctx = new_ctx.tomemctx_for_amend(ctx)
1572 new_node = mem_ctx.commit()
1572 new_node = mem_ctx.commit()
1573
1573
1574 if repo.dirstate.p1() == ctx.node():
1574 if repo.dirstate.p1() == ctx.node():
1575 with repo.dirstate.parentchange():
1575 with repo.dirstate.parentchange():
1576 scmutil.movedirstate(repo, repo[new_node])
1576 scmutil.movedirstate(repo, repo[new_node])
1577 replacements = {ctx.node(): [new_node]}
1577 replacements = {ctx.node(): [new_node]}
1578 scmutil.cleanupnodes(repo, replacements, b'copy', fixphase=True)
1578 scmutil.cleanupnodes(repo, replacements, b'copy', fixphase=True)
1579
1579
1580 return
1580 return
1581
1581
1582 # abssrc: hgsep
1582 # abssrc: hgsep
1583 # relsrc: ossep
1583 # relsrc: ossep
1584 # otarget: ossep
1584 # otarget: ossep
1585 def copyfile(abssrc, relsrc, otarget, exact):
1585 def copyfile(abssrc, relsrc, otarget, exact):
1586 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
1586 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
1587 if b'/' in abstarget:
1587 if b'/' in abstarget:
1588 # We cannot normalize abstarget itself, this would prevent
1588 # We cannot normalize abstarget itself, this would prevent
1589 # case only renames, like a => A.
1589 # case only renames, like a => A.
1590 abspath, absname = abstarget.rsplit(b'/', 1)
1590 abspath, absname = abstarget.rsplit(b'/', 1)
1591 abstarget = repo.dirstate.normalize(abspath) + b'/' + absname
1591 abstarget = repo.dirstate.normalize(abspath) + b'/' + absname
1592 reltarget = repo.pathto(abstarget, cwd)
1592 reltarget = repo.pathto(abstarget, cwd)
1593 target = repo.wjoin(abstarget)
1593 target = repo.wjoin(abstarget)
1594 src = repo.wjoin(abssrc)
1594 src = repo.wjoin(abssrc)
1595 state = repo.dirstate[abstarget]
1595 state = repo.dirstate[abstarget]
1596
1596
1597 scmutil.checkportable(ui, abstarget)
1597 scmutil.checkportable(ui, abstarget)
1598
1598
1599 # check for collisions
1599 # check for collisions
1600 prevsrc = targets.get(abstarget)
1600 prevsrc = targets.get(abstarget)
1601 if prevsrc is not None:
1601 if prevsrc is not None:
1602 ui.warn(
1602 ui.warn(
1603 _(b'%s: not overwriting - %s collides with %s\n')
1603 _(b'%s: not overwriting - %s collides with %s\n')
1604 % (
1604 % (
1605 reltarget,
1605 reltarget,
1606 repo.pathto(abssrc, cwd),
1606 repo.pathto(abssrc, cwd),
1607 repo.pathto(prevsrc, cwd),
1607 repo.pathto(prevsrc, cwd),
1608 )
1608 )
1609 )
1609 )
1610 return True # report a failure
1610 return True # report a failure
1611
1611
1612 # check for overwrites
1612 # check for overwrites
1613 exists = os.path.lexists(target)
1613 exists = os.path.lexists(target)
1614 samefile = False
1614 samefile = False
1615 if exists and abssrc != abstarget:
1615 if exists and abssrc != abstarget:
1616 if repo.dirstate.normalize(abssrc) == repo.dirstate.normalize(
1616 if repo.dirstate.normalize(abssrc) == repo.dirstate.normalize(
1617 abstarget
1617 abstarget
1618 ):
1618 ):
1619 if not rename:
1619 if not rename:
1620 ui.warn(_(b"%s: can't copy - same file\n") % reltarget)
1620 ui.warn(_(b"%s: can't copy - same file\n") % reltarget)
1621 return True # report a failure
1621 return True # report a failure
1622 exists = False
1622 exists = False
1623 samefile = True
1623 samefile = True
1624
1624
1625 if not after and exists or after and state in b'mn':
1625 if not after and exists or after and state in b'mn':
1626 if not opts[b'force']:
1626 if not opts[b'force']:
1627 if state in b'mn':
1627 if state in b'mn':
1628 msg = _(b'%s: not overwriting - file already committed\n')
1628 msg = _(b'%s: not overwriting - file already committed\n')
1629 if after:
1629 if after:
1630 flags = b'--after --force'
1630 flags = b'--after --force'
1631 else:
1631 else:
1632 flags = b'--force'
1632 flags = b'--force'
1633 if rename:
1633 if rename:
1634 hint = (
1634 hint = (
1635 _(
1635 _(
1636 b"('hg rename %s' to replace the file by "
1636 b"('hg rename %s' to replace the file by "
1637 b'recording a rename)\n'
1637 b'recording a rename)\n'
1638 )
1638 )
1639 % flags
1639 % flags
1640 )
1640 )
1641 else:
1641 else:
1642 hint = (
1642 hint = (
1643 _(
1643 _(
1644 b"('hg copy %s' to replace the file by "
1644 b"('hg copy %s' to replace the file by "
1645 b'recording a copy)\n'
1645 b'recording a copy)\n'
1646 )
1646 )
1647 % flags
1647 % flags
1648 )
1648 )
1649 else:
1649 else:
1650 msg = _(b'%s: not overwriting - file exists\n')
1650 msg = _(b'%s: not overwriting - file exists\n')
1651 if rename:
1651 if rename:
1652 hint = _(
1652 hint = _(
1653 b"('hg rename --after' to record the rename)\n"
1653 b"('hg rename --after' to record the rename)\n"
1654 )
1654 )
1655 else:
1655 else:
1656 hint = _(b"('hg copy --after' to record the copy)\n")
1656 hint = _(b"('hg copy --after' to record the copy)\n")
1657 ui.warn(msg % reltarget)
1657 ui.warn(msg % reltarget)
1658 ui.warn(hint)
1658 ui.warn(hint)
1659 return True # report a failure
1659 return True # report a failure
1660
1660
1661 if after:
1661 if after:
1662 if not exists:
1662 if not exists:
1663 if rename:
1663 if rename:
1664 ui.warn(
1664 ui.warn(
1665 _(b'%s: not recording move - %s does not exist\n')
1665 _(b'%s: not recording move - %s does not exist\n')
1666 % (relsrc, reltarget)
1666 % (relsrc, reltarget)
1667 )
1667 )
1668 else:
1668 else:
1669 ui.warn(
1669 ui.warn(
1670 _(b'%s: not recording copy - %s does not exist\n')
1670 _(b'%s: not recording copy - %s does not exist\n')
1671 % (relsrc, reltarget)
1671 % (relsrc, reltarget)
1672 )
1672 )
1673 return True # report a failure
1673 return True # report a failure
1674 elif not dryrun:
1674 elif not dryrun:
1675 try:
1675 try:
1676 if exists:
1676 if exists:
1677 os.unlink(target)
1677 os.unlink(target)
1678 targetdir = os.path.dirname(target) or b'.'
1678 targetdir = os.path.dirname(target) or b'.'
1679 if not os.path.isdir(targetdir):
1679 if not os.path.isdir(targetdir):
1680 os.makedirs(targetdir)
1680 os.makedirs(targetdir)
1681 if samefile:
1681 if samefile:
1682 tmp = target + b"~hgrename"
1682 tmp = target + b"~hgrename"
1683 os.rename(src, tmp)
1683 os.rename(src, tmp)
1684 os.rename(tmp, target)
1684 os.rename(tmp, target)
1685 else:
1685 else:
1686 # Preserve stat info on renames, not on copies; this matches
1686 # Preserve stat info on renames, not on copies; this matches
1687 # Linux CLI behavior.
1687 # Linux CLI behavior.
1688 util.copyfile(src, target, copystat=rename)
1688 util.copyfile(src, target, copystat=rename)
1689 srcexists = True
1689 srcexists = True
1690 except IOError as inst:
1690 except IOError as inst:
1691 if inst.errno == errno.ENOENT:
1691 if inst.errno == errno.ENOENT:
1692 ui.warn(_(b'%s: deleted in working directory\n') % relsrc)
1692 ui.warn(_(b'%s: deleted in working directory\n') % relsrc)
1693 srcexists = False
1693 srcexists = False
1694 else:
1694 else:
1695 ui.warn(
1695 ui.warn(
1696 _(b'%s: cannot copy - %s\n')
1696 _(b'%s: cannot copy - %s\n')
1697 % (relsrc, encoding.strtolocal(inst.strerror))
1697 % (relsrc, encoding.strtolocal(inst.strerror))
1698 )
1698 )
1699 return True # report a failure
1699 return True # report a failure
1700
1700
1701 if ui.verbose or not exact:
1701 if ui.verbose or not exact:
1702 if rename:
1702 if rename:
1703 ui.status(_(b'moving %s to %s\n') % (relsrc, reltarget))
1703 ui.status(_(b'moving %s to %s\n') % (relsrc, reltarget))
1704 else:
1704 else:
1705 ui.status(_(b'copying %s to %s\n') % (relsrc, reltarget))
1705 ui.status(_(b'copying %s to %s\n') % (relsrc, reltarget))
1706
1706
1707 targets[abstarget] = abssrc
1707 targets[abstarget] = abssrc
1708
1708
1709 # fix up dirstate
1709 # fix up dirstate
1710 scmutil.dirstatecopy(
1710 scmutil.dirstatecopy(
1711 ui, repo, ctx, abssrc, abstarget, dryrun=dryrun, cwd=cwd
1711 ui, repo, ctx, abssrc, abstarget, dryrun=dryrun, cwd=cwd
1712 )
1712 )
1713 if rename and not dryrun:
1713 if rename and not dryrun:
1714 if not after and srcexists and not samefile:
1714 if not after and srcexists and not samefile:
1715 rmdir = repo.ui.configbool(b'experimental', b'removeemptydirs')
1715 rmdir = repo.ui.configbool(b'experimental', b'removeemptydirs')
1716 repo.wvfs.unlinkpath(abssrc, rmdir=rmdir)
1716 repo.wvfs.unlinkpath(abssrc, rmdir=rmdir)
1717 ctx.forget([abssrc])
1717 ctx.forget([abssrc])
1718
1718
1719 # pat: ossep
1719 # pat: ossep
1720 # dest ossep
1720 # dest ossep
1721 # srcs: list of (hgsep, hgsep, ossep, bool)
1721 # srcs: list of (hgsep, hgsep, ossep, bool)
1722 # return: function that takes hgsep and returns ossep
1722 # return: function that takes hgsep and returns ossep
1723 def targetpathfn(pat, dest, srcs):
1723 def targetpathfn(pat, dest, srcs):
1724 if os.path.isdir(pat):
1724 if os.path.isdir(pat):
1725 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1725 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1726 abspfx = util.localpath(abspfx)
1726 abspfx = util.localpath(abspfx)
1727 if destdirexists:
1727 if destdirexists:
1728 striplen = len(os.path.split(abspfx)[0])
1728 striplen = len(os.path.split(abspfx)[0])
1729 else:
1729 else:
1730 striplen = len(abspfx)
1730 striplen = len(abspfx)
1731 if striplen:
1731 if striplen:
1732 striplen += len(pycompat.ossep)
1732 striplen += len(pycompat.ossep)
1733 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
1733 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
1734 elif destdirexists:
1734 elif destdirexists:
1735 res = lambda p: os.path.join(
1735 res = lambda p: os.path.join(
1736 dest, os.path.basename(util.localpath(p))
1736 dest, os.path.basename(util.localpath(p))
1737 )
1737 )
1738 else:
1738 else:
1739 res = lambda p: dest
1739 res = lambda p: dest
1740 return res
1740 return res
1741
1741
1742 # pat: ossep
1742 # pat: ossep
1743 # dest ossep
1743 # dest ossep
1744 # srcs: list of (hgsep, hgsep, ossep, bool)
1744 # srcs: list of (hgsep, hgsep, ossep, bool)
1745 # return: function that takes hgsep and returns ossep
1745 # return: function that takes hgsep and returns ossep
1746 def targetpathafterfn(pat, dest, srcs):
1746 def targetpathafterfn(pat, dest, srcs):
1747 if matchmod.patkind(pat):
1747 if matchmod.patkind(pat):
1748 # a mercurial pattern
1748 # a mercurial pattern
1749 res = lambda p: os.path.join(
1749 res = lambda p: os.path.join(
1750 dest, os.path.basename(util.localpath(p))
1750 dest, os.path.basename(util.localpath(p))
1751 )
1751 )
1752 else:
1752 else:
1753 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1753 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1754 if len(abspfx) < len(srcs[0][0]):
1754 if len(abspfx) < len(srcs[0][0]):
1755 # A directory. Either the target path contains the last
1755 # A directory. Either the target path contains the last
1756 # component of the source path or it does not.
1756 # component of the source path or it does not.
1757 def evalpath(striplen):
1757 def evalpath(striplen):
1758 score = 0
1758 score = 0
1759 for s in srcs:
1759 for s in srcs:
1760 t = os.path.join(dest, util.localpath(s[0])[striplen:])
1760 t = os.path.join(dest, util.localpath(s[0])[striplen:])
1761 if os.path.lexists(t):
1761 if os.path.lexists(t):
1762 score += 1
1762 score += 1
1763 return score
1763 return score
1764
1764
1765 abspfx = util.localpath(abspfx)
1765 abspfx = util.localpath(abspfx)
1766 striplen = len(abspfx)
1766 striplen = len(abspfx)
1767 if striplen:
1767 if striplen:
1768 striplen += len(pycompat.ossep)
1768 striplen += len(pycompat.ossep)
1769 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
1769 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
1770 score = evalpath(striplen)
1770 score = evalpath(striplen)
1771 striplen1 = len(os.path.split(abspfx)[0])
1771 striplen1 = len(os.path.split(abspfx)[0])
1772 if striplen1:
1772 if striplen1:
1773 striplen1 += len(pycompat.ossep)
1773 striplen1 += len(pycompat.ossep)
1774 if evalpath(striplen1) > score:
1774 if evalpath(striplen1) > score:
1775 striplen = striplen1
1775 striplen = striplen1
1776 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
1776 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
1777 else:
1777 else:
1778 # a file
1778 # a file
1779 if destdirexists:
1779 if destdirexists:
1780 res = lambda p: os.path.join(
1780 res = lambda p: os.path.join(
1781 dest, os.path.basename(util.localpath(p))
1781 dest, os.path.basename(util.localpath(p))
1782 )
1782 )
1783 else:
1783 else:
1784 res = lambda p: dest
1784 res = lambda p: dest
1785 return res
1785 return res
1786
1786
1787 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
1787 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
1788 if not destdirexists:
1788 if not destdirexists:
1789 if len(pats) > 1 or matchmod.patkind(pats[0]):
1789 if len(pats) > 1 or matchmod.patkind(pats[0]):
1790 raise error.Abort(
1790 raise error.Abort(
1791 _(
1791 _(
1792 b'with multiple sources, destination must be an '
1792 b'with multiple sources, destination must be an '
1793 b'existing directory'
1793 b'existing directory'
1794 )
1794 )
1795 )
1795 )
1796 if util.endswithsep(dest):
1796 if util.endswithsep(dest):
1797 raise error.Abort(_(b'destination %s is not a directory') % dest)
1797 raise error.Abort(_(b'destination %s is not a directory') % dest)
1798
1798
1799 tfn = targetpathfn
1799 tfn = targetpathfn
1800 if after:
1800 if after:
1801 tfn = targetpathafterfn
1801 tfn = targetpathafterfn
1802 copylist = []
1802 copylist = []
1803 for pat in pats:
1803 for pat in pats:
1804 srcs = walkpat(pat)
1804 srcs = walkpat(pat)
1805 if not srcs:
1805 if not srcs:
1806 continue
1806 continue
1807 copylist.append((tfn(pat, dest, srcs), srcs))
1807 copylist.append((tfn(pat, dest, srcs), srcs))
1808 if not copylist:
1808 if not copylist:
1809 raise error.Abort(_(b'no files to copy'))
1809 raise error.Abort(_(b'no files to copy'))
1810
1810
1811 errors = 0
1811 errors = 0
1812 for targetpath, srcs in copylist:
1812 for targetpath, srcs in copylist:
1813 for abssrc, relsrc, exact in srcs:
1813 for abssrc, relsrc, exact in srcs:
1814 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
1814 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
1815 errors += 1
1815 errors += 1
1816
1816
1817 return errors != 0
1817 return errors != 0
1818
1818
1819
1819
1820 ## facility to let extension process additional data into an import patch
1820 ## facility to let extension process additional data into an import patch
1821 # list of identifier to be executed in order
1821 # list of identifier to be executed in order
1822 extrapreimport = [] # run before commit
1822 extrapreimport = [] # run before commit
1823 extrapostimport = [] # run after commit
1823 extrapostimport = [] # run after commit
1824 # mapping from identifier to actual import function
1824 # mapping from identifier to actual import function
1825 #
1825 #
1826 # 'preimport' are run before the commit is made and are provided the following
1826 # 'preimport' are run before the commit is made and are provided the following
1827 # arguments:
1827 # arguments:
1828 # - repo: the localrepository instance,
1828 # - repo: the localrepository instance,
1829 # - patchdata: data extracted from patch header (cf m.patch.patchheadermap),
1829 # - patchdata: data extracted from patch header (cf m.patch.patchheadermap),
1830 # - extra: the future extra dictionary of the changeset, please mutate it,
1830 # - extra: the future extra dictionary of the changeset, please mutate it,
1831 # - opts: the import options.
1831 # - opts: the import options.
1832 # XXX ideally, we would just pass an ctx ready to be computed, that would allow
1832 # XXX ideally, we would just pass an ctx ready to be computed, that would allow
1833 # mutation of in memory commit and more. Feel free to rework the code to get
1833 # mutation of in memory commit and more. Feel free to rework the code to get
1834 # there.
1834 # there.
1835 extrapreimportmap = {}
1835 extrapreimportmap = {}
1836 # 'postimport' are run after the commit is made and are provided the following
1836 # 'postimport' are run after the commit is made and are provided the following
1837 # argument:
1837 # argument:
1838 # - ctx: the changectx created by import.
1838 # - ctx: the changectx created by import.
1839 extrapostimportmap = {}
1839 extrapostimportmap = {}
1840
1840
1841
1841
1842 def tryimportone(ui, repo, patchdata, parents, opts, msgs, updatefunc):
1842 def tryimportone(ui, repo, patchdata, parents, opts, msgs, updatefunc):
1843 """Utility function used by commands.import to import a single patch
1843 """Utility function used by commands.import to import a single patch
1844
1844
1845 This function is explicitly defined here to help the evolve extension to
1845 This function is explicitly defined here to help the evolve extension to
1846 wrap this part of the import logic.
1846 wrap this part of the import logic.
1847
1847
1848 The API is currently a bit ugly because it a simple code translation from
1848 The API is currently a bit ugly because it a simple code translation from
1849 the import command. Feel free to make it better.
1849 the import command. Feel free to make it better.
1850
1850
1851 :patchdata: a dictionary containing parsed patch data (such as from
1851 :patchdata: a dictionary containing parsed patch data (such as from
1852 ``patch.extract()``)
1852 ``patch.extract()``)
1853 :parents: nodes that will be parent of the created commit
1853 :parents: nodes that will be parent of the created commit
1854 :opts: the full dict of option passed to the import command
1854 :opts: the full dict of option passed to the import command
1855 :msgs: list to save commit message to.
1855 :msgs: list to save commit message to.
1856 (used in case we need to save it when failing)
1856 (used in case we need to save it when failing)
1857 :updatefunc: a function that update a repo to a given node
1857 :updatefunc: a function that update a repo to a given node
1858 updatefunc(<repo>, <node>)
1858 updatefunc(<repo>, <node>)
1859 """
1859 """
1860 # avoid cycle context -> subrepo -> cmdutil
1860 # avoid cycle context -> subrepo -> cmdutil
1861 from . import context
1861 from . import context
1862
1862
1863 tmpname = patchdata.get(b'filename')
1863 tmpname = patchdata.get(b'filename')
1864 message = patchdata.get(b'message')
1864 message = patchdata.get(b'message')
1865 user = opts.get(b'user') or patchdata.get(b'user')
1865 user = opts.get(b'user') or patchdata.get(b'user')
1866 date = opts.get(b'date') or patchdata.get(b'date')
1866 date = opts.get(b'date') or patchdata.get(b'date')
1867 branch = patchdata.get(b'branch')
1867 branch = patchdata.get(b'branch')
1868 nodeid = patchdata.get(b'nodeid')
1868 nodeid = patchdata.get(b'nodeid')
1869 p1 = patchdata.get(b'p1')
1869 p1 = patchdata.get(b'p1')
1870 p2 = patchdata.get(b'p2')
1870 p2 = patchdata.get(b'p2')
1871
1871
1872 nocommit = opts.get(b'no_commit')
1872 nocommit = opts.get(b'no_commit')
1873 importbranch = opts.get(b'import_branch')
1873 importbranch = opts.get(b'import_branch')
1874 update = not opts.get(b'bypass')
1874 update = not opts.get(b'bypass')
1875 strip = opts[b"strip"]
1875 strip = opts[b"strip"]
1876 prefix = opts[b"prefix"]
1876 prefix = opts[b"prefix"]
1877 sim = float(opts.get(b'similarity') or 0)
1877 sim = float(opts.get(b'similarity') or 0)
1878
1878
1879 if not tmpname:
1879 if not tmpname:
1880 return None, None, False
1880 return None, None, False
1881
1881
1882 rejects = False
1882 rejects = False
1883
1883
1884 cmdline_message = logmessage(ui, opts)
1884 cmdline_message = logmessage(ui, opts)
1885 if cmdline_message:
1885 if cmdline_message:
1886 # pickup the cmdline msg
1886 # pickup the cmdline msg
1887 message = cmdline_message
1887 message = cmdline_message
1888 elif message:
1888 elif message:
1889 # pickup the patch msg
1889 # pickup the patch msg
1890 message = message.strip()
1890 message = message.strip()
1891 else:
1891 else:
1892 # launch the editor
1892 # launch the editor
1893 message = None
1893 message = None
1894 ui.debug(b'message:\n%s\n' % (message or b''))
1894 ui.debug(b'message:\n%s\n' % (message or b''))
1895
1895
1896 if len(parents) == 1:
1896 if len(parents) == 1:
1897 parents.append(repo[nullid])
1897 parents.append(repo[nullid])
1898 if opts.get(b'exact'):
1898 if opts.get(b'exact'):
1899 if not nodeid or not p1:
1899 if not nodeid or not p1:
1900 raise error.Abort(_(b'not a Mercurial patch'))
1900 raise error.Abort(_(b'not a Mercurial patch'))
1901 p1 = repo[p1]
1901 p1 = repo[p1]
1902 p2 = repo[p2 or nullid]
1902 p2 = repo[p2 or nullid]
1903 elif p2:
1903 elif p2:
1904 try:
1904 try:
1905 p1 = repo[p1]
1905 p1 = repo[p1]
1906 p2 = repo[p2]
1906 p2 = repo[p2]
1907 # Without any options, consider p2 only if the
1907 # Without any options, consider p2 only if the
1908 # patch is being applied on top of the recorded
1908 # patch is being applied on top of the recorded
1909 # first parent.
1909 # first parent.
1910 if p1 != parents[0]:
1910 if p1 != parents[0]:
1911 p1 = parents[0]
1911 p1 = parents[0]
1912 p2 = repo[nullid]
1912 p2 = repo[nullid]
1913 except error.RepoError:
1913 except error.RepoError:
1914 p1, p2 = parents
1914 p1, p2 = parents
1915 if p2.node() == nullid:
1915 if p2.node() == nullid:
1916 ui.warn(
1916 ui.warn(
1917 _(
1917 _(
1918 b"warning: import the patch as a normal revision\n"
1918 b"warning: import the patch as a normal revision\n"
1919 b"(use --exact to import the patch as a merge)\n"
1919 b"(use --exact to import the patch as a merge)\n"
1920 )
1920 )
1921 )
1921 )
1922 else:
1922 else:
1923 p1, p2 = parents
1923 p1, p2 = parents
1924
1924
1925 n = None
1925 n = None
1926 if update:
1926 if update:
1927 if p1 != parents[0]:
1927 if p1 != parents[0]:
1928 updatefunc(repo, p1.node())
1928 updatefunc(repo, p1.node())
1929 if p2 != parents[1]:
1929 if p2 != parents[1]:
1930 repo.setparents(p1.node(), p2.node())
1930 repo.setparents(p1.node(), p2.node())
1931
1931
1932 if opts.get(b'exact') or importbranch:
1932 if opts.get(b'exact') or importbranch:
1933 repo.dirstate.setbranch(branch or b'default')
1933 repo.dirstate.setbranch(branch or b'default')
1934
1934
1935 partial = opts.get(b'partial', False)
1935 partial = opts.get(b'partial', False)
1936 files = set()
1936 files = set()
1937 try:
1937 try:
1938 patch.patch(
1938 patch.patch(
1939 ui,
1939 ui,
1940 repo,
1940 repo,
1941 tmpname,
1941 tmpname,
1942 strip=strip,
1942 strip=strip,
1943 prefix=prefix,
1943 prefix=prefix,
1944 files=files,
1944 files=files,
1945 eolmode=None,
1945 eolmode=None,
1946 similarity=sim / 100.0,
1946 similarity=sim / 100.0,
1947 )
1947 )
1948 except error.PatchError as e:
1948 except error.PatchError as e:
1949 if not partial:
1949 if not partial:
1950 raise error.Abort(pycompat.bytestr(e))
1950 raise error.Abort(pycompat.bytestr(e))
1951 if partial:
1951 if partial:
1952 rejects = True
1952 rejects = True
1953
1953
1954 files = list(files)
1954 files = list(files)
1955 if nocommit:
1955 if nocommit:
1956 if message:
1956 if message:
1957 msgs.append(message)
1957 msgs.append(message)
1958 else:
1958 else:
1959 if opts.get(b'exact') or p2:
1959 if opts.get(b'exact') or p2:
1960 # If you got here, you either use --force and know what
1960 # If you got here, you either use --force and know what
1961 # you are doing or used --exact or a merge patch while
1961 # you are doing or used --exact or a merge patch while
1962 # being updated to its first parent.
1962 # being updated to its first parent.
1963 m = None
1963 m = None
1964 else:
1964 else:
1965 m = scmutil.matchfiles(repo, files or [])
1965 m = scmutil.matchfiles(repo, files or [])
1966 editform = mergeeditform(repo[None], b'import.normal')
1966 editform = mergeeditform(repo[None], b'import.normal')
1967 if opts.get(b'exact'):
1967 if opts.get(b'exact'):
1968 editor = None
1968 editor = None
1969 else:
1969 else:
1970 editor = getcommiteditor(
1970 editor = getcommiteditor(
1971 editform=editform, **pycompat.strkwargs(opts)
1971 editform=editform, **pycompat.strkwargs(opts)
1972 )
1972 )
1973 extra = {}
1973 extra = {}
1974 for idfunc in extrapreimport:
1974 for idfunc in extrapreimport:
1975 extrapreimportmap[idfunc](repo, patchdata, extra, opts)
1975 extrapreimportmap[idfunc](repo, patchdata, extra, opts)
1976 overrides = {}
1976 overrides = {}
1977 if partial:
1977 if partial:
1978 overrides[(b'ui', b'allowemptycommit')] = True
1978 overrides[(b'ui', b'allowemptycommit')] = True
1979 if opts.get(b'secret'):
1979 if opts.get(b'secret'):
1980 overrides[(b'phases', b'new-commit')] = b'secret'
1980 overrides[(b'phases', b'new-commit')] = b'secret'
1981 with repo.ui.configoverride(overrides, b'import'):
1981 with repo.ui.configoverride(overrides, b'import'):
1982 n = repo.commit(
1982 n = repo.commit(
1983 message, user, date, match=m, editor=editor, extra=extra
1983 message, user, date, match=m, editor=editor, extra=extra
1984 )
1984 )
1985 for idfunc in extrapostimport:
1985 for idfunc in extrapostimport:
1986 extrapostimportmap[idfunc](repo[n])
1986 extrapostimportmap[idfunc](repo[n])
1987 else:
1987 else:
1988 if opts.get(b'exact') or importbranch:
1988 if opts.get(b'exact') or importbranch:
1989 branch = branch or b'default'
1989 branch = branch or b'default'
1990 else:
1990 else:
1991 branch = p1.branch()
1991 branch = p1.branch()
1992 store = patch.filestore()
1992 store = patch.filestore()
1993 try:
1993 try:
1994 files = set()
1994 files = set()
1995 try:
1995 try:
1996 patch.patchrepo(
1996 patch.patchrepo(
1997 ui,
1997 ui,
1998 repo,
1998 repo,
1999 p1,
1999 p1,
2000 store,
2000 store,
2001 tmpname,
2001 tmpname,
2002 strip,
2002 strip,
2003 prefix,
2003 prefix,
2004 files,
2004 files,
2005 eolmode=None,
2005 eolmode=None,
2006 )
2006 )
2007 except error.PatchError as e:
2007 except error.PatchError as e:
2008 raise error.Abort(stringutil.forcebytestr(e))
2008 raise error.Abort(stringutil.forcebytestr(e))
2009 if opts.get(b'exact'):
2009 if opts.get(b'exact'):
2010 editor = None
2010 editor = None
2011 else:
2011 else:
2012 editor = getcommiteditor(editform=b'import.bypass')
2012 editor = getcommiteditor(editform=b'import.bypass')
2013 memctx = context.memctx(
2013 memctx = context.memctx(
2014 repo,
2014 repo,
2015 (p1.node(), p2.node()),
2015 (p1.node(), p2.node()),
2016 message,
2016 message,
2017 files=files,
2017 files=files,
2018 filectxfn=store,
2018 filectxfn=store,
2019 user=user,
2019 user=user,
2020 date=date,
2020 date=date,
2021 branch=branch,
2021 branch=branch,
2022 editor=editor,
2022 editor=editor,
2023 )
2023 )
2024
2024
2025 overrides = {}
2025 overrides = {}
2026 if opts.get(b'secret'):
2026 if opts.get(b'secret'):
2027 overrides[(b'phases', b'new-commit')] = b'secret'
2027 overrides[(b'phases', b'new-commit')] = b'secret'
2028 with repo.ui.configoverride(overrides, b'import'):
2028 with repo.ui.configoverride(overrides, b'import'):
2029 n = memctx.commit()
2029 n = memctx.commit()
2030 finally:
2030 finally:
2031 store.close()
2031 store.close()
2032 if opts.get(b'exact') and nocommit:
2032 if opts.get(b'exact') and nocommit:
2033 # --exact with --no-commit is still useful in that it does merge
2033 # --exact with --no-commit is still useful in that it does merge
2034 # and branch bits
2034 # and branch bits
2035 ui.warn(_(b"warning: can't check exact import with --no-commit\n"))
2035 ui.warn(_(b"warning: can't check exact import with --no-commit\n"))
2036 elif opts.get(b'exact') and (not n or hex(n) != nodeid):
2036 elif opts.get(b'exact') and (not n or hex(n) != nodeid):
2037 raise error.Abort(_(b'patch is damaged or loses information'))
2037 raise error.Abort(_(b'patch is damaged or loses information'))
2038 msg = _(b'applied to working directory')
2038 msg = _(b'applied to working directory')
2039 if n:
2039 if n:
2040 # i18n: refers to a short changeset id
2040 # i18n: refers to a short changeset id
2041 msg = _(b'created %s') % short(n)
2041 msg = _(b'created %s') % short(n)
2042 return msg, n, rejects
2042 return msg, n, rejects
2043
2043
2044
2044
2045 # facility to let extensions include additional data in an exported patch
2045 # facility to let extensions include additional data in an exported patch
2046 # list of identifiers to be executed in order
2046 # list of identifiers to be executed in order
2047 extraexport = []
2047 extraexport = []
2048 # mapping from identifier to actual export function
2048 # mapping from identifier to actual export function
2049 # function as to return a string to be added to the header or None
2049 # function as to return a string to be added to the header or None
2050 # it is given two arguments (sequencenumber, changectx)
2050 # it is given two arguments (sequencenumber, changectx)
2051 extraexportmap = {}
2051 extraexportmap = {}
2052
2052
2053
2053
2054 def _exportsingle(repo, ctx, fm, match, switch_parent, seqno, diffopts):
2054 def _exportsingle(repo, ctx, fm, match, switch_parent, seqno, diffopts):
2055 node = scmutil.binnode(ctx)
2055 node = scmutil.binnode(ctx)
2056 parents = [p.node() for p in ctx.parents() if p]
2056 parents = [p.node() for p in ctx.parents() if p]
2057 branch = ctx.branch()
2057 branch = ctx.branch()
2058 if switch_parent:
2058 if switch_parent:
2059 parents.reverse()
2059 parents.reverse()
2060
2060
2061 if parents:
2061 if parents:
2062 prev = parents[0]
2062 prev = parents[0]
2063 else:
2063 else:
2064 prev = nullid
2064 prev = nullid
2065
2065
2066 fm.context(ctx=ctx)
2066 fm.context(ctx=ctx)
2067 fm.plain(b'# HG changeset patch\n')
2067 fm.plain(b'# HG changeset patch\n')
2068 fm.write(b'user', b'# User %s\n', ctx.user())
2068 fm.write(b'user', b'# User %s\n', ctx.user())
2069 fm.plain(b'# Date %d %d\n' % ctx.date())
2069 fm.plain(b'# Date %d %d\n' % ctx.date())
2070 fm.write(b'date', b'# %s\n', fm.formatdate(ctx.date()))
2070 fm.write(b'date', b'# %s\n', fm.formatdate(ctx.date()))
2071 fm.condwrite(
2071 fm.condwrite(
2072 branch and branch != b'default', b'branch', b'# Branch %s\n', branch
2072 branch and branch != b'default', b'branch', b'# Branch %s\n', branch
2073 )
2073 )
2074 fm.write(b'node', b'# Node ID %s\n', hex(node))
2074 fm.write(b'node', b'# Node ID %s\n', hex(node))
2075 fm.plain(b'# Parent %s\n' % hex(prev))
2075 fm.plain(b'# Parent %s\n' % hex(prev))
2076 if len(parents) > 1:
2076 if len(parents) > 1:
2077 fm.plain(b'# Parent %s\n' % hex(parents[1]))
2077 fm.plain(b'# Parent %s\n' % hex(parents[1]))
2078 fm.data(parents=fm.formatlist(pycompat.maplist(hex, parents), name=b'node'))
2078 fm.data(parents=fm.formatlist(pycompat.maplist(hex, parents), name=b'node'))
2079
2079
2080 # TODO: redesign extraexportmap function to support formatter
2080 # TODO: redesign extraexportmap function to support formatter
2081 for headerid in extraexport:
2081 for headerid in extraexport:
2082 header = extraexportmap[headerid](seqno, ctx)
2082 header = extraexportmap[headerid](seqno, ctx)
2083 if header is not None:
2083 if header is not None:
2084 fm.plain(b'# %s\n' % header)
2084 fm.plain(b'# %s\n' % header)
2085
2085
2086 fm.write(b'desc', b'%s\n', ctx.description().rstrip())
2086 fm.write(b'desc', b'%s\n', ctx.description().rstrip())
2087 fm.plain(b'\n')
2087 fm.plain(b'\n')
2088
2088
2089 if fm.isplain():
2089 if fm.isplain():
2090 chunkiter = patch.diffui(repo, prev, node, match, opts=diffopts)
2090 chunkiter = patch.diffui(repo, prev, node, match, opts=diffopts)
2091 for chunk, label in chunkiter:
2091 for chunk, label in chunkiter:
2092 fm.plain(chunk, label=label)
2092 fm.plain(chunk, label=label)
2093 else:
2093 else:
2094 chunkiter = patch.diff(repo, prev, node, match, opts=diffopts)
2094 chunkiter = patch.diff(repo, prev, node, match, opts=diffopts)
2095 # TODO: make it structured?
2095 # TODO: make it structured?
2096 fm.data(diff=b''.join(chunkiter))
2096 fm.data(diff=b''.join(chunkiter))
2097
2097
2098
2098
2099 def _exportfile(repo, revs, fm, dest, switch_parent, diffopts, match):
2099 def _exportfile(repo, revs, fm, dest, switch_parent, diffopts, match):
2100 """Export changesets to stdout or a single file"""
2100 """Export changesets to stdout or a single file"""
2101 for seqno, rev in enumerate(revs, 1):
2101 for seqno, rev in enumerate(revs, 1):
2102 ctx = repo[rev]
2102 ctx = repo[rev]
2103 if not dest.startswith(b'<'):
2103 if not dest.startswith(b'<'):
2104 repo.ui.note(b"%s\n" % dest)
2104 repo.ui.note(b"%s\n" % dest)
2105 fm.startitem()
2105 fm.startitem()
2106 _exportsingle(repo, ctx, fm, match, switch_parent, seqno, diffopts)
2106 _exportsingle(repo, ctx, fm, match, switch_parent, seqno, diffopts)
2107
2107
2108
2108
2109 def _exportfntemplate(
2109 def _exportfntemplate(
2110 repo, revs, basefm, fntemplate, switch_parent, diffopts, match
2110 repo, revs, basefm, fntemplate, switch_parent, diffopts, match
2111 ):
2111 ):
2112 """Export changesets to possibly multiple files"""
2112 """Export changesets to possibly multiple files"""
2113 total = len(revs)
2113 total = len(revs)
2114 revwidth = max(len(str(rev)) for rev in revs)
2114 revwidth = max(len(str(rev)) for rev in revs)
2115 filemap = util.sortdict() # filename: [(seqno, rev), ...]
2115 filemap = util.sortdict() # filename: [(seqno, rev), ...]
2116
2116
2117 for seqno, rev in enumerate(revs, 1):
2117 for seqno, rev in enumerate(revs, 1):
2118 ctx = repo[rev]
2118 ctx = repo[rev]
2119 dest = makefilename(
2119 dest = makefilename(
2120 ctx, fntemplate, total=total, seqno=seqno, revwidth=revwidth
2120 ctx, fntemplate, total=total, seqno=seqno, revwidth=revwidth
2121 )
2121 )
2122 filemap.setdefault(dest, []).append((seqno, rev))
2122 filemap.setdefault(dest, []).append((seqno, rev))
2123
2123
2124 for dest in filemap:
2124 for dest in filemap:
2125 with formatter.maybereopen(basefm, dest) as fm:
2125 with formatter.maybereopen(basefm, dest) as fm:
2126 repo.ui.note(b"%s\n" % dest)
2126 repo.ui.note(b"%s\n" % dest)
2127 for seqno, rev in filemap[dest]:
2127 for seqno, rev in filemap[dest]:
2128 fm.startitem()
2128 fm.startitem()
2129 ctx = repo[rev]
2129 ctx = repo[rev]
2130 _exportsingle(
2130 _exportsingle(
2131 repo, ctx, fm, match, switch_parent, seqno, diffopts
2131 repo, ctx, fm, match, switch_parent, seqno, diffopts
2132 )
2132 )
2133
2133
2134
2134
2135 def _prefetchchangedfiles(repo, revs, match):
2135 def _prefetchchangedfiles(repo, revs, match):
2136 allfiles = set()
2136 allfiles = set()
2137 for rev in revs:
2137 for rev in revs:
2138 for file in repo[rev].files():
2138 for file in repo[rev].files():
2139 if not match or match(file):
2139 if not match or match(file):
2140 allfiles.add(file)
2140 allfiles.add(file)
2141 match = scmutil.matchfiles(repo, allfiles)
2141 match = scmutil.matchfiles(repo, allfiles)
2142 revmatches = [(rev, match) for rev in revs]
2142 revmatches = [(rev, match) for rev in revs]
2143 scmutil.prefetchfiles(repo, revmatches)
2143 scmutil.prefetchfiles(repo, revmatches)
2144
2144
2145
2145
2146 def export(
2146 def export(
2147 repo,
2147 repo,
2148 revs,
2148 revs,
2149 basefm,
2149 basefm,
2150 fntemplate=b'hg-%h.patch',
2150 fntemplate=b'hg-%h.patch',
2151 switch_parent=False,
2151 switch_parent=False,
2152 opts=None,
2152 opts=None,
2153 match=None,
2153 match=None,
2154 ):
2154 ):
2155 '''export changesets as hg patches
2155 '''export changesets as hg patches
2156
2156
2157 Args:
2157 Args:
2158 repo: The repository from which we're exporting revisions.
2158 repo: The repository from which we're exporting revisions.
2159 revs: A list of revisions to export as revision numbers.
2159 revs: A list of revisions to export as revision numbers.
2160 basefm: A formatter to which patches should be written.
2160 basefm: A formatter to which patches should be written.
2161 fntemplate: An optional string to use for generating patch file names.
2161 fntemplate: An optional string to use for generating patch file names.
2162 switch_parent: If True, show diffs against second parent when not nullid.
2162 switch_parent: If True, show diffs against second parent when not nullid.
2163 Default is false, which always shows diff against p1.
2163 Default is false, which always shows diff against p1.
2164 opts: diff options to use for generating the patch.
2164 opts: diff options to use for generating the patch.
2165 match: If specified, only export changes to files matching this matcher.
2165 match: If specified, only export changes to files matching this matcher.
2166
2166
2167 Returns:
2167 Returns:
2168 Nothing.
2168 Nothing.
2169
2169
2170 Side Effect:
2170 Side Effect:
2171 "HG Changeset Patch" data is emitted to one of the following
2171 "HG Changeset Patch" data is emitted to one of the following
2172 destinations:
2172 destinations:
2173 fntemplate specified: Each rev is written to a unique file named using
2173 fntemplate specified: Each rev is written to a unique file named using
2174 the given template.
2174 the given template.
2175 Otherwise: All revs will be written to basefm.
2175 Otherwise: All revs will be written to basefm.
2176 '''
2176 '''
2177 _prefetchchangedfiles(repo, revs, match)
2177 _prefetchchangedfiles(repo, revs, match)
2178
2178
2179 if not fntemplate:
2179 if not fntemplate:
2180 _exportfile(
2180 _exportfile(
2181 repo, revs, basefm, b'<unnamed>', switch_parent, opts, match
2181 repo, revs, basefm, b'<unnamed>', switch_parent, opts, match
2182 )
2182 )
2183 else:
2183 else:
2184 _exportfntemplate(
2184 _exportfntemplate(
2185 repo, revs, basefm, fntemplate, switch_parent, opts, match
2185 repo, revs, basefm, fntemplate, switch_parent, opts, match
2186 )
2186 )
2187
2187
2188
2188
2189 def exportfile(repo, revs, fp, switch_parent=False, opts=None, match=None):
2189 def exportfile(repo, revs, fp, switch_parent=False, opts=None, match=None):
2190 """Export changesets to the given file stream"""
2190 """Export changesets to the given file stream"""
2191 _prefetchchangedfiles(repo, revs, match)
2191 _prefetchchangedfiles(repo, revs, match)
2192
2192
2193 dest = getattr(fp, 'name', b'<unnamed>')
2193 dest = getattr(fp, 'name', b'<unnamed>')
2194 with formatter.formatter(repo.ui, fp, b'export', {}) as fm:
2194 with formatter.formatter(repo.ui, fp, b'export', {}) as fm:
2195 _exportfile(repo, revs, fm, dest, switch_parent, opts, match)
2195 _exportfile(repo, revs, fm, dest, switch_parent, opts, match)
2196
2196
2197
2197
2198 def showmarker(fm, marker, index=None):
2198 def showmarker(fm, marker, index=None):
2199 """utility function to display obsolescence marker in a readable way
2199 """utility function to display obsolescence marker in a readable way
2200
2200
2201 To be used by debug function."""
2201 To be used by debug function."""
2202 if index is not None:
2202 if index is not None:
2203 fm.write(b'index', b'%i ', index)
2203 fm.write(b'index', b'%i ', index)
2204 fm.write(b'prednode', b'%s ', hex(marker.prednode()))
2204 fm.write(b'prednode', b'%s ', hex(marker.prednode()))
2205 succs = marker.succnodes()
2205 succs = marker.succnodes()
2206 fm.condwrite(
2206 fm.condwrite(
2207 succs,
2207 succs,
2208 b'succnodes',
2208 b'succnodes',
2209 b'%s ',
2209 b'%s ',
2210 fm.formatlist(map(hex, succs), name=b'node'),
2210 fm.formatlist(map(hex, succs), name=b'node'),
2211 )
2211 )
2212 fm.write(b'flag', b'%X ', marker.flags())
2212 fm.write(b'flag', b'%X ', marker.flags())
2213 parents = marker.parentnodes()
2213 parents = marker.parentnodes()
2214 if parents is not None:
2214 if parents is not None:
2215 fm.write(
2215 fm.write(
2216 b'parentnodes',
2216 b'parentnodes',
2217 b'{%s} ',
2217 b'{%s} ',
2218 fm.formatlist(map(hex, parents), name=b'node', sep=b', '),
2218 fm.formatlist(map(hex, parents), name=b'node', sep=b', '),
2219 )
2219 )
2220 fm.write(b'date', b'(%s) ', fm.formatdate(marker.date()))
2220 fm.write(b'date', b'(%s) ', fm.formatdate(marker.date()))
2221 meta = marker.metadata().copy()
2221 meta = marker.metadata().copy()
2222 meta.pop(b'date', None)
2222 meta.pop(b'date', None)
2223 smeta = pycompat.rapply(pycompat.maybebytestr, meta)
2223 smeta = pycompat.rapply(pycompat.maybebytestr, meta)
2224 fm.write(
2224 fm.write(
2225 b'metadata', b'{%s}', fm.formatdict(smeta, fmt=b'%r: %r', sep=b', ')
2225 b'metadata', b'{%s}', fm.formatdict(smeta, fmt=b'%r: %r', sep=b', ')
2226 )
2226 )
2227 fm.plain(b'\n')
2227 fm.plain(b'\n')
2228
2228
2229
2229
2230 def finddate(ui, repo, date):
2230 def finddate(ui, repo, date):
2231 """Find the tipmost changeset that matches the given date spec"""
2231 """Find the tipmost changeset that matches the given date spec"""
2232 mrevs = repo.revs(b'date(%s)', date)
2232 mrevs = repo.revs(b'date(%s)', date)
2233 try:
2233 try:
2234 rev = mrevs.max()
2234 rev = mrevs.max()
2235 except ValueError:
2235 except ValueError:
2236 raise error.Abort(_(b"revision matching date not found"))
2236 raise error.Abort(_(b"revision matching date not found"))
2237
2237
2238 ui.status(
2238 ui.status(
2239 _(b"found revision %d from %s\n")
2239 _(b"found revision %d from %s\n")
2240 % (rev, dateutil.datestr(repo[rev].date()))
2240 % (rev, dateutil.datestr(repo[rev].date()))
2241 )
2241 )
2242 return b'%d' % rev
2242 return b'%d' % rev
2243
2243
2244
2244
2245 def increasingwindows(windowsize=8, sizelimit=512):
2245 def increasingwindows(windowsize=8, sizelimit=512):
2246 while True:
2246 while True:
2247 yield windowsize
2247 yield windowsize
2248 if windowsize < sizelimit:
2248 if windowsize < sizelimit:
2249 windowsize *= 2
2249 windowsize *= 2
2250
2250
2251
2251
2252 def _walkrevs(repo, opts):
2252 def _walkrevs(repo, opts):
2253 # Default --rev value depends on --follow but --follow behavior
2253 # Default --rev value depends on --follow but --follow behavior
2254 # depends on revisions resolved from --rev...
2254 # depends on revisions resolved from --rev...
2255 follow = opts.get(b'follow') or opts.get(b'follow_first')
2255 follow = opts.get(b'follow') or opts.get(b'follow_first')
2256 revspec = opts.get(b'rev')
2256 revspec = opts.get(b'rev')
2257 if follow and revspec:
2257 if follow and revspec:
2258 revs = scmutil.revrange(repo, revspec)
2258 revs = scmutil.revrange(repo, revspec)
2259 revs = repo.revs(b'reverse(::%ld)', revs)
2259 revs = repo.revs(b'reverse(::%ld)', revs)
2260 elif revspec:
2260 elif revspec:
2261 revs = scmutil.revrange(repo, revspec)
2261 revs = scmutil.revrange(repo, revspec)
2262 elif follow and repo.dirstate.p1() == nullid:
2262 elif follow and repo.dirstate.p1() == nullid:
2263 revs = smartset.baseset()
2263 revs = smartset.baseset()
2264 elif follow:
2264 elif follow:
2265 revs = repo.revs(b'reverse(:.)')
2265 revs = repo.revs(b'reverse(:.)')
2266 else:
2266 else:
2267 revs = smartset.spanset(repo)
2267 revs = smartset.spanset(repo)
2268 revs.reverse()
2268 revs.reverse()
2269 return revs
2269 return revs
2270
2270
2271
2271
2272 class FileWalkError(Exception):
2272 class FileWalkError(Exception):
2273 pass
2273 pass
2274
2274
2275
2275
2276 def walkfilerevs(repo, match, follow, revs, fncache):
2276 def walkfilerevs(repo, match, follow, revs, fncache):
2277 '''Walks the file history for the matched files.
2277 '''Walks the file history for the matched files.
2278
2278
2279 Returns the changeset revs that are involved in the file history.
2279 Returns the changeset revs that are involved in the file history.
2280
2280
2281 Throws FileWalkError if the file history can't be walked using
2281 Throws FileWalkError if the file history can't be walked using
2282 filelogs alone.
2282 filelogs alone.
2283 '''
2283 '''
2284 wanted = set()
2284 wanted = set()
2285 copies = []
2285 copies = []
2286 minrev, maxrev = min(revs), max(revs)
2286 minrev, maxrev = min(revs), max(revs)
2287
2287
2288 def filerevs(filelog, last):
2288 def filerevs(filelog, last):
2289 """
2289 """
2290 Only files, no patterns. Check the history of each file.
2290 Only files, no patterns. Check the history of each file.
2291
2291
2292 Examines filelog entries within minrev, maxrev linkrev range
2292 Examines filelog entries within minrev, maxrev linkrev range
2293 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
2293 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
2294 tuples in backwards order
2294 tuples in backwards order
2295 """
2295 """
2296 cl_count = len(repo)
2296 cl_count = len(repo)
2297 revs = []
2297 revs = []
2298 for j in pycompat.xrange(0, last + 1):
2298 for j in pycompat.xrange(0, last + 1):
2299 linkrev = filelog.linkrev(j)
2299 linkrev = filelog.linkrev(j)
2300 if linkrev < minrev:
2300 if linkrev < minrev:
2301 continue
2301 continue
2302 # only yield rev for which we have the changelog, it can
2302 # only yield rev for which we have the changelog, it can
2303 # happen while doing "hg log" during a pull or commit
2303 # happen while doing "hg log" during a pull or commit
2304 if linkrev >= cl_count:
2304 if linkrev >= cl_count:
2305 break
2305 break
2306
2306
2307 parentlinkrevs = []
2307 parentlinkrevs = []
2308 for p in filelog.parentrevs(j):
2308 for p in filelog.parentrevs(j):
2309 if p != nullrev:
2309 if p != nullrev:
2310 parentlinkrevs.append(filelog.linkrev(p))
2310 parentlinkrevs.append(filelog.linkrev(p))
2311 n = filelog.node(j)
2311 n = filelog.node(j)
2312 revs.append(
2312 revs.append(
2313 (linkrev, parentlinkrevs, follow and filelog.renamed(n))
2313 (linkrev, parentlinkrevs, follow and filelog.renamed(n))
2314 )
2314 )
2315
2315
2316 return reversed(revs)
2316 return reversed(revs)
2317
2317
2318 def iterfiles():
2318 def iterfiles():
2319 pctx = repo[b'.']
2319 pctx = repo[b'.']
2320 for filename in match.files():
2320 for filename in match.files():
2321 if follow:
2321 if follow:
2322 if filename not in pctx:
2322 if filename not in pctx:
2323 raise error.Abort(
2323 raise error.Abort(
2324 _(
2324 _(
2325 b'cannot follow file not in parent '
2325 b'cannot follow file not in parent '
2326 b'revision: "%s"'
2326 b'revision: "%s"'
2327 )
2327 )
2328 % filename
2328 % filename
2329 )
2329 )
2330 yield filename, pctx[filename].filenode()
2330 yield filename, pctx[filename].filenode()
2331 else:
2331 else:
2332 yield filename, None
2332 yield filename, None
2333 for filename_node in copies:
2333 for filename_node in copies:
2334 yield filename_node
2334 yield filename_node
2335
2335
2336 for file_, node in iterfiles():
2336 for file_, node in iterfiles():
2337 filelog = repo.file(file_)
2337 filelog = repo.file(file_)
2338 if not len(filelog):
2338 if not len(filelog):
2339 if node is None:
2339 if node is None:
2340 # A zero count may be a directory or deleted file, so
2340 # A zero count may be a directory or deleted file, so
2341 # try to find matching entries on the slow path.
2341 # try to find matching entries on the slow path.
2342 if follow:
2342 if follow:
2343 raise error.Abort(
2343 raise error.Abort(
2344 _(b'cannot follow nonexistent file: "%s"') % file_
2344 _(b'cannot follow nonexistent file: "%s"') % file_
2345 )
2345 )
2346 raise FileWalkError(b"Cannot walk via filelog")
2346 raise FileWalkError(b"Cannot walk via filelog")
2347 else:
2347 else:
2348 continue
2348 continue
2349
2349
2350 if node is None:
2350 if node is None:
2351 last = len(filelog) - 1
2351 last = len(filelog) - 1
2352 else:
2352 else:
2353 last = filelog.rev(node)
2353 last = filelog.rev(node)
2354
2354
2355 # keep track of all ancestors of the file
2355 # keep track of all ancestors of the file
2356 ancestors = {filelog.linkrev(last)}
2356 ancestors = {filelog.linkrev(last)}
2357
2357
2358 # iterate from latest to oldest revision
2358 # iterate from latest to oldest revision
2359 for rev, flparentlinkrevs, copied in filerevs(filelog, last):
2359 for rev, flparentlinkrevs, copied in filerevs(filelog, last):
2360 if not follow:
2360 if not follow:
2361 if rev > maxrev:
2361 if rev > maxrev:
2362 continue
2362 continue
2363 else:
2363 else:
2364 # Note that last might not be the first interesting
2364 # Note that last might not be the first interesting
2365 # rev to us:
2365 # rev to us:
2366 # if the file has been changed after maxrev, we'll
2366 # if the file has been changed after maxrev, we'll
2367 # have linkrev(last) > maxrev, and we still need
2367 # have linkrev(last) > maxrev, and we still need
2368 # to explore the file graph
2368 # to explore the file graph
2369 if rev not in ancestors:
2369 if rev not in ancestors:
2370 continue
2370 continue
2371 # XXX insert 1327 fix here
2371 # XXX insert 1327 fix here
2372 if flparentlinkrevs:
2372 if flparentlinkrevs:
2373 ancestors.update(flparentlinkrevs)
2373 ancestors.update(flparentlinkrevs)
2374
2374
2375 fncache.setdefault(rev, []).append(file_)
2375 fncache.setdefault(rev, []).append(file_)
2376 wanted.add(rev)
2376 wanted.add(rev)
2377 if copied:
2377 if copied:
2378 copies.append(copied)
2378 copies.append(copied)
2379
2379
2380 return wanted
2380 return wanted
2381
2381
2382
2382
2383 class _followfilter(object):
2383 class _followfilter(object):
2384 def __init__(self, repo, onlyfirst=False):
2384 def __init__(self, repo, onlyfirst=False):
2385 self.repo = repo
2385 self.repo = repo
2386 self.startrev = nullrev
2386 self.startrev = nullrev
2387 self.roots = set()
2387 self.roots = set()
2388 self.onlyfirst = onlyfirst
2388 self.onlyfirst = onlyfirst
2389
2389
2390 def match(self, rev):
2390 def match(self, rev):
2391 def realparents(rev):
2391 def realparents(rev):
2392 try:
2392 try:
2393 if self.onlyfirst:
2393 if self.onlyfirst:
2394 return self.repo.changelog.parentrevs(rev)[0:1]
2394 return self.repo.changelog.parentrevs(rev)[0:1]
2395 else:
2395 else:
2396 return filter(
2396 return filter(
2397 lambda x: x != nullrev,
2397 lambda x: x != nullrev,
2398 self.repo.changelog.parentrevs(rev),
2398 self.repo.changelog.parentrevs(rev),
2399 )
2399 )
2400 except error.WdirUnsupported:
2400 except error.WdirUnsupported:
2401 prevs = [p.rev() for p in self.repo[rev].parents()]
2401 prevs = [p.rev() for p in self.repo[rev].parents()]
2402 if self.onlyfirst:
2402 if self.onlyfirst:
2403 return prevs[:1]
2403 return prevs[:1]
2404 else:
2404 else:
2405 return prevs
2405 return prevs
2406
2406
2407 if self.startrev == nullrev:
2407 if self.startrev == nullrev:
2408 self.startrev = rev
2408 self.startrev = rev
2409 return True
2409 return True
2410
2410
2411 if rev > self.startrev:
2411 if rev > self.startrev:
2412 # forward: all descendants
2412 # forward: all descendants
2413 if not self.roots:
2413 if not self.roots:
2414 self.roots.add(self.startrev)
2414 self.roots.add(self.startrev)
2415 for parent in realparents(rev):
2415 for parent in realparents(rev):
2416 if parent in self.roots:
2416 if parent in self.roots:
2417 self.roots.add(rev)
2417 self.roots.add(rev)
2418 return True
2418 return True
2419 else:
2419 else:
2420 # backwards: all parents
2420 # backwards: all parents
2421 if not self.roots:
2421 if not self.roots:
2422 self.roots.update(realparents(self.startrev))
2422 self.roots.update(realparents(self.startrev))
2423 if rev in self.roots:
2423 if rev in self.roots:
2424 self.roots.remove(rev)
2424 self.roots.remove(rev)
2425 self.roots.update(realparents(rev))
2425 self.roots.update(realparents(rev))
2426 return True
2426 return True
2427
2427
2428 return False
2428 return False
2429
2429
2430
2430
2431 def walkchangerevs(repo, match, opts, prepare):
2431 def walkchangerevs(repo, match, opts, prepare):
2432 '''Iterate over files and the revs in which they changed.
2432 '''Iterate over files and the revs in which they changed.
2433
2433
2434 Callers most commonly need to iterate backwards over the history
2434 Callers most commonly need to iterate backwards over the history
2435 in which they are interested. Doing so has awful (quadratic-looking)
2435 in which they are interested. Doing so has awful (quadratic-looking)
2436 performance, so we use iterators in a "windowed" way.
2436 performance, so we use iterators in a "windowed" way.
2437
2437
2438 We walk a window of revisions in the desired order. Within the
2438 We walk a window of revisions in the desired order. Within the
2439 window, we first walk forwards to gather data, then in the desired
2439 window, we first walk forwards to gather data, then in the desired
2440 order (usually backwards) to display it.
2440 order (usually backwards) to display it.
2441
2441
2442 This function returns an iterator yielding contexts. Before
2442 This function returns an iterator yielding contexts. Before
2443 yielding each context, the iterator will first call the prepare
2443 yielding each context, the iterator will first call the prepare
2444 function on each context in the window in forward order.'''
2444 function on each context in the window in forward order.'''
2445
2445
2446 allfiles = opts.get(b'all_files')
2446 allfiles = opts.get(b'all_files')
2447 follow = opts.get(b'follow') or opts.get(b'follow_first')
2447 follow = opts.get(b'follow') or opts.get(b'follow_first')
2448 revs = _walkrevs(repo, opts)
2448 revs = _walkrevs(repo, opts)
2449 if not revs:
2449 if not revs:
2450 return []
2450 return []
2451 wanted = set()
2451 wanted = set()
2452 slowpath = match.anypats() or (not match.always() and opts.get(b'removed'))
2452 slowpath = match.anypats() or (not match.always() and opts.get(b'removed'))
2453 fncache = {}
2453 fncache = {}
2454 change = repo.__getitem__
2454 change = repo.__getitem__
2455
2455
2456 # First step is to fill wanted, the set of revisions that we want to yield.
2456 # First step is to fill wanted, the set of revisions that we want to yield.
2457 # When it does not induce extra cost, we also fill fncache for revisions in
2457 # When it does not induce extra cost, we also fill fncache for revisions in
2458 # wanted: a cache of filenames that were changed (ctx.files()) and that
2458 # wanted: a cache of filenames that were changed (ctx.files()) and that
2459 # match the file filtering conditions.
2459 # match the file filtering conditions.
2460
2460
2461 if match.always() or allfiles:
2461 if match.always() or allfiles:
2462 # No files, no patterns. Display all revs.
2462 # No files, no patterns. Display all revs.
2463 wanted = revs
2463 wanted = revs
2464 elif not slowpath:
2464 elif not slowpath:
2465 # We only have to read through the filelog to find wanted revisions
2465 # We only have to read through the filelog to find wanted revisions
2466
2466
2467 try:
2467 try:
2468 wanted = walkfilerevs(repo, match, follow, revs, fncache)
2468 wanted = walkfilerevs(repo, match, follow, revs, fncache)
2469 except FileWalkError:
2469 except FileWalkError:
2470 slowpath = True
2470 slowpath = True
2471
2471
2472 # We decided to fall back to the slowpath because at least one
2472 # We decided to fall back to the slowpath because at least one
2473 # of the paths was not a file. Check to see if at least one of them
2473 # of the paths was not a file. Check to see if at least one of them
2474 # existed in history, otherwise simply return
2474 # existed in history, otherwise simply return
2475 for path in match.files():
2475 for path in match.files():
2476 if path == b'.' or path in repo.store:
2476 if path == b'.' or path in repo.store:
2477 break
2477 break
2478 else:
2478 else:
2479 return []
2479 return []
2480
2480
2481 if slowpath:
2481 if slowpath:
2482 # We have to read the changelog to match filenames against
2482 # We have to read the changelog to match filenames against
2483 # changed files
2483 # changed files
2484
2484
2485 if follow:
2485 if follow:
2486 raise error.Abort(
2486 raise error.Abort(
2487 _(b'can only follow copies/renames for explicit filenames')
2487 _(b'can only follow copies/renames for explicit filenames')
2488 )
2488 )
2489
2489
2490 # The slow path checks files modified in every changeset.
2490 # The slow path checks files modified in every changeset.
2491 # This is really slow on large repos, so compute the set lazily.
2491 # This is really slow on large repos, so compute the set lazily.
2492 class lazywantedset(object):
2492 class lazywantedset(object):
2493 def __init__(self):
2493 def __init__(self):
2494 self.set = set()
2494 self.set = set()
2495 self.revs = set(revs)
2495 self.revs = set(revs)
2496
2496
2497 # No need to worry about locality here because it will be accessed
2497 # No need to worry about locality here because it will be accessed
2498 # in the same order as the increasing window below.
2498 # in the same order as the increasing window below.
2499 def __contains__(self, value):
2499 def __contains__(self, value):
2500 if value in self.set:
2500 if value in self.set:
2501 return True
2501 return True
2502 elif not value in self.revs:
2502 elif not value in self.revs:
2503 return False
2503 return False
2504 else:
2504 else:
2505 self.revs.discard(value)
2505 self.revs.discard(value)
2506 ctx = change(value)
2506 ctx = change(value)
2507 if allfiles:
2507 if allfiles:
2508 matches = list(ctx.manifest().walk(match))
2508 matches = list(ctx.manifest().walk(match))
2509 else:
2509 else:
2510 matches = [f for f in ctx.files() if match(f)]
2510 matches = [f for f in ctx.files() if match(f)]
2511 if matches:
2511 if matches:
2512 fncache[value] = matches
2512 fncache[value] = matches
2513 self.set.add(value)
2513 self.set.add(value)
2514 return True
2514 return True
2515 return False
2515 return False
2516
2516
2517 def discard(self, value):
2517 def discard(self, value):
2518 self.revs.discard(value)
2518 self.revs.discard(value)
2519 self.set.discard(value)
2519 self.set.discard(value)
2520
2520
2521 wanted = lazywantedset()
2521 wanted = lazywantedset()
2522
2522
2523 # it might be worthwhile to do this in the iterator if the rev range
2523 # it might be worthwhile to do this in the iterator if the rev range
2524 # is descending and the prune args are all within that range
2524 # is descending and the prune args are all within that range
2525 for rev in opts.get(b'prune', ()):
2525 for rev in opts.get(b'prune', ()):
2526 rev = repo[rev].rev()
2526 rev = repo[rev].rev()
2527 ff = _followfilter(repo)
2527 ff = _followfilter(repo)
2528 stop = min(revs[0], revs[-1])
2528 stop = min(revs[0], revs[-1])
2529 for x in pycompat.xrange(rev, stop - 1, -1):
2529 for x in pycompat.xrange(rev, stop - 1, -1):
2530 if ff.match(x):
2530 if ff.match(x):
2531 wanted = wanted - [x]
2531 wanted = wanted - [x]
2532
2532
2533 # Now that wanted is correctly initialized, we can iterate over the
2533 # Now that wanted is correctly initialized, we can iterate over the
2534 # revision range, yielding only revisions in wanted.
2534 # revision range, yielding only revisions in wanted.
2535 def iterate():
2535 def iterate():
2536 if follow and match.always():
2536 if follow and match.always():
2537 ff = _followfilter(repo, onlyfirst=opts.get(b'follow_first'))
2537 ff = _followfilter(repo, onlyfirst=opts.get(b'follow_first'))
2538
2538
2539 def want(rev):
2539 def want(rev):
2540 return ff.match(rev) and rev in wanted
2540 return ff.match(rev) and rev in wanted
2541
2541
2542 else:
2542 else:
2543
2543
2544 def want(rev):
2544 def want(rev):
2545 return rev in wanted
2545 return rev in wanted
2546
2546
2547 it = iter(revs)
2547 it = iter(revs)
2548 stopiteration = False
2548 stopiteration = False
2549 for windowsize in increasingwindows():
2549 for windowsize in increasingwindows():
2550 nrevs = []
2550 nrevs = []
2551 for i in pycompat.xrange(windowsize):
2551 for i in pycompat.xrange(windowsize):
2552 rev = next(it, None)
2552 rev = next(it, None)
2553 if rev is None:
2553 if rev is None:
2554 stopiteration = True
2554 stopiteration = True
2555 break
2555 break
2556 elif want(rev):
2556 elif want(rev):
2557 nrevs.append(rev)
2557 nrevs.append(rev)
2558 for rev in sorted(nrevs):
2558 for rev in sorted(nrevs):
2559 fns = fncache.get(rev)
2559 fns = fncache.get(rev)
2560 ctx = change(rev)
2560 ctx = change(rev)
2561 if not fns:
2561 if not fns:
2562
2562
2563 def fns_generator():
2563 def fns_generator():
2564 if allfiles:
2564 if allfiles:
2565
2565
2566 def bad(f, msg):
2566 def bad(f, msg):
2567 pass
2567 pass
2568
2568
2569 for f in ctx.matches(matchmod.badmatch(match, bad)):
2569 for f in ctx.matches(matchmod.badmatch(match, bad)):
2570 yield f
2570 yield f
2571 else:
2571 else:
2572 for f in ctx.files():
2572 for f in ctx.files():
2573 if match(f):
2573 if match(f):
2574 yield f
2574 yield f
2575
2575
2576 fns = fns_generator()
2576 fns = fns_generator()
2577 prepare(ctx, fns)
2577 prepare(ctx, scmutil.matchfiles(repo, fns))
2578 for rev in nrevs:
2578 for rev in nrevs:
2579 yield change(rev)
2579 yield change(rev)
2580
2580
2581 if stopiteration:
2581 if stopiteration:
2582 break
2582 break
2583
2583
2584 return iterate()
2584 return iterate()
2585
2585
2586
2586
2587 def add(ui, repo, match, prefix, uipathfn, explicitonly, **opts):
2587 def add(ui, repo, match, prefix, uipathfn, explicitonly, **opts):
2588 bad = []
2588 bad = []
2589
2589
2590 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2590 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2591 names = []
2591 names = []
2592 wctx = repo[None]
2592 wctx = repo[None]
2593 cca = None
2593 cca = None
2594 abort, warn = scmutil.checkportabilityalert(ui)
2594 abort, warn = scmutil.checkportabilityalert(ui)
2595 if abort or warn:
2595 if abort or warn:
2596 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
2596 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
2597
2597
2598 match = repo.narrowmatch(match, includeexact=True)
2598 match = repo.narrowmatch(match, includeexact=True)
2599 badmatch = matchmod.badmatch(match, badfn)
2599 badmatch = matchmod.badmatch(match, badfn)
2600 dirstate = repo.dirstate
2600 dirstate = repo.dirstate
2601 # We don't want to just call wctx.walk here, since it would return a lot of
2601 # We don't want to just call wctx.walk here, since it would return a lot of
2602 # clean files, which we aren't interested in and takes time.
2602 # clean files, which we aren't interested in and takes time.
2603 for f in sorted(
2603 for f in sorted(
2604 dirstate.walk(
2604 dirstate.walk(
2605 badmatch,
2605 badmatch,
2606 subrepos=sorted(wctx.substate),
2606 subrepos=sorted(wctx.substate),
2607 unknown=True,
2607 unknown=True,
2608 ignored=False,
2608 ignored=False,
2609 full=False,
2609 full=False,
2610 )
2610 )
2611 ):
2611 ):
2612 exact = match.exact(f)
2612 exact = match.exact(f)
2613 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
2613 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
2614 if cca:
2614 if cca:
2615 cca(f)
2615 cca(f)
2616 names.append(f)
2616 names.append(f)
2617 if ui.verbose or not exact:
2617 if ui.verbose or not exact:
2618 ui.status(
2618 ui.status(
2619 _(b'adding %s\n') % uipathfn(f), label=b'ui.addremove.added'
2619 _(b'adding %s\n') % uipathfn(f), label=b'ui.addremove.added'
2620 )
2620 )
2621
2621
2622 for subpath in sorted(wctx.substate):
2622 for subpath in sorted(wctx.substate):
2623 sub = wctx.sub(subpath)
2623 sub = wctx.sub(subpath)
2624 try:
2624 try:
2625 submatch = matchmod.subdirmatcher(subpath, match)
2625 submatch = matchmod.subdirmatcher(subpath, match)
2626 subprefix = repo.wvfs.reljoin(prefix, subpath)
2626 subprefix = repo.wvfs.reljoin(prefix, subpath)
2627 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2627 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2628 if opts.get('subrepos'):
2628 if opts.get('subrepos'):
2629 bad.extend(
2629 bad.extend(
2630 sub.add(ui, submatch, subprefix, subuipathfn, False, **opts)
2630 sub.add(ui, submatch, subprefix, subuipathfn, False, **opts)
2631 )
2631 )
2632 else:
2632 else:
2633 bad.extend(
2633 bad.extend(
2634 sub.add(ui, submatch, subprefix, subuipathfn, True, **opts)
2634 sub.add(ui, submatch, subprefix, subuipathfn, True, **opts)
2635 )
2635 )
2636 except error.LookupError:
2636 except error.LookupError:
2637 ui.status(
2637 ui.status(
2638 _(b"skipping missing subrepository: %s\n") % uipathfn(subpath)
2638 _(b"skipping missing subrepository: %s\n") % uipathfn(subpath)
2639 )
2639 )
2640
2640
2641 if not opts.get('dry_run'):
2641 if not opts.get('dry_run'):
2642 rejected = wctx.add(names, prefix)
2642 rejected = wctx.add(names, prefix)
2643 bad.extend(f for f in rejected if f in match.files())
2643 bad.extend(f for f in rejected if f in match.files())
2644 return bad
2644 return bad
2645
2645
2646
2646
2647 def addwebdirpath(repo, serverpath, webconf):
2647 def addwebdirpath(repo, serverpath, webconf):
2648 webconf[serverpath] = repo.root
2648 webconf[serverpath] = repo.root
2649 repo.ui.debug(b'adding %s = %s\n' % (serverpath, repo.root))
2649 repo.ui.debug(b'adding %s = %s\n' % (serverpath, repo.root))
2650
2650
2651 for r in repo.revs(b'filelog("path:.hgsub")'):
2651 for r in repo.revs(b'filelog("path:.hgsub")'):
2652 ctx = repo[r]
2652 ctx = repo[r]
2653 for subpath in ctx.substate:
2653 for subpath in ctx.substate:
2654 ctx.sub(subpath).addwebdirpath(serverpath, webconf)
2654 ctx.sub(subpath).addwebdirpath(serverpath, webconf)
2655
2655
2656
2656
2657 def forget(
2657 def forget(
2658 ui, repo, match, prefix, uipathfn, explicitonly, dryrun, interactive
2658 ui, repo, match, prefix, uipathfn, explicitonly, dryrun, interactive
2659 ):
2659 ):
2660 if dryrun and interactive:
2660 if dryrun and interactive:
2661 raise error.Abort(_(b"cannot specify both --dry-run and --interactive"))
2661 raise error.Abort(_(b"cannot specify both --dry-run and --interactive"))
2662 bad = []
2662 bad = []
2663 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2663 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2664 wctx = repo[None]
2664 wctx = repo[None]
2665 forgot = []
2665 forgot = []
2666
2666
2667 s = repo.status(match=matchmod.badmatch(match, badfn), clean=True)
2667 s = repo.status(match=matchmod.badmatch(match, badfn), clean=True)
2668 forget = sorted(s.modified + s.added + s.deleted + s.clean)
2668 forget = sorted(s.modified + s.added + s.deleted + s.clean)
2669 if explicitonly:
2669 if explicitonly:
2670 forget = [f for f in forget if match.exact(f)]
2670 forget = [f for f in forget if match.exact(f)]
2671
2671
2672 for subpath in sorted(wctx.substate):
2672 for subpath in sorted(wctx.substate):
2673 sub = wctx.sub(subpath)
2673 sub = wctx.sub(subpath)
2674 submatch = matchmod.subdirmatcher(subpath, match)
2674 submatch = matchmod.subdirmatcher(subpath, match)
2675 subprefix = repo.wvfs.reljoin(prefix, subpath)
2675 subprefix = repo.wvfs.reljoin(prefix, subpath)
2676 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2676 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2677 try:
2677 try:
2678 subbad, subforgot = sub.forget(
2678 subbad, subforgot = sub.forget(
2679 submatch,
2679 submatch,
2680 subprefix,
2680 subprefix,
2681 subuipathfn,
2681 subuipathfn,
2682 dryrun=dryrun,
2682 dryrun=dryrun,
2683 interactive=interactive,
2683 interactive=interactive,
2684 )
2684 )
2685 bad.extend([subpath + b'/' + f for f in subbad])
2685 bad.extend([subpath + b'/' + f for f in subbad])
2686 forgot.extend([subpath + b'/' + f for f in subforgot])
2686 forgot.extend([subpath + b'/' + f for f in subforgot])
2687 except error.LookupError:
2687 except error.LookupError:
2688 ui.status(
2688 ui.status(
2689 _(b"skipping missing subrepository: %s\n") % uipathfn(subpath)
2689 _(b"skipping missing subrepository: %s\n") % uipathfn(subpath)
2690 )
2690 )
2691
2691
2692 if not explicitonly:
2692 if not explicitonly:
2693 for f in match.files():
2693 for f in match.files():
2694 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2694 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2695 if f not in forgot:
2695 if f not in forgot:
2696 if repo.wvfs.exists(f):
2696 if repo.wvfs.exists(f):
2697 # Don't complain if the exact case match wasn't given.
2697 # Don't complain if the exact case match wasn't given.
2698 # But don't do this until after checking 'forgot', so
2698 # But don't do this until after checking 'forgot', so
2699 # that subrepo files aren't normalized, and this op is
2699 # that subrepo files aren't normalized, and this op is
2700 # purely from data cached by the status walk above.
2700 # purely from data cached by the status walk above.
2701 if repo.dirstate.normalize(f) in repo.dirstate:
2701 if repo.dirstate.normalize(f) in repo.dirstate:
2702 continue
2702 continue
2703 ui.warn(
2703 ui.warn(
2704 _(
2704 _(
2705 b'not removing %s: '
2705 b'not removing %s: '
2706 b'file is already untracked\n'
2706 b'file is already untracked\n'
2707 )
2707 )
2708 % uipathfn(f)
2708 % uipathfn(f)
2709 )
2709 )
2710 bad.append(f)
2710 bad.append(f)
2711
2711
2712 if interactive:
2712 if interactive:
2713 responses = _(
2713 responses = _(
2714 b'[Ynsa?]'
2714 b'[Ynsa?]'
2715 b'$$ &Yes, forget this file'
2715 b'$$ &Yes, forget this file'
2716 b'$$ &No, skip this file'
2716 b'$$ &No, skip this file'
2717 b'$$ &Skip remaining files'
2717 b'$$ &Skip remaining files'
2718 b'$$ Include &all remaining files'
2718 b'$$ Include &all remaining files'
2719 b'$$ &? (display help)'
2719 b'$$ &? (display help)'
2720 )
2720 )
2721 for filename in forget[:]:
2721 for filename in forget[:]:
2722 r = ui.promptchoice(
2722 r = ui.promptchoice(
2723 _(b'forget %s %s') % (uipathfn(filename), responses)
2723 _(b'forget %s %s') % (uipathfn(filename), responses)
2724 )
2724 )
2725 if r == 4: # ?
2725 if r == 4: # ?
2726 while r == 4:
2726 while r == 4:
2727 for c, t in ui.extractchoices(responses)[1]:
2727 for c, t in ui.extractchoices(responses)[1]:
2728 ui.write(b'%s - %s\n' % (c, encoding.lower(t)))
2728 ui.write(b'%s - %s\n' % (c, encoding.lower(t)))
2729 r = ui.promptchoice(
2729 r = ui.promptchoice(
2730 _(b'forget %s %s') % (uipathfn(filename), responses)
2730 _(b'forget %s %s') % (uipathfn(filename), responses)
2731 )
2731 )
2732 if r == 0: # yes
2732 if r == 0: # yes
2733 continue
2733 continue
2734 elif r == 1: # no
2734 elif r == 1: # no
2735 forget.remove(filename)
2735 forget.remove(filename)
2736 elif r == 2: # Skip
2736 elif r == 2: # Skip
2737 fnindex = forget.index(filename)
2737 fnindex = forget.index(filename)
2738 del forget[fnindex:]
2738 del forget[fnindex:]
2739 break
2739 break
2740 elif r == 3: # All
2740 elif r == 3: # All
2741 break
2741 break
2742
2742
2743 for f in forget:
2743 for f in forget:
2744 if ui.verbose or not match.exact(f) or interactive:
2744 if ui.verbose or not match.exact(f) or interactive:
2745 ui.status(
2745 ui.status(
2746 _(b'removing %s\n') % uipathfn(f), label=b'ui.addremove.removed'
2746 _(b'removing %s\n') % uipathfn(f), label=b'ui.addremove.removed'
2747 )
2747 )
2748
2748
2749 if not dryrun:
2749 if not dryrun:
2750 rejected = wctx.forget(forget, prefix)
2750 rejected = wctx.forget(forget, prefix)
2751 bad.extend(f for f in rejected if f in match.files())
2751 bad.extend(f for f in rejected if f in match.files())
2752 forgot.extend(f for f in forget if f not in rejected)
2752 forgot.extend(f for f in forget if f not in rejected)
2753 return bad, forgot
2753 return bad, forgot
2754
2754
2755
2755
2756 def files(ui, ctx, m, uipathfn, fm, fmt, subrepos):
2756 def files(ui, ctx, m, uipathfn, fm, fmt, subrepos):
2757 ret = 1
2757 ret = 1
2758
2758
2759 needsfctx = ui.verbose or {b'size', b'flags'} & fm.datahint()
2759 needsfctx = ui.verbose or {b'size', b'flags'} & fm.datahint()
2760 if fm.isplain() and not needsfctx:
2760 if fm.isplain() and not needsfctx:
2761 # Fast path. The speed-up comes from skipping the formatter, and batching
2761 # Fast path. The speed-up comes from skipping the formatter, and batching
2762 # calls to ui.write.
2762 # calls to ui.write.
2763 buf = []
2763 buf = []
2764 for f in ctx.matches(m):
2764 for f in ctx.matches(m):
2765 buf.append(fmt % uipathfn(f))
2765 buf.append(fmt % uipathfn(f))
2766 if len(buf) > 100:
2766 if len(buf) > 100:
2767 ui.write(b''.join(buf))
2767 ui.write(b''.join(buf))
2768 del buf[:]
2768 del buf[:]
2769 ret = 0
2769 ret = 0
2770 if buf:
2770 if buf:
2771 ui.write(b''.join(buf))
2771 ui.write(b''.join(buf))
2772 else:
2772 else:
2773 for f in ctx.matches(m):
2773 for f in ctx.matches(m):
2774 fm.startitem()
2774 fm.startitem()
2775 fm.context(ctx=ctx)
2775 fm.context(ctx=ctx)
2776 if needsfctx:
2776 if needsfctx:
2777 fc = ctx[f]
2777 fc = ctx[f]
2778 fm.write(b'size flags', b'% 10d % 1s ', fc.size(), fc.flags())
2778 fm.write(b'size flags', b'% 10d % 1s ', fc.size(), fc.flags())
2779 fm.data(path=f)
2779 fm.data(path=f)
2780 fm.plain(fmt % uipathfn(f))
2780 fm.plain(fmt % uipathfn(f))
2781 ret = 0
2781 ret = 0
2782
2782
2783 for subpath in sorted(ctx.substate):
2783 for subpath in sorted(ctx.substate):
2784 submatch = matchmod.subdirmatcher(subpath, m)
2784 submatch = matchmod.subdirmatcher(subpath, m)
2785 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2785 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2786 if subrepos or m.exact(subpath) or any(submatch.files()):
2786 if subrepos or m.exact(subpath) or any(submatch.files()):
2787 sub = ctx.sub(subpath)
2787 sub = ctx.sub(subpath)
2788 try:
2788 try:
2789 recurse = m.exact(subpath) or subrepos
2789 recurse = m.exact(subpath) or subrepos
2790 if (
2790 if (
2791 sub.printfiles(ui, submatch, subuipathfn, fm, fmt, recurse)
2791 sub.printfiles(ui, submatch, subuipathfn, fm, fmt, recurse)
2792 == 0
2792 == 0
2793 ):
2793 ):
2794 ret = 0
2794 ret = 0
2795 except error.LookupError:
2795 except error.LookupError:
2796 ui.status(
2796 ui.status(
2797 _(b"skipping missing subrepository: %s\n")
2797 _(b"skipping missing subrepository: %s\n")
2798 % uipathfn(subpath)
2798 % uipathfn(subpath)
2799 )
2799 )
2800
2800
2801 return ret
2801 return ret
2802
2802
2803
2803
2804 def remove(
2804 def remove(
2805 ui, repo, m, prefix, uipathfn, after, force, subrepos, dryrun, warnings=None
2805 ui, repo, m, prefix, uipathfn, after, force, subrepos, dryrun, warnings=None
2806 ):
2806 ):
2807 ret = 0
2807 ret = 0
2808 s = repo.status(match=m, clean=True)
2808 s = repo.status(match=m, clean=True)
2809 modified, added, deleted, clean = s.modified, s.added, s.deleted, s.clean
2809 modified, added, deleted, clean = s.modified, s.added, s.deleted, s.clean
2810
2810
2811 wctx = repo[None]
2811 wctx = repo[None]
2812
2812
2813 if warnings is None:
2813 if warnings is None:
2814 warnings = []
2814 warnings = []
2815 warn = True
2815 warn = True
2816 else:
2816 else:
2817 warn = False
2817 warn = False
2818
2818
2819 subs = sorted(wctx.substate)
2819 subs = sorted(wctx.substate)
2820 progress = ui.makeprogress(
2820 progress = ui.makeprogress(
2821 _(b'searching'), total=len(subs), unit=_(b'subrepos')
2821 _(b'searching'), total=len(subs), unit=_(b'subrepos')
2822 )
2822 )
2823 for subpath in subs:
2823 for subpath in subs:
2824 submatch = matchmod.subdirmatcher(subpath, m)
2824 submatch = matchmod.subdirmatcher(subpath, m)
2825 subprefix = repo.wvfs.reljoin(prefix, subpath)
2825 subprefix = repo.wvfs.reljoin(prefix, subpath)
2826 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2826 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2827 if subrepos or m.exact(subpath) or any(submatch.files()):
2827 if subrepos or m.exact(subpath) or any(submatch.files()):
2828 progress.increment()
2828 progress.increment()
2829 sub = wctx.sub(subpath)
2829 sub = wctx.sub(subpath)
2830 try:
2830 try:
2831 if sub.removefiles(
2831 if sub.removefiles(
2832 submatch,
2832 submatch,
2833 subprefix,
2833 subprefix,
2834 subuipathfn,
2834 subuipathfn,
2835 after,
2835 after,
2836 force,
2836 force,
2837 subrepos,
2837 subrepos,
2838 dryrun,
2838 dryrun,
2839 warnings,
2839 warnings,
2840 ):
2840 ):
2841 ret = 1
2841 ret = 1
2842 except error.LookupError:
2842 except error.LookupError:
2843 warnings.append(
2843 warnings.append(
2844 _(b"skipping missing subrepository: %s\n")
2844 _(b"skipping missing subrepository: %s\n")
2845 % uipathfn(subpath)
2845 % uipathfn(subpath)
2846 )
2846 )
2847 progress.complete()
2847 progress.complete()
2848
2848
2849 # warn about failure to delete explicit files/dirs
2849 # warn about failure to delete explicit files/dirs
2850 deleteddirs = pathutil.dirs(deleted)
2850 deleteddirs = pathutil.dirs(deleted)
2851 files = m.files()
2851 files = m.files()
2852 progress = ui.makeprogress(
2852 progress = ui.makeprogress(
2853 _(b'deleting'), total=len(files), unit=_(b'files')
2853 _(b'deleting'), total=len(files), unit=_(b'files')
2854 )
2854 )
2855 for f in files:
2855 for f in files:
2856
2856
2857 def insubrepo():
2857 def insubrepo():
2858 for subpath in wctx.substate:
2858 for subpath in wctx.substate:
2859 if f.startswith(subpath + b'/'):
2859 if f.startswith(subpath + b'/'):
2860 return True
2860 return True
2861 return False
2861 return False
2862
2862
2863 progress.increment()
2863 progress.increment()
2864 isdir = f in deleteddirs or wctx.hasdir(f)
2864 isdir = f in deleteddirs or wctx.hasdir(f)
2865 if f in repo.dirstate or isdir or f == b'.' or insubrepo() or f in subs:
2865 if f in repo.dirstate or isdir or f == b'.' or insubrepo() or f in subs:
2866 continue
2866 continue
2867
2867
2868 if repo.wvfs.exists(f):
2868 if repo.wvfs.exists(f):
2869 if repo.wvfs.isdir(f):
2869 if repo.wvfs.isdir(f):
2870 warnings.append(
2870 warnings.append(
2871 _(b'not removing %s: no tracked files\n') % uipathfn(f)
2871 _(b'not removing %s: no tracked files\n') % uipathfn(f)
2872 )
2872 )
2873 else:
2873 else:
2874 warnings.append(
2874 warnings.append(
2875 _(b'not removing %s: file is untracked\n') % uipathfn(f)
2875 _(b'not removing %s: file is untracked\n') % uipathfn(f)
2876 )
2876 )
2877 # missing files will generate a warning elsewhere
2877 # missing files will generate a warning elsewhere
2878 ret = 1
2878 ret = 1
2879 progress.complete()
2879 progress.complete()
2880
2880
2881 if force:
2881 if force:
2882 list = modified + deleted + clean + added
2882 list = modified + deleted + clean + added
2883 elif after:
2883 elif after:
2884 list = deleted
2884 list = deleted
2885 remaining = modified + added + clean
2885 remaining = modified + added + clean
2886 progress = ui.makeprogress(
2886 progress = ui.makeprogress(
2887 _(b'skipping'), total=len(remaining), unit=_(b'files')
2887 _(b'skipping'), total=len(remaining), unit=_(b'files')
2888 )
2888 )
2889 for f in remaining:
2889 for f in remaining:
2890 progress.increment()
2890 progress.increment()
2891 if ui.verbose or (f in files):
2891 if ui.verbose or (f in files):
2892 warnings.append(
2892 warnings.append(
2893 _(b'not removing %s: file still exists\n') % uipathfn(f)
2893 _(b'not removing %s: file still exists\n') % uipathfn(f)
2894 )
2894 )
2895 ret = 1
2895 ret = 1
2896 progress.complete()
2896 progress.complete()
2897 else:
2897 else:
2898 list = deleted + clean
2898 list = deleted + clean
2899 progress = ui.makeprogress(
2899 progress = ui.makeprogress(
2900 _(b'skipping'), total=(len(modified) + len(added)), unit=_(b'files')
2900 _(b'skipping'), total=(len(modified) + len(added)), unit=_(b'files')
2901 )
2901 )
2902 for f in modified:
2902 for f in modified:
2903 progress.increment()
2903 progress.increment()
2904 warnings.append(
2904 warnings.append(
2905 _(
2905 _(
2906 b'not removing %s: file is modified (use -f'
2906 b'not removing %s: file is modified (use -f'
2907 b' to force removal)\n'
2907 b' to force removal)\n'
2908 )
2908 )
2909 % uipathfn(f)
2909 % uipathfn(f)
2910 )
2910 )
2911 ret = 1
2911 ret = 1
2912 for f in added:
2912 for f in added:
2913 progress.increment()
2913 progress.increment()
2914 warnings.append(
2914 warnings.append(
2915 _(
2915 _(
2916 b"not removing %s: file has been marked for add"
2916 b"not removing %s: file has been marked for add"
2917 b" (use 'hg forget' to undo add)\n"
2917 b" (use 'hg forget' to undo add)\n"
2918 )
2918 )
2919 % uipathfn(f)
2919 % uipathfn(f)
2920 )
2920 )
2921 ret = 1
2921 ret = 1
2922 progress.complete()
2922 progress.complete()
2923
2923
2924 list = sorted(list)
2924 list = sorted(list)
2925 progress = ui.makeprogress(
2925 progress = ui.makeprogress(
2926 _(b'deleting'), total=len(list), unit=_(b'files')
2926 _(b'deleting'), total=len(list), unit=_(b'files')
2927 )
2927 )
2928 for f in list:
2928 for f in list:
2929 if ui.verbose or not m.exact(f):
2929 if ui.verbose or not m.exact(f):
2930 progress.increment()
2930 progress.increment()
2931 ui.status(
2931 ui.status(
2932 _(b'removing %s\n') % uipathfn(f), label=b'ui.addremove.removed'
2932 _(b'removing %s\n') % uipathfn(f), label=b'ui.addremove.removed'
2933 )
2933 )
2934 progress.complete()
2934 progress.complete()
2935
2935
2936 if not dryrun:
2936 if not dryrun:
2937 with repo.wlock():
2937 with repo.wlock():
2938 if not after:
2938 if not after:
2939 for f in list:
2939 for f in list:
2940 if f in added:
2940 if f in added:
2941 continue # we never unlink added files on remove
2941 continue # we never unlink added files on remove
2942 rmdir = repo.ui.configbool(
2942 rmdir = repo.ui.configbool(
2943 b'experimental', b'removeemptydirs'
2943 b'experimental', b'removeemptydirs'
2944 )
2944 )
2945 repo.wvfs.unlinkpath(f, ignoremissing=True, rmdir=rmdir)
2945 repo.wvfs.unlinkpath(f, ignoremissing=True, rmdir=rmdir)
2946 repo[None].forget(list)
2946 repo[None].forget(list)
2947
2947
2948 if warn:
2948 if warn:
2949 for warning in warnings:
2949 for warning in warnings:
2950 ui.warn(warning)
2950 ui.warn(warning)
2951
2951
2952 return ret
2952 return ret
2953
2953
2954
2954
2955 def _catfmtneedsdata(fm):
2955 def _catfmtneedsdata(fm):
2956 return not fm.datahint() or b'data' in fm.datahint()
2956 return not fm.datahint() or b'data' in fm.datahint()
2957
2957
2958
2958
2959 def _updatecatformatter(fm, ctx, matcher, path, decode):
2959 def _updatecatformatter(fm, ctx, matcher, path, decode):
2960 """Hook for adding data to the formatter used by ``hg cat``.
2960 """Hook for adding data to the formatter used by ``hg cat``.
2961
2961
2962 Extensions (e.g., lfs) can wrap this to inject keywords/data, but must call
2962 Extensions (e.g., lfs) can wrap this to inject keywords/data, but must call
2963 this method first."""
2963 this method first."""
2964
2964
2965 # data() can be expensive to fetch (e.g. lfs), so don't fetch it if it
2965 # data() can be expensive to fetch (e.g. lfs), so don't fetch it if it
2966 # wasn't requested.
2966 # wasn't requested.
2967 data = b''
2967 data = b''
2968 if _catfmtneedsdata(fm):
2968 if _catfmtneedsdata(fm):
2969 data = ctx[path].data()
2969 data = ctx[path].data()
2970 if decode:
2970 if decode:
2971 data = ctx.repo().wwritedata(path, data)
2971 data = ctx.repo().wwritedata(path, data)
2972 fm.startitem()
2972 fm.startitem()
2973 fm.context(ctx=ctx)
2973 fm.context(ctx=ctx)
2974 fm.write(b'data', b'%s', data)
2974 fm.write(b'data', b'%s', data)
2975 fm.data(path=path)
2975 fm.data(path=path)
2976
2976
2977
2977
2978 def cat(ui, repo, ctx, matcher, basefm, fntemplate, prefix, **opts):
2978 def cat(ui, repo, ctx, matcher, basefm, fntemplate, prefix, **opts):
2979 err = 1
2979 err = 1
2980 opts = pycompat.byteskwargs(opts)
2980 opts = pycompat.byteskwargs(opts)
2981
2981
2982 def write(path):
2982 def write(path):
2983 filename = None
2983 filename = None
2984 if fntemplate:
2984 if fntemplate:
2985 filename = makefilename(
2985 filename = makefilename(
2986 ctx, fntemplate, pathname=os.path.join(prefix, path)
2986 ctx, fntemplate, pathname=os.path.join(prefix, path)
2987 )
2987 )
2988 # attempt to create the directory if it does not already exist
2988 # attempt to create the directory if it does not already exist
2989 try:
2989 try:
2990 os.makedirs(os.path.dirname(filename))
2990 os.makedirs(os.path.dirname(filename))
2991 except OSError:
2991 except OSError:
2992 pass
2992 pass
2993 with formatter.maybereopen(basefm, filename) as fm:
2993 with formatter.maybereopen(basefm, filename) as fm:
2994 _updatecatformatter(fm, ctx, matcher, path, opts.get(b'decode'))
2994 _updatecatformatter(fm, ctx, matcher, path, opts.get(b'decode'))
2995
2995
2996 # Automation often uses hg cat on single files, so special case it
2996 # Automation often uses hg cat on single files, so special case it
2997 # for performance to avoid the cost of parsing the manifest.
2997 # for performance to avoid the cost of parsing the manifest.
2998 if len(matcher.files()) == 1 and not matcher.anypats():
2998 if len(matcher.files()) == 1 and not matcher.anypats():
2999 file = matcher.files()[0]
2999 file = matcher.files()[0]
3000 mfl = repo.manifestlog
3000 mfl = repo.manifestlog
3001 mfnode = ctx.manifestnode()
3001 mfnode = ctx.manifestnode()
3002 try:
3002 try:
3003 if mfnode and mfl[mfnode].find(file)[0]:
3003 if mfnode and mfl[mfnode].find(file)[0]:
3004 if _catfmtneedsdata(basefm):
3004 if _catfmtneedsdata(basefm):
3005 scmutil.prefetchfiles(repo, [(ctx.rev(), matcher)])
3005 scmutil.prefetchfiles(repo, [(ctx.rev(), matcher)])
3006 write(file)
3006 write(file)
3007 return 0
3007 return 0
3008 except KeyError:
3008 except KeyError:
3009 pass
3009 pass
3010
3010
3011 if _catfmtneedsdata(basefm):
3011 if _catfmtneedsdata(basefm):
3012 scmutil.prefetchfiles(repo, [(ctx.rev(), matcher)])
3012 scmutil.prefetchfiles(repo, [(ctx.rev(), matcher)])
3013
3013
3014 for abs in ctx.walk(matcher):
3014 for abs in ctx.walk(matcher):
3015 write(abs)
3015 write(abs)
3016 err = 0
3016 err = 0
3017
3017
3018 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
3018 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
3019 for subpath in sorted(ctx.substate):
3019 for subpath in sorted(ctx.substate):
3020 sub = ctx.sub(subpath)
3020 sub = ctx.sub(subpath)
3021 try:
3021 try:
3022 submatch = matchmod.subdirmatcher(subpath, matcher)
3022 submatch = matchmod.subdirmatcher(subpath, matcher)
3023 subprefix = os.path.join(prefix, subpath)
3023 subprefix = os.path.join(prefix, subpath)
3024 if not sub.cat(
3024 if not sub.cat(
3025 submatch,
3025 submatch,
3026 basefm,
3026 basefm,
3027 fntemplate,
3027 fntemplate,
3028 subprefix,
3028 subprefix,
3029 **pycompat.strkwargs(opts)
3029 **pycompat.strkwargs(opts)
3030 ):
3030 ):
3031 err = 0
3031 err = 0
3032 except error.RepoLookupError:
3032 except error.RepoLookupError:
3033 ui.status(
3033 ui.status(
3034 _(b"skipping missing subrepository: %s\n") % uipathfn(subpath)
3034 _(b"skipping missing subrepository: %s\n") % uipathfn(subpath)
3035 )
3035 )
3036
3036
3037 return err
3037 return err
3038
3038
3039
3039
3040 def commit(ui, repo, commitfunc, pats, opts):
3040 def commit(ui, repo, commitfunc, pats, opts):
3041 '''commit the specified files or all outstanding changes'''
3041 '''commit the specified files or all outstanding changes'''
3042 date = opts.get(b'date')
3042 date = opts.get(b'date')
3043 if date:
3043 if date:
3044 opts[b'date'] = dateutil.parsedate(date)
3044 opts[b'date'] = dateutil.parsedate(date)
3045 message = logmessage(ui, opts)
3045 message = logmessage(ui, opts)
3046 matcher = scmutil.match(repo[None], pats, opts)
3046 matcher = scmutil.match(repo[None], pats, opts)
3047
3047
3048 dsguard = None
3048 dsguard = None
3049 # extract addremove carefully -- this function can be called from a command
3049 # extract addremove carefully -- this function can be called from a command
3050 # that doesn't support addremove
3050 # that doesn't support addremove
3051 if opts.get(b'addremove'):
3051 if opts.get(b'addremove'):
3052 dsguard = dirstateguard.dirstateguard(repo, b'commit')
3052 dsguard = dirstateguard.dirstateguard(repo, b'commit')
3053 with dsguard or util.nullcontextmanager():
3053 with dsguard or util.nullcontextmanager():
3054 if dsguard:
3054 if dsguard:
3055 relative = scmutil.anypats(pats, opts)
3055 relative = scmutil.anypats(pats, opts)
3056 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
3056 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
3057 if scmutil.addremove(repo, matcher, b"", uipathfn, opts) != 0:
3057 if scmutil.addremove(repo, matcher, b"", uipathfn, opts) != 0:
3058 raise error.Abort(
3058 raise error.Abort(
3059 _(b"failed to mark all new/missing files as added/removed")
3059 _(b"failed to mark all new/missing files as added/removed")
3060 )
3060 )
3061
3061
3062 return commitfunc(ui, repo, message, matcher, opts)
3062 return commitfunc(ui, repo, message, matcher, opts)
3063
3063
3064
3064
3065 def samefile(f, ctx1, ctx2):
3065 def samefile(f, ctx1, ctx2):
3066 if f in ctx1.manifest():
3066 if f in ctx1.manifest():
3067 a = ctx1.filectx(f)
3067 a = ctx1.filectx(f)
3068 if f in ctx2.manifest():
3068 if f in ctx2.manifest():
3069 b = ctx2.filectx(f)
3069 b = ctx2.filectx(f)
3070 return not a.cmp(b) and a.flags() == b.flags()
3070 return not a.cmp(b) and a.flags() == b.flags()
3071 else:
3071 else:
3072 return False
3072 return False
3073 else:
3073 else:
3074 return f not in ctx2.manifest()
3074 return f not in ctx2.manifest()
3075
3075
3076
3076
3077 def amend(ui, repo, old, extra, pats, opts):
3077 def amend(ui, repo, old, extra, pats, opts):
3078 # avoid cycle context -> subrepo -> cmdutil
3078 # avoid cycle context -> subrepo -> cmdutil
3079 from . import context
3079 from . import context
3080
3080
3081 # amend will reuse the existing user if not specified, but the obsolete
3081 # amend will reuse the existing user if not specified, but the obsolete
3082 # marker creation requires that the current user's name is specified.
3082 # marker creation requires that the current user's name is specified.
3083 if obsolete.isenabled(repo, obsolete.createmarkersopt):
3083 if obsolete.isenabled(repo, obsolete.createmarkersopt):
3084 ui.username() # raise exception if username not set
3084 ui.username() # raise exception if username not set
3085
3085
3086 ui.note(_(b'amending changeset %s\n') % old)
3086 ui.note(_(b'amending changeset %s\n') % old)
3087 base = old.p1()
3087 base = old.p1()
3088
3088
3089 with repo.wlock(), repo.lock(), repo.transaction(b'amend'):
3089 with repo.wlock(), repo.lock(), repo.transaction(b'amend'):
3090 # Participating changesets:
3090 # Participating changesets:
3091 #
3091 #
3092 # wctx o - workingctx that contains changes from working copy
3092 # wctx o - workingctx that contains changes from working copy
3093 # | to go into amending commit
3093 # | to go into amending commit
3094 # |
3094 # |
3095 # old o - changeset to amend
3095 # old o - changeset to amend
3096 # |
3096 # |
3097 # base o - first parent of the changeset to amend
3097 # base o - first parent of the changeset to amend
3098 wctx = repo[None]
3098 wctx = repo[None]
3099
3099
3100 # Copy to avoid mutating input
3100 # Copy to avoid mutating input
3101 extra = extra.copy()
3101 extra = extra.copy()
3102 # Update extra dict from amended commit (e.g. to preserve graft
3102 # Update extra dict from amended commit (e.g. to preserve graft
3103 # source)
3103 # source)
3104 extra.update(old.extra())
3104 extra.update(old.extra())
3105
3105
3106 # Also update it from the from the wctx
3106 # Also update it from the from the wctx
3107 extra.update(wctx.extra())
3107 extra.update(wctx.extra())
3108
3108
3109 # date-only change should be ignored?
3109 # date-only change should be ignored?
3110 datemaydiffer = resolvecommitoptions(ui, opts)
3110 datemaydiffer = resolvecommitoptions(ui, opts)
3111
3111
3112 date = old.date()
3112 date = old.date()
3113 if opts.get(b'date'):
3113 if opts.get(b'date'):
3114 date = dateutil.parsedate(opts.get(b'date'))
3114 date = dateutil.parsedate(opts.get(b'date'))
3115 user = opts.get(b'user') or old.user()
3115 user = opts.get(b'user') or old.user()
3116
3116
3117 if len(old.parents()) > 1:
3117 if len(old.parents()) > 1:
3118 # ctx.files() isn't reliable for merges, so fall back to the
3118 # ctx.files() isn't reliable for merges, so fall back to the
3119 # slower repo.status() method
3119 # slower repo.status() method
3120 st = base.status(old)
3120 st = base.status(old)
3121 files = set(st.modified) | set(st.added) | set(st.removed)
3121 files = set(st.modified) | set(st.added) | set(st.removed)
3122 else:
3122 else:
3123 files = set(old.files())
3123 files = set(old.files())
3124
3124
3125 # add/remove the files to the working copy if the "addremove" option
3125 # add/remove the files to the working copy if the "addremove" option
3126 # was specified.
3126 # was specified.
3127 matcher = scmutil.match(wctx, pats, opts)
3127 matcher = scmutil.match(wctx, pats, opts)
3128 relative = scmutil.anypats(pats, opts)
3128 relative = scmutil.anypats(pats, opts)
3129 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
3129 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
3130 if opts.get(b'addremove') and scmutil.addremove(
3130 if opts.get(b'addremove') and scmutil.addremove(
3131 repo, matcher, b"", uipathfn, opts
3131 repo, matcher, b"", uipathfn, opts
3132 ):
3132 ):
3133 raise error.Abort(
3133 raise error.Abort(
3134 _(b"failed to mark all new/missing files as added/removed")
3134 _(b"failed to mark all new/missing files as added/removed")
3135 )
3135 )
3136
3136
3137 # Check subrepos. This depends on in-place wctx._status update in
3137 # Check subrepos. This depends on in-place wctx._status update in
3138 # subrepo.precommit(). To minimize the risk of this hack, we do
3138 # subrepo.precommit(). To minimize the risk of this hack, we do
3139 # nothing if .hgsub does not exist.
3139 # nothing if .hgsub does not exist.
3140 if b'.hgsub' in wctx or b'.hgsub' in old:
3140 if b'.hgsub' in wctx or b'.hgsub' in old:
3141 subs, commitsubs, newsubstate = subrepoutil.precommit(
3141 subs, commitsubs, newsubstate = subrepoutil.precommit(
3142 ui, wctx, wctx._status, matcher
3142 ui, wctx, wctx._status, matcher
3143 )
3143 )
3144 # amend should abort if commitsubrepos is enabled
3144 # amend should abort if commitsubrepos is enabled
3145 assert not commitsubs
3145 assert not commitsubs
3146 if subs:
3146 if subs:
3147 subrepoutil.writestate(repo, newsubstate)
3147 subrepoutil.writestate(repo, newsubstate)
3148
3148
3149 ms = mergestatemod.mergestate.read(repo)
3149 ms = mergestatemod.mergestate.read(repo)
3150 mergeutil.checkunresolved(ms)
3150 mergeutil.checkunresolved(ms)
3151
3151
3152 filestoamend = {f for f in wctx.files() if matcher(f)}
3152 filestoamend = {f for f in wctx.files() if matcher(f)}
3153
3153
3154 changes = len(filestoamend) > 0
3154 changes = len(filestoamend) > 0
3155 if changes:
3155 if changes:
3156 # Recompute copies (avoid recording a -> b -> a)
3156 # Recompute copies (avoid recording a -> b -> a)
3157 copied = copies.pathcopies(base, wctx, matcher)
3157 copied = copies.pathcopies(base, wctx, matcher)
3158 if old.p2:
3158 if old.p2:
3159 copied.update(copies.pathcopies(old.p2(), wctx, matcher))
3159 copied.update(copies.pathcopies(old.p2(), wctx, matcher))
3160
3160
3161 # Prune files which were reverted by the updates: if old
3161 # Prune files which were reverted by the updates: if old
3162 # introduced file X and the file was renamed in the working
3162 # introduced file X and the file was renamed in the working
3163 # copy, then those two files are the same and
3163 # copy, then those two files are the same and
3164 # we can discard X from our list of files. Likewise if X
3164 # we can discard X from our list of files. Likewise if X
3165 # was removed, it's no longer relevant. If X is missing (aka
3165 # was removed, it's no longer relevant. If X is missing (aka
3166 # deleted), old X must be preserved.
3166 # deleted), old X must be preserved.
3167 files.update(filestoamend)
3167 files.update(filestoamend)
3168 files = [
3168 files = [
3169 f
3169 f
3170 for f in files
3170 for f in files
3171 if (f not in filestoamend or not samefile(f, wctx, base))
3171 if (f not in filestoamend or not samefile(f, wctx, base))
3172 ]
3172 ]
3173
3173
3174 def filectxfn(repo, ctx_, path):
3174 def filectxfn(repo, ctx_, path):
3175 try:
3175 try:
3176 # If the file being considered is not amongst the files
3176 # If the file being considered is not amongst the files
3177 # to be amended, we should return the file context from the
3177 # to be amended, we should return the file context from the
3178 # old changeset. This avoids issues when only some files in
3178 # old changeset. This avoids issues when only some files in
3179 # the working copy are being amended but there are also
3179 # the working copy are being amended but there are also
3180 # changes to other files from the old changeset.
3180 # changes to other files from the old changeset.
3181 if path not in filestoamend:
3181 if path not in filestoamend:
3182 return old.filectx(path)
3182 return old.filectx(path)
3183
3183
3184 # Return None for removed files.
3184 # Return None for removed files.
3185 if path in wctx.removed():
3185 if path in wctx.removed():
3186 return None
3186 return None
3187
3187
3188 fctx = wctx[path]
3188 fctx = wctx[path]
3189 flags = fctx.flags()
3189 flags = fctx.flags()
3190 mctx = context.memfilectx(
3190 mctx = context.memfilectx(
3191 repo,
3191 repo,
3192 ctx_,
3192 ctx_,
3193 fctx.path(),
3193 fctx.path(),
3194 fctx.data(),
3194 fctx.data(),
3195 islink=b'l' in flags,
3195 islink=b'l' in flags,
3196 isexec=b'x' in flags,
3196 isexec=b'x' in flags,
3197 copysource=copied.get(path),
3197 copysource=copied.get(path),
3198 )
3198 )
3199 return mctx
3199 return mctx
3200 except KeyError:
3200 except KeyError:
3201 return None
3201 return None
3202
3202
3203 else:
3203 else:
3204 ui.note(_(b'copying changeset %s to %s\n') % (old, base))
3204 ui.note(_(b'copying changeset %s to %s\n') % (old, base))
3205
3205
3206 # Use version of files as in the old cset
3206 # Use version of files as in the old cset
3207 def filectxfn(repo, ctx_, path):
3207 def filectxfn(repo, ctx_, path):
3208 try:
3208 try:
3209 return old.filectx(path)
3209 return old.filectx(path)
3210 except KeyError:
3210 except KeyError:
3211 return None
3211 return None
3212
3212
3213 # See if we got a message from -m or -l, if not, open the editor with
3213 # See if we got a message from -m or -l, if not, open the editor with
3214 # the message of the changeset to amend.
3214 # the message of the changeset to amend.
3215 message = logmessage(ui, opts)
3215 message = logmessage(ui, opts)
3216
3216
3217 editform = mergeeditform(old, b'commit.amend')
3217 editform = mergeeditform(old, b'commit.amend')
3218
3218
3219 if not message:
3219 if not message:
3220 message = old.description()
3220 message = old.description()
3221 # Default if message isn't provided and --edit is not passed is to
3221 # Default if message isn't provided and --edit is not passed is to
3222 # invoke editor, but allow --no-edit. If somehow we don't have any
3222 # invoke editor, but allow --no-edit. If somehow we don't have any
3223 # description, let's always start the editor.
3223 # description, let's always start the editor.
3224 doedit = not message or opts.get(b'edit') in [True, None]
3224 doedit = not message or opts.get(b'edit') in [True, None]
3225 else:
3225 else:
3226 # Default if message is provided is to not invoke editor, but allow
3226 # Default if message is provided is to not invoke editor, but allow
3227 # --edit.
3227 # --edit.
3228 doedit = opts.get(b'edit') is True
3228 doedit = opts.get(b'edit') is True
3229 editor = getcommiteditor(edit=doedit, editform=editform)
3229 editor = getcommiteditor(edit=doedit, editform=editform)
3230
3230
3231 pureextra = extra.copy()
3231 pureextra = extra.copy()
3232 extra[b'amend_source'] = old.hex()
3232 extra[b'amend_source'] = old.hex()
3233
3233
3234 new = context.memctx(
3234 new = context.memctx(
3235 repo,
3235 repo,
3236 parents=[base.node(), old.p2().node()],
3236 parents=[base.node(), old.p2().node()],
3237 text=message,
3237 text=message,
3238 files=files,
3238 files=files,
3239 filectxfn=filectxfn,
3239 filectxfn=filectxfn,
3240 user=user,
3240 user=user,
3241 date=date,
3241 date=date,
3242 extra=extra,
3242 extra=extra,
3243 editor=editor,
3243 editor=editor,
3244 )
3244 )
3245
3245
3246 newdesc = changelog.stripdesc(new.description())
3246 newdesc = changelog.stripdesc(new.description())
3247 if (
3247 if (
3248 (not changes)
3248 (not changes)
3249 and newdesc == old.description()
3249 and newdesc == old.description()
3250 and user == old.user()
3250 and user == old.user()
3251 and (date == old.date() or datemaydiffer)
3251 and (date == old.date() or datemaydiffer)
3252 and pureextra == old.extra()
3252 and pureextra == old.extra()
3253 ):
3253 ):
3254 # nothing changed. continuing here would create a new node
3254 # nothing changed. continuing here would create a new node
3255 # anyway because of the amend_source noise.
3255 # anyway because of the amend_source noise.
3256 #
3256 #
3257 # This not what we expect from amend.
3257 # This not what we expect from amend.
3258 return old.node()
3258 return old.node()
3259
3259
3260 commitphase = None
3260 commitphase = None
3261 if opts.get(b'secret'):
3261 if opts.get(b'secret'):
3262 commitphase = phases.secret
3262 commitphase = phases.secret
3263 newid = repo.commitctx(new)
3263 newid = repo.commitctx(new)
3264 ms.reset()
3264 ms.reset()
3265
3265
3266 # Reroute the working copy parent to the new changeset
3266 # Reroute the working copy parent to the new changeset
3267 repo.setparents(newid, nullid)
3267 repo.setparents(newid, nullid)
3268 mapping = {old.node(): (newid,)}
3268 mapping = {old.node(): (newid,)}
3269 obsmetadata = None
3269 obsmetadata = None
3270 if opts.get(b'note'):
3270 if opts.get(b'note'):
3271 obsmetadata = {b'note': encoding.fromlocal(opts[b'note'])}
3271 obsmetadata = {b'note': encoding.fromlocal(opts[b'note'])}
3272 backup = ui.configbool(b'rewrite', b'backup-bundle')
3272 backup = ui.configbool(b'rewrite', b'backup-bundle')
3273 scmutil.cleanupnodes(
3273 scmutil.cleanupnodes(
3274 repo,
3274 repo,
3275 mapping,
3275 mapping,
3276 b'amend',
3276 b'amend',
3277 metadata=obsmetadata,
3277 metadata=obsmetadata,
3278 fixphase=True,
3278 fixphase=True,
3279 targetphase=commitphase,
3279 targetphase=commitphase,
3280 backup=backup,
3280 backup=backup,
3281 )
3281 )
3282
3282
3283 # Fixing the dirstate because localrepo.commitctx does not update
3283 # Fixing the dirstate because localrepo.commitctx does not update
3284 # it. This is rather convenient because we did not need to update
3284 # it. This is rather convenient because we did not need to update
3285 # the dirstate for all the files in the new commit which commitctx
3285 # the dirstate for all the files in the new commit which commitctx
3286 # could have done if it updated the dirstate. Now, we can
3286 # could have done if it updated the dirstate. Now, we can
3287 # selectively update the dirstate only for the amended files.
3287 # selectively update the dirstate only for the amended files.
3288 dirstate = repo.dirstate
3288 dirstate = repo.dirstate
3289
3289
3290 # Update the state of the files which were added and modified in the
3290 # Update the state of the files which were added and modified in the
3291 # amend to "normal" in the dirstate. We need to use "normallookup" since
3291 # amend to "normal" in the dirstate. We need to use "normallookup" since
3292 # the files may have changed since the command started; using "normal"
3292 # the files may have changed since the command started; using "normal"
3293 # would mark them as clean but with uncommitted contents.
3293 # would mark them as clean but with uncommitted contents.
3294 normalfiles = set(wctx.modified() + wctx.added()) & filestoamend
3294 normalfiles = set(wctx.modified() + wctx.added()) & filestoamend
3295 for f in normalfiles:
3295 for f in normalfiles:
3296 dirstate.normallookup(f)
3296 dirstate.normallookup(f)
3297
3297
3298 # Update the state of files which were removed in the amend
3298 # Update the state of files which were removed in the amend
3299 # to "removed" in the dirstate.
3299 # to "removed" in the dirstate.
3300 removedfiles = set(wctx.removed()) & filestoamend
3300 removedfiles = set(wctx.removed()) & filestoamend
3301 for f in removedfiles:
3301 for f in removedfiles:
3302 dirstate.drop(f)
3302 dirstate.drop(f)
3303
3303
3304 return newid
3304 return newid
3305
3305
3306
3306
3307 def commiteditor(repo, ctx, subs, editform=b''):
3307 def commiteditor(repo, ctx, subs, editform=b''):
3308 if ctx.description():
3308 if ctx.description():
3309 return ctx.description()
3309 return ctx.description()
3310 return commitforceeditor(
3310 return commitforceeditor(
3311 repo, ctx, subs, editform=editform, unchangedmessagedetection=True
3311 repo, ctx, subs, editform=editform, unchangedmessagedetection=True
3312 )
3312 )
3313
3313
3314
3314
3315 def commitforceeditor(
3315 def commitforceeditor(
3316 repo,
3316 repo,
3317 ctx,
3317 ctx,
3318 subs,
3318 subs,
3319 finishdesc=None,
3319 finishdesc=None,
3320 extramsg=None,
3320 extramsg=None,
3321 editform=b'',
3321 editform=b'',
3322 unchangedmessagedetection=False,
3322 unchangedmessagedetection=False,
3323 ):
3323 ):
3324 if not extramsg:
3324 if not extramsg:
3325 extramsg = _(b"Leave message empty to abort commit.")
3325 extramsg = _(b"Leave message empty to abort commit.")
3326
3326
3327 forms = [e for e in editform.split(b'.') if e]
3327 forms = [e for e in editform.split(b'.') if e]
3328 forms.insert(0, b'changeset')
3328 forms.insert(0, b'changeset')
3329 templatetext = None
3329 templatetext = None
3330 while forms:
3330 while forms:
3331 ref = b'.'.join(forms)
3331 ref = b'.'.join(forms)
3332 if repo.ui.config(b'committemplate', ref):
3332 if repo.ui.config(b'committemplate', ref):
3333 templatetext = committext = buildcommittemplate(
3333 templatetext = committext = buildcommittemplate(
3334 repo, ctx, subs, extramsg, ref
3334 repo, ctx, subs, extramsg, ref
3335 )
3335 )
3336 break
3336 break
3337 forms.pop()
3337 forms.pop()
3338 else:
3338 else:
3339 committext = buildcommittext(repo, ctx, subs, extramsg)
3339 committext = buildcommittext(repo, ctx, subs, extramsg)
3340
3340
3341 # run editor in the repository root
3341 # run editor in the repository root
3342 olddir = encoding.getcwd()
3342 olddir = encoding.getcwd()
3343 os.chdir(repo.root)
3343 os.chdir(repo.root)
3344
3344
3345 # make in-memory changes visible to external process
3345 # make in-memory changes visible to external process
3346 tr = repo.currenttransaction()
3346 tr = repo.currenttransaction()
3347 repo.dirstate.write(tr)
3347 repo.dirstate.write(tr)
3348 pending = tr and tr.writepending() and repo.root
3348 pending = tr and tr.writepending() and repo.root
3349
3349
3350 editortext = repo.ui.edit(
3350 editortext = repo.ui.edit(
3351 committext,
3351 committext,
3352 ctx.user(),
3352 ctx.user(),
3353 ctx.extra(),
3353 ctx.extra(),
3354 editform=editform,
3354 editform=editform,
3355 pending=pending,
3355 pending=pending,
3356 repopath=repo.path,
3356 repopath=repo.path,
3357 action=b'commit',
3357 action=b'commit',
3358 )
3358 )
3359 text = editortext
3359 text = editortext
3360
3360
3361 # strip away anything below this special string (used for editors that want
3361 # strip away anything below this special string (used for editors that want
3362 # to display the diff)
3362 # to display the diff)
3363 stripbelow = re.search(_linebelow, text, flags=re.MULTILINE)
3363 stripbelow = re.search(_linebelow, text, flags=re.MULTILINE)
3364 if stripbelow:
3364 if stripbelow:
3365 text = text[: stripbelow.start()]
3365 text = text[: stripbelow.start()]
3366
3366
3367 text = re.sub(b"(?m)^HG:.*(\n|$)", b"", text)
3367 text = re.sub(b"(?m)^HG:.*(\n|$)", b"", text)
3368 os.chdir(olddir)
3368 os.chdir(olddir)
3369
3369
3370 if finishdesc:
3370 if finishdesc:
3371 text = finishdesc(text)
3371 text = finishdesc(text)
3372 if not text.strip():
3372 if not text.strip():
3373 raise error.Abort(_(b"empty commit message"))
3373 raise error.Abort(_(b"empty commit message"))
3374 if unchangedmessagedetection and editortext == templatetext:
3374 if unchangedmessagedetection and editortext == templatetext:
3375 raise error.Abort(_(b"commit message unchanged"))
3375 raise error.Abort(_(b"commit message unchanged"))
3376
3376
3377 return text
3377 return text
3378
3378
3379
3379
3380 def buildcommittemplate(repo, ctx, subs, extramsg, ref):
3380 def buildcommittemplate(repo, ctx, subs, extramsg, ref):
3381 ui = repo.ui
3381 ui = repo.ui
3382 spec = formatter.reference_templatespec(ref)
3382 spec = formatter.reference_templatespec(ref)
3383 t = logcmdutil.changesettemplater(ui, repo, spec)
3383 t = logcmdutil.changesettemplater(ui, repo, spec)
3384 t.t.cache.update(
3384 t.t.cache.update(
3385 (k, templater.unquotestring(v))
3385 (k, templater.unquotestring(v))
3386 for k, v in repo.ui.configitems(b'committemplate')
3386 for k, v in repo.ui.configitems(b'committemplate')
3387 )
3387 )
3388
3388
3389 if not extramsg:
3389 if not extramsg:
3390 extramsg = b'' # ensure that extramsg is string
3390 extramsg = b'' # ensure that extramsg is string
3391
3391
3392 ui.pushbuffer()
3392 ui.pushbuffer()
3393 t.show(ctx, extramsg=extramsg)
3393 t.show(ctx, extramsg=extramsg)
3394 return ui.popbuffer()
3394 return ui.popbuffer()
3395
3395
3396
3396
3397 def hgprefix(msg):
3397 def hgprefix(msg):
3398 return b"\n".join([b"HG: %s" % a for a in msg.split(b"\n") if a])
3398 return b"\n".join([b"HG: %s" % a for a in msg.split(b"\n") if a])
3399
3399
3400
3400
3401 def buildcommittext(repo, ctx, subs, extramsg):
3401 def buildcommittext(repo, ctx, subs, extramsg):
3402 edittext = []
3402 edittext = []
3403 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
3403 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
3404 if ctx.description():
3404 if ctx.description():
3405 edittext.append(ctx.description())
3405 edittext.append(ctx.description())
3406 edittext.append(b"")
3406 edittext.append(b"")
3407 edittext.append(b"") # Empty line between message and comments.
3407 edittext.append(b"") # Empty line between message and comments.
3408 edittext.append(
3408 edittext.append(
3409 hgprefix(
3409 hgprefix(
3410 _(
3410 _(
3411 b"Enter commit message."
3411 b"Enter commit message."
3412 b" Lines beginning with 'HG:' are removed."
3412 b" Lines beginning with 'HG:' are removed."
3413 )
3413 )
3414 )
3414 )
3415 )
3415 )
3416 edittext.append(hgprefix(extramsg))
3416 edittext.append(hgprefix(extramsg))
3417 edittext.append(b"HG: --")
3417 edittext.append(b"HG: --")
3418 edittext.append(hgprefix(_(b"user: %s") % ctx.user()))
3418 edittext.append(hgprefix(_(b"user: %s") % ctx.user()))
3419 if ctx.p2():
3419 if ctx.p2():
3420 edittext.append(hgprefix(_(b"branch merge")))
3420 edittext.append(hgprefix(_(b"branch merge")))
3421 if ctx.branch():
3421 if ctx.branch():
3422 edittext.append(hgprefix(_(b"branch '%s'") % ctx.branch()))
3422 edittext.append(hgprefix(_(b"branch '%s'") % ctx.branch()))
3423 if bookmarks.isactivewdirparent(repo):
3423 if bookmarks.isactivewdirparent(repo):
3424 edittext.append(hgprefix(_(b"bookmark '%s'") % repo._activebookmark))
3424 edittext.append(hgprefix(_(b"bookmark '%s'") % repo._activebookmark))
3425 edittext.extend([hgprefix(_(b"subrepo %s") % s) for s in subs])
3425 edittext.extend([hgprefix(_(b"subrepo %s") % s) for s in subs])
3426 edittext.extend([hgprefix(_(b"added %s") % f) for f in added])
3426 edittext.extend([hgprefix(_(b"added %s") % f) for f in added])
3427 edittext.extend([hgprefix(_(b"changed %s") % f) for f in modified])
3427 edittext.extend([hgprefix(_(b"changed %s") % f) for f in modified])
3428 edittext.extend([hgprefix(_(b"removed %s") % f) for f in removed])
3428 edittext.extend([hgprefix(_(b"removed %s") % f) for f in removed])
3429 if not added and not modified and not removed:
3429 if not added and not modified and not removed:
3430 edittext.append(hgprefix(_(b"no files changed")))
3430 edittext.append(hgprefix(_(b"no files changed")))
3431 edittext.append(b"")
3431 edittext.append(b"")
3432
3432
3433 return b"\n".join(edittext)
3433 return b"\n".join(edittext)
3434
3434
3435
3435
3436 def commitstatus(repo, node, branch, bheads=None, opts=None):
3436 def commitstatus(repo, node, branch, bheads=None, opts=None):
3437 if opts is None:
3437 if opts is None:
3438 opts = {}
3438 opts = {}
3439 ctx = repo[node]
3439 ctx = repo[node]
3440 parents = ctx.parents()
3440 parents = ctx.parents()
3441
3441
3442 if (
3442 if (
3443 not opts.get(b'amend')
3443 not opts.get(b'amend')
3444 and bheads
3444 and bheads
3445 and node not in bheads
3445 and node not in bheads
3446 and not any(
3446 and not any(
3447 p.node() in bheads and p.branch() == branch for p in parents
3447 p.node() in bheads and p.branch() == branch for p in parents
3448 )
3448 )
3449 ):
3449 ):
3450 repo.ui.status(_(b'created new head\n'))
3450 repo.ui.status(_(b'created new head\n'))
3451 # The message is not printed for initial roots. For the other
3451 # The message is not printed for initial roots. For the other
3452 # changesets, it is printed in the following situations:
3452 # changesets, it is printed in the following situations:
3453 #
3453 #
3454 # Par column: for the 2 parents with ...
3454 # Par column: for the 2 parents with ...
3455 # N: null or no parent
3455 # N: null or no parent
3456 # B: parent is on another named branch
3456 # B: parent is on another named branch
3457 # C: parent is a regular non head changeset
3457 # C: parent is a regular non head changeset
3458 # H: parent was a branch head of the current branch
3458 # H: parent was a branch head of the current branch
3459 # Msg column: whether we print "created new head" message
3459 # Msg column: whether we print "created new head" message
3460 # In the following, it is assumed that there already exists some
3460 # In the following, it is assumed that there already exists some
3461 # initial branch heads of the current branch, otherwise nothing is
3461 # initial branch heads of the current branch, otherwise nothing is
3462 # printed anyway.
3462 # printed anyway.
3463 #
3463 #
3464 # Par Msg Comment
3464 # Par Msg Comment
3465 # N N y additional topo root
3465 # N N y additional topo root
3466 #
3466 #
3467 # B N y additional branch root
3467 # B N y additional branch root
3468 # C N y additional topo head
3468 # C N y additional topo head
3469 # H N n usual case
3469 # H N n usual case
3470 #
3470 #
3471 # B B y weird additional branch root
3471 # B B y weird additional branch root
3472 # C B y branch merge
3472 # C B y branch merge
3473 # H B n merge with named branch
3473 # H B n merge with named branch
3474 #
3474 #
3475 # C C y additional head from merge
3475 # C C y additional head from merge
3476 # C H n merge with a head
3476 # C H n merge with a head
3477 #
3477 #
3478 # H H n head merge: head count decreases
3478 # H H n head merge: head count decreases
3479
3479
3480 if not opts.get(b'close_branch'):
3480 if not opts.get(b'close_branch'):
3481 for r in parents:
3481 for r in parents:
3482 if r.closesbranch() and r.branch() == branch:
3482 if r.closesbranch() and r.branch() == branch:
3483 repo.ui.status(
3483 repo.ui.status(
3484 _(b'reopening closed branch head %d\n') % r.rev()
3484 _(b'reopening closed branch head %d\n') % r.rev()
3485 )
3485 )
3486
3486
3487 if repo.ui.debugflag:
3487 if repo.ui.debugflag:
3488 repo.ui.write(
3488 repo.ui.write(
3489 _(b'committed changeset %d:%s\n') % (ctx.rev(), ctx.hex())
3489 _(b'committed changeset %d:%s\n') % (ctx.rev(), ctx.hex())
3490 )
3490 )
3491 elif repo.ui.verbose:
3491 elif repo.ui.verbose:
3492 repo.ui.write(_(b'committed changeset %d:%s\n') % (ctx.rev(), ctx))
3492 repo.ui.write(_(b'committed changeset %d:%s\n') % (ctx.rev(), ctx))
3493
3493
3494
3494
3495 def postcommitstatus(repo, pats, opts):
3495 def postcommitstatus(repo, pats, opts):
3496 return repo.status(match=scmutil.match(repo[None], pats, opts))
3496 return repo.status(match=scmutil.match(repo[None], pats, opts))
3497
3497
3498
3498
3499 def revert(ui, repo, ctx, *pats, **opts):
3499 def revert(ui, repo, ctx, *pats, **opts):
3500 opts = pycompat.byteskwargs(opts)
3500 opts = pycompat.byteskwargs(opts)
3501 parent, p2 = repo.dirstate.parents()
3501 parent, p2 = repo.dirstate.parents()
3502 node = ctx.node()
3502 node = ctx.node()
3503
3503
3504 mf = ctx.manifest()
3504 mf = ctx.manifest()
3505 if node == p2:
3505 if node == p2:
3506 parent = p2
3506 parent = p2
3507
3507
3508 # need all matching names in dirstate and manifest of target rev,
3508 # need all matching names in dirstate and manifest of target rev,
3509 # so have to walk both. do not print errors if files exist in one
3509 # so have to walk both. do not print errors if files exist in one
3510 # but not other. in both cases, filesets should be evaluated against
3510 # but not other. in both cases, filesets should be evaluated against
3511 # workingctx to get consistent result (issue4497). this means 'set:**'
3511 # workingctx to get consistent result (issue4497). this means 'set:**'
3512 # cannot be used to select missing files from target rev.
3512 # cannot be used to select missing files from target rev.
3513
3513
3514 # `names` is a mapping for all elements in working copy and target revision
3514 # `names` is a mapping for all elements in working copy and target revision
3515 # The mapping is in the form:
3515 # The mapping is in the form:
3516 # <abs path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
3516 # <abs path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
3517 names = {}
3517 names = {}
3518 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
3518 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
3519
3519
3520 with repo.wlock():
3520 with repo.wlock():
3521 ## filling of the `names` mapping
3521 ## filling of the `names` mapping
3522 # walk dirstate to fill `names`
3522 # walk dirstate to fill `names`
3523
3523
3524 interactive = opts.get(b'interactive', False)
3524 interactive = opts.get(b'interactive', False)
3525 wctx = repo[None]
3525 wctx = repo[None]
3526 m = scmutil.match(wctx, pats, opts)
3526 m = scmutil.match(wctx, pats, opts)
3527
3527
3528 # we'll need this later
3528 # we'll need this later
3529 targetsubs = sorted(s for s in wctx.substate if m(s))
3529 targetsubs = sorted(s for s in wctx.substate if m(s))
3530
3530
3531 if not m.always():
3531 if not m.always():
3532 matcher = matchmod.badmatch(m, lambda x, y: False)
3532 matcher = matchmod.badmatch(m, lambda x, y: False)
3533 for abs in wctx.walk(matcher):
3533 for abs in wctx.walk(matcher):
3534 names[abs] = m.exact(abs)
3534 names[abs] = m.exact(abs)
3535
3535
3536 # walk target manifest to fill `names`
3536 # walk target manifest to fill `names`
3537
3537
3538 def badfn(path, msg):
3538 def badfn(path, msg):
3539 if path in names:
3539 if path in names:
3540 return
3540 return
3541 if path in ctx.substate:
3541 if path in ctx.substate:
3542 return
3542 return
3543 path_ = path + b'/'
3543 path_ = path + b'/'
3544 for f in names:
3544 for f in names:
3545 if f.startswith(path_):
3545 if f.startswith(path_):
3546 return
3546 return
3547 ui.warn(b"%s: %s\n" % (uipathfn(path), msg))
3547 ui.warn(b"%s: %s\n" % (uipathfn(path), msg))
3548
3548
3549 for abs in ctx.walk(matchmod.badmatch(m, badfn)):
3549 for abs in ctx.walk(matchmod.badmatch(m, badfn)):
3550 if abs not in names:
3550 if abs not in names:
3551 names[abs] = m.exact(abs)
3551 names[abs] = m.exact(abs)
3552
3552
3553 # Find status of all file in `names`.
3553 # Find status of all file in `names`.
3554 m = scmutil.matchfiles(repo, names)
3554 m = scmutil.matchfiles(repo, names)
3555
3555
3556 changes = repo.status(
3556 changes = repo.status(
3557 node1=node, match=m, unknown=True, ignored=True, clean=True
3557 node1=node, match=m, unknown=True, ignored=True, clean=True
3558 )
3558 )
3559 else:
3559 else:
3560 changes = repo.status(node1=node, match=m)
3560 changes = repo.status(node1=node, match=m)
3561 for kind in changes:
3561 for kind in changes:
3562 for abs in kind:
3562 for abs in kind:
3563 names[abs] = m.exact(abs)
3563 names[abs] = m.exact(abs)
3564
3564
3565 m = scmutil.matchfiles(repo, names)
3565 m = scmutil.matchfiles(repo, names)
3566
3566
3567 modified = set(changes.modified)
3567 modified = set(changes.modified)
3568 added = set(changes.added)
3568 added = set(changes.added)
3569 removed = set(changes.removed)
3569 removed = set(changes.removed)
3570 _deleted = set(changes.deleted)
3570 _deleted = set(changes.deleted)
3571 unknown = set(changes.unknown)
3571 unknown = set(changes.unknown)
3572 unknown.update(changes.ignored)
3572 unknown.update(changes.ignored)
3573 clean = set(changes.clean)
3573 clean = set(changes.clean)
3574 modadded = set()
3574 modadded = set()
3575
3575
3576 # We need to account for the state of the file in the dirstate,
3576 # We need to account for the state of the file in the dirstate,
3577 # even when we revert against something else than parent. This will
3577 # even when we revert against something else than parent. This will
3578 # slightly alter the behavior of revert (doing back up or not, delete
3578 # slightly alter the behavior of revert (doing back up or not, delete
3579 # or just forget etc).
3579 # or just forget etc).
3580 if parent == node:
3580 if parent == node:
3581 dsmodified = modified
3581 dsmodified = modified
3582 dsadded = added
3582 dsadded = added
3583 dsremoved = removed
3583 dsremoved = removed
3584 # store all local modifications, useful later for rename detection
3584 # store all local modifications, useful later for rename detection
3585 localchanges = dsmodified | dsadded
3585 localchanges = dsmodified | dsadded
3586 modified, added, removed = set(), set(), set()
3586 modified, added, removed = set(), set(), set()
3587 else:
3587 else:
3588 changes = repo.status(node1=parent, match=m)
3588 changes = repo.status(node1=parent, match=m)
3589 dsmodified = set(changes.modified)
3589 dsmodified = set(changes.modified)
3590 dsadded = set(changes.added)
3590 dsadded = set(changes.added)
3591 dsremoved = set(changes.removed)
3591 dsremoved = set(changes.removed)
3592 # store all local modifications, useful later for rename detection
3592 # store all local modifications, useful later for rename detection
3593 localchanges = dsmodified | dsadded
3593 localchanges = dsmodified | dsadded
3594
3594
3595 # only take into account for removes between wc and target
3595 # only take into account for removes between wc and target
3596 clean |= dsremoved - removed
3596 clean |= dsremoved - removed
3597 dsremoved &= removed
3597 dsremoved &= removed
3598 # distinct between dirstate remove and other
3598 # distinct between dirstate remove and other
3599 removed -= dsremoved
3599 removed -= dsremoved
3600
3600
3601 modadded = added & dsmodified
3601 modadded = added & dsmodified
3602 added -= modadded
3602 added -= modadded
3603
3603
3604 # tell newly modified apart.
3604 # tell newly modified apart.
3605 dsmodified &= modified
3605 dsmodified &= modified
3606 dsmodified |= modified & dsadded # dirstate added may need backup
3606 dsmodified |= modified & dsadded # dirstate added may need backup
3607 modified -= dsmodified
3607 modified -= dsmodified
3608
3608
3609 # We need to wait for some post-processing to update this set
3609 # We need to wait for some post-processing to update this set
3610 # before making the distinction. The dirstate will be used for
3610 # before making the distinction. The dirstate will be used for
3611 # that purpose.
3611 # that purpose.
3612 dsadded = added
3612 dsadded = added
3613
3613
3614 # in case of merge, files that are actually added can be reported as
3614 # in case of merge, files that are actually added can be reported as
3615 # modified, we need to post process the result
3615 # modified, we need to post process the result
3616 if p2 != nullid:
3616 if p2 != nullid:
3617 mergeadd = set(dsmodified)
3617 mergeadd = set(dsmodified)
3618 for path in dsmodified:
3618 for path in dsmodified:
3619 if path in mf:
3619 if path in mf:
3620 mergeadd.remove(path)
3620 mergeadd.remove(path)
3621 dsadded |= mergeadd
3621 dsadded |= mergeadd
3622 dsmodified -= mergeadd
3622 dsmodified -= mergeadd
3623
3623
3624 # if f is a rename, update `names` to also revert the source
3624 # if f is a rename, update `names` to also revert the source
3625 for f in localchanges:
3625 for f in localchanges:
3626 src = repo.dirstate.copied(f)
3626 src = repo.dirstate.copied(f)
3627 # XXX should we check for rename down to target node?
3627 # XXX should we check for rename down to target node?
3628 if src and src not in names and repo.dirstate[src] == b'r':
3628 if src and src not in names and repo.dirstate[src] == b'r':
3629 dsremoved.add(src)
3629 dsremoved.add(src)
3630 names[src] = True
3630 names[src] = True
3631
3631
3632 # determine the exact nature of the deleted changesets
3632 # determine the exact nature of the deleted changesets
3633 deladded = set(_deleted)
3633 deladded = set(_deleted)
3634 for path in _deleted:
3634 for path in _deleted:
3635 if path in mf:
3635 if path in mf:
3636 deladded.remove(path)
3636 deladded.remove(path)
3637 deleted = _deleted - deladded
3637 deleted = _deleted - deladded
3638
3638
3639 # distinguish between file to forget and the other
3639 # distinguish between file to forget and the other
3640 added = set()
3640 added = set()
3641 for abs in dsadded:
3641 for abs in dsadded:
3642 if repo.dirstate[abs] != b'a':
3642 if repo.dirstate[abs] != b'a':
3643 added.add(abs)
3643 added.add(abs)
3644 dsadded -= added
3644 dsadded -= added
3645
3645
3646 for abs in deladded:
3646 for abs in deladded:
3647 if repo.dirstate[abs] == b'a':
3647 if repo.dirstate[abs] == b'a':
3648 dsadded.add(abs)
3648 dsadded.add(abs)
3649 deladded -= dsadded
3649 deladded -= dsadded
3650
3650
3651 # For files marked as removed, we check if an unknown file is present at
3651 # For files marked as removed, we check if an unknown file is present at
3652 # the same path. If a such file exists it may need to be backed up.
3652 # the same path. If a such file exists it may need to be backed up.
3653 # Making the distinction at this stage helps have simpler backup
3653 # Making the distinction at this stage helps have simpler backup
3654 # logic.
3654 # logic.
3655 removunk = set()
3655 removunk = set()
3656 for abs in removed:
3656 for abs in removed:
3657 target = repo.wjoin(abs)
3657 target = repo.wjoin(abs)
3658 if os.path.lexists(target):
3658 if os.path.lexists(target):
3659 removunk.add(abs)
3659 removunk.add(abs)
3660 removed -= removunk
3660 removed -= removunk
3661
3661
3662 dsremovunk = set()
3662 dsremovunk = set()
3663 for abs in dsremoved:
3663 for abs in dsremoved:
3664 target = repo.wjoin(abs)
3664 target = repo.wjoin(abs)
3665 if os.path.lexists(target):
3665 if os.path.lexists(target):
3666 dsremovunk.add(abs)
3666 dsremovunk.add(abs)
3667 dsremoved -= dsremovunk
3667 dsremoved -= dsremovunk
3668
3668
3669 # action to be actually performed by revert
3669 # action to be actually performed by revert
3670 # (<list of file>, message>) tuple
3670 # (<list of file>, message>) tuple
3671 actions = {
3671 actions = {
3672 b'revert': ([], _(b'reverting %s\n')),
3672 b'revert': ([], _(b'reverting %s\n')),
3673 b'add': ([], _(b'adding %s\n')),
3673 b'add': ([], _(b'adding %s\n')),
3674 b'remove': ([], _(b'removing %s\n')),
3674 b'remove': ([], _(b'removing %s\n')),
3675 b'drop': ([], _(b'removing %s\n')),
3675 b'drop': ([], _(b'removing %s\n')),
3676 b'forget': ([], _(b'forgetting %s\n')),
3676 b'forget': ([], _(b'forgetting %s\n')),
3677 b'undelete': ([], _(b'undeleting %s\n')),
3677 b'undelete': ([], _(b'undeleting %s\n')),
3678 b'noop': (None, _(b'no changes needed to %s\n')),
3678 b'noop': (None, _(b'no changes needed to %s\n')),
3679 b'unknown': (None, _(b'file not managed: %s\n')),
3679 b'unknown': (None, _(b'file not managed: %s\n')),
3680 }
3680 }
3681
3681
3682 # "constant" that convey the backup strategy.
3682 # "constant" that convey the backup strategy.
3683 # All set to `discard` if `no-backup` is set do avoid checking
3683 # All set to `discard` if `no-backup` is set do avoid checking
3684 # no_backup lower in the code.
3684 # no_backup lower in the code.
3685 # These values are ordered for comparison purposes
3685 # These values are ordered for comparison purposes
3686 backupinteractive = 3 # do backup if interactively modified
3686 backupinteractive = 3 # do backup if interactively modified
3687 backup = 2 # unconditionally do backup
3687 backup = 2 # unconditionally do backup
3688 check = 1 # check if the existing file differs from target
3688 check = 1 # check if the existing file differs from target
3689 discard = 0 # never do backup
3689 discard = 0 # never do backup
3690 if opts.get(b'no_backup'):
3690 if opts.get(b'no_backup'):
3691 backupinteractive = backup = check = discard
3691 backupinteractive = backup = check = discard
3692 if interactive:
3692 if interactive:
3693 dsmodifiedbackup = backupinteractive
3693 dsmodifiedbackup = backupinteractive
3694 else:
3694 else:
3695 dsmodifiedbackup = backup
3695 dsmodifiedbackup = backup
3696 tobackup = set()
3696 tobackup = set()
3697
3697
3698 backupanddel = actions[b'remove']
3698 backupanddel = actions[b'remove']
3699 if not opts.get(b'no_backup'):
3699 if not opts.get(b'no_backup'):
3700 backupanddel = actions[b'drop']
3700 backupanddel = actions[b'drop']
3701
3701
3702 disptable = (
3702 disptable = (
3703 # dispatch table:
3703 # dispatch table:
3704 # file state
3704 # file state
3705 # action
3705 # action
3706 # make backup
3706 # make backup
3707 ## Sets that results that will change file on disk
3707 ## Sets that results that will change file on disk
3708 # Modified compared to target, no local change
3708 # Modified compared to target, no local change
3709 (modified, actions[b'revert'], discard),
3709 (modified, actions[b'revert'], discard),
3710 # Modified compared to target, but local file is deleted
3710 # Modified compared to target, but local file is deleted
3711 (deleted, actions[b'revert'], discard),
3711 (deleted, actions[b'revert'], discard),
3712 # Modified compared to target, local change
3712 # Modified compared to target, local change
3713 (dsmodified, actions[b'revert'], dsmodifiedbackup),
3713 (dsmodified, actions[b'revert'], dsmodifiedbackup),
3714 # Added since target
3714 # Added since target
3715 (added, actions[b'remove'], discard),
3715 (added, actions[b'remove'], discard),
3716 # Added in working directory
3716 # Added in working directory
3717 (dsadded, actions[b'forget'], discard),
3717 (dsadded, actions[b'forget'], discard),
3718 # Added since target, have local modification
3718 # Added since target, have local modification
3719 (modadded, backupanddel, backup),
3719 (modadded, backupanddel, backup),
3720 # Added since target but file is missing in working directory
3720 # Added since target but file is missing in working directory
3721 (deladded, actions[b'drop'], discard),
3721 (deladded, actions[b'drop'], discard),
3722 # Removed since target, before working copy parent
3722 # Removed since target, before working copy parent
3723 (removed, actions[b'add'], discard),
3723 (removed, actions[b'add'], discard),
3724 # Same as `removed` but an unknown file exists at the same path
3724 # Same as `removed` but an unknown file exists at the same path
3725 (removunk, actions[b'add'], check),
3725 (removunk, actions[b'add'], check),
3726 # Removed since targe, marked as such in working copy parent
3726 # Removed since targe, marked as such in working copy parent
3727 (dsremoved, actions[b'undelete'], discard),
3727 (dsremoved, actions[b'undelete'], discard),
3728 # Same as `dsremoved` but an unknown file exists at the same path
3728 # Same as `dsremoved` but an unknown file exists at the same path
3729 (dsremovunk, actions[b'undelete'], check),
3729 (dsremovunk, actions[b'undelete'], check),
3730 ## the following sets does not result in any file changes
3730 ## the following sets does not result in any file changes
3731 # File with no modification
3731 # File with no modification
3732 (clean, actions[b'noop'], discard),
3732 (clean, actions[b'noop'], discard),
3733 # Existing file, not tracked anywhere
3733 # Existing file, not tracked anywhere
3734 (unknown, actions[b'unknown'], discard),
3734 (unknown, actions[b'unknown'], discard),
3735 )
3735 )
3736
3736
3737 for abs, exact in sorted(names.items()):
3737 for abs, exact in sorted(names.items()):
3738 # target file to be touch on disk (relative to cwd)
3738 # target file to be touch on disk (relative to cwd)
3739 target = repo.wjoin(abs)
3739 target = repo.wjoin(abs)
3740 # search the entry in the dispatch table.
3740 # search the entry in the dispatch table.
3741 # if the file is in any of these sets, it was touched in the working
3741 # if the file is in any of these sets, it was touched in the working
3742 # directory parent and we are sure it needs to be reverted.
3742 # directory parent and we are sure it needs to be reverted.
3743 for table, (xlist, msg), dobackup in disptable:
3743 for table, (xlist, msg), dobackup in disptable:
3744 if abs not in table:
3744 if abs not in table:
3745 continue
3745 continue
3746 if xlist is not None:
3746 if xlist is not None:
3747 xlist.append(abs)
3747 xlist.append(abs)
3748 if dobackup:
3748 if dobackup:
3749 # If in interactive mode, don't automatically create
3749 # If in interactive mode, don't automatically create
3750 # .orig files (issue4793)
3750 # .orig files (issue4793)
3751 if dobackup == backupinteractive:
3751 if dobackup == backupinteractive:
3752 tobackup.add(abs)
3752 tobackup.add(abs)
3753 elif backup <= dobackup or wctx[abs].cmp(ctx[abs]):
3753 elif backup <= dobackup or wctx[abs].cmp(ctx[abs]):
3754 absbakname = scmutil.backuppath(ui, repo, abs)
3754 absbakname = scmutil.backuppath(ui, repo, abs)
3755 bakname = os.path.relpath(
3755 bakname = os.path.relpath(
3756 absbakname, start=repo.root
3756 absbakname, start=repo.root
3757 )
3757 )
3758 ui.note(
3758 ui.note(
3759 _(b'saving current version of %s as %s\n')
3759 _(b'saving current version of %s as %s\n')
3760 % (uipathfn(abs), uipathfn(bakname))
3760 % (uipathfn(abs), uipathfn(bakname))
3761 )
3761 )
3762 if not opts.get(b'dry_run'):
3762 if not opts.get(b'dry_run'):
3763 if interactive:
3763 if interactive:
3764 util.copyfile(target, absbakname)
3764 util.copyfile(target, absbakname)
3765 else:
3765 else:
3766 util.rename(target, absbakname)
3766 util.rename(target, absbakname)
3767 if opts.get(b'dry_run'):
3767 if opts.get(b'dry_run'):
3768 if ui.verbose or not exact:
3768 if ui.verbose or not exact:
3769 ui.status(msg % uipathfn(abs))
3769 ui.status(msg % uipathfn(abs))
3770 elif exact:
3770 elif exact:
3771 ui.warn(msg % uipathfn(abs))
3771 ui.warn(msg % uipathfn(abs))
3772 break
3772 break
3773
3773
3774 if not opts.get(b'dry_run'):
3774 if not opts.get(b'dry_run'):
3775 needdata = (b'revert', b'add', b'undelete')
3775 needdata = (b'revert', b'add', b'undelete')
3776 oplist = [actions[name][0] for name in needdata]
3776 oplist = [actions[name][0] for name in needdata]
3777 prefetch = scmutil.prefetchfiles
3777 prefetch = scmutil.prefetchfiles
3778 matchfiles = scmutil.matchfiles(
3778 matchfiles = scmutil.matchfiles(
3779 repo, [f for sublist in oplist for f in sublist]
3779 repo, [f for sublist in oplist for f in sublist]
3780 )
3780 )
3781 prefetch(
3781 prefetch(
3782 repo, [(ctx.rev(), matchfiles)],
3782 repo, [(ctx.rev(), matchfiles)],
3783 )
3783 )
3784 match = scmutil.match(repo[None], pats)
3784 match = scmutil.match(repo[None], pats)
3785 _performrevert(
3785 _performrevert(
3786 repo,
3786 repo,
3787 ctx,
3787 ctx,
3788 names,
3788 names,
3789 uipathfn,
3789 uipathfn,
3790 actions,
3790 actions,
3791 match,
3791 match,
3792 interactive,
3792 interactive,
3793 tobackup,
3793 tobackup,
3794 )
3794 )
3795
3795
3796 if targetsubs:
3796 if targetsubs:
3797 # Revert the subrepos on the revert list
3797 # Revert the subrepos on the revert list
3798 for sub in targetsubs:
3798 for sub in targetsubs:
3799 try:
3799 try:
3800 wctx.sub(sub).revert(
3800 wctx.sub(sub).revert(
3801 ctx.substate[sub], *pats, **pycompat.strkwargs(opts)
3801 ctx.substate[sub], *pats, **pycompat.strkwargs(opts)
3802 )
3802 )
3803 except KeyError:
3803 except KeyError:
3804 raise error.Abort(
3804 raise error.Abort(
3805 b"subrepository '%s' does not exist in %s!"
3805 b"subrepository '%s' does not exist in %s!"
3806 % (sub, short(ctx.node()))
3806 % (sub, short(ctx.node()))
3807 )
3807 )
3808
3808
3809
3809
3810 def _performrevert(
3810 def _performrevert(
3811 repo,
3811 repo,
3812 ctx,
3812 ctx,
3813 names,
3813 names,
3814 uipathfn,
3814 uipathfn,
3815 actions,
3815 actions,
3816 match,
3816 match,
3817 interactive=False,
3817 interactive=False,
3818 tobackup=None,
3818 tobackup=None,
3819 ):
3819 ):
3820 """function that actually perform all the actions computed for revert
3820 """function that actually perform all the actions computed for revert
3821
3821
3822 This is an independent function to let extension to plug in and react to
3822 This is an independent function to let extension to plug in and react to
3823 the imminent revert.
3823 the imminent revert.
3824
3824
3825 Make sure you have the working directory locked when calling this function.
3825 Make sure you have the working directory locked when calling this function.
3826 """
3826 """
3827 parent, p2 = repo.dirstate.parents()
3827 parent, p2 = repo.dirstate.parents()
3828 node = ctx.node()
3828 node = ctx.node()
3829 excluded_files = []
3829 excluded_files = []
3830
3830
3831 def checkout(f):
3831 def checkout(f):
3832 fc = ctx[f]
3832 fc = ctx[f]
3833 repo.wwrite(f, fc.data(), fc.flags())
3833 repo.wwrite(f, fc.data(), fc.flags())
3834
3834
3835 def doremove(f):
3835 def doremove(f):
3836 try:
3836 try:
3837 rmdir = repo.ui.configbool(b'experimental', b'removeemptydirs')
3837 rmdir = repo.ui.configbool(b'experimental', b'removeemptydirs')
3838 repo.wvfs.unlinkpath(f, rmdir=rmdir)
3838 repo.wvfs.unlinkpath(f, rmdir=rmdir)
3839 except OSError:
3839 except OSError:
3840 pass
3840 pass
3841 repo.dirstate.remove(f)
3841 repo.dirstate.remove(f)
3842
3842
3843 def prntstatusmsg(action, f):
3843 def prntstatusmsg(action, f):
3844 exact = names[f]
3844 exact = names[f]
3845 if repo.ui.verbose or not exact:
3845 if repo.ui.verbose or not exact:
3846 repo.ui.status(actions[action][1] % uipathfn(f))
3846 repo.ui.status(actions[action][1] % uipathfn(f))
3847
3847
3848 audit_path = pathutil.pathauditor(repo.root, cached=True)
3848 audit_path = pathutil.pathauditor(repo.root, cached=True)
3849 for f in actions[b'forget'][0]:
3849 for f in actions[b'forget'][0]:
3850 if interactive:
3850 if interactive:
3851 choice = repo.ui.promptchoice(
3851 choice = repo.ui.promptchoice(
3852 _(b"forget added file %s (Yn)?$$ &Yes $$ &No") % uipathfn(f)
3852 _(b"forget added file %s (Yn)?$$ &Yes $$ &No") % uipathfn(f)
3853 )
3853 )
3854 if choice == 0:
3854 if choice == 0:
3855 prntstatusmsg(b'forget', f)
3855 prntstatusmsg(b'forget', f)
3856 repo.dirstate.drop(f)
3856 repo.dirstate.drop(f)
3857 else:
3857 else:
3858 excluded_files.append(f)
3858 excluded_files.append(f)
3859 else:
3859 else:
3860 prntstatusmsg(b'forget', f)
3860 prntstatusmsg(b'forget', f)
3861 repo.dirstate.drop(f)
3861 repo.dirstate.drop(f)
3862 for f in actions[b'remove'][0]:
3862 for f in actions[b'remove'][0]:
3863 audit_path(f)
3863 audit_path(f)
3864 if interactive:
3864 if interactive:
3865 choice = repo.ui.promptchoice(
3865 choice = repo.ui.promptchoice(
3866 _(b"remove added file %s (Yn)?$$ &Yes $$ &No") % uipathfn(f)
3866 _(b"remove added file %s (Yn)?$$ &Yes $$ &No") % uipathfn(f)
3867 )
3867 )
3868 if choice == 0:
3868 if choice == 0:
3869 prntstatusmsg(b'remove', f)
3869 prntstatusmsg(b'remove', f)
3870 doremove(f)
3870 doremove(f)
3871 else:
3871 else:
3872 excluded_files.append(f)
3872 excluded_files.append(f)
3873 else:
3873 else:
3874 prntstatusmsg(b'remove', f)
3874 prntstatusmsg(b'remove', f)
3875 doremove(f)
3875 doremove(f)
3876 for f in actions[b'drop'][0]:
3876 for f in actions[b'drop'][0]:
3877 audit_path(f)
3877 audit_path(f)
3878 prntstatusmsg(b'drop', f)
3878 prntstatusmsg(b'drop', f)
3879 repo.dirstate.remove(f)
3879 repo.dirstate.remove(f)
3880
3880
3881 normal = None
3881 normal = None
3882 if node == parent:
3882 if node == parent:
3883 # We're reverting to our parent. If possible, we'd like status
3883 # We're reverting to our parent. If possible, we'd like status
3884 # to report the file as clean. We have to use normallookup for
3884 # to report the file as clean. We have to use normallookup for
3885 # merges to avoid losing information about merged/dirty files.
3885 # merges to avoid losing information about merged/dirty files.
3886 if p2 != nullid:
3886 if p2 != nullid:
3887 normal = repo.dirstate.normallookup
3887 normal = repo.dirstate.normallookup
3888 else:
3888 else:
3889 normal = repo.dirstate.normal
3889 normal = repo.dirstate.normal
3890
3890
3891 newlyaddedandmodifiedfiles = set()
3891 newlyaddedandmodifiedfiles = set()
3892 if interactive:
3892 if interactive:
3893 # Prompt the user for changes to revert
3893 # Prompt the user for changes to revert
3894 torevert = [f for f in actions[b'revert'][0] if f not in excluded_files]
3894 torevert = [f for f in actions[b'revert'][0] if f not in excluded_files]
3895 m = scmutil.matchfiles(repo, torevert)
3895 m = scmutil.matchfiles(repo, torevert)
3896 diffopts = patch.difffeatureopts(
3896 diffopts = patch.difffeatureopts(
3897 repo.ui,
3897 repo.ui,
3898 whitespace=True,
3898 whitespace=True,
3899 section=b'commands',
3899 section=b'commands',
3900 configprefix=b'revert.interactive.',
3900 configprefix=b'revert.interactive.',
3901 )
3901 )
3902 diffopts.nodates = True
3902 diffopts.nodates = True
3903 diffopts.git = True
3903 diffopts.git = True
3904 operation = b'apply'
3904 operation = b'apply'
3905 if node == parent:
3905 if node == parent:
3906 if repo.ui.configbool(
3906 if repo.ui.configbool(
3907 b'experimental', b'revert.interactive.select-to-keep'
3907 b'experimental', b'revert.interactive.select-to-keep'
3908 ):
3908 ):
3909 operation = b'keep'
3909 operation = b'keep'
3910 else:
3910 else:
3911 operation = b'discard'
3911 operation = b'discard'
3912
3912
3913 if operation == b'apply':
3913 if operation == b'apply':
3914 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
3914 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
3915 else:
3915 else:
3916 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
3916 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
3917 originalchunks = patch.parsepatch(diff)
3917 originalchunks = patch.parsepatch(diff)
3918
3918
3919 try:
3919 try:
3920
3920
3921 chunks, opts = recordfilter(
3921 chunks, opts = recordfilter(
3922 repo.ui, originalchunks, match, operation=operation
3922 repo.ui, originalchunks, match, operation=operation
3923 )
3923 )
3924 if operation == b'discard':
3924 if operation == b'discard':
3925 chunks = patch.reversehunks(chunks)
3925 chunks = patch.reversehunks(chunks)
3926
3926
3927 except error.PatchError as err:
3927 except error.PatchError as err:
3928 raise error.Abort(_(b'error parsing patch: %s') % err)
3928 raise error.Abort(_(b'error parsing patch: %s') % err)
3929
3929
3930 # FIXME: when doing an interactive revert of a copy, there's no way of
3930 # FIXME: when doing an interactive revert of a copy, there's no way of
3931 # performing a partial revert of the added file, the only option is
3931 # performing a partial revert of the added file, the only option is
3932 # "remove added file <name> (Yn)?", so we don't need to worry about the
3932 # "remove added file <name> (Yn)?", so we don't need to worry about the
3933 # alsorestore value. Ideally we'd be able to partially revert
3933 # alsorestore value. Ideally we'd be able to partially revert
3934 # copied/renamed files.
3934 # copied/renamed files.
3935 newlyaddedandmodifiedfiles, unusedalsorestore = newandmodified(
3935 newlyaddedandmodifiedfiles, unusedalsorestore = newandmodified(
3936 chunks, originalchunks
3936 chunks, originalchunks
3937 )
3937 )
3938 if tobackup is None:
3938 if tobackup is None:
3939 tobackup = set()
3939 tobackup = set()
3940 # Apply changes
3940 # Apply changes
3941 fp = stringio()
3941 fp = stringio()
3942 # chunks are serialized per file, but files aren't sorted
3942 # chunks are serialized per file, but files aren't sorted
3943 for f in sorted({c.header.filename() for c in chunks if ishunk(c)}):
3943 for f in sorted({c.header.filename() for c in chunks if ishunk(c)}):
3944 prntstatusmsg(b'revert', f)
3944 prntstatusmsg(b'revert', f)
3945 files = set()
3945 files = set()
3946 for c in chunks:
3946 for c in chunks:
3947 if ishunk(c):
3947 if ishunk(c):
3948 abs = c.header.filename()
3948 abs = c.header.filename()
3949 # Create a backup file only if this hunk should be backed up
3949 # Create a backup file only if this hunk should be backed up
3950 if c.header.filename() in tobackup:
3950 if c.header.filename() in tobackup:
3951 target = repo.wjoin(abs)
3951 target = repo.wjoin(abs)
3952 bakname = scmutil.backuppath(repo.ui, repo, abs)
3952 bakname = scmutil.backuppath(repo.ui, repo, abs)
3953 util.copyfile(target, bakname)
3953 util.copyfile(target, bakname)
3954 tobackup.remove(abs)
3954 tobackup.remove(abs)
3955 if abs not in files:
3955 if abs not in files:
3956 files.add(abs)
3956 files.add(abs)
3957 if operation == b'keep':
3957 if operation == b'keep':
3958 checkout(abs)
3958 checkout(abs)
3959 c.write(fp)
3959 c.write(fp)
3960 dopatch = fp.tell()
3960 dopatch = fp.tell()
3961 fp.seek(0)
3961 fp.seek(0)
3962 if dopatch:
3962 if dopatch:
3963 try:
3963 try:
3964 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3964 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3965 except error.PatchError as err:
3965 except error.PatchError as err:
3966 raise error.Abort(pycompat.bytestr(err))
3966 raise error.Abort(pycompat.bytestr(err))
3967 del fp
3967 del fp
3968 else:
3968 else:
3969 for f in actions[b'revert'][0]:
3969 for f in actions[b'revert'][0]:
3970 prntstatusmsg(b'revert', f)
3970 prntstatusmsg(b'revert', f)
3971 checkout(f)
3971 checkout(f)
3972 if normal:
3972 if normal:
3973 normal(f)
3973 normal(f)
3974
3974
3975 for f in actions[b'add'][0]:
3975 for f in actions[b'add'][0]:
3976 # Don't checkout modified files, they are already created by the diff
3976 # Don't checkout modified files, they are already created by the diff
3977 if f not in newlyaddedandmodifiedfiles:
3977 if f not in newlyaddedandmodifiedfiles:
3978 prntstatusmsg(b'add', f)
3978 prntstatusmsg(b'add', f)
3979 checkout(f)
3979 checkout(f)
3980 repo.dirstate.add(f)
3980 repo.dirstate.add(f)
3981
3981
3982 normal = repo.dirstate.normallookup
3982 normal = repo.dirstate.normallookup
3983 if node == parent and p2 == nullid:
3983 if node == parent and p2 == nullid:
3984 normal = repo.dirstate.normal
3984 normal = repo.dirstate.normal
3985 for f in actions[b'undelete'][0]:
3985 for f in actions[b'undelete'][0]:
3986 if interactive:
3986 if interactive:
3987 choice = repo.ui.promptchoice(
3987 choice = repo.ui.promptchoice(
3988 _(b"add back removed file %s (Yn)?$$ &Yes $$ &No") % f
3988 _(b"add back removed file %s (Yn)?$$ &Yes $$ &No") % f
3989 )
3989 )
3990 if choice == 0:
3990 if choice == 0:
3991 prntstatusmsg(b'undelete', f)
3991 prntstatusmsg(b'undelete', f)
3992 checkout(f)
3992 checkout(f)
3993 normal(f)
3993 normal(f)
3994 else:
3994 else:
3995 excluded_files.append(f)
3995 excluded_files.append(f)
3996 else:
3996 else:
3997 prntstatusmsg(b'undelete', f)
3997 prntstatusmsg(b'undelete', f)
3998 checkout(f)
3998 checkout(f)
3999 normal(f)
3999 normal(f)
4000
4000
4001 copied = copies.pathcopies(repo[parent], ctx)
4001 copied = copies.pathcopies(repo[parent], ctx)
4002
4002
4003 for f in (
4003 for f in (
4004 actions[b'add'][0] + actions[b'undelete'][0] + actions[b'revert'][0]
4004 actions[b'add'][0] + actions[b'undelete'][0] + actions[b'revert'][0]
4005 ):
4005 ):
4006 if f in copied:
4006 if f in copied:
4007 repo.dirstate.copy(copied[f], f)
4007 repo.dirstate.copy(copied[f], f)
4008
4008
4009
4009
4010 # a list of (ui, repo, otherpeer, opts, missing) functions called by
4010 # a list of (ui, repo, otherpeer, opts, missing) functions called by
4011 # commands.outgoing. "missing" is "missing" of the result of
4011 # commands.outgoing. "missing" is "missing" of the result of
4012 # "findcommonoutgoing()"
4012 # "findcommonoutgoing()"
4013 outgoinghooks = util.hooks()
4013 outgoinghooks = util.hooks()
4014
4014
4015 # a list of (ui, repo) functions called by commands.summary
4015 # a list of (ui, repo) functions called by commands.summary
4016 summaryhooks = util.hooks()
4016 summaryhooks = util.hooks()
4017
4017
4018 # a list of (ui, repo, opts, changes) functions called by commands.summary.
4018 # a list of (ui, repo, opts, changes) functions called by commands.summary.
4019 #
4019 #
4020 # functions should return tuple of booleans below, if 'changes' is None:
4020 # functions should return tuple of booleans below, if 'changes' is None:
4021 # (whether-incomings-are-needed, whether-outgoings-are-needed)
4021 # (whether-incomings-are-needed, whether-outgoings-are-needed)
4022 #
4022 #
4023 # otherwise, 'changes' is a tuple of tuples below:
4023 # otherwise, 'changes' is a tuple of tuples below:
4024 # - (sourceurl, sourcebranch, sourcepeer, incoming)
4024 # - (sourceurl, sourcebranch, sourcepeer, incoming)
4025 # - (desturl, destbranch, destpeer, outgoing)
4025 # - (desturl, destbranch, destpeer, outgoing)
4026 summaryremotehooks = util.hooks()
4026 summaryremotehooks = util.hooks()
4027
4027
4028
4028
4029 def checkunfinished(repo, commit=False, skipmerge=False):
4029 def checkunfinished(repo, commit=False, skipmerge=False):
4030 '''Look for an unfinished multistep operation, like graft, and abort
4030 '''Look for an unfinished multistep operation, like graft, and abort
4031 if found. It's probably good to check this right before
4031 if found. It's probably good to check this right before
4032 bailifchanged().
4032 bailifchanged().
4033 '''
4033 '''
4034 # Check for non-clearable states first, so things like rebase will take
4034 # Check for non-clearable states first, so things like rebase will take
4035 # precedence over update.
4035 # precedence over update.
4036 for state in statemod._unfinishedstates:
4036 for state in statemod._unfinishedstates:
4037 if (
4037 if (
4038 state._clearable
4038 state._clearable
4039 or (commit and state._allowcommit)
4039 or (commit and state._allowcommit)
4040 or state._reportonly
4040 or state._reportonly
4041 ):
4041 ):
4042 continue
4042 continue
4043 if state.isunfinished(repo):
4043 if state.isunfinished(repo):
4044 raise error.Abort(state.msg(), hint=state.hint())
4044 raise error.Abort(state.msg(), hint=state.hint())
4045
4045
4046 for s in statemod._unfinishedstates:
4046 for s in statemod._unfinishedstates:
4047 if (
4047 if (
4048 not s._clearable
4048 not s._clearable
4049 or (commit and s._allowcommit)
4049 or (commit and s._allowcommit)
4050 or (s._opname == b'merge' and skipmerge)
4050 or (s._opname == b'merge' and skipmerge)
4051 or s._reportonly
4051 or s._reportonly
4052 ):
4052 ):
4053 continue
4053 continue
4054 if s.isunfinished(repo):
4054 if s.isunfinished(repo):
4055 raise error.Abort(s.msg(), hint=s.hint())
4055 raise error.Abort(s.msg(), hint=s.hint())
4056
4056
4057
4057
4058 def clearunfinished(repo):
4058 def clearunfinished(repo):
4059 '''Check for unfinished operations (as above), and clear the ones
4059 '''Check for unfinished operations (as above), and clear the ones
4060 that are clearable.
4060 that are clearable.
4061 '''
4061 '''
4062 for state in statemod._unfinishedstates:
4062 for state in statemod._unfinishedstates:
4063 if state._reportonly:
4063 if state._reportonly:
4064 continue
4064 continue
4065 if not state._clearable and state.isunfinished(repo):
4065 if not state._clearable and state.isunfinished(repo):
4066 raise error.Abort(state.msg(), hint=state.hint())
4066 raise error.Abort(state.msg(), hint=state.hint())
4067
4067
4068 for s in statemod._unfinishedstates:
4068 for s in statemod._unfinishedstates:
4069 if s._opname == b'merge' or state._reportonly:
4069 if s._opname == b'merge' or state._reportonly:
4070 continue
4070 continue
4071 if s._clearable and s.isunfinished(repo):
4071 if s._clearable and s.isunfinished(repo):
4072 util.unlink(repo.vfs.join(s._fname))
4072 util.unlink(repo.vfs.join(s._fname))
4073
4073
4074
4074
4075 def getunfinishedstate(repo):
4075 def getunfinishedstate(repo):
4076 ''' Checks for unfinished operations and returns statecheck object
4076 ''' Checks for unfinished operations and returns statecheck object
4077 for it'''
4077 for it'''
4078 for state in statemod._unfinishedstates:
4078 for state in statemod._unfinishedstates:
4079 if state.isunfinished(repo):
4079 if state.isunfinished(repo):
4080 return state
4080 return state
4081 return None
4081 return None
4082
4082
4083
4083
4084 def howtocontinue(repo):
4084 def howtocontinue(repo):
4085 '''Check for an unfinished operation and return the command to finish
4085 '''Check for an unfinished operation and return the command to finish
4086 it.
4086 it.
4087
4087
4088 statemod._unfinishedstates list is checked for an unfinished operation
4088 statemod._unfinishedstates list is checked for an unfinished operation
4089 and the corresponding message to finish it is generated if a method to
4089 and the corresponding message to finish it is generated if a method to
4090 continue is supported by the operation.
4090 continue is supported by the operation.
4091
4091
4092 Returns a (msg, warning) tuple. 'msg' is a string and 'warning' is
4092 Returns a (msg, warning) tuple. 'msg' is a string and 'warning' is
4093 a boolean.
4093 a boolean.
4094 '''
4094 '''
4095 contmsg = _(b"continue: %s")
4095 contmsg = _(b"continue: %s")
4096 for state in statemod._unfinishedstates:
4096 for state in statemod._unfinishedstates:
4097 if not state._continueflag:
4097 if not state._continueflag:
4098 continue
4098 continue
4099 if state.isunfinished(repo):
4099 if state.isunfinished(repo):
4100 return contmsg % state.continuemsg(), True
4100 return contmsg % state.continuemsg(), True
4101 if repo[None].dirty(missing=True, merge=False, branch=False):
4101 if repo[None].dirty(missing=True, merge=False, branch=False):
4102 return contmsg % _(b"hg commit"), False
4102 return contmsg % _(b"hg commit"), False
4103 return None, None
4103 return None, None
4104
4104
4105
4105
4106 def checkafterresolved(repo):
4106 def checkafterresolved(repo):
4107 '''Inform the user about the next action after completing hg resolve
4107 '''Inform the user about the next action after completing hg resolve
4108
4108
4109 If there's a an unfinished operation that supports continue flag,
4109 If there's a an unfinished operation that supports continue flag,
4110 howtocontinue will yield repo.ui.warn as the reporter.
4110 howtocontinue will yield repo.ui.warn as the reporter.
4111
4111
4112 Otherwise, it will yield repo.ui.note.
4112 Otherwise, it will yield repo.ui.note.
4113 '''
4113 '''
4114 msg, warning = howtocontinue(repo)
4114 msg, warning = howtocontinue(repo)
4115 if msg is not None:
4115 if msg is not None:
4116 if warning:
4116 if warning:
4117 repo.ui.warn(b"%s\n" % msg)
4117 repo.ui.warn(b"%s\n" % msg)
4118 else:
4118 else:
4119 repo.ui.note(b"%s\n" % msg)
4119 repo.ui.note(b"%s\n" % msg)
4120
4120
4121
4121
4122 def wrongtooltocontinue(repo, task):
4122 def wrongtooltocontinue(repo, task):
4123 '''Raise an abort suggesting how to properly continue if there is an
4123 '''Raise an abort suggesting how to properly continue if there is an
4124 active task.
4124 active task.
4125
4125
4126 Uses howtocontinue() to find the active task.
4126 Uses howtocontinue() to find the active task.
4127
4127
4128 If there's no task (repo.ui.note for 'hg commit'), it does not offer
4128 If there's no task (repo.ui.note for 'hg commit'), it does not offer
4129 a hint.
4129 a hint.
4130 '''
4130 '''
4131 after = howtocontinue(repo)
4131 after = howtocontinue(repo)
4132 hint = None
4132 hint = None
4133 if after[1]:
4133 if after[1]:
4134 hint = after[0]
4134 hint = after[0]
4135 raise error.Abort(_(b'no %s in progress') % task, hint=hint)
4135 raise error.Abort(_(b'no %s in progress') % task, hint=hint)
4136
4136
4137
4137
4138 def abortgraft(ui, repo, graftstate):
4138 def abortgraft(ui, repo, graftstate):
4139 """abort the interrupted graft and rollbacks to the state before interrupted
4139 """abort the interrupted graft and rollbacks to the state before interrupted
4140 graft"""
4140 graft"""
4141 if not graftstate.exists():
4141 if not graftstate.exists():
4142 raise error.Abort(_(b"no interrupted graft to abort"))
4142 raise error.Abort(_(b"no interrupted graft to abort"))
4143 statedata = readgraftstate(repo, graftstate)
4143 statedata = readgraftstate(repo, graftstate)
4144 newnodes = statedata.get(b'newnodes')
4144 newnodes = statedata.get(b'newnodes')
4145 if newnodes is None:
4145 if newnodes is None:
4146 # and old graft state which does not have all the data required to abort
4146 # and old graft state which does not have all the data required to abort
4147 # the graft
4147 # the graft
4148 raise error.Abort(_(b"cannot abort using an old graftstate"))
4148 raise error.Abort(_(b"cannot abort using an old graftstate"))
4149
4149
4150 # changeset from which graft operation was started
4150 # changeset from which graft operation was started
4151 if len(newnodes) > 0:
4151 if len(newnodes) > 0:
4152 startctx = repo[newnodes[0]].p1()
4152 startctx = repo[newnodes[0]].p1()
4153 else:
4153 else:
4154 startctx = repo[b'.']
4154 startctx = repo[b'.']
4155 # whether to strip or not
4155 # whether to strip or not
4156 cleanup = False
4156 cleanup = False
4157
4157
4158 if newnodes:
4158 if newnodes:
4159 newnodes = [repo[r].rev() for r in newnodes]
4159 newnodes = [repo[r].rev() for r in newnodes]
4160 cleanup = True
4160 cleanup = True
4161 # checking that none of the newnodes turned public or is public
4161 # checking that none of the newnodes turned public or is public
4162 immutable = [c for c in newnodes if not repo[c].mutable()]
4162 immutable = [c for c in newnodes if not repo[c].mutable()]
4163 if immutable:
4163 if immutable:
4164 repo.ui.warn(
4164 repo.ui.warn(
4165 _(b"cannot clean up public changesets %s\n")
4165 _(b"cannot clean up public changesets %s\n")
4166 % b', '.join(bytes(repo[r]) for r in immutable),
4166 % b', '.join(bytes(repo[r]) for r in immutable),
4167 hint=_(b"see 'hg help phases' for details"),
4167 hint=_(b"see 'hg help phases' for details"),
4168 )
4168 )
4169 cleanup = False
4169 cleanup = False
4170
4170
4171 # checking that no new nodes are created on top of grafted revs
4171 # checking that no new nodes are created on top of grafted revs
4172 desc = set(repo.changelog.descendants(newnodes))
4172 desc = set(repo.changelog.descendants(newnodes))
4173 if desc - set(newnodes):
4173 if desc - set(newnodes):
4174 repo.ui.warn(
4174 repo.ui.warn(
4175 _(
4175 _(
4176 b"new changesets detected on destination "
4176 b"new changesets detected on destination "
4177 b"branch, can't strip\n"
4177 b"branch, can't strip\n"
4178 )
4178 )
4179 )
4179 )
4180 cleanup = False
4180 cleanup = False
4181
4181
4182 if cleanup:
4182 if cleanup:
4183 with repo.wlock(), repo.lock():
4183 with repo.wlock(), repo.lock():
4184 mergemod.clean_update(startctx)
4184 mergemod.clean_update(startctx)
4185 # stripping the new nodes created
4185 # stripping the new nodes created
4186 strippoints = [
4186 strippoints = [
4187 c.node() for c in repo.set(b"roots(%ld)", newnodes)
4187 c.node() for c in repo.set(b"roots(%ld)", newnodes)
4188 ]
4188 ]
4189 repair.strip(repo.ui, repo, strippoints, backup=False)
4189 repair.strip(repo.ui, repo, strippoints, backup=False)
4190
4190
4191 if not cleanup:
4191 if not cleanup:
4192 # we don't update to the startnode if we can't strip
4192 # we don't update to the startnode if we can't strip
4193 startctx = repo[b'.']
4193 startctx = repo[b'.']
4194 mergemod.clean_update(startctx)
4194 mergemod.clean_update(startctx)
4195
4195
4196 ui.status(_(b"graft aborted\n"))
4196 ui.status(_(b"graft aborted\n"))
4197 ui.status(_(b"working directory is now at %s\n") % startctx.hex()[:12])
4197 ui.status(_(b"working directory is now at %s\n") % startctx.hex()[:12])
4198 graftstate.delete()
4198 graftstate.delete()
4199 return 0
4199 return 0
4200
4200
4201
4201
4202 def readgraftstate(repo, graftstate):
4202 def readgraftstate(repo, graftstate):
4203 # type: (Any, statemod.cmdstate) -> Dict[bytes, Any]
4203 # type: (Any, statemod.cmdstate) -> Dict[bytes, Any]
4204 """read the graft state file and return a dict of the data stored in it"""
4204 """read the graft state file and return a dict of the data stored in it"""
4205 try:
4205 try:
4206 return graftstate.read()
4206 return graftstate.read()
4207 except error.CorruptedState:
4207 except error.CorruptedState:
4208 nodes = repo.vfs.read(b'graftstate').splitlines()
4208 nodes = repo.vfs.read(b'graftstate').splitlines()
4209 return {b'nodes': nodes}
4209 return {b'nodes': nodes}
4210
4210
4211
4211
4212 def hgabortgraft(ui, repo):
4212 def hgabortgraft(ui, repo):
4213 """ abort logic for aborting graft using 'hg abort'"""
4213 """ abort logic for aborting graft using 'hg abort'"""
4214 with repo.wlock():
4214 with repo.wlock():
4215 graftstate = statemod.cmdstate(repo, b'graftstate')
4215 graftstate = statemod.cmdstate(repo, b'graftstate')
4216 return abortgraft(ui, repo, graftstate)
4216 return abortgraft(ui, repo, graftstate)
@@ -1,7810 +1,7811 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 mergestate as mergestatemod,
49 mergestate as mergestatemod,
50 narrowspec,
50 narrowspec,
51 obsolete,
51 obsolete,
52 obsutil,
52 obsutil,
53 patch,
53 patch,
54 phases,
54 phases,
55 pycompat,
55 pycompat,
56 rcutil,
56 rcutil,
57 registrar,
57 registrar,
58 requirements,
58 requirements,
59 revsetlang,
59 revsetlang,
60 rewriteutil,
60 rewriteutil,
61 scmutil,
61 scmutil,
62 server,
62 server,
63 shelve as shelvemod,
63 shelve as shelvemod,
64 state as statemod,
64 state as statemod,
65 streamclone,
65 streamclone,
66 tags as tagsmod,
66 tags as tagsmod,
67 ui as uimod,
67 ui as uimod,
68 util,
68 util,
69 verify as verifymod,
69 verify as verifymod,
70 vfs as vfsmod,
70 vfs as vfsmod,
71 wireprotoserver,
71 wireprotoserver,
72 )
72 )
73 from .utils import (
73 from .utils import (
74 dateutil,
74 dateutil,
75 stringutil,
75 stringutil,
76 )
76 )
77
77
78 table = {}
78 table = {}
79 table.update(debugcommandsmod.command._table)
79 table.update(debugcommandsmod.command._table)
80
80
81 command = registrar.command(table)
81 command = registrar.command(table)
82 INTENT_READONLY = registrar.INTENT_READONLY
82 INTENT_READONLY = registrar.INTENT_READONLY
83
83
84 # common command options
84 # common command options
85
85
86 globalopts = [
86 globalopts = [
87 (
87 (
88 b'R',
88 b'R',
89 b'repository',
89 b'repository',
90 b'',
90 b'',
91 _(b'repository root directory or name of overlay bundle file'),
91 _(b'repository root directory or name of overlay bundle file'),
92 _(b'REPO'),
92 _(b'REPO'),
93 ),
93 ),
94 (b'', b'cwd', b'', _(b'change working directory'), _(b'DIR')),
94 (b'', b'cwd', b'', _(b'change working directory'), _(b'DIR')),
95 (
95 (
96 b'y',
96 b'y',
97 b'noninteractive',
97 b'noninteractive',
98 None,
98 None,
99 _(
99 _(
100 b'do not prompt, automatically pick the first choice for all prompts'
100 b'do not prompt, automatically pick the first choice for all prompts'
101 ),
101 ),
102 ),
102 ),
103 (b'q', b'quiet', None, _(b'suppress output')),
103 (b'q', b'quiet', None, _(b'suppress output')),
104 (b'v', b'verbose', None, _(b'enable additional output')),
104 (b'v', b'verbose', None, _(b'enable additional output')),
105 (
105 (
106 b'',
106 b'',
107 b'color',
107 b'color',
108 b'',
108 b'',
109 # i18n: 'always', 'auto', 'never', and 'debug' are keywords
109 # i18n: 'always', 'auto', 'never', and 'debug' are keywords
110 # and should not be translated
110 # and should not be translated
111 _(b"when to colorize (boolean, always, auto, never, or debug)"),
111 _(b"when to colorize (boolean, always, auto, never, or debug)"),
112 _(b'TYPE'),
112 _(b'TYPE'),
113 ),
113 ),
114 (
114 (
115 b'',
115 b'',
116 b'config',
116 b'config',
117 [],
117 [],
118 _(b'set/override config option (use \'section.name=value\')'),
118 _(b'set/override config option (use \'section.name=value\')'),
119 _(b'CONFIG'),
119 _(b'CONFIG'),
120 ),
120 ),
121 (b'', b'debug', None, _(b'enable debugging output')),
121 (b'', b'debug', None, _(b'enable debugging output')),
122 (b'', b'debugger', None, _(b'start debugger')),
122 (b'', b'debugger', None, _(b'start debugger')),
123 (
123 (
124 b'',
124 b'',
125 b'encoding',
125 b'encoding',
126 encoding.encoding,
126 encoding.encoding,
127 _(b'set the charset encoding'),
127 _(b'set the charset encoding'),
128 _(b'ENCODE'),
128 _(b'ENCODE'),
129 ),
129 ),
130 (
130 (
131 b'',
131 b'',
132 b'encodingmode',
132 b'encodingmode',
133 encoding.encodingmode,
133 encoding.encodingmode,
134 _(b'set the charset encoding mode'),
134 _(b'set the charset encoding mode'),
135 _(b'MODE'),
135 _(b'MODE'),
136 ),
136 ),
137 (b'', b'traceback', None, _(b'always print a traceback on exception')),
137 (b'', b'traceback', None, _(b'always print a traceback on exception')),
138 (b'', b'time', None, _(b'time how long the command takes')),
138 (b'', b'time', None, _(b'time how long the command takes')),
139 (b'', b'profile', None, _(b'print command execution profile')),
139 (b'', b'profile', None, _(b'print command execution profile')),
140 (b'', b'version', None, _(b'output version information and exit')),
140 (b'', b'version', None, _(b'output version information and exit')),
141 (b'h', b'help', None, _(b'display help and exit')),
141 (b'h', b'help', None, _(b'display help and exit')),
142 (b'', b'hidden', False, _(b'consider hidden changesets')),
142 (b'', b'hidden', False, _(b'consider hidden changesets')),
143 (
143 (
144 b'',
144 b'',
145 b'pager',
145 b'pager',
146 b'auto',
146 b'auto',
147 _(b"when to paginate (boolean, always, auto, or never)"),
147 _(b"when to paginate (boolean, always, auto, or never)"),
148 _(b'TYPE'),
148 _(b'TYPE'),
149 ),
149 ),
150 ]
150 ]
151
151
152 dryrunopts = cmdutil.dryrunopts
152 dryrunopts = cmdutil.dryrunopts
153 remoteopts = cmdutil.remoteopts
153 remoteopts = cmdutil.remoteopts
154 walkopts = cmdutil.walkopts
154 walkopts = cmdutil.walkopts
155 commitopts = cmdutil.commitopts
155 commitopts = cmdutil.commitopts
156 commitopts2 = cmdutil.commitopts2
156 commitopts2 = cmdutil.commitopts2
157 commitopts3 = cmdutil.commitopts3
157 commitopts3 = cmdutil.commitopts3
158 formatteropts = cmdutil.formatteropts
158 formatteropts = cmdutil.formatteropts
159 templateopts = cmdutil.templateopts
159 templateopts = cmdutil.templateopts
160 logopts = cmdutil.logopts
160 logopts = cmdutil.logopts
161 diffopts = cmdutil.diffopts
161 diffopts = cmdutil.diffopts
162 diffwsopts = cmdutil.diffwsopts
162 diffwsopts = cmdutil.diffwsopts
163 diffopts2 = cmdutil.diffopts2
163 diffopts2 = cmdutil.diffopts2
164 mergetoolopts = cmdutil.mergetoolopts
164 mergetoolopts = cmdutil.mergetoolopts
165 similarityopts = cmdutil.similarityopts
165 similarityopts = cmdutil.similarityopts
166 subrepoopts = cmdutil.subrepoopts
166 subrepoopts = cmdutil.subrepoopts
167 debugrevlogopts = cmdutil.debugrevlogopts
167 debugrevlogopts = cmdutil.debugrevlogopts
168
168
169 # Commands start here, listed alphabetically
169 # Commands start here, listed alphabetically
170
170
171
171
172 @command(
172 @command(
173 b'abort',
173 b'abort',
174 dryrunopts,
174 dryrunopts,
175 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
175 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
176 helpbasic=True,
176 helpbasic=True,
177 )
177 )
178 def abort(ui, repo, **opts):
178 def abort(ui, repo, **opts):
179 """abort an unfinished operation (EXPERIMENTAL)
179 """abort an unfinished operation (EXPERIMENTAL)
180
180
181 Aborts a multistep operation like graft, histedit, rebase, merge,
181 Aborts a multistep operation like graft, histedit, rebase, merge,
182 and unshelve if they are in an unfinished state.
182 and unshelve if they are in an unfinished state.
183
183
184 use --dry-run/-n to dry run the command.
184 use --dry-run/-n to dry run the command.
185 """
185 """
186 dryrun = opts.get('dry_run')
186 dryrun = opts.get('dry_run')
187 abortstate = cmdutil.getunfinishedstate(repo)
187 abortstate = cmdutil.getunfinishedstate(repo)
188 if not abortstate:
188 if not abortstate:
189 raise error.Abort(_(b'no operation in progress'))
189 raise error.Abort(_(b'no operation in progress'))
190 if not abortstate.abortfunc:
190 if not abortstate.abortfunc:
191 raise error.Abort(
191 raise error.Abort(
192 (
192 (
193 _(b"%s in progress but does not support 'hg abort'")
193 _(b"%s in progress but does not support 'hg abort'")
194 % (abortstate._opname)
194 % (abortstate._opname)
195 ),
195 ),
196 hint=abortstate.hint(),
196 hint=abortstate.hint(),
197 )
197 )
198 if dryrun:
198 if dryrun:
199 ui.status(
199 ui.status(
200 _(b'%s in progress, will be aborted\n') % (abortstate._opname)
200 _(b'%s in progress, will be aborted\n') % (abortstate._opname)
201 )
201 )
202 return
202 return
203 return abortstate.abortfunc(ui, repo)
203 return abortstate.abortfunc(ui, repo)
204
204
205
205
206 @command(
206 @command(
207 b'add',
207 b'add',
208 walkopts + subrepoopts + dryrunopts,
208 walkopts + subrepoopts + dryrunopts,
209 _(b'[OPTION]... [FILE]...'),
209 _(b'[OPTION]... [FILE]...'),
210 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
210 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
211 helpbasic=True,
211 helpbasic=True,
212 inferrepo=True,
212 inferrepo=True,
213 )
213 )
214 def add(ui, repo, *pats, **opts):
214 def add(ui, repo, *pats, **opts):
215 """add the specified files on the next commit
215 """add the specified files on the next commit
216
216
217 Schedule files to be version controlled and added to the
217 Schedule files to be version controlled and added to the
218 repository.
218 repository.
219
219
220 The files will be added to the repository at the next commit. To
220 The files will be added to the repository at the next commit. To
221 undo an add before that, see :hg:`forget`.
221 undo an add before that, see :hg:`forget`.
222
222
223 If no names are given, add all files to the repository (except
223 If no names are given, add all files to the repository (except
224 files matching ``.hgignore``).
224 files matching ``.hgignore``).
225
225
226 .. container:: verbose
226 .. container:: verbose
227
227
228 Examples:
228 Examples:
229
229
230 - New (unknown) files are added
230 - New (unknown) files are added
231 automatically by :hg:`add`::
231 automatically by :hg:`add`::
232
232
233 $ ls
233 $ ls
234 foo.c
234 foo.c
235 $ hg status
235 $ hg status
236 ? foo.c
236 ? foo.c
237 $ hg add
237 $ hg add
238 adding foo.c
238 adding foo.c
239 $ hg status
239 $ hg status
240 A foo.c
240 A foo.c
241
241
242 - Specific files to be added can be specified::
242 - Specific files to be added can be specified::
243
243
244 $ ls
244 $ ls
245 bar.c foo.c
245 bar.c foo.c
246 $ hg status
246 $ hg status
247 ? bar.c
247 ? bar.c
248 ? foo.c
248 ? foo.c
249 $ hg add bar.c
249 $ hg add bar.c
250 $ hg status
250 $ hg status
251 A bar.c
251 A bar.c
252 ? foo.c
252 ? foo.c
253
253
254 Returns 0 if all files are successfully added.
254 Returns 0 if all files are successfully added.
255 """
255 """
256
256
257 m = scmutil.match(repo[None], pats, pycompat.byteskwargs(opts))
257 m = scmutil.match(repo[None], pats, pycompat.byteskwargs(opts))
258 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
258 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
259 rejected = cmdutil.add(ui, repo, m, b"", uipathfn, False, **opts)
259 rejected = cmdutil.add(ui, repo, m, b"", uipathfn, False, **opts)
260 return rejected and 1 or 0
260 return rejected and 1 or 0
261
261
262
262
263 @command(
263 @command(
264 b'addremove',
264 b'addremove',
265 similarityopts + subrepoopts + walkopts + dryrunopts,
265 similarityopts + subrepoopts + walkopts + dryrunopts,
266 _(b'[OPTION]... [FILE]...'),
266 _(b'[OPTION]... [FILE]...'),
267 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
267 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
268 inferrepo=True,
268 inferrepo=True,
269 )
269 )
270 def addremove(ui, repo, *pats, **opts):
270 def addremove(ui, repo, *pats, **opts):
271 """add all new files, delete all missing files
271 """add all new files, delete all missing files
272
272
273 Add all new files and remove all missing files from the
273 Add all new files and remove all missing files from the
274 repository.
274 repository.
275
275
276 Unless names are given, new files are ignored if they match any of
276 Unless names are given, new files are ignored if they match any of
277 the patterns in ``.hgignore``. As with add, these changes take
277 the patterns in ``.hgignore``. As with add, these changes take
278 effect at the next commit.
278 effect at the next commit.
279
279
280 Use the -s/--similarity option to detect renamed files. This
280 Use the -s/--similarity option to detect renamed files. This
281 option takes a percentage between 0 (disabled) and 100 (files must
281 option takes a percentage between 0 (disabled) and 100 (files must
282 be identical) as its parameter. With a parameter greater than 0,
282 be identical) as its parameter. With a parameter greater than 0,
283 this compares every removed file with every added file and records
283 this compares every removed file with every added file and records
284 those similar enough as renames. Detecting renamed files this way
284 those similar enough as renames. Detecting renamed files this way
285 can be expensive. After using this option, :hg:`status -C` can be
285 can be expensive. After using this option, :hg:`status -C` can be
286 used to check which files were identified as moved or renamed. If
286 used to check which files were identified as moved or renamed. If
287 not specified, -s/--similarity defaults to 100 and only renames of
287 not specified, -s/--similarity defaults to 100 and only renames of
288 identical files are detected.
288 identical files are detected.
289
289
290 .. container:: verbose
290 .. container:: verbose
291
291
292 Examples:
292 Examples:
293
293
294 - A number of files (bar.c and foo.c) are new,
294 - A number of files (bar.c and foo.c) are new,
295 while foobar.c has been removed (without using :hg:`remove`)
295 while foobar.c has been removed (without using :hg:`remove`)
296 from the repository::
296 from the repository::
297
297
298 $ ls
298 $ ls
299 bar.c foo.c
299 bar.c foo.c
300 $ hg status
300 $ hg status
301 ! foobar.c
301 ! foobar.c
302 ? bar.c
302 ? bar.c
303 ? foo.c
303 ? foo.c
304 $ hg addremove
304 $ hg addremove
305 adding bar.c
305 adding bar.c
306 adding foo.c
306 adding foo.c
307 removing foobar.c
307 removing foobar.c
308 $ hg status
308 $ hg status
309 A bar.c
309 A bar.c
310 A foo.c
310 A foo.c
311 R foobar.c
311 R foobar.c
312
312
313 - A file foobar.c was moved to foo.c without using :hg:`rename`.
313 - A file foobar.c was moved to foo.c without using :hg:`rename`.
314 Afterwards, it was edited slightly::
314 Afterwards, it was edited slightly::
315
315
316 $ ls
316 $ ls
317 foo.c
317 foo.c
318 $ hg status
318 $ hg status
319 ! foobar.c
319 ! foobar.c
320 ? foo.c
320 ? foo.c
321 $ hg addremove --similarity 90
321 $ hg addremove --similarity 90
322 removing foobar.c
322 removing foobar.c
323 adding foo.c
323 adding foo.c
324 recording removal of foobar.c as rename to foo.c (94% similar)
324 recording removal of foobar.c as rename to foo.c (94% similar)
325 $ hg status -C
325 $ hg status -C
326 A foo.c
326 A foo.c
327 foobar.c
327 foobar.c
328 R foobar.c
328 R foobar.c
329
329
330 Returns 0 if all files are successfully added.
330 Returns 0 if all files are successfully added.
331 """
331 """
332 opts = pycompat.byteskwargs(opts)
332 opts = pycompat.byteskwargs(opts)
333 if not opts.get(b'similarity'):
333 if not opts.get(b'similarity'):
334 opts[b'similarity'] = b'100'
334 opts[b'similarity'] = b'100'
335 matcher = scmutil.match(repo[None], pats, opts)
335 matcher = scmutil.match(repo[None], pats, opts)
336 relative = scmutil.anypats(pats, opts)
336 relative = scmutil.anypats(pats, opts)
337 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
337 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
338 return scmutil.addremove(repo, matcher, b"", uipathfn, opts)
338 return scmutil.addremove(repo, matcher, b"", uipathfn, opts)
339
339
340
340
341 @command(
341 @command(
342 b'annotate|blame',
342 b'annotate|blame',
343 [
343 [
344 (b'r', b'rev', b'', _(b'annotate the specified revision'), _(b'REV')),
344 (b'r', b'rev', b'', _(b'annotate the specified revision'), _(b'REV')),
345 (
345 (
346 b'',
346 b'',
347 b'follow',
347 b'follow',
348 None,
348 None,
349 _(b'follow copies/renames and list the filename (DEPRECATED)'),
349 _(b'follow copies/renames and list the filename (DEPRECATED)'),
350 ),
350 ),
351 (b'', b'no-follow', None, _(b"don't follow copies and renames")),
351 (b'', b'no-follow', None, _(b"don't follow copies and renames")),
352 (b'a', b'text', None, _(b'treat all files as text')),
352 (b'a', b'text', None, _(b'treat all files as text')),
353 (b'u', b'user', None, _(b'list the author (long with -v)')),
353 (b'u', b'user', None, _(b'list the author (long with -v)')),
354 (b'f', b'file', None, _(b'list the filename')),
354 (b'f', b'file', None, _(b'list the filename')),
355 (b'd', b'date', None, _(b'list the date (short with -q)')),
355 (b'd', b'date', None, _(b'list the date (short with -q)')),
356 (b'n', b'number', None, _(b'list the revision number (default)')),
356 (b'n', b'number', None, _(b'list the revision number (default)')),
357 (b'c', b'changeset', None, _(b'list the changeset')),
357 (b'c', b'changeset', None, _(b'list the changeset')),
358 (
358 (
359 b'l',
359 b'l',
360 b'line-number',
360 b'line-number',
361 None,
361 None,
362 _(b'show line number at the first appearance'),
362 _(b'show line number at the first appearance'),
363 ),
363 ),
364 (
364 (
365 b'',
365 b'',
366 b'skip',
366 b'skip',
367 [],
367 [],
368 _(b'revset to not display (EXPERIMENTAL)'),
368 _(b'revset to not display (EXPERIMENTAL)'),
369 _(b'REV'),
369 _(b'REV'),
370 ),
370 ),
371 ]
371 ]
372 + diffwsopts
372 + diffwsopts
373 + walkopts
373 + walkopts
374 + formatteropts,
374 + formatteropts,
375 _(b'[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'),
375 _(b'[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'),
376 helpcategory=command.CATEGORY_FILE_CONTENTS,
376 helpcategory=command.CATEGORY_FILE_CONTENTS,
377 helpbasic=True,
377 helpbasic=True,
378 inferrepo=True,
378 inferrepo=True,
379 )
379 )
380 def annotate(ui, repo, *pats, **opts):
380 def annotate(ui, repo, *pats, **opts):
381 """show changeset information by line for each file
381 """show changeset information by line for each file
382
382
383 List changes in files, showing the revision id responsible for
383 List changes in files, showing the revision id responsible for
384 each line.
384 each line.
385
385
386 This command is useful for discovering when a change was made and
386 This command is useful for discovering when a change was made and
387 by whom.
387 by whom.
388
388
389 If you include --file, --user, or --date, the revision number is
389 If you include --file, --user, or --date, the revision number is
390 suppressed unless you also include --number.
390 suppressed unless you also include --number.
391
391
392 Without the -a/--text option, annotate will avoid processing files
392 Without the -a/--text option, annotate will avoid processing files
393 it detects as binary. With -a, annotate will annotate the file
393 it detects as binary. With -a, annotate will annotate the file
394 anyway, although the results will probably be neither useful
394 anyway, although the results will probably be neither useful
395 nor desirable.
395 nor desirable.
396
396
397 .. container:: verbose
397 .. container:: verbose
398
398
399 Template:
399 Template:
400
400
401 The following keywords are supported in addition to the common template
401 The following keywords are supported in addition to the common template
402 keywords and functions. See also :hg:`help templates`.
402 keywords and functions. See also :hg:`help templates`.
403
403
404 :lines: List of lines with annotation data.
404 :lines: List of lines with annotation data.
405 :path: String. Repository-absolute path of the specified file.
405 :path: String. Repository-absolute path of the specified file.
406
406
407 And each entry of ``{lines}`` provides the following sub-keywords in
407 And each entry of ``{lines}`` provides the following sub-keywords in
408 addition to ``{date}``, ``{node}``, ``{rev}``, ``{user}``, etc.
408 addition to ``{date}``, ``{node}``, ``{rev}``, ``{user}``, etc.
409
409
410 :line: String. Line content.
410 :line: String. Line content.
411 :lineno: Integer. Line number at that revision.
411 :lineno: Integer. Line number at that revision.
412 :path: String. Repository-absolute path of the file at that revision.
412 :path: String. Repository-absolute path of the file at that revision.
413
413
414 See :hg:`help templates.operators` for the list expansion syntax.
414 See :hg:`help templates.operators` for the list expansion syntax.
415
415
416 Returns 0 on success.
416 Returns 0 on success.
417 """
417 """
418 opts = pycompat.byteskwargs(opts)
418 opts = pycompat.byteskwargs(opts)
419 if not pats:
419 if not pats:
420 raise error.Abort(_(b'at least one filename or pattern is required'))
420 raise error.Abort(_(b'at least one filename or pattern is required'))
421
421
422 if opts.get(b'follow'):
422 if opts.get(b'follow'):
423 # --follow is deprecated and now just an alias for -f/--file
423 # --follow is deprecated and now just an alias for -f/--file
424 # to mimic the behavior of Mercurial before version 1.5
424 # to mimic the behavior of Mercurial before version 1.5
425 opts[b'file'] = True
425 opts[b'file'] = True
426
426
427 if (
427 if (
428 not opts.get(b'user')
428 not opts.get(b'user')
429 and not opts.get(b'changeset')
429 and not opts.get(b'changeset')
430 and not opts.get(b'date')
430 and not opts.get(b'date')
431 and not opts.get(b'file')
431 and not opts.get(b'file')
432 ):
432 ):
433 opts[b'number'] = True
433 opts[b'number'] = True
434
434
435 linenumber = opts.get(b'line_number') is not None
435 linenumber = opts.get(b'line_number') is not None
436 if (
436 if (
437 linenumber
437 linenumber
438 and (not opts.get(b'changeset'))
438 and (not opts.get(b'changeset'))
439 and (not opts.get(b'number'))
439 and (not opts.get(b'number'))
440 ):
440 ):
441 raise error.Abort(_(b'at least one of -n/-c is required for -l'))
441 raise error.Abort(_(b'at least one of -n/-c is required for -l'))
442
442
443 rev = opts.get(b'rev')
443 rev = opts.get(b'rev')
444 if rev:
444 if rev:
445 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
445 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
446 ctx = scmutil.revsingle(repo, rev)
446 ctx = scmutil.revsingle(repo, rev)
447
447
448 ui.pager(b'annotate')
448 ui.pager(b'annotate')
449 rootfm = ui.formatter(b'annotate', opts)
449 rootfm = ui.formatter(b'annotate', opts)
450 if ui.debugflag:
450 if ui.debugflag:
451 shorthex = pycompat.identity
451 shorthex = pycompat.identity
452 else:
452 else:
453
453
454 def shorthex(h):
454 def shorthex(h):
455 return h[:12]
455 return h[:12]
456
456
457 if ui.quiet:
457 if ui.quiet:
458 datefunc = dateutil.shortdate
458 datefunc = dateutil.shortdate
459 else:
459 else:
460 datefunc = dateutil.datestr
460 datefunc = dateutil.datestr
461 if ctx.rev() is None:
461 if ctx.rev() is None:
462 if opts.get(b'changeset'):
462 if opts.get(b'changeset'):
463 # omit "+" suffix which is appended to node hex
463 # omit "+" suffix which is appended to node hex
464 def formatrev(rev):
464 def formatrev(rev):
465 if rev == wdirrev:
465 if rev == wdirrev:
466 return b'%d' % ctx.p1().rev()
466 return b'%d' % ctx.p1().rev()
467 else:
467 else:
468 return b'%d' % rev
468 return b'%d' % rev
469
469
470 else:
470 else:
471
471
472 def formatrev(rev):
472 def formatrev(rev):
473 if rev == wdirrev:
473 if rev == wdirrev:
474 return b'%d+' % ctx.p1().rev()
474 return b'%d+' % ctx.p1().rev()
475 else:
475 else:
476 return b'%d ' % rev
476 return b'%d ' % rev
477
477
478 def formathex(h):
478 def formathex(h):
479 if h == wdirhex:
479 if h == wdirhex:
480 return b'%s+' % shorthex(hex(ctx.p1().node()))
480 return b'%s+' % shorthex(hex(ctx.p1().node()))
481 else:
481 else:
482 return b'%s ' % shorthex(h)
482 return b'%s ' % shorthex(h)
483
483
484 else:
484 else:
485 formatrev = b'%d'.__mod__
485 formatrev = b'%d'.__mod__
486 formathex = shorthex
486 formathex = shorthex
487
487
488 opmap = [
488 opmap = [
489 (b'user', b' ', lambda x: x.fctx.user(), ui.shortuser),
489 (b'user', b' ', lambda x: x.fctx.user(), ui.shortuser),
490 (b'rev', b' ', lambda x: scmutil.intrev(x.fctx), formatrev),
490 (b'rev', b' ', lambda x: scmutil.intrev(x.fctx), formatrev),
491 (b'node', b' ', lambda x: hex(scmutil.binnode(x.fctx)), formathex),
491 (b'node', b' ', lambda x: hex(scmutil.binnode(x.fctx)), formathex),
492 (b'date', b' ', lambda x: x.fctx.date(), util.cachefunc(datefunc)),
492 (b'date', b' ', lambda x: x.fctx.date(), util.cachefunc(datefunc)),
493 (b'path', b' ', lambda x: x.fctx.path(), pycompat.bytestr),
493 (b'path', b' ', lambda x: x.fctx.path(), pycompat.bytestr),
494 (b'lineno', b':', lambda x: x.lineno, pycompat.bytestr),
494 (b'lineno', b':', lambda x: x.lineno, pycompat.bytestr),
495 ]
495 ]
496 opnamemap = {
496 opnamemap = {
497 b'rev': b'number',
497 b'rev': b'number',
498 b'node': b'changeset',
498 b'node': b'changeset',
499 b'path': b'file',
499 b'path': b'file',
500 b'lineno': b'line_number',
500 b'lineno': b'line_number',
501 }
501 }
502
502
503 if rootfm.isplain():
503 if rootfm.isplain():
504
504
505 def makefunc(get, fmt):
505 def makefunc(get, fmt):
506 return lambda x: fmt(get(x))
506 return lambda x: fmt(get(x))
507
507
508 else:
508 else:
509
509
510 def makefunc(get, fmt):
510 def makefunc(get, fmt):
511 return get
511 return get
512
512
513 datahint = rootfm.datahint()
513 datahint = rootfm.datahint()
514 funcmap = [
514 funcmap = [
515 (makefunc(get, fmt), sep)
515 (makefunc(get, fmt), sep)
516 for fn, sep, get, fmt in opmap
516 for fn, sep, get, fmt in opmap
517 if opts.get(opnamemap.get(fn, fn)) or fn in datahint
517 if opts.get(opnamemap.get(fn, fn)) or fn in datahint
518 ]
518 ]
519 funcmap[0] = (funcmap[0][0], b'') # no separator in front of first column
519 funcmap[0] = (funcmap[0][0], b'') # no separator in front of first column
520 fields = b' '.join(
520 fields = b' '.join(
521 fn
521 fn
522 for fn, sep, get, fmt in opmap
522 for fn, sep, get, fmt in opmap
523 if opts.get(opnamemap.get(fn, fn)) or fn in datahint
523 if opts.get(opnamemap.get(fn, fn)) or fn in datahint
524 )
524 )
525
525
526 def bad(x, y):
526 def bad(x, y):
527 raise error.Abort(b"%s: %s" % (x, y))
527 raise error.Abort(b"%s: %s" % (x, y))
528
528
529 m = scmutil.match(ctx, pats, opts, badfn=bad)
529 m = scmutil.match(ctx, pats, opts, badfn=bad)
530
530
531 follow = not opts.get(b'no_follow')
531 follow = not opts.get(b'no_follow')
532 diffopts = patch.difffeatureopts(
532 diffopts = patch.difffeatureopts(
533 ui, opts, section=b'annotate', whitespace=True
533 ui, opts, section=b'annotate', whitespace=True
534 )
534 )
535 skiprevs = opts.get(b'skip')
535 skiprevs = opts.get(b'skip')
536 if skiprevs:
536 if skiprevs:
537 skiprevs = scmutil.revrange(repo, skiprevs)
537 skiprevs = scmutil.revrange(repo, skiprevs)
538
538
539 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
539 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
540 for abs in ctx.walk(m):
540 for abs in ctx.walk(m):
541 fctx = ctx[abs]
541 fctx = ctx[abs]
542 rootfm.startitem()
542 rootfm.startitem()
543 rootfm.data(path=abs)
543 rootfm.data(path=abs)
544 if not opts.get(b'text') and fctx.isbinary():
544 if not opts.get(b'text') and fctx.isbinary():
545 rootfm.plain(_(b"%s: binary file\n") % uipathfn(abs))
545 rootfm.plain(_(b"%s: binary file\n") % uipathfn(abs))
546 continue
546 continue
547
547
548 fm = rootfm.nested(b'lines', tmpl=b'{rev}: {line}')
548 fm = rootfm.nested(b'lines', tmpl=b'{rev}: {line}')
549 lines = fctx.annotate(
549 lines = fctx.annotate(
550 follow=follow, skiprevs=skiprevs, diffopts=diffopts
550 follow=follow, skiprevs=skiprevs, diffopts=diffopts
551 )
551 )
552 if not lines:
552 if not lines:
553 fm.end()
553 fm.end()
554 continue
554 continue
555 formats = []
555 formats = []
556 pieces = []
556 pieces = []
557
557
558 for f, sep in funcmap:
558 for f, sep in funcmap:
559 l = [f(n) for n in lines]
559 l = [f(n) for n in lines]
560 if fm.isplain():
560 if fm.isplain():
561 sizes = [encoding.colwidth(x) for x in l]
561 sizes = [encoding.colwidth(x) for x in l]
562 ml = max(sizes)
562 ml = max(sizes)
563 formats.append([sep + b' ' * (ml - w) + b'%s' for w in sizes])
563 formats.append([sep + b' ' * (ml - w) + b'%s' for w in sizes])
564 else:
564 else:
565 formats.append([b'%s'] * len(l))
565 formats.append([b'%s'] * len(l))
566 pieces.append(l)
566 pieces.append(l)
567
567
568 for f, p, n in zip(zip(*formats), zip(*pieces), lines):
568 for f, p, n in zip(zip(*formats), zip(*pieces), lines):
569 fm.startitem()
569 fm.startitem()
570 fm.context(fctx=n.fctx)
570 fm.context(fctx=n.fctx)
571 fm.write(fields, b"".join(f), *p)
571 fm.write(fields, b"".join(f), *p)
572 if n.skip:
572 if n.skip:
573 fmt = b"* %s"
573 fmt = b"* %s"
574 else:
574 else:
575 fmt = b": %s"
575 fmt = b": %s"
576 fm.write(b'line', fmt, n.text)
576 fm.write(b'line', fmt, n.text)
577
577
578 if not lines[-1].text.endswith(b'\n'):
578 if not lines[-1].text.endswith(b'\n'):
579 fm.plain(b'\n')
579 fm.plain(b'\n')
580 fm.end()
580 fm.end()
581
581
582 rootfm.end()
582 rootfm.end()
583
583
584
584
585 @command(
585 @command(
586 b'archive',
586 b'archive',
587 [
587 [
588 (b'', b'no-decode', None, _(b'do not pass files through decoders')),
588 (b'', b'no-decode', None, _(b'do not pass files through decoders')),
589 (
589 (
590 b'p',
590 b'p',
591 b'prefix',
591 b'prefix',
592 b'',
592 b'',
593 _(b'directory prefix for files in archive'),
593 _(b'directory prefix for files in archive'),
594 _(b'PREFIX'),
594 _(b'PREFIX'),
595 ),
595 ),
596 (b'r', b'rev', b'', _(b'revision to distribute'), _(b'REV')),
596 (b'r', b'rev', b'', _(b'revision to distribute'), _(b'REV')),
597 (b't', b'type', b'', _(b'type of distribution to create'), _(b'TYPE')),
597 (b't', b'type', b'', _(b'type of distribution to create'), _(b'TYPE')),
598 ]
598 ]
599 + subrepoopts
599 + subrepoopts
600 + walkopts,
600 + walkopts,
601 _(b'[OPTION]... DEST'),
601 _(b'[OPTION]... DEST'),
602 helpcategory=command.CATEGORY_IMPORT_EXPORT,
602 helpcategory=command.CATEGORY_IMPORT_EXPORT,
603 )
603 )
604 def archive(ui, repo, dest, **opts):
604 def archive(ui, repo, dest, **opts):
605 '''create an unversioned archive of a repository revision
605 '''create an unversioned archive of a repository revision
606
606
607 By default, the revision used is the parent of the working
607 By default, the revision used is the parent of the working
608 directory; use -r/--rev to specify a different revision.
608 directory; use -r/--rev to specify a different revision.
609
609
610 The archive type is automatically detected based on file
610 The archive type is automatically detected based on file
611 extension (to override, use -t/--type).
611 extension (to override, use -t/--type).
612
612
613 .. container:: verbose
613 .. container:: verbose
614
614
615 Examples:
615 Examples:
616
616
617 - create a zip file containing the 1.0 release::
617 - create a zip file containing the 1.0 release::
618
618
619 hg archive -r 1.0 project-1.0.zip
619 hg archive -r 1.0 project-1.0.zip
620
620
621 - create a tarball excluding .hg files::
621 - create a tarball excluding .hg files::
622
622
623 hg archive project.tar.gz -X ".hg*"
623 hg archive project.tar.gz -X ".hg*"
624
624
625 Valid types are:
625 Valid types are:
626
626
627 :``files``: a directory full of files (default)
627 :``files``: a directory full of files (default)
628 :``tar``: tar archive, uncompressed
628 :``tar``: tar archive, uncompressed
629 :``tbz2``: tar archive, compressed using bzip2
629 :``tbz2``: tar archive, compressed using bzip2
630 :``tgz``: tar archive, compressed using gzip
630 :``tgz``: tar archive, compressed using gzip
631 :``txz``: tar archive, compressed using lzma (only in Python 3)
631 :``txz``: tar archive, compressed using lzma (only in Python 3)
632 :``uzip``: zip archive, uncompressed
632 :``uzip``: zip archive, uncompressed
633 :``zip``: zip archive, compressed using deflate
633 :``zip``: zip archive, compressed using deflate
634
634
635 The exact name of the destination archive or directory is given
635 The exact name of the destination archive or directory is given
636 using a format string; see :hg:`help export` for details.
636 using a format string; see :hg:`help export` for details.
637
637
638 Each member added to an archive file has a directory prefix
638 Each member added to an archive file has a directory prefix
639 prepended. Use -p/--prefix to specify a format string for the
639 prepended. Use -p/--prefix to specify a format string for the
640 prefix. The default is the basename of the archive, with suffixes
640 prefix. The default is the basename of the archive, with suffixes
641 removed.
641 removed.
642
642
643 Returns 0 on success.
643 Returns 0 on success.
644 '''
644 '''
645
645
646 opts = pycompat.byteskwargs(opts)
646 opts = pycompat.byteskwargs(opts)
647 rev = opts.get(b'rev')
647 rev = opts.get(b'rev')
648 if rev:
648 if rev:
649 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
649 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
650 ctx = scmutil.revsingle(repo, rev)
650 ctx = scmutil.revsingle(repo, rev)
651 if not ctx:
651 if not ctx:
652 raise error.Abort(_(b'no working directory: please specify a revision'))
652 raise error.Abort(_(b'no working directory: please specify a revision'))
653 node = ctx.node()
653 node = ctx.node()
654 dest = cmdutil.makefilename(ctx, dest)
654 dest = cmdutil.makefilename(ctx, dest)
655 if os.path.realpath(dest) == repo.root:
655 if os.path.realpath(dest) == repo.root:
656 raise error.Abort(_(b'repository root cannot be destination'))
656 raise error.Abort(_(b'repository root cannot be destination'))
657
657
658 kind = opts.get(b'type') or archival.guesskind(dest) or b'files'
658 kind = opts.get(b'type') or archival.guesskind(dest) or b'files'
659 prefix = opts.get(b'prefix')
659 prefix = opts.get(b'prefix')
660
660
661 if dest == b'-':
661 if dest == b'-':
662 if kind == b'files':
662 if kind == b'files':
663 raise error.Abort(_(b'cannot archive plain files to stdout'))
663 raise error.Abort(_(b'cannot archive plain files to stdout'))
664 dest = cmdutil.makefileobj(ctx, dest)
664 dest = cmdutil.makefileobj(ctx, dest)
665 if not prefix:
665 if not prefix:
666 prefix = os.path.basename(repo.root) + b'-%h'
666 prefix = os.path.basename(repo.root) + b'-%h'
667
667
668 prefix = cmdutil.makefilename(ctx, prefix)
668 prefix = cmdutil.makefilename(ctx, prefix)
669 match = scmutil.match(ctx, [], opts)
669 match = scmutil.match(ctx, [], opts)
670 archival.archive(
670 archival.archive(
671 repo,
671 repo,
672 dest,
672 dest,
673 node,
673 node,
674 kind,
674 kind,
675 not opts.get(b'no_decode'),
675 not opts.get(b'no_decode'),
676 match,
676 match,
677 prefix,
677 prefix,
678 subrepos=opts.get(b'subrepos'),
678 subrepos=opts.get(b'subrepos'),
679 )
679 )
680
680
681
681
682 @command(
682 @command(
683 b'backout',
683 b'backout',
684 [
684 [
685 (
685 (
686 b'',
686 b'',
687 b'merge',
687 b'merge',
688 None,
688 None,
689 _(b'merge with old dirstate parent after backout'),
689 _(b'merge with old dirstate parent after backout'),
690 ),
690 ),
691 (
691 (
692 b'',
692 b'',
693 b'commit',
693 b'commit',
694 None,
694 None,
695 _(b'commit if no conflicts were encountered (DEPRECATED)'),
695 _(b'commit if no conflicts were encountered (DEPRECATED)'),
696 ),
696 ),
697 (b'', b'no-commit', None, _(b'do not commit')),
697 (b'', b'no-commit', None, _(b'do not commit')),
698 (
698 (
699 b'',
699 b'',
700 b'parent',
700 b'parent',
701 b'',
701 b'',
702 _(b'parent to choose when backing out merge (DEPRECATED)'),
702 _(b'parent to choose when backing out merge (DEPRECATED)'),
703 _(b'REV'),
703 _(b'REV'),
704 ),
704 ),
705 (b'r', b'rev', b'', _(b'revision to backout'), _(b'REV')),
705 (b'r', b'rev', b'', _(b'revision to backout'), _(b'REV')),
706 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
706 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
707 ]
707 ]
708 + mergetoolopts
708 + mergetoolopts
709 + walkopts
709 + walkopts
710 + commitopts
710 + commitopts
711 + commitopts2,
711 + commitopts2,
712 _(b'[OPTION]... [-r] REV'),
712 _(b'[OPTION]... [-r] REV'),
713 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
713 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
714 )
714 )
715 def backout(ui, repo, node=None, rev=None, **opts):
715 def backout(ui, repo, node=None, rev=None, **opts):
716 '''reverse effect of earlier changeset
716 '''reverse effect of earlier changeset
717
717
718 Prepare a new changeset with the effect of REV undone in the
718 Prepare a new changeset with the effect of REV undone in the
719 current working directory. If no conflicts were encountered,
719 current working directory. If no conflicts were encountered,
720 it will be committed immediately.
720 it will be committed immediately.
721
721
722 If REV is the parent of the working directory, then this new changeset
722 If REV is the parent of the working directory, then this new changeset
723 is committed automatically (unless --no-commit is specified).
723 is committed automatically (unless --no-commit is specified).
724
724
725 .. note::
725 .. note::
726
726
727 :hg:`backout` cannot be used to fix either an unwanted or
727 :hg:`backout` cannot be used to fix either an unwanted or
728 incorrect merge.
728 incorrect merge.
729
729
730 .. container:: verbose
730 .. container:: verbose
731
731
732 Examples:
732 Examples:
733
733
734 - Reverse the effect of the parent of the working directory.
734 - Reverse the effect of the parent of the working directory.
735 This backout will be committed immediately::
735 This backout will be committed immediately::
736
736
737 hg backout -r .
737 hg backout -r .
738
738
739 - Reverse the effect of previous bad revision 23::
739 - Reverse the effect of previous bad revision 23::
740
740
741 hg backout -r 23
741 hg backout -r 23
742
742
743 - Reverse the effect of previous bad revision 23 and
743 - Reverse the effect of previous bad revision 23 and
744 leave changes uncommitted::
744 leave changes uncommitted::
745
745
746 hg backout -r 23 --no-commit
746 hg backout -r 23 --no-commit
747 hg commit -m "Backout revision 23"
747 hg commit -m "Backout revision 23"
748
748
749 By default, the pending changeset will have one parent,
749 By default, the pending changeset will have one parent,
750 maintaining a linear history. With --merge, the pending
750 maintaining a linear history. With --merge, the pending
751 changeset will instead have two parents: the old parent of the
751 changeset will instead have two parents: the old parent of the
752 working directory and a new child of REV that simply undoes REV.
752 working directory and a new child of REV that simply undoes REV.
753
753
754 Before version 1.7, the behavior without --merge was equivalent
754 Before version 1.7, the behavior without --merge was equivalent
755 to specifying --merge followed by :hg:`update --clean .` to
755 to specifying --merge followed by :hg:`update --clean .` to
756 cancel the merge and leave the child of REV as a head to be
756 cancel the merge and leave the child of REV as a head to be
757 merged separately.
757 merged separately.
758
758
759 See :hg:`help dates` for a list of formats valid for -d/--date.
759 See :hg:`help dates` for a list of formats valid for -d/--date.
760
760
761 See :hg:`help revert` for a way to restore files to the state
761 See :hg:`help revert` for a way to restore files to the state
762 of another revision.
762 of another revision.
763
763
764 Returns 0 on success, 1 if nothing to backout or there are unresolved
764 Returns 0 on success, 1 if nothing to backout or there are unresolved
765 files.
765 files.
766 '''
766 '''
767 with repo.wlock(), repo.lock():
767 with repo.wlock(), repo.lock():
768 return _dobackout(ui, repo, node, rev, **opts)
768 return _dobackout(ui, repo, node, rev, **opts)
769
769
770
770
771 def _dobackout(ui, repo, node=None, rev=None, **opts):
771 def _dobackout(ui, repo, node=None, rev=None, **opts):
772 opts = pycompat.byteskwargs(opts)
772 opts = pycompat.byteskwargs(opts)
773 if opts.get(b'commit') and opts.get(b'no_commit'):
773 if opts.get(b'commit') and opts.get(b'no_commit'):
774 raise error.Abort(_(b"cannot use --commit with --no-commit"))
774 raise error.Abort(_(b"cannot use --commit with --no-commit"))
775 if opts.get(b'merge') and opts.get(b'no_commit'):
775 if opts.get(b'merge') and opts.get(b'no_commit'):
776 raise error.Abort(_(b"cannot use --merge with --no-commit"))
776 raise error.Abort(_(b"cannot use --merge with --no-commit"))
777
777
778 if rev and node:
778 if rev and node:
779 raise error.Abort(_(b"please specify just one revision"))
779 raise error.Abort(_(b"please specify just one revision"))
780
780
781 if not rev:
781 if not rev:
782 rev = node
782 rev = node
783
783
784 if not rev:
784 if not rev:
785 raise error.Abort(_(b"please specify a revision to backout"))
785 raise error.Abort(_(b"please specify a revision to backout"))
786
786
787 date = opts.get(b'date')
787 date = opts.get(b'date')
788 if date:
788 if date:
789 opts[b'date'] = dateutil.parsedate(date)
789 opts[b'date'] = dateutil.parsedate(date)
790
790
791 cmdutil.checkunfinished(repo)
791 cmdutil.checkunfinished(repo)
792 cmdutil.bailifchanged(repo)
792 cmdutil.bailifchanged(repo)
793 ctx = scmutil.revsingle(repo, rev)
793 ctx = scmutil.revsingle(repo, rev)
794 node = ctx.node()
794 node = ctx.node()
795
795
796 op1, op2 = repo.dirstate.parents()
796 op1, op2 = repo.dirstate.parents()
797 if not repo.changelog.isancestor(node, op1):
797 if not repo.changelog.isancestor(node, op1):
798 raise error.Abort(_(b'cannot backout change that is not an ancestor'))
798 raise error.Abort(_(b'cannot backout change that is not an ancestor'))
799
799
800 p1, p2 = repo.changelog.parents(node)
800 p1, p2 = repo.changelog.parents(node)
801 if p1 == nullid:
801 if p1 == nullid:
802 raise error.Abort(_(b'cannot backout a change with no parents'))
802 raise error.Abort(_(b'cannot backout a change with no parents'))
803 if p2 != nullid:
803 if p2 != nullid:
804 if not opts.get(b'parent'):
804 if not opts.get(b'parent'):
805 raise error.Abort(_(b'cannot backout a merge changeset'))
805 raise error.Abort(_(b'cannot backout a merge changeset'))
806 p = repo.lookup(opts[b'parent'])
806 p = repo.lookup(opts[b'parent'])
807 if p not in (p1, p2):
807 if p not in (p1, p2):
808 raise error.Abort(
808 raise error.Abort(
809 _(b'%s is not a parent of %s') % (short(p), short(node))
809 _(b'%s is not a parent of %s') % (short(p), short(node))
810 )
810 )
811 parent = p
811 parent = p
812 else:
812 else:
813 if opts.get(b'parent'):
813 if opts.get(b'parent'):
814 raise error.Abort(_(b'cannot use --parent on non-merge changeset'))
814 raise error.Abort(_(b'cannot use --parent on non-merge changeset'))
815 parent = p1
815 parent = p1
816
816
817 # the backout should appear on the same branch
817 # the backout should appear on the same branch
818 branch = repo.dirstate.branch()
818 branch = repo.dirstate.branch()
819 bheads = repo.branchheads(branch)
819 bheads = repo.branchheads(branch)
820 rctx = scmutil.revsingle(repo, hex(parent))
820 rctx = scmutil.revsingle(repo, hex(parent))
821 if not opts.get(b'merge') and op1 != node:
821 if not opts.get(b'merge') and op1 != node:
822 with dirstateguard.dirstateguard(repo, b'backout'):
822 with dirstateguard.dirstateguard(repo, b'backout'):
823 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
823 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
824 with ui.configoverride(overrides, b'backout'):
824 with ui.configoverride(overrides, b'backout'):
825 stats = mergemod.back_out(ctx, parent=repo[parent])
825 stats = mergemod.back_out(ctx, parent=repo[parent])
826 repo.setparents(op1, op2)
826 repo.setparents(op1, op2)
827 hg._showstats(repo, stats)
827 hg._showstats(repo, stats)
828 if stats.unresolvedcount:
828 if stats.unresolvedcount:
829 repo.ui.status(
829 repo.ui.status(
830 _(b"use 'hg resolve' to retry unresolved file merges\n")
830 _(b"use 'hg resolve' to retry unresolved file merges\n")
831 )
831 )
832 return 1
832 return 1
833 else:
833 else:
834 hg.clean(repo, node, show_stats=False)
834 hg.clean(repo, node, show_stats=False)
835 repo.dirstate.setbranch(branch)
835 repo.dirstate.setbranch(branch)
836 cmdutil.revert(ui, repo, rctx)
836 cmdutil.revert(ui, repo, rctx)
837
837
838 if opts.get(b'no_commit'):
838 if opts.get(b'no_commit'):
839 msg = _(b"changeset %s backed out, don't forget to commit.\n")
839 msg = _(b"changeset %s backed out, don't forget to commit.\n")
840 ui.status(msg % short(node))
840 ui.status(msg % short(node))
841 return 0
841 return 0
842
842
843 def commitfunc(ui, repo, message, match, opts):
843 def commitfunc(ui, repo, message, match, opts):
844 editform = b'backout'
844 editform = b'backout'
845 e = cmdutil.getcommiteditor(
845 e = cmdutil.getcommiteditor(
846 editform=editform, **pycompat.strkwargs(opts)
846 editform=editform, **pycompat.strkwargs(opts)
847 )
847 )
848 if not message:
848 if not message:
849 # we don't translate commit messages
849 # we don't translate commit messages
850 message = b"Backed out changeset %s" % short(node)
850 message = b"Backed out changeset %s" % short(node)
851 e = cmdutil.getcommiteditor(edit=True, editform=editform)
851 e = cmdutil.getcommiteditor(edit=True, editform=editform)
852 return repo.commit(
852 return repo.commit(
853 message, opts.get(b'user'), opts.get(b'date'), match, editor=e
853 message, opts.get(b'user'), opts.get(b'date'), match, editor=e
854 )
854 )
855
855
856 newnode = cmdutil.commit(ui, repo, commitfunc, [], opts)
856 newnode = cmdutil.commit(ui, repo, commitfunc, [], opts)
857 if not newnode:
857 if not newnode:
858 ui.status(_(b"nothing changed\n"))
858 ui.status(_(b"nothing changed\n"))
859 return 1
859 return 1
860 cmdutil.commitstatus(repo, newnode, branch, bheads)
860 cmdutil.commitstatus(repo, newnode, branch, bheads)
861
861
862 def nice(node):
862 def nice(node):
863 return b'%d:%s' % (repo.changelog.rev(node), short(node))
863 return b'%d:%s' % (repo.changelog.rev(node), short(node))
864
864
865 ui.status(
865 ui.status(
866 _(b'changeset %s backs out changeset %s\n')
866 _(b'changeset %s backs out changeset %s\n')
867 % (nice(repo.changelog.tip()), nice(node))
867 % (nice(repo.changelog.tip()), nice(node))
868 )
868 )
869 if opts.get(b'merge') and op1 != node:
869 if opts.get(b'merge') and op1 != node:
870 hg.clean(repo, op1, show_stats=False)
870 hg.clean(repo, op1, show_stats=False)
871 ui.status(
871 ui.status(
872 _(b'merging with changeset %s\n') % nice(repo.changelog.tip())
872 _(b'merging with changeset %s\n') % nice(repo.changelog.tip())
873 )
873 )
874 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
874 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
875 with ui.configoverride(overrides, b'backout'):
875 with ui.configoverride(overrides, b'backout'):
876 return hg.merge(repo[b'tip'])
876 return hg.merge(repo[b'tip'])
877 return 0
877 return 0
878
878
879
879
880 @command(
880 @command(
881 b'bisect',
881 b'bisect',
882 [
882 [
883 (b'r', b'reset', False, _(b'reset bisect state')),
883 (b'r', b'reset', False, _(b'reset bisect state')),
884 (b'g', b'good', False, _(b'mark changeset good')),
884 (b'g', b'good', False, _(b'mark changeset good')),
885 (b'b', b'bad', False, _(b'mark changeset bad')),
885 (b'b', b'bad', False, _(b'mark changeset bad')),
886 (b's', b'skip', False, _(b'skip testing changeset')),
886 (b's', b'skip', False, _(b'skip testing changeset')),
887 (b'e', b'extend', False, _(b'extend the bisect range')),
887 (b'e', b'extend', False, _(b'extend the bisect range')),
888 (
888 (
889 b'c',
889 b'c',
890 b'command',
890 b'command',
891 b'',
891 b'',
892 _(b'use command to check changeset state'),
892 _(b'use command to check changeset state'),
893 _(b'CMD'),
893 _(b'CMD'),
894 ),
894 ),
895 (b'U', b'noupdate', False, _(b'do not update to target')),
895 (b'U', b'noupdate', False, _(b'do not update to target')),
896 ],
896 ],
897 _(b"[-gbsr] [-U] [-c CMD] [REV]"),
897 _(b"[-gbsr] [-U] [-c CMD] [REV]"),
898 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
898 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
899 )
899 )
900 def bisect(
900 def bisect(
901 ui,
901 ui,
902 repo,
902 repo,
903 rev=None,
903 rev=None,
904 extra=None,
904 extra=None,
905 command=None,
905 command=None,
906 reset=None,
906 reset=None,
907 good=None,
907 good=None,
908 bad=None,
908 bad=None,
909 skip=None,
909 skip=None,
910 extend=None,
910 extend=None,
911 noupdate=None,
911 noupdate=None,
912 ):
912 ):
913 """subdivision search of changesets
913 """subdivision search of changesets
914
914
915 This command helps to find changesets which introduce problems. To
915 This command helps to find changesets which introduce problems. To
916 use, mark the earliest changeset you know exhibits the problem as
916 use, mark the earliest changeset you know exhibits the problem as
917 bad, then mark the latest changeset which is free from the problem
917 bad, then mark the latest changeset which is free from the problem
918 as good. Bisect will update your working directory to a revision
918 as good. Bisect will update your working directory to a revision
919 for testing (unless the -U/--noupdate option is specified). Once
919 for testing (unless the -U/--noupdate option is specified). Once
920 you have performed tests, mark the working directory as good or
920 you have performed tests, mark the working directory as good or
921 bad, and bisect will either update to another candidate changeset
921 bad, and bisect will either update to another candidate changeset
922 or announce that it has found the bad revision.
922 or announce that it has found the bad revision.
923
923
924 As a shortcut, you can also use the revision argument to mark a
924 As a shortcut, you can also use the revision argument to mark a
925 revision as good or bad without checking it out first.
925 revision as good or bad without checking it out first.
926
926
927 If you supply a command, it will be used for automatic bisection.
927 If you supply a command, it will be used for automatic bisection.
928 The environment variable HG_NODE will contain the ID of the
928 The environment variable HG_NODE will contain the ID of the
929 changeset being tested. The exit status of the command will be
929 changeset being tested. The exit status of the command will be
930 used to mark revisions as good or bad: status 0 means good, 125
930 used to mark revisions as good or bad: status 0 means good, 125
931 means to skip the revision, 127 (command not found) will abort the
931 means to skip the revision, 127 (command not found) will abort the
932 bisection, and any other non-zero exit status means the revision
932 bisection, and any other non-zero exit status means the revision
933 is bad.
933 is bad.
934
934
935 .. container:: verbose
935 .. container:: verbose
936
936
937 Some examples:
937 Some examples:
938
938
939 - start a bisection with known bad revision 34, and good revision 12::
939 - start a bisection with known bad revision 34, and good revision 12::
940
940
941 hg bisect --bad 34
941 hg bisect --bad 34
942 hg bisect --good 12
942 hg bisect --good 12
943
943
944 - advance the current bisection by marking current revision as good or
944 - advance the current bisection by marking current revision as good or
945 bad::
945 bad::
946
946
947 hg bisect --good
947 hg bisect --good
948 hg bisect --bad
948 hg bisect --bad
949
949
950 - mark the current revision, or a known revision, to be skipped (e.g. if
950 - mark the current revision, or a known revision, to be skipped (e.g. if
951 that revision is not usable because of another issue)::
951 that revision is not usable because of another issue)::
952
952
953 hg bisect --skip
953 hg bisect --skip
954 hg bisect --skip 23
954 hg bisect --skip 23
955
955
956 - skip all revisions that do not touch directories ``foo`` or ``bar``::
956 - skip all revisions that do not touch directories ``foo`` or ``bar``::
957
957
958 hg bisect --skip "!( file('path:foo') & file('path:bar') )"
958 hg bisect --skip "!( file('path:foo') & file('path:bar') )"
959
959
960 - forget the current bisection::
960 - forget the current bisection::
961
961
962 hg bisect --reset
962 hg bisect --reset
963
963
964 - use 'make && make tests' to automatically find the first broken
964 - use 'make && make tests' to automatically find the first broken
965 revision::
965 revision::
966
966
967 hg bisect --reset
967 hg bisect --reset
968 hg bisect --bad 34
968 hg bisect --bad 34
969 hg bisect --good 12
969 hg bisect --good 12
970 hg bisect --command "make && make tests"
970 hg bisect --command "make && make tests"
971
971
972 - see all changesets whose states are already known in the current
972 - see all changesets whose states are already known in the current
973 bisection::
973 bisection::
974
974
975 hg log -r "bisect(pruned)"
975 hg log -r "bisect(pruned)"
976
976
977 - see the changeset currently being bisected (especially useful
977 - see the changeset currently being bisected (especially useful
978 if running with -U/--noupdate)::
978 if running with -U/--noupdate)::
979
979
980 hg log -r "bisect(current)"
980 hg log -r "bisect(current)"
981
981
982 - see all changesets that took part in the current bisection::
982 - see all changesets that took part in the current bisection::
983
983
984 hg log -r "bisect(range)"
984 hg log -r "bisect(range)"
985
985
986 - you can even get a nice graph::
986 - you can even get a nice graph::
987
987
988 hg log --graph -r "bisect(range)"
988 hg log --graph -r "bisect(range)"
989
989
990 See :hg:`help revisions.bisect` for more about the `bisect()` predicate.
990 See :hg:`help revisions.bisect` for more about the `bisect()` predicate.
991
991
992 Returns 0 on success.
992 Returns 0 on success.
993 """
993 """
994 # backward compatibility
994 # backward compatibility
995 if rev in b"good bad reset init".split():
995 if rev in b"good bad reset init".split():
996 ui.warn(_(b"(use of 'hg bisect <cmd>' is deprecated)\n"))
996 ui.warn(_(b"(use of 'hg bisect <cmd>' is deprecated)\n"))
997 cmd, rev, extra = rev, extra, None
997 cmd, rev, extra = rev, extra, None
998 if cmd == b"good":
998 if cmd == b"good":
999 good = True
999 good = True
1000 elif cmd == b"bad":
1000 elif cmd == b"bad":
1001 bad = True
1001 bad = True
1002 else:
1002 else:
1003 reset = True
1003 reset = True
1004 elif extra:
1004 elif extra:
1005 raise error.Abort(_(b'incompatible arguments'))
1005 raise error.Abort(_(b'incompatible arguments'))
1006
1006
1007 incompatibles = {
1007 incompatibles = {
1008 b'--bad': bad,
1008 b'--bad': bad,
1009 b'--command': bool(command),
1009 b'--command': bool(command),
1010 b'--extend': extend,
1010 b'--extend': extend,
1011 b'--good': good,
1011 b'--good': good,
1012 b'--reset': reset,
1012 b'--reset': reset,
1013 b'--skip': skip,
1013 b'--skip': skip,
1014 }
1014 }
1015
1015
1016 enabled = [x for x in incompatibles if incompatibles[x]]
1016 enabled = [x for x in incompatibles if incompatibles[x]]
1017
1017
1018 if len(enabled) > 1:
1018 if len(enabled) > 1:
1019 raise error.Abort(
1019 raise error.Abort(
1020 _(b'%s and %s are incompatible') % tuple(sorted(enabled)[0:2])
1020 _(b'%s and %s are incompatible') % tuple(sorted(enabled)[0:2])
1021 )
1021 )
1022
1022
1023 if reset:
1023 if reset:
1024 hbisect.resetstate(repo)
1024 hbisect.resetstate(repo)
1025 return
1025 return
1026
1026
1027 state = hbisect.load_state(repo)
1027 state = hbisect.load_state(repo)
1028
1028
1029 # update state
1029 # update state
1030 if good or bad or skip:
1030 if good or bad or skip:
1031 if rev:
1031 if rev:
1032 nodes = [repo[i].node() for i in scmutil.revrange(repo, [rev])]
1032 nodes = [repo[i].node() for i in scmutil.revrange(repo, [rev])]
1033 else:
1033 else:
1034 nodes = [repo.lookup(b'.')]
1034 nodes = [repo.lookup(b'.')]
1035 if good:
1035 if good:
1036 state[b'good'] += nodes
1036 state[b'good'] += nodes
1037 elif bad:
1037 elif bad:
1038 state[b'bad'] += nodes
1038 state[b'bad'] += nodes
1039 elif skip:
1039 elif skip:
1040 state[b'skip'] += nodes
1040 state[b'skip'] += nodes
1041 hbisect.save_state(repo, state)
1041 hbisect.save_state(repo, state)
1042 if not (state[b'good'] and state[b'bad']):
1042 if not (state[b'good'] and state[b'bad']):
1043 return
1043 return
1044
1044
1045 def mayupdate(repo, node, show_stats=True):
1045 def mayupdate(repo, node, show_stats=True):
1046 """common used update sequence"""
1046 """common used update sequence"""
1047 if noupdate:
1047 if noupdate:
1048 return
1048 return
1049 cmdutil.checkunfinished(repo)
1049 cmdutil.checkunfinished(repo)
1050 cmdutil.bailifchanged(repo)
1050 cmdutil.bailifchanged(repo)
1051 return hg.clean(repo, node, show_stats=show_stats)
1051 return hg.clean(repo, node, show_stats=show_stats)
1052
1052
1053 displayer = logcmdutil.changesetdisplayer(ui, repo, {})
1053 displayer = logcmdutil.changesetdisplayer(ui, repo, {})
1054
1054
1055 if command:
1055 if command:
1056 changesets = 1
1056 changesets = 1
1057 if noupdate:
1057 if noupdate:
1058 try:
1058 try:
1059 node = state[b'current'][0]
1059 node = state[b'current'][0]
1060 except LookupError:
1060 except LookupError:
1061 raise error.Abort(
1061 raise error.Abort(
1062 _(
1062 _(
1063 b'current bisect revision is unknown - '
1063 b'current bisect revision is unknown - '
1064 b'start a new bisect to fix'
1064 b'start a new bisect to fix'
1065 )
1065 )
1066 )
1066 )
1067 else:
1067 else:
1068 node, p2 = repo.dirstate.parents()
1068 node, p2 = repo.dirstate.parents()
1069 if p2 != nullid:
1069 if p2 != nullid:
1070 raise error.Abort(_(b'current bisect revision is a merge'))
1070 raise error.Abort(_(b'current bisect revision is a merge'))
1071 if rev:
1071 if rev:
1072 node = repo[scmutil.revsingle(repo, rev, node)].node()
1072 node = repo[scmutil.revsingle(repo, rev, node)].node()
1073 with hbisect.restore_state(repo, state, node):
1073 with hbisect.restore_state(repo, state, node):
1074 while changesets:
1074 while changesets:
1075 # update state
1075 # update state
1076 state[b'current'] = [node]
1076 state[b'current'] = [node]
1077 hbisect.save_state(repo, state)
1077 hbisect.save_state(repo, state)
1078 status = ui.system(
1078 status = ui.system(
1079 command,
1079 command,
1080 environ={b'HG_NODE': hex(node)},
1080 environ={b'HG_NODE': hex(node)},
1081 blockedtag=b'bisect_check',
1081 blockedtag=b'bisect_check',
1082 )
1082 )
1083 if status == 125:
1083 if status == 125:
1084 transition = b"skip"
1084 transition = b"skip"
1085 elif status == 0:
1085 elif status == 0:
1086 transition = b"good"
1086 transition = b"good"
1087 # status < 0 means process was killed
1087 # status < 0 means process was killed
1088 elif status == 127:
1088 elif status == 127:
1089 raise error.Abort(_(b"failed to execute %s") % command)
1089 raise error.Abort(_(b"failed to execute %s") % command)
1090 elif status < 0:
1090 elif status < 0:
1091 raise error.Abort(_(b"%s killed") % command)
1091 raise error.Abort(_(b"%s killed") % command)
1092 else:
1092 else:
1093 transition = b"bad"
1093 transition = b"bad"
1094 state[transition].append(node)
1094 state[transition].append(node)
1095 ctx = repo[node]
1095 ctx = repo[node]
1096 ui.status(
1096 ui.status(
1097 _(b'changeset %d:%s: %s\n') % (ctx.rev(), ctx, transition)
1097 _(b'changeset %d:%s: %s\n') % (ctx.rev(), ctx, transition)
1098 )
1098 )
1099 hbisect.checkstate(state)
1099 hbisect.checkstate(state)
1100 # bisect
1100 # bisect
1101 nodes, changesets, bgood = hbisect.bisect(repo, state)
1101 nodes, changesets, bgood = hbisect.bisect(repo, state)
1102 # update to next check
1102 # update to next check
1103 node = nodes[0]
1103 node = nodes[0]
1104 mayupdate(repo, node, show_stats=False)
1104 mayupdate(repo, node, show_stats=False)
1105 hbisect.printresult(ui, repo, state, displayer, nodes, bgood)
1105 hbisect.printresult(ui, repo, state, displayer, nodes, bgood)
1106 return
1106 return
1107
1107
1108 hbisect.checkstate(state)
1108 hbisect.checkstate(state)
1109
1109
1110 # actually bisect
1110 # actually bisect
1111 nodes, changesets, good = hbisect.bisect(repo, state)
1111 nodes, changesets, good = hbisect.bisect(repo, state)
1112 if extend:
1112 if extend:
1113 if not changesets:
1113 if not changesets:
1114 extendnode = hbisect.extendrange(repo, state, nodes, good)
1114 extendnode = hbisect.extendrange(repo, state, nodes, good)
1115 if extendnode is not None:
1115 if extendnode is not None:
1116 ui.write(
1116 ui.write(
1117 _(b"Extending search to changeset %d:%s\n")
1117 _(b"Extending search to changeset %d:%s\n")
1118 % (extendnode.rev(), extendnode)
1118 % (extendnode.rev(), extendnode)
1119 )
1119 )
1120 state[b'current'] = [extendnode.node()]
1120 state[b'current'] = [extendnode.node()]
1121 hbisect.save_state(repo, state)
1121 hbisect.save_state(repo, state)
1122 return mayupdate(repo, extendnode.node())
1122 return mayupdate(repo, extendnode.node())
1123 raise error.Abort(_(b"nothing to extend"))
1123 raise error.Abort(_(b"nothing to extend"))
1124
1124
1125 if changesets == 0:
1125 if changesets == 0:
1126 hbisect.printresult(ui, repo, state, displayer, nodes, good)
1126 hbisect.printresult(ui, repo, state, displayer, nodes, good)
1127 else:
1127 else:
1128 assert len(nodes) == 1 # only a single node can be tested next
1128 assert len(nodes) == 1 # only a single node can be tested next
1129 node = nodes[0]
1129 node = nodes[0]
1130 # compute the approximate number of remaining tests
1130 # compute the approximate number of remaining tests
1131 tests, size = 0, 2
1131 tests, size = 0, 2
1132 while size <= changesets:
1132 while size <= changesets:
1133 tests, size = tests + 1, size * 2
1133 tests, size = tests + 1, size * 2
1134 rev = repo.changelog.rev(node)
1134 rev = repo.changelog.rev(node)
1135 ui.write(
1135 ui.write(
1136 _(
1136 _(
1137 b"Testing changeset %d:%s "
1137 b"Testing changeset %d:%s "
1138 b"(%d changesets remaining, ~%d tests)\n"
1138 b"(%d changesets remaining, ~%d tests)\n"
1139 )
1139 )
1140 % (rev, short(node), changesets, tests)
1140 % (rev, short(node), changesets, tests)
1141 )
1141 )
1142 state[b'current'] = [node]
1142 state[b'current'] = [node]
1143 hbisect.save_state(repo, state)
1143 hbisect.save_state(repo, state)
1144 return mayupdate(repo, node)
1144 return mayupdate(repo, node)
1145
1145
1146
1146
1147 @command(
1147 @command(
1148 b'bookmarks|bookmark',
1148 b'bookmarks|bookmark',
1149 [
1149 [
1150 (b'f', b'force', False, _(b'force')),
1150 (b'f', b'force', False, _(b'force')),
1151 (b'r', b'rev', b'', _(b'revision for bookmark action'), _(b'REV')),
1151 (b'r', b'rev', b'', _(b'revision for bookmark action'), _(b'REV')),
1152 (b'd', b'delete', False, _(b'delete a given bookmark')),
1152 (b'd', b'delete', False, _(b'delete a given bookmark')),
1153 (b'm', b'rename', b'', _(b'rename a given bookmark'), _(b'OLD')),
1153 (b'm', b'rename', b'', _(b'rename a given bookmark'), _(b'OLD')),
1154 (b'i', b'inactive', False, _(b'mark a bookmark inactive')),
1154 (b'i', b'inactive', False, _(b'mark a bookmark inactive')),
1155 (b'l', b'list', False, _(b'list existing bookmarks')),
1155 (b'l', b'list', False, _(b'list existing bookmarks')),
1156 ]
1156 ]
1157 + formatteropts,
1157 + formatteropts,
1158 _(b'hg bookmarks [OPTIONS]... [NAME]...'),
1158 _(b'hg bookmarks [OPTIONS]... [NAME]...'),
1159 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1159 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1160 )
1160 )
1161 def bookmark(ui, repo, *names, **opts):
1161 def bookmark(ui, repo, *names, **opts):
1162 '''create a new bookmark or list existing bookmarks
1162 '''create a new bookmark or list existing bookmarks
1163
1163
1164 Bookmarks are labels on changesets to help track lines of development.
1164 Bookmarks are labels on changesets to help track lines of development.
1165 Bookmarks are unversioned and can be moved, renamed and deleted.
1165 Bookmarks are unversioned and can be moved, renamed and deleted.
1166 Deleting or moving a bookmark has no effect on the associated changesets.
1166 Deleting or moving a bookmark has no effect on the associated changesets.
1167
1167
1168 Creating or updating to a bookmark causes it to be marked as 'active'.
1168 Creating or updating to a bookmark causes it to be marked as 'active'.
1169 The active bookmark is indicated with a '*'.
1169 The active bookmark is indicated with a '*'.
1170 When a commit is made, the active bookmark will advance to the new commit.
1170 When a commit is made, the active bookmark will advance to the new commit.
1171 A plain :hg:`update` will also advance an active bookmark, if possible.
1171 A plain :hg:`update` will also advance an active bookmark, if possible.
1172 Updating away from a bookmark will cause it to be deactivated.
1172 Updating away from a bookmark will cause it to be deactivated.
1173
1173
1174 Bookmarks can be pushed and pulled between repositories (see
1174 Bookmarks can be pushed and pulled between repositories (see
1175 :hg:`help push` and :hg:`help pull`). If a shared bookmark has
1175 :hg:`help push` and :hg:`help pull`). If a shared bookmark has
1176 diverged, a new 'divergent bookmark' of the form 'name@path' will
1176 diverged, a new 'divergent bookmark' of the form 'name@path' will
1177 be created. Using :hg:`merge` will resolve the divergence.
1177 be created. Using :hg:`merge` will resolve the divergence.
1178
1178
1179 Specifying bookmark as '.' to -m/-d/-l options is equivalent to specifying
1179 Specifying bookmark as '.' to -m/-d/-l options is equivalent to specifying
1180 the active bookmark's name.
1180 the active bookmark's name.
1181
1181
1182 A bookmark named '@' has the special property that :hg:`clone` will
1182 A bookmark named '@' has the special property that :hg:`clone` will
1183 check it out by default if it exists.
1183 check it out by default if it exists.
1184
1184
1185 .. container:: verbose
1185 .. container:: verbose
1186
1186
1187 Template:
1187 Template:
1188
1188
1189 The following keywords are supported in addition to the common template
1189 The following keywords are supported in addition to the common template
1190 keywords and functions such as ``{bookmark}``. See also
1190 keywords and functions such as ``{bookmark}``. See also
1191 :hg:`help templates`.
1191 :hg:`help templates`.
1192
1192
1193 :active: Boolean. True if the bookmark is active.
1193 :active: Boolean. True if the bookmark is active.
1194
1194
1195 Examples:
1195 Examples:
1196
1196
1197 - create an active bookmark for a new line of development::
1197 - create an active bookmark for a new line of development::
1198
1198
1199 hg book new-feature
1199 hg book new-feature
1200
1200
1201 - create an inactive bookmark as a place marker::
1201 - create an inactive bookmark as a place marker::
1202
1202
1203 hg book -i reviewed
1203 hg book -i reviewed
1204
1204
1205 - create an inactive bookmark on another changeset::
1205 - create an inactive bookmark on another changeset::
1206
1206
1207 hg book -r .^ tested
1207 hg book -r .^ tested
1208
1208
1209 - rename bookmark turkey to dinner::
1209 - rename bookmark turkey to dinner::
1210
1210
1211 hg book -m turkey dinner
1211 hg book -m turkey dinner
1212
1212
1213 - move the '@' bookmark from another branch::
1213 - move the '@' bookmark from another branch::
1214
1214
1215 hg book -f @
1215 hg book -f @
1216
1216
1217 - print only the active bookmark name::
1217 - print only the active bookmark name::
1218
1218
1219 hg book -ql .
1219 hg book -ql .
1220 '''
1220 '''
1221 opts = pycompat.byteskwargs(opts)
1221 opts = pycompat.byteskwargs(opts)
1222 force = opts.get(b'force')
1222 force = opts.get(b'force')
1223 rev = opts.get(b'rev')
1223 rev = opts.get(b'rev')
1224 inactive = opts.get(b'inactive') # meaning add/rename to inactive bookmark
1224 inactive = opts.get(b'inactive') # meaning add/rename to inactive bookmark
1225
1225
1226 action = cmdutil.check_at_most_one_arg(opts, b'delete', b'rename', b'list')
1226 action = cmdutil.check_at_most_one_arg(opts, b'delete', b'rename', b'list')
1227 if action:
1227 if action:
1228 cmdutil.check_incompatible_arguments(opts, action, [b'rev'])
1228 cmdutil.check_incompatible_arguments(opts, action, [b'rev'])
1229 elif names or rev:
1229 elif names or rev:
1230 action = b'add'
1230 action = b'add'
1231 elif inactive:
1231 elif inactive:
1232 action = b'inactive' # meaning deactivate
1232 action = b'inactive' # meaning deactivate
1233 else:
1233 else:
1234 action = b'list'
1234 action = b'list'
1235
1235
1236 cmdutil.check_incompatible_arguments(
1236 cmdutil.check_incompatible_arguments(
1237 opts, b'inactive', [b'delete', b'list']
1237 opts, b'inactive', [b'delete', b'list']
1238 )
1238 )
1239 if not names and action in {b'add', b'delete'}:
1239 if not names and action in {b'add', b'delete'}:
1240 raise error.Abort(_(b"bookmark name required"))
1240 raise error.Abort(_(b"bookmark name required"))
1241
1241
1242 if action in {b'add', b'delete', b'rename', b'inactive'}:
1242 if action in {b'add', b'delete', b'rename', b'inactive'}:
1243 with repo.wlock(), repo.lock(), repo.transaction(b'bookmark') as tr:
1243 with repo.wlock(), repo.lock(), repo.transaction(b'bookmark') as tr:
1244 if action == b'delete':
1244 if action == b'delete':
1245 names = pycompat.maplist(repo._bookmarks.expandname, names)
1245 names = pycompat.maplist(repo._bookmarks.expandname, names)
1246 bookmarks.delete(repo, tr, names)
1246 bookmarks.delete(repo, tr, names)
1247 elif action == b'rename':
1247 elif action == b'rename':
1248 if not names:
1248 if not names:
1249 raise error.Abort(_(b"new bookmark name required"))
1249 raise error.Abort(_(b"new bookmark name required"))
1250 elif len(names) > 1:
1250 elif len(names) > 1:
1251 raise error.Abort(_(b"only one new bookmark name allowed"))
1251 raise error.Abort(_(b"only one new bookmark name allowed"))
1252 oldname = repo._bookmarks.expandname(opts[b'rename'])
1252 oldname = repo._bookmarks.expandname(opts[b'rename'])
1253 bookmarks.rename(repo, tr, oldname, names[0], force, inactive)
1253 bookmarks.rename(repo, tr, oldname, names[0], force, inactive)
1254 elif action == b'add':
1254 elif action == b'add':
1255 bookmarks.addbookmarks(repo, tr, names, rev, force, inactive)
1255 bookmarks.addbookmarks(repo, tr, names, rev, force, inactive)
1256 elif action == b'inactive':
1256 elif action == b'inactive':
1257 if len(repo._bookmarks) == 0:
1257 if len(repo._bookmarks) == 0:
1258 ui.status(_(b"no bookmarks set\n"))
1258 ui.status(_(b"no bookmarks set\n"))
1259 elif not repo._activebookmark:
1259 elif not repo._activebookmark:
1260 ui.status(_(b"no active bookmark\n"))
1260 ui.status(_(b"no active bookmark\n"))
1261 else:
1261 else:
1262 bookmarks.deactivate(repo)
1262 bookmarks.deactivate(repo)
1263 elif action == b'list':
1263 elif action == b'list':
1264 names = pycompat.maplist(repo._bookmarks.expandname, names)
1264 names = pycompat.maplist(repo._bookmarks.expandname, names)
1265 with ui.formatter(b'bookmarks', opts) as fm:
1265 with ui.formatter(b'bookmarks', opts) as fm:
1266 bookmarks.printbookmarks(ui, repo, fm, names)
1266 bookmarks.printbookmarks(ui, repo, fm, names)
1267 else:
1267 else:
1268 raise error.ProgrammingError(b'invalid action: %s' % action)
1268 raise error.ProgrammingError(b'invalid action: %s' % action)
1269
1269
1270
1270
1271 @command(
1271 @command(
1272 b'branch',
1272 b'branch',
1273 [
1273 [
1274 (
1274 (
1275 b'f',
1275 b'f',
1276 b'force',
1276 b'force',
1277 None,
1277 None,
1278 _(b'set branch name even if it shadows an existing branch'),
1278 _(b'set branch name even if it shadows an existing branch'),
1279 ),
1279 ),
1280 (b'C', b'clean', None, _(b'reset branch name to parent branch name')),
1280 (b'C', b'clean', None, _(b'reset branch name to parent branch name')),
1281 (
1281 (
1282 b'r',
1282 b'r',
1283 b'rev',
1283 b'rev',
1284 [],
1284 [],
1285 _(b'change branches of the given revs (EXPERIMENTAL)'),
1285 _(b'change branches of the given revs (EXPERIMENTAL)'),
1286 ),
1286 ),
1287 ],
1287 ],
1288 _(b'[-fC] [NAME]'),
1288 _(b'[-fC] [NAME]'),
1289 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1289 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1290 )
1290 )
1291 def branch(ui, repo, label=None, **opts):
1291 def branch(ui, repo, label=None, **opts):
1292 """set or show the current branch name
1292 """set or show the current branch name
1293
1293
1294 .. note::
1294 .. note::
1295
1295
1296 Branch names are permanent and global. Use :hg:`bookmark` to create a
1296 Branch names are permanent and global. Use :hg:`bookmark` to create a
1297 light-weight bookmark instead. See :hg:`help glossary` for more
1297 light-weight bookmark instead. See :hg:`help glossary` for more
1298 information about named branches and bookmarks.
1298 information about named branches and bookmarks.
1299
1299
1300 With no argument, show the current branch name. With one argument,
1300 With no argument, show the current branch name. With one argument,
1301 set the working directory branch name (the branch will not exist
1301 set the working directory branch name (the branch will not exist
1302 in the repository until the next commit). Standard practice
1302 in the repository until the next commit). Standard practice
1303 recommends that primary development take place on the 'default'
1303 recommends that primary development take place on the 'default'
1304 branch.
1304 branch.
1305
1305
1306 Unless -f/--force is specified, branch will not let you set a
1306 Unless -f/--force is specified, branch will not let you set a
1307 branch name that already exists.
1307 branch name that already exists.
1308
1308
1309 Use -C/--clean to reset the working directory branch to that of
1309 Use -C/--clean to reset the working directory branch to that of
1310 the parent of the working directory, negating a previous branch
1310 the parent of the working directory, negating a previous branch
1311 change.
1311 change.
1312
1312
1313 Use the command :hg:`update` to switch to an existing branch. Use
1313 Use the command :hg:`update` to switch to an existing branch. Use
1314 :hg:`commit --close-branch` to mark this branch head as closed.
1314 :hg:`commit --close-branch` to mark this branch head as closed.
1315 When all heads of a branch are closed, the branch will be
1315 When all heads of a branch are closed, the branch will be
1316 considered closed.
1316 considered closed.
1317
1317
1318 Returns 0 on success.
1318 Returns 0 on success.
1319 """
1319 """
1320 opts = pycompat.byteskwargs(opts)
1320 opts = pycompat.byteskwargs(opts)
1321 revs = opts.get(b'rev')
1321 revs = opts.get(b'rev')
1322 if label:
1322 if label:
1323 label = label.strip()
1323 label = label.strip()
1324
1324
1325 if not opts.get(b'clean') and not label:
1325 if not opts.get(b'clean') and not label:
1326 if revs:
1326 if revs:
1327 raise error.Abort(_(b"no branch name specified for the revisions"))
1327 raise error.Abort(_(b"no branch name specified for the revisions"))
1328 ui.write(b"%s\n" % repo.dirstate.branch())
1328 ui.write(b"%s\n" % repo.dirstate.branch())
1329 return
1329 return
1330
1330
1331 with repo.wlock():
1331 with repo.wlock():
1332 if opts.get(b'clean'):
1332 if opts.get(b'clean'):
1333 label = repo[b'.'].branch()
1333 label = repo[b'.'].branch()
1334 repo.dirstate.setbranch(label)
1334 repo.dirstate.setbranch(label)
1335 ui.status(_(b'reset working directory to branch %s\n') % label)
1335 ui.status(_(b'reset working directory to branch %s\n') % label)
1336 elif label:
1336 elif label:
1337
1337
1338 scmutil.checknewlabel(repo, label, b'branch')
1338 scmutil.checknewlabel(repo, label, b'branch')
1339 if revs:
1339 if revs:
1340 return cmdutil.changebranch(ui, repo, revs, label, opts)
1340 return cmdutil.changebranch(ui, repo, revs, label, opts)
1341
1341
1342 if not opts.get(b'force') and label in repo.branchmap():
1342 if not opts.get(b'force') and label in repo.branchmap():
1343 if label not in [p.branch() for p in repo[None].parents()]:
1343 if label not in [p.branch() for p in repo[None].parents()]:
1344 raise error.Abort(
1344 raise error.Abort(
1345 _(b'a branch of the same name already exists'),
1345 _(b'a branch of the same name already exists'),
1346 # i18n: "it" refers to an existing branch
1346 # i18n: "it" refers to an existing branch
1347 hint=_(b"use 'hg update' to switch to it"),
1347 hint=_(b"use 'hg update' to switch to it"),
1348 )
1348 )
1349
1349
1350 repo.dirstate.setbranch(label)
1350 repo.dirstate.setbranch(label)
1351 ui.status(_(b'marked working directory as branch %s\n') % label)
1351 ui.status(_(b'marked working directory as branch %s\n') % label)
1352
1352
1353 # find any open named branches aside from default
1353 # find any open named branches aside from default
1354 for n, h, t, c in repo.branchmap().iterbranches():
1354 for n, h, t, c in repo.branchmap().iterbranches():
1355 if n != b"default" and not c:
1355 if n != b"default" and not c:
1356 return 0
1356 return 0
1357 ui.status(
1357 ui.status(
1358 _(
1358 _(
1359 b'(branches are permanent and global, '
1359 b'(branches are permanent and global, '
1360 b'did you want a bookmark?)\n'
1360 b'did you want a bookmark?)\n'
1361 )
1361 )
1362 )
1362 )
1363
1363
1364
1364
1365 @command(
1365 @command(
1366 b'branches',
1366 b'branches',
1367 [
1367 [
1368 (
1368 (
1369 b'a',
1369 b'a',
1370 b'active',
1370 b'active',
1371 False,
1371 False,
1372 _(b'show only branches that have unmerged heads (DEPRECATED)'),
1372 _(b'show only branches that have unmerged heads (DEPRECATED)'),
1373 ),
1373 ),
1374 (b'c', b'closed', False, _(b'show normal and closed branches')),
1374 (b'c', b'closed', False, _(b'show normal and closed branches')),
1375 (b'r', b'rev', [], _(b'show branch name(s) of the given rev')),
1375 (b'r', b'rev', [], _(b'show branch name(s) of the given rev')),
1376 ]
1376 ]
1377 + formatteropts,
1377 + formatteropts,
1378 _(b'[-c]'),
1378 _(b'[-c]'),
1379 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1379 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1380 intents={INTENT_READONLY},
1380 intents={INTENT_READONLY},
1381 )
1381 )
1382 def branches(ui, repo, active=False, closed=False, **opts):
1382 def branches(ui, repo, active=False, closed=False, **opts):
1383 """list repository named branches
1383 """list repository named branches
1384
1384
1385 List the repository's named branches, indicating which ones are
1385 List the repository's named branches, indicating which ones are
1386 inactive. If -c/--closed is specified, also list branches which have
1386 inactive. If -c/--closed is specified, also list branches which have
1387 been marked closed (see :hg:`commit --close-branch`).
1387 been marked closed (see :hg:`commit --close-branch`).
1388
1388
1389 Use the command :hg:`update` to switch to an existing branch.
1389 Use the command :hg:`update` to switch to an existing branch.
1390
1390
1391 .. container:: verbose
1391 .. container:: verbose
1392
1392
1393 Template:
1393 Template:
1394
1394
1395 The following keywords are supported in addition to the common template
1395 The following keywords are supported in addition to the common template
1396 keywords and functions such as ``{branch}``. See also
1396 keywords and functions such as ``{branch}``. See also
1397 :hg:`help templates`.
1397 :hg:`help templates`.
1398
1398
1399 :active: Boolean. True if the branch is active.
1399 :active: Boolean. True if the branch is active.
1400 :closed: Boolean. True if the branch is closed.
1400 :closed: Boolean. True if the branch is closed.
1401 :current: Boolean. True if it is the current branch.
1401 :current: Boolean. True if it is the current branch.
1402
1402
1403 Returns 0.
1403 Returns 0.
1404 """
1404 """
1405
1405
1406 opts = pycompat.byteskwargs(opts)
1406 opts = pycompat.byteskwargs(opts)
1407 revs = opts.get(b'rev')
1407 revs = opts.get(b'rev')
1408 selectedbranches = None
1408 selectedbranches = None
1409 if revs:
1409 if revs:
1410 revs = scmutil.revrange(repo, revs)
1410 revs = scmutil.revrange(repo, revs)
1411 getbi = repo.revbranchcache().branchinfo
1411 getbi = repo.revbranchcache().branchinfo
1412 selectedbranches = {getbi(r)[0] for r in revs}
1412 selectedbranches = {getbi(r)[0] for r in revs}
1413
1413
1414 ui.pager(b'branches')
1414 ui.pager(b'branches')
1415 fm = ui.formatter(b'branches', opts)
1415 fm = ui.formatter(b'branches', opts)
1416 hexfunc = fm.hexfunc
1416 hexfunc = fm.hexfunc
1417
1417
1418 allheads = set(repo.heads())
1418 allheads = set(repo.heads())
1419 branches = []
1419 branches = []
1420 for tag, heads, tip, isclosed in repo.branchmap().iterbranches():
1420 for tag, heads, tip, isclosed in repo.branchmap().iterbranches():
1421 if selectedbranches is not None and tag not in selectedbranches:
1421 if selectedbranches is not None and tag not in selectedbranches:
1422 continue
1422 continue
1423 isactive = False
1423 isactive = False
1424 if not isclosed:
1424 if not isclosed:
1425 openheads = set(repo.branchmap().iteropen(heads))
1425 openheads = set(repo.branchmap().iteropen(heads))
1426 isactive = bool(openheads & allheads)
1426 isactive = bool(openheads & allheads)
1427 branches.append((tag, repo[tip], isactive, not isclosed))
1427 branches.append((tag, repo[tip], isactive, not isclosed))
1428 branches.sort(key=lambda i: (i[2], i[1].rev(), i[0], i[3]), reverse=True)
1428 branches.sort(key=lambda i: (i[2], i[1].rev(), i[0], i[3]), reverse=True)
1429
1429
1430 for tag, ctx, isactive, isopen in branches:
1430 for tag, ctx, isactive, isopen in branches:
1431 if active and not isactive:
1431 if active and not isactive:
1432 continue
1432 continue
1433 if isactive:
1433 if isactive:
1434 label = b'branches.active'
1434 label = b'branches.active'
1435 notice = b''
1435 notice = b''
1436 elif not isopen:
1436 elif not isopen:
1437 if not closed:
1437 if not closed:
1438 continue
1438 continue
1439 label = b'branches.closed'
1439 label = b'branches.closed'
1440 notice = _(b' (closed)')
1440 notice = _(b' (closed)')
1441 else:
1441 else:
1442 label = b'branches.inactive'
1442 label = b'branches.inactive'
1443 notice = _(b' (inactive)')
1443 notice = _(b' (inactive)')
1444 current = tag == repo.dirstate.branch()
1444 current = tag == repo.dirstate.branch()
1445 if current:
1445 if current:
1446 label = b'branches.current'
1446 label = b'branches.current'
1447
1447
1448 fm.startitem()
1448 fm.startitem()
1449 fm.write(b'branch', b'%s', tag, label=label)
1449 fm.write(b'branch', b'%s', tag, label=label)
1450 rev = ctx.rev()
1450 rev = ctx.rev()
1451 padsize = max(31 - len(b"%d" % rev) - encoding.colwidth(tag), 0)
1451 padsize = max(31 - len(b"%d" % rev) - encoding.colwidth(tag), 0)
1452 fmt = b' ' * padsize + b' %d:%s'
1452 fmt = b' ' * padsize + b' %d:%s'
1453 fm.condwrite(
1453 fm.condwrite(
1454 not ui.quiet,
1454 not ui.quiet,
1455 b'rev node',
1455 b'rev node',
1456 fmt,
1456 fmt,
1457 rev,
1457 rev,
1458 hexfunc(ctx.node()),
1458 hexfunc(ctx.node()),
1459 label=b'log.changeset changeset.%s' % ctx.phasestr(),
1459 label=b'log.changeset changeset.%s' % ctx.phasestr(),
1460 )
1460 )
1461 fm.context(ctx=ctx)
1461 fm.context(ctx=ctx)
1462 fm.data(active=isactive, closed=not isopen, current=current)
1462 fm.data(active=isactive, closed=not isopen, current=current)
1463 if not ui.quiet:
1463 if not ui.quiet:
1464 fm.plain(notice)
1464 fm.plain(notice)
1465 fm.plain(b'\n')
1465 fm.plain(b'\n')
1466 fm.end()
1466 fm.end()
1467
1467
1468
1468
1469 @command(
1469 @command(
1470 b'bundle',
1470 b'bundle',
1471 [
1471 [
1472 (
1472 (
1473 b'f',
1473 b'f',
1474 b'force',
1474 b'force',
1475 None,
1475 None,
1476 _(b'run even when the destination is unrelated'),
1476 _(b'run even when the destination is unrelated'),
1477 ),
1477 ),
1478 (
1478 (
1479 b'r',
1479 b'r',
1480 b'rev',
1480 b'rev',
1481 [],
1481 [],
1482 _(b'a changeset intended to be added to the destination'),
1482 _(b'a changeset intended to be added to the destination'),
1483 _(b'REV'),
1483 _(b'REV'),
1484 ),
1484 ),
1485 (
1485 (
1486 b'b',
1486 b'b',
1487 b'branch',
1487 b'branch',
1488 [],
1488 [],
1489 _(b'a specific branch you would like to bundle'),
1489 _(b'a specific branch you would like to bundle'),
1490 _(b'BRANCH'),
1490 _(b'BRANCH'),
1491 ),
1491 ),
1492 (
1492 (
1493 b'',
1493 b'',
1494 b'base',
1494 b'base',
1495 [],
1495 [],
1496 _(b'a base changeset assumed to be available at the destination'),
1496 _(b'a base changeset assumed to be available at the destination'),
1497 _(b'REV'),
1497 _(b'REV'),
1498 ),
1498 ),
1499 (b'a', b'all', None, _(b'bundle all changesets in the repository')),
1499 (b'a', b'all', None, _(b'bundle all changesets in the repository')),
1500 (
1500 (
1501 b't',
1501 b't',
1502 b'type',
1502 b'type',
1503 b'bzip2',
1503 b'bzip2',
1504 _(b'bundle compression type to use'),
1504 _(b'bundle compression type to use'),
1505 _(b'TYPE'),
1505 _(b'TYPE'),
1506 ),
1506 ),
1507 ]
1507 ]
1508 + remoteopts,
1508 + remoteopts,
1509 _(b'[-f] [-t BUNDLESPEC] [-a] [-r REV]... [--base REV]... FILE [DEST]'),
1509 _(b'[-f] [-t BUNDLESPEC] [-a] [-r REV]... [--base REV]... FILE [DEST]'),
1510 helpcategory=command.CATEGORY_IMPORT_EXPORT,
1510 helpcategory=command.CATEGORY_IMPORT_EXPORT,
1511 )
1511 )
1512 def bundle(ui, repo, fname, dest=None, **opts):
1512 def bundle(ui, repo, fname, dest=None, **opts):
1513 """create a bundle file
1513 """create a bundle file
1514
1514
1515 Generate a bundle file containing data to be transferred to another
1515 Generate a bundle file containing data to be transferred to another
1516 repository.
1516 repository.
1517
1517
1518 To create a bundle containing all changesets, use -a/--all
1518 To create a bundle containing all changesets, use -a/--all
1519 (or --base null). Otherwise, hg assumes the destination will have
1519 (or --base null). Otherwise, hg assumes the destination will have
1520 all the nodes you specify with --base parameters. Otherwise, hg
1520 all the nodes you specify with --base parameters. Otherwise, hg
1521 will assume the repository has all the nodes in destination, or
1521 will assume the repository has all the nodes in destination, or
1522 default-push/default if no destination is specified, where destination
1522 default-push/default if no destination is specified, where destination
1523 is the repository you provide through DEST option.
1523 is the repository you provide through DEST option.
1524
1524
1525 You can change bundle format with the -t/--type option. See
1525 You can change bundle format with the -t/--type option. See
1526 :hg:`help bundlespec` for documentation on this format. By default,
1526 :hg:`help bundlespec` for documentation on this format. By default,
1527 the most appropriate format is used and compression defaults to
1527 the most appropriate format is used and compression defaults to
1528 bzip2.
1528 bzip2.
1529
1529
1530 The bundle file can then be transferred using conventional means
1530 The bundle file can then be transferred using conventional means
1531 and applied to another repository with the unbundle or pull
1531 and applied to another repository with the unbundle or pull
1532 command. This is useful when direct push and pull are not
1532 command. This is useful when direct push and pull are not
1533 available or when exporting an entire repository is undesirable.
1533 available or when exporting an entire repository is undesirable.
1534
1534
1535 Applying bundles preserves all changeset contents including
1535 Applying bundles preserves all changeset contents including
1536 permissions, copy/rename information, and revision history.
1536 permissions, copy/rename information, and revision history.
1537
1537
1538 Returns 0 on success, 1 if no changes found.
1538 Returns 0 on success, 1 if no changes found.
1539 """
1539 """
1540 opts = pycompat.byteskwargs(opts)
1540 opts = pycompat.byteskwargs(opts)
1541 revs = None
1541 revs = None
1542 if b'rev' in opts:
1542 if b'rev' in opts:
1543 revstrings = opts[b'rev']
1543 revstrings = opts[b'rev']
1544 revs = scmutil.revrange(repo, revstrings)
1544 revs = scmutil.revrange(repo, revstrings)
1545 if revstrings and not revs:
1545 if revstrings and not revs:
1546 raise error.Abort(_(b'no commits to bundle'))
1546 raise error.Abort(_(b'no commits to bundle'))
1547
1547
1548 bundletype = opts.get(b'type', b'bzip2').lower()
1548 bundletype = opts.get(b'type', b'bzip2').lower()
1549 try:
1549 try:
1550 bundlespec = exchange.parsebundlespec(repo, bundletype, strict=False)
1550 bundlespec = exchange.parsebundlespec(repo, bundletype, strict=False)
1551 except error.UnsupportedBundleSpecification as e:
1551 except error.UnsupportedBundleSpecification as e:
1552 raise error.Abort(
1552 raise error.Abort(
1553 pycompat.bytestr(e),
1553 pycompat.bytestr(e),
1554 hint=_(b"see 'hg help bundlespec' for supported values for --type"),
1554 hint=_(b"see 'hg help bundlespec' for supported values for --type"),
1555 )
1555 )
1556 cgversion = bundlespec.contentopts[b"cg.version"]
1556 cgversion = bundlespec.contentopts[b"cg.version"]
1557
1557
1558 # Packed bundles are a pseudo bundle format for now.
1558 # Packed bundles are a pseudo bundle format for now.
1559 if cgversion == b's1':
1559 if cgversion == b's1':
1560 raise error.Abort(
1560 raise error.Abort(
1561 _(b'packed bundles cannot be produced by "hg bundle"'),
1561 _(b'packed bundles cannot be produced by "hg bundle"'),
1562 hint=_(b"use 'hg debugcreatestreamclonebundle'"),
1562 hint=_(b"use 'hg debugcreatestreamclonebundle'"),
1563 )
1563 )
1564
1564
1565 if opts.get(b'all'):
1565 if opts.get(b'all'):
1566 if dest:
1566 if dest:
1567 raise error.Abort(
1567 raise error.Abort(
1568 _(b"--all is incompatible with specifying a destination")
1568 _(b"--all is incompatible with specifying a destination")
1569 )
1569 )
1570 if opts.get(b'base'):
1570 if opts.get(b'base'):
1571 ui.warn(_(b"ignoring --base because --all was specified\n"))
1571 ui.warn(_(b"ignoring --base because --all was specified\n"))
1572 base = [nullrev]
1572 base = [nullrev]
1573 else:
1573 else:
1574 base = scmutil.revrange(repo, opts.get(b'base'))
1574 base = scmutil.revrange(repo, opts.get(b'base'))
1575 if cgversion not in changegroup.supportedoutgoingversions(repo):
1575 if cgversion not in changegroup.supportedoutgoingversions(repo):
1576 raise error.Abort(
1576 raise error.Abort(
1577 _(b"repository does not support bundle version %s") % cgversion
1577 _(b"repository does not support bundle version %s") % cgversion
1578 )
1578 )
1579
1579
1580 if base:
1580 if base:
1581 if dest:
1581 if dest:
1582 raise error.Abort(
1582 raise error.Abort(
1583 _(b"--base is incompatible with specifying a destination")
1583 _(b"--base is incompatible with specifying a destination")
1584 )
1584 )
1585 common = [repo[rev].node() for rev in base]
1585 common = [repo[rev].node() for rev in base]
1586 heads = [repo[r].node() for r in revs] if revs else None
1586 heads = [repo[r].node() for r in revs] if revs else None
1587 outgoing = discovery.outgoing(repo, common, heads)
1587 outgoing = discovery.outgoing(repo, common, heads)
1588 else:
1588 else:
1589 dest = ui.expandpath(dest or b'default-push', dest or b'default')
1589 dest = ui.expandpath(dest or b'default-push', dest or b'default')
1590 dest, branches = hg.parseurl(dest, opts.get(b'branch'))
1590 dest, branches = hg.parseurl(dest, opts.get(b'branch'))
1591 other = hg.peer(repo, opts, dest)
1591 other = hg.peer(repo, opts, dest)
1592 revs = [repo[r].hex() for r in revs]
1592 revs = [repo[r].hex() for r in revs]
1593 revs, checkout = hg.addbranchrevs(repo, repo, branches, revs)
1593 revs, checkout = hg.addbranchrevs(repo, repo, branches, revs)
1594 heads = revs and pycompat.maplist(repo.lookup, revs) or revs
1594 heads = revs and pycompat.maplist(repo.lookup, revs) or revs
1595 outgoing = discovery.findcommonoutgoing(
1595 outgoing = discovery.findcommonoutgoing(
1596 repo,
1596 repo,
1597 other,
1597 other,
1598 onlyheads=heads,
1598 onlyheads=heads,
1599 force=opts.get(b'force'),
1599 force=opts.get(b'force'),
1600 portable=True,
1600 portable=True,
1601 )
1601 )
1602
1602
1603 if not outgoing.missing:
1603 if not outgoing.missing:
1604 scmutil.nochangesfound(ui, repo, not base and outgoing.excluded)
1604 scmutil.nochangesfound(ui, repo, not base and outgoing.excluded)
1605 return 1
1605 return 1
1606
1606
1607 if cgversion == b'01': # bundle1
1607 if cgversion == b'01': # bundle1
1608 bversion = b'HG10' + bundlespec.wirecompression
1608 bversion = b'HG10' + bundlespec.wirecompression
1609 bcompression = None
1609 bcompression = None
1610 elif cgversion in (b'02', b'03'):
1610 elif cgversion in (b'02', b'03'):
1611 bversion = b'HG20'
1611 bversion = b'HG20'
1612 bcompression = bundlespec.wirecompression
1612 bcompression = bundlespec.wirecompression
1613 else:
1613 else:
1614 raise error.ProgrammingError(
1614 raise error.ProgrammingError(
1615 b'bundle: unexpected changegroup version %s' % cgversion
1615 b'bundle: unexpected changegroup version %s' % cgversion
1616 )
1616 )
1617
1617
1618 # TODO compression options should be derived from bundlespec parsing.
1618 # TODO compression options should be derived from bundlespec parsing.
1619 # This is a temporary hack to allow adjusting bundle compression
1619 # This is a temporary hack to allow adjusting bundle compression
1620 # level without a) formalizing the bundlespec changes to declare it
1620 # level without a) formalizing the bundlespec changes to declare it
1621 # b) introducing a command flag.
1621 # b) introducing a command flag.
1622 compopts = {}
1622 compopts = {}
1623 complevel = ui.configint(
1623 complevel = ui.configint(
1624 b'experimental', b'bundlecomplevel.' + bundlespec.compression
1624 b'experimental', b'bundlecomplevel.' + bundlespec.compression
1625 )
1625 )
1626 if complevel is None:
1626 if complevel is None:
1627 complevel = ui.configint(b'experimental', b'bundlecomplevel')
1627 complevel = ui.configint(b'experimental', b'bundlecomplevel')
1628 if complevel is not None:
1628 if complevel is not None:
1629 compopts[b'level'] = complevel
1629 compopts[b'level'] = complevel
1630
1630
1631 # Allow overriding the bundling of obsmarker in phases through
1631 # Allow overriding the bundling of obsmarker in phases through
1632 # configuration while we don't have a bundle version that include them
1632 # configuration while we don't have a bundle version that include them
1633 if repo.ui.configbool(b'experimental', b'evolution.bundle-obsmarker'):
1633 if repo.ui.configbool(b'experimental', b'evolution.bundle-obsmarker'):
1634 bundlespec.contentopts[b'obsolescence'] = True
1634 bundlespec.contentopts[b'obsolescence'] = True
1635 if repo.ui.configbool(b'experimental', b'bundle-phases'):
1635 if repo.ui.configbool(b'experimental', b'bundle-phases'):
1636 bundlespec.contentopts[b'phases'] = True
1636 bundlespec.contentopts[b'phases'] = True
1637
1637
1638 bundle2.writenewbundle(
1638 bundle2.writenewbundle(
1639 ui,
1639 ui,
1640 repo,
1640 repo,
1641 b'bundle',
1641 b'bundle',
1642 fname,
1642 fname,
1643 bversion,
1643 bversion,
1644 outgoing,
1644 outgoing,
1645 bundlespec.contentopts,
1645 bundlespec.contentopts,
1646 compression=bcompression,
1646 compression=bcompression,
1647 compopts=compopts,
1647 compopts=compopts,
1648 )
1648 )
1649
1649
1650
1650
1651 @command(
1651 @command(
1652 b'cat',
1652 b'cat',
1653 [
1653 [
1654 (
1654 (
1655 b'o',
1655 b'o',
1656 b'output',
1656 b'output',
1657 b'',
1657 b'',
1658 _(b'print output to file with formatted name'),
1658 _(b'print output to file with formatted name'),
1659 _(b'FORMAT'),
1659 _(b'FORMAT'),
1660 ),
1660 ),
1661 (b'r', b'rev', b'', _(b'print the given revision'), _(b'REV')),
1661 (b'r', b'rev', b'', _(b'print the given revision'), _(b'REV')),
1662 (b'', b'decode', None, _(b'apply any matching decode filter')),
1662 (b'', b'decode', None, _(b'apply any matching decode filter')),
1663 ]
1663 ]
1664 + walkopts
1664 + walkopts
1665 + formatteropts,
1665 + formatteropts,
1666 _(b'[OPTION]... FILE...'),
1666 _(b'[OPTION]... FILE...'),
1667 helpcategory=command.CATEGORY_FILE_CONTENTS,
1667 helpcategory=command.CATEGORY_FILE_CONTENTS,
1668 inferrepo=True,
1668 inferrepo=True,
1669 intents={INTENT_READONLY},
1669 intents={INTENT_READONLY},
1670 )
1670 )
1671 def cat(ui, repo, file1, *pats, **opts):
1671 def cat(ui, repo, file1, *pats, **opts):
1672 """output the current or given revision of files
1672 """output the current or given revision of files
1673
1673
1674 Print the specified files as they were at the given revision. If
1674 Print the specified files as they were at the given revision. If
1675 no revision is given, the parent of the working directory is used.
1675 no revision is given, the parent of the working directory is used.
1676
1676
1677 Output may be to a file, in which case the name of the file is
1677 Output may be to a file, in which case the name of the file is
1678 given using a template string. See :hg:`help templates`. In addition
1678 given using a template string. See :hg:`help templates`. In addition
1679 to the common template keywords, the following formatting rules are
1679 to the common template keywords, the following formatting rules are
1680 supported:
1680 supported:
1681
1681
1682 :``%%``: literal "%" character
1682 :``%%``: literal "%" character
1683 :``%s``: basename of file being printed
1683 :``%s``: basename of file being printed
1684 :``%d``: dirname of file being printed, or '.' if in repository root
1684 :``%d``: dirname of file being printed, or '.' if in repository root
1685 :``%p``: root-relative path name of file being printed
1685 :``%p``: root-relative path name of file being printed
1686 :``%H``: changeset hash (40 hexadecimal digits)
1686 :``%H``: changeset hash (40 hexadecimal digits)
1687 :``%R``: changeset revision number
1687 :``%R``: changeset revision number
1688 :``%h``: short-form changeset hash (12 hexadecimal digits)
1688 :``%h``: short-form changeset hash (12 hexadecimal digits)
1689 :``%r``: zero-padded changeset revision number
1689 :``%r``: zero-padded changeset revision number
1690 :``%b``: basename of the exporting repository
1690 :``%b``: basename of the exporting repository
1691 :``\\``: literal "\\" character
1691 :``\\``: literal "\\" character
1692
1692
1693 .. container:: verbose
1693 .. container:: verbose
1694
1694
1695 Template:
1695 Template:
1696
1696
1697 The following keywords are supported in addition to the common template
1697 The following keywords are supported in addition to the common template
1698 keywords and functions. See also :hg:`help templates`.
1698 keywords and functions. See also :hg:`help templates`.
1699
1699
1700 :data: String. File content.
1700 :data: String. File content.
1701 :path: String. Repository-absolute path of the file.
1701 :path: String. Repository-absolute path of the file.
1702
1702
1703 Returns 0 on success.
1703 Returns 0 on success.
1704 """
1704 """
1705 opts = pycompat.byteskwargs(opts)
1705 opts = pycompat.byteskwargs(opts)
1706 rev = opts.get(b'rev')
1706 rev = opts.get(b'rev')
1707 if rev:
1707 if rev:
1708 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
1708 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
1709 ctx = scmutil.revsingle(repo, rev)
1709 ctx = scmutil.revsingle(repo, rev)
1710 m = scmutil.match(ctx, (file1,) + pats, opts)
1710 m = scmutil.match(ctx, (file1,) + pats, opts)
1711 fntemplate = opts.pop(b'output', b'')
1711 fntemplate = opts.pop(b'output', b'')
1712 if cmdutil.isstdiofilename(fntemplate):
1712 if cmdutil.isstdiofilename(fntemplate):
1713 fntemplate = b''
1713 fntemplate = b''
1714
1714
1715 if fntemplate:
1715 if fntemplate:
1716 fm = formatter.nullformatter(ui, b'cat', opts)
1716 fm = formatter.nullformatter(ui, b'cat', opts)
1717 else:
1717 else:
1718 ui.pager(b'cat')
1718 ui.pager(b'cat')
1719 fm = ui.formatter(b'cat', opts)
1719 fm = ui.formatter(b'cat', opts)
1720 with fm:
1720 with fm:
1721 return cmdutil.cat(
1721 return cmdutil.cat(
1722 ui, repo, ctx, m, fm, fntemplate, b'', **pycompat.strkwargs(opts)
1722 ui, repo, ctx, m, fm, fntemplate, b'', **pycompat.strkwargs(opts)
1723 )
1723 )
1724
1724
1725
1725
1726 @command(
1726 @command(
1727 b'clone',
1727 b'clone',
1728 [
1728 [
1729 (
1729 (
1730 b'U',
1730 b'U',
1731 b'noupdate',
1731 b'noupdate',
1732 None,
1732 None,
1733 _(
1733 _(
1734 b'the clone will include an empty working '
1734 b'the clone will include an empty working '
1735 b'directory (only a repository)'
1735 b'directory (only a repository)'
1736 ),
1736 ),
1737 ),
1737 ),
1738 (
1738 (
1739 b'u',
1739 b'u',
1740 b'updaterev',
1740 b'updaterev',
1741 b'',
1741 b'',
1742 _(b'revision, tag, or branch to check out'),
1742 _(b'revision, tag, or branch to check out'),
1743 _(b'REV'),
1743 _(b'REV'),
1744 ),
1744 ),
1745 (
1745 (
1746 b'r',
1746 b'r',
1747 b'rev',
1747 b'rev',
1748 [],
1748 [],
1749 _(
1749 _(
1750 b'do not clone everything, but include this changeset'
1750 b'do not clone everything, but include this changeset'
1751 b' and its ancestors'
1751 b' and its ancestors'
1752 ),
1752 ),
1753 _(b'REV'),
1753 _(b'REV'),
1754 ),
1754 ),
1755 (
1755 (
1756 b'b',
1756 b'b',
1757 b'branch',
1757 b'branch',
1758 [],
1758 [],
1759 _(
1759 _(
1760 b'do not clone everything, but include this branch\'s'
1760 b'do not clone everything, but include this branch\'s'
1761 b' changesets and their ancestors'
1761 b' changesets and their ancestors'
1762 ),
1762 ),
1763 _(b'BRANCH'),
1763 _(b'BRANCH'),
1764 ),
1764 ),
1765 (b'', b'pull', None, _(b'use pull protocol to copy metadata')),
1765 (b'', b'pull', None, _(b'use pull protocol to copy metadata')),
1766 (b'', b'uncompressed', None, _(b'an alias to --stream (DEPRECATED)')),
1766 (b'', b'uncompressed', None, _(b'an alias to --stream (DEPRECATED)')),
1767 (b'', b'stream', None, _(b'clone with minimal data processing')),
1767 (b'', b'stream', None, _(b'clone with minimal data processing')),
1768 ]
1768 ]
1769 + remoteopts,
1769 + remoteopts,
1770 _(b'[OPTION]... SOURCE [DEST]'),
1770 _(b'[OPTION]... SOURCE [DEST]'),
1771 helpcategory=command.CATEGORY_REPO_CREATION,
1771 helpcategory=command.CATEGORY_REPO_CREATION,
1772 helpbasic=True,
1772 helpbasic=True,
1773 norepo=True,
1773 norepo=True,
1774 )
1774 )
1775 def clone(ui, source, dest=None, **opts):
1775 def clone(ui, source, dest=None, **opts):
1776 """make a copy of an existing repository
1776 """make a copy of an existing repository
1777
1777
1778 Create a copy of an existing repository in a new directory.
1778 Create a copy of an existing repository in a new directory.
1779
1779
1780 If no destination directory name is specified, it defaults to the
1780 If no destination directory name is specified, it defaults to the
1781 basename of the source.
1781 basename of the source.
1782
1782
1783 The location of the source is added to the new repository's
1783 The location of the source is added to the new repository's
1784 ``.hg/hgrc`` file, as the default to be used for future pulls.
1784 ``.hg/hgrc`` file, as the default to be used for future pulls.
1785
1785
1786 Only local paths and ``ssh://`` URLs are supported as
1786 Only local paths and ``ssh://`` URLs are supported as
1787 destinations. For ``ssh://`` destinations, no working directory or
1787 destinations. For ``ssh://`` destinations, no working directory or
1788 ``.hg/hgrc`` will be created on the remote side.
1788 ``.hg/hgrc`` will be created on the remote side.
1789
1789
1790 If the source repository has a bookmark called '@' set, that
1790 If the source repository has a bookmark called '@' set, that
1791 revision will be checked out in the new repository by default.
1791 revision will be checked out in the new repository by default.
1792
1792
1793 To check out a particular version, use -u/--update, or
1793 To check out a particular version, use -u/--update, or
1794 -U/--noupdate to create a clone with no working directory.
1794 -U/--noupdate to create a clone with no working directory.
1795
1795
1796 To pull only a subset of changesets, specify one or more revisions
1796 To pull only a subset of changesets, specify one or more revisions
1797 identifiers with -r/--rev or branches with -b/--branch. The
1797 identifiers with -r/--rev or branches with -b/--branch. The
1798 resulting clone will contain only the specified changesets and
1798 resulting clone will contain only the specified changesets and
1799 their ancestors. These options (or 'clone src#rev dest') imply
1799 their ancestors. These options (or 'clone src#rev dest') imply
1800 --pull, even for local source repositories.
1800 --pull, even for local source repositories.
1801
1801
1802 In normal clone mode, the remote normalizes repository data into a common
1802 In normal clone mode, the remote normalizes repository data into a common
1803 exchange format and the receiving end translates this data into its local
1803 exchange format and the receiving end translates this data into its local
1804 storage format. --stream activates a different clone mode that essentially
1804 storage format. --stream activates a different clone mode that essentially
1805 copies repository files from the remote with minimal data processing. This
1805 copies repository files from the remote with minimal data processing. This
1806 significantly reduces the CPU cost of a clone both remotely and locally.
1806 significantly reduces the CPU cost of a clone both remotely and locally.
1807 However, it often increases the transferred data size by 30-40%. This can
1807 However, it often increases the transferred data size by 30-40%. This can
1808 result in substantially faster clones where I/O throughput is plentiful,
1808 result in substantially faster clones where I/O throughput is plentiful,
1809 especially for larger repositories. A side-effect of --stream clones is
1809 especially for larger repositories. A side-effect of --stream clones is
1810 that storage settings and requirements on the remote are applied locally:
1810 that storage settings and requirements on the remote are applied locally:
1811 a modern client may inherit legacy or inefficient storage used by the
1811 a modern client may inherit legacy or inefficient storage used by the
1812 remote or a legacy Mercurial client may not be able to clone from a
1812 remote or a legacy Mercurial client may not be able to clone from a
1813 modern Mercurial remote.
1813 modern Mercurial remote.
1814
1814
1815 .. note::
1815 .. note::
1816
1816
1817 Specifying a tag will include the tagged changeset but not the
1817 Specifying a tag will include the tagged changeset but not the
1818 changeset containing the tag.
1818 changeset containing the tag.
1819
1819
1820 .. container:: verbose
1820 .. container:: verbose
1821
1821
1822 For efficiency, hardlinks are used for cloning whenever the
1822 For efficiency, hardlinks are used for cloning whenever the
1823 source and destination are on the same filesystem (note this
1823 source and destination are on the same filesystem (note this
1824 applies only to the repository data, not to the working
1824 applies only to the repository data, not to the working
1825 directory). Some filesystems, such as AFS, implement hardlinking
1825 directory). Some filesystems, such as AFS, implement hardlinking
1826 incorrectly, but do not report errors. In these cases, use the
1826 incorrectly, but do not report errors. In these cases, use the
1827 --pull option to avoid hardlinking.
1827 --pull option to avoid hardlinking.
1828
1828
1829 Mercurial will update the working directory to the first applicable
1829 Mercurial will update the working directory to the first applicable
1830 revision from this list:
1830 revision from this list:
1831
1831
1832 a) null if -U or the source repository has no changesets
1832 a) null if -U or the source repository has no changesets
1833 b) if -u . and the source repository is local, the first parent of
1833 b) if -u . and the source repository is local, the first parent of
1834 the source repository's working directory
1834 the source repository's working directory
1835 c) the changeset specified with -u (if a branch name, this means the
1835 c) the changeset specified with -u (if a branch name, this means the
1836 latest head of that branch)
1836 latest head of that branch)
1837 d) the changeset specified with -r
1837 d) the changeset specified with -r
1838 e) the tipmost head specified with -b
1838 e) the tipmost head specified with -b
1839 f) the tipmost head specified with the url#branch source syntax
1839 f) the tipmost head specified with the url#branch source syntax
1840 g) the revision marked with the '@' bookmark, if present
1840 g) the revision marked with the '@' bookmark, if present
1841 h) the tipmost head of the default branch
1841 h) the tipmost head of the default branch
1842 i) tip
1842 i) tip
1843
1843
1844 When cloning from servers that support it, Mercurial may fetch
1844 When cloning from servers that support it, Mercurial may fetch
1845 pre-generated data from a server-advertised URL or inline from the
1845 pre-generated data from a server-advertised URL or inline from the
1846 same stream. When this is done, hooks operating on incoming changesets
1846 same stream. When this is done, hooks operating on incoming changesets
1847 and changegroups may fire more than once, once for each pre-generated
1847 and changegroups may fire more than once, once for each pre-generated
1848 bundle and as well as for any additional remaining data. In addition,
1848 bundle and as well as for any additional remaining data. In addition,
1849 if an error occurs, the repository may be rolled back to a partial
1849 if an error occurs, the repository may be rolled back to a partial
1850 clone. This behavior may change in future releases.
1850 clone. This behavior may change in future releases.
1851 See :hg:`help -e clonebundles` for more.
1851 See :hg:`help -e clonebundles` for more.
1852
1852
1853 Examples:
1853 Examples:
1854
1854
1855 - clone a remote repository to a new directory named hg/::
1855 - clone a remote repository to a new directory named hg/::
1856
1856
1857 hg clone https://www.mercurial-scm.org/repo/hg/
1857 hg clone https://www.mercurial-scm.org/repo/hg/
1858
1858
1859 - create a lightweight local clone::
1859 - create a lightweight local clone::
1860
1860
1861 hg clone project/ project-feature/
1861 hg clone project/ project-feature/
1862
1862
1863 - clone from an absolute path on an ssh server (note double-slash)::
1863 - clone from an absolute path on an ssh server (note double-slash)::
1864
1864
1865 hg clone ssh://user@server//home/projects/alpha/
1865 hg clone ssh://user@server//home/projects/alpha/
1866
1866
1867 - do a streaming clone while checking out a specified version::
1867 - do a streaming clone while checking out a specified version::
1868
1868
1869 hg clone --stream http://server/repo -u 1.5
1869 hg clone --stream http://server/repo -u 1.5
1870
1870
1871 - create a repository without changesets after a particular revision::
1871 - create a repository without changesets after a particular revision::
1872
1872
1873 hg clone -r 04e544 experimental/ good/
1873 hg clone -r 04e544 experimental/ good/
1874
1874
1875 - clone (and track) a particular named branch::
1875 - clone (and track) a particular named branch::
1876
1876
1877 hg clone https://www.mercurial-scm.org/repo/hg/#stable
1877 hg clone https://www.mercurial-scm.org/repo/hg/#stable
1878
1878
1879 See :hg:`help urls` for details on specifying URLs.
1879 See :hg:`help urls` for details on specifying URLs.
1880
1880
1881 Returns 0 on success.
1881 Returns 0 on success.
1882 """
1882 """
1883 opts = pycompat.byteskwargs(opts)
1883 opts = pycompat.byteskwargs(opts)
1884 cmdutil.check_at_most_one_arg(opts, b'noupdate', b'updaterev')
1884 cmdutil.check_at_most_one_arg(opts, b'noupdate', b'updaterev')
1885
1885
1886 # --include/--exclude can come from narrow or sparse.
1886 # --include/--exclude can come from narrow or sparse.
1887 includepats, excludepats = None, None
1887 includepats, excludepats = None, None
1888
1888
1889 # hg.clone() differentiates between None and an empty set. So make sure
1889 # hg.clone() differentiates between None and an empty set. So make sure
1890 # patterns are sets if narrow is requested without patterns.
1890 # patterns are sets if narrow is requested without patterns.
1891 if opts.get(b'narrow'):
1891 if opts.get(b'narrow'):
1892 includepats = set()
1892 includepats = set()
1893 excludepats = set()
1893 excludepats = set()
1894
1894
1895 if opts.get(b'include'):
1895 if opts.get(b'include'):
1896 includepats = narrowspec.parsepatterns(opts.get(b'include'))
1896 includepats = narrowspec.parsepatterns(opts.get(b'include'))
1897 if opts.get(b'exclude'):
1897 if opts.get(b'exclude'):
1898 excludepats = narrowspec.parsepatterns(opts.get(b'exclude'))
1898 excludepats = narrowspec.parsepatterns(opts.get(b'exclude'))
1899
1899
1900 r = hg.clone(
1900 r = hg.clone(
1901 ui,
1901 ui,
1902 opts,
1902 opts,
1903 source,
1903 source,
1904 dest,
1904 dest,
1905 pull=opts.get(b'pull'),
1905 pull=opts.get(b'pull'),
1906 stream=opts.get(b'stream') or opts.get(b'uncompressed'),
1906 stream=opts.get(b'stream') or opts.get(b'uncompressed'),
1907 revs=opts.get(b'rev'),
1907 revs=opts.get(b'rev'),
1908 update=opts.get(b'updaterev') or not opts.get(b'noupdate'),
1908 update=opts.get(b'updaterev') or not opts.get(b'noupdate'),
1909 branch=opts.get(b'branch'),
1909 branch=opts.get(b'branch'),
1910 shareopts=opts.get(b'shareopts'),
1910 shareopts=opts.get(b'shareopts'),
1911 storeincludepats=includepats,
1911 storeincludepats=includepats,
1912 storeexcludepats=excludepats,
1912 storeexcludepats=excludepats,
1913 depth=opts.get(b'depth') or None,
1913 depth=opts.get(b'depth') or None,
1914 )
1914 )
1915
1915
1916 return r is None
1916 return r is None
1917
1917
1918
1918
1919 @command(
1919 @command(
1920 b'commit|ci',
1920 b'commit|ci',
1921 [
1921 [
1922 (
1922 (
1923 b'A',
1923 b'A',
1924 b'addremove',
1924 b'addremove',
1925 None,
1925 None,
1926 _(b'mark new/missing files as added/removed before committing'),
1926 _(b'mark new/missing files as added/removed before committing'),
1927 ),
1927 ),
1928 (b'', b'close-branch', None, _(b'mark a branch head as closed')),
1928 (b'', b'close-branch', None, _(b'mark a branch head as closed')),
1929 (b'', b'amend', None, _(b'amend the parent of the working directory')),
1929 (b'', b'amend', None, _(b'amend the parent of the working directory')),
1930 (b's', b'secret', None, _(b'use the secret phase for committing')),
1930 (b's', b'secret', None, _(b'use the secret phase for committing')),
1931 (b'e', b'edit', None, _(b'invoke editor on commit messages')),
1931 (b'e', b'edit', None, _(b'invoke editor on commit messages')),
1932 (
1932 (
1933 b'',
1933 b'',
1934 b'force-close-branch',
1934 b'force-close-branch',
1935 None,
1935 None,
1936 _(b'forcibly close branch from a non-head changeset (ADVANCED)'),
1936 _(b'forcibly close branch from a non-head changeset (ADVANCED)'),
1937 ),
1937 ),
1938 (b'i', b'interactive', None, _(b'use interactive mode')),
1938 (b'i', b'interactive', None, _(b'use interactive mode')),
1939 ]
1939 ]
1940 + walkopts
1940 + walkopts
1941 + commitopts
1941 + commitopts
1942 + commitopts2
1942 + commitopts2
1943 + subrepoopts,
1943 + subrepoopts,
1944 _(b'[OPTION]... [FILE]...'),
1944 _(b'[OPTION]... [FILE]...'),
1945 helpcategory=command.CATEGORY_COMMITTING,
1945 helpcategory=command.CATEGORY_COMMITTING,
1946 helpbasic=True,
1946 helpbasic=True,
1947 inferrepo=True,
1947 inferrepo=True,
1948 )
1948 )
1949 def commit(ui, repo, *pats, **opts):
1949 def commit(ui, repo, *pats, **opts):
1950 """commit the specified files or all outstanding changes
1950 """commit the specified files or all outstanding changes
1951
1951
1952 Commit changes to the given files into the repository. Unlike a
1952 Commit changes to the given files into the repository. Unlike a
1953 centralized SCM, this operation is a local operation. See
1953 centralized SCM, this operation is a local operation. See
1954 :hg:`push` for a way to actively distribute your changes.
1954 :hg:`push` for a way to actively distribute your changes.
1955
1955
1956 If a list of files is omitted, all changes reported by :hg:`status`
1956 If a list of files is omitted, all changes reported by :hg:`status`
1957 will be committed.
1957 will be committed.
1958
1958
1959 If you are committing the result of a merge, do not provide any
1959 If you are committing the result of a merge, do not provide any
1960 filenames or -I/-X filters.
1960 filenames or -I/-X filters.
1961
1961
1962 If no commit message is specified, Mercurial starts your
1962 If no commit message is specified, Mercurial starts your
1963 configured editor where you can enter a message. In case your
1963 configured editor where you can enter a message. In case your
1964 commit fails, you will find a backup of your message in
1964 commit fails, you will find a backup of your message in
1965 ``.hg/last-message.txt``.
1965 ``.hg/last-message.txt``.
1966
1966
1967 The --close-branch flag can be used to mark the current branch
1967 The --close-branch flag can be used to mark the current branch
1968 head closed. When all heads of a branch are closed, the branch
1968 head closed. When all heads of a branch are closed, the branch
1969 will be considered closed and no longer listed.
1969 will be considered closed and no longer listed.
1970
1970
1971 The --amend flag can be used to amend the parent of the
1971 The --amend flag can be used to amend the parent of the
1972 working directory with a new commit that contains the changes
1972 working directory with a new commit that contains the changes
1973 in the parent in addition to those currently reported by :hg:`status`,
1973 in the parent in addition to those currently reported by :hg:`status`,
1974 if there are any. The old commit is stored in a backup bundle in
1974 if there are any. The old commit is stored in a backup bundle in
1975 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
1975 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
1976 on how to restore it).
1976 on how to restore it).
1977
1977
1978 Message, user and date are taken from the amended commit unless
1978 Message, user and date are taken from the amended commit unless
1979 specified. When a message isn't specified on the command line,
1979 specified. When a message isn't specified on the command line,
1980 the editor will open with the message of the amended commit.
1980 the editor will open with the message of the amended commit.
1981
1981
1982 It is not possible to amend public changesets (see :hg:`help phases`)
1982 It is not possible to amend public changesets (see :hg:`help phases`)
1983 or changesets that have children.
1983 or changesets that have children.
1984
1984
1985 See :hg:`help dates` for a list of formats valid for -d/--date.
1985 See :hg:`help dates` for a list of formats valid for -d/--date.
1986
1986
1987 Returns 0 on success, 1 if nothing changed.
1987 Returns 0 on success, 1 if nothing changed.
1988
1988
1989 .. container:: verbose
1989 .. container:: verbose
1990
1990
1991 Examples:
1991 Examples:
1992
1992
1993 - commit all files ending in .py::
1993 - commit all files ending in .py::
1994
1994
1995 hg commit --include "set:**.py"
1995 hg commit --include "set:**.py"
1996
1996
1997 - commit all non-binary files::
1997 - commit all non-binary files::
1998
1998
1999 hg commit --exclude "set:binary()"
1999 hg commit --exclude "set:binary()"
2000
2000
2001 - amend the current commit and set the date to now::
2001 - amend the current commit and set the date to now::
2002
2002
2003 hg commit --amend --date now
2003 hg commit --amend --date now
2004 """
2004 """
2005 with repo.wlock(), repo.lock():
2005 with repo.wlock(), repo.lock():
2006 return _docommit(ui, repo, *pats, **opts)
2006 return _docommit(ui, repo, *pats, **opts)
2007
2007
2008
2008
2009 def _docommit(ui, repo, *pats, **opts):
2009 def _docommit(ui, repo, *pats, **opts):
2010 if opts.get('interactive'):
2010 if opts.get('interactive'):
2011 opts.pop('interactive')
2011 opts.pop('interactive')
2012 ret = cmdutil.dorecord(
2012 ret = cmdutil.dorecord(
2013 ui, repo, commit, None, False, cmdutil.recordfilter, *pats, **opts
2013 ui, repo, commit, None, False, cmdutil.recordfilter, *pats, **opts
2014 )
2014 )
2015 # ret can be 0 (no changes to record) or the value returned by
2015 # ret can be 0 (no changes to record) or the value returned by
2016 # commit(), 1 if nothing changed or None on success.
2016 # commit(), 1 if nothing changed or None on success.
2017 return 1 if ret == 0 else ret
2017 return 1 if ret == 0 else ret
2018
2018
2019 opts = pycompat.byteskwargs(opts)
2019 opts = pycompat.byteskwargs(opts)
2020 if opts.get(b'subrepos'):
2020 if opts.get(b'subrepos'):
2021 if opts.get(b'amend'):
2021 if opts.get(b'amend'):
2022 raise error.Abort(_(b'cannot amend with --subrepos'))
2022 raise error.Abort(_(b'cannot amend with --subrepos'))
2023 # Let --subrepos on the command line override config setting.
2023 # Let --subrepos on the command line override config setting.
2024 ui.setconfig(b'ui', b'commitsubrepos', True, b'commit')
2024 ui.setconfig(b'ui', b'commitsubrepos', True, b'commit')
2025
2025
2026 cmdutil.checkunfinished(repo, commit=True)
2026 cmdutil.checkunfinished(repo, commit=True)
2027
2027
2028 branch = repo[None].branch()
2028 branch = repo[None].branch()
2029 bheads = repo.branchheads(branch)
2029 bheads = repo.branchheads(branch)
2030
2030
2031 extra = {}
2031 extra = {}
2032 if opts.get(b'close_branch') or opts.get(b'force_close_branch'):
2032 if opts.get(b'close_branch') or opts.get(b'force_close_branch'):
2033 extra[b'close'] = b'1'
2033 extra[b'close'] = b'1'
2034
2034
2035 if repo[b'.'].closesbranch():
2035 if repo[b'.'].closesbranch():
2036 raise error.Abort(
2036 raise error.Abort(
2037 _(b'current revision is already a branch closing head')
2037 _(b'current revision is already a branch closing head')
2038 )
2038 )
2039 elif not bheads:
2039 elif not bheads:
2040 raise error.Abort(_(b'branch "%s" has no heads to close') % branch)
2040 raise error.Abort(_(b'branch "%s" has no heads to close') % branch)
2041 elif (
2041 elif (
2042 branch == repo[b'.'].branch()
2042 branch == repo[b'.'].branch()
2043 and repo[b'.'].node() not in bheads
2043 and repo[b'.'].node() not in bheads
2044 and not opts.get(b'force_close_branch')
2044 and not opts.get(b'force_close_branch')
2045 ):
2045 ):
2046 hint = _(
2046 hint = _(
2047 b'use --force-close-branch to close branch from a non-head'
2047 b'use --force-close-branch to close branch from a non-head'
2048 b' changeset'
2048 b' changeset'
2049 )
2049 )
2050 raise error.Abort(_(b'can only close branch heads'), hint=hint)
2050 raise error.Abort(_(b'can only close branch heads'), hint=hint)
2051 elif opts.get(b'amend'):
2051 elif opts.get(b'amend'):
2052 if (
2052 if (
2053 repo[b'.'].p1().branch() != branch
2053 repo[b'.'].p1().branch() != branch
2054 and repo[b'.'].p2().branch() != branch
2054 and repo[b'.'].p2().branch() != branch
2055 ):
2055 ):
2056 raise error.Abort(_(b'can only close branch heads'))
2056 raise error.Abort(_(b'can only close branch heads'))
2057
2057
2058 if opts.get(b'amend'):
2058 if opts.get(b'amend'):
2059 if ui.configbool(b'ui', b'commitsubrepos'):
2059 if ui.configbool(b'ui', b'commitsubrepos'):
2060 raise error.Abort(_(b'cannot amend with ui.commitsubrepos enabled'))
2060 raise error.Abort(_(b'cannot amend with ui.commitsubrepos enabled'))
2061
2061
2062 old = repo[b'.']
2062 old = repo[b'.']
2063 rewriteutil.precheck(repo, [old.rev()], b'amend')
2063 rewriteutil.precheck(repo, [old.rev()], b'amend')
2064
2064
2065 # Currently histedit gets confused if an amend happens while histedit
2065 # Currently histedit gets confused if an amend happens while histedit
2066 # is in progress. Since we have a checkunfinished command, we are
2066 # is in progress. Since we have a checkunfinished command, we are
2067 # temporarily honoring it.
2067 # temporarily honoring it.
2068 #
2068 #
2069 # Note: eventually this guard will be removed. Please do not expect
2069 # Note: eventually this guard will be removed. Please do not expect
2070 # this behavior to remain.
2070 # this behavior to remain.
2071 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
2071 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
2072 cmdutil.checkunfinished(repo)
2072 cmdutil.checkunfinished(repo)
2073
2073
2074 node = cmdutil.amend(ui, repo, old, extra, pats, opts)
2074 node = cmdutil.amend(ui, repo, old, extra, pats, opts)
2075 if node == old.node():
2075 if node == old.node():
2076 ui.status(_(b"nothing changed\n"))
2076 ui.status(_(b"nothing changed\n"))
2077 return 1
2077 return 1
2078 else:
2078 else:
2079
2079
2080 def commitfunc(ui, repo, message, match, opts):
2080 def commitfunc(ui, repo, message, match, opts):
2081 overrides = {}
2081 overrides = {}
2082 if opts.get(b'secret'):
2082 if opts.get(b'secret'):
2083 overrides[(b'phases', b'new-commit')] = b'secret'
2083 overrides[(b'phases', b'new-commit')] = b'secret'
2084
2084
2085 baseui = repo.baseui
2085 baseui = repo.baseui
2086 with baseui.configoverride(overrides, b'commit'):
2086 with baseui.configoverride(overrides, b'commit'):
2087 with ui.configoverride(overrides, b'commit'):
2087 with ui.configoverride(overrides, b'commit'):
2088 editform = cmdutil.mergeeditform(
2088 editform = cmdutil.mergeeditform(
2089 repo[None], b'commit.normal'
2089 repo[None], b'commit.normal'
2090 )
2090 )
2091 editor = cmdutil.getcommiteditor(
2091 editor = cmdutil.getcommiteditor(
2092 editform=editform, **pycompat.strkwargs(opts)
2092 editform=editform, **pycompat.strkwargs(opts)
2093 )
2093 )
2094 return repo.commit(
2094 return repo.commit(
2095 message,
2095 message,
2096 opts.get(b'user'),
2096 opts.get(b'user'),
2097 opts.get(b'date'),
2097 opts.get(b'date'),
2098 match,
2098 match,
2099 editor=editor,
2099 editor=editor,
2100 extra=extra,
2100 extra=extra,
2101 )
2101 )
2102
2102
2103 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
2103 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
2104
2104
2105 if not node:
2105 if not node:
2106 stat = cmdutil.postcommitstatus(repo, pats, opts)
2106 stat = cmdutil.postcommitstatus(repo, pats, opts)
2107 if stat.deleted:
2107 if stat.deleted:
2108 ui.status(
2108 ui.status(
2109 _(
2109 _(
2110 b"nothing changed (%d missing files, see "
2110 b"nothing changed (%d missing files, see "
2111 b"'hg status')\n"
2111 b"'hg status')\n"
2112 )
2112 )
2113 % len(stat.deleted)
2113 % len(stat.deleted)
2114 )
2114 )
2115 else:
2115 else:
2116 ui.status(_(b"nothing changed\n"))
2116 ui.status(_(b"nothing changed\n"))
2117 return 1
2117 return 1
2118
2118
2119 cmdutil.commitstatus(repo, node, branch, bheads, opts)
2119 cmdutil.commitstatus(repo, node, branch, bheads, opts)
2120
2120
2121 if not ui.quiet and ui.configbool(b'commands', b'commit.post-status'):
2121 if not ui.quiet and ui.configbool(b'commands', b'commit.post-status'):
2122 status(
2122 status(
2123 ui,
2123 ui,
2124 repo,
2124 repo,
2125 modified=True,
2125 modified=True,
2126 added=True,
2126 added=True,
2127 removed=True,
2127 removed=True,
2128 deleted=True,
2128 deleted=True,
2129 unknown=True,
2129 unknown=True,
2130 subrepos=opts.get(b'subrepos'),
2130 subrepos=opts.get(b'subrepos'),
2131 )
2131 )
2132
2132
2133
2133
2134 @command(
2134 @command(
2135 b'config|showconfig|debugconfig',
2135 b'config|showconfig|debugconfig',
2136 [
2136 [
2137 (b'u', b'untrusted', None, _(b'show untrusted configuration options')),
2137 (b'u', b'untrusted', None, _(b'show untrusted configuration options')),
2138 (b'e', b'edit', None, _(b'edit user config')),
2138 (b'e', b'edit', None, _(b'edit user config')),
2139 (b'l', b'local', None, _(b'edit repository config')),
2139 (b'l', b'local', None, _(b'edit repository config')),
2140 (
2140 (
2141 b'',
2141 b'',
2142 b'shared',
2142 b'shared',
2143 None,
2143 None,
2144 _(b'edit shared source repository config (EXPERIMENTAL)'),
2144 _(b'edit shared source repository config (EXPERIMENTAL)'),
2145 ),
2145 ),
2146 (b'g', b'global', None, _(b'edit global config')),
2146 (b'g', b'global', None, _(b'edit global config')),
2147 ]
2147 ]
2148 + formatteropts,
2148 + formatteropts,
2149 _(b'[-u] [NAME]...'),
2149 _(b'[-u] [NAME]...'),
2150 helpcategory=command.CATEGORY_HELP,
2150 helpcategory=command.CATEGORY_HELP,
2151 optionalrepo=True,
2151 optionalrepo=True,
2152 intents={INTENT_READONLY},
2152 intents={INTENT_READONLY},
2153 )
2153 )
2154 def config(ui, repo, *values, **opts):
2154 def config(ui, repo, *values, **opts):
2155 """show combined config settings from all hgrc files
2155 """show combined config settings from all hgrc files
2156
2156
2157 With no arguments, print names and values of all config items.
2157 With no arguments, print names and values of all config items.
2158
2158
2159 With one argument of the form section.name, print just the value
2159 With one argument of the form section.name, print just the value
2160 of that config item.
2160 of that config item.
2161
2161
2162 With multiple arguments, print names and values of all config
2162 With multiple arguments, print names and values of all config
2163 items with matching section names or section.names.
2163 items with matching section names or section.names.
2164
2164
2165 With --edit, start an editor on the user-level config file. With
2165 With --edit, start an editor on the user-level config file. With
2166 --global, edit the system-wide config file. With --local, edit the
2166 --global, edit the system-wide config file. With --local, edit the
2167 repository-level config file.
2167 repository-level config file.
2168
2168
2169 With --debug, the source (filename and line number) is printed
2169 With --debug, the source (filename and line number) is printed
2170 for each config item.
2170 for each config item.
2171
2171
2172 See :hg:`help config` for more information about config files.
2172 See :hg:`help config` for more information about config files.
2173
2173
2174 .. container:: verbose
2174 .. container:: verbose
2175
2175
2176 Template:
2176 Template:
2177
2177
2178 The following keywords are supported. See also :hg:`help templates`.
2178 The following keywords are supported. See also :hg:`help templates`.
2179
2179
2180 :name: String. Config name.
2180 :name: String. Config name.
2181 :source: String. Filename and line number where the item is defined.
2181 :source: String. Filename and line number where the item is defined.
2182 :value: String. Config value.
2182 :value: String. Config value.
2183
2183
2184 The --shared flag can be used to edit the config file of shared source
2184 The --shared flag can be used to edit the config file of shared source
2185 repository. It only works when you have shared using the experimental
2185 repository. It only works when you have shared using the experimental
2186 share safe feature.
2186 share safe feature.
2187
2187
2188 Returns 0 on success, 1 if NAME does not exist.
2188 Returns 0 on success, 1 if NAME does not exist.
2189
2189
2190 """
2190 """
2191
2191
2192 opts = pycompat.byteskwargs(opts)
2192 opts = pycompat.byteskwargs(opts)
2193 editopts = (b'edit', b'local', b'global', b'shared')
2193 editopts = (b'edit', b'local', b'global', b'shared')
2194 if any(opts.get(o) for o in editopts):
2194 if any(opts.get(o) for o in editopts):
2195 cmdutil.check_at_most_one_arg(opts, *editopts[1:])
2195 cmdutil.check_at_most_one_arg(opts, *editopts[1:])
2196 if opts.get(b'local'):
2196 if opts.get(b'local'):
2197 if not repo:
2197 if not repo:
2198 raise error.Abort(_(b"can't use --local outside a repository"))
2198 raise error.Abort(_(b"can't use --local outside a repository"))
2199 paths = [repo.vfs.join(b'hgrc')]
2199 paths = [repo.vfs.join(b'hgrc')]
2200 elif opts.get(b'global'):
2200 elif opts.get(b'global'):
2201 paths = rcutil.systemrcpath()
2201 paths = rcutil.systemrcpath()
2202 elif opts.get(b'shared'):
2202 elif opts.get(b'shared'):
2203 if not repo.shared():
2203 if not repo.shared():
2204 raise error.Abort(
2204 raise error.Abort(
2205 _(b"repository is not shared; can't use --shared")
2205 _(b"repository is not shared; can't use --shared")
2206 )
2206 )
2207 if requirements.SHARESAFE_REQUIREMENT not in repo.requirements:
2207 if requirements.SHARESAFE_REQUIREMENT not in repo.requirements:
2208 raise error.Abort(
2208 raise error.Abort(
2209 _(
2209 _(
2210 b"share safe feature not unabled; "
2210 b"share safe feature not unabled; "
2211 b"unable to edit shared source repository config"
2211 b"unable to edit shared source repository config"
2212 )
2212 )
2213 )
2213 )
2214 paths = [vfsmod.vfs(repo.sharedpath).join(b'hgrc')]
2214 paths = [vfsmod.vfs(repo.sharedpath).join(b'hgrc')]
2215 else:
2215 else:
2216 paths = rcutil.userrcpath()
2216 paths = rcutil.userrcpath()
2217
2217
2218 for f in paths:
2218 for f in paths:
2219 if os.path.exists(f):
2219 if os.path.exists(f):
2220 break
2220 break
2221 else:
2221 else:
2222 if opts.get(b'global'):
2222 if opts.get(b'global'):
2223 samplehgrc = uimod.samplehgrcs[b'global']
2223 samplehgrc = uimod.samplehgrcs[b'global']
2224 elif opts.get(b'local'):
2224 elif opts.get(b'local'):
2225 samplehgrc = uimod.samplehgrcs[b'local']
2225 samplehgrc = uimod.samplehgrcs[b'local']
2226 else:
2226 else:
2227 samplehgrc = uimod.samplehgrcs[b'user']
2227 samplehgrc = uimod.samplehgrcs[b'user']
2228
2228
2229 f = paths[0]
2229 f = paths[0]
2230 fp = open(f, b"wb")
2230 fp = open(f, b"wb")
2231 fp.write(util.tonativeeol(samplehgrc))
2231 fp.write(util.tonativeeol(samplehgrc))
2232 fp.close()
2232 fp.close()
2233
2233
2234 editor = ui.geteditor()
2234 editor = ui.geteditor()
2235 ui.system(
2235 ui.system(
2236 b"%s \"%s\"" % (editor, f),
2236 b"%s \"%s\"" % (editor, f),
2237 onerr=error.Abort,
2237 onerr=error.Abort,
2238 errprefix=_(b"edit failed"),
2238 errprefix=_(b"edit failed"),
2239 blockedtag=b'config_edit',
2239 blockedtag=b'config_edit',
2240 )
2240 )
2241 return
2241 return
2242 ui.pager(b'config')
2242 ui.pager(b'config')
2243 fm = ui.formatter(b'config', opts)
2243 fm = ui.formatter(b'config', opts)
2244 for t, f in rcutil.rccomponents():
2244 for t, f in rcutil.rccomponents():
2245 if t == b'path':
2245 if t == b'path':
2246 ui.debug(b'read config from: %s\n' % f)
2246 ui.debug(b'read config from: %s\n' % f)
2247 elif t == b'resource':
2247 elif t == b'resource':
2248 ui.debug(b'read config from: resource:%s.%s\n' % (f[0], f[1]))
2248 ui.debug(b'read config from: resource:%s.%s\n' % (f[0], f[1]))
2249 elif t == b'items':
2249 elif t == b'items':
2250 # Don't print anything for 'items'.
2250 # Don't print anything for 'items'.
2251 pass
2251 pass
2252 else:
2252 else:
2253 raise error.ProgrammingError(b'unknown rctype: %s' % t)
2253 raise error.ProgrammingError(b'unknown rctype: %s' % t)
2254 untrusted = bool(opts.get(b'untrusted'))
2254 untrusted = bool(opts.get(b'untrusted'))
2255
2255
2256 selsections = selentries = []
2256 selsections = selentries = []
2257 if values:
2257 if values:
2258 selsections = [v for v in values if b'.' not in v]
2258 selsections = [v for v in values if b'.' not in v]
2259 selentries = [v for v in values if b'.' in v]
2259 selentries = [v for v in values if b'.' in v]
2260 uniquesel = len(selentries) == 1 and not selsections
2260 uniquesel = len(selentries) == 1 and not selsections
2261 selsections = set(selsections)
2261 selsections = set(selsections)
2262 selentries = set(selentries)
2262 selentries = set(selentries)
2263
2263
2264 matched = False
2264 matched = False
2265 for section, name, value in ui.walkconfig(untrusted=untrusted):
2265 for section, name, value in ui.walkconfig(untrusted=untrusted):
2266 source = ui.configsource(section, name, untrusted)
2266 source = ui.configsource(section, name, untrusted)
2267 value = pycompat.bytestr(value)
2267 value = pycompat.bytestr(value)
2268 defaultvalue = ui.configdefault(section, name)
2268 defaultvalue = ui.configdefault(section, name)
2269 if fm.isplain():
2269 if fm.isplain():
2270 source = source or b'none'
2270 source = source or b'none'
2271 value = value.replace(b'\n', b'\\n')
2271 value = value.replace(b'\n', b'\\n')
2272 entryname = section + b'.' + name
2272 entryname = section + b'.' + name
2273 if values and not (section in selsections or entryname in selentries):
2273 if values and not (section in selsections or entryname in selentries):
2274 continue
2274 continue
2275 fm.startitem()
2275 fm.startitem()
2276 fm.condwrite(ui.debugflag, b'source', b'%s: ', source)
2276 fm.condwrite(ui.debugflag, b'source', b'%s: ', source)
2277 if uniquesel:
2277 if uniquesel:
2278 fm.data(name=entryname)
2278 fm.data(name=entryname)
2279 fm.write(b'value', b'%s\n', value)
2279 fm.write(b'value', b'%s\n', value)
2280 else:
2280 else:
2281 fm.write(b'name value', b'%s=%s\n', entryname, value)
2281 fm.write(b'name value', b'%s=%s\n', entryname, value)
2282 if formatter.isprintable(defaultvalue):
2282 if formatter.isprintable(defaultvalue):
2283 fm.data(defaultvalue=defaultvalue)
2283 fm.data(defaultvalue=defaultvalue)
2284 elif isinstance(defaultvalue, list) and all(
2284 elif isinstance(defaultvalue, list) and all(
2285 formatter.isprintable(e) for e in defaultvalue
2285 formatter.isprintable(e) for e in defaultvalue
2286 ):
2286 ):
2287 fm.data(defaultvalue=fm.formatlist(defaultvalue, name=b'value'))
2287 fm.data(defaultvalue=fm.formatlist(defaultvalue, name=b'value'))
2288 # TODO: no idea how to process unsupported defaultvalue types
2288 # TODO: no idea how to process unsupported defaultvalue types
2289 matched = True
2289 matched = True
2290 fm.end()
2290 fm.end()
2291 if matched:
2291 if matched:
2292 return 0
2292 return 0
2293 return 1
2293 return 1
2294
2294
2295
2295
2296 @command(
2296 @command(
2297 b'continue',
2297 b'continue',
2298 dryrunopts,
2298 dryrunopts,
2299 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
2299 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
2300 helpbasic=True,
2300 helpbasic=True,
2301 )
2301 )
2302 def continuecmd(ui, repo, **opts):
2302 def continuecmd(ui, repo, **opts):
2303 """resumes an interrupted operation (EXPERIMENTAL)
2303 """resumes an interrupted operation (EXPERIMENTAL)
2304
2304
2305 Finishes a multistep operation like graft, histedit, rebase, merge,
2305 Finishes a multistep operation like graft, histedit, rebase, merge,
2306 and unshelve if they are in an interrupted state.
2306 and unshelve if they are in an interrupted state.
2307
2307
2308 use --dry-run/-n to dry run the command.
2308 use --dry-run/-n to dry run the command.
2309 """
2309 """
2310 dryrun = opts.get('dry_run')
2310 dryrun = opts.get('dry_run')
2311 contstate = cmdutil.getunfinishedstate(repo)
2311 contstate = cmdutil.getunfinishedstate(repo)
2312 if not contstate:
2312 if not contstate:
2313 raise error.Abort(_(b'no operation in progress'))
2313 raise error.Abort(_(b'no operation in progress'))
2314 if not contstate.continuefunc:
2314 if not contstate.continuefunc:
2315 raise error.Abort(
2315 raise error.Abort(
2316 (
2316 (
2317 _(b"%s in progress but does not support 'hg continue'")
2317 _(b"%s in progress but does not support 'hg continue'")
2318 % (contstate._opname)
2318 % (contstate._opname)
2319 ),
2319 ),
2320 hint=contstate.continuemsg(),
2320 hint=contstate.continuemsg(),
2321 )
2321 )
2322 if dryrun:
2322 if dryrun:
2323 ui.status(_(b'%s in progress, will be resumed\n') % (contstate._opname))
2323 ui.status(_(b'%s in progress, will be resumed\n') % (contstate._opname))
2324 return
2324 return
2325 return contstate.continuefunc(ui, repo)
2325 return contstate.continuefunc(ui, repo)
2326
2326
2327
2327
2328 @command(
2328 @command(
2329 b'copy|cp',
2329 b'copy|cp',
2330 [
2330 [
2331 (b'', b'forget', None, _(b'unmark a file as copied')),
2331 (b'', b'forget', None, _(b'unmark a file as copied')),
2332 (b'A', b'after', None, _(b'record a copy that has already occurred')),
2332 (b'A', b'after', None, _(b'record a copy that has already occurred')),
2333 (
2333 (
2334 b'',
2334 b'',
2335 b'at-rev',
2335 b'at-rev',
2336 b'',
2336 b'',
2337 _(b'(un)mark copies in the given revision (EXPERIMENTAL)'),
2337 _(b'(un)mark copies in the given revision (EXPERIMENTAL)'),
2338 _(b'REV'),
2338 _(b'REV'),
2339 ),
2339 ),
2340 (
2340 (
2341 b'f',
2341 b'f',
2342 b'force',
2342 b'force',
2343 None,
2343 None,
2344 _(b'forcibly copy over an existing managed file'),
2344 _(b'forcibly copy over an existing managed file'),
2345 ),
2345 ),
2346 ]
2346 ]
2347 + walkopts
2347 + walkopts
2348 + dryrunopts,
2348 + dryrunopts,
2349 _(b'[OPTION]... SOURCE... DEST'),
2349 _(b'[OPTION]... SOURCE... DEST'),
2350 helpcategory=command.CATEGORY_FILE_CONTENTS,
2350 helpcategory=command.CATEGORY_FILE_CONTENTS,
2351 )
2351 )
2352 def copy(ui, repo, *pats, **opts):
2352 def copy(ui, repo, *pats, **opts):
2353 """mark files as copied for the next commit
2353 """mark files as copied for the next commit
2354
2354
2355 Mark dest as having copies of source files. If dest is a
2355 Mark dest as having copies of source files. If dest is a
2356 directory, copies are put in that directory. If dest is a file,
2356 directory, copies are put in that directory. If dest is a file,
2357 the source must be a single file.
2357 the source must be a single file.
2358
2358
2359 By default, this command copies the contents of files as they
2359 By default, this command copies the contents of files as they
2360 exist in the working directory. If invoked with -A/--after, the
2360 exist in the working directory. If invoked with -A/--after, the
2361 operation is recorded, but no copying is performed.
2361 operation is recorded, but no copying is performed.
2362
2362
2363 To undo marking a file as copied, use --forget. With that option,
2363 To undo marking a file as copied, use --forget. With that option,
2364 all given (positional) arguments are unmarked as copies. The destination
2364 all given (positional) arguments are unmarked as copies. The destination
2365 file(s) will be left in place (still tracked).
2365 file(s) will be left in place (still tracked).
2366
2366
2367 This command takes effect with the next commit by default.
2367 This command takes effect with the next commit by default.
2368
2368
2369 Returns 0 on success, 1 if errors are encountered.
2369 Returns 0 on success, 1 if errors are encountered.
2370 """
2370 """
2371 opts = pycompat.byteskwargs(opts)
2371 opts = pycompat.byteskwargs(opts)
2372 with repo.wlock():
2372 with repo.wlock():
2373 return cmdutil.copy(ui, repo, pats, opts)
2373 return cmdutil.copy(ui, repo, pats, opts)
2374
2374
2375
2375
2376 @command(
2376 @command(
2377 b'debugcommands',
2377 b'debugcommands',
2378 [],
2378 [],
2379 _(b'[COMMAND]'),
2379 _(b'[COMMAND]'),
2380 helpcategory=command.CATEGORY_HELP,
2380 helpcategory=command.CATEGORY_HELP,
2381 norepo=True,
2381 norepo=True,
2382 )
2382 )
2383 def debugcommands(ui, cmd=b'', *args):
2383 def debugcommands(ui, cmd=b'', *args):
2384 """list all available commands and options"""
2384 """list all available commands and options"""
2385 for cmd, vals in sorted(pycompat.iteritems(table)):
2385 for cmd, vals in sorted(pycompat.iteritems(table)):
2386 cmd = cmd.split(b'|')[0]
2386 cmd = cmd.split(b'|')[0]
2387 opts = b', '.join([i[1] for i in vals[1]])
2387 opts = b', '.join([i[1] for i in vals[1]])
2388 ui.write(b'%s: %s\n' % (cmd, opts))
2388 ui.write(b'%s: %s\n' % (cmd, opts))
2389
2389
2390
2390
2391 @command(
2391 @command(
2392 b'debugcomplete',
2392 b'debugcomplete',
2393 [(b'o', b'options', None, _(b'show the command options'))],
2393 [(b'o', b'options', None, _(b'show the command options'))],
2394 _(b'[-o] CMD'),
2394 _(b'[-o] CMD'),
2395 helpcategory=command.CATEGORY_HELP,
2395 helpcategory=command.CATEGORY_HELP,
2396 norepo=True,
2396 norepo=True,
2397 )
2397 )
2398 def debugcomplete(ui, cmd=b'', **opts):
2398 def debugcomplete(ui, cmd=b'', **opts):
2399 """returns the completion list associated with the given command"""
2399 """returns the completion list associated with the given command"""
2400
2400
2401 if opts.get('options'):
2401 if opts.get('options'):
2402 options = []
2402 options = []
2403 otables = [globalopts]
2403 otables = [globalopts]
2404 if cmd:
2404 if cmd:
2405 aliases, entry = cmdutil.findcmd(cmd, table, False)
2405 aliases, entry = cmdutil.findcmd(cmd, table, False)
2406 otables.append(entry[1])
2406 otables.append(entry[1])
2407 for t in otables:
2407 for t in otables:
2408 for o in t:
2408 for o in t:
2409 if b"(DEPRECATED)" in o[3]:
2409 if b"(DEPRECATED)" in o[3]:
2410 continue
2410 continue
2411 if o[0]:
2411 if o[0]:
2412 options.append(b'-%s' % o[0])
2412 options.append(b'-%s' % o[0])
2413 options.append(b'--%s' % o[1])
2413 options.append(b'--%s' % o[1])
2414 ui.write(b"%s\n" % b"\n".join(options))
2414 ui.write(b"%s\n" % b"\n".join(options))
2415 return
2415 return
2416
2416
2417 cmdlist, unused_allcmds = cmdutil.findpossible(cmd, table)
2417 cmdlist, unused_allcmds = cmdutil.findpossible(cmd, table)
2418 if ui.verbose:
2418 if ui.verbose:
2419 cmdlist = [b' '.join(c[0]) for c in cmdlist.values()]
2419 cmdlist = [b' '.join(c[0]) for c in cmdlist.values()]
2420 ui.write(b"%s\n" % b"\n".join(sorted(cmdlist)))
2420 ui.write(b"%s\n" % b"\n".join(sorted(cmdlist)))
2421
2421
2422
2422
2423 @command(
2423 @command(
2424 b'diff',
2424 b'diff',
2425 [
2425 [
2426 (b'r', b'rev', [], _(b'revision'), _(b'REV')),
2426 (b'r', b'rev', [], _(b'revision'), _(b'REV')),
2427 (b'c', b'change', b'', _(b'change made by revision'), _(b'REV')),
2427 (b'c', b'change', b'', _(b'change made by revision'), _(b'REV')),
2428 ]
2428 ]
2429 + diffopts
2429 + diffopts
2430 + diffopts2
2430 + diffopts2
2431 + walkopts
2431 + walkopts
2432 + subrepoopts,
2432 + subrepoopts,
2433 _(b'[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'),
2433 _(b'[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'),
2434 helpcategory=command.CATEGORY_FILE_CONTENTS,
2434 helpcategory=command.CATEGORY_FILE_CONTENTS,
2435 helpbasic=True,
2435 helpbasic=True,
2436 inferrepo=True,
2436 inferrepo=True,
2437 intents={INTENT_READONLY},
2437 intents={INTENT_READONLY},
2438 )
2438 )
2439 def diff(ui, repo, *pats, **opts):
2439 def diff(ui, repo, *pats, **opts):
2440 """diff repository (or selected files)
2440 """diff repository (or selected files)
2441
2441
2442 Show differences between revisions for the specified files.
2442 Show differences between revisions for the specified files.
2443
2443
2444 Differences between files are shown using the unified diff format.
2444 Differences between files are shown using the unified diff format.
2445
2445
2446 .. note::
2446 .. note::
2447
2447
2448 :hg:`diff` may generate unexpected results for merges, as it will
2448 :hg:`diff` may generate unexpected results for merges, as it will
2449 default to comparing against the working directory's first
2449 default to comparing against the working directory's first
2450 parent changeset if no revisions are specified.
2450 parent changeset if no revisions are specified.
2451
2451
2452 When two revision arguments are given, then changes are shown
2452 When two revision arguments are given, then changes are shown
2453 between those revisions. If only one revision is specified then
2453 between those revisions. If only one revision is specified then
2454 that revision is compared to the working directory, and, when no
2454 that revision is compared to the working directory, and, when no
2455 revisions are specified, the working directory files are compared
2455 revisions are specified, the working directory files are compared
2456 to its first parent.
2456 to its first parent.
2457
2457
2458 Alternatively you can specify -c/--change with a revision to see
2458 Alternatively you can specify -c/--change with a revision to see
2459 the changes in that changeset relative to its first parent.
2459 the changes in that changeset relative to its first parent.
2460
2460
2461 Without the -a/--text option, diff will avoid generating diffs of
2461 Without the -a/--text option, diff will avoid generating diffs of
2462 files it detects as binary. With -a, diff will generate a diff
2462 files it detects as binary. With -a, diff will generate a diff
2463 anyway, probably with undesirable results.
2463 anyway, probably with undesirable results.
2464
2464
2465 Use the -g/--git option to generate diffs in the git extended diff
2465 Use the -g/--git option to generate diffs in the git extended diff
2466 format. For more information, read :hg:`help diffs`.
2466 format. For more information, read :hg:`help diffs`.
2467
2467
2468 .. container:: verbose
2468 .. container:: verbose
2469
2469
2470 Examples:
2470 Examples:
2471
2471
2472 - compare a file in the current working directory to its parent::
2472 - compare a file in the current working directory to its parent::
2473
2473
2474 hg diff foo.c
2474 hg diff foo.c
2475
2475
2476 - compare two historical versions of a directory, with rename info::
2476 - compare two historical versions of a directory, with rename info::
2477
2477
2478 hg diff --git -r 1.0:1.2 lib/
2478 hg diff --git -r 1.0:1.2 lib/
2479
2479
2480 - get change stats relative to the last change on some date::
2480 - get change stats relative to the last change on some date::
2481
2481
2482 hg diff --stat -r "date('may 2')"
2482 hg diff --stat -r "date('may 2')"
2483
2483
2484 - diff all newly-added files that contain a keyword::
2484 - diff all newly-added files that contain a keyword::
2485
2485
2486 hg diff "set:added() and grep(GNU)"
2486 hg diff "set:added() and grep(GNU)"
2487
2487
2488 - compare a revision and its parents::
2488 - compare a revision and its parents::
2489
2489
2490 hg diff -c 9353 # compare against first parent
2490 hg diff -c 9353 # compare against first parent
2491 hg diff -r 9353^:9353 # same using revset syntax
2491 hg diff -r 9353^:9353 # same using revset syntax
2492 hg diff -r 9353^2:9353 # compare against the second parent
2492 hg diff -r 9353^2:9353 # compare against the second parent
2493
2493
2494 Returns 0 on success.
2494 Returns 0 on success.
2495 """
2495 """
2496
2496
2497 cmdutil.check_at_most_one_arg(opts, 'rev', 'change')
2497 cmdutil.check_at_most_one_arg(opts, 'rev', 'change')
2498 opts = pycompat.byteskwargs(opts)
2498 opts = pycompat.byteskwargs(opts)
2499 revs = opts.get(b'rev')
2499 revs = opts.get(b'rev')
2500 change = opts.get(b'change')
2500 change = opts.get(b'change')
2501 stat = opts.get(b'stat')
2501 stat = opts.get(b'stat')
2502 reverse = opts.get(b'reverse')
2502 reverse = opts.get(b'reverse')
2503
2503
2504 if change:
2504 if change:
2505 repo = scmutil.unhidehashlikerevs(repo, [change], b'nowarn')
2505 repo = scmutil.unhidehashlikerevs(repo, [change], b'nowarn')
2506 ctx2 = scmutil.revsingle(repo, change, None)
2506 ctx2 = scmutil.revsingle(repo, change, None)
2507 ctx1 = ctx2.p1()
2507 ctx1 = ctx2.p1()
2508 else:
2508 else:
2509 repo = scmutil.unhidehashlikerevs(repo, revs, b'nowarn')
2509 repo = scmutil.unhidehashlikerevs(repo, revs, b'nowarn')
2510 ctx1, ctx2 = scmutil.revpair(repo, revs)
2510 ctx1, ctx2 = scmutil.revpair(repo, revs)
2511
2511
2512 if reverse:
2512 if reverse:
2513 ctxleft = ctx2
2513 ctxleft = ctx2
2514 ctxright = ctx1
2514 ctxright = ctx1
2515 else:
2515 else:
2516 ctxleft = ctx1
2516 ctxleft = ctx1
2517 ctxright = ctx2
2517 ctxright = ctx2
2518
2518
2519 diffopts = patch.diffallopts(ui, opts)
2519 diffopts = patch.diffallopts(ui, opts)
2520 m = scmutil.match(ctx2, pats, opts)
2520 m = scmutil.match(ctx2, pats, opts)
2521 m = repo.narrowmatch(m)
2521 m = repo.narrowmatch(m)
2522 ui.pager(b'diff')
2522 ui.pager(b'diff')
2523 logcmdutil.diffordiffstat(
2523 logcmdutil.diffordiffstat(
2524 ui,
2524 ui,
2525 repo,
2525 repo,
2526 diffopts,
2526 diffopts,
2527 ctxleft,
2527 ctxleft,
2528 ctxright,
2528 ctxright,
2529 m,
2529 m,
2530 stat=stat,
2530 stat=stat,
2531 listsubrepos=opts.get(b'subrepos'),
2531 listsubrepos=opts.get(b'subrepos'),
2532 root=opts.get(b'root'),
2532 root=opts.get(b'root'),
2533 )
2533 )
2534
2534
2535
2535
2536 @command(
2536 @command(
2537 b'export',
2537 b'export',
2538 [
2538 [
2539 (
2539 (
2540 b'B',
2540 b'B',
2541 b'bookmark',
2541 b'bookmark',
2542 b'',
2542 b'',
2543 _(b'export changes only reachable by given bookmark'),
2543 _(b'export changes only reachable by given bookmark'),
2544 _(b'BOOKMARK'),
2544 _(b'BOOKMARK'),
2545 ),
2545 ),
2546 (
2546 (
2547 b'o',
2547 b'o',
2548 b'output',
2548 b'output',
2549 b'',
2549 b'',
2550 _(b'print output to file with formatted name'),
2550 _(b'print output to file with formatted name'),
2551 _(b'FORMAT'),
2551 _(b'FORMAT'),
2552 ),
2552 ),
2553 (b'', b'switch-parent', None, _(b'diff against the second parent')),
2553 (b'', b'switch-parent', None, _(b'diff against the second parent')),
2554 (b'r', b'rev', [], _(b'revisions to export'), _(b'REV')),
2554 (b'r', b'rev', [], _(b'revisions to export'), _(b'REV')),
2555 ]
2555 ]
2556 + diffopts
2556 + diffopts
2557 + formatteropts,
2557 + formatteropts,
2558 _(b'[OPTION]... [-o OUTFILESPEC] [-r] [REV]...'),
2558 _(b'[OPTION]... [-o OUTFILESPEC] [-r] [REV]...'),
2559 helpcategory=command.CATEGORY_IMPORT_EXPORT,
2559 helpcategory=command.CATEGORY_IMPORT_EXPORT,
2560 helpbasic=True,
2560 helpbasic=True,
2561 intents={INTENT_READONLY},
2561 intents={INTENT_READONLY},
2562 )
2562 )
2563 def export(ui, repo, *changesets, **opts):
2563 def export(ui, repo, *changesets, **opts):
2564 """dump the header and diffs for one or more changesets
2564 """dump the header and diffs for one or more changesets
2565
2565
2566 Print the changeset header and diffs for one or more revisions.
2566 Print the changeset header and diffs for one or more revisions.
2567 If no revision is given, the parent of the working directory is used.
2567 If no revision is given, the parent of the working directory is used.
2568
2568
2569 The information shown in the changeset header is: author, date,
2569 The information shown in the changeset header is: author, date,
2570 branch name (if non-default), changeset hash, parent(s) and commit
2570 branch name (if non-default), changeset hash, parent(s) and commit
2571 comment.
2571 comment.
2572
2572
2573 .. note::
2573 .. note::
2574
2574
2575 :hg:`export` may generate unexpected diff output for merge
2575 :hg:`export` may generate unexpected diff output for merge
2576 changesets, as it will compare the merge changeset against its
2576 changesets, as it will compare the merge changeset against its
2577 first parent only.
2577 first parent only.
2578
2578
2579 Output may be to a file, in which case the name of the file is
2579 Output may be to a file, in which case the name of the file is
2580 given using a template string. See :hg:`help templates`. In addition
2580 given using a template string. See :hg:`help templates`. In addition
2581 to the common template keywords, the following formatting rules are
2581 to the common template keywords, the following formatting rules are
2582 supported:
2582 supported:
2583
2583
2584 :``%%``: literal "%" character
2584 :``%%``: literal "%" character
2585 :``%H``: changeset hash (40 hexadecimal digits)
2585 :``%H``: changeset hash (40 hexadecimal digits)
2586 :``%N``: number of patches being generated
2586 :``%N``: number of patches being generated
2587 :``%R``: changeset revision number
2587 :``%R``: changeset revision number
2588 :``%b``: basename of the exporting repository
2588 :``%b``: basename of the exporting repository
2589 :``%h``: short-form changeset hash (12 hexadecimal digits)
2589 :``%h``: short-form changeset hash (12 hexadecimal digits)
2590 :``%m``: first line of the commit message (only alphanumeric characters)
2590 :``%m``: first line of the commit message (only alphanumeric characters)
2591 :``%n``: zero-padded sequence number, starting at 1
2591 :``%n``: zero-padded sequence number, starting at 1
2592 :``%r``: zero-padded changeset revision number
2592 :``%r``: zero-padded changeset revision number
2593 :``\\``: literal "\\" character
2593 :``\\``: literal "\\" character
2594
2594
2595 Without the -a/--text option, export will avoid generating diffs
2595 Without the -a/--text option, export will avoid generating diffs
2596 of files it detects as binary. With -a, export will generate a
2596 of files it detects as binary. With -a, export will generate a
2597 diff anyway, probably with undesirable results.
2597 diff anyway, probably with undesirable results.
2598
2598
2599 With -B/--bookmark changesets reachable by the given bookmark are
2599 With -B/--bookmark changesets reachable by the given bookmark are
2600 selected.
2600 selected.
2601
2601
2602 Use the -g/--git option to generate diffs in the git extended diff
2602 Use the -g/--git option to generate diffs in the git extended diff
2603 format. See :hg:`help diffs` for more information.
2603 format. See :hg:`help diffs` for more information.
2604
2604
2605 With the --switch-parent option, the diff will be against the
2605 With the --switch-parent option, the diff will be against the
2606 second parent. It can be useful to review a merge.
2606 second parent. It can be useful to review a merge.
2607
2607
2608 .. container:: verbose
2608 .. container:: verbose
2609
2609
2610 Template:
2610 Template:
2611
2611
2612 The following keywords are supported in addition to the common template
2612 The following keywords are supported in addition to the common template
2613 keywords and functions. See also :hg:`help templates`.
2613 keywords and functions. See also :hg:`help templates`.
2614
2614
2615 :diff: String. Diff content.
2615 :diff: String. Diff content.
2616 :parents: List of strings. Parent nodes of the changeset.
2616 :parents: List of strings. Parent nodes of the changeset.
2617
2617
2618 Examples:
2618 Examples:
2619
2619
2620 - use export and import to transplant a bugfix to the current
2620 - use export and import to transplant a bugfix to the current
2621 branch::
2621 branch::
2622
2622
2623 hg export -r 9353 | hg import -
2623 hg export -r 9353 | hg import -
2624
2624
2625 - export all the changesets between two revisions to a file with
2625 - export all the changesets between two revisions to a file with
2626 rename information::
2626 rename information::
2627
2627
2628 hg export --git -r 123:150 > changes.txt
2628 hg export --git -r 123:150 > changes.txt
2629
2629
2630 - split outgoing changes into a series of patches with
2630 - split outgoing changes into a series of patches with
2631 descriptive names::
2631 descriptive names::
2632
2632
2633 hg export -r "outgoing()" -o "%n-%m.patch"
2633 hg export -r "outgoing()" -o "%n-%m.patch"
2634
2634
2635 Returns 0 on success.
2635 Returns 0 on success.
2636 """
2636 """
2637 opts = pycompat.byteskwargs(opts)
2637 opts = pycompat.byteskwargs(opts)
2638 bookmark = opts.get(b'bookmark')
2638 bookmark = opts.get(b'bookmark')
2639 changesets += tuple(opts.get(b'rev', []))
2639 changesets += tuple(opts.get(b'rev', []))
2640
2640
2641 cmdutil.check_at_most_one_arg(opts, b'rev', b'bookmark')
2641 cmdutil.check_at_most_one_arg(opts, b'rev', b'bookmark')
2642
2642
2643 if bookmark:
2643 if bookmark:
2644 if bookmark not in repo._bookmarks:
2644 if bookmark not in repo._bookmarks:
2645 raise error.Abort(_(b"bookmark '%s' not found") % bookmark)
2645 raise error.Abort(_(b"bookmark '%s' not found") % bookmark)
2646
2646
2647 revs = scmutil.bookmarkrevs(repo, bookmark)
2647 revs = scmutil.bookmarkrevs(repo, bookmark)
2648 else:
2648 else:
2649 if not changesets:
2649 if not changesets:
2650 changesets = [b'.']
2650 changesets = [b'.']
2651
2651
2652 repo = scmutil.unhidehashlikerevs(repo, changesets, b'nowarn')
2652 repo = scmutil.unhidehashlikerevs(repo, changesets, b'nowarn')
2653 revs = scmutil.revrange(repo, changesets)
2653 revs = scmutil.revrange(repo, changesets)
2654
2654
2655 if not revs:
2655 if not revs:
2656 raise error.Abort(_(b"export requires at least one changeset"))
2656 raise error.Abort(_(b"export requires at least one changeset"))
2657 if len(revs) > 1:
2657 if len(revs) > 1:
2658 ui.note(_(b'exporting patches:\n'))
2658 ui.note(_(b'exporting patches:\n'))
2659 else:
2659 else:
2660 ui.note(_(b'exporting patch:\n'))
2660 ui.note(_(b'exporting patch:\n'))
2661
2661
2662 fntemplate = opts.get(b'output')
2662 fntemplate = opts.get(b'output')
2663 if cmdutil.isstdiofilename(fntemplate):
2663 if cmdutil.isstdiofilename(fntemplate):
2664 fntemplate = b''
2664 fntemplate = b''
2665
2665
2666 if fntemplate:
2666 if fntemplate:
2667 fm = formatter.nullformatter(ui, b'export', opts)
2667 fm = formatter.nullformatter(ui, b'export', opts)
2668 else:
2668 else:
2669 ui.pager(b'export')
2669 ui.pager(b'export')
2670 fm = ui.formatter(b'export', opts)
2670 fm = ui.formatter(b'export', opts)
2671 with fm:
2671 with fm:
2672 cmdutil.export(
2672 cmdutil.export(
2673 repo,
2673 repo,
2674 revs,
2674 revs,
2675 fm,
2675 fm,
2676 fntemplate=fntemplate,
2676 fntemplate=fntemplate,
2677 switch_parent=opts.get(b'switch_parent'),
2677 switch_parent=opts.get(b'switch_parent'),
2678 opts=patch.diffallopts(ui, opts),
2678 opts=patch.diffallopts(ui, opts),
2679 )
2679 )
2680
2680
2681
2681
2682 @command(
2682 @command(
2683 b'files',
2683 b'files',
2684 [
2684 [
2685 (
2685 (
2686 b'r',
2686 b'r',
2687 b'rev',
2687 b'rev',
2688 b'',
2688 b'',
2689 _(b'search the repository as it is in REV'),
2689 _(b'search the repository as it is in REV'),
2690 _(b'REV'),
2690 _(b'REV'),
2691 ),
2691 ),
2692 (
2692 (
2693 b'0',
2693 b'0',
2694 b'print0',
2694 b'print0',
2695 None,
2695 None,
2696 _(b'end filenames with NUL, for use with xargs'),
2696 _(b'end filenames with NUL, for use with xargs'),
2697 ),
2697 ),
2698 ]
2698 ]
2699 + walkopts
2699 + walkopts
2700 + formatteropts
2700 + formatteropts
2701 + subrepoopts,
2701 + subrepoopts,
2702 _(b'[OPTION]... [FILE]...'),
2702 _(b'[OPTION]... [FILE]...'),
2703 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2703 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2704 intents={INTENT_READONLY},
2704 intents={INTENT_READONLY},
2705 )
2705 )
2706 def files(ui, repo, *pats, **opts):
2706 def files(ui, repo, *pats, **opts):
2707 """list tracked files
2707 """list tracked files
2708
2708
2709 Print files under Mercurial control in the working directory or
2709 Print files under Mercurial control in the working directory or
2710 specified revision for given files (excluding removed files).
2710 specified revision for given files (excluding removed files).
2711 Files can be specified as filenames or filesets.
2711 Files can be specified as filenames or filesets.
2712
2712
2713 If no files are given to match, this command prints the names
2713 If no files are given to match, this command prints the names
2714 of all files under Mercurial control.
2714 of all files under Mercurial control.
2715
2715
2716 .. container:: verbose
2716 .. container:: verbose
2717
2717
2718 Template:
2718 Template:
2719
2719
2720 The following keywords are supported in addition to the common template
2720 The following keywords are supported in addition to the common template
2721 keywords and functions. See also :hg:`help templates`.
2721 keywords and functions. See also :hg:`help templates`.
2722
2722
2723 :flags: String. Character denoting file's symlink and executable bits.
2723 :flags: String. Character denoting file's symlink and executable bits.
2724 :path: String. Repository-absolute path of the file.
2724 :path: String. Repository-absolute path of the file.
2725 :size: Integer. Size of the file in bytes.
2725 :size: Integer. Size of the file in bytes.
2726
2726
2727 Examples:
2727 Examples:
2728
2728
2729 - list all files under the current directory::
2729 - list all files under the current directory::
2730
2730
2731 hg files .
2731 hg files .
2732
2732
2733 - shows sizes and flags for current revision::
2733 - shows sizes and flags for current revision::
2734
2734
2735 hg files -vr .
2735 hg files -vr .
2736
2736
2737 - list all files named README::
2737 - list all files named README::
2738
2738
2739 hg files -I "**/README"
2739 hg files -I "**/README"
2740
2740
2741 - list all binary files::
2741 - list all binary files::
2742
2742
2743 hg files "set:binary()"
2743 hg files "set:binary()"
2744
2744
2745 - find files containing a regular expression::
2745 - find files containing a regular expression::
2746
2746
2747 hg files "set:grep('bob')"
2747 hg files "set:grep('bob')"
2748
2748
2749 - search tracked file contents with xargs and grep::
2749 - search tracked file contents with xargs and grep::
2750
2750
2751 hg files -0 | xargs -0 grep foo
2751 hg files -0 | xargs -0 grep foo
2752
2752
2753 See :hg:`help patterns` and :hg:`help filesets` for more information
2753 See :hg:`help patterns` and :hg:`help filesets` for more information
2754 on specifying file patterns.
2754 on specifying file patterns.
2755
2755
2756 Returns 0 if a match is found, 1 otherwise.
2756 Returns 0 if a match is found, 1 otherwise.
2757
2757
2758 """
2758 """
2759
2759
2760 opts = pycompat.byteskwargs(opts)
2760 opts = pycompat.byteskwargs(opts)
2761 rev = opts.get(b'rev')
2761 rev = opts.get(b'rev')
2762 if rev:
2762 if rev:
2763 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
2763 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
2764 ctx = scmutil.revsingle(repo, rev, None)
2764 ctx = scmutil.revsingle(repo, rev, None)
2765
2765
2766 end = b'\n'
2766 end = b'\n'
2767 if opts.get(b'print0'):
2767 if opts.get(b'print0'):
2768 end = b'\0'
2768 end = b'\0'
2769 fmt = b'%s' + end
2769 fmt = b'%s' + end
2770
2770
2771 m = scmutil.match(ctx, pats, opts)
2771 m = scmutil.match(ctx, pats, opts)
2772 ui.pager(b'files')
2772 ui.pager(b'files')
2773 uipathfn = scmutil.getuipathfn(ctx.repo(), legacyrelativevalue=True)
2773 uipathfn = scmutil.getuipathfn(ctx.repo(), legacyrelativevalue=True)
2774 with ui.formatter(b'files', opts) as fm:
2774 with ui.formatter(b'files', opts) as fm:
2775 return cmdutil.files(
2775 return cmdutil.files(
2776 ui, ctx, m, uipathfn, fm, fmt, opts.get(b'subrepos')
2776 ui, ctx, m, uipathfn, fm, fmt, opts.get(b'subrepos')
2777 )
2777 )
2778
2778
2779
2779
2780 @command(
2780 @command(
2781 b'forget',
2781 b'forget',
2782 [(b'i', b'interactive', None, _(b'use interactive mode')),]
2782 [(b'i', b'interactive', None, _(b'use interactive mode')),]
2783 + walkopts
2783 + walkopts
2784 + dryrunopts,
2784 + dryrunopts,
2785 _(b'[OPTION]... FILE...'),
2785 _(b'[OPTION]... FILE...'),
2786 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2786 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2787 helpbasic=True,
2787 helpbasic=True,
2788 inferrepo=True,
2788 inferrepo=True,
2789 )
2789 )
2790 def forget(ui, repo, *pats, **opts):
2790 def forget(ui, repo, *pats, **opts):
2791 """forget the specified files on the next commit
2791 """forget the specified files on the next commit
2792
2792
2793 Mark the specified files so they will no longer be tracked
2793 Mark the specified files so they will no longer be tracked
2794 after the next commit.
2794 after the next commit.
2795
2795
2796 This only removes files from the current branch, not from the
2796 This only removes files from the current branch, not from the
2797 entire project history, and it does not delete them from the
2797 entire project history, and it does not delete them from the
2798 working directory.
2798 working directory.
2799
2799
2800 To delete the file from the working directory, see :hg:`remove`.
2800 To delete the file from the working directory, see :hg:`remove`.
2801
2801
2802 To undo a forget before the next commit, see :hg:`add`.
2802 To undo a forget before the next commit, see :hg:`add`.
2803
2803
2804 .. container:: verbose
2804 .. container:: verbose
2805
2805
2806 Examples:
2806 Examples:
2807
2807
2808 - forget newly-added binary files::
2808 - forget newly-added binary files::
2809
2809
2810 hg forget "set:added() and binary()"
2810 hg forget "set:added() and binary()"
2811
2811
2812 - forget files that would be excluded by .hgignore::
2812 - forget files that would be excluded by .hgignore::
2813
2813
2814 hg forget "set:hgignore()"
2814 hg forget "set:hgignore()"
2815
2815
2816 Returns 0 on success.
2816 Returns 0 on success.
2817 """
2817 """
2818
2818
2819 opts = pycompat.byteskwargs(opts)
2819 opts = pycompat.byteskwargs(opts)
2820 if not pats:
2820 if not pats:
2821 raise error.Abort(_(b'no files specified'))
2821 raise error.Abort(_(b'no files specified'))
2822
2822
2823 m = scmutil.match(repo[None], pats, opts)
2823 m = scmutil.match(repo[None], pats, opts)
2824 dryrun, interactive = opts.get(b'dry_run'), opts.get(b'interactive')
2824 dryrun, interactive = opts.get(b'dry_run'), opts.get(b'interactive')
2825 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
2825 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
2826 rejected = cmdutil.forget(
2826 rejected = cmdutil.forget(
2827 ui,
2827 ui,
2828 repo,
2828 repo,
2829 m,
2829 m,
2830 prefix=b"",
2830 prefix=b"",
2831 uipathfn=uipathfn,
2831 uipathfn=uipathfn,
2832 explicitonly=False,
2832 explicitonly=False,
2833 dryrun=dryrun,
2833 dryrun=dryrun,
2834 interactive=interactive,
2834 interactive=interactive,
2835 )[0]
2835 )[0]
2836 return rejected and 1 or 0
2836 return rejected and 1 or 0
2837
2837
2838
2838
2839 @command(
2839 @command(
2840 b'graft',
2840 b'graft',
2841 [
2841 [
2842 (b'r', b'rev', [], _(b'revisions to graft'), _(b'REV')),
2842 (b'r', b'rev', [], _(b'revisions to graft'), _(b'REV')),
2843 (
2843 (
2844 b'',
2844 b'',
2845 b'base',
2845 b'base',
2846 b'',
2846 b'',
2847 _(b'base revision when doing the graft merge (ADVANCED)'),
2847 _(b'base revision when doing the graft merge (ADVANCED)'),
2848 _(b'REV'),
2848 _(b'REV'),
2849 ),
2849 ),
2850 (b'c', b'continue', False, _(b'resume interrupted graft')),
2850 (b'c', b'continue', False, _(b'resume interrupted graft')),
2851 (b'', b'stop', False, _(b'stop interrupted graft')),
2851 (b'', b'stop', False, _(b'stop interrupted graft')),
2852 (b'', b'abort', False, _(b'abort interrupted graft')),
2852 (b'', b'abort', False, _(b'abort interrupted graft')),
2853 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
2853 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
2854 (b'', b'log', None, _(b'append graft info to log message')),
2854 (b'', b'log', None, _(b'append graft info to log message')),
2855 (
2855 (
2856 b'',
2856 b'',
2857 b'no-commit',
2857 b'no-commit',
2858 None,
2858 None,
2859 _(b"don't commit, just apply the changes in working directory"),
2859 _(b"don't commit, just apply the changes in working directory"),
2860 ),
2860 ),
2861 (b'f', b'force', False, _(b'force graft')),
2861 (b'f', b'force', False, _(b'force graft')),
2862 (
2862 (
2863 b'D',
2863 b'D',
2864 b'currentdate',
2864 b'currentdate',
2865 False,
2865 False,
2866 _(b'record the current date as commit date'),
2866 _(b'record the current date as commit date'),
2867 ),
2867 ),
2868 (
2868 (
2869 b'U',
2869 b'U',
2870 b'currentuser',
2870 b'currentuser',
2871 False,
2871 False,
2872 _(b'record the current user as committer'),
2872 _(b'record the current user as committer'),
2873 ),
2873 ),
2874 ]
2874 ]
2875 + commitopts2
2875 + commitopts2
2876 + mergetoolopts
2876 + mergetoolopts
2877 + dryrunopts,
2877 + dryrunopts,
2878 _(b'[OPTION]... [-r REV]... REV...'),
2878 _(b'[OPTION]... [-r REV]... REV...'),
2879 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
2879 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
2880 )
2880 )
2881 def graft(ui, repo, *revs, **opts):
2881 def graft(ui, repo, *revs, **opts):
2882 '''copy changes from other branches onto the current branch
2882 '''copy changes from other branches onto the current branch
2883
2883
2884 This command uses Mercurial's merge logic to copy individual
2884 This command uses Mercurial's merge logic to copy individual
2885 changes from other branches without merging branches in the
2885 changes from other branches without merging branches in the
2886 history graph. This is sometimes known as 'backporting' or
2886 history graph. This is sometimes known as 'backporting' or
2887 'cherry-picking'. By default, graft will copy user, date, and
2887 'cherry-picking'. By default, graft will copy user, date, and
2888 description from the source changesets.
2888 description from the source changesets.
2889
2889
2890 Changesets that are ancestors of the current revision, that have
2890 Changesets that are ancestors of the current revision, that have
2891 already been grafted, or that are merges will be skipped.
2891 already been grafted, or that are merges will be skipped.
2892
2892
2893 If --log is specified, log messages will have a comment appended
2893 If --log is specified, log messages will have a comment appended
2894 of the form::
2894 of the form::
2895
2895
2896 (grafted from CHANGESETHASH)
2896 (grafted from CHANGESETHASH)
2897
2897
2898 If --force is specified, revisions will be grafted even if they
2898 If --force is specified, revisions will be grafted even if they
2899 are already ancestors of, or have been grafted to, the destination.
2899 are already ancestors of, or have been grafted to, the destination.
2900 This is useful when the revisions have since been backed out.
2900 This is useful when the revisions have since been backed out.
2901
2901
2902 If a graft merge results in conflicts, the graft process is
2902 If a graft merge results in conflicts, the graft process is
2903 interrupted so that the current merge can be manually resolved.
2903 interrupted so that the current merge can be manually resolved.
2904 Once all conflicts are addressed, the graft process can be
2904 Once all conflicts are addressed, the graft process can be
2905 continued with the -c/--continue option.
2905 continued with the -c/--continue option.
2906
2906
2907 The -c/--continue option reapplies all the earlier options.
2907 The -c/--continue option reapplies all the earlier options.
2908
2908
2909 .. container:: verbose
2909 .. container:: verbose
2910
2910
2911 The --base option exposes more of how graft internally uses merge with a
2911 The --base option exposes more of how graft internally uses merge with a
2912 custom base revision. --base can be used to specify another ancestor than
2912 custom base revision. --base can be used to specify another ancestor than
2913 the first and only parent.
2913 the first and only parent.
2914
2914
2915 The command::
2915 The command::
2916
2916
2917 hg graft -r 345 --base 234
2917 hg graft -r 345 --base 234
2918
2918
2919 is thus pretty much the same as::
2919 is thus pretty much the same as::
2920
2920
2921 hg diff -r 234 -r 345 | hg import
2921 hg diff -r 234 -r 345 | hg import
2922
2922
2923 but using merge to resolve conflicts and track moved files.
2923 but using merge to resolve conflicts and track moved files.
2924
2924
2925 The result of a merge can thus be backported as a single commit by
2925 The result of a merge can thus be backported as a single commit by
2926 specifying one of the merge parents as base, and thus effectively
2926 specifying one of the merge parents as base, and thus effectively
2927 grafting the changes from the other side.
2927 grafting the changes from the other side.
2928
2928
2929 It is also possible to collapse multiple changesets and clean up history
2929 It is also possible to collapse multiple changesets and clean up history
2930 by specifying another ancestor as base, much like rebase --collapse
2930 by specifying another ancestor as base, much like rebase --collapse
2931 --keep.
2931 --keep.
2932
2932
2933 The commit message can be tweaked after the fact using commit --amend .
2933 The commit message can be tweaked after the fact using commit --amend .
2934
2934
2935 For using non-ancestors as the base to backout changes, see the backout
2935 For using non-ancestors as the base to backout changes, see the backout
2936 command and the hidden --parent option.
2936 command and the hidden --parent option.
2937
2937
2938 .. container:: verbose
2938 .. container:: verbose
2939
2939
2940 Examples:
2940 Examples:
2941
2941
2942 - copy a single change to the stable branch and edit its description::
2942 - copy a single change to the stable branch and edit its description::
2943
2943
2944 hg update stable
2944 hg update stable
2945 hg graft --edit 9393
2945 hg graft --edit 9393
2946
2946
2947 - graft a range of changesets with one exception, updating dates::
2947 - graft a range of changesets with one exception, updating dates::
2948
2948
2949 hg graft -D "2085::2093 and not 2091"
2949 hg graft -D "2085::2093 and not 2091"
2950
2950
2951 - continue a graft after resolving conflicts::
2951 - continue a graft after resolving conflicts::
2952
2952
2953 hg graft -c
2953 hg graft -c
2954
2954
2955 - show the source of a grafted changeset::
2955 - show the source of a grafted changeset::
2956
2956
2957 hg log --debug -r .
2957 hg log --debug -r .
2958
2958
2959 - show revisions sorted by date::
2959 - show revisions sorted by date::
2960
2960
2961 hg log -r "sort(all(), date)"
2961 hg log -r "sort(all(), date)"
2962
2962
2963 - backport the result of a merge as a single commit::
2963 - backport the result of a merge as a single commit::
2964
2964
2965 hg graft -r 123 --base 123^
2965 hg graft -r 123 --base 123^
2966
2966
2967 - land a feature branch as one changeset::
2967 - land a feature branch as one changeset::
2968
2968
2969 hg up -cr default
2969 hg up -cr default
2970 hg graft -r featureX --base "ancestor('featureX', 'default')"
2970 hg graft -r featureX --base "ancestor('featureX', 'default')"
2971
2971
2972 See :hg:`help revisions` for more about specifying revisions.
2972 See :hg:`help revisions` for more about specifying revisions.
2973
2973
2974 Returns 0 on successful completion, 1 if there are unresolved files.
2974 Returns 0 on successful completion, 1 if there are unresolved files.
2975 '''
2975 '''
2976 with repo.wlock():
2976 with repo.wlock():
2977 return _dograft(ui, repo, *revs, **opts)
2977 return _dograft(ui, repo, *revs, **opts)
2978
2978
2979
2979
2980 def _dograft(ui, repo, *revs, **opts):
2980 def _dograft(ui, repo, *revs, **opts):
2981 opts = pycompat.byteskwargs(opts)
2981 opts = pycompat.byteskwargs(opts)
2982 if revs and opts.get(b'rev'):
2982 if revs and opts.get(b'rev'):
2983 ui.warn(
2983 ui.warn(
2984 _(
2984 _(
2985 b'warning: inconsistent use of --rev might give unexpected '
2985 b'warning: inconsistent use of --rev might give unexpected '
2986 b'revision ordering!\n'
2986 b'revision ordering!\n'
2987 )
2987 )
2988 )
2988 )
2989
2989
2990 revs = list(revs)
2990 revs = list(revs)
2991 revs.extend(opts.get(b'rev'))
2991 revs.extend(opts.get(b'rev'))
2992 # a dict of data to be stored in state file
2992 # a dict of data to be stored in state file
2993 statedata = {}
2993 statedata = {}
2994 # list of new nodes created by ongoing graft
2994 # list of new nodes created by ongoing graft
2995 statedata[b'newnodes'] = []
2995 statedata[b'newnodes'] = []
2996
2996
2997 cmdutil.resolvecommitoptions(ui, opts)
2997 cmdutil.resolvecommitoptions(ui, opts)
2998
2998
2999 editor = cmdutil.getcommiteditor(
2999 editor = cmdutil.getcommiteditor(
3000 editform=b'graft', **pycompat.strkwargs(opts)
3000 editform=b'graft', **pycompat.strkwargs(opts)
3001 )
3001 )
3002
3002
3003 cmdutil.check_at_most_one_arg(opts, b'abort', b'stop', b'continue')
3003 cmdutil.check_at_most_one_arg(opts, b'abort', b'stop', b'continue')
3004
3004
3005 cont = False
3005 cont = False
3006 if opts.get(b'no_commit'):
3006 if opts.get(b'no_commit'):
3007 cmdutil.check_incompatible_arguments(
3007 cmdutil.check_incompatible_arguments(
3008 opts,
3008 opts,
3009 b'no_commit',
3009 b'no_commit',
3010 [b'edit', b'currentuser', b'currentdate', b'log'],
3010 [b'edit', b'currentuser', b'currentdate', b'log'],
3011 )
3011 )
3012
3012
3013 graftstate = statemod.cmdstate(repo, b'graftstate')
3013 graftstate = statemod.cmdstate(repo, b'graftstate')
3014
3014
3015 if opts.get(b'stop'):
3015 if opts.get(b'stop'):
3016 cmdutil.check_incompatible_arguments(
3016 cmdutil.check_incompatible_arguments(
3017 opts,
3017 opts,
3018 b'stop',
3018 b'stop',
3019 [
3019 [
3020 b'edit',
3020 b'edit',
3021 b'log',
3021 b'log',
3022 b'user',
3022 b'user',
3023 b'date',
3023 b'date',
3024 b'currentdate',
3024 b'currentdate',
3025 b'currentuser',
3025 b'currentuser',
3026 b'rev',
3026 b'rev',
3027 ],
3027 ],
3028 )
3028 )
3029 return _stopgraft(ui, repo, graftstate)
3029 return _stopgraft(ui, repo, graftstate)
3030 elif opts.get(b'abort'):
3030 elif opts.get(b'abort'):
3031 cmdutil.check_incompatible_arguments(
3031 cmdutil.check_incompatible_arguments(
3032 opts,
3032 opts,
3033 b'abort',
3033 b'abort',
3034 [
3034 [
3035 b'edit',
3035 b'edit',
3036 b'log',
3036 b'log',
3037 b'user',
3037 b'user',
3038 b'date',
3038 b'date',
3039 b'currentdate',
3039 b'currentdate',
3040 b'currentuser',
3040 b'currentuser',
3041 b'rev',
3041 b'rev',
3042 ],
3042 ],
3043 )
3043 )
3044 return cmdutil.abortgraft(ui, repo, graftstate)
3044 return cmdutil.abortgraft(ui, repo, graftstate)
3045 elif opts.get(b'continue'):
3045 elif opts.get(b'continue'):
3046 cont = True
3046 cont = True
3047 if revs:
3047 if revs:
3048 raise error.Abort(_(b"can't specify --continue and revisions"))
3048 raise error.Abort(_(b"can't specify --continue and revisions"))
3049 # read in unfinished revisions
3049 # read in unfinished revisions
3050 if graftstate.exists():
3050 if graftstate.exists():
3051 statedata = cmdutil.readgraftstate(repo, graftstate)
3051 statedata = cmdutil.readgraftstate(repo, graftstate)
3052 if statedata.get(b'date'):
3052 if statedata.get(b'date'):
3053 opts[b'date'] = statedata[b'date']
3053 opts[b'date'] = statedata[b'date']
3054 if statedata.get(b'user'):
3054 if statedata.get(b'user'):
3055 opts[b'user'] = statedata[b'user']
3055 opts[b'user'] = statedata[b'user']
3056 if statedata.get(b'log'):
3056 if statedata.get(b'log'):
3057 opts[b'log'] = True
3057 opts[b'log'] = True
3058 if statedata.get(b'no_commit'):
3058 if statedata.get(b'no_commit'):
3059 opts[b'no_commit'] = statedata.get(b'no_commit')
3059 opts[b'no_commit'] = statedata.get(b'no_commit')
3060 if statedata.get(b'base'):
3060 if statedata.get(b'base'):
3061 opts[b'base'] = statedata.get(b'base')
3061 opts[b'base'] = statedata.get(b'base')
3062 nodes = statedata[b'nodes']
3062 nodes = statedata[b'nodes']
3063 revs = [repo[node].rev() for node in nodes]
3063 revs = [repo[node].rev() for node in nodes]
3064 else:
3064 else:
3065 cmdutil.wrongtooltocontinue(repo, _(b'graft'))
3065 cmdutil.wrongtooltocontinue(repo, _(b'graft'))
3066 else:
3066 else:
3067 if not revs:
3067 if not revs:
3068 raise error.Abort(_(b'no revisions specified'))
3068 raise error.Abort(_(b'no revisions specified'))
3069 cmdutil.checkunfinished(repo)
3069 cmdutil.checkunfinished(repo)
3070 cmdutil.bailifchanged(repo)
3070 cmdutil.bailifchanged(repo)
3071 revs = scmutil.revrange(repo, revs)
3071 revs = scmutil.revrange(repo, revs)
3072
3072
3073 skipped = set()
3073 skipped = set()
3074 basectx = None
3074 basectx = None
3075 if opts.get(b'base'):
3075 if opts.get(b'base'):
3076 basectx = scmutil.revsingle(repo, opts[b'base'], None)
3076 basectx = scmutil.revsingle(repo, opts[b'base'], None)
3077 if basectx is None:
3077 if basectx is None:
3078 # check for merges
3078 # check for merges
3079 for rev in repo.revs(b'%ld and merge()', revs):
3079 for rev in repo.revs(b'%ld and merge()', revs):
3080 ui.warn(_(b'skipping ungraftable merge revision %d\n') % rev)
3080 ui.warn(_(b'skipping ungraftable merge revision %d\n') % rev)
3081 skipped.add(rev)
3081 skipped.add(rev)
3082 revs = [r for r in revs if r not in skipped]
3082 revs = [r for r in revs if r not in skipped]
3083 if not revs:
3083 if not revs:
3084 return -1
3084 return -1
3085 if basectx is not None and len(revs) != 1:
3085 if basectx is not None and len(revs) != 1:
3086 raise error.Abort(_(b'only one revision allowed with --base '))
3086 raise error.Abort(_(b'only one revision allowed with --base '))
3087
3087
3088 # Don't check in the --continue case, in effect retaining --force across
3088 # Don't check in the --continue case, in effect retaining --force across
3089 # --continues. That's because without --force, any revisions we decided to
3089 # --continues. That's because without --force, any revisions we decided to
3090 # skip would have been filtered out here, so they wouldn't have made their
3090 # skip would have been filtered out here, so they wouldn't have made their
3091 # way to the graftstate. With --force, any revisions we would have otherwise
3091 # way to the graftstate. With --force, any revisions we would have otherwise
3092 # skipped would not have been filtered out, and if they hadn't been applied
3092 # skipped would not have been filtered out, and if they hadn't been applied
3093 # already, they'd have been in the graftstate.
3093 # already, they'd have been in the graftstate.
3094 if not (cont or opts.get(b'force')) and basectx is None:
3094 if not (cont or opts.get(b'force')) and basectx is None:
3095 # check for ancestors of dest branch
3095 # check for ancestors of dest branch
3096 ancestors = repo.revs(b'%ld & (::.)', revs)
3096 ancestors = repo.revs(b'%ld & (::.)', revs)
3097 for rev in ancestors:
3097 for rev in ancestors:
3098 ui.warn(_(b'skipping ancestor revision %d:%s\n') % (rev, repo[rev]))
3098 ui.warn(_(b'skipping ancestor revision %d:%s\n') % (rev, repo[rev]))
3099
3099
3100 revs = [r for r in revs if r not in ancestors]
3100 revs = [r for r in revs if r not in ancestors]
3101
3101
3102 if not revs:
3102 if not revs:
3103 return -1
3103 return -1
3104
3104
3105 # analyze revs for earlier grafts
3105 # analyze revs for earlier grafts
3106 ids = {}
3106 ids = {}
3107 for ctx in repo.set(b"%ld", revs):
3107 for ctx in repo.set(b"%ld", revs):
3108 ids[ctx.hex()] = ctx.rev()
3108 ids[ctx.hex()] = ctx.rev()
3109 n = ctx.extra().get(b'source')
3109 n = ctx.extra().get(b'source')
3110 if n:
3110 if n:
3111 ids[n] = ctx.rev()
3111 ids[n] = ctx.rev()
3112
3112
3113 # check ancestors for earlier grafts
3113 # check ancestors for earlier grafts
3114 ui.debug(b'scanning for duplicate grafts\n')
3114 ui.debug(b'scanning for duplicate grafts\n')
3115
3115
3116 # The only changesets we can be sure doesn't contain grafts of any
3116 # The only changesets we can be sure doesn't contain grafts of any
3117 # revs, are the ones that are common ancestors of *all* revs:
3117 # revs, are the ones that are common ancestors of *all* revs:
3118 for rev in repo.revs(b'only(%d,ancestor(%ld))', repo[b'.'].rev(), revs):
3118 for rev in repo.revs(b'only(%d,ancestor(%ld))', repo[b'.'].rev(), revs):
3119 ctx = repo[rev]
3119 ctx = repo[rev]
3120 n = ctx.extra().get(b'source')
3120 n = ctx.extra().get(b'source')
3121 if n in ids:
3121 if n in ids:
3122 try:
3122 try:
3123 r = repo[n].rev()
3123 r = repo[n].rev()
3124 except error.RepoLookupError:
3124 except error.RepoLookupError:
3125 r = None
3125 r = None
3126 if r in revs:
3126 if r in revs:
3127 ui.warn(
3127 ui.warn(
3128 _(
3128 _(
3129 b'skipping revision %d:%s '
3129 b'skipping revision %d:%s '
3130 b'(already grafted to %d:%s)\n'
3130 b'(already grafted to %d:%s)\n'
3131 )
3131 )
3132 % (r, repo[r], rev, ctx)
3132 % (r, repo[r], rev, ctx)
3133 )
3133 )
3134 revs.remove(r)
3134 revs.remove(r)
3135 elif ids[n] in revs:
3135 elif ids[n] in revs:
3136 if r is None:
3136 if r is None:
3137 ui.warn(
3137 ui.warn(
3138 _(
3138 _(
3139 b'skipping already grafted revision %d:%s '
3139 b'skipping already grafted revision %d:%s '
3140 b'(%d:%s also has unknown origin %s)\n'
3140 b'(%d:%s also has unknown origin %s)\n'
3141 )
3141 )
3142 % (ids[n], repo[ids[n]], rev, ctx, n[:12])
3142 % (ids[n], repo[ids[n]], rev, ctx, n[:12])
3143 )
3143 )
3144 else:
3144 else:
3145 ui.warn(
3145 ui.warn(
3146 _(
3146 _(
3147 b'skipping already grafted revision %d:%s '
3147 b'skipping already grafted revision %d:%s '
3148 b'(%d:%s also has origin %d:%s)\n'
3148 b'(%d:%s also has origin %d:%s)\n'
3149 )
3149 )
3150 % (ids[n], repo[ids[n]], rev, ctx, r, n[:12])
3150 % (ids[n], repo[ids[n]], rev, ctx, r, n[:12])
3151 )
3151 )
3152 revs.remove(ids[n])
3152 revs.remove(ids[n])
3153 elif ctx.hex() in ids:
3153 elif ctx.hex() in ids:
3154 r = ids[ctx.hex()]
3154 r = ids[ctx.hex()]
3155 if r in revs:
3155 if r in revs:
3156 ui.warn(
3156 ui.warn(
3157 _(
3157 _(
3158 b'skipping already grafted revision %d:%s '
3158 b'skipping already grafted revision %d:%s '
3159 b'(was grafted from %d:%s)\n'
3159 b'(was grafted from %d:%s)\n'
3160 )
3160 )
3161 % (r, repo[r], rev, ctx)
3161 % (r, repo[r], rev, ctx)
3162 )
3162 )
3163 revs.remove(r)
3163 revs.remove(r)
3164 if not revs:
3164 if not revs:
3165 return -1
3165 return -1
3166
3166
3167 if opts.get(b'no_commit'):
3167 if opts.get(b'no_commit'):
3168 statedata[b'no_commit'] = True
3168 statedata[b'no_commit'] = True
3169 if opts.get(b'base'):
3169 if opts.get(b'base'):
3170 statedata[b'base'] = opts[b'base']
3170 statedata[b'base'] = opts[b'base']
3171 for pos, ctx in enumerate(repo.set(b"%ld", revs)):
3171 for pos, ctx in enumerate(repo.set(b"%ld", revs)):
3172 desc = b'%d:%s "%s"' % (
3172 desc = b'%d:%s "%s"' % (
3173 ctx.rev(),
3173 ctx.rev(),
3174 ctx,
3174 ctx,
3175 ctx.description().split(b'\n', 1)[0],
3175 ctx.description().split(b'\n', 1)[0],
3176 )
3176 )
3177 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
3177 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
3178 if names:
3178 if names:
3179 desc += b' (%s)' % b' '.join(names)
3179 desc += b' (%s)' % b' '.join(names)
3180 ui.status(_(b'grafting %s\n') % desc)
3180 ui.status(_(b'grafting %s\n') % desc)
3181 if opts.get(b'dry_run'):
3181 if opts.get(b'dry_run'):
3182 continue
3182 continue
3183
3183
3184 source = ctx.extra().get(b'source')
3184 source = ctx.extra().get(b'source')
3185 extra = {}
3185 extra = {}
3186 if source:
3186 if source:
3187 extra[b'source'] = source
3187 extra[b'source'] = source
3188 extra[b'intermediate-source'] = ctx.hex()
3188 extra[b'intermediate-source'] = ctx.hex()
3189 else:
3189 else:
3190 extra[b'source'] = ctx.hex()
3190 extra[b'source'] = ctx.hex()
3191 user = ctx.user()
3191 user = ctx.user()
3192 if opts.get(b'user'):
3192 if opts.get(b'user'):
3193 user = opts[b'user']
3193 user = opts[b'user']
3194 statedata[b'user'] = user
3194 statedata[b'user'] = user
3195 date = ctx.date()
3195 date = ctx.date()
3196 if opts.get(b'date'):
3196 if opts.get(b'date'):
3197 date = opts[b'date']
3197 date = opts[b'date']
3198 statedata[b'date'] = date
3198 statedata[b'date'] = date
3199 message = ctx.description()
3199 message = ctx.description()
3200 if opts.get(b'log'):
3200 if opts.get(b'log'):
3201 message += b'\n(grafted from %s)' % ctx.hex()
3201 message += b'\n(grafted from %s)' % ctx.hex()
3202 statedata[b'log'] = True
3202 statedata[b'log'] = True
3203
3203
3204 # we don't merge the first commit when continuing
3204 # we don't merge the first commit when continuing
3205 if not cont:
3205 if not cont:
3206 # perform the graft merge with p1(rev) as 'ancestor'
3206 # perform the graft merge with p1(rev) as 'ancestor'
3207 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
3207 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
3208 base = ctx.p1() if basectx is None else basectx
3208 base = ctx.p1() if basectx is None else basectx
3209 with ui.configoverride(overrides, b'graft'):
3209 with ui.configoverride(overrides, b'graft'):
3210 stats = mergemod.graft(repo, ctx, base, [b'local', b'graft'])
3210 stats = mergemod.graft(repo, ctx, base, [b'local', b'graft'])
3211 # report any conflicts
3211 # report any conflicts
3212 if stats.unresolvedcount > 0:
3212 if stats.unresolvedcount > 0:
3213 # write out state for --continue
3213 # write out state for --continue
3214 nodes = [repo[rev].hex() for rev in revs[pos:]]
3214 nodes = [repo[rev].hex() for rev in revs[pos:]]
3215 statedata[b'nodes'] = nodes
3215 statedata[b'nodes'] = nodes
3216 stateversion = 1
3216 stateversion = 1
3217 graftstate.save(stateversion, statedata)
3217 graftstate.save(stateversion, statedata)
3218 ui.error(_(b"abort: unresolved conflicts, can't continue\n"))
3218 ui.error(_(b"abort: unresolved conflicts, can't continue\n"))
3219 ui.error(_(b"(use 'hg resolve' and 'hg graft --continue')\n"))
3219 ui.error(_(b"(use 'hg resolve' and 'hg graft --continue')\n"))
3220 return 1
3220 return 1
3221 else:
3221 else:
3222 cont = False
3222 cont = False
3223
3223
3224 # commit if --no-commit is false
3224 # commit if --no-commit is false
3225 if not opts.get(b'no_commit'):
3225 if not opts.get(b'no_commit'):
3226 node = repo.commit(
3226 node = repo.commit(
3227 text=message, user=user, date=date, extra=extra, editor=editor
3227 text=message, user=user, date=date, extra=extra, editor=editor
3228 )
3228 )
3229 if node is None:
3229 if node is None:
3230 ui.warn(
3230 ui.warn(
3231 _(b'note: graft of %d:%s created no changes to commit\n')
3231 _(b'note: graft of %d:%s created no changes to commit\n')
3232 % (ctx.rev(), ctx)
3232 % (ctx.rev(), ctx)
3233 )
3233 )
3234 # checking that newnodes exist because old state files won't have it
3234 # checking that newnodes exist because old state files won't have it
3235 elif statedata.get(b'newnodes') is not None:
3235 elif statedata.get(b'newnodes') is not None:
3236 statedata[b'newnodes'].append(node)
3236 statedata[b'newnodes'].append(node)
3237
3237
3238 # remove state when we complete successfully
3238 # remove state when we complete successfully
3239 if not opts.get(b'dry_run'):
3239 if not opts.get(b'dry_run'):
3240 graftstate.delete()
3240 graftstate.delete()
3241
3241
3242 return 0
3242 return 0
3243
3243
3244
3244
3245 def _stopgraft(ui, repo, graftstate):
3245 def _stopgraft(ui, repo, graftstate):
3246 """stop the interrupted graft"""
3246 """stop the interrupted graft"""
3247 if not graftstate.exists():
3247 if not graftstate.exists():
3248 raise error.Abort(_(b"no interrupted graft found"))
3248 raise error.Abort(_(b"no interrupted graft found"))
3249 pctx = repo[b'.']
3249 pctx = repo[b'.']
3250 mergemod.clean_update(pctx)
3250 mergemod.clean_update(pctx)
3251 graftstate.delete()
3251 graftstate.delete()
3252 ui.status(_(b"stopped the interrupted graft\n"))
3252 ui.status(_(b"stopped the interrupted graft\n"))
3253 ui.status(_(b"working directory is now at %s\n") % pctx.hex()[:12])
3253 ui.status(_(b"working directory is now at %s\n") % pctx.hex()[:12])
3254 return 0
3254 return 0
3255
3255
3256
3256
3257 statemod.addunfinished(
3257 statemod.addunfinished(
3258 b'graft',
3258 b'graft',
3259 fname=b'graftstate',
3259 fname=b'graftstate',
3260 clearable=True,
3260 clearable=True,
3261 stopflag=True,
3261 stopflag=True,
3262 continueflag=True,
3262 continueflag=True,
3263 abortfunc=cmdutil.hgabortgraft,
3263 abortfunc=cmdutil.hgabortgraft,
3264 cmdhint=_(b"use 'hg graft --continue' or 'hg graft --stop' to stop"),
3264 cmdhint=_(b"use 'hg graft --continue' or 'hg graft --stop' to stop"),
3265 )
3265 )
3266
3266
3267
3267
3268 @command(
3268 @command(
3269 b'grep',
3269 b'grep',
3270 [
3270 [
3271 (b'0', b'print0', None, _(b'end fields with NUL')),
3271 (b'0', b'print0', None, _(b'end fields with NUL')),
3272 (b'', b'all', None, _(b'an alias to --diff (DEPRECATED)')),
3272 (b'', b'all', None, _(b'an alias to --diff (DEPRECATED)')),
3273 (
3273 (
3274 b'',
3274 b'',
3275 b'diff',
3275 b'diff',
3276 None,
3276 None,
3277 _(
3277 _(
3278 b'search revision differences for when the pattern was added '
3278 b'search revision differences for when the pattern was added '
3279 b'or removed'
3279 b'or removed'
3280 ),
3280 ),
3281 ),
3281 ),
3282 (b'a', b'text', None, _(b'treat all files as text')),
3282 (b'a', b'text', None, _(b'treat all files as text')),
3283 (
3283 (
3284 b'f',
3284 b'f',
3285 b'follow',
3285 b'follow',
3286 None,
3286 None,
3287 _(
3287 _(
3288 b'follow changeset history,'
3288 b'follow changeset history,'
3289 b' or file history across copies and renames'
3289 b' or file history across copies and renames'
3290 ),
3290 ),
3291 ),
3291 ),
3292 (b'i', b'ignore-case', None, _(b'ignore case when matching')),
3292 (b'i', b'ignore-case', None, _(b'ignore case when matching')),
3293 (
3293 (
3294 b'l',
3294 b'l',
3295 b'files-with-matches',
3295 b'files-with-matches',
3296 None,
3296 None,
3297 _(b'print only filenames and revisions that match'),
3297 _(b'print only filenames and revisions that match'),
3298 ),
3298 ),
3299 (b'n', b'line-number', None, _(b'print matching line numbers')),
3299 (b'n', b'line-number', None, _(b'print matching line numbers')),
3300 (
3300 (
3301 b'r',
3301 b'r',
3302 b'rev',
3302 b'rev',
3303 [],
3303 [],
3304 _(b'search files changed within revision range'),
3304 _(b'search files changed within revision range'),
3305 _(b'REV'),
3305 _(b'REV'),
3306 ),
3306 ),
3307 (
3307 (
3308 b'',
3308 b'',
3309 b'all-files',
3309 b'all-files',
3310 None,
3310 None,
3311 _(
3311 _(
3312 b'include all files in the changeset while grepping (DEPRECATED)'
3312 b'include all files in the changeset while grepping (DEPRECATED)'
3313 ),
3313 ),
3314 ),
3314 ),
3315 (b'u', b'user', None, _(b'list the author (long with -v)')),
3315 (b'u', b'user', None, _(b'list the author (long with -v)')),
3316 (b'd', b'date', None, _(b'list the date (short with -q)')),
3316 (b'd', b'date', None, _(b'list the date (short with -q)')),
3317 ]
3317 ]
3318 + formatteropts
3318 + formatteropts
3319 + walkopts,
3319 + walkopts,
3320 _(b'[--diff] [OPTION]... PATTERN [FILE]...'),
3320 _(b'[--diff] [OPTION]... PATTERN [FILE]...'),
3321 helpcategory=command.CATEGORY_FILE_CONTENTS,
3321 helpcategory=command.CATEGORY_FILE_CONTENTS,
3322 inferrepo=True,
3322 inferrepo=True,
3323 intents={INTENT_READONLY},
3323 intents={INTENT_READONLY},
3324 )
3324 )
3325 def grep(ui, repo, pattern, *pats, **opts):
3325 def grep(ui, repo, pattern, *pats, **opts):
3326 """search for a pattern in specified files
3326 """search for a pattern in specified files
3327
3327
3328 Search the working directory or revision history for a regular
3328 Search the working directory or revision history for a regular
3329 expression in the specified files for the entire repository.
3329 expression in the specified files for the entire repository.
3330
3330
3331 By default, grep searches the repository files in the working
3331 By default, grep searches the repository files in the working
3332 directory and prints the files where it finds a match. To specify
3332 directory and prints the files where it finds a match. To specify
3333 historical revisions instead of the working directory, use the
3333 historical revisions instead of the working directory, use the
3334 --rev flag.
3334 --rev flag.
3335
3335
3336 To search instead historical revision differences that contains a
3336 To search instead historical revision differences that contains a
3337 change in match status ("-" for a match that becomes a non-match,
3337 change in match status ("-" for a match that becomes a non-match,
3338 or "+" for a non-match that becomes a match), use the --diff flag.
3338 or "+" for a non-match that becomes a match), use the --diff flag.
3339
3339
3340 PATTERN can be any Python (roughly Perl-compatible) regular
3340 PATTERN can be any Python (roughly Perl-compatible) regular
3341 expression.
3341 expression.
3342
3342
3343 If no FILEs are specified and the --rev flag isn't supplied, all
3343 If no FILEs are specified and the --rev flag isn't supplied, all
3344 files in the working directory are searched. When using the --rev
3344 files in the working directory are searched. When using the --rev
3345 flag and specifying FILEs, use the --follow argument to also
3345 flag and specifying FILEs, use the --follow argument to also
3346 follow the specified FILEs across renames and copies.
3346 follow the specified FILEs across renames and copies.
3347
3347
3348 .. container:: verbose
3348 .. container:: verbose
3349
3349
3350 Template:
3350 Template:
3351
3351
3352 The following keywords are supported in addition to the common template
3352 The following keywords are supported in addition to the common template
3353 keywords and functions. See also :hg:`help templates`.
3353 keywords and functions. See also :hg:`help templates`.
3354
3354
3355 :change: String. Character denoting insertion ``+`` or removal ``-``.
3355 :change: String. Character denoting insertion ``+`` or removal ``-``.
3356 Available if ``--diff`` is specified.
3356 Available if ``--diff`` is specified.
3357 :lineno: Integer. Line number of the match.
3357 :lineno: Integer. Line number of the match.
3358 :path: String. Repository-absolute path of the file.
3358 :path: String. Repository-absolute path of the file.
3359 :texts: List of text chunks.
3359 :texts: List of text chunks.
3360
3360
3361 And each entry of ``{texts}`` provides the following sub-keywords.
3361 And each entry of ``{texts}`` provides the following sub-keywords.
3362
3362
3363 :matched: Boolean. True if the chunk matches the specified pattern.
3363 :matched: Boolean. True if the chunk matches the specified pattern.
3364 :text: String. Chunk content.
3364 :text: String. Chunk content.
3365
3365
3366 See :hg:`help templates.operators` for the list expansion syntax.
3366 See :hg:`help templates.operators` for the list expansion syntax.
3367
3367
3368 Returns 0 if a match is found, 1 otherwise.
3368 Returns 0 if a match is found, 1 otherwise.
3369
3369
3370 """
3370 """
3371 opts = pycompat.byteskwargs(opts)
3371 opts = pycompat.byteskwargs(opts)
3372 diff = opts.get(b'all') or opts.get(b'diff')
3372 diff = opts.get(b'all') or opts.get(b'diff')
3373 if diff and opts.get(b'all_files'):
3373 if diff and opts.get(b'all_files'):
3374 raise error.Abort(_(b'--diff and --all-files are mutually exclusive'))
3374 raise error.Abort(_(b'--diff and --all-files are mutually exclusive'))
3375 if opts.get(b'all_files') is None and not diff:
3375 if opts.get(b'all_files') is None and not diff:
3376 opts[b'all_files'] = True
3376 opts[b'all_files'] = True
3377 plaingrep = (
3377 plaingrep = (
3378 opts.get(b'all_files')
3378 opts.get(b'all_files')
3379 and not opts.get(b'rev')
3379 and not opts.get(b'rev')
3380 and not opts.get(b'follow')
3380 and not opts.get(b'follow')
3381 )
3381 )
3382 all_files = opts.get(b'all_files')
3382 all_files = opts.get(b'all_files')
3383 if plaingrep:
3383 if plaingrep:
3384 opts[b'rev'] = [b'wdir()']
3384 opts[b'rev'] = [b'wdir()']
3385
3385
3386 reflags = re.M
3386 reflags = re.M
3387 if opts.get(b'ignore_case'):
3387 if opts.get(b'ignore_case'):
3388 reflags |= re.I
3388 reflags |= re.I
3389 try:
3389 try:
3390 regexp = util.re.compile(pattern, reflags)
3390 regexp = util.re.compile(pattern, reflags)
3391 except re.error as inst:
3391 except re.error as inst:
3392 ui.warn(
3392 ui.warn(
3393 _(b"grep: invalid match pattern: %s\n") % pycompat.bytestr(inst)
3393 _(b"grep: invalid match pattern: %s\n") % pycompat.bytestr(inst)
3394 )
3394 )
3395 return 1
3395 return 1
3396 sep, eol = b':', b'\n'
3396 sep, eol = b':', b'\n'
3397 if opts.get(b'print0'):
3397 if opts.get(b'print0'):
3398 sep = eol = b'\0'
3398 sep = eol = b'\0'
3399
3399
3400 getfile = util.lrucachefunc(repo.file)
3400 getfile = util.lrucachefunc(repo.file)
3401
3401
3402 def matchlines(body):
3402 def matchlines(body):
3403 begin = 0
3403 begin = 0
3404 linenum = 0
3404 linenum = 0
3405 while begin < len(body):
3405 while begin < len(body):
3406 match = regexp.search(body, begin)
3406 match = regexp.search(body, begin)
3407 if not match:
3407 if not match:
3408 break
3408 break
3409 mstart, mend = match.span()
3409 mstart, mend = match.span()
3410 linenum += body.count(b'\n', begin, mstart) + 1
3410 linenum += body.count(b'\n', begin, mstart) + 1
3411 lstart = body.rfind(b'\n', begin, mstart) + 1 or begin
3411 lstart = body.rfind(b'\n', begin, mstart) + 1 or begin
3412 begin = body.find(b'\n', mend) + 1 or len(body) + 1
3412 begin = body.find(b'\n', mend) + 1 or len(body) + 1
3413 lend = begin - 1
3413 lend = begin - 1
3414 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
3414 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
3415
3415
3416 class linestate(object):
3416 class linestate(object):
3417 def __init__(self, line, linenum, colstart, colend):
3417 def __init__(self, line, linenum, colstart, colend):
3418 self.line = line
3418 self.line = line
3419 self.linenum = linenum
3419 self.linenum = linenum
3420 self.colstart = colstart
3420 self.colstart = colstart
3421 self.colend = colend
3421 self.colend = colend
3422
3422
3423 def __hash__(self):
3423 def __hash__(self):
3424 return hash(self.line)
3424 return hash(self.line)
3425
3425
3426 def __eq__(self, other):
3426 def __eq__(self, other):
3427 return self.line == other.line
3427 return self.line == other.line
3428
3428
3429 def findpos(self):
3429 def findpos(self):
3430 """Iterate all (start, end) indices of matches"""
3430 """Iterate all (start, end) indices of matches"""
3431 yield self.colstart, self.colend
3431 yield self.colstart, self.colend
3432 p = self.colend
3432 p = self.colend
3433 while p < len(self.line):
3433 while p < len(self.line):
3434 m = regexp.search(self.line, p)
3434 m = regexp.search(self.line, p)
3435 if not m:
3435 if not m:
3436 break
3436 break
3437 if m.end() == p:
3437 if m.end() == p:
3438 p += 1
3438 p += 1
3439 else:
3439 else:
3440 yield m.span()
3440 yield m.span()
3441 p = m.end()
3441 p = m.end()
3442
3442
3443 matches = {}
3443 matches = {}
3444 copies = {}
3444 copies = {}
3445
3445
3446 def grepbody(fn, rev, body):
3446 def grepbody(fn, rev, body):
3447 matches[rev].setdefault(fn, [])
3447 matches[rev].setdefault(fn, [])
3448 m = matches[rev][fn]
3448 m = matches[rev][fn]
3449 if body is None:
3449 if body is None:
3450 return
3450 return
3451
3451
3452 for lnum, cstart, cend, line in matchlines(body):
3452 for lnum, cstart, cend, line in matchlines(body):
3453 s = linestate(line, lnum, cstart, cend)
3453 s = linestate(line, lnum, cstart, cend)
3454 m.append(s)
3454 m.append(s)
3455
3455
3456 def difflinestates(a, b):
3456 def difflinestates(a, b):
3457 sm = difflib.SequenceMatcher(None, a, b)
3457 sm = difflib.SequenceMatcher(None, a, b)
3458 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
3458 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
3459 if tag == 'insert':
3459 if tag == 'insert':
3460 for i in pycompat.xrange(blo, bhi):
3460 for i in pycompat.xrange(blo, bhi):
3461 yield (b'+', b[i])
3461 yield (b'+', b[i])
3462 elif tag == 'delete':
3462 elif tag == 'delete':
3463 for i in pycompat.xrange(alo, ahi):
3463 for i in pycompat.xrange(alo, ahi):
3464 yield (b'-', a[i])
3464 yield (b'-', a[i])
3465 elif tag == 'replace':
3465 elif tag == 'replace':
3466 for i in pycompat.xrange(alo, ahi):
3466 for i in pycompat.xrange(alo, ahi):
3467 yield (b'-', a[i])
3467 yield (b'-', a[i])
3468 for i in pycompat.xrange(blo, bhi):
3468 for i in pycompat.xrange(blo, bhi):
3469 yield (b'+', b[i])
3469 yield (b'+', b[i])
3470
3470
3471 uipathfn = scmutil.getuipathfn(repo)
3471 uipathfn = scmutil.getuipathfn(repo)
3472
3472
3473 def display(fm, fn, ctx, pstates, states):
3473 def display(fm, fn, ctx, pstates, states):
3474 rev = scmutil.intrev(ctx)
3474 rev = scmutil.intrev(ctx)
3475 if fm.isplain():
3475 if fm.isplain():
3476 formatuser = ui.shortuser
3476 formatuser = ui.shortuser
3477 else:
3477 else:
3478 formatuser = pycompat.bytestr
3478 formatuser = pycompat.bytestr
3479 if ui.quiet:
3479 if ui.quiet:
3480 datefmt = b'%Y-%m-%d'
3480 datefmt = b'%Y-%m-%d'
3481 else:
3481 else:
3482 datefmt = b'%a %b %d %H:%M:%S %Y %1%2'
3482 datefmt = b'%a %b %d %H:%M:%S %Y %1%2'
3483 found = False
3483 found = False
3484
3484
3485 @util.cachefunc
3485 @util.cachefunc
3486 def binary():
3486 def binary():
3487 flog = getfile(fn)
3487 flog = getfile(fn)
3488 try:
3488 try:
3489 return stringutil.binary(flog.read(ctx.filenode(fn)))
3489 return stringutil.binary(flog.read(ctx.filenode(fn)))
3490 except error.WdirUnsupported:
3490 except error.WdirUnsupported:
3491 return ctx[fn].isbinary()
3491 return ctx[fn].isbinary()
3492
3492
3493 fieldnamemap = {b'linenumber': b'lineno'}
3493 fieldnamemap = {b'linenumber': b'lineno'}
3494 if diff:
3494 if diff:
3495 iter = difflinestates(pstates, states)
3495 iter = difflinestates(pstates, states)
3496 else:
3496 else:
3497 iter = [(b'', l) for l in states]
3497 iter = [(b'', l) for l in states]
3498 for change, l in iter:
3498 for change, l in iter:
3499 fm.startitem()
3499 fm.startitem()
3500 fm.context(ctx=ctx)
3500 fm.context(ctx=ctx)
3501 fm.data(node=fm.hexfunc(scmutil.binnode(ctx)), path=fn)
3501 fm.data(node=fm.hexfunc(scmutil.binnode(ctx)), path=fn)
3502 fm.plain(uipathfn(fn), label=b'grep.filename')
3502 fm.plain(uipathfn(fn), label=b'grep.filename')
3503
3503
3504 cols = [
3504 cols = [
3505 (b'rev', b'%d', rev, not plaingrep, b''),
3505 (b'rev', b'%d', rev, not plaingrep, b''),
3506 (
3506 (
3507 b'linenumber',
3507 b'linenumber',
3508 b'%d',
3508 b'%d',
3509 l.linenum,
3509 l.linenum,
3510 opts.get(b'line_number'),
3510 opts.get(b'line_number'),
3511 b'',
3511 b'',
3512 ),
3512 ),
3513 ]
3513 ]
3514 if diff:
3514 if diff:
3515 cols.append(
3515 cols.append(
3516 (
3516 (
3517 b'change',
3517 b'change',
3518 b'%s',
3518 b'%s',
3519 change,
3519 change,
3520 True,
3520 True,
3521 b'grep.inserted '
3521 b'grep.inserted '
3522 if change == b'+'
3522 if change == b'+'
3523 else b'grep.deleted ',
3523 else b'grep.deleted ',
3524 )
3524 )
3525 )
3525 )
3526 cols.extend(
3526 cols.extend(
3527 [
3527 [
3528 (
3528 (
3529 b'user',
3529 b'user',
3530 b'%s',
3530 b'%s',
3531 formatuser(ctx.user()),
3531 formatuser(ctx.user()),
3532 opts.get(b'user'),
3532 opts.get(b'user'),
3533 b'',
3533 b'',
3534 ),
3534 ),
3535 (
3535 (
3536 b'date',
3536 b'date',
3537 b'%s',
3537 b'%s',
3538 fm.formatdate(ctx.date(), datefmt),
3538 fm.formatdate(ctx.date(), datefmt),
3539 opts.get(b'date'),
3539 opts.get(b'date'),
3540 b'',
3540 b'',
3541 ),
3541 ),
3542 ]
3542 ]
3543 )
3543 )
3544 for name, fmt, data, cond, extra_label in cols:
3544 for name, fmt, data, cond, extra_label in cols:
3545 if cond:
3545 if cond:
3546 fm.plain(sep, label=b'grep.sep')
3546 fm.plain(sep, label=b'grep.sep')
3547 field = fieldnamemap.get(name, name)
3547 field = fieldnamemap.get(name, name)
3548 label = extra_label + (b'grep.%s' % name)
3548 label = extra_label + (b'grep.%s' % name)
3549 fm.condwrite(cond, field, fmt, data, label=label)
3549 fm.condwrite(cond, field, fmt, data, label=label)
3550 if not opts.get(b'files_with_matches'):
3550 if not opts.get(b'files_with_matches'):
3551 fm.plain(sep, label=b'grep.sep')
3551 fm.plain(sep, label=b'grep.sep')
3552 if not opts.get(b'text') and binary():
3552 if not opts.get(b'text') and binary():
3553 fm.plain(_(b" Binary file matches"))
3553 fm.plain(_(b" Binary file matches"))
3554 else:
3554 else:
3555 displaymatches(fm.nested(b'texts', tmpl=b'{text}'), l)
3555 displaymatches(fm.nested(b'texts', tmpl=b'{text}'), l)
3556 fm.plain(eol)
3556 fm.plain(eol)
3557 found = True
3557 found = True
3558 if opts.get(b'files_with_matches'):
3558 if opts.get(b'files_with_matches'):
3559 break
3559 break
3560 return found
3560 return found
3561
3561
3562 def displaymatches(fm, l):
3562 def displaymatches(fm, l):
3563 p = 0
3563 p = 0
3564 for s, e in l.findpos():
3564 for s, e in l.findpos():
3565 if p < s:
3565 if p < s:
3566 fm.startitem()
3566 fm.startitem()
3567 fm.write(b'text', b'%s', l.line[p:s])
3567 fm.write(b'text', b'%s', l.line[p:s])
3568 fm.data(matched=False)
3568 fm.data(matched=False)
3569 fm.startitem()
3569 fm.startitem()
3570 fm.write(b'text', b'%s', l.line[s:e], label=b'grep.match')
3570 fm.write(b'text', b'%s', l.line[s:e], label=b'grep.match')
3571 fm.data(matched=True)
3571 fm.data(matched=True)
3572 p = e
3572 p = e
3573 if p < len(l.line):
3573 if p < len(l.line):
3574 fm.startitem()
3574 fm.startitem()
3575 fm.write(b'text', b'%s', l.line[p:])
3575 fm.write(b'text', b'%s', l.line[p:])
3576 fm.data(matched=False)
3576 fm.data(matched=False)
3577 fm.end()
3577 fm.end()
3578
3578
3579 skip = set()
3579 skip = set()
3580 revfiles = {}
3580 revfiles = {}
3581 match = scmutil.match(repo[None], pats, opts)
3581 match = scmutil.match(repo[None], pats, opts)
3582 found = False
3582 found = False
3583 follow = opts.get(b'follow')
3583 follow = opts.get(b'follow')
3584
3584
3585 getrenamed = scmutil.getrenamedfn(repo)
3585 getrenamed = scmutil.getrenamedfn(repo)
3586
3586
3587 def readfile(ctx, fn):
3587 def readfile(ctx, fn):
3588 rev = ctx.rev()
3588 rev = ctx.rev()
3589 if rev is None:
3589 if rev is None:
3590 fctx = ctx[fn]
3590 fctx = ctx[fn]
3591 try:
3591 try:
3592 return fctx.data()
3592 return fctx.data()
3593 except IOError as e:
3593 except IOError as e:
3594 if e.errno != errno.ENOENT:
3594 if e.errno != errno.ENOENT:
3595 raise
3595 raise
3596 else:
3596 else:
3597 flog = getfile(fn)
3597 flog = getfile(fn)
3598 fnode = ctx.filenode(fn)
3598 fnode = ctx.filenode(fn)
3599 try:
3599 try:
3600 return flog.read(fnode)
3600 return flog.read(fnode)
3601 except error.CensoredNodeError:
3601 except error.CensoredNodeError:
3602 ui.warn(
3602 ui.warn(
3603 _(
3603 _(
3604 b'cannot search in censored file: %(filename)s:%(revnum)s\n'
3604 b'cannot search in censored file: %(filename)s:%(revnum)s\n'
3605 )
3605 )
3606 % {b'filename': fn, b'revnum': pycompat.bytestr(rev),}
3606 % {b'filename': fn, b'revnum': pycompat.bytestr(rev),}
3607 )
3607 )
3608
3608
3609 def prep(ctx, fns):
3609 def prep(ctx, fmatch):
3610 rev = ctx.rev()
3610 rev = ctx.rev()
3611 pctx = ctx.p1()
3611 pctx = ctx.p1()
3612 matches.setdefault(rev, {})
3612 matches.setdefault(rev, {})
3613 if diff:
3613 if diff:
3614 parent = pctx.rev()
3614 parent = pctx.rev()
3615 matches.setdefault(parent, {})
3615 matches.setdefault(parent, {})
3616 files = revfiles.setdefault(rev, [])
3616 files = revfiles.setdefault(rev, [])
3617 if rev is None:
3617 if rev is None:
3618 # in `hg grep pattern`, 2/3 of the time is spent is spent in
3618 # in `hg grep pattern`, 2/3 of the time is spent is spent in
3619 # pathauditor checks without this in mozilla-central
3619 # pathauditor checks without this in mozilla-central
3620 contextmanager = repo.wvfs.audit.cached
3620 contextmanager = repo.wvfs.audit.cached
3621 else:
3621 else:
3622 contextmanager = util.nullcontextmanager
3622 contextmanager = util.nullcontextmanager
3623 with contextmanager():
3623 with contextmanager():
3624 for fn in fns:
3624 assert fmatch.isexact()
3625 for fn in fmatch.files():
3625 # fn might not exist in the revision (could be a file removed by
3626 # fn might not exist in the revision (could be a file removed by
3626 # the revision). We could check `fn not in ctx` even when rev is
3627 # the revision). We could check `fn not in ctx` even when rev is
3627 # None, but it's less racy to protect againt that in readfile.
3628 # None, but it's less racy to protect againt that in readfile.
3628 if rev is not None and fn not in ctx:
3629 if rev is not None and fn not in ctx:
3629 continue
3630 continue
3630
3631
3631 copy = None
3632 copy = None
3632 if follow:
3633 if follow:
3633 copy = getrenamed(fn, rev)
3634 copy = getrenamed(fn, rev)
3634 if copy:
3635 if copy:
3635 copies.setdefault(rev, {})[fn] = copy
3636 copies.setdefault(rev, {})[fn] = copy
3636 if fn in skip:
3637 if fn in skip:
3637 skip.add(copy)
3638 skip.add(copy)
3638 if fn in skip:
3639 if fn in skip:
3639 continue
3640 continue
3640 files.append(fn)
3641 files.append(fn)
3641
3642
3642 if fn not in matches[rev]:
3643 if fn not in matches[rev]:
3643 grepbody(fn, rev, readfile(ctx, fn))
3644 grepbody(fn, rev, readfile(ctx, fn))
3644
3645
3645 if diff:
3646 if diff:
3646 pfn = copy or fn
3647 pfn = copy or fn
3647 if pfn not in matches[parent] and pfn in pctx:
3648 if pfn not in matches[parent] and pfn in pctx:
3648 grepbody(pfn, parent, readfile(pctx, pfn))
3649 grepbody(pfn, parent, readfile(pctx, pfn))
3649
3650
3650 ui.pager(b'grep')
3651 ui.pager(b'grep')
3651 fm = ui.formatter(b'grep', opts)
3652 fm = ui.formatter(b'grep', opts)
3652 for ctx in cmdutil.walkchangerevs(repo, match, opts, prep):
3653 for ctx in cmdutil.walkchangerevs(repo, match, opts, prep):
3653 rev = ctx.rev()
3654 rev = ctx.rev()
3654 parent = ctx.p1().rev()
3655 parent = ctx.p1().rev()
3655 for fn in sorted(revfiles.get(rev, [])):
3656 for fn in sorted(revfiles.get(rev, [])):
3656 states = matches[rev][fn]
3657 states = matches[rev][fn]
3657 copy = copies.get(rev, {}).get(fn)
3658 copy = copies.get(rev, {}).get(fn)
3658 if fn in skip:
3659 if fn in skip:
3659 if copy:
3660 if copy:
3660 skip.add(copy)
3661 skip.add(copy)
3661 continue
3662 continue
3662 pstates = matches.get(parent, {}).get(copy or fn, [])
3663 pstates = matches.get(parent, {}).get(copy or fn, [])
3663 if pstates or states:
3664 if pstates or states:
3664 r = display(fm, fn, ctx, pstates, states)
3665 r = display(fm, fn, ctx, pstates, states)
3665 found = found or r
3666 found = found or r
3666 if r and not diff and not all_files:
3667 if r and not diff and not all_files:
3667 skip.add(fn)
3668 skip.add(fn)
3668 if copy:
3669 if copy:
3669 skip.add(copy)
3670 skip.add(copy)
3670 del revfiles[rev]
3671 del revfiles[rev]
3671 # We will keep the matches dict for the duration of the window
3672 # We will keep the matches dict for the duration of the window
3672 # clear the matches dict once the window is over
3673 # clear the matches dict once the window is over
3673 if not revfiles:
3674 if not revfiles:
3674 matches.clear()
3675 matches.clear()
3675 fm.end()
3676 fm.end()
3676
3677
3677 return not found
3678 return not found
3678
3679
3679
3680
3680 @command(
3681 @command(
3681 b'heads',
3682 b'heads',
3682 [
3683 [
3683 (
3684 (
3684 b'r',
3685 b'r',
3685 b'rev',
3686 b'rev',
3686 b'',
3687 b'',
3687 _(b'show only heads which are descendants of STARTREV'),
3688 _(b'show only heads which are descendants of STARTREV'),
3688 _(b'STARTREV'),
3689 _(b'STARTREV'),
3689 ),
3690 ),
3690 (b't', b'topo', False, _(b'show topological heads only')),
3691 (b't', b'topo', False, _(b'show topological heads only')),
3691 (
3692 (
3692 b'a',
3693 b'a',
3693 b'active',
3694 b'active',
3694 False,
3695 False,
3695 _(b'show active branchheads only (DEPRECATED)'),
3696 _(b'show active branchheads only (DEPRECATED)'),
3696 ),
3697 ),
3697 (b'c', b'closed', False, _(b'show normal and closed branch heads')),
3698 (b'c', b'closed', False, _(b'show normal and closed branch heads')),
3698 ]
3699 ]
3699 + templateopts,
3700 + templateopts,
3700 _(b'[-ct] [-r STARTREV] [REV]...'),
3701 _(b'[-ct] [-r STARTREV] [REV]...'),
3701 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3702 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3702 intents={INTENT_READONLY},
3703 intents={INTENT_READONLY},
3703 )
3704 )
3704 def heads(ui, repo, *branchrevs, **opts):
3705 def heads(ui, repo, *branchrevs, **opts):
3705 """show branch heads
3706 """show branch heads
3706
3707
3707 With no arguments, show all open branch heads in the repository.
3708 With no arguments, show all open branch heads in the repository.
3708 Branch heads are changesets that have no descendants on the
3709 Branch heads are changesets that have no descendants on the
3709 same branch. They are where development generally takes place and
3710 same branch. They are where development generally takes place and
3710 are the usual targets for update and merge operations.
3711 are the usual targets for update and merge operations.
3711
3712
3712 If one or more REVs are given, only open branch heads on the
3713 If one or more REVs are given, only open branch heads on the
3713 branches associated with the specified changesets are shown. This
3714 branches associated with the specified changesets are shown. This
3714 means that you can use :hg:`heads .` to see the heads on the
3715 means that you can use :hg:`heads .` to see the heads on the
3715 currently checked-out branch.
3716 currently checked-out branch.
3716
3717
3717 If -c/--closed is specified, also show branch heads marked closed
3718 If -c/--closed is specified, also show branch heads marked closed
3718 (see :hg:`commit --close-branch`).
3719 (see :hg:`commit --close-branch`).
3719
3720
3720 If STARTREV is specified, only those heads that are descendants of
3721 If STARTREV is specified, only those heads that are descendants of
3721 STARTREV will be displayed.
3722 STARTREV will be displayed.
3722
3723
3723 If -t/--topo is specified, named branch mechanics will be ignored and only
3724 If -t/--topo is specified, named branch mechanics will be ignored and only
3724 topological heads (changesets with no children) will be shown.
3725 topological heads (changesets with no children) will be shown.
3725
3726
3726 Returns 0 if matching heads are found, 1 if not.
3727 Returns 0 if matching heads are found, 1 if not.
3727 """
3728 """
3728
3729
3729 opts = pycompat.byteskwargs(opts)
3730 opts = pycompat.byteskwargs(opts)
3730 start = None
3731 start = None
3731 rev = opts.get(b'rev')
3732 rev = opts.get(b'rev')
3732 if rev:
3733 if rev:
3733 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
3734 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
3734 start = scmutil.revsingle(repo, rev, None).node()
3735 start = scmutil.revsingle(repo, rev, None).node()
3735
3736
3736 if opts.get(b'topo'):
3737 if opts.get(b'topo'):
3737 heads = [repo[h] for h in repo.heads(start)]
3738 heads = [repo[h] for h in repo.heads(start)]
3738 else:
3739 else:
3739 heads = []
3740 heads = []
3740 for branch in repo.branchmap():
3741 for branch in repo.branchmap():
3741 heads += repo.branchheads(branch, start, opts.get(b'closed'))
3742 heads += repo.branchheads(branch, start, opts.get(b'closed'))
3742 heads = [repo[h] for h in heads]
3743 heads = [repo[h] for h in heads]
3743
3744
3744 if branchrevs:
3745 if branchrevs:
3745 branches = {
3746 branches = {
3746 repo[r].branch() for r in scmutil.revrange(repo, branchrevs)
3747 repo[r].branch() for r in scmutil.revrange(repo, branchrevs)
3747 }
3748 }
3748 heads = [h for h in heads if h.branch() in branches]
3749 heads = [h for h in heads if h.branch() in branches]
3749
3750
3750 if opts.get(b'active') and branchrevs:
3751 if opts.get(b'active') and branchrevs:
3751 dagheads = repo.heads(start)
3752 dagheads = repo.heads(start)
3752 heads = [h for h in heads if h.node() in dagheads]
3753 heads = [h for h in heads if h.node() in dagheads]
3753
3754
3754 if branchrevs:
3755 if branchrevs:
3755 haveheads = {h.branch() for h in heads}
3756 haveheads = {h.branch() for h in heads}
3756 if branches - haveheads:
3757 if branches - haveheads:
3757 headless = b', '.join(b for b in branches - haveheads)
3758 headless = b', '.join(b for b in branches - haveheads)
3758 msg = _(b'no open branch heads found on branches %s')
3759 msg = _(b'no open branch heads found on branches %s')
3759 if opts.get(b'rev'):
3760 if opts.get(b'rev'):
3760 msg += _(b' (started at %s)') % opts[b'rev']
3761 msg += _(b' (started at %s)') % opts[b'rev']
3761 ui.warn((msg + b'\n') % headless)
3762 ui.warn((msg + b'\n') % headless)
3762
3763
3763 if not heads:
3764 if not heads:
3764 return 1
3765 return 1
3765
3766
3766 ui.pager(b'heads')
3767 ui.pager(b'heads')
3767 heads = sorted(heads, key=lambda x: -(x.rev()))
3768 heads = sorted(heads, key=lambda x: -(x.rev()))
3768 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
3769 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
3769 for ctx in heads:
3770 for ctx in heads:
3770 displayer.show(ctx)
3771 displayer.show(ctx)
3771 displayer.close()
3772 displayer.close()
3772
3773
3773
3774
3774 @command(
3775 @command(
3775 b'help',
3776 b'help',
3776 [
3777 [
3777 (b'e', b'extension', None, _(b'show only help for extensions')),
3778 (b'e', b'extension', None, _(b'show only help for extensions')),
3778 (b'c', b'command', None, _(b'show only help for commands')),
3779 (b'c', b'command', None, _(b'show only help for commands')),
3779 (b'k', b'keyword', None, _(b'show topics matching keyword')),
3780 (b'k', b'keyword', None, _(b'show topics matching keyword')),
3780 (
3781 (
3781 b's',
3782 b's',
3782 b'system',
3783 b'system',
3783 [],
3784 [],
3784 _(b'show help for specific platform(s)'),
3785 _(b'show help for specific platform(s)'),
3785 _(b'PLATFORM'),
3786 _(b'PLATFORM'),
3786 ),
3787 ),
3787 ],
3788 ],
3788 _(b'[-eck] [-s PLATFORM] [TOPIC]'),
3789 _(b'[-eck] [-s PLATFORM] [TOPIC]'),
3789 helpcategory=command.CATEGORY_HELP,
3790 helpcategory=command.CATEGORY_HELP,
3790 norepo=True,
3791 norepo=True,
3791 intents={INTENT_READONLY},
3792 intents={INTENT_READONLY},
3792 )
3793 )
3793 def help_(ui, name=None, **opts):
3794 def help_(ui, name=None, **opts):
3794 """show help for a given topic or a help overview
3795 """show help for a given topic or a help overview
3795
3796
3796 With no arguments, print a list of commands with short help messages.
3797 With no arguments, print a list of commands with short help messages.
3797
3798
3798 Given a topic, extension, or command name, print help for that
3799 Given a topic, extension, or command name, print help for that
3799 topic.
3800 topic.
3800
3801
3801 Returns 0 if successful.
3802 Returns 0 if successful.
3802 """
3803 """
3803
3804
3804 keep = opts.get('system') or []
3805 keep = opts.get('system') or []
3805 if len(keep) == 0:
3806 if len(keep) == 0:
3806 if pycompat.sysplatform.startswith(b'win'):
3807 if pycompat.sysplatform.startswith(b'win'):
3807 keep.append(b'windows')
3808 keep.append(b'windows')
3808 elif pycompat.sysplatform == b'OpenVMS':
3809 elif pycompat.sysplatform == b'OpenVMS':
3809 keep.append(b'vms')
3810 keep.append(b'vms')
3810 elif pycompat.sysplatform == b'plan9':
3811 elif pycompat.sysplatform == b'plan9':
3811 keep.append(b'plan9')
3812 keep.append(b'plan9')
3812 else:
3813 else:
3813 keep.append(b'unix')
3814 keep.append(b'unix')
3814 keep.append(pycompat.sysplatform.lower())
3815 keep.append(pycompat.sysplatform.lower())
3815 if ui.verbose:
3816 if ui.verbose:
3816 keep.append(b'verbose')
3817 keep.append(b'verbose')
3817
3818
3818 commands = sys.modules[__name__]
3819 commands = sys.modules[__name__]
3819 formatted = help.formattedhelp(ui, commands, name, keep=keep, **opts)
3820 formatted = help.formattedhelp(ui, commands, name, keep=keep, **opts)
3820 ui.pager(b'help')
3821 ui.pager(b'help')
3821 ui.write(formatted)
3822 ui.write(formatted)
3822
3823
3823
3824
3824 @command(
3825 @command(
3825 b'identify|id',
3826 b'identify|id',
3826 [
3827 [
3827 (b'r', b'rev', b'', _(b'identify the specified revision'), _(b'REV')),
3828 (b'r', b'rev', b'', _(b'identify the specified revision'), _(b'REV')),
3828 (b'n', b'num', None, _(b'show local revision number')),
3829 (b'n', b'num', None, _(b'show local revision number')),
3829 (b'i', b'id', None, _(b'show global revision id')),
3830 (b'i', b'id', None, _(b'show global revision id')),
3830 (b'b', b'branch', None, _(b'show branch')),
3831 (b'b', b'branch', None, _(b'show branch')),
3831 (b't', b'tags', None, _(b'show tags')),
3832 (b't', b'tags', None, _(b'show tags')),
3832 (b'B', b'bookmarks', None, _(b'show bookmarks')),
3833 (b'B', b'bookmarks', None, _(b'show bookmarks')),
3833 ]
3834 ]
3834 + remoteopts
3835 + remoteopts
3835 + formatteropts,
3836 + formatteropts,
3836 _(b'[-nibtB] [-r REV] [SOURCE]'),
3837 _(b'[-nibtB] [-r REV] [SOURCE]'),
3837 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3838 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3838 optionalrepo=True,
3839 optionalrepo=True,
3839 intents={INTENT_READONLY},
3840 intents={INTENT_READONLY},
3840 )
3841 )
3841 def identify(
3842 def identify(
3842 ui,
3843 ui,
3843 repo,
3844 repo,
3844 source=None,
3845 source=None,
3845 rev=None,
3846 rev=None,
3846 num=None,
3847 num=None,
3847 id=None,
3848 id=None,
3848 branch=None,
3849 branch=None,
3849 tags=None,
3850 tags=None,
3850 bookmarks=None,
3851 bookmarks=None,
3851 **opts
3852 **opts
3852 ):
3853 ):
3853 """identify the working directory or specified revision
3854 """identify the working directory or specified revision
3854
3855
3855 Print a summary identifying the repository state at REV using one or
3856 Print a summary identifying the repository state at REV using one or
3856 two parent hash identifiers, followed by a "+" if the working
3857 two parent hash identifiers, followed by a "+" if the working
3857 directory has uncommitted changes, the branch name (if not default),
3858 directory has uncommitted changes, the branch name (if not default),
3858 a list of tags, and a list of bookmarks.
3859 a list of tags, and a list of bookmarks.
3859
3860
3860 When REV is not given, print a summary of the current state of the
3861 When REV is not given, print a summary of the current state of the
3861 repository including the working directory. Specify -r. to get information
3862 repository including the working directory. Specify -r. to get information
3862 of the working directory parent without scanning uncommitted changes.
3863 of the working directory parent without scanning uncommitted changes.
3863
3864
3864 Specifying a path to a repository root or Mercurial bundle will
3865 Specifying a path to a repository root or Mercurial bundle will
3865 cause lookup to operate on that repository/bundle.
3866 cause lookup to operate on that repository/bundle.
3866
3867
3867 .. container:: verbose
3868 .. container:: verbose
3868
3869
3869 Template:
3870 Template:
3870
3871
3871 The following keywords are supported in addition to the common template
3872 The following keywords are supported in addition to the common template
3872 keywords and functions. See also :hg:`help templates`.
3873 keywords and functions. See also :hg:`help templates`.
3873
3874
3874 :dirty: String. Character ``+`` denoting if the working directory has
3875 :dirty: String. Character ``+`` denoting if the working directory has
3875 uncommitted changes.
3876 uncommitted changes.
3876 :id: String. One or two nodes, optionally followed by ``+``.
3877 :id: String. One or two nodes, optionally followed by ``+``.
3877 :parents: List of strings. Parent nodes of the changeset.
3878 :parents: List of strings. Parent nodes of the changeset.
3878
3879
3879 Examples:
3880 Examples:
3880
3881
3881 - generate a build identifier for the working directory::
3882 - generate a build identifier for the working directory::
3882
3883
3883 hg id --id > build-id.dat
3884 hg id --id > build-id.dat
3884
3885
3885 - find the revision corresponding to a tag::
3886 - find the revision corresponding to a tag::
3886
3887
3887 hg id -n -r 1.3
3888 hg id -n -r 1.3
3888
3889
3889 - check the most recent revision of a remote repository::
3890 - check the most recent revision of a remote repository::
3890
3891
3891 hg id -r tip https://www.mercurial-scm.org/repo/hg/
3892 hg id -r tip https://www.mercurial-scm.org/repo/hg/
3892
3893
3893 See :hg:`log` for generating more information about specific revisions,
3894 See :hg:`log` for generating more information about specific revisions,
3894 including full hash identifiers.
3895 including full hash identifiers.
3895
3896
3896 Returns 0 if successful.
3897 Returns 0 if successful.
3897 """
3898 """
3898
3899
3899 opts = pycompat.byteskwargs(opts)
3900 opts = pycompat.byteskwargs(opts)
3900 if not repo and not source:
3901 if not repo and not source:
3901 raise error.Abort(
3902 raise error.Abort(
3902 _(b"there is no Mercurial repository here (.hg not found)")
3903 _(b"there is no Mercurial repository here (.hg not found)")
3903 )
3904 )
3904
3905
3905 default = not (num or id or branch or tags or bookmarks)
3906 default = not (num or id or branch or tags or bookmarks)
3906 output = []
3907 output = []
3907 revs = []
3908 revs = []
3908
3909
3909 if source:
3910 if source:
3910 source, branches = hg.parseurl(ui.expandpath(source))
3911 source, branches = hg.parseurl(ui.expandpath(source))
3911 peer = hg.peer(repo or ui, opts, source) # only pass ui when no repo
3912 peer = hg.peer(repo or ui, opts, source) # only pass ui when no repo
3912 repo = peer.local()
3913 repo = peer.local()
3913 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
3914 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
3914
3915
3915 fm = ui.formatter(b'identify', opts)
3916 fm = ui.formatter(b'identify', opts)
3916 fm.startitem()
3917 fm.startitem()
3917
3918
3918 if not repo:
3919 if not repo:
3919 if num or branch or tags:
3920 if num or branch or tags:
3920 raise error.Abort(
3921 raise error.Abort(
3921 _(b"can't query remote revision number, branch, or tags")
3922 _(b"can't query remote revision number, branch, or tags")
3922 )
3923 )
3923 if not rev and revs:
3924 if not rev and revs:
3924 rev = revs[0]
3925 rev = revs[0]
3925 if not rev:
3926 if not rev:
3926 rev = b"tip"
3927 rev = b"tip"
3927
3928
3928 remoterev = peer.lookup(rev)
3929 remoterev = peer.lookup(rev)
3929 hexrev = fm.hexfunc(remoterev)
3930 hexrev = fm.hexfunc(remoterev)
3930 if default or id:
3931 if default or id:
3931 output = [hexrev]
3932 output = [hexrev]
3932 fm.data(id=hexrev)
3933 fm.data(id=hexrev)
3933
3934
3934 @util.cachefunc
3935 @util.cachefunc
3935 def getbms():
3936 def getbms():
3936 bms = []
3937 bms = []
3937
3938
3938 if b'bookmarks' in peer.listkeys(b'namespaces'):
3939 if b'bookmarks' in peer.listkeys(b'namespaces'):
3939 hexremoterev = hex(remoterev)
3940 hexremoterev = hex(remoterev)
3940 bms = [
3941 bms = [
3941 bm
3942 bm
3942 for bm, bmr in pycompat.iteritems(
3943 for bm, bmr in pycompat.iteritems(
3943 peer.listkeys(b'bookmarks')
3944 peer.listkeys(b'bookmarks')
3944 )
3945 )
3945 if bmr == hexremoterev
3946 if bmr == hexremoterev
3946 ]
3947 ]
3947
3948
3948 return sorted(bms)
3949 return sorted(bms)
3949
3950
3950 if fm.isplain():
3951 if fm.isplain():
3951 if bookmarks:
3952 if bookmarks:
3952 output.extend(getbms())
3953 output.extend(getbms())
3953 elif default and not ui.quiet:
3954 elif default and not ui.quiet:
3954 # multiple bookmarks for a single parent separated by '/'
3955 # multiple bookmarks for a single parent separated by '/'
3955 bm = b'/'.join(getbms())
3956 bm = b'/'.join(getbms())
3956 if bm:
3957 if bm:
3957 output.append(bm)
3958 output.append(bm)
3958 else:
3959 else:
3959 fm.data(node=hex(remoterev))
3960 fm.data(node=hex(remoterev))
3960 if bookmarks or b'bookmarks' in fm.datahint():
3961 if bookmarks or b'bookmarks' in fm.datahint():
3961 fm.data(bookmarks=fm.formatlist(getbms(), name=b'bookmark'))
3962 fm.data(bookmarks=fm.formatlist(getbms(), name=b'bookmark'))
3962 else:
3963 else:
3963 if rev:
3964 if rev:
3964 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
3965 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
3965 ctx = scmutil.revsingle(repo, rev, None)
3966 ctx = scmutil.revsingle(repo, rev, None)
3966
3967
3967 if ctx.rev() is None:
3968 if ctx.rev() is None:
3968 ctx = repo[None]
3969 ctx = repo[None]
3969 parents = ctx.parents()
3970 parents = ctx.parents()
3970 taglist = []
3971 taglist = []
3971 for p in parents:
3972 for p in parents:
3972 taglist.extend(p.tags())
3973 taglist.extend(p.tags())
3973
3974
3974 dirty = b""
3975 dirty = b""
3975 if ctx.dirty(missing=True, merge=False, branch=False):
3976 if ctx.dirty(missing=True, merge=False, branch=False):
3976 dirty = b'+'
3977 dirty = b'+'
3977 fm.data(dirty=dirty)
3978 fm.data(dirty=dirty)
3978
3979
3979 hexoutput = [fm.hexfunc(p.node()) for p in parents]
3980 hexoutput = [fm.hexfunc(p.node()) for p in parents]
3980 if default or id:
3981 if default or id:
3981 output = [b"%s%s" % (b'+'.join(hexoutput), dirty)]
3982 output = [b"%s%s" % (b'+'.join(hexoutput), dirty)]
3982 fm.data(id=b"%s%s" % (b'+'.join(hexoutput), dirty))
3983 fm.data(id=b"%s%s" % (b'+'.join(hexoutput), dirty))
3983
3984
3984 if num:
3985 if num:
3985 numoutput = [b"%d" % p.rev() for p in parents]
3986 numoutput = [b"%d" % p.rev() for p in parents]
3986 output.append(b"%s%s" % (b'+'.join(numoutput), dirty))
3987 output.append(b"%s%s" % (b'+'.join(numoutput), dirty))
3987
3988
3988 fm.data(
3989 fm.data(
3989 parents=fm.formatlist(
3990 parents=fm.formatlist(
3990 [fm.hexfunc(p.node()) for p in parents], name=b'node'
3991 [fm.hexfunc(p.node()) for p in parents], name=b'node'
3991 )
3992 )
3992 )
3993 )
3993 else:
3994 else:
3994 hexoutput = fm.hexfunc(ctx.node())
3995 hexoutput = fm.hexfunc(ctx.node())
3995 if default or id:
3996 if default or id:
3996 output = [hexoutput]
3997 output = [hexoutput]
3997 fm.data(id=hexoutput)
3998 fm.data(id=hexoutput)
3998
3999
3999 if num:
4000 if num:
4000 output.append(pycompat.bytestr(ctx.rev()))
4001 output.append(pycompat.bytestr(ctx.rev()))
4001 taglist = ctx.tags()
4002 taglist = ctx.tags()
4002
4003
4003 if default and not ui.quiet:
4004 if default and not ui.quiet:
4004 b = ctx.branch()
4005 b = ctx.branch()
4005 if b != b'default':
4006 if b != b'default':
4006 output.append(b"(%s)" % b)
4007 output.append(b"(%s)" % b)
4007
4008
4008 # multiple tags for a single parent separated by '/'
4009 # multiple tags for a single parent separated by '/'
4009 t = b'/'.join(taglist)
4010 t = b'/'.join(taglist)
4010 if t:
4011 if t:
4011 output.append(t)
4012 output.append(t)
4012
4013
4013 # multiple bookmarks for a single parent separated by '/'
4014 # multiple bookmarks for a single parent separated by '/'
4014 bm = b'/'.join(ctx.bookmarks())
4015 bm = b'/'.join(ctx.bookmarks())
4015 if bm:
4016 if bm:
4016 output.append(bm)
4017 output.append(bm)
4017 else:
4018 else:
4018 if branch:
4019 if branch:
4019 output.append(ctx.branch())
4020 output.append(ctx.branch())
4020
4021
4021 if tags:
4022 if tags:
4022 output.extend(taglist)
4023 output.extend(taglist)
4023
4024
4024 if bookmarks:
4025 if bookmarks:
4025 output.extend(ctx.bookmarks())
4026 output.extend(ctx.bookmarks())
4026
4027
4027 fm.data(node=ctx.hex())
4028 fm.data(node=ctx.hex())
4028 fm.data(branch=ctx.branch())
4029 fm.data(branch=ctx.branch())
4029 fm.data(tags=fm.formatlist(taglist, name=b'tag', sep=b':'))
4030 fm.data(tags=fm.formatlist(taglist, name=b'tag', sep=b':'))
4030 fm.data(bookmarks=fm.formatlist(ctx.bookmarks(), name=b'bookmark'))
4031 fm.data(bookmarks=fm.formatlist(ctx.bookmarks(), name=b'bookmark'))
4031 fm.context(ctx=ctx)
4032 fm.context(ctx=ctx)
4032
4033
4033 fm.plain(b"%s\n" % b' '.join(output))
4034 fm.plain(b"%s\n" % b' '.join(output))
4034 fm.end()
4035 fm.end()
4035
4036
4036
4037
4037 @command(
4038 @command(
4038 b'import|patch',
4039 b'import|patch',
4039 [
4040 [
4040 (
4041 (
4041 b'p',
4042 b'p',
4042 b'strip',
4043 b'strip',
4043 1,
4044 1,
4044 _(
4045 _(
4045 b'directory strip option for patch. This has the same '
4046 b'directory strip option for patch. This has the same '
4046 b'meaning as the corresponding patch option'
4047 b'meaning as the corresponding patch option'
4047 ),
4048 ),
4048 _(b'NUM'),
4049 _(b'NUM'),
4049 ),
4050 ),
4050 (b'b', b'base', b'', _(b'base path (DEPRECATED)'), _(b'PATH')),
4051 (b'b', b'base', b'', _(b'base path (DEPRECATED)'), _(b'PATH')),
4051 (b'', b'secret', None, _(b'use the secret phase for committing')),
4052 (b'', b'secret', None, _(b'use the secret phase for committing')),
4052 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
4053 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
4053 (
4054 (
4054 b'f',
4055 b'f',
4055 b'force',
4056 b'force',
4056 None,
4057 None,
4057 _(b'skip check for outstanding uncommitted changes (DEPRECATED)'),
4058 _(b'skip check for outstanding uncommitted changes (DEPRECATED)'),
4058 ),
4059 ),
4059 (
4060 (
4060 b'',
4061 b'',
4061 b'no-commit',
4062 b'no-commit',
4062 None,
4063 None,
4063 _(b"don't commit, just update the working directory"),
4064 _(b"don't commit, just update the working directory"),
4064 ),
4065 ),
4065 (
4066 (
4066 b'',
4067 b'',
4067 b'bypass',
4068 b'bypass',
4068 None,
4069 None,
4069 _(b"apply patch without touching the working directory"),
4070 _(b"apply patch without touching the working directory"),
4070 ),
4071 ),
4071 (b'', b'partial', None, _(b'commit even if some hunks fail')),
4072 (b'', b'partial', None, _(b'commit even if some hunks fail')),
4072 (b'', b'exact', None, _(b'abort if patch would apply lossily')),
4073 (b'', b'exact', None, _(b'abort if patch would apply lossily')),
4073 (b'', b'prefix', b'', _(b'apply patch to subdirectory'), _(b'DIR')),
4074 (b'', b'prefix', b'', _(b'apply patch to subdirectory'), _(b'DIR')),
4074 (
4075 (
4075 b'',
4076 b'',
4076 b'import-branch',
4077 b'import-branch',
4077 None,
4078 None,
4078 _(b'use any branch information in patch (implied by --exact)'),
4079 _(b'use any branch information in patch (implied by --exact)'),
4079 ),
4080 ),
4080 ]
4081 ]
4081 + commitopts
4082 + commitopts
4082 + commitopts2
4083 + commitopts2
4083 + similarityopts,
4084 + similarityopts,
4084 _(b'[OPTION]... PATCH...'),
4085 _(b'[OPTION]... PATCH...'),
4085 helpcategory=command.CATEGORY_IMPORT_EXPORT,
4086 helpcategory=command.CATEGORY_IMPORT_EXPORT,
4086 )
4087 )
4087 def import_(ui, repo, patch1=None, *patches, **opts):
4088 def import_(ui, repo, patch1=None, *patches, **opts):
4088 """import an ordered set of patches
4089 """import an ordered set of patches
4089
4090
4090 Import a list of patches and commit them individually (unless
4091 Import a list of patches and commit them individually (unless
4091 --no-commit is specified).
4092 --no-commit is specified).
4092
4093
4093 To read a patch from standard input (stdin), use "-" as the patch
4094 To read a patch from standard input (stdin), use "-" as the patch
4094 name. If a URL is specified, the patch will be downloaded from
4095 name. If a URL is specified, the patch will be downloaded from
4095 there.
4096 there.
4096
4097
4097 Import first applies changes to the working directory (unless
4098 Import first applies changes to the working directory (unless
4098 --bypass is specified), import will abort if there are outstanding
4099 --bypass is specified), import will abort if there are outstanding
4099 changes.
4100 changes.
4100
4101
4101 Use --bypass to apply and commit patches directly to the
4102 Use --bypass to apply and commit patches directly to the
4102 repository, without affecting the working directory. Without
4103 repository, without affecting the working directory. Without
4103 --exact, patches will be applied on top of the working directory
4104 --exact, patches will be applied on top of the working directory
4104 parent revision.
4105 parent revision.
4105
4106
4106 You can import a patch straight from a mail message. Even patches
4107 You can import a patch straight from a mail message. Even patches
4107 as attachments work (to use the body part, it must have type
4108 as attachments work (to use the body part, it must have type
4108 text/plain or text/x-patch). From and Subject headers of email
4109 text/plain or text/x-patch). From and Subject headers of email
4109 message are used as default committer and commit message. All
4110 message are used as default committer and commit message. All
4110 text/plain body parts before first diff are added to the commit
4111 text/plain body parts before first diff are added to the commit
4111 message.
4112 message.
4112
4113
4113 If the imported patch was generated by :hg:`export`, user and
4114 If the imported patch was generated by :hg:`export`, user and
4114 description from patch override values from message headers and
4115 description from patch override values from message headers and
4115 body. Values given on command line with -m/--message and -u/--user
4116 body. Values given on command line with -m/--message and -u/--user
4116 override these.
4117 override these.
4117
4118
4118 If --exact is specified, import will set the working directory to
4119 If --exact is specified, import will set the working directory to
4119 the parent of each patch before applying it, and will abort if the
4120 the parent of each patch before applying it, and will abort if the
4120 resulting changeset has a different ID than the one recorded in
4121 resulting changeset has a different ID than the one recorded in
4121 the patch. This will guard against various ways that portable
4122 the patch. This will guard against various ways that portable
4122 patch formats and mail systems might fail to transfer Mercurial
4123 patch formats and mail systems might fail to transfer Mercurial
4123 data or metadata. See :hg:`bundle` for lossless transmission.
4124 data or metadata. See :hg:`bundle` for lossless transmission.
4124
4125
4125 Use --partial to ensure a changeset will be created from the patch
4126 Use --partial to ensure a changeset will be created from the patch
4126 even if some hunks fail to apply. Hunks that fail to apply will be
4127 even if some hunks fail to apply. Hunks that fail to apply will be
4127 written to a <target-file>.rej file. Conflicts can then be resolved
4128 written to a <target-file>.rej file. Conflicts can then be resolved
4128 by hand before :hg:`commit --amend` is run to update the created
4129 by hand before :hg:`commit --amend` is run to update the created
4129 changeset. This flag exists to let people import patches that
4130 changeset. This flag exists to let people import patches that
4130 partially apply without losing the associated metadata (author,
4131 partially apply without losing the associated metadata (author,
4131 date, description, ...).
4132 date, description, ...).
4132
4133
4133 .. note::
4134 .. note::
4134
4135
4135 When no hunks apply cleanly, :hg:`import --partial` will create
4136 When no hunks apply cleanly, :hg:`import --partial` will create
4136 an empty changeset, importing only the patch metadata.
4137 an empty changeset, importing only the patch metadata.
4137
4138
4138 With -s/--similarity, hg will attempt to discover renames and
4139 With -s/--similarity, hg will attempt to discover renames and
4139 copies in the patch in the same way as :hg:`addremove`.
4140 copies in the patch in the same way as :hg:`addremove`.
4140
4141
4141 It is possible to use external patch programs to perform the patch
4142 It is possible to use external patch programs to perform the patch
4142 by setting the ``ui.patch`` configuration option. For the default
4143 by setting the ``ui.patch`` configuration option. For the default
4143 internal tool, the fuzz can also be configured via ``patch.fuzz``.
4144 internal tool, the fuzz can also be configured via ``patch.fuzz``.
4144 See :hg:`help config` for more information about configuration
4145 See :hg:`help config` for more information about configuration
4145 files and how to use these options.
4146 files and how to use these options.
4146
4147
4147 See :hg:`help dates` for a list of formats valid for -d/--date.
4148 See :hg:`help dates` for a list of formats valid for -d/--date.
4148
4149
4149 .. container:: verbose
4150 .. container:: verbose
4150
4151
4151 Examples:
4152 Examples:
4152
4153
4153 - import a traditional patch from a website and detect renames::
4154 - import a traditional patch from a website and detect renames::
4154
4155
4155 hg import -s 80 http://example.com/bugfix.patch
4156 hg import -s 80 http://example.com/bugfix.patch
4156
4157
4157 - import a changeset from an hgweb server::
4158 - import a changeset from an hgweb server::
4158
4159
4159 hg import https://www.mercurial-scm.org/repo/hg/rev/5ca8c111e9aa
4160 hg import https://www.mercurial-scm.org/repo/hg/rev/5ca8c111e9aa
4160
4161
4161 - import all the patches in an Unix-style mbox::
4162 - import all the patches in an Unix-style mbox::
4162
4163
4163 hg import incoming-patches.mbox
4164 hg import incoming-patches.mbox
4164
4165
4165 - import patches from stdin::
4166 - import patches from stdin::
4166
4167
4167 hg import -
4168 hg import -
4168
4169
4169 - attempt to exactly restore an exported changeset (not always
4170 - attempt to exactly restore an exported changeset (not always
4170 possible)::
4171 possible)::
4171
4172
4172 hg import --exact proposed-fix.patch
4173 hg import --exact proposed-fix.patch
4173
4174
4174 - use an external tool to apply a patch which is too fuzzy for
4175 - use an external tool to apply a patch which is too fuzzy for
4175 the default internal tool.
4176 the default internal tool.
4176
4177
4177 hg import --config ui.patch="patch --merge" fuzzy.patch
4178 hg import --config ui.patch="patch --merge" fuzzy.patch
4178
4179
4179 - change the default fuzzing from 2 to a less strict 7
4180 - change the default fuzzing from 2 to a less strict 7
4180
4181
4181 hg import --config ui.fuzz=7 fuzz.patch
4182 hg import --config ui.fuzz=7 fuzz.patch
4182
4183
4183 Returns 0 on success, 1 on partial success (see --partial).
4184 Returns 0 on success, 1 on partial success (see --partial).
4184 """
4185 """
4185
4186
4186 opts = pycompat.byteskwargs(opts)
4187 opts = pycompat.byteskwargs(opts)
4187 if not patch1:
4188 if not patch1:
4188 raise error.Abort(_(b'need at least one patch to import'))
4189 raise error.Abort(_(b'need at least one patch to import'))
4189
4190
4190 patches = (patch1,) + patches
4191 patches = (patch1,) + patches
4191
4192
4192 date = opts.get(b'date')
4193 date = opts.get(b'date')
4193 if date:
4194 if date:
4194 opts[b'date'] = dateutil.parsedate(date)
4195 opts[b'date'] = dateutil.parsedate(date)
4195
4196
4196 exact = opts.get(b'exact')
4197 exact = opts.get(b'exact')
4197 update = not opts.get(b'bypass')
4198 update = not opts.get(b'bypass')
4198 if not update and opts.get(b'no_commit'):
4199 if not update and opts.get(b'no_commit'):
4199 raise error.Abort(_(b'cannot use --no-commit with --bypass'))
4200 raise error.Abort(_(b'cannot use --no-commit with --bypass'))
4200 if opts.get(b'secret') and opts.get(b'no_commit'):
4201 if opts.get(b'secret') and opts.get(b'no_commit'):
4201 raise error.Abort(_(b'cannot use --no-commit with --secret'))
4202 raise error.Abort(_(b'cannot use --no-commit with --secret'))
4202 try:
4203 try:
4203 sim = float(opts.get(b'similarity') or 0)
4204 sim = float(opts.get(b'similarity') or 0)
4204 except ValueError:
4205 except ValueError:
4205 raise error.Abort(_(b'similarity must be a number'))
4206 raise error.Abort(_(b'similarity must be a number'))
4206 if sim < 0 or sim > 100:
4207 if sim < 0 or sim > 100:
4207 raise error.Abort(_(b'similarity must be between 0 and 100'))
4208 raise error.Abort(_(b'similarity must be between 0 and 100'))
4208 if sim and not update:
4209 if sim and not update:
4209 raise error.Abort(_(b'cannot use --similarity with --bypass'))
4210 raise error.Abort(_(b'cannot use --similarity with --bypass'))
4210 if exact:
4211 if exact:
4211 if opts.get(b'edit'):
4212 if opts.get(b'edit'):
4212 raise error.Abort(_(b'cannot use --exact with --edit'))
4213 raise error.Abort(_(b'cannot use --exact with --edit'))
4213 if opts.get(b'prefix'):
4214 if opts.get(b'prefix'):
4214 raise error.Abort(_(b'cannot use --exact with --prefix'))
4215 raise error.Abort(_(b'cannot use --exact with --prefix'))
4215
4216
4216 base = opts[b"base"]
4217 base = opts[b"base"]
4217 msgs = []
4218 msgs = []
4218 ret = 0
4219 ret = 0
4219
4220
4220 with repo.wlock():
4221 with repo.wlock():
4221 if update:
4222 if update:
4222 cmdutil.checkunfinished(repo)
4223 cmdutil.checkunfinished(repo)
4223 if exact or not opts.get(b'force'):
4224 if exact or not opts.get(b'force'):
4224 cmdutil.bailifchanged(repo)
4225 cmdutil.bailifchanged(repo)
4225
4226
4226 if not opts.get(b'no_commit'):
4227 if not opts.get(b'no_commit'):
4227 lock = repo.lock
4228 lock = repo.lock
4228 tr = lambda: repo.transaction(b'import')
4229 tr = lambda: repo.transaction(b'import')
4229 dsguard = util.nullcontextmanager
4230 dsguard = util.nullcontextmanager
4230 else:
4231 else:
4231 lock = util.nullcontextmanager
4232 lock = util.nullcontextmanager
4232 tr = util.nullcontextmanager
4233 tr = util.nullcontextmanager
4233 dsguard = lambda: dirstateguard.dirstateguard(repo, b'import')
4234 dsguard = lambda: dirstateguard.dirstateguard(repo, b'import')
4234 with lock(), tr(), dsguard():
4235 with lock(), tr(), dsguard():
4235 parents = repo[None].parents()
4236 parents = repo[None].parents()
4236 for patchurl in patches:
4237 for patchurl in patches:
4237 if patchurl == b'-':
4238 if patchurl == b'-':
4238 ui.status(_(b'applying patch from stdin\n'))
4239 ui.status(_(b'applying patch from stdin\n'))
4239 patchfile = ui.fin
4240 patchfile = ui.fin
4240 patchurl = b'stdin' # for error message
4241 patchurl = b'stdin' # for error message
4241 else:
4242 else:
4242 patchurl = os.path.join(base, patchurl)
4243 patchurl = os.path.join(base, patchurl)
4243 ui.status(_(b'applying %s\n') % patchurl)
4244 ui.status(_(b'applying %s\n') % patchurl)
4244 patchfile = hg.openpath(ui, patchurl, sendaccept=False)
4245 patchfile = hg.openpath(ui, patchurl, sendaccept=False)
4245
4246
4246 haspatch = False
4247 haspatch = False
4247 for hunk in patch.split(patchfile):
4248 for hunk in patch.split(patchfile):
4248 with patch.extract(ui, hunk) as patchdata:
4249 with patch.extract(ui, hunk) as patchdata:
4249 msg, node, rej = cmdutil.tryimportone(
4250 msg, node, rej = cmdutil.tryimportone(
4250 ui, repo, patchdata, parents, opts, msgs, hg.clean
4251 ui, repo, patchdata, parents, opts, msgs, hg.clean
4251 )
4252 )
4252 if msg:
4253 if msg:
4253 haspatch = True
4254 haspatch = True
4254 ui.note(msg + b'\n')
4255 ui.note(msg + b'\n')
4255 if update or exact:
4256 if update or exact:
4256 parents = repo[None].parents()
4257 parents = repo[None].parents()
4257 else:
4258 else:
4258 parents = [repo[node]]
4259 parents = [repo[node]]
4259 if rej:
4260 if rej:
4260 ui.write_err(_(b"patch applied partially\n"))
4261 ui.write_err(_(b"patch applied partially\n"))
4261 ui.write_err(
4262 ui.write_err(
4262 _(
4263 _(
4263 b"(fix the .rej files and run "
4264 b"(fix the .rej files and run "
4264 b"`hg commit --amend`)\n"
4265 b"`hg commit --amend`)\n"
4265 )
4266 )
4266 )
4267 )
4267 ret = 1
4268 ret = 1
4268 break
4269 break
4269
4270
4270 if not haspatch:
4271 if not haspatch:
4271 raise error.Abort(_(b'%s: no diffs found') % patchurl)
4272 raise error.Abort(_(b'%s: no diffs found') % patchurl)
4272
4273
4273 if msgs:
4274 if msgs:
4274 repo.savecommitmessage(b'\n* * *\n'.join(msgs))
4275 repo.savecommitmessage(b'\n* * *\n'.join(msgs))
4275 return ret
4276 return ret
4276
4277
4277
4278
4278 @command(
4279 @command(
4279 b'incoming|in',
4280 b'incoming|in',
4280 [
4281 [
4281 (
4282 (
4282 b'f',
4283 b'f',
4283 b'force',
4284 b'force',
4284 None,
4285 None,
4285 _(b'run even if remote repository is unrelated'),
4286 _(b'run even if remote repository is unrelated'),
4286 ),
4287 ),
4287 (b'n', b'newest-first', None, _(b'show newest record first')),
4288 (b'n', b'newest-first', None, _(b'show newest record first')),
4288 (b'', b'bundle', b'', _(b'file to store the bundles into'), _(b'FILE')),
4289 (b'', b'bundle', b'', _(b'file to store the bundles into'), _(b'FILE')),
4289 (
4290 (
4290 b'r',
4291 b'r',
4291 b'rev',
4292 b'rev',
4292 [],
4293 [],
4293 _(b'a remote changeset intended to be added'),
4294 _(b'a remote changeset intended to be added'),
4294 _(b'REV'),
4295 _(b'REV'),
4295 ),
4296 ),
4296 (b'B', b'bookmarks', False, _(b"compare bookmarks")),
4297 (b'B', b'bookmarks', False, _(b"compare bookmarks")),
4297 (
4298 (
4298 b'b',
4299 b'b',
4299 b'branch',
4300 b'branch',
4300 [],
4301 [],
4301 _(b'a specific branch you would like to pull'),
4302 _(b'a specific branch you would like to pull'),
4302 _(b'BRANCH'),
4303 _(b'BRANCH'),
4303 ),
4304 ),
4304 ]
4305 ]
4305 + logopts
4306 + logopts
4306 + remoteopts
4307 + remoteopts
4307 + subrepoopts,
4308 + subrepoopts,
4308 _(b'[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'),
4309 _(b'[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'),
4309 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4310 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4310 )
4311 )
4311 def incoming(ui, repo, source=b"default", **opts):
4312 def incoming(ui, repo, source=b"default", **opts):
4312 """show new changesets found in source
4313 """show new changesets found in source
4313
4314
4314 Show new changesets found in the specified path/URL or the default
4315 Show new changesets found in the specified path/URL or the default
4315 pull location. These are the changesets that would have been pulled
4316 pull location. These are the changesets that would have been pulled
4316 by :hg:`pull` at the time you issued this command.
4317 by :hg:`pull` at the time you issued this command.
4317
4318
4318 See pull for valid source format details.
4319 See pull for valid source format details.
4319
4320
4320 .. container:: verbose
4321 .. container:: verbose
4321
4322
4322 With -B/--bookmarks, the result of bookmark comparison between
4323 With -B/--bookmarks, the result of bookmark comparison between
4323 local and remote repositories is displayed. With -v/--verbose,
4324 local and remote repositories is displayed. With -v/--verbose,
4324 status is also displayed for each bookmark like below::
4325 status is also displayed for each bookmark like below::
4325
4326
4326 BM1 01234567890a added
4327 BM1 01234567890a added
4327 BM2 1234567890ab advanced
4328 BM2 1234567890ab advanced
4328 BM3 234567890abc diverged
4329 BM3 234567890abc diverged
4329 BM4 34567890abcd changed
4330 BM4 34567890abcd changed
4330
4331
4331 The action taken locally when pulling depends on the
4332 The action taken locally when pulling depends on the
4332 status of each bookmark:
4333 status of each bookmark:
4333
4334
4334 :``added``: pull will create it
4335 :``added``: pull will create it
4335 :``advanced``: pull will update it
4336 :``advanced``: pull will update it
4336 :``diverged``: pull will create a divergent bookmark
4337 :``diverged``: pull will create a divergent bookmark
4337 :``changed``: result depends on remote changesets
4338 :``changed``: result depends on remote changesets
4338
4339
4339 From the point of view of pulling behavior, bookmark
4340 From the point of view of pulling behavior, bookmark
4340 existing only in the remote repository are treated as ``added``,
4341 existing only in the remote repository are treated as ``added``,
4341 even if it is in fact locally deleted.
4342 even if it is in fact locally deleted.
4342
4343
4343 .. container:: verbose
4344 .. container:: verbose
4344
4345
4345 For remote repository, using --bundle avoids downloading the
4346 For remote repository, using --bundle avoids downloading the
4346 changesets twice if the incoming is followed by a pull.
4347 changesets twice if the incoming is followed by a pull.
4347
4348
4348 Examples:
4349 Examples:
4349
4350
4350 - show incoming changes with patches and full description::
4351 - show incoming changes with patches and full description::
4351
4352
4352 hg incoming -vp
4353 hg incoming -vp
4353
4354
4354 - show incoming changes excluding merges, store a bundle::
4355 - show incoming changes excluding merges, store a bundle::
4355
4356
4356 hg in -vpM --bundle incoming.hg
4357 hg in -vpM --bundle incoming.hg
4357 hg pull incoming.hg
4358 hg pull incoming.hg
4358
4359
4359 - briefly list changes inside a bundle::
4360 - briefly list changes inside a bundle::
4360
4361
4361 hg in changes.hg -T "{desc|firstline}\\n"
4362 hg in changes.hg -T "{desc|firstline}\\n"
4362
4363
4363 Returns 0 if there are incoming changes, 1 otherwise.
4364 Returns 0 if there are incoming changes, 1 otherwise.
4364 """
4365 """
4365 opts = pycompat.byteskwargs(opts)
4366 opts = pycompat.byteskwargs(opts)
4366 if opts.get(b'graph'):
4367 if opts.get(b'graph'):
4367 logcmdutil.checkunsupportedgraphflags([], opts)
4368 logcmdutil.checkunsupportedgraphflags([], opts)
4368
4369
4369 def display(other, chlist, displayer):
4370 def display(other, chlist, displayer):
4370 revdag = logcmdutil.graphrevs(other, chlist, opts)
4371 revdag = logcmdutil.graphrevs(other, chlist, opts)
4371 logcmdutil.displaygraph(
4372 logcmdutil.displaygraph(
4372 ui, repo, revdag, displayer, graphmod.asciiedges
4373 ui, repo, revdag, displayer, graphmod.asciiedges
4373 )
4374 )
4374
4375
4375 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
4376 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
4376 return 0
4377 return 0
4377
4378
4378 if opts.get(b'bundle') and opts.get(b'subrepos'):
4379 if opts.get(b'bundle') and opts.get(b'subrepos'):
4379 raise error.Abort(_(b'cannot combine --bundle and --subrepos'))
4380 raise error.Abort(_(b'cannot combine --bundle and --subrepos'))
4380
4381
4381 if opts.get(b'bookmarks'):
4382 if opts.get(b'bookmarks'):
4382 source, branches = hg.parseurl(
4383 source, branches = hg.parseurl(
4383 ui.expandpath(source), opts.get(b'branch')
4384 ui.expandpath(source), opts.get(b'branch')
4384 )
4385 )
4385 other = hg.peer(repo, opts, source)
4386 other = hg.peer(repo, opts, source)
4386 if b'bookmarks' not in other.listkeys(b'namespaces'):
4387 if b'bookmarks' not in other.listkeys(b'namespaces'):
4387 ui.warn(_(b"remote doesn't support bookmarks\n"))
4388 ui.warn(_(b"remote doesn't support bookmarks\n"))
4388 return 0
4389 return 0
4389 ui.pager(b'incoming')
4390 ui.pager(b'incoming')
4390 ui.status(_(b'comparing with %s\n') % util.hidepassword(source))
4391 ui.status(_(b'comparing with %s\n') % util.hidepassword(source))
4391 return bookmarks.incoming(ui, repo, other)
4392 return bookmarks.incoming(ui, repo, other)
4392
4393
4393 repo._subtoppath = ui.expandpath(source)
4394 repo._subtoppath = ui.expandpath(source)
4394 try:
4395 try:
4395 return hg.incoming(ui, repo, source, opts)
4396 return hg.incoming(ui, repo, source, opts)
4396 finally:
4397 finally:
4397 del repo._subtoppath
4398 del repo._subtoppath
4398
4399
4399
4400
4400 @command(
4401 @command(
4401 b'init',
4402 b'init',
4402 remoteopts,
4403 remoteopts,
4403 _(b'[-e CMD] [--remotecmd CMD] [DEST]'),
4404 _(b'[-e CMD] [--remotecmd CMD] [DEST]'),
4404 helpcategory=command.CATEGORY_REPO_CREATION,
4405 helpcategory=command.CATEGORY_REPO_CREATION,
4405 helpbasic=True,
4406 helpbasic=True,
4406 norepo=True,
4407 norepo=True,
4407 )
4408 )
4408 def init(ui, dest=b".", **opts):
4409 def init(ui, dest=b".", **opts):
4409 """create a new repository in the given directory
4410 """create a new repository in the given directory
4410
4411
4411 Initialize a new repository in the given directory. If the given
4412 Initialize a new repository in the given directory. If the given
4412 directory does not exist, it will be created.
4413 directory does not exist, it will be created.
4413
4414
4414 If no directory is given, the current directory is used.
4415 If no directory is given, the current directory is used.
4415
4416
4416 It is possible to specify an ``ssh://`` URL as the destination.
4417 It is possible to specify an ``ssh://`` URL as the destination.
4417 See :hg:`help urls` for more information.
4418 See :hg:`help urls` for more information.
4418
4419
4419 Returns 0 on success.
4420 Returns 0 on success.
4420 """
4421 """
4421 opts = pycompat.byteskwargs(opts)
4422 opts = pycompat.byteskwargs(opts)
4422 hg.peer(ui, opts, ui.expandpath(dest), create=True)
4423 hg.peer(ui, opts, ui.expandpath(dest), create=True)
4423
4424
4424
4425
4425 @command(
4426 @command(
4426 b'locate',
4427 b'locate',
4427 [
4428 [
4428 (
4429 (
4429 b'r',
4430 b'r',
4430 b'rev',
4431 b'rev',
4431 b'',
4432 b'',
4432 _(b'search the repository as it is in REV'),
4433 _(b'search the repository as it is in REV'),
4433 _(b'REV'),
4434 _(b'REV'),
4434 ),
4435 ),
4435 (
4436 (
4436 b'0',
4437 b'0',
4437 b'print0',
4438 b'print0',
4438 None,
4439 None,
4439 _(b'end filenames with NUL, for use with xargs'),
4440 _(b'end filenames with NUL, for use with xargs'),
4440 ),
4441 ),
4441 (
4442 (
4442 b'f',
4443 b'f',
4443 b'fullpath',
4444 b'fullpath',
4444 None,
4445 None,
4445 _(b'print complete paths from the filesystem root'),
4446 _(b'print complete paths from the filesystem root'),
4446 ),
4447 ),
4447 ]
4448 ]
4448 + walkopts,
4449 + walkopts,
4449 _(b'[OPTION]... [PATTERN]...'),
4450 _(b'[OPTION]... [PATTERN]...'),
4450 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
4451 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
4451 )
4452 )
4452 def locate(ui, repo, *pats, **opts):
4453 def locate(ui, repo, *pats, **opts):
4453 """locate files matching specific patterns (DEPRECATED)
4454 """locate files matching specific patterns (DEPRECATED)
4454
4455
4455 Print files under Mercurial control in the working directory whose
4456 Print files under Mercurial control in the working directory whose
4456 names match the given patterns.
4457 names match the given patterns.
4457
4458
4458 By default, this command searches all directories in the working
4459 By default, this command searches all directories in the working
4459 directory. To search just the current directory and its
4460 directory. To search just the current directory and its
4460 subdirectories, use "--include .".
4461 subdirectories, use "--include .".
4461
4462
4462 If no patterns are given to match, this command prints the names
4463 If no patterns are given to match, this command prints the names
4463 of all files under Mercurial control in the working directory.
4464 of all files under Mercurial control in the working directory.
4464
4465
4465 If you want to feed the output of this command into the "xargs"
4466 If you want to feed the output of this command into the "xargs"
4466 command, use the -0 option to both this command and "xargs". This
4467 command, use the -0 option to both this command and "xargs". This
4467 will avoid the problem of "xargs" treating single filenames that
4468 will avoid the problem of "xargs" treating single filenames that
4468 contain whitespace as multiple filenames.
4469 contain whitespace as multiple filenames.
4469
4470
4470 See :hg:`help files` for a more versatile command.
4471 See :hg:`help files` for a more versatile command.
4471
4472
4472 Returns 0 if a match is found, 1 otherwise.
4473 Returns 0 if a match is found, 1 otherwise.
4473 """
4474 """
4474 opts = pycompat.byteskwargs(opts)
4475 opts = pycompat.byteskwargs(opts)
4475 if opts.get(b'print0'):
4476 if opts.get(b'print0'):
4476 end = b'\0'
4477 end = b'\0'
4477 else:
4478 else:
4478 end = b'\n'
4479 end = b'\n'
4479 ctx = scmutil.revsingle(repo, opts.get(b'rev'), None)
4480 ctx = scmutil.revsingle(repo, opts.get(b'rev'), None)
4480
4481
4481 ret = 1
4482 ret = 1
4482 m = scmutil.match(
4483 m = scmutil.match(
4483 ctx, pats, opts, default=b'relglob', badfn=lambda x, y: False
4484 ctx, pats, opts, default=b'relglob', badfn=lambda x, y: False
4484 )
4485 )
4485
4486
4486 ui.pager(b'locate')
4487 ui.pager(b'locate')
4487 if ctx.rev() is None:
4488 if ctx.rev() is None:
4488 # When run on the working copy, "locate" includes removed files, so
4489 # When run on the working copy, "locate" includes removed files, so
4489 # we get the list of files from the dirstate.
4490 # we get the list of files from the dirstate.
4490 filesgen = sorted(repo.dirstate.matches(m))
4491 filesgen = sorted(repo.dirstate.matches(m))
4491 else:
4492 else:
4492 filesgen = ctx.matches(m)
4493 filesgen = ctx.matches(m)
4493 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=bool(pats))
4494 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=bool(pats))
4494 for abs in filesgen:
4495 for abs in filesgen:
4495 if opts.get(b'fullpath'):
4496 if opts.get(b'fullpath'):
4496 ui.write(repo.wjoin(abs), end)
4497 ui.write(repo.wjoin(abs), end)
4497 else:
4498 else:
4498 ui.write(uipathfn(abs), end)
4499 ui.write(uipathfn(abs), end)
4499 ret = 0
4500 ret = 0
4500
4501
4501 return ret
4502 return ret
4502
4503
4503
4504
4504 @command(
4505 @command(
4505 b'log|history',
4506 b'log|history',
4506 [
4507 [
4507 (
4508 (
4508 b'f',
4509 b'f',
4509 b'follow',
4510 b'follow',
4510 None,
4511 None,
4511 _(
4512 _(
4512 b'follow changeset history, or file history across copies and renames'
4513 b'follow changeset history, or file history across copies and renames'
4513 ),
4514 ),
4514 ),
4515 ),
4515 (
4516 (
4516 b'',
4517 b'',
4517 b'follow-first',
4518 b'follow-first',
4518 None,
4519 None,
4519 _(b'only follow the first parent of merge changesets (DEPRECATED)'),
4520 _(b'only follow the first parent of merge changesets (DEPRECATED)'),
4520 ),
4521 ),
4521 (
4522 (
4522 b'd',
4523 b'd',
4523 b'date',
4524 b'date',
4524 b'',
4525 b'',
4525 _(b'show revisions matching date spec'),
4526 _(b'show revisions matching date spec'),
4526 _(b'DATE'),
4527 _(b'DATE'),
4527 ),
4528 ),
4528 (b'C', b'copies', None, _(b'show copied files')),
4529 (b'C', b'copies', None, _(b'show copied files')),
4529 (
4530 (
4530 b'k',
4531 b'k',
4531 b'keyword',
4532 b'keyword',
4532 [],
4533 [],
4533 _(b'do case-insensitive search for a given text'),
4534 _(b'do case-insensitive search for a given text'),
4534 _(b'TEXT'),
4535 _(b'TEXT'),
4535 ),
4536 ),
4536 (
4537 (
4537 b'r',
4538 b'r',
4538 b'rev',
4539 b'rev',
4539 [],
4540 [],
4540 _(b'show the specified revision or revset'),
4541 _(b'show the specified revision or revset'),
4541 _(b'REV'),
4542 _(b'REV'),
4542 ),
4543 ),
4543 (
4544 (
4544 b'L',
4545 b'L',
4545 b'line-range',
4546 b'line-range',
4546 [],
4547 [],
4547 _(b'follow line range of specified file (EXPERIMENTAL)'),
4548 _(b'follow line range of specified file (EXPERIMENTAL)'),
4548 _(b'FILE,RANGE'),
4549 _(b'FILE,RANGE'),
4549 ),
4550 ),
4550 (
4551 (
4551 b'',
4552 b'',
4552 b'removed',
4553 b'removed',
4553 None,
4554 None,
4554 _(b'include revisions where files were removed'),
4555 _(b'include revisions where files were removed'),
4555 ),
4556 ),
4556 (
4557 (
4557 b'm',
4558 b'm',
4558 b'only-merges',
4559 b'only-merges',
4559 None,
4560 None,
4560 _(b'show only merges (DEPRECATED) (use -r "merge()" instead)'),
4561 _(b'show only merges (DEPRECATED) (use -r "merge()" instead)'),
4561 ),
4562 ),
4562 (b'u', b'user', [], _(b'revisions committed by user'), _(b'USER')),
4563 (b'u', b'user', [], _(b'revisions committed by user'), _(b'USER')),
4563 (
4564 (
4564 b'',
4565 b'',
4565 b'only-branch',
4566 b'only-branch',
4566 [],
4567 [],
4567 _(
4568 _(
4568 b'show only changesets within the given named branch (DEPRECATED)'
4569 b'show only changesets within the given named branch (DEPRECATED)'
4569 ),
4570 ),
4570 _(b'BRANCH'),
4571 _(b'BRANCH'),
4571 ),
4572 ),
4572 (
4573 (
4573 b'b',
4574 b'b',
4574 b'branch',
4575 b'branch',
4575 [],
4576 [],
4576 _(b'show changesets within the given named branch'),
4577 _(b'show changesets within the given named branch'),
4577 _(b'BRANCH'),
4578 _(b'BRANCH'),
4578 ),
4579 ),
4579 (
4580 (
4580 b'P',
4581 b'P',
4581 b'prune',
4582 b'prune',
4582 [],
4583 [],
4583 _(b'do not display revision or any of its ancestors'),
4584 _(b'do not display revision or any of its ancestors'),
4584 _(b'REV'),
4585 _(b'REV'),
4585 ),
4586 ),
4586 ]
4587 ]
4587 + logopts
4588 + logopts
4588 + walkopts,
4589 + walkopts,
4589 _(b'[OPTION]... [FILE]'),
4590 _(b'[OPTION]... [FILE]'),
4590 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
4591 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
4591 helpbasic=True,
4592 helpbasic=True,
4592 inferrepo=True,
4593 inferrepo=True,
4593 intents={INTENT_READONLY},
4594 intents={INTENT_READONLY},
4594 )
4595 )
4595 def log(ui, repo, *pats, **opts):
4596 def log(ui, repo, *pats, **opts):
4596 """show revision history of entire repository or files
4597 """show revision history of entire repository or files
4597
4598
4598 Print the revision history of the specified files or the entire
4599 Print the revision history of the specified files or the entire
4599 project.
4600 project.
4600
4601
4601 If no revision range is specified, the default is ``tip:0`` unless
4602 If no revision range is specified, the default is ``tip:0`` unless
4602 --follow is set, in which case the working directory parent is
4603 --follow is set, in which case the working directory parent is
4603 used as the starting revision.
4604 used as the starting revision.
4604
4605
4605 File history is shown without following rename or copy history of
4606 File history is shown without following rename or copy history of
4606 files. Use -f/--follow with a filename to follow history across
4607 files. Use -f/--follow with a filename to follow history across
4607 renames and copies. --follow without a filename will only show
4608 renames and copies. --follow without a filename will only show
4608 ancestors of the starting revision.
4609 ancestors of the starting revision.
4609
4610
4610 By default this command prints revision number and changeset id,
4611 By default this command prints revision number and changeset id,
4611 tags, non-trivial parents, user, date and time, and a summary for
4612 tags, non-trivial parents, user, date and time, and a summary for
4612 each commit. When the -v/--verbose switch is used, the list of
4613 each commit. When the -v/--verbose switch is used, the list of
4613 changed files and full commit message are shown.
4614 changed files and full commit message are shown.
4614
4615
4615 With --graph the revisions are shown as an ASCII art DAG with the most
4616 With --graph the revisions are shown as an ASCII art DAG with the most
4616 recent changeset at the top.
4617 recent changeset at the top.
4617 'o' is a changeset, '@' is a working directory parent, '%' is a changeset
4618 'o' is a changeset, '@' is a working directory parent, '%' is a changeset
4618 involved in an unresolved merge conflict, '_' closes a branch,
4619 involved in an unresolved merge conflict, '_' closes a branch,
4619 'x' is obsolete, '*' is unstable, and '+' represents a fork where the
4620 'x' is obsolete, '*' is unstable, and '+' represents a fork where the
4620 changeset from the lines below is a parent of the 'o' merge on the same
4621 changeset from the lines below is a parent of the 'o' merge on the same
4621 line.
4622 line.
4622 Paths in the DAG are represented with '|', '/' and so forth. ':' in place
4623 Paths in the DAG are represented with '|', '/' and so forth. ':' in place
4623 of a '|' indicates one or more revisions in a path are omitted.
4624 of a '|' indicates one or more revisions in a path are omitted.
4624
4625
4625 .. container:: verbose
4626 .. container:: verbose
4626
4627
4627 Use -L/--line-range FILE,M:N options to follow the history of lines
4628 Use -L/--line-range FILE,M:N options to follow the history of lines
4628 from M to N in FILE. With -p/--patch only diff hunks affecting
4629 from M to N in FILE. With -p/--patch only diff hunks affecting
4629 specified line range will be shown. This option requires --follow;
4630 specified line range will be shown. This option requires --follow;
4630 it can be specified multiple times. Currently, this option is not
4631 it can be specified multiple times. Currently, this option is not
4631 compatible with --graph. This option is experimental.
4632 compatible with --graph. This option is experimental.
4632
4633
4633 .. note::
4634 .. note::
4634
4635
4635 :hg:`log --patch` may generate unexpected diff output for merge
4636 :hg:`log --patch` may generate unexpected diff output for merge
4636 changesets, as it will only compare the merge changeset against
4637 changesets, as it will only compare the merge changeset against
4637 its first parent. Also, only files different from BOTH parents
4638 its first parent. Also, only files different from BOTH parents
4638 will appear in files:.
4639 will appear in files:.
4639
4640
4640 .. note::
4641 .. note::
4641
4642
4642 For performance reasons, :hg:`log FILE` may omit duplicate changes
4643 For performance reasons, :hg:`log FILE` may omit duplicate changes
4643 made on branches and will not show removals or mode changes. To
4644 made on branches and will not show removals or mode changes. To
4644 see all such changes, use the --removed switch.
4645 see all such changes, use the --removed switch.
4645
4646
4646 .. container:: verbose
4647 .. container:: verbose
4647
4648
4648 .. note::
4649 .. note::
4649
4650
4650 The history resulting from -L/--line-range options depends on diff
4651 The history resulting from -L/--line-range options depends on diff
4651 options; for instance if white-spaces are ignored, respective changes
4652 options; for instance if white-spaces are ignored, respective changes
4652 with only white-spaces in specified line range will not be listed.
4653 with only white-spaces in specified line range will not be listed.
4653
4654
4654 .. container:: verbose
4655 .. container:: verbose
4655
4656
4656 Some examples:
4657 Some examples:
4657
4658
4658 - changesets with full descriptions and file lists::
4659 - changesets with full descriptions and file lists::
4659
4660
4660 hg log -v
4661 hg log -v
4661
4662
4662 - changesets ancestral to the working directory::
4663 - changesets ancestral to the working directory::
4663
4664
4664 hg log -f
4665 hg log -f
4665
4666
4666 - last 10 commits on the current branch::
4667 - last 10 commits on the current branch::
4667
4668
4668 hg log -l 10 -b .
4669 hg log -l 10 -b .
4669
4670
4670 - changesets showing all modifications of a file, including removals::
4671 - changesets showing all modifications of a file, including removals::
4671
4672
4672 hg log --removed file.c
4673 hg log --removed file.c
4673
4674
4674 - all changesets that touch a directory, with diffs, excluding merges::
4675 - all changesets that touch a directory, with diffs, excluding merges::
4675
4676
4676 hg log -Mp lib/
4677 hg log -Mp lib/
4677
4678
4678 - all revision numbers that match a keyword::
4679 - all revision numbers that match a keyword::
4679
4680
4680 hg log -k bug --template "{rev}\\n"
4681 hg log -k bug --template "{rev}\\n"
4681
4682
4682 - the full hash identifier of the working directory parent::
4683 - the full hash identifier of the working directory parent::
4683
4684
4684 hg log -r . --template "{node}\\n"
4685 hg log -r . --template "{node}\\n"
4685
4686
4686 - list available log templates::
4687 - list available log templates::
4687
4688
4688 hg log -T list
4689 hg log -T list
4689
4690
4690 - check if a given changeset is included in a tagged release::
4691 - check if a given changeset is included in a tagged release::
4691
4692
4692 hg log -r "a21ccf and ancestor(1.9)"
4693 hg log -r "a21ccf and ancestor(1.9)"
4693
4694
4694 - find all changesets by some user in a date range::
4695 - find all changesets by some user in a date range::
4695
4696
4696 hg log -k alice -d "may 2008 to jul 2008"
4697 hg log -k alice -d "may 2008 to jul 2008"
4697
4698
4698 - summary of all changesets after the last tag::
4699 - summary of all changesets after the last tag::
4699
4700
4700 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
4701 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
4701
4702
4702 - changesets touching lines 13 to 23 for file.c::
4703 - changesets touching lines 13 to 23 for file.c::
4703
4704
4704 hg log -L file.c,13:23
4705 hg log -L file.c,13:23
4705
4706
4706 - changesets touching lines 13 to 23 for file.c and lines 2 to 6 of
4707 - changesets touching lines 13 to 23 for file.c and lines 2 to 6 of
4707 main.c with patch::
4708 main.c with patch::
4708
4709
4709 hg log -L file.c,13:23 -L main.c,2:6 -p
4710 hg log -L file.c,13:23 -L main.c,2:6 -p
4710
4711
4711 See :hg:`help dates` for a list of formats valid for -d/--date.
4712 See :hg:`help dates` for a list of formats valid for -d/--date.
4712
4713
4713 See :hg:`help revisions` for more about specifying and ordering
4714 See :hg:`help revisions` for more about specifying and ordering
4714 revisions.
4715 revisions.
4715
4716
4716 See :hg:`help templates` for more about pre-packaged styles and
4717 See :hg:`help templates` for more about pre-packaged styles and
4717 specifying custom templates. The default template used by the log
4718 specifying custom templates. The default template used by the log
4718 command can be customized via the ``ui.logtemplate`` configuration
4719 command can be customized via the ``ui.logtemplate`` configuration
4719 setting.
4720 setting.
4720
4721
4721 Returns 0 on success.
4722 Returns 0 on success.
4722
4723
4723 """
4724 """
4724 opts = pycompat.byteskwargs(opts)
4725 opts = pycompat.byteskwargs(opts)
4725 linerange = opts.get(b'line_range')
4726 linerange = opts.get(b'line_range')
4726
4727
4727 if linerange and not opts.get(b'follow'):
4728 if linerange and not opts.get(b'follow'):
4728 raise error.Abort(_(b'--line-range requires --follow'))
4729 raise error.Abort(_(b'--line-range requires --follow'))
4729
4730
4730 if linerange and pats:
4731 if linerange and pats:
4731 # TODO: take pats as patterns with no line-range filter
4732 # TODO: take pats as patterns with no line-range filter
4732 raise error.Abort(
4733 raise error.Abort(
4733 _(b'FILE arguments are not compatible with --line-range option')
4734 _(b'FILE arguments are not compatible with --line-range option')
4734 )
4735 )
4735
4736
4736 repo = scmutil.unhidehashlikerevs(repo, opts.get(b'rev'), b'nowarn')
4737 repo = scmutil.unhidehashlikerevs(repo, opts.get(b'rev'), b'nowarn')
4737 revs, differ = logcmdutil.getrevs(
4738 revs, differ = logcmdutil.getrevs(
4738 repo, logcmdutil.parseopts(ui, pats, opts)
4739 repo, logcmdutil.parseopts(ui, pats, opts)
4739 )
4740 )
4740 if linerange:
4741 if linerange:
4741 # TODO: should follow file history from logcmdutil._initialrevs(),
4742 # TODO: should follow file history from logcmdutil._initialrevs(),
4742 # then filter the result by logcmdutil._makerevset() and --limit
4743 # then filter the result by logcmdutil._makerevset() and --limit
4743 revs, differ = logcmdutil.getlinerangerevs(repo, revs, opts)
4744 revs, differ = logcmdutil.getlinerangerevs(repo, revs, opts)
4744
4745
4745 getcopies = None
4746 getcopies = None
4746 if opts.get(b'copies'):
4747 if opts.get(b'copies'):
4747 endrev = None
4748 endrev = None
4748 if revs:
4749 if revs:
4749 endrev = revs.max() + 1
4750 endrev = revs.max() + 1
4750 getcopies = scmutil.getcopiesfn(repo, endrev=endrev)
4751 getcopies = scmutil.getcopiesfn(repo, endrev=endrev)
4751
4752
4752 ui.pager(b'log')
4753 ui.pager(b'log')
4753 displayer = logcmdutil.changesetdisplayer(
4754 displayer = logcmdutil.changesetdisplayer(
4754 ui, repo, opts, differ, buffered=True
4755 ui, repo, opts, differ, buffered=True
4755 )
4756 )
4756 if opts.get(b'graph'):
4757 if opts.get(b'graph'):
4757 displayfn = logcmdutil.displaygraphrevs
4758 displayfn = logcmdutil.displaygraphrevs
4758 else:
4759 else:
4759 displayfn = logcmdutil.displayrevs
4760 displayfn = logcmdutil.displayrevs
4760 displayfn(ui, repo, revs, displayer, getcopies)
4761 displayfn(ui, repo, revs, displayer, getcopies)
4761
4762
4762
4763
4763 @command(
4764 @command(
4764 b'manifest',
4765 b'manifest',
4765 [
4766 [
4766 (b'r', b'rev', b'', _(b'revision to display'), _(b'REV')),
4767 (b'r', b'rev', b'', _(b'revision to display'), _(b'REV')),
4767 (b'', b'all', False, _(b"list files from all revisions")),
4768 (b'', b'all', False, _(b"list files from all revisions")),
4768 ]
4769 ]
4769 + formatteropts,
4770 + formatteropts,
4770 _(b'[-r REV]'),
4771 _(b'[-r REV]'),
4771 helpcategory=command.CATEGORY_MAINTENANCE,
4772 helpcategory=command.CATEGORY_MAINTENANCE,
4772 intents={INTENT_READONLY},
4773 intents={INTENT_READONLY},
4773 )
4774 )
4774 def manifest(ui, repo, node=None, rev=None, **opts):
4775 def manifest(ui, repo, node=None, rev=None, **opts):
4775 """output the current or given revision of the project manifest
4776 """output the current or given revision of the project manifest
4776
4777
4777 Print a list of version controlled files for the given revision.
4778 Print a list of version controlled files for the given revision.
4778 If no revision is given, the first parent of the working directory
4779 If no revision is given, the first parent of the working directory
4779 is used, or the null revision if no revision is checked out.
4780 is used, or the null revision if no revision is checked out.
4780
4781
4781 With -v, print file permissions, symlink and executable bits.
4782 With -v, print file permissions, symlink and executable bits.
4782 With --debug, print file revision hashes.
4783 With --debug, print file revision hashes.
4783
4784
4784 If option --all is specified, the list of all files from all revisions
4785 If option --all is specified, the list of all files from all revisions
4785 is printed. This includes deleted and renamed files.
4786 is printed. This includes deleted and renamed files.
4786
4787
4787 Returns 0 on success.
4788 Returns 0 on success.
4788 """
4789 """
4789 opts = pycompat.byteskwargs(opts)
4790 opts = pycompat.byteskwargs(opts)
4790 fm = ui.formatter(b'manifest', opts)
4791 fm = ui.formatter(b'manifest', opts)
4791
4792
4792 if opts.get(b'all'):
4793 if opts.get(b'all'):
4793 if rev or node:
4794 if rev or node:
4794 raise error.Abort(_(b"can't specify a revision with --all"))
4795 raise error.Abort(_(b"can't specify a revision with --all"))
4795
4796
4796 res = set()
4797 res = set()
4797 for rev in repo:
4798 for rev in repo:
4798 ctx = repo[rev]
4799 ctx = repo[rev]
4799 res |= set(ctx.files())
4800 res |= set(ctx.files())
4800
4801
4801 ui.pager(b'manifest')
4802 ui.pager(b'manifest')
4802 for f in sorted(res):
4803 for f in sorted(res):
4803 fm.startitem()
4804 fm.startitem()
4804 fm.write(b"path", b'%s\n', f)
4805 fm.write(b"path", b'%s\n', f)
4805 fm.end()
4806 fm.end()
4806 return
4807 return
4807
4808
4808 if rev and node:
4809 if rev and node:
4809 raise error.Abort(_(b"please specify just one revision"))
4810 raise error.Abort(_(b"please specify just one revision"))
4810
4811
4811 if not node:
4812 if not node:
4812 node = rev
4813 node = rev
4813
4814
4814 char = {b'l': b'@', b'x': b'*', b'': b'', b't': b'd'}
4815 char = {b'l': b'@', b'x': b'*', b'': b'', b't': b'd'}
4815 mode = {b'l': b'644', b'x': b'755', b'': b'644', b't': b'755'}
4816 mode = {b'l': b'644', b'x': b'755', b'': b'644', b't': b'755'}
4816 if node:
4817 if node:
4817 repo = scmutil.unhidehashlikerevs(repo, [node], b'nowarn')
4818 repo = scmutil.unhidehashlikerevs(repo, [node], b'nowarn')
4818 ctx = scmutil.revsingle(repo, node)
4819 ctx = scmutil.revsingle(repo, node)
4819 mf = ctx.manifest()
4820 mf = ctx.manifest()
4820 ui.pager(b'manifest')
4821 ui.pager(b'manifest')
4821 for f in ctx:
4822 for f in ctx:
4822 fm.startitem()
4823 fm.startitem()
4823 fm.context(ctx=ctx)
4824 fm.context(ctx=ctx)
4824 fl = ctx[f].flags()
4825 fl = ctx[f].flags()
4825 fm.condwrite(ui.debugflag, b'hash', b'%s ', hex(mf[f]))
4826 fm.condwrite(ui.debugflag, b'hash', b'%s ', hex(mf[f]))
4826 fm.condwrite(ui.verbose, b'mode type', b'%s %1s ', mode[fl], char[fl])
4827 fm.condwrite(ui.verbose, b'mode type', b'%s %1s ', mode[fl], char[fl])
4827 fm.write(b'path', b'%s\n', f)
4828 fm.write(b'path', b'%s\n', f)
4828 fm.end()
4829 fm.end()
4829
4830
4830
4831
4831 @command(
4832 @command(
4832 b'merge',
4833 b'merge',
4833 [
4834 [
4834 (
4835 (
4835 b'f',
4836 b'f',
4836 b'force',
4837 b'force',
4837 None,
4838 None,
4838 _(b'force a merge including outstanding changes (DEPRECATED)'),
4839 _(b'force a merge including outstanding changes (DEPRECATED)'),
4839 ),
4840 ),
4840 (b'r', b'rev', b'', _(b'revision to merge'), _(b'REV')),
4841 (b'r', b'rev', b'', _(b'revision to merge'), _(b'REV')),
4841 (
4842 (
4842 b'P',
4843 b'P',
4843 b'preview',
4844 b'preview',
4844 None,
4845 None,
4845 _(b'review revisions to merge (no merge is performed)'),
4846 _(b'review revisions to merge (no merge is performed)'),
4846 ),
4847 ),
4847 (b'', b'abort', None, _(b'abort the ongoing merge')),
4848 (b'', b'abort', None, _(b'abort the ongoing merge')),
4848 ]
4849 ]
4849 + mergetoolopts,
4850 + mergetoolopts,
4850 _(b'[-P] [[-r] REV]'),
4851 _(b'[-P] [[-r] REV]'),
4851 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
4852 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
4852 helpbasic=True,
4853 helpbasic=True,
4853 )
4854 )
4854 def merge(ui, repo, node=None, **opts):
4855 def merge(ui, repo, node=None, **opts):
4855 """merge another revision into working directory
4856 """merge another revision into working directory
4856
4857
4857 The current working directory is updated with all changes made in
4858 The current working directory is updated with all changes made in
4858 the requested revision since the last common predecessor revision.
4859 the requested revision since the last common predecessor revision.
4859
4860
4860 Files that changed between either parent are marked as changed for
4861 Files that changed between either parent are marked as changed for
4861 the next commit and a commit must be performed before any further
4862 the next commit and a commit must be performed before any further
4862 updates to the repository are allowed. The next commit will have
4863 updates to the repository are allowed. The next commit will have
4863 two parents.
4864 two parents.
4864
4865
4865 ``--tool`` can be used to specify the merge tool used for file
4866 ``--tool`` can be used to specify the merge tool used for file
4866 merges. It overrides the HGMERGE environment variable and your
4867 merges. It overrides the HGMERGE environment variable and your
4867 configuration files. See :hg:`help merge-tools` for options.
4868 configuration files. See :hg:`help merge-tools` for options.
4868
4869
4869 If no revision is specified, the working directory's parent is a
4870 If no revision is specified, the working directory's parent is a
4870 head revision, and the current branch contains exactly one other
4871 head revision, and the current branch contains exactly one other
4871 head, the other head is merged with by default. Otherwise, an
4872 head, the other head is merged with by default. Otherwise, an
4872 explicit revision with which to merge must be provided.
4873 explicit revision with which to merge must be provided.
4873
4874
4874 See :hg:`help resolve` for information on handling file conflicts.
4875 See :hg:`help resolve` for information on handling file conflicts.
4875
4876
4876 To undo an uncommitted merge, use :hg:`merge --abort` which
4877 To undo an uncommitted merge, use :hg:`merge --abort` which
4877 will check out a clean copy of the original merge parent, losing
4878 will check out a clean copy of the original merge parent, losing
4878 all changes.
4879 all changes.
4879
4880
4880 Returns 0 on success, 1 if there are unresolved files.
4881 Returns 0 on success, 1 if there are unresolved files.
4881 """
4882 """
4882
4883
4883 opts = pycompat.byteskwargs(opts)
4884 opts = pycompat.byteskwargs(opts)
4884 abort = opts.get(b'abort')
4885 abort = opts.get(b'abort')
4885 if abort and repo.dirstate.p2() == nullid:
4886 if abort and repo.dirstate.p2() == nullid:
4886 cmdutil.wrongtooltocontinue(repo, _(b'merge'))
4887 cmdutil.wrongtooltocontinue(repo, _(b'merge'))
4887 cmdutil.check_incompatible_arguments(opts, b'abort', [b'rev', b'preview'])
4888 cmdutil.check_incompatible_arguments(opts, b'abort', [b'rev', b'preview'])
4888 if abort:
4889 if abort:
4889 state = cmdutil.getunfinishedstate(repo)
4890 state = cmdutil.getunfinishedstate(repo)
4890 if state and state._opname != b'merge':
4891 if state and state._opname != b'merge':
4891 raise error.Abort(
4892 raise error.Abort(
4892 _(b'cannot abort merge with %s in progress') % (state._opname),
4893 _(b'cannot abort merge with %s in progress') % (state._opname),
4893 hint=state.hint(),
4894 hint=state.hint(),
4894 )
4895 )
4895 if node:
4896 if node:
4896 raise error.Abort(_(b"cannot specify a node with --abort"))
4897 raise error.Abort(_(b"cannot specify a node with --abort"))
4897 return hg.abortmerge(repo.ui, repo)
4898 return hg.abortmerge(repo.ui, repo)
4898
4899
4899 if opts.get(b'rev') and node:
4900 if opts.get(b'rev') and node:
4900 raise error.Abort(_(b"please specify just one revision"))
4901 raise error.Abort(_(b"please specify just one revision"))
4901 if not node:
4902 if not node:
4902 node = opts.get(b'rev')
4903 node = opts.get(b'rev')
4903
4904
4904 if node:
4905 if node:
4905 ctx = scmutil.revsingle(repo, node)
4906 ctx = scmutil.revsingle(repo, node)
4906 else:
4907 else:
4907 if ui.configbool(b'commands', b'merge.require-rev'):
4908 if ui.configbool(b'commands', b'merge.require-rev'):
4908 raise error.Abort(
4909 raise error.Abort(
4909 _(
4910 _(
4910 b'configuration requires specifying revision to merge '
4911 b'configuration requires specifying revision to merge '
4911 b'with'
4912 b'with'
4912 )
4913 )
4913 )
4914 )
4914 ctx = repo[destutil.destmerge(repo)]
4915 ctx = repo[destutil.destmerge(repo)]
4915
4916
4916 if ctx.node() is None:
4917 if ctx.node() is None:
4917 raise error.Abort(_(b'merging with the working copy has no effect'))
4918 raise error.Abort(_(b'merging with the working copy has no effect'))
4918
4919
4919 if opts.get(b'preview'):
4920 if opts.get(b'preview'):
4920 # find nodes that are ancestors of p2 but not of p1
4921 # find nodes that are ancestors of p2 but not of p1
4921 p1 = repo[b'.'].node()
4922 p1 = repo[b'.'].node()
4922 p2 = ctx.node()
4923 p2 = ctx.node()
4923 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
4924 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
4924
4925
4925 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
4926 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
4926 for node in nodes:
4927 for node in nodes:
4927 displayer.show(repo[node])
4928 displayer.show(repo[node])
4928 displayer.close()
4929 displayer.close()
4929 return 0
4930 return 0
4930
4931
4931 # ui.forcemerge is an internal variable, do not document
4932 # ui.forcemerge is an internal variable, do not document
4932 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
4933 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
4933 with ui.configoverride(overrides, b'merge'):
4934 with ui.configoverride(overrides, b'merge'):
4934 force = opts.get(b'force')
4935 force = opts.get(b'force')
4935 labels = [b'working copy', b'merge rev']
4936 labels = [b'working copy', b'merge rev']
4936 return hg.merge(ctx, force=force, labels=labels)
4937 return hg.merge(ctx, force=force, labels=labels)
4937
4938
4938
4939
4939 statemod.addunfinished(
4940 statemod.addunfinished(
4940 b'merge',
4941 b'merge',
4941 fname=None,
4942 fname=None,
4942 clearable=True,
4943 clearable=True,
4943 allowcommit=True,
4944 allowcommit=True,
4944 cmdmsg=_(b'outstanding uncommitted merge'),
4945 cmdmsg=_(b'outstanding uncommitted merge'),
4945 abortfunc=hg.abortmerge,
4946 abortfunc=hg.abortmerge,
4946 statushint=_(
4947 statushint=_(
4947 b'To continue: hg commit\nTo abort: hg merge --abort'
4948 b'To continue: hg commit\nTo abort: hg merge --abort'
4948 ),
4949 ),
4949 cmdhint=_(b"use 'hg commit' or 'hg merge --abort'"),
4950 cmdhint=_(b"use 'hg commit' or 'hg merge --abort'"),
4950 )
4951 )
4951
4952
4952
4953
4953 @command(
4954 @command(
4954 b'outgoing|out',
4955 b'outgoing|out',
4955 [
4956 [
4956 (
4957 (
4957 b'f',
4958 b'f',
4958 b'force',
4959 b'force',
4959 None,
4960 None,
4960 _(b'run even when the destination is unrelated'),
4961 _(b'run even when the destination is unrelated'),
4961 ),
4962 ),
4962 (
4963 (
4963 b'r',
4964 b'r',
4964 b'rev',
4965 b'rev',
4965 [],
4966 [],
4966 _(b'a changeset intended to be included in the destination'),
4967 _(b'a changeset intended to be included in the destination'),
4967 _(b'REV'),
4968 _(b'REV'),
4968 ),
4969 ),
4969 (b'n', b'newest-first', None, _(b'show newest record first')),
4970 (b'n', b'newest-first', None, _(b'show newest record first')),
4970 (b'B', b'bookmarks', False, _(b'compare bookmarks')),
4971 (b'B', b'bookmarks', False, _(b'compare bookmarks')),
4971 (
4972 (
4972 b'b',
4973 b'b',
4973 b'branch',
4974 b'branch',
4974 [],
4975 [],
4975 _(b'a specific branch you would like to push'),
4976 _(b'a specific branch you would like to push'),
4976 _(b'BRANCH'),
4977 _(b'BRANCH'),
4977 ),
4978 ),
4978 ]
4979 ]
4979 + logopts
4980 + logopts
4980 + remoteopts
4981 + remoteopts
4981 + subrepoopts,
4982 + subrepoopts,
4982 _(b'[-M] [-p] [-n] [-f] [-r REV]... [DEST]'),
4983 _(b'[-M] [-p] [-n] [-f] [-r REV]... [DEST]'),
4983 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4984 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4984 )
4985 )
4985 def outgoing(ui, repo, dest=None, **opts):
4986 def outgoing(ui, repo, dest=None, **opts):
4986 """show changesets not found in the destination
4987 """show changesets not found in the destination
4987
4988
4988 Show changesets not found in the specified destination repository
4989 Show changesets not found in the specified destination repository
4989 or the default push location. These are the changesets that would
4990 or the default push location. These are the changesets that would
4990 be pushed if a push was requested.
4991 be pushed if a push was requested.
4991
4992
4992 See pull for details of valid destination formats.
4993 See pull for details of valid destination formats.
4993
4994
4994 .. container:: verbose
4995 .. container:: verbose
4995
4996
4996 With -B/--bookmarks, the result of bookmark comparison between
4997 With -B/--bookmarks, the result of bookmark comparison between
4997 local and remote repositories is displayed. With -v/--verbose,
4998 local and remote repositories is displayed. With -v/--verbose,
4998 status is also displayed for each bookmark like below::
4999 status is also displayed for each bookmark like below::
4999
5000
5000 BM1 01234567890a added
5001 BM1 01234567890a added
5001 BM2 deleted
5002 BM2 deleted
5002 BM3 234567890abc advanced
5003 BM3 234567890abc advanced
5003 BM4 34567890abcd diverged
5004 BM4 34567890abcd diverged
5004 BM5 4567890abcde changed
5005 BM5 4567890abcde changed
5005
5006
5006 The action taken when pushing depends on the
5007 The action taken when pushing depends on the
5007 status of each bookmark:
5008 status of each bookmark:
5008
5009
5009 :``added``: push with ``-B`` will create it
5010 :``added``: push with ``-B`` will create it
5010 :``deleted``: push with ``-B`` will delete it
5011 :``deleted``: push with ``-B`` will delete it
5011 :``advanced``: push will update it
5012 :``advanced``: push will update it
5012 :``diverged``: push with ``-B`` will update it
5013 :``diverged``: push with ``-B`` will update it
5013 :``changed``: push with ``-B`` will update it
5014 :``changed``: push with ``-B`` will update it
5014
5015
5015 From the point of view of pushing behavior, bookmarks
5016 From the point of view of pushing behavior, bookmarks
5016 existing only in the remote repository are treated as
5017 existing only in the remote repository are treated as
5017 ``deleted``, even if it is in fact added remotely.
5018 ``deleted``, even if it is in fact added remotely.
5018
5019
5019 Returns 0 if there are outgoing changes, 1 otherwise.
5020 Returns 0 if there are outgoing changes, 1 otherwise.
5020 """
5021 """
5021 # hg._outgoing() needs to re-resolve the path in order to handle #branch
5022 # hg._outgoing() needs to re-resolve the path in order to handle #branch
5022 # style URLs, so don't overwrite dest.
5023 # style URLs, so don't overwrite dest.
5023 path = ui.paths.getpath(dest, default=(b'default-push', b'default'))
5024 path = ui.paths.getpath(dest, default=(b'default-push', b'default'))
5024 if not path:
5025 if not path:
5025 raise error.Abort(
5026 raise error.Abort(
5026 _(b'default repository not configured!'),
5027 _(b'default repository not configured!'),
5027 hint=_(b"see 'hg help config.paths'"),
5028 hint=_(b"see 'hg help config.paths'"),
5028 )
5029 )
5029
5030
5030 opts = pycompat.byteskwargs(opts)
5031 opts = pycompat.byteskwargs(opts)
5031 if opts.get(b'graph'):
5032 if opts.get(b'graph'):
5032 logcmdutil.checkunsupportedgraphflags([], opts)
5033 logcmdutil.checkunsupportedgraphflags([], opts)
5033 o, other = hg._outgoing(ui, repo, dest, opts)
5034 o, other = hg._outgoing(ui, repo, dest, opts)
5034 if not o:
5035 if not o:
5035 cmdutil.outgoinghooks(ui, repo, other, opts, o)
5036 cmdutil.outgoinghooks(ui, repo, other, opts, o)
5036 return
5037 return
5037
5038
5038 revdag = logcmdutil.graphrevs(repo, o, opts)
5039 revdag = logcmdutil.graphrevs(repo, o, opts)
5039 ui.pager(b'outgoing')
5040 ui.pager(b'outgoing')
5040 displayer = logcmdutil.changesetdisplayer(ui, repo, opts, buffered=True)
5041 displayer = logcmdutil.changesetdisplayer(ui, repo, opts, buffered=True)
5041 logcmdutil.displaygraph(
5042 logcmdutil.displaygraph(
5042 ui, repo, revdag, displayer, graphmod.asciiedges
5043 ui, repo, revdag, displayer, graphmod.asciiedges
5043 )
5044 )
5044 cmdutil.outgoinghooks(ui, repo, other, opts, o)
5045 cmdutil.outgoinghooks(ui, repo, other, opts, o)
5045 return 0
5046 return 0
5046
5047
5047 if opts.get(b'bookmarks'):
5048 if opts.get(b'bookmarks'):
5048 dest = path.pushloc or path.loc
5049 dest = path.pushloc or path.loc
5049 other = hg.peer(repo, opts, dest)
5050 other = hg.peer(repo, opts, dest)
5050 if b'bookmarks' not in other.listkeys(b'namespaces'):
5051 if b'bookmarks' not in other.listkeys(b'namespaces'):
5051 ui.warn(_(b"remote doesn't support bookmarks\n"))
5052 ui.warn(_(b"remote doesn't support bookmarks\n"))
5052 return 0
5053 return 0
5053 ui.status(_(b'comparing with %s\n') % util.hidepassword(dest))
5054 ui.status(_(b'comparing with %s\n') % util.hidepassword(dest))
5054 ui.pager(b'outgoing')
5055 ui.pager(b'outgoing')
5055 return bookmarks.outgoing(ui, repo, other)
5056 return bookmarks.outgoing(ui, repo, other)
5056
5057
5057 repo._subtoppath = path.pushloc or path.loc
5058 repo._subtoppath = path.pushloc or path.loc
5058 try:
5059 try:
5059 return hg.outgoing(ui, repo, dest, opts)
5060 return hg.outgoing(ui, repo, dest, opts)
5060 finally:
5061 finally:
5061 del repo._subtoppath
5062 del repo._subtoppath
5062
5063
5063
5064
5064 @command(
5065 @command(
5065 b'parents',
5066 b'parents',
5066 [
5067 [
5067 (
5068 (
5068 b'r',
5069 b'r',
5069 b'rev',
5070 b'rev',
5070 b'',
5071 b'',
5071 _(b'show parents of the specified revision'),
5072 _(b'show parents of the specified revision'),
5072 _(b'REV'),
5073 _(b'REV'),
5073 ),
5074 ),
5074 ]
5075 ]
5075 + templateopts,
5076 + templateopts,
5076 _(b'[-r REV] [FILE]'),
5077 _(b'[-r REV] [FILE]'),
5077 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
5078 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
5078 inferrepo=True,
5079 inferrepo=True,
5079 )
5080 )
5080 def parents(ui, repo, file_=None, **opts):
5081 def parents(ui, repo, file_=None, **opts):
5081 """show the parents of the working directory or revision (DEPRECATED)
5082 """show the parents of the working directory or revision (DEPRECATED)
5082
5083
5083 Print the working directory's parent revisions. If a revision is
5084 Print the working directory's parent revisions. If a revision is
5084 given via -r/--rev, the parent of that revision will be printed.
5085 given via -r/--rev, the parent of that revision will be printed.
5085 If a file argument is given, the revision in which the file was
5086 If a file argument is given, the revision in which the file was
5086 last changed (before the working directory revision or the
5087 last changed (before the working directory revision or the
5087 argument to --rev if given) is printed.
5088 argument to --rev if given) is printed.
5088
5089
5089 This command is equivalent to::
5090 This command is equivalent to::
5090
5091
5091 hg log -r "p1()+p2()" or
5092 hg log -r "p1()+p2()" or
5092 hg log -r "p1(REV)+p2(REV)" or
5093 hg log -r "p1(REV)+p2(REV)" or
5093 hg log -r "max(::p1() and file(FILE))+max(::p2() and file(FILE))" or
5094 hg log -r "max(::p1() and file(FILE))+max(::p2() and file(FILE))" or
5094 hg log -r "max(::p1(REV) and file(FILE))+max(::p2(REV) and file(FILE))"
5095 hg log -r "max(::p1(REV) and file(FILE))+max(::p2(REV) and file(FILE))"
5095
5096
5096 See :hg:`summary` and :hg:`help revsets` for related information.
5097 See :hg:`summary` and :hg:`help revsets` for related information.
5097
5098
5098 Returns 0 on success.
5099 Returns 0 on success.
5099 """
5100 """
5100
5101
5101 opts = pycompat.byteskwargs(opts)
5102 opts = pycompat.byteskwargs(opts)
5102 rev = opts.get(b'rev')
5103 rev = opts.get(b'rev')
5103 if rev:
5104 if rev:
5104 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
5105 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
5105 ctx = scmutil.revsingle(repo, rev, None)
5106 ctx = scmutil.revsingle(repo, rev, None)
5106
5107
5107 if file_:
5108 if file_:
5108 m = scmutil.match(ctx, (file_,), opts)
5109 m = scmutil.match(ctx, (file_,), opts)
5109 if m.anypats() or len(m.files()) != 1:
5110 if m.anypats() or len(m.files()) != 1:
5110 raise error.Abort(_(b'can only specify an explicit filename'))
5111 raise error.Abort(_(b'can only specify an explicit filename'))
5111 file_ = m.files()[0]
5112 file_ = m.files()[0]
5112 filenodes = []
5113 filenodes = []
5113 for cp in ctx.parents():
5114 for cp in ctx.parents():
5114 if not cp:
5115 if not cp:
5115 continue
5116 continue
5116 try:
5117 try:
5117 filenodes.append(cp.filenode(file_))
5118 filenodes.append(cp.filenode(file_))
5118 except error.LookupError:
5119 except error.LookupError:
5119 pass
5120 pass
5120 if not filenodes:
5121 if not filenodes:
5121 raise error.Abort(_(b"'%s' not found in manifest!") % file_)
5122 raise error.Abort(_(b"'%s' not found in manifest!") % file_)
5122 p = []
5123 p = []
5123 for fn in filenodes:
5124 for fn in filenodes:
5124 fctx = repo.filectx(file_, fileid=fn)
5125 fctx = repo.filectx(file_, fileid=fn)
5125 p.append(fctx.node())
5126 p.append(fctx.node())
5126 else:
5127 else:
5127 p = [cp.node() for cp in ctx.parents()]
5128 p = [cp.node() for cp in ctx.parents()]
5128
5129
5129 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
5130 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
5130 for n in p:
5131 for n in p:
5131 if n != nullid:
5132 if n != nullid:
5132 displayer.show(repo[n])
5133 displayer.show(repo[n])
5133 displayer.close()
5134 displayer.close()
5134
5135
5135
5136
5136 @command(
5137 @command(
5137 b'paths',
5138 b'paths',
5138 formatteropts,
5139 formatteropts,
5139 _(b'[NAME]'),
5140 _(b'[NAME]'),
5140 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5141 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5141 optionalrepo=True,
5142 optionalrepo=True,
5142 intents={INTENT_READONLY},
5143 intents={INTENT_READONLY},
5143 )
5144 )
5144 def paths(ui, repo, search=None, **opts):
5145 def paths(ui, repo, search=None, **opts):
5145 """show aliases for remote repositories
5146 """show aliases for remote repositories
5146
5147
5147 Show definition of symbolic path name NAME. If no name is given,
5148 Show definition of symbolic path name NAME. If no name is given,
5148 show definition of all available names.
5149 show definition of all available names.
5149
5150
5150 Option -q/--quiet suppresses all output when searching for NAME
5151 Option -q/--quiet suppresses all output when searching for NAME
5151 and shows only the path names when listing all definitions.
5152 and shows only the path names when listing all definitions.
5152
5153
5153 Path names are defined in the [paths] section of your
5154 Path names are defined in the [paths] section of your
5154 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
5155 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
5155 repository, ``.hg/hgrc`` is used, too.
5156 repository, ``.hg/hgrc`` is used, too.
5156
5157
5157 The path names ``default`` and ``default-push`` have a special
5158 The path names ``default`` and ``default-push`` have a special
5158 meaning. When performing a push or pull operation, they are used
5159 meaning. When performing a push or pull operation, they are used
5159 as fallbacks if no location is specified on the command-line.
5160 as fallbacks if no location is specified on the command-line.
5160 When ``default-push`` is set, it will be used for push and
5161 When ``default-push`` is set, it will be used for push and
5161 ``default`` will be used for pull; otherwise ``default`` is used
5162 ``default`` will be used for pull; otherwise ``default`` is used
5162 as the fallback for both. When cloning a repository, the clone
5163 as the fallback for both. When cloning a repository, the clone
5163 source is written as ``default`` in ``.hg/hgrc``.
5164 source is written as ``default`` in ``.hg/hgrc``.
5164
5165
5165 .. note::
5166 .. note::
5166
5167
5167 ``default`` and ``default-push`` apply to all inbound (e.g.
5168 ``default`` and ``default-push`` apply to all inbound (e.g.
5168 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email`
5169 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email`
5169 and :hg:`bundle`) operations.
5170 and :hg:`bundle`) operations.
5170
5171
5171 See :hg:`help urls` for more information.
5172 See :hg:`help urls` for more information.
5172
5173
5173 .. container:: verbose
5174 .. container:: verbose
5174
5175
5175 Template:
5176 Template:
5176
5177
5177 The following keywords are supported. See also :hg:`help templates`.
5178 The following keywords are supported. See also :hg:`help templates`.
5178
5179
5179 :name: String. Symbolic name of the path alias.
5180 :name: String. Symbolic name of the path alias.
5180 :pushurl: String. URL for push operations.
5181 :pushurl: String. URL for push operations.
5181 :url: String. URL or directory path for the other operations.
5182 :url: String. URL or directory path for the other operations.
5182
5183
5183 Returns 0 on success.
5184 Returns 0 on success.
5184 """
5185 """
5185
5186
5186 opts = pycompat.byteskwargs(opts)
5187 opts = pycompat.byteskwargs(opts)
5187 ui.pager(b'paths')
5188 ui.pager(b'paths')
5188 if search:
5189 if search:
5189 pathitems = [
5190 pathitems = [
5190 (name, path)
5191 (name, path)
5191 for name, path in pycompat.iteritems(ui.paths)
5192 for name, path in pycompat.iteritems(ui.paths)
5192 if name == search
5193 if name == search
5193 ]
5194 ]
5194 else:
5195 else:
5195 pathitems = sorted(pycompat.iteritems(ui.paths))
5196 pathitems = sorted(pycompat.iteritems(ui.paths))
5196
5197
5197 fm = ui.formatter(b'paths', opts)
5198 fm = ui.formatter(b'paths', opts)
5198 if fm.isplain():
5199 if fm.isplain():
5199 hidepassword = util.hidepassword
5200 hidepassword = util.hidepassword
5200 else:
5201 else:
5201 hidepassword = bytes
5202 hidepassword = bytes
5202 if ui.quiet:
5203 if ui.quiet:
5203 namefmt = b'%s\n'
5204 namefmt = b'%s\n'
5204 else:
5205 else:
5205 namefmt = b'%s = '
5206 namefmt = b'%s = '
5206 showsubopts = not search and not ui.quiet
5207 showsubopts = not search and not ui.quiet
5207
5208
5208 for name, path in pathitems:
5209 for name, path in pathitems:
5209 fm.startitem()
5210 fm.startitem()
5210 fm.condwrite(not search, b'name', namefmt, name)
5211 fm.condwrite(not search, b'name', namefmt, name)
5211 fm.condwrite(not ui.quiet, b'url', b'%s\n', hidepassword(path.rawloc))
5212 fm.condwrite(not ui.quiet, b'url', b'%s\n', hidepassword(path.rawloc))
5212 for subopt, value in sorted(path.suboptions.items()):
5213 for subopt, value in sorted(path.suboptions.items()):
5213 assert subopt not in (b'name', b'url')
5214 assert subopt not in (b'name', b'url')
5214 if showsubopts:
5215 if showsubopts:
5215 fm.plain(b'%s:%s = ' % (name, subopt))
5216 fm.plain(b'%s:%s = ' % (name, subopt))
5216 fm.condwrite(showsubopts, subopt, b'%s\n', value)
5217 fm.condwrite(showsubopts, subopt, b'%s\n', value)
5217
5218
5218 fm.end()
5219 fm.end()
5219
5220
5220 if search and not pathitems:
5221 if search and not pathitems:
5221 if not ui.quiet:
5222 if not ui.quiet:
5222 ui.warn(_(b"not found!\n"))
5223 ui.warn(_(b"not found!\n"))
5223 return 1
5224 return 1
5224 else:
5225 else:
5225 return 0
5226 return 0
5226
5227
5227
5228
5228 @command(
5229 @command(
5229 b'phase',
5230 b'phase',
5230 [
5231 [
5231 (b'p', b'public', False, _(b'set changeset phase to public')),
5232 (b'p', b'public', False, _(b'set changeset phase to public')),
5232 (b'd', b'draft', False, _(b'set changeset phase to draft')),
5233 (b'd', b'draft', False, _(b'set changeset phase to draft')),
5233 (b's', b'secret', False, _(b'set changeset phase to secret')),
5234 (b's', b'secret', False, _(b'set changeset phase to secret')),
5234 (b'f', b'force', False, _(b'allow to move boundary backward')),
5235 (b'f', b'force', False, _(b'allow to move boundary backward')),
5235 (b'r', b'rev', [], _(b'target revision'), _(b'REV')),
5236 (b'r', b'rev', [], _(b'target revision'), _(b'REV')),
5236 ],
5237 ],
5237 _(b'[-p|-d|-s] [-f] [-r] [REV...]'),
5238 _(b'[-p|-d|-s] [-f] [-r] [REV...]'),
5238 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
5239 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
5239 )
5240 )
5240 def phase(ui, repo, *revs, **opts):
5241 def phase(ui, repo, *revs, **opts):
5241 """set or show the current phase name
5242 """set or show the current phase name
5242
5243
5243 With no argument, show the phase name of the current revision(s).
5244 With no argument, show the phase name of the current revision(s).
5244
5245
5245 With one of -p/--public, -d/--draft or -s/--secret, change the
5246 With one of -p/--public, -d/--draft or -s/--secret, change the
5246 phase value of the specified revisions.
5247 phase value of the specified revisions.
5247
5248
5248 Unless -f/--force is specified, :hg:`phase` won't move changesets from a
5249 Unless -f/--force is specified, :hg:`phase` won't move changesets from a
5249 lower phase to a higher phase. Phases are ordered as follows::
5250 lower phase to a higher phase. Phases are ordered as follows::
5250
5251
5251 public < draft < secret
5252 public < draft < secret
5252
5253
5253 Returns 0 on success, 1 if some phases could not be changed.
5254 Returns 0 on success, 1 if some phases could not be changed.
5254
5255
5255 (For more information about the phases concept, see :hg:`help phases`.)
5256 (For more information about the phases concept, see :hg:`help phases`.)
5256 """
5257 """
5257 opts = pycompat.byteskwargs(opts)
5258 opts = pycompat.byteskwargs(opts)
5258 # search for a unique phase argument
5259 # search for a unique phase argument
5259 targetphase = None
5260 targetphase = None
5260 for idx, name in enumerate(phases.cmdphasenames):
5261 for idx, name in enumerate(phases.cmdphasenames):
5261 if opts[name]:
5262 if opts[name]:
5262 if targetphase is not None:
5263 if targetphase is not None:
5263 raise error.Abort(_(b'only one phase can be specified'))
5264 raise error.Abort(_(b'only one phase can be specified'))
5264 targetphase = idx
5265 targetphase = idx
5265
5266
5266 # look for specified revision
5267 # look for specified revision
5267 revs = list(revs)
5268 revs = list(revs)
5268 revs.extend(opts[b'rev'])
5269 revs.extend(opts[b'rev'])
5269 if not revs:
5270 if not revs:
5270 # display both parents as the second parent phase can influence
5271 # display both parents as the second parent phase can influence
5271 # the phase of a merge commit
5272 # the phase of a merge commit
5272 revs = [c.rev() for c in repo[None].parents()]
5273 revs = [c.rev() for c in repo[None].parents()]
5273
5274
5274 revs = scmutil.revrange(repo, revs)
5275 revs = scmutil.revrange(repo, revs)
5275
5276
5276 ret = 0
5277 ret = 0
5277 if targetphase is None:
5278 if targetphase is None:
5278 # display
5279 # display
5279 for r in revs:
5280 for r in revs:
5280 ctx = repo[r]
5281 ctx = repo[r]
5281 ui.write(b'%i: %s\n' % (ctx.rev(), ctx.phasestr()))
5282 ui.write(b'%i: %s\n' % (ctx.rev(), ctx.phasestr()))
5282 else:
5283 else:
5283 with repo.lock(), repo.transaction(b"phase") as tr:
5284 with repo.lock(), repo.transaction(b"phase") as tr:
5284 # set phase
5285 # set phase
5285 if not revs:
5286 if not revs:
5286 raise error.Abort(_(b'empty revision set'))
5287 raise error.Abort(_(b'empty revision set'))
5287 nodes = [repo[r].node() for r in revs]
5288 nodes = [repo[r].node() for r in revs]
5288 # moving revision from public to draft may hide them
5289 # moving revision from public to draft may hide them
5289 # We have to check result on an unfiltered repository
5290 # We have to check result on an unfiltered repository
5290 unfi = repo.unfiltered()
5291 unfi = repo.unfiltered()
5291 getphase = unfi._phasecache.phase
5292 getphase = unfi._phasecache.phase
5292 olddata = [getphase(unfi, r) for r in unfi]
5293 olddata = [getphase(unfi, r) for r in unfi]
5293 phases.advanceboundary(repo, tr, targetphase, nodes)
5294 phases.advanceboundary(repo, tr, targetphase, nodes)
5294 if opts[b'force']:
5295 if opts[b'force']:
5295 phases.retractboundary(repo, tr, targetphase, nodes)
5296 phases.retractboundary(repo, tr, targetphase, nodes)
5296 getphase = unfi._phasecache.phase
5297 getphase = unfi._phasecache.phase
5297 newdata = [getphase(unfi, r) for r in unfi]
5298 newdata = [getphase(unfi, r) for r in unfi]
5298 changes = sum(newdata[r] != olddata[r] for r in unfi)
5299 changes = sum(newdata[r] != olddata[r] for r in unfi)
5299 cl = unfi.changelog
5300 cl = unfi.changelog
5300 rejected = [n for n in nodes if newdata[cl.rev(n)] < targetphase]
5301 rejected = [n for n in nodes if newdata[cl.rev(n)] < targetphase]
5301 if rejected:
5302 if rejected:
5302 ui.warn(
5303 ui.warn(
5303 _(
5304 _(
5304 b'cannot move %i changesets to a higher '
5305 b'cannot move %i changesets to a higher '
5305 b'phase, use --force\n'
5306 b'phase, use --force\n'
5306 )
5307 )
5307 % len(rejected)
5308 % len(rejected)
5308 )
5309 )
5309 ret = 1
5310 ret = 1
5310 if changes:
5311 if changes:
5311 msg = _(b'phase changed for %i changesets\n') % changes
5312 msg = _(b'phase changed for %i changesets\n') % changes
5312 if ret:
5313 if ret:
5313 ui.status(msg)
5314 ui.status(msg)
5314 else:
5315 else:
5315 ui.note(msg)
5316 ui.note(msg)
5316 else:
5317 else:
5317 ui.warn(_(b'no phases changed\n'))
5318 ui.warn(_(b'no phases changed\n'))
5318 return ret
5319 return ret
5319
5320
5320
5321
5321 def postincoming(ui, repo, modheads, optupdate, checkout, brev):
5322 def postincoming(ui, repo, modheads, optupdate, checkout, brev):
5322 """Run after a changegroup has been added via pull/unbundle
5323 """Run after a changegroup has been added via pull/unbundle
5323
5324
5324 This takes arguments below:
5325 This takes arguments below:
5325
5326
5326 :modheads: change of heads by pull/unbundle
5327 :modheads: change of heads by pull/unbundle
5327 :optupdate: updating working directory is needed or not
5328 :optupdate: updating working directory is needed or not
5328 :checkout: update destination revision (or None to default destination)
5329 :checkout: update destination revision (or None to default destination)
5329 :brev: a name, which might be a bookmark to be activated after updating
5330 :brev: a name, which might be a bookmark to be activated after updating
5330 """
5331 """
5331 if modheads == 0:
5332 if modheads == 0:
5332 return
5333 return
5333 if optupdate:
5334 if optupdate:
5334 try:
5335 try:
5335 return hg.updatetotally(ui, repo, checkout, brev)
5336 return hg.updatetotally(ui, repo, checkout, brev)
5336 except error.UpdateAbort as inst:
5337 except error.UpdateAbort as inst:
5337 msg = _(b"not updating: %s") % stringutil.forcebytestr(inst)
5338 msg = _(b"not updating: %s") % stringutil.forcebytestr(inst)
5338 hint = inst.hint
5339 hint = inst.hint
5339 raise error.UpdateAbort(msg, hint=hint)
5340 raise error.UpdateAbort(msg, hint=hint)
5340 if modheads is not None and modheads > 1:
5341 if modheads is not None and modheads > 1:
5341 currentbranchheads = len(repo.branchheads())
5342 currentbranchheads = len(repo.branchheads())
5342 if currentbranchheads == modheads:
5343 if currentbranchheads == modheads:
5343 ui.status(
5344 ui.status(
5344 _(b"(run 'hg heads' to see heads, 'hg merge' to merge)\n")
5345 _(b"(run 'hg heads' to see heads, 'hg merge' to merge)\n")
5345 )
5346 )
5346 elif currentbranchheads > 1:
5347 elif currentbranchheads > 1:
5347 ui.status(
5348 ui.status(
5348 _(b"(run 'hg heads .' to see heads, 'hg merge' to merge)\n")
5349 _(b"(run 'hg heads .' to see heads, 'hg merge' to merge)\n")
5349 )
5350 )
5350 else:
5351 else:
5351 ui.status(_(b"(run 'hg heads' to see heads)\n"))
5352 ui.status(_(b"(run 'hg heads' to see heads)\n"))
5352 elif not ui.configbool(b'commands', b'update.requiredest'):
5353 elif not ui.configbool(b'commands', b'update.requiredest'):
5353 ui.status(_(b"(run 'hg update' to get a working copy)\n"))
5354 ui.status(_(b"(run 'hg update' to get a working copy)\n"))
5354
5355
5355
5356
5356 @command(
5357 @command(
5357 b'pull',
5358 b'pull',
5358 [
5359 [
5359 (
5360 (
5360 b'u',
5361 b'u',
5361 b'update',
5362 b'update',
5362 None,
5363 None,
5363 _(b'update to new branch head if new descendants were pulled'),
5364 _(b'update to new branch head if new descendants were pulled'),
5364 ),
5365 ),
5365 (
5366 (
5366 b'f',
5367 b'f',
5367 b'force',
5368 b'force',
5368 None,
5369 None,
5369 _(b'run even when remote repository is unrelated'),
5370 _(b'run even when remote repository is unrelated'),
5370 ),
5371 ),
5371 (b'', b'confirm', None, _(b'confirm pull before applying changes'),),
5372 (b'', b'confirm', None, _(b'confirm pull before applying changes'),),
5372 (
5373 (
5373 b'r',
5374 b'r',
5374 b'rev',
5375 b'rev',
5375 [],
5376 [],
5376 _(b'a remote changeset intended to be added'),
5377 _(b'a remote changeset intended to be added'),
5377 _(b'REV'),
5378 _(b'REV'),
5378 ),
5379 ),
5379 (b'B', b'bookmark', [], _(b"bookmark to pull"), _(b'BOOKMARK')),
5380 (b'B', b'bookmark', [], _(b"bookmark to pull"), _(b'BOOKMARK')),
5380 (
5381 (
5381 b'b',
5382 b'b',
5382 b'branch',
5383 b'branch',
5383 [],
5384 [],
5384 _(b'a specific branch you would like to pull'),
5385 _(b'a specific branch you would like to pull'),
5385 _(b'BRANCH'),
5386 _(b'BRANCH'),
5386 ),
5387 ),
5387 ]
5388 ]
5388 + remoteopts,
5389 + remoteopts,
5389 _(b'[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'),
5390 _(b'[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'),
5390 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5391 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5391 helpbasic=True,
5392 helpbasic=True,
5392 )
5393 )
5393 def pull(ui, repo, source=b"default", **opts):
5394 def pull(ui, repo, source=b"default", **opts):
5394 """pull changes from the specified source
5395 """pull changes from the specified source
5395
5396
5396 Pull changes from a remote repository to a local one.
5397 Pull changes from a remote repository to a local one.
5397
5398
5398 This finds all changes from the repository at the specified path
5399 This finds all changes from the repository at the specified path
5399 or URL and adds them to a local repository (the current one unless
5400 or URL and adds them to a local repository (the current one unless
5400 -R is specified). By default, this does not update the copy of the
5401 -R is specified). By default, this does not update the copy of the
5401 project in the working directory.
5402 project in the working directory.
5402
5403
5403 When cloning from servers that support it, Mercurial may fetch
5404 When cloning from servers that support it, Mercurial may fetch
5404 pre-generated data. When this is done, hooks operating on incoming
5405 pre-generated data. When this is done, hooks operating on incoming
5405 changesets and changegroups may fire more than once, once for each
5406 changesets and changegroups may fire more than once, once for each
5406 pre-generated bundle and as well as for any additional remaining
5407 pre-generated bundle and as well as for any additional remaining
5407 data. See :hg:`help -e clonebundles` for more.
5408 data. See :hg:`help -e clonebundles` for more.
5408
5409
5409 Use :hg:`incoming` if you want to see what would have been added
5410 Use :hg:`incoming` if you want to see what would have been added
5410 by a pull at the time you issued this command. If you then decide
5411 by a pull at the time you issued this command. If you then decide
5411 to add those changes to the repository, you should use :hg:`pull
5412 to add those changes to the repository, you should use :hg:`pull
5412 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
5413 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
5413
5414
5414 If SOURCE is omitted, the 'default' path will be used.
5415 If SOURCE is omitted, the 'default' path will be used.
5415 See :hg:`help urls` for more information.
5416 See :hg:`help urls` for more information.
5416
5417
5417 Specifying bookmark as ``.`` is equivalent to specifying the active
5418 Specifying bookmark as ``.`` is equivalent to specifying the active
5418 bookmark's name.
5419 bookmark's name.
5419
5420
5420 Returns 0 on success, 1 if an update had unresolved files.
5421 Returns 0 on success, 1 if an update had unresolved files.
5421 """
5422 """
5422
5423
5423 opts = pycompat.byteskwargs(opts)
5424 opts = pycompat.byteskwargs(opts)
5424 if ui.configbool(b'commands', b'update.requiredest') and opts.get(
5425 if ui.configbool(b'commands', b'update.requiredest') and opts.get(
5425 b'update'
5426 b'update'
5426 ):
5427 ):
5427 msg = _(b'update destination required by configuration')
5428 msg = _(b'update destination required by configuration')
5428 hint = _(b'use hg pull followed by hg update DEST')
5429 hint = _(b'use hg pull followed by hg update DEST')
5429 raise error.Abort(msg, hint=hint)
5430 raise error.Abort(msg, hint=hint)
5430
5431
5431 source, branches = hg.parseurl(ui.expandpath(source), opts.get(b'branch'))
5432 source, branches = hg.parseurl(ui.expandpath(source), opts.get(b'branch'))
5432 ui.status(_(b'pulling from %s\n') % util.hidepassword(source))
5433 ui.status(_(b'pulling from %s\n') % util.hidepassword(source))
5433 other = hg.peer(repo, opts, source)
5434 other = hg.peer(repo, opts, source)
5434 try:
5435 try:
5435 revs, checkout = hg.addbranchrevs(
5436 revs, checkout = hg.addbranchrevs(
5436 repo, other, branches, opts.get(b'rev')
5437 repo, other, branches, opts.get(b'rev')
5437 )
5438 )
5438
5439
5439 pullopargs = {}
5440 pullopargs = {}
5440
5441
5441 nodes = None
5442 nodes = None
5442 if opts.get(b'bookmark') or revs:
5443 if opts.get(b'bookmark') or revs:
5443 # The list of bookmark used here is the same used to actually update
5444 # The list of bookmark used here is the same used to actually update
5444 # the bookmark names, to avoid the race from issue 4689 and we do
5445 # the bookmark names, to avoid the race from issue 4689 and we do
5445 # all lookup and bookmark queries in one go so they see the same
5446 # all lookup and bookmark queries in one go so they see the same
5446 # version of the server state (issue 4700).
5447 # version of the server state (issue 4700).
5447 nodes = []
5448 nodes = []
5448 fnodes = []
5449 fnodes = []
5449 revs = revs or []
5450 revs = revs or []
5450 if revs and not other.capable(b'lookup'):
5451 if revs and not other.capable(b'lookup'):
5451 err = _(
5452 err = _(
5452 b"other repository doesn't support revision lookup, "
5453 b"other repository doesn't support revision lookup, "
5453 b"so a rev cannot be specified."
5454 b"so a rev cannot be specified."
5454 )
5455 )
5455 raise error.Abort(err)
5456 raise error.Abort(err)
5456 with other.commandexecutor() as e:
5457 with other.commandexecutor() as e:
5457 fremotebookmarks = e.callcommand(
5458 fremotebookmarks = e.callcommand(
5458 b'listkeys', {b'namespace': b'bookmarks'}
5459 b'listkeys', {b'namespace': b'bookmarks'}
5459 )
5460 )
5460 for r in revs:
5461 for r in revs:
5461 fnodes.append(e.callcommand(b'lookup', {b'key': r}))
5462 fnodes.append(e.callcommand(b'lookup', {b'key': r}))
5462 remotebookmarks = fremotebookmarks.result()
5463 remotebookmarks = fremotebookmarks.result()
5463 remotebookmarks = bookmarks.unhexlifybookmarks(remotebookmarks)
5464 remotebookmarks = bookmarks.unhexlifybookmarks(remotebookmarks)
5464 pullopargs[b'remotebookmarks'] = remotebookmarks
5465 pullopargs[b'remotebookmarks'] = remotebookmarks
5465 for b in opts.get(b'bookmark', []):
5466 for b in opts.get(b'bookmark', []):
5466 b = repo._bookmarks.expandname(b)
5467 b = repo._bookmarks.expandname(b)
5467 if b not in remotebookmarks:
5468 if b not in remotebookmarks:
5468 raise error.Abort(_(b'remote bookmark %s not found!') % b)
5469 raise error.Abort(_(b'remote bookmark %s not found!') % b)
5469 nodes.append(remotebookmarks[b])
5470 nodes.append(remotebookmarks[b])
5470 for i, rev in enumerate(revs):
5471 for i, rev in enumerate(revs):
5471 node = fnodes[i].result()
5472 node = fnodes[i].result()
5472 nodes.append(node)
5473 nodes.append(node)
5473 if rev == checkout:
5474 if rev == checkout:
5474 checkout = node
5475 checkout = node
5475
5476
5476 wlock = util.nullcontextmanager()
5477 wlock = util.nullcontextmanager()
5477 if opts.get(b'update'):
5478 if opts.get(b'update'):
5478 wlock = repo.wlock()
5479 wlock = repo.wlock()
5479 with wlock:
5480 with wlock:
5480 pullopargs.update(opts.get(b'opargs', {}))
5481 pullopargs.update(opts.get(b'opargs', {}))
5481 modheads = exchange.pull(
5482 modheads = exchange.pull(
5482 repo,
5483 repo,
5483 other,
5484 other,
5484 heads=nodes,
5485 heads=nodes,
5485 force=opts.get(b'force'),
5486 force=opts.get(b'force'),
5486 bookmarks=opts.get(b'bookmark', ()),
5487 bookmarks=opts.get(b'bookmark', ()),
5487 opargs=pullopargs,
5488 opargs=pullopargs,
5488 confirm=opts.get(b'confirm'),
5489 confirm=opts.get(b'confirm'),
5489 ).cgresult
5490 ).cgresult
5490
5491
5491 # brev is a name, which might be a bookmark to be activated at
5492 # brev is a name, which might be a bookmark to be activated at
5492 # the end of the update. In other words, it is an explicit
5493 # the end of the update. In other words, it is an explicit
5493 # destination of the update
5494 # destination of the update
5494 brev = None
5495 brev = None
5495
5496
5496 if checkout:
5497 if checkout:
5497 checkout = repo.unfiltered().changelog.rev(checkout)
5498 checkout = repo.unfiltered().changelog.rev(checkout)
5498
5499
5499 # order below depends on implementation of
5500 # order below depends on implementation of
5500 # hg.addbranchrevs(). opts['bookmark'] is ignored,
5501 # hg.addbranchrevs(). opts['bookmark'] is ignored,
5501 # because 'checkout' is determined without it.
5502 # because 'checkout' is determined without it.
5502 if opts.get(b'rev'):
5503 if opts.get(b'rev'):
5503 brev = opts[b'rev'][0]
5504 brev = opts[b'rev'][0]
5504 elif opts.get(b'branch'):
5505 elif opts.get(b'branch'):
5505 brev = opts[b'branch'][0]
5506 brev = opts[b'branch'][0]
5506 else:
5507 else:
5507 brev = branches[0]
5508 brev = branches[0]
5508 repo._subtoppath = source
5509 repo._subtoppath = source
5509 try:
5510 try:
5510 ret = postincoming(
5511 ret = postincoming(
5511 ui, repo, modheads, opts.get(b'update'), checkout, brev
5512 ui, repo, modheads, opts.get(b'update'), checkout, brev
5512 )
5513 )
5513 except error.FilteredRepoLookupError as exc:
5514 except error.FilteredRepoLookupError as exc:
5514 msg = _(b'cannot update to target: %s') % exc.args[0]
5515 msg = _(b'cannot update to target: %s') % exc.args[0]
5515 exc.args = (msg,) + exc.args[1:]
5516 exc.args = (msg,) + exc.args[1:]
5516 raise
5517 raise
5517 finally:
5518 finally:
5518 del repo._subtoppath
5519 del repo._subtoppath
5519
5520
5520 finally:
5521 finally:
5521 other.close()
5522 other.close()
5522 return ret
5523 return ret
5523
5524
5524
5525
5525 @command(
5526 @command(
5526 b'push',
5527 b'push',
5527 [
5528 [
5528 (b'f', b'force', None, _(b'force push')),
5529 (b'f', b'force', None, _(b'force push')),
5529 (
5530 (
5530 b'r',
5531 b'r',
5531 b'rev',
5532 b'rev',
5532 [],
5533 [],
5533 _(b'a changeset intended to be included in the destination'),
5534 _(b'a changeset intended to be included in the destination'),
5534 _(b'REV'),
5535 _(b'REV'),
5535 ),
5536 ),
5536 (b'B', b'bookmark', [], _(b"bookmark to push"), _(b'BOOKMARK')),
5537 (b'B', b'bookmark', [], _(b"bookmark to push"), _(b'BOOKMARK')),
5537 (
5538 (
5538 b'b',
5539 b'b',
5539 b'branch',
5540 b'branch',
5540 [],
5541 [],
5541 _(b'a specific branch you would like to push'),
5542 _(b'a specific branch you would like to push'),
5542 _(b'BRANCH'),
5543 _(b'BRANCH'),
5543 ),
5544 ),
5544 (b'', b'new-branch', False, _(b'allow pushing a new branch')),
5545 (b'', b'new-branch', False, _(b'allow pushing a new branch')),
5545 (
5546 (
5546 b'',
5547 b'',
5547 b'pushvars',
5548 b'pushvars',
5548 [],
5549 [],
5549 _(b'variables that can be sent to server (ADVANCED)'),
5550 _(b'variables that can be sent to server (ADVANCED)'),
5550 ),
5551 ),
5551 (
5552 (
5552 b'',
5553 b'',
5553 b'publish',
5554 b'publish',
5554 False,
5555 False,
5555 _(b'push the changeset as public (EXPERIMENTAL)'),
5556 _(b'push the changeset as public (EXPERIMENTAL)'),
5556 ),
5557 ),
5557 ]
5558 ]
5558 + remoteopts,
5559 + remoteopts,
5559 _(b'[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'),
5560 _(b'[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'),
5560 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5561 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5561 helpbasic=True,
5562 helpbasic=True,
5562 )
5563 )
5563 def push(ui, repo, dest=None, **opts):
5564 def push(ui, repo, dest=None, **opts):
5564 """push changes to the specified destination
5565 """push changes to the specified destination
5565
5566
5566 Push changesets from the local repository to the specified
5567 Push changesets from the local repository to the specified
5567 destination.
5568 destination.
5568
5569
5569 This operation is symmetrical to pull: it is identical to a pull
5570 This operation is symmetrical to pull: it is identical to a pull
5570 in the destination repository from the current one.
5571 in the destination repository from the current one.
5571
5572
5572 By default, push will not allow creation of new heads at the
5573 By default, push will not allow creation of new heads at the
5573 destination, since multiple heads would make it unclear which head
5574 destination, since multiple heads would make it unclear which head
5574 to use. In this situation, it is recommended to pull and merge
5575 to use. In this situation, it is recommended to pull and merge
5575 before pushing.
5576 before pushing.
5576
5577
5577 Use --new-branch if you want to allow push to create a new named
5578 Use --new-branch if you want to allow push to create a new named
5578 branch that is not present at the destination. This allows you to
5579 branch that is not present at the destination. This allows you to
5579 only create a new branch without forcing other changes.
5580 only create a new branch without forcing other changes.
5580
5581
5581 .. note::
5582 .. note::
5582
5583
5583 Extra care should be taken with the -f/--force option,
5584 Extra care should be taken with the -f/--force option,
5584 which will push all new heads on all branches, an action which will
5585 which will push all new heads on all branches, an action which will
5585 almost always cause confusion for collaborators.
5586 almost always cause confusion for collaborators.
5586
5587
5587 If -r/--rev is used, the specified revision and all its ancestors
5588 If -r/--rev is used, the specified revision and all its ancestors
5588 will be pushed to the remote repository.
5589 will be pushed to the remote repository.
5589
5590
5590 If -B/--bookmark is used, the specified bookmarked revision, its
5591 If -B/--bookmark is used, the specified bookmarked revision, its
5591 ancestors, and the bookmark will be pushed to the remote
5592 ancestors, and the bookmark will be pushed to the remote
5592 repository. Specifying ``.`` is equivalent to specifying the active
5593 repository. Specifying ``.`` is equivalent to specifying the active
5593 bookmark's name.
5594 bookmark's name.
5594
5595
5595 Please see :hg:`help urls` for important details about ``ssh://``
5596 Please see :hg:`help urls` for important details about ``ssh://``
5596 URLs. If DESTINATION is omitted, a default path will be used.
5597 URLs. If DESTINATION is omitted, a default path will be used.
5597
5598
5598 .. container:: verbose
5599 .. container:: verbose
5599
5600
5600 The --pushvars option sends strings to the server that become
5601 The --pushvars option sends strings to the server that become
5601 environment variables prepended with ``HG_USERVAR_``. For example,
5602 environment variables prepended with ``HG_USERVAR_``. For example,
5602 ``--pushvars ENABLE_FEATURE=true``, provides the server side hooks with
5603 ``--pushvars ENABLE_FEATURE=true``, provides the server side hooks with
5603 ``HG_USERVAR_ENABLE_FEATURE=true`` as part of their environment.
5604 ``HG_USERVAR_ENABLE_FEATURE=true`` as part of their environment.
5604
5605
5605 pushvars can provide for user-overridable hooks as well as set debug
5606 pushvars can provide for user-overridable hooks as well as set debug
5606 levels. One example is having a hook that blocks commits containing
5607 levels. One example is having a hook that blocks commits containing
5607 conflict markers, but enables the user to override the hook if the file
5608 conflict markers, but enables the user to override the hook if the file
5608 is using conflict markers for testing purposes or the file format has
5609 is using conflict markers for testing purposes or the file format has
5609 strings that look like conflict markers.
5610 strings that look like conflict markers.
5610
5611
5611 By default, servers will ignore `--pushvars`. To enable it add the
5612 By default, servers will ignore `--pushvars`. To enable it add the
5612 following to your configuration file::
5613 following to your configuration file::
5613
5614
5614 [push]
5615 [push]
5615 pushvars.server = true
5616 pushvars.server = true
5616
5617
5617 Returns 0 if push was successful, 1 if nothing to push.
5618 Returns 0 if push was successful, 1 if nothing to push.
5618 """
5619 """
5619
5620
5620 opts = pycompat.byteskwargs(opts)
5621 opts = pycompat.byteskwargs(opts)
5621 if opts.get(b'bookmark'):
5622 if opts.get(b'bookmark'):
5622 ui.setconfig(b'bookmarks', b'pushing', opts[b'bookmark'], b'push')
5623 ui.setconfig(b'bookmarks', b'pushing', opts[b'bookmark'], b'push')
5623 for b in opts[b'bookmark']:
5624 for b in opts[b'bookmark']:
5624 # translate -B options to -r so changesets get pushed
5625 # translate -B options to -r so changesets get pushed
5625 b = repo._bookmarks.expandname(b)
5626 b = repo._bookmarks.expandname(b)
5626 if b in repo._bookmarks:
5627 if b in repo._bookmarks:
5627 opts.setdefault(b'rev', []).append(b)
5628 opts.setdefault(b'rev', []).append(b)
5628 else:
5629 else:
5629 # if we try to push a deleted bookmark, translate it to null
5630 # if we try to push a deleted bookmark, translate it to null
5630 # this lets simultaneous -r, -b options continue working
5631 # this lets simultaneous -r, -b options continue working
5631 opts.setdefault(b'rev', []).append(b"null")
5632 opts.setdefault(b'rev', []).append(b"null")
5632
5633
5633 path = ui.paths.getpath(dest, default=(b'default-push', b'default'))
5634 path = ui.paths.getpath(dest, default=(b'default-push', b'default'))
5634 if not path:
5635 if not path:
5635 raise error.Abort(
5636 raise error.Abort(
5636 _(b'default repository not configured!'),
5637 _(b'default repository not configured!'),
5637 hint=_(b"see 'hg help config.paths'"),
5638 hint=_(b"see 'hg help config.paths'"),
5638 )
5639 )
5639 dest = path.pushloc or path.loc
5640 dest = path.pushloc or path.loc
5640 branches = (path.branch, opts.get(b'branch') or [])
5641 branches = (path.branch, opts.get(b'branch') or [])
5641 ui.status(_(b'pushing to %s\n') % util.hidepassword(dest))
5642 ui.status(_(b'pushing to %s\n') % util.hidepassword(dest))
5642 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get(b'rev'))
5643 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get(b'rev'))
5643 other = hg.peer(repo, opts, dest)
5644 other = hg.peer(repo, opts, dest)
5644
5645
5645 if revs:
5646 if revs:
5646 revs = [repo[r].node() for r in scmutil.revrange(repo, revs)]
5647 revs = [repo[r].node() for r in scmutil.revrange(repo, revs)]
5647 if not revs:
5648 if not revs:
5648 raise error.Abort(
5649 raise error.Abort(
5649 _(b"specified revisions evaluate to an empty set"),
5650 _(b"specified revisions evaluate to an empty set"),
5650 hint=_(b"use different revision arguments"),
5651 hint=_(b"use different revision arguments"),
5651 )
5652 )
5652 elif path.pushrev:
5653 elif path.pushrev:
5653 # It doesn't make any sense to specify ancestor revisions. So limit
5654 # It doesn't make any sense to specify ancestor revisions. So limit
5654 # to DAG heads to make discovery simpler.
5655 # to DAG heads to make discovery simpler.
5655 expr = revsetlang.formatspec(b'heads(%r)', path.pushrev)
5656 expr = revsetlang.formatspec(b'heads(%r)', path.pushrev)
5656 revs = scmutil.revrange(repo, [expr])
5657 revs = scmutil.revrange(repo, [expr])
5657 revs = [repo[rev].node() for rev in revs]
5658 revs = [repo[rev].node() for rev in revs]
5658 if not revs:
5659 if not revs:
5659 raise error.Abort(
5660 raise error.Abort(
5660 _(b'default push revset for path evaluates to an empty set')
5661 _(b'default push revset for path evaluates to an empty set')
5661 )
5662 )
5662 elif ui.configbool(b'commands', b'push.require-revs'):
5663 elif ui.configbool(b'commands', b'push.require-revs'):
5663 raise error.Abort(
5664 raise error.Abort(
5664 _(b'no revisions specified to push'),
5665 _(b'no revisions specified to push'),
5665 hint=_(b'did you mean "hg push -r ."?'),
5666 hint=_(b'did you mean "hg push -r ."?'),
5666 )
5667 )
5667
5668
5668 repo._subtoppath = dest
5669 repo._subtoppath = dest
5669 try:
5670 try:
5670 # push subrepos depth-first for coherent ordering
5671 # push subrepos depth-first for coherent ordering
5671 c = repo[b'.']
5672 c = repo[b'.']
5672 subs = c.substate # only repos that are committed
5673 subs = c.substate # only repos that are committed
5673 for s in sorted(subs):
5674 for s in sorted(subs):
5674 result = c.sub(s).push(opts)
5675 result = c.sub(s).push(opts)
5675 if result == 0:
5676 if result == 0:
5676 return not result
5677 return not result
5677 finally:
5678 finally:
5678 del repo._subtoppath
5679 del repo._subtoppath
5679
5680
5680 opargs = dict(opts.get(b'opargs', {})) # copy opargs since we may mutate it
5681 opargs = dict(opts.get(b'opargs', {})) # copy opargs since we may mutate it
5681 opargs.setdefault(b'pushvars', []).extend(opts.get(b'pushvars', []))
5682 opargs.setdefault(b'pushvars', []).extend(opts.get(b'pushvars', []))
5682
5683
5683 pushop = exchange.push(
5684 pushop = exchange.push(
5684 repo,
5685 repo,
5685 other,
5686 other,
5686 opts.get(b'force'),
5687 opts.get(b'force'),
5687 revs=revs,
5688 revs=revs,
5688 newbranch=opts.get(b'new_branch'),
5689 newbranch=opts.get(b'new_branch'),
5689 bookmarks=opts.get(b'bookmark', ()),
5690 bookmarks=opts.get(b'bookmark', ()),
5690 publish=opts.get(b'publish'),
5691 publish=opts.get(b'publish'),
5691 opargs=opargs,
5692 opargs=opargs,
5692 )
5693 )
5693
5694
5694 result = not pushop.cgresult
5695 result = not pushop.cgresult
5695
5696
5696 if pushop.bkresult is not None:
5697 if pushop.bkresult is not None:
5697 if pushop.bkresult == 2:
5698 if pushop.bkresult == 2:
5698 result = 2
5699 result = 2
5699 elif not result and pushop.bkresult:
5700 elif not result and pushop.bkresult:
5700 result = 2
5701 result = 2
5701
5702
5702 return result
5703 return result
5703
5704
5704
5705
5705 @command(
5706 @command(
5706 b'recover',
5707 b'recover',
5707 [(b'', b'verify', False, b"run `hg verify` after successful recover"),],
5708 [(b'', b'verify', False, b"run `hg verify` after successful recover"),],
5708 helpcategory=command.CATEGORY_MAINTENANCE,
5709 helpcategory=command.CATEGORY_MAINTENANCE,
5709 )
5710 )
5710 def recover(ui, repo, **opts):
5711 def recover(ui, repo, **opts):
5711 """roll back an interrupted transaction
5712 """roll back an interrupted transaction
5712
5713
5713 Recover from an interrupted commit or pull.
5714 Recover from an interrupted commit or pull.
5714
5715
5715 This command tries to fix the repository status after an
5716 This command tries to fix the repository status after an
5716 interrupted operation. It should only be necessary when Mercurial
5717 interrupted operation. It should only be necessary when Mercurial
5717 suggests it.
5718 suggests it.
5718
5719
5719 Returns 0 if successful, 1 if nothing to recover or verify fails.
5720 Returns 0 if successful, 1 if nothing to recover or verify fails.
5720 """
5721 """
5721 ret = repo.recover()
5722 ret = repo.recover()
5722 if ret:
5723 if ret:
5723 if opts['verify']:
5724 if opts['verify']:
5724 return hg.verify(repo)
5725 return hg.verify(repo)
5725 else:
5726 else:
5726 msg = _(
5727 msg = _(
5727 b"(verify step skipped, run `hg verify` to check your "
5728 b"(verify step skipped, run `hg verify` to check your "
5728 b"repository content)\n"
5729 b"repository content)\n"
5729 )
5730 )
5730 ui.warn(msg)
5731 ui.warn(msg)
5731 return 0
5732 return 0
5732 return 1
5733 return 1
5733
5734
5734
5735
5735 @command(
5736 @command(
5736 b'remove|rm',
5737 b'remove|rm',
5737 [
5738 [
5738 (b'A', b'after', None, _(b'record delete for missing files')),
5739 (b'A', b'after', None, _(b'record delete for missing files')),
5739 (b'f', b'force', None, _(b'forget added files, delete modified files')),
5740 (b'f', b'force', None, _(b'forget added files, delete modified files')),
5740 ]
5741 ]
5741 + subrepoopts
5742 + subrepoopts
5742 + walkopts
5743 + walkopts
5743 + dryrunopts,
5744 + dryrunopts,
5744 _(b'[OPTION]... FILE...'),
5745 _(b'[OPTION]... FILE...'),
5745 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5746 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5746 helpbasic=True,
5747 helpbasic=True,
5747 inferrepo=True,
5748 inferrepo=True,
5748 )
5749 )
5749 def remove(ui, repo, *pats, **opts):
5750 def remove(ui, repo, *pats, **opts):
5750 """remove the specified files on the next commit
5751 """remove the specified files on the next commit
5751
5752
5752 Schedule the indicated files for removal from the current branch.
5753 Schedule the indicated files for removal from the current branch.
5753
5754
5754 This command schedules the files to be removed at the next commit.
5755 This command schedules the files to be removed at the next commit.
5755 To undo a remove before that, see :hg:`revert`. To undo added
5756 To undo a remove before that, see :hg:`revert`. To undo added
5756 files, see :hg:`forget`.
5757 files, see :hg:`forget`.
5757
5758
5758 .. container:: verbose
5759 .. container:: verbose
5759
5760
5760 -A/--after can be used to remove only files that have already
5761 -A/--after can be used to remove only files that have already
5761 been deleted, -f/--force can be used to force deletion, and -Af
5762 been deleted, -f/--force can be used to force deletion, and -Af
5762 can be used to remove files from the next revision without
5763 can be used to remove files from the next revision without
5763 deleting them from the working directory.
5764 deleting them from the working directory.
5764
5765
5765 The following table details the behavior of remove for different
5766 The following table details the behavior of remove for different
5766 file states (columns) and option combinations (rows). The file
5767 file states (columns) and option combinations (rows). The file
5767 states are Added [A], Clean [C], Modified [M] and Missing [!]
5768 states are Added [A], Clean [C], Modified [M] and Missing [!]
5768 (as reported by :hg:`status`). The actions are Warn, Remove
5769 (as reported by :hg:`status`). The actions are Warn, Remove
5769 (from branch) and Delete (from disk):
5770 (from branch) and Delete (from disk):
5770
5771
5771 ========= == == == ==
5772 ========= == == == ==
5772 opt/state A C M !
5773 opt/state A C M !
5773 ========= == == == ==
5774 ========= == == == ==
5774 none W RD W R
5775 none W RD W R
5775 -f R RD RD R
5776 -f R RD RD R
5776 -A W W W R
5777 -A W W W R
5777 -Af R R R R
5778 -Af R R R R
5778 ========= == == == ==
5779 ========= == == == ==
5779
5780
5780 .. note::
5781 .. note::
5781
5782
5782 :hg:`remove` never deletes files in Added [A] state from the
5783 :hg:`remove` never deletes files in Added [A] state from the
5783 working directory, not even if ``--force`` is specified.
5784 working directory, not even if ``--force`` is specified.
5784
5785
5785 Returns 0 on success, 1 if any warnings encountered.
5786 Returns 0 on success, 1 if any warnings encountered.
5786 """
5787 """
5787
5788
5788 opts = pycompat.byteskwargs(opts)
5789 opts = pycompat.byteskwargs(opts)
5789 after, force = opts.get(b'after'), opts.get(b'force')
5790 after, force = opts.get(b'after'), opts.get(b'force')
5790 dryrun = opts.get(b'dry_run')
5791 dryrun = opts.get(b'dry_run')
5791 if not pats and not after:
5792 if not pats and not after:
5792 raise error.Abort(_(b'no files specified'))
5793 raise error.Abort(_(b'no files specified'))
5793
5794
5794 m = scmutil.match(repo[None], pats, opts)
5795 m = scmutil.match(repo[None], pats, opts)
5795 subrepos = opts.get(b'subrepos')
5796 subrepos = opts.get(b'subrepos')
5796 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
5797 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
5797 return cmdutil.remove(
5798 return cmdutil.remove(
5798 ui, repo, m, b"", uipathfn, after, force, subrepos, dryrun=dryrun
5799 ui, repo, m, b"", uipathfn, after, force, subrepos, dryrun=dryrun
5799 )
5800 )
5800
5801
5801
5802
5802 @command(
5803 @command(
5803 b'rename|move|mv',
5804 b'rename|move|mv',
5804 [
5805 [
5805 (b'A', b'after', None, _(b'record a rename that has already occurred')),
5806 (b'A', b'after', None, _(b'record a rename that has already occurred')),
5806 (
5807 (
5807 b'',
5808 b'',
5808 b'at-rev',
5809 b'at-rev',
5809 b'',
5810 b'',
5810 _(b'(un)mark renames in the given revision (EXPERIMENTAL)'),
5811 _(b'(un)mark renames in the given revision (EXPERIMENTAL)'),
5811 _(b'REV'),
5812 _(b'REV'),
5812 ),
5813 ),
5813 (
5814 (
5814 b'f',
5815 b'f',
5815 b'force',
5816 b'force',
5816 None,
5817 None,
5817 _(b'forcibly move over an existing managed file'),
5818 _(b'forcibly move over an existing managed file'),
5818 ),
5819 ),
5819 ]
5820 ]
5820 + walkopts
5821 + walkopts
5821 + dryrunopts,
5822 + dryrunopts,
5822 _(b'[OPTION]... SOURCE... DEST'),
5823 _(b'[OPTION]... SOURCE... DEST'),
5823 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5824 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5824 )
5825 )
5825 def rename(ui, repo, *pats, **opts):
5826 def rename(ui, repo, *pats, **opts):
5826 """rename files; equivalent of copy + remove
5827 """rename files; equivalent of copy + remove
5827
5828
5828 Mark dest as copies of sources; mark sources for deletion. If dest
5829 Mark dest as copies of sources; mark sources for deletion. If dest
5829 is a directory, copies are put in that directory. If dest is a
5830 is a directory, copies are put in that directory. If dest is a
5830 file, there can only be one source.
5831 file, there can only be one source.
5831
5832
5832 By default, this command copies the contents of files as they
5833 By default, this command copies the contents of files as they
5833 exist in the working directory. If invoked with -A/--after, the
5834 exist in the working directory. If invoked with -A/--after, the
5834 operation is recorded, but no copying is performed.
5835 operation is recorded, but no copying is performed.
5835
5836
5836 This command takes effect at the next commit. To undo a rename
5837 This command takes effect at the next commit. To undo a rename
5837 before that, see :hg:`revert`.
5838 before that, see :hg:`revert`.
5838
5839
5839 Returns 0 on success, 1 if errors are encountered.
5840 Returns 0 on success, 1 if errors are encountered.
5840 """
5841 """
5841 opts = pycompat.byteskwargs(opts)
5842 opts = pycompat.byteskwargs(opts)
5842 with repo.wlock():
5843 with repo.wlock():
5843 return cmdutil.copy(ui, repo, pats, opts, rename=True)
5844 return cmdutil.copy(ui, repo, pats, opts, rename=True)
5844
5845
5845
5846
5846 @command(
5847 @command(
5847 b'resolve',
5848 b'resolve',
5848 [
5849 [
5849 (b'a', b'all', None, _(b'select all unresolved files')),
5850 (b'a', b'all', None, _(b'select all unresolved files')),
5850 (b'l', b'list', None, _(b'list state of files needing merge')),
5851 (b'l', b'list', None, _(b'list state of files needing merge')),
5851 (b'm', b'mark', None, _(b'mark files as resolved')),
5852 (b'm', b'mark', None, _(b'mark files as resolved')),
5852 (b'u', b'unmark', None, _(b'mark files as unresolved')),
5853 (b'u', b'unmark', None, _(b'mark files as unresolved')),
5853 (b'n', b'no-status', None, _(b'hide status prefix')),
5854 (b'n', b'no-status', None, _(b'hide status prefix')),
5854 (b'', b're-merge', None, _(b're-merge files')),
5855 (b'', b're-merge', None, _(b're-merge files')),
5855 ]
5856 ]
5856 + mergetoolopts
5857 + mergetoolopts
5857 + walkopts
5858 + walkopts
5858 + formatteropts,
5859 + formatteropts,
5859 _(b'[OPTION]... [FILE]...'),
5860 _(b'[OPTION]... [FILE]...'),
5860 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5861 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5861 inferrepo=True,
5862 inferrepo=True,
5862 )
5863 )
5863 def resolve(ui, repo, *pats, **opts):
5864 def resolve(ui, repo, *pats, **opts):
5864 """redo merges or set/view the merge status of files
5865 """redo merges or set/view the merge status of files
5865
5866
5866 Merges with unresolved conflicts are often the result of
5867 Merges with unresolved conflicts are often the result of
5867 non-interactive merging using the ``internal:merge`` configuration
5868 non-interactive merging using the ``internal:merge`` configuration
5868 setting, or a command-line merge tool like ``diff3``. The resolve
5869 setting, or a command-line merge tool like ``diff3``. The resolve
5869 command is used to manage the files involved in a merge, after
5870 command is used to manage the files involved in a merge, after
5870 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
5871 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
5871 working directory must have two parents). See :hg:`help
5872 working directory must have two parents). See :hg:`help
5872 merge-tools` for information on configuring merge tools.
5873 merge-tools` for information on configuring merge tools.
5873
5874
5874 The resolve command can be used in the following ways:
5875 The resolve command can be used in the following ways:
5875
5876
5876 - :hg:`resolve [--re-merge] [--tool TOOL] FILE...`: attempt to re-merge
5877 - :hg:`resolve [--re-merge] [--tool TOOL] FILE...`: attempt to re-merge
5877 the specified files, discarding any previous merge attempts. Re-merging
5878 the specified files, discarding any previous merge attempts. Re-merging
5878 is not performed for files already marked as resolved. Use ``--all/-a``
5879 is not performed for files already marked as resolved. Use ``--all/-a``
5879 to select all unresolved files. ``--tool`` can be used to specify
5880 to select all unresolved files. ``--tool`` can be used to specify
5880 the merge tool used for the given files. It overrides the HGMERGE
5881 the merge tool used for the given files. It overrides the HGMERGE
5881 environment variable and your configuration files. Previous file
5882 environment variable and your configuration files. Previous file
5882 contents are saved with a ``.orig`` suffix.
5883 contents are saved with a ``.orig`` suffix.
5883
5884
5884 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
5885 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
5885 (e.g. after having manually fixed-up the files). The default is
5886 (e.g. after having manually fixed-up the files). The default is
5886 to mark all unresolved files.
5887 to mark all unresolved files.
5887
5888
5888 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
5889 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
5889 default is to mark all resolved files.
5890 default is to mark all resolved files.
5890
5891
5891 - :hg:`resolve -l`: list files which had or still have conflicts.
5892 - :hg:`resolve -l`: list files which had or still have conflicts.
5892 In the printed list, ``U`` = unresolved and ``R`` = resolved.
5893 In the printed list, ``U`` = unresolved and ``R`` = resolved.
5893 You can use ``set:unresolved()`` or ``set:resolved()`` to filter
5894 You can use ``set:unresolved()`` or ``set:resolved()`` to filter
5894 the list. See :hg:`help filesets` for details.
5895 the list. See :hg:`help filesets` for details.
5895
5896
5896 .. note::
5897 .. note::
5897
5898
5898 Mercurial will not let you commit files with unresolved merge
5899 Mercurial will not let you commit files with unresolved merge
5899 conflicts. You must use :hg:`resolve -m ...` before you can
5900 conflicts. You must use :hg:`resolve -m ...` before you can
5900 commit after a conflicting merge.
5901 commit after a conflicting merge.
5901
5902
5902 .. container:: verbose
5903 .. container:: verbose
5903
5904
5904 Template:
5905 Template:
5905
5906
5906 The following keywords are supported in addition to the common template
5907 The following keywords are supported in addition to the common template
5907 keywords and functions. See also :hg:`help templates`.
5908 keywords and functions. See also :hg:`help templates`.
5908
5909
5909 :mergestatus: String. Character denoting merge conflicts, ``U`` or ``R``.
5910 :mergestatus: String. Character denoting merge conflicts, ``U`` or ``R``.
5910 :path: String. Repository-absolute path of the file.
5911 :path: String. Repository-absolute path of the file.
5911
5912
5912 Returns 0 on success, 1 if any files fail a resolve attempt.
5913 Returns 0 on success, 1 if any files fail a resolve attempt.
5913 """
5914 """
5914
5915
5915 opts = pycompat.byteskwargs(opts)
5916 opts = pycompat.byteskwargs(opts)
5916 confirm = ui.configbool(b'commands', b'resolve.confirm')
5917 confirm = ui.configbool(b'commands', b'resolve.confirm')
5917 flaglist = b'all mark unmark list no_status re_merge'.split()
5918 flaglist = b'all mark unmark list no_status re_merge'.split()
5918 all, mark, unmark, show, nostatus, remerge = [opts.get(o) for o in flaglist]
5919 all, mark, unmark, show, nostatus, remerge = [opts.get(o) for o in flaglist]
5919
5920
5920 actioncount = len(list(filter(None, [show, mark, unmark, remerge])))
5921 actioncount = len(list(filter(None, [show, mark, unmark, remerge])))
5921 if actioncount > 1:
5922 if actioncount > 1:
5922 raise error.Abort(_(b"too many actions specified"))
5923 raise error.Abort(_(b"too many actions specified"))
5923 elif actioncount == 0 and ui.configbool(
5924 elif actioncount == 0 and ui.configbool(
5924 b'commands', b'resolve.explicit-re-merge'
5925 b'commands', b'resolve.explicit-re-merge'
5925 ):
5926 ):
5926 hint = _(b'use --mark, --unmark, --list or --re-merge')
5927 hint = _(b'use --mark, --unmark, --list or --re-merge')
5927 raise error.Abort(_(b'no action specified'), hint=hint)
5928 raise error.Abort(_(b'no action specified'), hint=hint)
5928 if pats and all:
5929 if pats and all:
5929 raise error.Abort(_(b"can't specify --all and patterns"))
5930 raise error.Abort(_(b"can't specify --all and patterns"))
5930 if not (all or pats or show or mark or unmark):
5931 if not (all or pats or show or mark or unmark):
5931 raise error.Abort(
5932 raise error.Abort(
5932 _(b'no files or directories specified'),
5933 _(b'no files or directories specified'),
5933 hint=b'use --all to re-merge all unresolved files',
5934 hint=b'use --all to re-merge all unresolved files',
5934 )
5935 )
5935
5936
5936 if confirm:
5937 if confirm:
5937 if all:
5938 if all:
5938 if ui.promptchoice(
5939 if ui.promptchoice(
5939 _(b're-merge all unresolved files (yn)?$$ &Yes $$ &No')
5940 _(b're-merge all unresolved files (yn)?$$ &Yes $$ &No')
5940 ):
5941 ):
5941 raise error.Abort(_(b'user quit'))
5942 raise error.Abort(_(b'user quit'))
5942 if mark and not pats:
5943 if mark and not pats:
5943 if ui.promptchoice(
5944 if ui.promptchoice(
5944 _(
5945 _(
5945 b'mark all unresolved files as resolved (yn)?'
5946 b'mark all unresolved files as resolved (yn)?'
5946 b'$$ &Yes $$ &No'
5947 b'$$ &Yes $$ &No'
5947 )
5948 )
5948 ):
5949 ):
5949 raise error.Abort(_(b'user quit'))
5950 raise error.Abort(_(b'user quit'))
5950 if unmark and not pats:
5951 if unmark and not pats:
5951 if ui.promptchoice(
5952 if ui.promptchoice(
5952 _(
5953 _(
5953 b'mark all resolved files as unresolved (yn)?'
5954 b'mark all resolved files as unresolved (yn)?'
5954 b'$$ &Yes $$ &No'
5955 b'$$ &Yes $$ &No'
5955 )
5956 )
5956 ):
5957 ):
5957 raise error.Abort(_(b'user quit'))
5958 raise error.Abort(_(b'user quit'))
5958
5959
5959 uipathfn = scmutil.getuipathfn(repo)
5960 uipathfn = scmutil.getuipathfn(repo)
5960
5961
5961 if show:
5962 if show:
5962 ui.pager(b'resolve')
5963 ui.pager(b'resolve')
5963 fm = ui.formatter(b'resolve', opts)
5964 fm = ui.formatter(b'resolve', opts)
5964 ms = mergestatemod.mergestate.read(repo)
5965 ms = mergestatemod.mergestate.read(repo)
5965 wctx = repo[None]
5966 wctx = repo[None]
5966 m = scmutil.match(wctx, pats, opts)
5967 m = scmutil.match(wctx, pats, opts)
5967
5968
5968 # Labels and keys based on merge state. Unresolved path conflicts show
5969 # Labels and keys based on merge state. Unresolved path conflicts show
5969 # as 'P'. Resolved path conflicts show as 'R', the same as normal
5970 # as 'P'. Resolved path conflicts show as 'R', the same as normal
5970 # resolved conflicts.
5971 # resolved conflicts.
5971 mergestateinfo = {
5972 mergestateinfo = {
5972 mergestatemod.MERGE_RECORD_UNRESOLVED: (
5973 mergestatemod.MERGE_RECORD_UNRESOLVED: (
5973 b'resolve.unresolved',
5974 b'resolve.unresolved',
5974 b'U',
5975 b'U',
5975 ),
5976 ),
5976 mergestatemod.MERGE_RECORD_RESOLVED: (b'resolve.resolved', b'R'),
5977 mergestatemod.MERGE_RECORD_RESOLVED: (b'resolve.resolved', b'R'),
5977 mergestatemod.MERGE_RECORD_UNRESOLVED_PATH: (
5978 mergestatemod.MERGE_RECORD_UNRESOLVED_PATH: (
5978 b'resolve.unresolved',
5979 b'resolve.unresolved',
5979 b'P',
5980 b'P',
5980 ),
5981 ),
5981 mergestatemod.MERGE_RECORD_RESOLVED_PATH: (
5982 mergestatemod.MERGE_RECORD_RESOLVED_PATH: (
5982 b'resolve.resolved',
5983 b'resolve.resolved',
5983 b'R',
5984 b'R',
5984 ),
5985 ),
5985 }
5986 }
5986
5987
5987 for f in ms:
5988 for f in ms:
5988 if not m(f):
5989 if not m(f):
5989 continue
5990 continue
5990
5991
5991 label, key = mergestateinfo[ms[f]]
5992 label, key = mergestateinfo[ms[f]]
5992 fm.startitem()
5993 fm.startitem()
5993 fm.context(ctx=wctx)
5994 fm.context(ctx=wctx)
5994 fm.condwrite(not nostatus, b'mergestatus', b'%s ', key, label=label)
5995 fm.condwrite(not nostatus, b'mergestatus', b'%s ', key, label=label)
5995 fm.data(path=f)
5996 fm.data(path=f)
5996 fm.plain(b'%s\n' % uipathfn(f), label=label)
5997 fm.plain(b'%s\n' % uipathfn(f), label=label)
5997 fm.end()
5998 fm.end()
5998 return 0
5999 return 0
5999
6000
6000 with repo.wlock():
6001 with repo.wlock():
6001 ms = mergestatemod.mergestate.read(repo)
6002 ms = mergestatemod.mergestate.read(repo)
6002
6003
6003 if not (ms.active() or repo.dirstate.p2() != nullid):
6004 if not (ms.active() or repo.dirstate.p2() != nullid):
6004 raise error.Abort(
6005 raise error.Abort(
6005 _(b'resolve command not applicable when not merging')
6006 _(b'resolve command not applicable when not merging')
6006 )
6007 )
6007
6008
6008 wctx = repo[None]
6009 wctx = repo[None]
6009 m = scmutil.match(wctx, pats, opts)
6010 m = scmutil.match(wctx, pats, opts)
6010 ret = 0
6011 ret = 0
6011 didwork = False
6012 didwork = False
6012
6013
6013 tocomplete = []
6014 tocomplete = []
6014 hasconflictmarkers = []
6015 hasconflictmarkers = []
6015 if mark:
6016 if mark:
6016 markcheck = ui.config(b'commands', b'resolve.mark-check')
6017 markcheck = ui.config(b'commands', b'resolve.mark-check')
6017 if markcheck not in [b'warn', b'abort']:
6018 if markcheck not in [b'warn', b'abort']:
6018 # Treat all invalid / unrecognized values as 'none'.
6019 # Treat all invalid / unrecognized values as 'none'.
6019 markcheck = False
6020 markcheck = False
6020 for f in ms:
6021 for f in ms:
6021 if not m(f):
6022 if not m(f):
6022 continue
6023 continue
6023
6024
6024 didwork = True
6025 didwork = True
6025
6026
6026 # path conflicts must be resolved manually
6027 # path conflicts must be resolved manually
6027 if ms[f] in (
6028 if ms[f] in (
6028 mergestatemod.MERGE_RECORD_UNRESOLVED_PATH,
6029 mergestatemod.MERGE_RECORD_UNRESOLVED_PATH,
6029 mergestatemod.MERGE_RECORD_RESOLVED_PATH,
6030 mergestatemod.MERGE_RECORD_RESOLVED_PATH,
6030 ):
6031 ):
6031 if mark:
6032 if mark:
6032 ms.mark(f, mergestatemod.MERGE_RECORD_RESOLVED_PATH)
6033 ms.mark(f, mergestatemod.MERGE_RECORD_RESOLVED_PATH)
6033 elif unmark:
6034 elif unmark:
6034 ms.mark(f, mergestatemod.MERGE_RECORD_UNRESOLVED_PATH)
6035 ms.mark(f, mergestatemod.MERGE_RECORD_UNRESOLVED_PATH)
6035 elif ms[f] == mergestatemod.MERGE_RECORD_UNRESOLVED_PATH:
6036 elif ms[f] == mergestatemod.MERGE_RECORD_UNRESOLVED_PATH:
6036 ui.warn(
6037 ui.warn(
6037 _(b'%s: path conflict must be resolved manually\n')
6038 _(b'%s: path conflict must be resolved manually\n')
6038 % uipathfn(f)
6039 % uipathfn(f)
6039 )
6040 )
6040 continue
6041 continue
6041
6042
6042 if mark:
6043 if mark:
6043 if markcheck:
6044 if markcheck:
6044 fdata = repo.wvfs.tryread(f)
6045 fdata = repo.wvfs.tryread(f)
6045 if (
6046 if (
6046 filemerge.hasconflictmarkers(fdata)
6047 filemerge.hasconflictmarkers(fdata)
6047 and ms[f] != mergestatemod.MERGE_RECORD_RESOLVED
6048 and ms[f] != mergestatemod.MERGE_RECORD_RESOLVED
6048 ):
6049 ):
6049 hasconflictmarkers.append(f)
6050 hasconflictmarkers.append(f)
6050 ms.mark(f, mergestatemod.MERGE_RECORD_RESOLVED)
6051 ms.mark(f, mergestatemod.MERGE_RECORD_RESOLVED)
6051 elif unmark:
6052 elif unmark:
6052 ms.mark(f, mergestatemod.MERGE_RECORD_UNRESOLVED)
6053 ms.mark(f, mergestatemod.MERGE_RECORD_UNRESOLVED)
6053 else:
6054 else:
6054 # backup pre-resolve (merge uses .orig for its own purposes)
6055 # backup pre-resolve (merge uses .orig for its own purposes)
6055 a = repo.wjoin(f)
6056 a = repo.wjoin(f)
6056 try:
6057 try:
6057 util.copyfile(a, a + b".resolve")
6058 util.copyfile(a, a + b".resolve")
6058 except (IOError, OSError) as inst:
6059 except (IOError, OSError) as inst:
6059 if inst.errno != errno.ENOENT:
6060 if inst.errno != errno.ENOENT:
6060 raise
6061 raise
6061
6062
6062 try:
6063 try:
6063 # preresolve file
6064 # preresolve file
6064 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
6065 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
6065 with ui.configoverride(overrides, b'resolve'):
6066 with ui.configoverride(overrides, b'resolve'):
6066 complete, r = ms.preresolve(f, wctx)
6067 complete, r = ms.preresolve(f, wctx)
6067 if not complete:
6068 if not complete:
6068 tocomplete.append(f)
6069 tocomplete.append(f)
6069 elif r:
6070 elif r:
6070 ret = 1
6071 ret = 1
6071 finally:
6072 finally:
6072 ms.commit()
6073 ms.commit()
6073
6074
6074 # replace filemerge's .orig file with our resolve file, but only
6075 # replace filemerge's .orig file with our resolve file, but only
6075 # for merges that are complete
6076 # for merges that are complete
6076 if complete:
6077 if complete:
6077 try:
6078 try:
6078 util.rename(
6079 util.rename(
6079 a + b".resolve", scmutil.backuppath(ui, repo, f)
6080 a + b".resolve", scmutil.backuppath(ui, repo, f)
6080 )
6081 )
6081 except OSError as inst:
6082 except OSError as inst:
6082 if inst.errno != errno.ENOENT:
6083 if inst.errno != errno.ENOENT:
6083 raise
6084 raise
6084
6085
6085 if hasconflictmarkers:
6086 if hasconflictmarkers:
6086 ui.warn(
6087 ui.warn(
6087 _(
6088 _(
6088 b'warning: the following files still have conflict '
6089 b'warning: the following files still have conflict '
6089 b'markers:\n'
6090 b'markers:\n'
6090 )
6091 )
6091 + b''.join(
6092 + b''.join(
6092 b' ' + uipathfn(f) + b'\n' for f in hasconflictmarkers
6093 b' ' + uipathfn(f) + b'\n' for f in hasconflictmarkers
6093 )
6094 )
6094 )
6095 )
6095 if markcheck == b'abort' and not all and not pats:
6096 if markcheck == b'abort' and not all and not pats:
6096 raise error.Abort(
6097 raise error.Abort(
6097 _(b'conflict markers detected'),
6098 _(b'conflict markers detected'),
6098 hint=_(b'use --all to mark anyway'),
6099 hint=_(b'use --all to mark anyway'),
6099 )
6100 )
6100
6101
6101 for f in tocomplete:
6102 for f in tocomplete:
6102 try:
6103 try:
6103 # resolve file
6104 # resolve file
6104 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
6105 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
6105 with ui.configoverride(overrides, b'resolve'):
6106 with ui.configoverride(overrides, b'resolve'):
6106 r = ms.resolve(f, wctx)
6107 r = ms.resolve(f, wctx)
6107 if r:
6108 if r:
6108 ret = 1
6109 ret = 1
6109 finally:
6110 finally:
6110 ms.commit()
6111 ms.commit()
6111
6112
6112 # replace filemerge's .orig file with our resolve file
6113 # replace filemerge's .orig file with our resolve file
6113 a = repo.wjoin(f)
6114 a = repo.wjoin(f)
6114 try:
6115 try:
6115 util.rename(a + b".resolve", scmutil.backuppath(ui, repo, f))
6116 util.rename(a + b".resolve", scmutil.backuppath(ui, repo, f))
6116 except OSError as inst:
6117 except OSError as inst:
6117 if inst.errno != errno.ENOENT:
6118 if inst.errno != errno.ENOENT:
6118 raise
6119 raise
6119
6120
6120 ms.commit()
6121 ms.commit()
6121 branchmerge = repo.dirstate.p2() != nullid
6122 branchmerge = repo.dirstate.p2() != nullid
6122 mergestatemod.recordupdates(repo, ms.actions(), branchmerge, None)
6123 mergestatemod.recordupdates(repo, ms.actions(), branchmerge, None)
6123
6124
6124 if not didwork and pats:
6125 if not didwork and pats:
6125 hint = None
6126 hint = None
6126 if not any([p for p in pats if p.find(b':') >= 0]):
6127 if not any([p for p in pats if p.find(b':') >= 0]):
6127 pats = [b'path:%s' % p for p in pats]
6128 pats = [b'path:%s' % p for p in pats]
6128 m = scmutil.match(wctx, pats, opts)
6129 m = scmutil.match(wctx, pats, opts)
6129 for f in ms:
6130 for f in ms:
6130 if not m(f):
6131 if not m(f):
6131 continue
6132 continue
6132
6133
6133 def flag(o):
6134 def flag(o):
6134 if o == b're_merge':
6135 if o == b're_merge':
6135 return b'--re-merge '
6136 return b'--re-merge '
6136 return b'-%s ' % o[0:1]
6137 return b'-%s ' % o[0:1]
6137
6138
6138 flags = b''.join([flag(o) for o in flaglist if opts.get(o)])
6139 flags = b''.join([flag(o) for o in flaglist if opts.get(o)])
6139 hint = _(b"(try: hg resolve %s%s)\n") % (
6140 hint = _(b"(try: hg resolve %s%s)\n") % (
6140 flags,
6141 flags,
6141 b' '.join(pats),
6142 b' '.join(pats),
6142 )
6143 )
6143 break
6144 break
6144 ui.warn(_(b"arguments do not match paths that need resolving\n"))
6145 ui.warn(_(b"arguments do not match paths that need resolving\n"))
6145 if hint:
6146 if hint:
6146 ui.warn(hint)
6147 ui.warn(hint)
6147
6148
6148 unresolvedf = list(ms.unresolved())
6149 unresolvedf = list(ms.unresolved())
6149 if not unresolvedf:
6150 if not unresolvedf:
6150 ui.status(_(b'(no more unresolved files)\n'))
6151 ui.status(_(b'(no more unresolved files)\n'))
6151 cmdutil.checkafterresolved(repo)
6152 cmdutil.checkafterresolved(repo)
6152
6153
6153 return ret
6154 return ret
6154
6155
6155
6156
6156 @command(
6157 @command(
6157 b'revert',
6158 b'revert',
6158 [
6159 [
6159 (b'a', b'all', None, _(b'revert all changes when no arguments given')),
6160 (b'a', b'all', None, _(b'revert all changes when no arguments given')),
6160 (b'd', b'date', b'', _(b'tipmost revision matching date'), _(b'DATE')),
6161 (b'd', b'date', b'', _(b'tipmost revision matching date'), _(b'DATE')),
6161 (b'r', b'rev', b'', _(b'revert to the specified revision'), _(b'REV')),
6162 (b'r', b'rev', b'', _(b'revert to the specified revision'), _(b'REV')),
6162 (b'C', b'no-backup', None, _(b'do not save backup copies of files')),
6163 (b'C', b'no-backup', None, _(b'do not save backup copies of files')),
6163 (b'i', b'interactive', None, _(b'interactively select the changes')),
6164 (b'i', b'interactive', None, _(b'interactively select the changes')),
6164 ]
6165 ]
6165 + walkopts
6166 + walkopts
6166 + dryrunopts,
6167 + dryrunopts,
6167 _(b'[OPTION]... [-r REV] [NAME]...'),
6168 _(b'[OPTION]... [-r REV] [NAME]...'),
6168 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6169 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6169 )
6170 )
6170 def revert(ui, repo, *pats, **opts):
6171 def revert(ui, repo, *pats, **opts):
6171 """restore files to their checkout state
6172 """restore files to their checkout state
6172
6173
6173 .. note::
6174 .. note::
6174
6175
6175 To check out earlier revisions, you should use :hg:`update REV`.
6176 To check out earlier revisions, you should use :hg:`update REV`.
6176 To cancel an uncommitted merge (and lose your changes),
6177 To cancel an uncommitted merge (and lose your changes),
6177 use :hg:`merge --abort`.
6178 use :hg:`merge --abort`.
6178
6179
6179 With no revision specified, revert the specified files or directories
6180 With no revision specified, revert the specified files or directories
6180 to the contents they had in the parent of the working directory.
6181 to the contents they had in the parent of the working directory.
6181 This restores the contents of files to an unmodified
6182 This restores the contents of files to an unmodified
6182 state and unschedules adds, removes, copies, and renames. If the
6183 state and unschedules adds, removes, copies, and renames. If the
6183 working directory has two parents, you must explicitly specify a
6184 working directory has two parents, you must explicitly specify a
6184 revision.
6185 revision.
6185
6186
6186 Using the -r/--rev or -d/--date options, revert the given files or
6187 Using the -r/--rev or -d/--date options, revert the given files or
6187 directories to their states as of a specific revision. Because
6188 directories to their states as of a specific revision. Because
6188 revert does not change the working directory parents, this will
6189 revert does not change the working directory parents, this will
6189 cause these files to appear modified. This can be helpful to "back
6190 cause these files to appear modified. This can be helpful to "back
6190 out" some or all of an earlier change. See :hg:`backout` for a
6191 out" some or all of an earlier change. See :hg:`backout` for a
6191 related method.
6192 related method.
6192
6193
6193 Modified files are saved with a .orig suffix before reverting.
6194 Modified files are saved with a .orig suffix before reverting.
6194 To disable these backups, use --no-backup. It is possible to store
6195 To disable these backups, use --no-backup. It is possible to store
6195 the backup files in a custom directory relative to the root of the
6196 the backup files in a custom directory relative to the root of the
6196 repository by setting the ``ui.origbackuppath`` configuration
6197 repository by setting the ``ui.origbackuppath`` configuration
6197 option.
6198 option.
6198
6199
6199 See :hg:`help dates` for a list of formats valid for -d/--date.
6200 See :hg:`help dates` for a list of formats valid for -d/--date.
6200
6201
6201 See :hg:`help backout` for a way to reverse the effect of an
6202 See :hg:`help backout` for a way to reverse the effect of an
6202 earlier changeset.
6203 earlier changeset.
6203
6204
6204 Returns 0 on success.
6205 Returns 0 on success.
6205 """
6206 """
6206
6207
6207 opts = pycompat.byteskwargs(opts)
6208 opts = pycompat.byteskwargs(opts)
6208 if opts.get(b"date"):
6209 if opts.get(b"date"):
6209 if opts.get(b"rev"):
6210 if opts.get(b"rev"):
6210 raise error.Abort(_(b"you can't specify a revision and a date"))
6211 raise error.Abort(_(b"you can't specify a revision and a date"))
6211 opts[b"rev"] = cmdutil.finddate(ui, repo, opts[b"date"])
6212 opts[b"rev"] = cmdutil.finddate(ui, repo, opts[b"date"])
6212
6213
6213 parent, p2 = repo.dirstate.parents()
6214 parent, p2 = repo.dirstate.parents()
6214 if not opts.get(b'rev') and p2 != nullid:
6215 if not opts.get(b'rev') and p2 != nullid:
6215 # revert after merge is a trap for new users (issue2915)
6216 # revert after merge is a trap for new users (issue2915)
6216 raise error.Abort(
6217 raise error.Abort(
6217 _(b'uncommitted merge with no revision specified'),
6218 _(b'uncommitted merge with no revision specified'),
6218 hint=_(b"use 'hg update' or see 'hg help revert'"),
6219 hint=_(b"use 'hg update' or see 'hg help revert'"),
6219 )
6220 )
6220
6221
6221 rev = opts.get(b'rev')
6222 rev = opts.get(b'rev')
6222 if rev:
6223 if rev:
6223 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
6224 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
6224 ctx = scmutil.revsingle(repo, rev)
6225 ctx = scmutil.revsingle(repo, rev)
6225
6226
6226 if not (
6227 if not (
6227 pats
6228 pats
6228 or opts.get(b'include')
6229 or opts.get(b'include')
6229 or opts.get(b'exclude')
6230 or opts.get(b'exclude')
6230 or opts.get(b'all')
6231 or opts.get(b'all')
6231 or opts.get(b'interactive')
6232 or opts.get(b'interactive')
6232 ):
6233 ):
6233 msg = _(b"no files or directories specified")
6234 msg = _(b"no files or directories specified")
6234 if p2 != nullid:
6235 if p2 != nullid:
6235 hint = _(
6236 hint = _(
6236 b"uncommitted merge, use --all to discard all changes,"
6237 b"uncommitted merge, use --all to discard all changes,"
6237 b" or 'hg update -C .' to abort the merge"
6238 b" or 'hg update -C .' to abort the merge"
6238 )
6239 )
6239 raise error.Abort(msg, hint=hint)
6240 raise error.Abort(msg, hint=hint)
6240 dirty = any(repo.status())
6241 dirty = any(repo.status())
6241 node = ctx.node()
6242 node = ctx.node()
6242 if node != parent:
6243 if node != parent:
6243 if dirty:
6244 if dirty:
6244 hint = (
6245 hint = (
6245 _(
6246 _(
6246 b"uncommitted changes, use --all to discard all"
6247 b"uncommitted changes, use --all to discard all"
6247 b" changes, or 'hg update %d' to update"
6248 b" changes, or 'hg update %d' to update"
6248 )
6249 )
6249 % ctx.rev()
6250 % ctx.rev()
6250 )
6251 )
6251 else:
6252 else:
6252 hint = (
6253 hint = (
6253 _(
6254 _(
6254 b"use --all to revert all files,"
6255 b"use --all to revert all files,"
6255 b" or 'hg update %d' to update"
6256 b" or 'hg update %d' to update"
6256 )
6257 )
6257 % ctx.rev()
6258 % ctx.rev()
6258 )
6259 )
6259 elif dirty:
6260 elif dirty:
6260 hint = _(b"uncommitted changes, use --all to discard all changes")
6261 hint = _(b"uncommitted changes, use --all to discard all changes")
6261 else:
6262 else:
6262 hint = _(b"use --all to revert all files")
6263 hint = _(b"use --all to revert all files")
6263 raise error.Abort(msg, hint=hint)
6264 raise error.Abort(msg, hint=hint)
6264
6265
6265 return cmdutil.revert(ui, repo, ctx, *pats, **pycompat.strkwargs(opts))
6266 return cmdutil.revert(ui, repo, ctx, *pats, **pycompat.strkwargs(opts))
6266
6267
6267
6268
6268 @command(
6269 @command(
6269 b'rollback',
6270 b'rollback',
6270 dryrunopts + [(b'f', b'force', False, _(b'ignore safety measures'))],
6271 dryrunopts + [(b'f', b'force', False, _(b'ignore safety measures'))],
6271 helpcategory=command.CATEGORY_MAINTENANCE,
6272 helpcategory=command.CATEGORY_MAINTENANCE,
6272 )
6273 )
6273 def rollback(ui, repo, **opts):
6274 def rollback(ui, repo, **opts):
6274 """roll back the last transaction (DANGEROUS) (DEPRECATED)
6275 """roll back the last transaction (DANGEROUS) (DEPRECATED)
6275
6276
6276 Please use :hg:`commit --amend` instead of rollback to correct
6277 Please use :hg:`commit --amend` instead of rollback to correct
6277 mistakes in the last commit.
6278 mistakes in the last commit.
6278
6279
6279 This command should be used with care. There is only one level of
6280 This command should be used with care. There is only one level of
6280 rollback, and there is no way to undo a rollback. It will also
6281 rollback, and there is no way to undo a rollback. It will also
6281 restore the dirstate at the time of the last transaction, losing
6282 restore the dirstate at the time of the last transaction, losing
6282 any dirstate changes since that time. This command does not alter
6283 any dirstate changes since that time. This command does not alter
6283 the working directory.
6284 the working directory.
6284
6285
6285 Transactions are used to encapsulate the effects of all commands
6286 Transactions are used to encapsulate the effects of all commands
6286 that create new changesets or propagate existing changesets into a
6287 that create new changesets or propagate existing changesets into a
6287 repository.
6288 repository.
6288
6289
6289 .. container:: verbose
6290 .. container:: verbose
6290
6291
6291 For example, the following commands are transactional, and their
6292 For example, the following commands are transactional, and their
6292 effects can be rolled back:
6293 effects can be rolled back:
6293
6294
6294 - commit
6295 - commit
6295 - import
6296 - import
6296 - pull
6297 - pull
6297 - push (with this repository as the destination)
6298 - push (with this repository as the destination)
6298 - unbundle
6299 - unbundle
6299
6300
6300 To avoid permanent data loss, rollback will refuse to rollback a
6301 To avoid permanent data loss, rollback will refuse to rollback a
6301 commit transaction if it isn't checked out. Use --force to
6302 commit transaction if it isn't checked out. Use --force to
6302 override this protection.
6303 override this protection.
6303
6304
6304 The rollback command can be entirely disabled by setting the
6305 The rollback command can be entirely disabled by setting the
6305 ``ui.rollback`` configuration setting to false. If you're here
6306 ``ui.rollback`` configuration setting to false. If you're here
6306 because you want to use rollback and it's disabled, you can
6307 because you want to use rollback and it's disabled, you can
6307 re-enable the command by setting ``ui.rollback`` to true.
6308 re-enable the command by setting ``ui.rollback`` to true.
6308
6309
6309 This command is not intended for use on public repositories. Once
6310 This command is not intended for use on public repositories. Once
6310 changes are visible for pull by other users, rolling a transaction
6311 changes are visible for pull by other users, rolling a transaction
6311 back locally is ineffective (someone else may already have pulled
6312 back locally is ineffective (someone else may already have pulled
6312 the changes). Furthermore, a race is possible with readers of the
6313 the changes). Furthermore, a race is possible with readers of the
6313 repository; for example an in-progress pull from the repository
6314 repository; for example an in-progress pull from the repository
6314 may fail if a rollback is performed.
6315 may fail if a rollback is performed.
6315
6316
6316 Returns 0 on success, 1 if no rollback data is available.
6317 Returns 0 on success, 1 if no rollback data is available.
6317 """
6318 """
6318 if not ui.configbool(b'ui', b'rollback'):
6319 if not ui.configbool(b'ui', b'rollback'):
6319 raise error.Abort(
6320 raise error.Abort(
6320 _(b'rollback is disabled because it is unsafe'),
6321 _(b'rollback is disabled because it is unsafe'),
6321 hint=b'see `hg help -v rollback` for information',
6322 hint=b'see `hg help -v rollback` for information',
6322 )
6323 )
6323 return repo.rollback(dryrun=opts.get('dry_run'), force=opts.get('force'))
6324 return repo.rollback(dryrun=opts.get('dry_run'), force=opts.get('force'))
6324
6325
6325
6326
6326 @command(
6327 @command(
6327 b'root',
6328 b'root',
6328 [] + formatteropts,
6329 [] + formatteropts,
6329 intents={INTENT_READONLY},
6330 intents={INTENT_READONLY},
6330 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6331 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6331 )
6332 )
6332 def root(ui, repo, **opts):
6333 def root(ui, repo, **opts):
6333 """print the root (top) of the current working directory
6334 """print the root (top) of the current working directory
6334
6335
6335 Print the root directory of the current repository.
6336 Print the root directory of the current repository.
6336
6337
6337 .. container:: verbose
6338 .. container:: verbose
6338
6339
6339 Template:
6340 Template:
6340
6341
6341 The following keywords are supported in addition to the common template
6342 The following keywords are supported in addition to the common template
6342 keywords and functions. See also :hg:`help templates`.
6343 keywords and functions. See also :hg:`help templates`.
6343
6344
6344 :hgpath: String. Path to the .hg directory.
6345 :hgpath: String. Path to the .hg directory.
6345 :storepath: String. Path to the directory holding versioned data.
6346 :storepath: String. Path to the directory holding versioned data.
6346
6347
6347 Returns 0 on success.
6348 Returns 0 on success.
6348 """
6349 """
6349 opts = pycompat.byteskwargs(opts)
6350 opts = pycompat.byteskwargs(opts)
6350 with ui.formatter(b'root', opts) as fm:
6351 with ui.formatter(b'root', opts) as fm:
6351 fm.startitem()
6352 fm.startitem()
6352 fm.write(b'reporoot', b'%s\n', repo.root)
6353 fm.write(b'reporoot', b'%s\n', repo.root)
6353 fm.data(hgpath=repo.path, storepath=repo.spath)
6354 fm.data(hgpath=repo.path, storepath=repo.spath)
6354
6355
6355
6356
6356 @command(
6357 @command(
6357 b'serve',
6358 b'serve',
6358 [
6359 [
6359 (
6360 (
6360 b'A',
6361 b'A',
6361 b'accesslog',
6362 b'accesslog',
6362 b'',
6363 b'',
6363 _(b'name of access log file to write to'),
6364 _(b'name of access log file to write to'),
6364 _(b'FILE'),
6365 _(b'FILE'),
6365 ),
6366 ),
6366 (b'd', b'daemon', None, _(b'run server in background')),
6367 (b'd', b'daemon', None, _(b'run server in background')),
6367 (b'', b'daemon-postexec', [], _(b'used internally by daemon mode')),
6368 (b'', b'daemon-postexec', [], _(b'used internally by daemon mode')),
6368 (
6369 (
6369 b'E',
6370 b'E',
6370 b'errorlog',
6371 b'errorlog',
6371 b'',
6372 b'',
6372 _(b'name of error log file to write to'),
6373 _(b'name of error log file to write to'),
6373 _(b'FILE'),
6374 _(b'FILE'),
6374 ),
6375 ),
6375 # use string type, then we can check if something was passed
6376 # use string type, then we can check if something was passed
6376 (
6377 (
6377 b'p',
6378 b'p',
6378 b'port',
6379 b'port',
6379 b'',
6380 b'',
6380 _(b'port to listen on (default: 8000)'),
6381 _(b'port to listen on (default: 8000)'),
6381 _(b'PORT'),
6382 _(b'PORT'),
6382 ),
6383 ),
6383 (
6384 (
6384 b'a',
6385 b'a',
6385 b'address',
6386 b'address',
6386 b'',
6387 b'',
6387 _(b'address to listen on (default: all interfaces)'),
6388 _(b'address to listen on (default: all interfaces)'),
6388 _(b'ADDR'),
6389 _(b'ADDR'),
6389 ),
6390 ),
6390 (
6391 (
6391 b'',
6392 b'',
6392 b'prefix',
6393 b'prefix',
6393 b'',
6394 b'',
6394 _(b'prefix path to serve from (default: server root)'),
6395 _(b'prefix path to serve from (default: server root)'),
6395 _(b'PREFIX'),
6396 _(b'PREFIX'),
6396 ),
6397 ),
6397 (
6398 (
6398 b'n',
6399 b'n',
6399 b'name',
6400 b'name',
6400 b'',
6401 b'',
6401 _(b'name to show in web pages (default: working directory)'),
6402 _(b'name to show in web pages (default: working directory)'),
6402 _(b'NAME'),
6403 _(b'NAME'),
6403 ),
6404 ),
6404 (
6405 (
6405 b'',
6406 b'',
6406 b'web-conf',
6407 b'web-conf',
6407 b'',
6408 b'',
6408 _(b"name of the hgweb config file (see 'hg help hgweb')"),
6409 _(b"name of the hgweb config file (see 'hg help hgweb')"),
6409 _(b'FILE'),
6410 _(b'FILE'),
6410 ),
6411 ),
6411 (
6412 (
6412 b'',
6413 b'',
6413 b'webdir-conf',
6414 b'webdir-conf',
6414 b'',
6415 b'',
6415 _(b'name of the hgweb config file (DEPRECATED)'),
6416 _(b'name of the hgweb config file (DEPRECATED)'),
6416 _(b'FILE'),
6417 _(b'FILE'),
6417 ),
6418 ),
6418 (
6419 (
6419 b'',
6420 b'',
6420 b'pid-file',
6421 b'pid-file',
6421 b'',
6422 b'',
6422 _(b'name of file to write process ID to'),
6423 _(b'name of file to write process ID to'),
6423 _(b'FILE'),
6424 _(b'FILE'),
6424 ),
6425 ),
6425 (b'', b'stdio', None, _(b'for remote clients (ADVANCED)')),
6426 (b'', b'stdio', None, _(b'for remote clients (ADVANCED)')),
6426 (
6427 (
6427 b'',
6428 b'',
6428 b'cmdserver',
6429 b'cmdserver',
6429 b'',
6430 b'',
6430 _(b'for remote clients (ADVANCED)'),
6431 _(b'for remote clients (ADVANCED)'),
6431 _(b'MODE'),
6432 _(b'MODE'),
6432 ),
6433 ),
6433 (b't', b'templates', b'', _(b'web templates to use'), _(b'TEMPLATE')),
6434 (b't', b'templates', b'', _(b'web templates to use'), _(b'TEMPLATE')),
6434 (b'', b'style', b'', _(b'template style to use'), _(b'STYLE')),
6435 (b'', b'style', b'', _(b'template style to use'), _(b'STYLE')),
6435 (b'6', b'ipv6', None, _(b'use IPv6 in addition to IPv4')),
6436 (b'6', b'ipv6', None, _(b'use IPv6 in addition to IPv4')),
6436 (b'', b'certificate', b'', _(b'SSL certificate file'), _(b'FILE')),
6437 (b'', b'certificate', b'', _(b'SSL certificate file'), _(b'FILE')),
6437 (b'', b'print-url', None, _(b'start and print only the URL')),
6438 (b'', b'print-url', None, _(b'start and print only the URL')),
6438 ]
6439 ]
6439 + subrepoopts,
6440 + subrepoopts,
6440 _(b'[OPTION]...'),
6441 _(b'[OPTION]...'),
6441 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
6442 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
6442 helpbasic=True,
6443 helpbasic=True,
6443 optionalrepo=True,
6444 optionalrepo=True,
6444 )
6445 )
6445 def serve(ui, repo, **opts):
6446 def serve(ui, repo, **opts):
6446 """start stand-alone webserver
6447 """start stand-alone webserver
6447
6448
6448 Start a local HTTP repository browser and pull server. You can use
6449 Start a local HTTP repository browser and pull server. You can use
6449 this for ad-hoc sharing and browsing of repositories. It is
6450 this for ad-hoc sharing and browsing of repositories. It is
6450 recommended to use a real web server to serve a repository for
6451 recommended to use a real web server to serve a repository for
6451 longer periods of time.
6452 longer periods of time.
6452
6453
6453 Please note that the server does not implement access control.
6454 Please note that the server does not implement access control.
6454 This means that, by default, anybody can read from the server and
6455 This means that, by default, anybody can read from the server and
6455 nobody can write to it by default. Set the ``web.allow-push``
6456 nobody can write to it by default. Set the ``web.allow-push``
6456 option to ``*`` to allow everybody to push to the server. You
6457 option to ``*`` to allow everybody to push to the server. You
6457 should use a real web server if you need to authenticate users.
6458 should use a real web server if you need to authenticate users.
6458
6459
6459 By default, the server logs accesses to stdout and errors to
6460 By default, the server logs accesses to stdout and errors to
6460 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
6461 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
6461 files.
6462 files.
6462
6463
6463 To have the server choose a free port number to listen on, specify
6464 To have the server choose a free port number to listen on, specify
6464 a port number of 0; in this case, the server will print the port
6465 a port number of 0; in this case, the server will print the port
6465 number it uses.
6466 number it uses.
6466
6467
6467 Returns 0 on success.
6468 Returns 0 on success.
6468 """
6469 """
6469
6470
6470 opts = pycompat.byteskwargs(opts)
6471 opts = pycompat.byteskwargs(opts)
6471 if opts[b"stdio"] and opts[b"cmdserver"]:
6472 if opts[b"stdio"] and opts[b"cmdserver"]:
6472 raise error.Abort(_(b"cannot use --stdio with --cmdserver"))
6473 raise error.Abort(_(b"cannot use --stdio with --cmdserver"))
6473 if opts[b"print_url"] and ui.verbose:
6474 if opts[b"print_url"] and ui.verbose:
6474 raise error.Abort(_(b"cannot use --print-url with --verbose"))
6475 raise error.Abort(_(b"cannot use --print-url with --verbose"))
6475
6476
6476 if opts[b"stdio"]:
6477 if opts[b"stdio"]:
6477 if repo is None:
6478 if repo is None:
6478 raise error.RepoError(
6479 raise error.RepoError(
6479 _(b"there is no Mercurial repository here (.hg not found)")
6480 _(b"there is no Mercurial repository here (.hg not found)")
6480 )
6481 )
6481 s = wireprotoserver.sshserver(ui, repo)
6482 s = wireprotoserver.sshserver(ui, repo)
6482 s.serve_forever()
6483 s.serve_forever()
6483
6484
6484 service = server.createservice(ui, repo, opts)
6485 service = server.createservice(ui, repo, opts)
6485 return server.runservice(opts, initfn=service.init, runfn=service.run)
6486 return server.runservice(opts, initfn=service.init, runfn=service.run)
6486
6487
6487
6488
6488 @command(
6489 @command(
6489 b'shelve',
6490 b'shelve',
6490 [
6491 [
6491 (
6492 (
6492 b'A',
6493 b'A',
6493 b'addremove',
6494 b'addremove',
6494 None,
6495 None,
6495 _(b'mark new/missing files as added/removed before shelving'),
6496 _(b'mark new/missing files as added/removed before shelving'),
6496 ),
6497 ),
6497 (b'u', b'unknown', None, _(b'store unknown files in the shelve')),
6498 (b'u', b'unknown', None, _(b'store unknown files in the shelve')),
6498 (b'', b'cleanup', None, _(b'delete all shelved changes')),
6499 (b'', b'cleanup', None, _(b'delete all shelved changes')),
6499 (
6500 (
6500 b'',
6501 b'',
6501 b'date',
6502 b'date',
6502 b'',
6503 b'',
6503 _(b'shelve with the specified commit date'),
6504 _(b'shelve with the specified commit date'),
6504 _(b'DATE'),
6505 _(b'DATE'),
6505 ),
6506 ),
6506 (b'd', b'delete', None, _(b'delete the named shelved change(s)')),
6507 (b'd', b'delete', None, _(b'delete the named shelved change(s)')),
6507 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
6508 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
6508 (
6509 (
6509 b'k',
6510 b'k',
6510 b'keep',
6511 b'keep',
6511 False,
6512 False,
6512 _(b'shelve, but keep changes in the working directory'),
6513 _(b'shelve, but keep changes in the working directory'),
6513 ),
6514 ),
6514 (b'l', b'list', None, _(b'list current shelves')),
6515 (b'l', b'list', None, _(b'list current shelves')),
6515 (b'm', b'message', b'', _(b'use text as shelve message'), _(b'TEXT')),
6516 (b'm', b'message', b'', _(b'use text as shelve message'), _(b'TEXT')),
6516 (
6517 (
6517 b'n',
6518 b'n',
6518 b'name',
6519 b'name',
6519 b'',
6520 b'',
6520 _(b'use the given name for the shelved commit'),
6521 _(b'use the given name for the shelved commit'),
6521 _(b'NAME'),
6522 _(b'NAME'),
6522 ),
6523 ),
6523 (
6524 (
6524 b'p',
6525 b'p',
6525 b'patch',
6526 b'patch',
6526 None,
6527 None,
6527 _(
6528 _(
6528 b'output patches for changes (provide the names of the shelved '
6529 b'output patches for changes (provide the names of the shelved '
6529 b'changes as positional arguments)'
6530 b'changes as positional arguments)'
6530 ),
6531 ),
6531 ),
6532 ),
6532 (b'i', b'interactive', None, _(b'interactive mode')),
6533 (b'i', b'interactive', None, _(b'interactive mode')),
6533 (
6534 (
6534 b'',
6535 b'',
6535 b'stat',
6536 b'stat',
6536 None,
6537 None,
6537 _(
6538 _(
6538 b'output diffstat-style summary of changes (provide the names of '
6539 b'output diffstat-style summary of changes (provide the names of '
6539 b'the shelved changes as positional arguments)'
6540 b'the shelved changes as positional arguments)'
6540 ),
6541 ),
6541 ),
6542 ),
6542 ]
6543 ]
6543 + cmdutil.walkopts,
6544 + cmdutil.walkopts,
6544 _(b'hg shelve [OPTION]... [FILE]...'),
6545 _(b'hg shelve [OPTION]... [FILE]...'),
6545 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6546 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6546 )
6547 )
6547 def shelve(ui, repo, *pats, **opts):
6548 def shelve(ui, repo, *pats, **opts):
6548 '''save and set aside changes from the working directory
6549 '''save and set aside changes from the working directory
6549
6550
6550 Shelving takes files that "hg status" reports as not clean, saves
6551 Shelving takes files that "hg status" reports as not clean, saves
6551 the modifications to a bundle (a shelved change), and reverts the
6552 the modifications to a bundle (a shelved change), and reverts the
6552 files so that their state in the working directory becomes clean.
6553 files so that their state in the working directory becomes clean.
6553
6554
6554 To restore these changes to the working directory, using "hg
6555 To restore these changes to the working directory, using "hg
6555 unshelve"; this will work even if you switch to a different
6556 unshelve"; this will work even if you switch to a different
6556 commit.
6557 commit.
6557
6558
6558 When no files are specified, "hg shelve" saves all not-clean
6559 When no files are specified, "hg shelve" saves all not-clean
6559 files. If specific files or directories are named, only changes to
6560 files. If specific files or directories are named, only changes to
6560 those files are shelved.
6561 those files are shelved.
6561
6562
6562 In bare shelve (when no files are specified, without interactive,
6563 In bare shelve (when no files are specified, without interactive,
6563 include and exclude option), shelving remembers information if the
6564 include and exclude option), shelving remembers information if the
6564 working directory was on newly created branch, in other words working
6565 working directory was on newly created branch, in other words working
6565 directory was on different branch than its first parent. In this
6566 directory was on different branch than its first parent. In this
6566 situation unshelving restores branch information to the working directory.
6567 situation unshelving restores branch information to the working directory.
6567
6568
6568 Each shelved change has a name that makes it easier to find later.
6569 Each shelved change has a name that makes it easier to find later.
6569 The name of a shelved change defaults to being based on the active
6570 The name of a shelved change defaults to being based on the active
6570 bookmark, or if there is no active bookmark, the current named
6571 bookmark, or if there is no active bookmark, the current named
6571 branch. To specify a different name, use ``--name``.
6572 branch. To specify a different name, use ``--name``.
6572
6573
6573 To see a list of existing shelved changes, use the ``--list``
6574 To see a list of existing shelved changes, use the ``--list``
6574 option. For each shelved change, this will print its name, age,
6575 option. For each shelved change, this will print its name, age,
6575 and description; use ``--patch`` or ``--stat`` for more details.
6576 and description; use ``--patch`` or ``--stat`` for more details.
6576
6577
6577 To delete specific shelved changes, use ``--delete``. To delete
6578 To delete specific shelved changes, use ``--delete``. To delete
6578 all shelved changes, use ``--cleanup``.
6579 all shelved changes, use ``--cleanup``.
6579 '''
6580 '''
6580 opts = pycompat.byteskwargs(opts)
6581 opts = pycompat.byteskwargs(opts)
6581 allowables = [
6582 allowables = [
6582 (b'addremove', {b'create'}), # 'create' is pseudo action
6583 (b'addremove', {b'create'}), # 'create' is pseudo action
6583 (b'unknown', {b'create'}),
6584 (b'unknown', {b'create'}),
6584 (b'cleanup', {b'cleanup'}),
6585 (b'cleanup', {b'cleanup'}),
6585 # ('date', {'create'}), # ignored for passing '--date "0 0"' in tests
6586 # ('date', {'create'}), # ignored for passing '--date "0 0"' in tests
6586 (b'delete', {b'delete'}),
6587 (b'delete', {b'delete'}),
6587 (b'edit', {b'create'}),
6588 (b'edit', {b'create'}),
6588 (b'keep', {b'create'}),
6589 (b'keep', {b'create'}),
6589 (b'list', {b'list'}),
6590 (b'list', {b'list'}),
6590 (b'message', {b'create'}),
6591 (b'message', {b'create'}),
6591 (b'name', {b'create'}),
6592 (b'name', {b'create'}),
6592 (b'patch', {b'patch', b'list'}),
6593 (b'patch', {b'patch', b'list'}),
6593 (b'stat', {b'stat', b'list'}),
6594 (b'stat', {b'stat', b'list'}),
6594 ]
6595 ]
6595
6596
6596 def checkopt(opt):
6597 def checkopt(opt):
6597 if opts.get(opt):
6598 if opts.get(opt):
6598 for i, allowable in allowables:
6599 for i, allowable in allowables:
6599 if opts[i] and opt not in allowable:
6600 if opts[i] and opt not in allowable:
6600 raise error.Abort(
6601 raise error.Abort(
6601 _(
6602 _(
6602 b"options '--%s' and '--%s' may not be "
6603 b"options '--%s' and '--%s' may not be "
6603 b"used together"
6604 b"used together"
6604 )
6605 )
6605 % (opt, i)
6606 % (opt, i)
6606 )
6607 )
6607 return True
6608 return True
6608
6609
6609 if checkopt(b'cleanup'):
6610 if checkopt(b'cleanup'):
6610 if pats:
6611 if pats:
6611 raise error.Abort(_(b"cannot specify names when using '--cleanup'"))
6612 raise error.Abort(_(b"cannot specify names when using '--cleanup'"))
6612 return shelvemod.cleanupcmd(ui, repo)
6613 return shelvemod.cleanupcmd(ui, repo)
6613 elif checkopt(b'delete'):
6614 elif checkopt(b'delete'):
6614 return shelvemod.deletecmd(ui, repo, pats)
6615 return shelvemod.deletecmd(ui, repo, pats)
6615 elif checkopt(b'list'):
6616 elif checkopt(b'list'):
6616 return shelvemod.listcmd(ui, repo, pats, opts)
6617 return shelvemod.listcmd(ui, repo, pats, opts)
6617 elif checkopt(b'patch') or checkopt(b'stat'):
6618 elif checkopt(b'patch') or checkopt(b'stat'):
6618 return shelvemod.patchcmds(ui, repo, pats, opts)
6619 return shelvemod.patchcmds(ui, repo, pats, opts)
6619 else:
6620 else:
6620 return shelvemod.createcmd(ui, repo, pats, opts)
6621 return shelvemod.createcmd(ui, repo, pats, opts)
6621
6622
6622
6623
6623 _NOTTERSE = b'nothing'
6624 _NOTTERSE = b'nothing'
6624
6625
6625
6626
6626 @command(
6627 @command(
6627 b'status|st',
6628 b'status|st',
6628 [
6629 [
6629 (b'A', b'all', None, _(b'show status of all files')),
6630 (b'A', b'all', None, _(b'show status of all files')),
6630 (b'm', b'modified', None, _(b'show only modified files')),
6631 (b'm', b'modified', None, _(b'show only modified files')),
6631 (b'a', b'added', None, _(b'show only added files')),
6632 (b'a', b'added', None, _(b'show only added files')),
6632 (b'r', b'removed', None, _(b'show only removed files')),
6633 (b'r', b'removed', None, _(b'show only removed files')),
6633 (b'd', b'deleted', None, _(b'show only missing files')),
6634 (b'd', b'deleted', None, _(b'show only missing files')),
6634 (b'c', b'clean', None, _(b'show only files without changes')),
6635 (b'c', b'clean', None, _(b'show only files without changes')),
6635 (b'u', b'unknown', None, _(b'show only unknown (not tracked) files')),
6636 (b'u', b'unknown', None, _(b'show only unknown (not tracked) files')),
6636 (b'i', b'ignored', None, _(b'show only ignored files')),
6637 (b'i', b'ignored', None, _(b'show only ignored files')),
6637 (b'n', b'no-status', None, _(b'hide status prefix')),
6638 (b'n', b'no-status', None, _(b'hide status prefix')),
6638 (b't', b'terse', _NOTTERSE, _(b'show the terse output (EXPERIMENTAL)')),
6639 (b't', b'terse', _NOTTERSE, _(b'show the terse output (EXPERIMENTAL)')),
6639 (
6640 (
6640 b'C',
6641 b'C',
6641 b'copies',
6642 b'copies',
6642 None,
6643 None,
6643 _(b'show source of copied files (DEFAULT: ui.statuscopies)'),
6644 _(b'show source of copied files (DEFAULT: ui.statuscopies)'),
6644 ),
6645 ),
6645 (
6646 (
6646 b'0',
6647 b'0',
6647 b'print0',
6648 b'print0',
6648 None,
6649 None,
6649 _(b'end filenames with NUL, for use with xargs'),
6650 _(b'end filenames with NUL, for use with xargs'),
6650 ),
6651 ),
6651 (b'', b'rev', [], _(b'show difference from revision'), _(b'REV')),
6652 (b'', b'rev', [], _(b'show difference from revision'), _(b'REV')),
6652 (
6653 (
6653 b'',
6654 b'',
6654 b'change',
6655 b'change',
6655 b'',
6656 b'',
6656 _(b'list the changed files of a revision'),
6657 _(b'list the changed files of a revision'),
6657 _(b'REV'),
6658 _(b'REV'),
6658 ),
6659 ),
6659 ]
6660 ]
6660 + walkopts
6661 + walkopts
6661 + subrepoopts
6662 + subrepoopts
6662 + formatteropts,
6663 + formatteropts,
6663 _(b'[OPTION]... [FILE]...'),
6664 _(b'[OPTION]... [FILE]...'),
6664 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6665 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6665 helpbasic=True,
6666 helpbasic=True,
6666 inferrepo=True,
6667 inferrepo=True,
6667 intents={INTENT_READONLY},
6668 intents={INTENT_READONLY},
6668 )
6669 )
6669 def status(ui, repo, *pats, **opts):
6670 def status(ui, repo, *pats, **opts):
6670 """show changed files in the working directory
6671 """show changed files in the working directory
6671
6672
6672 Show status of files in the repository. If names are given, only
6673 Show status of files in the repository. If names are given, only
6673 files that match are shown. Files that are clean or ignored or
6674 files that match are shown. Files that are clean or ignored or
6674 the source of a copy/move operation, are not listed unless
6675 the source of a copy/move operation, are not listed unless
6675 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
6676 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
6676 Unless options described with "show only ..." are given, the
6677 Unless options described with "show only ..." are given, the
6677 options -mardu are used.
6678 options -mardu are used.
6678
6679
6679 Option -q/--quiet hides untracked (unknown and ignored) files
6680 Option -q/--quiet hides untracked (unknown and ignored) files
6680 unless explicitly requested with -u/--unknown or -i/--ignored.
6681 unless explicitly requested with -u/--unknown or -i/--ignored.
6681
6682
6682 .. note::
6683 .. note::
6683
6684
6684 :hg:`status` may appear to disagree with diff if permissions have
6685 :hg:`status` may appear to disagree with diff if permissions have
6685 changed or a merge has occurred. The standard diff format does
6686 changed or a merge has occurred. The standard diff format does
6686 not report permission changes and diff only reports changes
6687 not report permission changes and diff only reports changes
6687 relative to one merge parent.
6688 relative to one merge parent.
6688
6689
6689 If one revision is given, it is used as the base revision.
6690 If one revision is given, it is used as the base revision.
6690 If two revisions are given, the differences between them are
6691 If two revisions are given, the differences between them are
6691 shown. The --change option can also be used as a shortcut to list
6692 shown. The --change option can also be used as a shortcut to list
6692 the changed files of a revision from its first parent.
6693 the changed files of a revision from its first parent.
6693
6694
6694 The codes used to show the status of files are::
6695 The codes used to show the status of files are::
6695
6696
6696 M = modified
6697 M = modified
6697 A = added
6698 A = added
6698 R = removed
6699 R = removed
6699 C = clean
6700 C = clean
6700 ! = missing (deleted by non-hg command, but still tracked)
6701 ! = missing (deleted by non-hg command, but still tracked)
6701 ? = not tracked
6702 ? = not tracked
6702 I = ignored
6703 I = ignored
6703 = origin of the previous file (with --copies)
6704 = origin of the previous file (with --copies)
6704
6705
6705 .. container:: verbose
6706 .. container:: verbose
6706
6707
6707 The -t/--terse option abbreviates the output by showing only the directory
6708 The -t/--terse option abbreviates the output by showing only the directory
6708 name if all the files in it share the same status. The option takes an
6709 name if all the files in it share the same status. The option takes an
6709 argument indicating the statuses to abbreviate: 'm' for 'modified', 'a'
6710 argument indicating the statuses to abbreviate: 'm' for 'modified', 'a'
6710 for 'added', 'r' for 'removed', 'd' for 'deleted', 'u' for 'unknown', 'i'
6711 for 'added', 'r' for 'removed', 'd' for 'deleted', 'u' for 'unknown', 'i'
6711 for 'ignored' and 'c' for clean.
6712 for 'ignored' and 'c' for clean.
6712
6713
6713 It abbreviates only those statuses which are passed. Note that clean and
6714 It abbreviates only those statuses which are passed. Note that clean and
6714 ignored files are not displayed with '--terse ic' unless the -c/--clean
6715 ignored files are not displayed with '--terse ic' unless the -c/--clean
6715 and -i/--ignored options are also used.
6716 and -i/--ignored options are also used.
6716
6717
6717 The -v/--verbose option shows information when the repository is in an
6718 The -v/--verbose option shows information when the repository is in an
6718 unfinished merge, shelve, rebase state etc. You can have this behavior
6719 unfinished merge, shelve, rebase state etc. You can have this behavior
6719 turned on by default by enabling the ``commands.status.verbose`` option.
6720 turned on by default by enabling the ``commands.status.verbose`` option.
6720
6721
6721 You can skip displaying some of these states by setting
6722 You can skip displaying some of these states by setting
6722 ``commands.status.skipstates`` to one or more of: 'bisect', 'graft',
6723 ``commands.status.skipstates`` to one or more of: 'bisect', 'graft',
6723 'histedit', 'merge', 'rebase', or 'unshelve'.
6724 'histedit', 'merge', 'rebase', or 'unshelve'.
6724
6725
6725 Template:
6726 Template:
6726
6727
6727 The following keywords are supported in addition to the common template
6728 The following keywords are supported in addition to the common template
6728 keywords and functions. See also :hg:`help templates`.
6729 keywords and functions. See also :hg:`help templates`.
6729
6730
6730 :path: String. Repository-absolute path of the file.
6731 :path: String. Repository-absolute path of the file.
6731 :source: String. Repository-absolute path of the file originated from.
6732 :source: String. Repository-absolute path of the file originated from.
6732 Available if ``--copies`` is specified.
6733 Available if ``--copies`` is specified.
6733 :status: String. Character denoting file's status.
6734 :status: String. Character denoting file's status.
6734
6735
6735 Examples:
6736 Examples:
6736
6737
6737 - show changes in the working directory relative to a
6738 - show changes in the working directory relative to a
6738 changeset::
6739 changeset::
6739
6740
6740 hg status --rev 9353
6741 hg status --rev 9353
6741
6742
6742 - show changes in the working directory relative to the
6743 - show changes in the working directory relative to the
6743 current directory (see :hg:`help patterns` for more information)::
6744 current directory (see :hg:`help patterns` for more information)::
6744
6745
6745 hg status re:
6746 hg status re:
6746
6747
6747 - show all changes including copies in an existing changeset::
6748 - show all changes including copies in an existing changeset::
6748
6749
6749 hg status --copies --change 9353
6750 hg status --copies --change 9353
6750
6751
6751 - get a NUL separated list of added files, suitable for xargs::
6752 - get a NUL separated list of added files, suitable for xargs::
6752
6753
6753 hg status -an0
6754 hg status -an0
6754
6755
6755 - show more information about the repository status, abbreviating
6756 - show more information about the repository status, abbreviating
6756 added, removed, modified, deleted, and untracked paths::
6757 added, removed, modified, deleted, and untracked paths::
6757
6758
6758 hg status -v -t mardu
6759 hg status -v -t mardu
6759
6760
6760 Returns 0 on success.
6761 Returns 0 on success.
6761
6762
6762 """
6763 """
6763
6764
6764 cmdutil.check_at_most_one_arg(opts, 'rev', 'change')
6765 cmdutil.check_at_most_one_arg(opts, 'rev', 'change')
6765 opts = pycompat.byteskwargs(opts)
6766 opts = pycompat.byteskwargs(opts)
6766 revs = opts.get(b'rev')
6767 revs = opts.get(b'rev')
6767 change = opts.get(b'change')
6768 change = opts.get(b'change')
6768 terse = opts.get(b'terse')
6769 terse = opts.get(b'terse')
6769 if terse is _NOTTERSE:
6770 if terse is _NOTTERSE:
6770 if revs:
6771 if revs:
6771 terse = b''
6772 terse = b''
6772 else:
6773 else:
6773 terse = ui.config(b'commands', b'status.terse')
6774 terse = ui.config(b'commands', b'status.terse')
6774
6775
6775 if revs and terse:
6776 if revs and terse:
6776 msg = _(b'cannot use --terse with --rev')
6777 msg = _(b'cannot use --terse with --rev')
6777 raise error.Abort(msg)
6778 raise error.Abort(msg)
6778 elif change:
6779 elif change:
6779 repo = scmutil.unhidehashlikerevs(repo, [change], b'nowarn')
6780 repo = scmutil.unhidehashlikerevs(repo, [change], b'nowarn')
6780 ctx2 = scmutil.revsingle(repo, change, None)
6781 ctx2 = scmutil.revsingle(repo, change, None)
6781 ctx1 = ctx2.p1()
6782 ctx1 = ctx2.p1()
6782 else:
6783 else:
6783 repo = scmutil.unhidehashlikerevs(repo, revs, b'nowarn')
6784 repo = scmutil.unhidehashlikerevs(repo, revs, b'nowarn')
6784 ctx1, ctx2 = scmutil.revpair(repo, revs)
6785 ctx1, ctx2 = scmutil.revpair(repo, revs)
6785
6786
6786 forcerelativevalue = None
6787 forcerelativevalue = None
6787 if ui.hasconfig(b'commands', b'status.relative'):
6788 if ui.hasconfig(b'commands', b'status.relative'):
6788 forcerelativevalue = ui.configbool(b'commands', b'status.relative')
6789 forcerelativevalue = ui.configbool(b'commands', b'status.relative')
6789 uipathfn = scmutil.getuipathfn(
6790 uipathfn = scmutil.getuipathfn(
6790 repo,
6791 repo,
6791 legacyrelativevalue=bool(pats),
6792 legacyrelativevalue=bool(pats),
6792 forcerelativevalue=forcerelativevalue,
6793 forcerelativevalue=forcerelativevalue,
6793 )
6794 )
6794
6795
6795 if opts.get(b'print0'):
6796 if opts.get(b'print0'):
6796 end = b'\0'
6797 end = b'\0'
6797 else:
6798 else:
6798 end = b'\n'
6799 end = b'\n'
6799 states = b'modified added removed deleted unknown ignored clean'.split()
6800 states = b'modified added removed deleted unknown ignored clean'.split()
6800 show = [k for k in states if opts.get(k)]
6801 show = [k for k in states if opts.get(k)]
6801 if opts.get(b'all'):
6802 if opts.get(b'all'):
6802 show += ui.quiet and (states[:4] + [b'clean']) or states
6803 show += ui.quiet and (states[:4] + [b'clean']) or states
6803
6804
6804 if not show:
6805 if not show:
6805 if ui.quiet:
6806 if ui.quiet:
6806 show = states[:4]
6807 show = states[:4]
6807 else:
6808 else:
6808 show = states[:5]
6809 show = states[:5]
6809
6810
6810 m = scmutil.match(ctx2, pats, opts)
6811 m = scmutil.match(ctx2, pats, opts)
6811 if terse:
6812 if terse:
6812 # we need to compute clean and unknown to terse
6813 # we need to compute clean and unknown to terse
6813 stat = repo.status(
6814 stat = repo.status(
6814 ctx1.node(),
6815 ctx1.node(),
6815 ctx2.node(),
6816 ctx2.node(),
6816 m,
6817 m,
6817 b'ignored' in show or b'i' in terse,
6818 b'ignored' in show or b'i' in terse,
6818 clean=True,
6819 clean=True,
6819 unknown=True,
6820 unknown=True,
6820 listsubrepos=opts.get(b'subrepos'),
6821 listsubrepos=opts.get(b'subrepos'),
6821 )
6822 )
6822
6823
6823 stat = cmdutil.tersedir(stat, terse)
6824 stat = cmdutil.tersedir(stat, terse)
6824 else:
6825 else:
6825 stat = repo.status(
6826 stat = repo.status(
6826 ctx1.node(),
6827 ctx1.node(),
6827 ctx2.node(),
6828 ctx2.node(),
6828 m,
6829 m,
6829 b'ignored' in show,
6830 b'ignored' in show,
6830 b'clean' in show,
6831 b'clean' in show,
6831 b'unknown' in show,
6832 b'unknown' in show,
6832 opts.get(b'subrepos'),
6833 opts.get(b'subrepos'),
6833 )
6834 )
6834
6835
6835 changestates = zip(
6836 changestates = zip(
6836 states,
6837 states,
6837 pycompat.iterbytestr(b'MAR!?IC'),
6838 pycompat.iterbytestr(b'MAR!?IC'),
6838 [getattr(stat, s.decode('utf8')) for s in states],
6839 [getattr(stat, s.decode('utf8')) for s in states],
6839 )
6840 )
6840
6841
6841 copy = {}
6842 copy = {}
6842 if (
6843 if (
6843 opts.get(b'all')
6844 opts.get(b'all')
6844 or opts.get(b'copies')
6845 or opts.get(b'copies')
6845 or ui.configbool(b'ui', b'statuscopies')
6846 or ui.configbool(b'ui', b'statuscopies')
6846 ) and not opts.get(b'no_status'):
6847 ) and not opts.get(b'no_status'):
6847 copy = copies.pathcopies(ctx1, ctx2, m)
6848 copy = copies.pathcopies(ctx1, ctx2, m)
6848
6849
6849 morestatus = None
6850 morestatus = None
6850 if (
6851 if (
6851 ui.verbose or ui.configbool(b'commands', b'status.verbose')
6852 ui.verbose or ui.configbool(b'commands', b'status.verbose')
6852 ) and not ui.plain():
6853 ) and not ui.plain():
6853 morestatus = cmdutil.readmorestatus(repo)
6854 morestatus = cmdutil.readmorestatus(repo)
6854
6855
6855 ui.pager(b'status')
6856 ui.pager(b'status')
6856 fm = ui.formatter(b'status', opts)
6857 fm = ui.formatter(b'status', opts)
6857 fmt = b'%s' + end
6858 fmt = b'%s' + end
6858 showchar = not opts.get(b'no_status')
6859 showchar = not opts.get(b'no_status')
6859
6860
6860 for state, char, files in changestates:
6861 for state, char, files in changestates:
6861 if state in show:
6862 if state in show:
6862 label = b'status.' + state
6863 label = b'status.' + state
6863 for f in files:
6864 for f in files:
6864 fm.startitem()
6865 fm.startitem()
6865 fm.context(ctx=ctx2)
6866 fm.context(ctx=ctx2)
6866 fm.data(itemtype=b'file', path=f)
6867 fm.data(itemtype=b'file', path=f)
6867 fm.condwrite(showchar, b'status', b'%s ', char, label=label)
6868 fm.condwrite(showchar, b'status', b'%s ', char, label=label)
6868 fm.plain(fmt % uipathfn(f), label=label)
6869 fm.plain(fmt % uipathfn(f), label=label)
6869 if f in copy:
6870 if f in copy:
6870 fm.data(source=copy[f])
6871 fm.data(source=copy[f])
6871 fm.plain(
6872 fm.plain(
6872 (b' %s' + end) % uipathfn(copy[f]),
6873 (b' %s' + end) % uipathfn(copy[f]),
6873 label=b'status.copied',
6874 label=b'status.copied',
6874 )
6875 )
6875 if morestatus:
6876 if morestatus:
6876 morestatus.formatfile(f, fm)
6877 morestatus.formatfile(f, fm)
6877
6878
6878 if morestatus:
6879 if morestatus:
6879 morestatus.formatfooter(fm)
6880 morestatus.formatfooter(fm)
6880 fm.end()
6881 fm.end()
6881
6882
6882
6883
6883 @command(
6884 @command(
6884 b'summary|sum',
6885 b'summary|sum',
6885 [(b'', b'remote', None, _(b'check for push and pull'))],
6886 [(b'', b'remote', None, _(b'check for push and pull'))],
6886 b'[--remote]',
6887 b'[--remote]',
6887 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6888 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6888 helpbasic=True,
6889 helpbasic=True,
6889 intents={INTENT_READONLY},
6890 intents={INTENT_READONLY},
6890 )
6891 )
6891 def summary(ui, repo, **opts):
6892 def summary(ui, repo, **opts):
6892 """summarize working directory state
6893 """summarize working directory state
6893
6894
6894 This generates a brief summary of the working directory state,
6895 This generates a brief summary of the working directory state,
6895 including parents, branch, commit status, phase and available updates.
6896 including parents, branch, commit status, phase and available updates.
6896
6897
6897 With the --remote option, this will check the default paths for
6898 With the --remote option, this will check the default paths for
6898 incoming and outgoing changes. This can be time-consuming.
6899 incoming and outgoing changes. This can be time-consuming.
6899
6900
6900 Returns 0 on success.
6901 Returns 0 on success.
6901 """
6902 """
6902
6903
6903 opts = pycompat.byteskwargs(opts)
6904 opts = pycompat.byteskwargs(opts)
6904 ui.pager(b'summary')
6905 ui.pager(b'summary')
6905 ctx = repo[None]
6906 ctx = repo[None]
6906 parents = ctx.parents()
6907 parents = ctx.parents()
6907 pnode = parents[0].node()
6908 pnode = parents[0].node()
6908 marks = []
6909 marks = []
6909
6910
6910 try:
6911 try:
6911 ms = mergestatemod.mergestate.read(repo)
6912 ms = mergestatemod.mergestate.read(repo)
6912 except error.UnsupportedMergeRecords as e:
6913 except error.UnsupportedMergeRecords as e:
6913 s = b' '.join(e.recordtypes)
6914 s = b' '.join(e.recordtypes)
6914 ui.warn(
6915 ui.warn(
6915 _(b'warning: merge state has unsupported record types: %s\n') % s
6916 _(b'warning: merge state has unsupported record types: %s\n') % s
6916 )
6917 )
6917 unresolved = []
6918 unresolved = []
6918 else:
6919 else:
6919 unresolved = list(ms.unresolved())
6920 unresolved = list(ms.unresolved())
6920
6921
6921 for p in parents:
6922 for p in parents:
6922 # label with log.changeset (instead of log.parent) since this
6923 # label with log.changeset (instead of log.parent) since this
6923 # shows a working directory parent *changeset*:
6924 # shows a working directory parent *changeset*:
6924 # i18n: column positioning for "hg summary"
6925 # i18n: column positioning for "hg summary"
6925 ui.write(
6926 ui.write(
6926 _(b'parent: %d:%s ') % (p.rev(), p),
6927 _(b'parent: %d:%s ') % (p.rev(), p),
6927 label=logcmdutil.changesetlabels(p),
6928 label=logcmdutil.changesetlabels(p),
6928 )
6929 )
6929 ui.write(b' '.join(p.tags()), label=b'log.tag')
6930 ui.write(b' '.join(p.tags()), label=b'log.tag')
6930 if p.bookmarks():
6931 if p.bookmarks():
6931 marks.extend(p.bookmarks())
6932 marks.extend(p.bookmarks())
6932 if p.rev() == -1:
6933 if p.rev() == -1:
6933 if not len(repo):
6934 if not len(repo):
6934 ui.write(_(b' (empty repository)'))
6935 ui.write(_(b' (empty repository)'))
6935 else:
6936 else:
6936 ui.write(_(b' (no revision checked out)'))
6937 ui.write(_(b' (no revision checked out)'))
6937 if p.obsolete():
6938 if p.obsolete():
6938 ui.write(_(b' (obsolete)'))
6939 ui.write(_(b' (obsolete)'))
6939 if p.isunstable():
6940 if p.isunstable():
6940 instabilities = (
6941 instabilities = (
6941 ui.label(instability, b'trouble.%s' % instability)
6942 ui.label(instability, b'trouble.%s' % instability)
6942 for instability in p.instabilities()
6943 for instability in p.instabilities()
6943 )
6944 )
6944 ui.write(b' (' + b', '.join(instabilities) + b')')
6945 ui.write(b' (' + b', '.join(instabilities) + b')')
6945 ui.write(b'\n')
6946 ui.write(b'\n')
6946 if p.description():
6947 if p.description():
6947 ui.status(
6948 ui.status(
6948 b' ' + p.description().splitlines()[0].strip() + b'\n',
6949 b' ' + p.description().splitlines()[0].strip() + b'\n',
6949 label=b'log.summary',
6950 label=b'log.summary',
6950 )
6951 )
6951
6952
6952 branch = ctx.branch()
6953 branch = ctx.branch()
6953 bheads = repo.branchheads(branch)
6954 bheads = repo.branchheads(branch)
6954 # i18n: column positioning for "hg summary"
6955 # i18n: column positioning for "hg summary"
6955 m = _(b'branch: %s\n') % branch
6956 m = _(b'branch: %s\n') % branch
6956 if branch != b'default':
6957 if branch != b'default':
6957 ui.write(m, label=b'log.branch')
6958 ui.write(m, label=b'log.branch')
6958 else:
6959 else:
6959 ui.status(m, label=b'log.branch')
6960 ui.status(m, label=b'log.branch')
6960
6961
6961 if marks:
6962 if marks:
6962 active = repo._activebookmark
6963 active = repo._activebookmark
6963 # i18n: column positioning for "hg summary"
6964 # i18n: column positioning for "hg summary"
6964 ui.write(_(b'bookmarks:'), label=b'log.bookmark')
6965 ui.write(_(b'bookmarks:'), label=b'log.bookmark')
6965 if active is not None:
6966 if active is not None:
6966 if active in marks:
6967 if active in marks:
6967 ui.write(b' *' + active, label=bookmarks.activebookmarklabel)
6968 ui.write(b' *' + active, label=bookmarks.activebookmarklabel)
6968 marks.remove(active)
6969 marks.remove(active)
6969 else:
6970 else:
6970 ui.write(b' [%s]' % active, label=bookmarks.activebookmarklabel)
6971 ui.write(b' [%s]' % active, label=bookmarks.activebookmarklabel)
6971 for m in marks:
6972 for m in marks:
6972 ui.write(b' ' + m, label=b'log.bookmark')
6973 ui.write(b' ' + m, label=b'log.bookmark')
6973 ui.write(b'\n', label=b'log.bookmark')
6974 ui.write(b'\n', label=b'log.bookmark')
6974
6975
6975 status = repo.status(unknown=True)
6976 status = repo.status(unknown=True)
6976
6977
6977 c = repo.dirstate.copies()
6978 c = repo.dirstate.copies()
6978 copied, renamed = [], []
6979 copied, renamed = [], []
6979 for d, s in pycompat.iteritems(c):
6980 for d, s in pycompat.iteritems(c):
6980 if s in status.removed:
6981 if s in status.removed:
6981 status.removed.remove(s)
6982 status.removed.remove(s)
6982 renamed.append(d)
6983 renamed.append(d)
6983 else:
6984 else:
6984 copied.append(d)
6985 copied.append(d)
6985 if d in status.added:
6986 if d in status.added:
6986 status.added.remove(d)
6987 status.added.remove(d)
6987
6988
6988 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
6989 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
6989
6990
6990 labels = [
6991 labels = [
6991 (ui.label(_(b'%d modified'), b'status.modified'), status.modified),
6992 (ui.label(_(b'%d modified'), b'status.modified'), status.modified),
6992 (ui.label(_(b'%d added'), b'status.added'), status.added),
6993 (ui.label(_(b'%d added'), b'status.added'), status.added),
6993 (ui.label(_(b'%d removed'), b'status.removed'), status.removed),
6994 (ui.label(_(b'%d removed'), b'status.removed'), status.removed),
6994 (ui.label(_(b'%d renamed'), b'status.copied'), renamed),
6995 (ui.label(_(b'%d renamed'), b'status.copied'), renamed),
6995 (ui.label(_(b'%d copied'), b'status.copied'), copied),
6996 (ui.label(_(b'%d copied'), b'status.copied'), copied),
6996 (ui.label(_(b'%d deleted'), b'status.deleted'), status.deleted),
6997 (ui.label(_(b'%d deleted'), b'status.deleted'), status.deleted),
6997 (ui.label(_(b'%d unknown'), b'status.unknown'), status.unknown),
6998 (ui.label(_(b'%d unknown'), b'status.unknown'), status.unknown),
6998 (ui.label(_(b'%d unresolved'), b'resolve.unresolved'), unresolved),
6999 (ui.label(_(b'%d unresolved'), b'resolve.unresolved'), unresolved),
6999 (ui.label(_(b'%d subrepos'), b'status.modified'), subs),
7000 (ui.label(_(b'%d subrepos'), b'status.modified'), subs),
7000 ]
7001 ]
7001 t = []
7002 t = []
7002 for l, s in labels:
7003 for l, s in labels:
7003 if s:
7004 if s:
7004 t.append(l % len(s))
7005 t.append(l % len(s))
7005
7006
7006 t = b', '.join(t)
7007 t = b', '.join(t)
7007 cleanworkdir = False
7008 cleanworkdir = False
7008
7009
7009 if repo.vfs.exists(b'graftstate'):
7010 if repo.vfs.exists(b'graftstate'):
7010 t += _(b' (graft in progress)')
7011 t += _(b' (graft in progress)')
7011 if repo.vfs.exists(b'updatestate'):
7012 if repo.vfs.exists(b'updatestate'):
7012 t += _(b' (interrupted update)')
7013 t += _(b' (interrupted update)')
7013 elif len(parents) > 1:
7014 elif len(parents) > 1:
7014 t += _(b' (merge)')
7015 t += _(b' (merge)')
7015 elif branch != parents[0].branch():
7016 elif branch != parents[0].branch():
7016 t += _(b' (new branch)')
7017 t += _(b' (new branch)')
7017 elif parents[0].closesbranch() and pnode in repo.branchheads(
7018 elif parents[0].closesbranch() and pnode in repo.branchheads(
7018 branch, closed=True
7019 branch, closed=True
7019 ):
7020 ):
7020 t += _(b' (head closed)')
7021 t += _(b' (head closed)')
7021 elif not (
7022 elif not (
7022 status.modified
7023 status.modified
7023 or status.added
7024 or status.added
7024 or status.removed
7025 or status.removed
7025 or renamed
7026 or renamed
7026 or copied
7027 or copied
7027 or subs
7028 or subs
7028 ):
7029 ):
7029 t += _(b' (clean)')
7030 t += _(b' (clean)')
7030 cleanworkdir = True
7031 cleanworkdir = True
7031 elif pnode not in bheads:
7032 elif pnode not in bheads:
7032 t += _(b' (new branch head)')
7033 t += _(b' (new branch head)')
7033
7034
7034 if parents:
7035 if parents:
7035 pendingphase = max(p.phase() for p in parents)
7036 pendingphase = max(p.phase() for p in parents)
7036 else:
7037 else:
7037 pendingphase = phases.public
7038 pendingphase = phases.public
7038
7039
7039 if pendingphase > phases.newcommitphase(ui):
7040 if pendingphase > phases.newcommitphase(ui):
7040 t += b' (%s)' % phases.phasenames[pendingphase]
7041 t += b' (%s)' % phases.phasenames[pendingphase]
7041
7042
7042 if cleanworkdir:
7043 if cleanworkdir:
7043 # i18n: column positioning for "hg summary"
7044 # i18n: column positioning for "hg summary"
7044 ui.status(_(b'commit: %s\n') % t.strip())
7045 ui.status(_(b'commit: %s\n') % t.strip())
7045 else:
7046 else:
7046 # i18n: column positioning for "hg summary"
7047 # i18n: column positioning for "hg summary"
7047 ui.write(_(b'commit: %s\n') % t.strip())
7048 ui.write(_(b'commit: %s\n') % t.strip())
7048
7049
7049 # all ancestors of branch heads - all ancestors of parent = new csets
7050 # all ancestors of branch heads - all ancestors of parent = new csets
7050 new = len(
7051 new = len(
7051 repo.changelog.findmissing([pctx.node() for pctx in parents], bheads)
7052 repo.changelog.findmissing([pctx.node() for pctx in parents], bheads)
7052 )
7053 )
7053
7054
7054 if new == 0:
7055 if new == 0:
7055 # i18n: column positioning for "hg summary"
7056 # i18n: column positioning for "hg summary"
7056 ui.status(_(b'update: (current)\n'))
7057 ui.status(_(b'update: (current)\n'))
7057 elif pnode not in bheads:
7058 elif pnode not in bheads:
7058 # i18n: column positioning for "hg summary"
7059 # i18n: column positioning for "hg summary"
7059 ui.write(_(b'update: %d new changesets (update)\n') % new)
7060 ui.write(_(b'update: %d new changesets (update)\n') % new)
7060 else:
7061 else:
7061 # i18n: column positioning for "hg summary"
7062 # i18n: column positioning for "hg summary"
7062 ui.write(
7063 ui.write(
7063 _(b'update: %d new changesets, %d branch heads (merge)\n')
7064 _(b'update: %d new changesets, %d branch heads (merge)\n')
7064 % (new, len(bheads))
7065 % (new, len(bheads))
7065 )
7066 )
7066
7067
7067 t = []
7068 t = []
7068 draft = len(repo.revs(b'draft()'))
7069 draft = len(repo.revs(b'draft()'))
7069 if draft:
7070 if draft:
7070 t.append(_(b'%d draft') % draft)
7071 t.append(_(b'%d draft') % draft)
7071 secret = len(repo.revs(b'secret()'))
7072 secret = len(repo.revs(b'secret()'))
7072 if secret:
7073 if secret:
7073 t.append(_(b'%d secret') % secret)
7074 t.append(_(b'%d secret') % secret)
7074
7075
7075 if draft or secret:
7076 if draft or secret:
7076 ui.status(_(b'phases: %s\n') % b', '.join(t))
7077 ui.status(_(b'phases: %s\n') % b', '.join(t))
7077
7078
7078 if obsolete.isenabled(repo, obsolete.createmarkersopt):
7079 if obsolete.isenabled(repo, obsolete.createmarkersopt):
7079 for trouble in (b"orphan", b"contentdivergent", b"phasedivergent"):
7080 for trouble in (b"orphan", b"contentdivergent", b"phasedivergent"):
7080 numtrouble = len(repo.revs(trouble + b"()"))
7081 numtrouble = len(repo.revs(trouble + b"()"))
7081 # We write all the possibilities to ease translation
7082 # We write all the possibilities to ease translation
7082 troublemsg = {
7083 troublemsg = {
7083 b"orphan": _(b"orphan: %d changesets"),
7084 b"orphan": _(b"orphan: %d changesets"),
7084 b"contentdivergent": _(b"content-divergent: %d changesets"),
7085 b"contentdivergent": _(b"content-divergent: %d changesets"),
7085 b"phasedivergent": _(b"phase-divergent: %d changesets"),
7086 b"phasedivergent": _(b"phase-divergent: %d changesets"),
7086 }
7087 }
7087 if numtrouble > 0:
7088 if numtrouble > 0:
7088 ui.status(troublemsg[trouble] % numtrouble + b"\n")
7089 ui.status(troublemsg[trouble] % numtrouble + b"\n")
7089
7090
7090 cmdutil.summaryhooks(ui, repo)
7091 cmdutil.summaryhooks(ui, repo)
7091
7092
7092 if opts.get(b'remote'):
7093 if opts.get(b'remote'):
7093 needsincoming, needsoutgoing = True, True
7094 needsincoming, needsoutgoing = True, True
7094 else:
7095 else:
7095 needsincoming, needsoutgoing = False, False
7096 needsincoming, needsoutgoing = False, False
7096 for i, o in cmdutil.summaryremotehooks(ui, repo, opts, None):
7097 for i, o in cmdutil.summaryremotehooks(ui, repo, opts, None):
7097 if i:
7098 if i:
7098 needsincoming = True
7099 needsincoming = True
7099 if o:
7100 if o:
7100 needsoutgoing = True
7101 needsoutgoing = True
7101 if not needsincoming and not needsoutgoing:
7102 if not needsincoming and not needsoutgoing:
7102 return
7103 return
7103
7104
7104 def getincoming():
7105 def getincoming():
7105 source, branches = hg.parseurl(ui.expandpath(b'default'))
7106 source, branches = hg.parseurl(ui.expandpath(b'default'))
7106 sbranch = branches[0]
7107 sbranch = branches[0]
7107 try:
7108 try:
7108 other = hg.peer(repo, {}, source)
7109 other = hg.peer(repo, {}, source)
7109 except error.RepoError:
7110 except error.RepoError:
7110 if opts.get(b'remote'):
7111 if opts.get(b'remote'):
7111 raise
7112 raise
7112 return source, sbranch, None, None, None
7113 return source, sbranch, None, None, None
7113 revs, checkout = hg.addbranchrevs(repo, other, branches, None)
7114 revs, checkout = hg.addbranchrevs(repo, other, branches, None)
7114 if revs:
7115 if revs:
7115 revs = [other.lookup(rev) for rev in revs]
7116 revs = [other.lookup(rev) for rev in revs]
7116 ui.debug(b'comparing with %s\n' % util.hidepassword(source))
7117 ui.debug(b'comparing with %s\n' % util.hidepassword(source))
7117 repo.ui.pushbuffer()
7118 repo.ui.pushbuffer()
7118 commoninc = discovery.findcommonincoming(repo, other, heads=revs)
7119 commoninc = discovery.findcommonincoming(repo, other, heads=revs)
7119 repo.ui.popbuffer()
7120 repo.ui.popbuffer()
7120 return source, sbranch, other, commoninc, commoninc[1]
7121 return source, sbranch, other, commoninc, commoninc[1]
7121
7122
7122 if needsincoming:
7123 if needsincoming:
7123 source, sbranch, sother, commoninc, incoming = getincoming()
7124 source, sbranch, sother, commoninc, incoming = getincoming()
7124 else:
7125 else:
7125 source = sbranch = sother = commoninc = incoming = None
7126 source = sbranch = sother = commoninc = incoming = None
7126
7127
7127 def getoutgoing():
7128 def getoutgoing():
7128 dest, branches = hg.parseurl(ui.expandpath(b'default-push', b'default'))
7129 dest, branches = hg.parseurl(ui.expandpath(b'default-push', b'default'))
7129 dbranch = branches[0]
7130 dbranch = branches[0]
7130 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
7131 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
7131 if source != dest:
7132 if source != dest:
7132 try:
7133 try:
7133 dother = hg.peer(repo, {}, dest)
7134 dother = hg.peer(repo, {}, dest)
7134 except error.RepoError:
7135 except error.RepoError:
7135 if opts.get(b'remote'):
7136 if opts.get(b'remote'):
7136 raise
7137 raise
7137 return dest, dbranch, None, None
7138 return dest, dbranch, None, None
7138 ui.debug(b'comparing with %s\n' % util.hidepassword(dest))
7139 ui.debug(b'comparing with %s\n' % util.hidepassword(dest))
7139 elif sother is None:
7140 elif sother is None:
7140 # there is no explicit destination peer, but source one is invalid
7141 # there is no explicit destination peer, but source one is invalid
7141 return dest, dbranch, None, None
7142 return dest, dbranch, None, None
7142 else:
7143 else:
7143 dother = sother
7144 dother = sother
7144 if source != dest or (sbranch is not None and sbranch != dbranch):
7145 if source != dest or (sbranch is not None and sbranch != dbranch):
7145 common = None
7146 common = None
7146 else:
7147 else:
7147 common = commoninc
7148 common = commoninc
7148 if revs:
7149 if revs:
7149 revs = [repo.lookup(rev) for rev in revs]
7150 revs = [repo.lookup(rev) for rev in revs]
7150 repo.ui.pushbuffer()
7151 repo.ui.pushbuffer()
7151 outgoing = discovery.findcommonoutgoing(
7152 outgoing = discovery.findcommonoutgoing(
7152 repo, dother, onlyheads=revs, commoninc=common
7153 repo, dother, onlyheads=revs, commoninc=common
7153 )
7154 )
7154 repo.ui.popbuffer()
7155 repo.ui.popbuffer()
7155 return dest, dbranch, dother, outgoing
7156 return dest, dbranch, dother, outgoing
7156
7157
7157 if needsoutgoing:
7158 if needsoutgoing:
7158 dest, dbranch, dother, outgoing = getoutgoing()
7159 dest, dbranch, dother, outgoing = getoutgoing()
7159 else:
7160 else:
7160 dest = dbranch = dother = outgoing = None
7161 dest = dbranch = dother = outgoing = None
7161
7162
7162 if opts.get(b'remote'):
7163 if opts.get(b'remote'):
7163 t = []
7164 t = []
7164 if incoming:
7165 if incoming:
7165 t.append(_(b'1 or more incoming'))
7166 t.append(_(b'1 or more incoming'))
7166 o = outgoing.missing
7167 o = outgoing.missing
7167 if o:
7168 if o:
7168 t.append(_(b'%d outgoing') % len(o))
7169 t.append(_(b'%d outgoing') % len(o))
7169 other = dother or sother
7170 other = dother or sother
7170 if b'bookmarks' in other.listkeys(b'namespaces'):
7171 if b'bookmarks' in other.listkeys(b'namespaces'):
7171 counts = bookmarks.summary(repo, other)
7172 counts = bookmarks.summary(repo, other)
7172 if counts[0] > 0:
7173 if counts[0] > 0:
7173 t.append(_(b'%d incoming bookmarks') % counts[0])
7174 t.append(_(b'%d incoming bookmarks') % counts[0])
7174 if counts[1] > 0:
7175 if counts[1] > 0:
7175 t.append(_(b'%d outgoing bookmarks') % counts[1])
7176 t.append(_(b'%d outgoing bookmarks') % counts[1])
7176
7177
7177 if t:
7178 if t:
7178 # i18n: column positioning for "hg summary"
7179 # i18n: column positioning for "hg summary"
7179 ui.write(_(b'remote: %s\n') % (b', '.join(t)))
7180 ui.write(_(b'remote: %s\n') % (b', '.join(t)))
7180 else:
7181 else:
7181 # i18n: column positioning for "hg summary"
7182 # i18n: column positioning for "hg summary"
7182 ui.status(_(b'remote: (synced)\n'))
7183 ui.status(_(b'remote: (synced)\n'))
7183
7184
7184 cmdutil.summaryremotehooks(
7185 cmdutil.summaryremotehooks(
7185 ui,
7186 ui,
7186 repo,
7187 repo,
7187 opts,
7188 opts,
7188 (
7189 (
7189 (source, sbranch, sother, commoninc),
7190 (source, sbranch, sother, commoninc),
7190 (dest, dbranch, dother, outgoing),
7191 (dest, dbranch, dother, outgoing),
7191 ),
7192 ),
7192 )
7193 )
7193
7194
7194
7195
7195 @command(
7196 @command(
7196 b'tag',
7197 b'tag',
7197 [
7198 [
7198 (b'f', b'force', None, _(b'force tag')),
7199 (b'f', b'force', None, _(b'force tag')),
7199 (b'l', b'local', None, _(b'make the tag local')),
7200 (b'l', b'local', None, _(b'make the tag local')),
7200 (b'r', b'rev', b'', _(b'revision to tag'), _(b'REV')),
7201 (b'r', b'rev', b'', _(b'revision to tag'), _(b'REV')),
7201 (b'', b'remove', None, _(b'remove a tag')),
7202 (b'', b'remove', None, _(b'remove a tag')),
7202 # -l/--local is already there, commitopts cannot be used
7203 # -l/--local is already there, commitopts cannot be used
7203 (b'e', b'edit', None, _(b'invoke editor on commit messages')),
7204 (b'e', b'edit', None, _(b'invoke editor on commit messages')),
7204 (b'm', b'message', b'', _(b'use text as commit message'), _(b'TEXT')),
7205 (b'm', b'message', b'', _(b'use text as commit message'), _(b'TEXT')),
7205 ]
7206 ]
7206 + commitopts2,
7207 + commitopts2,
7207 _(b'[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'),
7208 _(b'[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'),
7208 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
7209 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
7209 )
7210 )
7210 def tag(ui, repo, name1, *names, **opts):
7211 def tag(ui, repo, name1, *names, **opts):
7211 """add one or more tags for the current or given revision
7212 """add one or more tags for the current or given revision
7212
7213
7213 Name a particular revision using <name>.
7214 Name a particular revision using <name>.
7214
7215
7215 Tags are used to name particular revisions of the repository and are
7216 Tags are used to name particular revisions of the repository and are
7216 very useful to compare different revisions, to go back to significant
7217 very useful to compare different revisions, to go back to significant
7217 earlier versions or to mark branch points as releases, etc. Changing
7218 earlier versions or to mark branch points as releases, etc. Changing
7218 an existing tag is normally disallowed; use -f/--force to override.
7219 an existing tag is normally disallowed; use -f/--force to override.
7219
7220
7220 If no revision is given, the parent of the working directory is
7221 If no revision is given, the parent of the working directory is
7221 used.
7222 used.
7222
7223
7223 To facilitate version control, distribution, and merging of tags,
7224 To facilitate version control, distribution, and merging of tags,
7224 they are stored as a file named ".hgtags" which is managed similarly
7225 they are stored as a file named ".hgtags" which is managed similarly
7225 to other project files and can be hand-edited if necessary. This
7226 to other project files and can be hand-edited if necessary. This
7226 also means that tagging creates a new commit. The file
7227 also means that tagging creates a new commit. The file
7227 ".hg/localtags" is used for local tags (not shared among
7228 ".hg/localtags" is used for local tags (not shared among
7228 repositories).
7229 repositories).
7229
7230
7230 Tag commits are usually made at the head of a branch. If the parent
7231 Tag commits are usually made at the head of a branch. If the parent
7231 of the working directory is not a branch head, :hg:`tag` aborts; use
7232 of the working directory is not a branch head, :hg:`tag` aborts; use
7232 -f/--force to force the tag commit to be based on a non-head
7233 -f/--force to force the tag commit to be based on a non-head
7233 changeset.
7234 changeset.
7234
7235
7235 See :hg:`help dates` for a list of formats valid for -d/--date.
7236 See :hg:`help dates` for a list of formats valid for -d/--date.
7236
7237
7237 Since tag names have priority over branch names during revision
7238 Since tag names have priority over branch names during revision
7238 lookup, using an existing branch name as a tag name is discouraged.
7239 lookup, using an existing branch name as a tag name is discouraged.
7239
7240
7240 Returns 0 on success.
7241 Returns 0 on success.
7241 """
7242 """
7242 opts = pycompat.byteskwargs(opts)
7243 opts = pycompat.byteskwargs(opts)
7243 with repo.wlock(), repo.lock():
7244 with repo.wlock(), repo.lock():
7244 rev_ = b"."
7245 rev_ = b"."
7245 names = [t.strip() for t in (name1,) + names]
7246 names = [t.strip() for t in (name1,) + names]
7246 if len(names) != len(set(names)):
7247 if len(names) != len(set(names)):
7247 raise error.Abort(_(b'tag names must be unique'))
7248 raise error.Abort(_(b'tag names must be unique'))
7248 for n in names:
7249 for n in names:
7249 scmutil.checknewlabel(repo, n, b'tag')
7250 scmutil.checknewlabel(repo, n, b'tag')
7250 if not n:
7251 if not n:
7251 raise error.Abort(
7252 raise error.Abort(
7252 _(b'tag names cannot consist entirely of whitespace')
7253 _(b'tag names cannot consist entirely of whitespace')
7253 )
7254 )
7254 if opts.get(b'rev') and opts.get(b'remove'):
7255 if opts.get(b'rev') and opts.get(b'remove'):
7255 raise error.Abort(_(b"--rev and --remove are incompatible"))
7256 raise error.Abort(_(b"--rev and --remove are incompatible"))
7256 if opts.get(b'rev'):
7257 if opts.get(b'rev'):
7257 rev_ = opts[b'rev']
7258 rev_ = opts[b'rev']
7258 message = opts.get(b'message')
7259 message = opts.get(b'message')
7259 if opts.get(b'remove'):
7260 if opts.get(b'remove'):
7260 if opts.get(b'local'):
7261 if opts.get(b'local'):
7261 expectedtype = b'local'
7262 expectedtype = b'local'
7262 else:
7263 else:
7263 expectedtype = b'global'
7264 expectedtype = b'global'
7264
7265
7265 for n in names:
7266 for n in names:
7266 if repo.tagtype(n) == b'global':
7267 if repo.tagtype(n) == b'global':
7267 alltags = tagsmod.findglobaltags(ui, repo)
7268 alltags = tagsmod.findglobaltags(ui, repo)
7268 if alltags[n][0] == nullid:
7269 if alltags[n][0] == nullid:
7269 raise error.Abort(_(b"tag '%s' is already removed") % n)
7270 raise error.Abort(_(b"tag '%s' is already removed") % n)
7270 if not repo.tagtype(n):
7271 if not repo.tagtype(n):
7271 raise error.Abort(_(b"tag '%s' does not exist") % n)
7272 raise error.Abort(_(b"tag '%s' does not exist") % n)
7272 if repo.tagtype(n) != expectedtype:
7273 if repo.tagtype(n) != expectedtype:
7273 if expectedtype == b'global':
7274 if expectedtype == b'global':
7274 raise error.Abort(
7275 raise error.Abort(
7275 _(b"tag '%s' is not a global tag") % n
7276 _(b"tag '%s' is not a global tag") % n
7276 )
7277 )
7277 else:
7278 else:
7278 raise error.Abort(_(b"tag '%s' is not a local tag") % n)
7279 raise error.Abort(_(b"tag '%s' is not a local tag") % n)
7279 rev_ = b'null'
7280 rev_ = b'null'
7280 if not message:
7281 if not message:
7281 # we don't translate commit messages
7282 # we don't translate commit messages
7282 message = b'Removed tag %s' % b', '.join(names)
7283 message = b'Removed tag %s' % b', '.join(names)
7283 elif not opts.get(b'force'):
7284 elif not opts.get(b'force'):
7284 for n in names:
7285 for n in names:
7285 if n in repo.tags():
7286 if n in repo.tags():
7286 raise error.Abort(
7287 raise error.Abort(
7287 _(b"tag '%s' already exists (use -f to force)") % n
7288 _(b"tag '%s' already exists (use -f to force)") % n
7288 )
7289 )
7289 if not opts.get(b'local'):
7290 if not opts.get(b'local'):
7290 p1, p2 = repo.dirstate.parents()
7291 p1, p2 = repo.dirstate.parents()
7291 if p2 != nullid:
7292 if p2 != nullid:
7292 raise error.Abort(_(b'uncommitted merge'))
7293 raise error.Abort(_(b'uncommitted merge'))
7293 bheads = repo.branchheads()
7294 bheads = repo.branchheads()
7294 if not opts.get(b'force') and bheads and p1 not in bheads:
7295 if not opts.get(b'force') and bheads and p1 not in bheads:
7295 raise error.Abort(
7296 raise error.Abort(
7296 _(
7297 _(
7297 b'working directory is not at a branch head '
7298 b'working directory is not at a branch head '
7298 b'(use -f to force)'
7299 b'(use -f to force)'
7299 )
7300 )
7300 )
7301 )
7301 node = scmutil.revsingle(repo, rev_).node()
7302 node = scmutil.revsingle(repo, rev_).node()
7302
7303
7303 if not message:
7304 if not message:
7304 # we don't translate commit messages
7305 # we don't translate commit messages
7305 message = b'Added tag %s for changeset %s' % (
7306 message = b'Added tag %s for changeset %s' % (
7306 b', '.join(names),
7307 b', '.join(names),
7307 short(node),
7308 short(node),
7308 )
7309 )
7309
7310
7310 date = opts.get(b'date')
7311 date = opts.get(b'date')
7311 if date:
7312 if date:
7312 date = dateutil.parsedate(date)
7313 date = dateutil.parsedate(date)
7313
7314
7314 if opts.get(b'remove'):
7315 if opts.get(b'remove'):
7315 editform = b'tag.remove'
7316 editform = b'tag.remove'
7316 else:
7317 else:
7317 editform = b'tag.add'
7318 editform = b'tag.add'
7318 editor = cmdutil.getcommiteditor(
7319 editor = cmdutil.getcommiteditor(
7319 editform=editform, **pycompat.strkwargs(opts)
7320 editform=editform, **pycompat.strkwargs(opts)
7320 )
7321 )
7321
7322
7322 # don't allow tagging the null rev
7323 # don't allow tagging the null rev
7323 if (
7324 if (
7324 not opts.get(b'remove')
7325 not opts.get(b'remove')
7325 and scmutil.revsingle(repo, rev_).rev() == nullrev
7326 and scmutil.revsingle(repo, rev_).rev() == nullrev
7326 ):
7327 ):
7327 raise error.Abort(_(b"cannot tag null revision"))
7328 raise error.Abort(_(b"cannot tag null revision"))
7328
7329
7329 tagsmod.tag(
7330 tagsmod.tag(
7330 repo,
7331 repo,
7331 names,
7332 names,
7332 node,
7333 node,
7333 message,
7334 message,
7334 opts.get(b'local'),
7335 opts.get(b'local'),
7335 opts.get(b'user'),
7336 opts.get(b'user'),
7336 date,
7337 date,
7337 editor=editor,
7338 editor=editor,
7338 )
7339 )
7339
7340
7340
7341
7341 @command(
7342 @command(
7342 b'tags',
7343 b'tags',
7343 formatteropts,
7344 formatteropts,
7344 b'',
7345 b'',
7345 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
7346 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
7346 intents={INTENT_READONLY},
7347 intents={INTENT_READONLY},
7347 )
7348 )
7348 def tags(ui, repo, **opts):
7349 def tags(ui, repo, **opts):
7349 """list repository tags
7350 """list repository tags
7350
7351
7351 This lists both regular and local tags. When the -v/--verbose
7352 This lists both regular and local tags. When the -v/--verbose
7352 switch is used, a third column "local" is printed for local tags.
7353 switch is used, a third column "local" is printed for local tags.
7353 When the -q/--quiet switch is used, only the tag name is printed.
7354 When the -q/--quiet switch is used, only the tag name is printed.
7354
7355
7355 .. container:: verbose
7356 .. container:: verbose
7356
7357
7357 Template:
7358 Template:
7358
7359
7359 The following keywords are supported in addition to the common template
7360 The following keywords are supported in addition to the common template
7360 keywords and functions such as ``{tag}``. See also
7361 keywords and functions such as ``{tag}``. See also
7361 :hg:`help templates`.
7362 :hg:`help templates`.
7362
7363
7363 :type: String. ``local`` for local tags.
7364 :type: String. ``local`` for local tags.
7364
7365
7365 Returns 0 on success.
7366 Returns 0 on success.
7366 """
7367 """
7367
7368
7368 opts = pycompat.byteskwargs(opts)
7369 opts = pycompat.byteskwargs(opts)
7369 ui.pager(b'tags')
7370 ui.pager(b'tags')
7370 fm = ui.formatter(b'tags', opts)
7371 fm = ui.formatter(b'tags', opts)
7371 hexfunc = fm.hexfunc
7372 hexfunc = fm.hexfunc
7372
7373
7373 for t, n in reversed(repo.tagslist()):
7374 for t, n in reversed(repo.tagslist()):
7374 hn = hexfunc(n)
7375 hn = hexfunc(n)
7375 label = b'tags.normal'
7376 label = b'tags.normal'
7376 tagtype = b''
7377 tagtype = b''
7377 if repo.tagtype(t) == b'local':
7378 if repo.tagtype(t) == b'local':
7378 label = b'tags.local'
7379 label = b'tags.local'
7379 tagtype = b'local'
7380 tagtype = b'local'
7380
7381
7381 fm.startitem()
7382 fm.startitem()
7382 fm.context(repo=repo)
7383 fm.context(repo=repo)
7383 fm.write(b'tag', b'%s', t, label=label)
7384 fm.write(b'tag', b'%s', t, label=label)
7384 fmt = b" " * (30 - encoding.colwidth(t)) + b' %5d:%s'
7385 fmt = b" " * (30 - encoding.colwidth(t)) + b' %5d:%s'
7385 fm.condwrite(
7386 fm.condwrite(
7386 not ui.quiet,
7387 not ui.quiet,
7387 b'rev node',
7388 b'rev node',
7388 fmt,
7389 fmt,
7389 repo.changelog.rev(n),
7390 repo.changelog.rev(n),
7390 hn,
7391 hn,
7391 label=label,
7392 label=label,
7392 )
7393 )
7393 fm.condwrite(
7394 fm.condwrite(
7394 ui.verbose and tagtype, b'type', b' %s', tagtype, label=label
7395 ui.verbose and tagtype, b'type', b' %s', tagtype, label=label
7395 )
7396 )
7396 fm.plain(b'\n')
7397 fm.plain(b'\n')
7397 fm.end()
7398 fm.end()
7398
7399
7399
7400
7400 @command(
7401 @command(
7401 b'tip',
7402 b'tip',
7402 [
7403 [
7403 (b'p', b'patch', None, _(b'show patch')),
7404 (b'p', b'patch', None, _(b'show patch')),
7404 (b'g', b'git', None, _(b'use git extended diff format')),
7405 (b'g', b'git', None, _(b'use git extended diff format')),
7405 ]
7406 ]
7406 + templateopts,
7407 + templateopts,
7407 _(b'[-p] [-g]'),
7408 _(b'[-p] [-g]'),
7408 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
7409 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
7409 )
7410 )
7410 def tip(ui, repo, **opts):
7411 def tip(ui, repo, **opts):
7411 """show the tip revision (DEPRECATED)
7412 """show the tip revision (DEPRECATED)
7412
7413
7413 The tip revision (usually just called the tip) is the changeset
7414 The tip revision (usually just called the tip) is the changeset
7414 most recently added to the repository (and therefore the most
7415 most recently added to the repository (and therefore the most
7415 recently changed head).
7416 recently changed head).
7416
7417
7417 If you have just made a commit, that commit will be the tip. If
7418 If you have just made a commit, that commit will be the tip. If
7418 you have just pulled changes from another repository, the tip of
7419 you have just pulled changes from another repository, the tip of
7419 that repository becomes the current tip. The "tip" tag is special
7420 that repository becomes the current tip. The "tip" tag is special
7420 and cannot be renamed or assigned to a different changeset.
7421 and cannot be renamed or assigned to a different changeset.
7421
7422
7422 This command is deprecated, please use :hg:`heads` instead.
7423 This command is deprecated, please use :hg:`heads` instead.
7423
7424
7424 Returns 0 on success.
7425 Returns 0 on success.
7425 """
7426 """
7426 opts = pycompat.byteskwargs(opts)
7427 opts = pycompat.byteskwargs(opts)
7427 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
7428 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
7428 displayer.show(repo[b'tip'])
7429 displayer.show(repo[b'tip'])
7429 displayer.close()
7430 displayer.close()
7430
7431
7431
7432
7432 @command(
7433 @command(
7433 b'unbundle',
7434 b'unbundle',
7434 [
7435 [
7435 (
7436 (
7436 b'u',
7437 b'u',
7437 b'update',
7438 b'update',
7438 None,
7439 None,
7439 _(b'update to new branch head if changesets were unbundled'),
7440 _(b'update to new branch head if changesets were unbundled'),
7440 )
7441 )
7441 ],
7442 ],
7442 _(b'[-u] FILE...'),
7443 _(b'[-u] FILE...'),
7443 helpcategory=command.CATEGORY_IMPORT_EXPORT,
7444 helpcategory=command.CATEGORY_IMPORT_EXPORT,
7444 )
7445 )
7445 def unbundle(ui, repo, fname1, *fnames, **opts):
7446 def unbundle(ui, repo, fname1, *fnames, **opts):
7446 """apply one or more bundle files
7447 """apply one or more bundle files
7447
7448
7448 Apply one or more bundle files generated by :hg:`bundle`.
7449 Apply one or more bundle files generated by :hg:`bundle`.
7449
7450
7450 Returns 0 on success, 1 if an update has unresolved files.
7451 Returns 0 on success, 1 if an update has unresolved files.
7451 """
7452 """
7452 fnames = (fname1,) + fnames
7453 fnames = (fname1,) + fnames
7453
7454
7454 with repo.lock():
7455 with repo.lock():
7455 for fname in fnames:
7456 for fname in fnames:
7456 f = hg.openpath(ui, fname)
7457 f = hg.openpath(ui, fname)
7457 gen = exchange.readbundle(ui, f, fname)
7458 gen = exchange.readbundle(ui, f, fname)
7458 if isinstance(gen, streamclone.streamcloneapplier):
7459 if isinstance(gen, streamclone.streamcloneapplier):
7459 raise error.Abort(
7460 raise error.Abort(
7460 _(
7461 _(
7461 b'packed bundles cannot be applied with '
7462 b'packed bundles cannot be applied with '
7462 b'"hg unbundle"'
7463 b'"hg unbundle"'
7463 ),
7464 ),
7464 hint=_(b'use "hg debugapplystreamclonebundle"'),
7465 hint=_(b'use "hg debugapplystreamclonebundle"'),
7465 )
7466 )
7466 url = b'bundle:' + fname
7467 url = b'bundle:' + fname
7467 try:
7468 try:
7468 txnname = b'unbundle'
7469 txnname = b'unbundle'
7469 if not isinstance(gen, bundle2.unbundle20):
7470 if not isinstance(gen, bundle2.unbundle20):
7470 txnname = b'unbundle\n%s' % util.hidepassword(url)
7471 txnname = b'unbundle\n%s' % util.hidepassword(url)
7471 with repo.transaction(txnname) as tr:
7472 with repo.transaction(txnname) as tr:
7472 op = bundle2.applybundle(
7473 op = bundle2.applybundle(
7473 repo, gen, tr, source=b'unbundle', url=url
7474 repo, gen, tr, source=b'unbundle', url=url
7474 )
7475 )
7475 except error.BundleUnknownFeatureError as exc:
7476 except error.BundleUnknownFeatureError as exc:
7476 raise error.Abort(
7477 raise error.Abort(
7477 _(b'%s: unknown bundle feature, %s') % (fname, exc),
7478 _(b'%s: unknown bundle feature, %s') % (fname, exc),
7478 hint=_(
7479 hint=_(
7479 b"see https://mercurial-scm.org/"
7480 b"see https://mercurial-scm.org/"
7480 b"wiki/BundleFeature for more "
7481 b"wiki/BundleFeature for more "
7481 b"information"
7482 b"information"
7482 ),
7483 ),
7483 )
7484 )
7484 modheads = bundle2.combinechangegroupresults(op)
7485 modheads = bundle2.combinechangegroupresults(op)
7485
7486
7486 return postincoming(ui, repo, modheads, opts.get('update'), None, None)
7487 return postincoming(ui, repo, modheads, opts.get('update'), None, None)
7487
7488
7488
7489
7489 @command(
7490 @command(
7490 b'unshelve',
7491 b'unshelve',
7491 [
7492 [
7492 (b'a', b'abort', None, _(b'abort an incomplete unshelve operation')),
7493 (b'a', b'abort', None, _(b'abort an incomplete unshelve operation')),
7493 (
7494 (
7494 b'c',
7495 b'c',
7495 b'continue',
7496 b'continue',
7496 None,
7497 None,
7497 _(b'continue an incomplete unshelve operation'),
7498 _(b'continue an incomplete unshelve operation'),
7498 ),
7499 ),
7499 (b'i', b'interactive', None, _(b'use interactive mode (EXPERIMENTAL)')),
7500 (b'i', b'interactive', None, _(b'use interactive mode (EXPERIMENTAL)')),
7500 (b'k', b'keep', None, _(b'keep shelve after unshelving')),
7501 (b'k', b'keep', None, _(b'keep shelve after unshelving')),
7501 (
7502 (
7502 b'n',
7503 b'n',
7503 b'name',
7504 b'name',
7504 b'',
7505 b'',
7505 _(b'restore shelved change with given name'),
7506 _(b'restore shelved change with given name'),
7506 _(b'NAME'),
7507 _(b'NAME'),
7507 ),
7508 ),
7508 (b't', b'tool', b'', _(b'specify merge tool')),
7509 (b't', b'tool', b'', _(b'specify merge tool')),
7509 (
7510 (
7510 b'',
7511 b'',
7511 b'date',
7512 b'date',
7512 b'',
7513 b'',
7513 _(b'set date for temporary commits (DEPRECATED)'),
7514 _(b'set date for temporary commits (DEPRECATED)'),
7514 _(b'DATE'),
7515 _(b'DATE'),
7515 ),
7516 ),
7516 ],
7517 ],
7517 _(b'hg unshelve [OPTION]... [[-n] SHELVED]'),
7518 _(b'hg unshelve [OPTION]... [[-n] SHELVED]'),
7518 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
7519 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
7519 )
7520 )
7520 def unshelve(ui, repo, *shelved, **opts):
7521 def unshelve(ui, repo, *shelved, **opts):
7521 """restore a shelved change to the working directory
7522 """restore a shelved change to the working directory
7522
7523
7523 This command accepts an optional name of a shelved change to
7524 This command accepts an optional name of a shelved change to
7524 restore. If none is given, the most recent shelved change is used.
7525 restore. If none is given, the most recent shelved change is used.
7525
7526
7526 If a shelved change is applied successfully, the bundle that
7527 If a shelved change is applied successfully, the bundle that
7527 contains the shelved changes is moved to a backup location
7528 contains the shelved changes is moved to a backup location
7528 (.hg/shelve-backup).
7529 (.hg/shelve-backup).
7529
7530
7530 Since you can restore a shelved change on top of an arbitrary
7531 Since you can restore a shelved change on top of an arbitrary
7531 commit, it is possible that unshelving will result in a conflict
7532 commit, it is possible that unshelving will result in a conflict
7532 between your changes and the commits you are unshelving onto. If
7533 between your changes and the commits you are unshelving onto. If
7533 this occurs, you must resolve the conflict, then use
7534 this occurs, you must resolve the conflict, then use
7534 ``--continue`` to complete the unshelve operation. (The bundle
7535 ``--continue`` to complete the unshelve operation. (The bundle
7535 will not be moved until you successfully complete the unshelve.)
7536 will not be moved until you successfully complete the unshelve.)
7536
7537
7537 (Alternatively, you can use ``--abort`` to abandon an unshelve
7538 (Alternatively, you can use ``--abort`` to abandon an unshelve
7538 that causes a conflict. This reverts the unshelved changes, and
7539 that causes a conflict. This reverts the unshelved changes, and
7539 leaves the bundle in place.)
7540 leaves the bundle in place.)
7540
7541
7541 If bare shelved change (without interactive, include and exclude
7542 If bare shelved change (without interactive, include and exclude
7542 option) was done on newly created branch it would restore branch
7543 option) was done on newly created branch it would restore branch
7543 information to the working directory.
7544 information to the working directory.
7544
7545
7545 After a successful unshelve, the shelved changes are stored in a
7546 After a successful unshelve, the shelved changes are stored in a
7546 backup directory. Only the N most recent backups are kept. N
7547 backup directory. Only the N most recent backups are kept. N
7547 defaults to 10 but can be overridden using the ``shelve.maxbackups``
7548 defaults to 10 but can be overridden using the ``shelve.maxbackups``
7548 configuration option.
7549 configuration option.
7549
7550
7550 .. container:: verbose
7551 .. container:: verbose
7551
7552
7552 Timestamp in seconds is used to decide order of backups. More
7553 Timestamp in seconds is used to decide order of backups. More
7553 than ``maxbackups`` backups are kept, if same timestamp
7554 than ``maxbackups`` backups are kept, if same timestamp
7554 prevents from deciding exact order of them, for safety.
7555 prevents from deciding exact order of them, for safety.
7555
7556
7556 Selected changes can be unshelved with ``--interactive`` flag.
7557 Selected changes can be unshelved with ``--interactive`` flag.
7557 The working directory is updated with the selected changes, and
7558 The working directory is updated with the selected changes, and
7558 only the unselected changes remain shelved.
7559 only the unselected changes remain shelved.
7559 Note: The whole shelve is applied to working directory first before
7560 Note: The whole shelve is applied to working directory first before
7560 running interactively. So, this will bring up all the conflicts between
7561 running interactively. So, this will bring up all the conflicts between
7561 working directory and the shelve, irrespective of which changes will be
7562 working directory and the shelve, irrespective of which changes will be
7562 unshelved.
7563 unshelved.
7563 """
7564 """
7564 with repo.wlock():
7565 with repo.wlock():
7565 return shelvemod.unshelvecmd(ui, repo, *shelved, **opts)
7566 return shelvemod.unshelvecmd(ui, repo, *shelved, **opts)
7566
7567
7567
7568
7568 statemod.addunfinished(
7569 statemod.addunfinished(
7569 b'unshelve',
7570 b'unshelve',
7570 fname=b'shelvedstate',
7571 fname=b'shelvedstate',
7571 continueflag=True,
7572 continueflag=True,
7572 abortfunc=shelvemod.hgabortunshelve,
7573 abortfunc=shelvemod.hgabortunshelve,
7573 continuefunc=shelvemod.hgcontinueunshelve,
7574 continuefunc=shelvemod.hgcontinueunshelve,
7574 cmdmsg=_(b'unshelve already in progress'),
7575 cmdmsg=_(b'unshelve already in progress'),
7575 )
7576 )
7576
7577
7577
7578
7578 @command(
7579 @command(
7579 b'update|up|checkout|co',
7580 b'update|up|checkout|co',
7580 [
7581 [
7581 (b'C', b'clean', None, _(b'discard uncommitted changes (no backup)')),
7582 (b'C', b'clean', None, _(b'discard uncommitted changes (no backup)')),
7582 (b'c', b'check', None, _(b'require clean working directory')),
7583 (b'c', b'check', None, _(b'require clean working directory')),
7583 (b'm', b'merge', None, _(b'merge uncommitted changes')),
7584 (b'm', b'merge', None, _(b'merge uncommitted changes')),
7584 (b'd', b'date', b'', _(b'tipmost revision matching date'), _(b'DATE')),
7585 (b'd', b'date', b'', _(b'tipmost revision matching date'), _(b'DATE')),
7585 (b'r', b'rev', b'', _(b'revision'), _(b'REV')),
7586 (b'r', b'rev', b'', _(b'revision'), _(b'REV')),
7586 ]
7587 ]
7587 + mergetoolopts,
7588 + mergetoolopts,
7588 _(b'[-C|-c|-m] [-d DATE] [[-r] REV]'),
7589 _(b'[-C|-c|-m] [-d DATE] [[-r] REV]'),
7589 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
7590 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
7590 helpbasic=True,
7591 helpbasic=True,
7591 )
7592 )
7592 def update(ui, repo, node=None, **opts):
7593 def update(ui, repo, node=None, **opts):
7593 """update working directory (or switch revisions)
7594 """update working directory (or switch revisions)
7594
7595
7595 Update the repository's working directory to the specified
7596 Update the repository's working directory to the specified
7596 changeset. If no changeset is specified, update to the tip of the
7597 changeset. If no changeset is specified, update to the tip of the
7597 current named branch and move the active bookmark (see :hg:`help
7598 current named branch and move the active bookmark (see :hg:`help
7598 bookmarks`).
7599 bookmarks`).
7599
7600
7600 Update sets the working directory's parent revision to the specified
7601 Update sets the working directory's parent revision to the specified
7601 changeset (see :hg:`help parents`).
7602 changeset (see :hg:`help parents`).
7602
7603
7603 If the changeset is not a descendant or ancestor of the working
7604 If the changeset is not a descendant or ancestor of the working
7604 directory's parent and there are uncommitted changes, the update is
7605 directory's parent and there are uncommitted changes, the update is
7605 aborted. With the -c/--check option, the working directory is checked
7606 aborted. With the -c/--check option, the working directory is checked
7606 for uncommitted changes; if none are found, the working directory is
7607 for uncommitted changes; if none are found, the working directory is
7607 updated to the specified changeset.
7608 updated to the specified changeset.
7608
7609
7609 .. container:: verbose
7610 .. container:: verbose
7610
7611
7611 The -C/--clean, -c/--check, and -m/--merge options control what
7612 The -C/--clean, -c/--check, and -m/--merge options control what
7612 happens if the working directory contains uncommitted changes.
7613 happens if the working directory contains uncommitted changes.
7613 At most of one of them can be specified.
7614 At most of one of them can be specified.
7614
7615
7615 1. If no option is specified, and if
7616 1. If no option is specified, and if
7616 the requested changeset is an ancestor or descendant of
7617 the requested changeset is an ancestor or descendant of
7617 the working directory's parent, the uncommitted changes
7618 the working directory's parent, the uncommitted changes
7618 are merged into the requested changeset and the merged
7619 are merged into the requested changeset and the merged
7619 result is left uncommitted. If the requested changeset is
7620 result is left uncommitted. If the requested changeset is
7620 not an ancestor or descendant (that is, it is on another
7621 not an ancestor or descendant (that is, it is on another
7621 branch), the update is aborted and the uncommitted changes
7622 branch), the update is aborted and the uncommitted changes
7622 are preserved.
7623 are preserved.
7623
7624
7624 2. With the -m/--merge option, the update is allowed even if the
7625 2. With the -m/--merge option, the update is allowed even if the
7625 requested changeset is not an ancestor or descendant of
7626 requested changeset is not an ancestor or descendant of
7626 the working directory's parent.
7627 the working directory's parent.
7627
7628
7628 3. With the -c/--check option, the update is aborted and the
7629 3. With the -c/--check option, the update is aborted and the
7629 uncommitted changes are preserved.
7630 uncommitted changes are preserved.
7630
7631
7631 4. With the -C/--clean option, uncommitted changes are discarded and
7632 4. With the -C/--clean option, uncommitted changes are discarded and
7632 the working directory is updated to the requested changeset.
7633 the working directory is updated to the requested changeset.
7633
7634
7634 To cancel an uncommitted merge (and lose your changes), use
7635 To cancel an uncommitted merge (and lose your changes), use
7635 :hg:`merge --abort`.
7636 :hg:`merge --abort`.
7636
7637
7637 Use null as the changeset to remove the working directory (like
7638 Use null as the changeset to remove the working directory (like
7638 :hg:`clone -U`).
7639 :hg:`clone -U`).
7639
7640
7640 If you want to revert just one file to an older revision, use
7641 If you want to revert just one file to an older revision, use
7641 :hg:`revert [-r REV] NAME`.
7642 :hg:`revert [-r REV] NAME`.
7642
7643
7643 See :hg:`help dates` for a list of formats valid for -d/--date.
7644 See :hg:`help dates` for a list of formats valid for -d/--date.
7644
7645
7645 Returns 0 on success, 1 if there are unresolved files.
7646 Returns 0 on success, 1 if there are unresolved files.
7646 """
7647 """
7647 cmdutil.check_at_most_one_arg(opts, 'clean', 'check', 'merge')
7648 cmdutil.check_at_most_one_arg(opts, 'clean', 'check', 'merge')
7648 rev = opts.get('rev')
7649 rev = opts.get('rev')
7649 date = opts.get('date')
7650 date = opts.get('date')
7650 clean = opts.get('clean')
7651 clean = opts.get('clean')
7651 check = opts.get('check')
7652 check = opts.get('check')
7652 merge = opts.get('merge')
7653 merge = opts.get('merge')
7653 if rev and node:
7654 if rev and node:
7654 raise error.Abort(_(b"please specify just one revision"))
7655 raise error.Abort(_(b"please specify just one revision"))
7655
7656
7656 if ui.configbool(b'commands', b'update.requiredest'):
7657 if ui.configbool(b'commands', b'update.requiredest'):
7657 if not node and not rev and not date:
7658 if not node and not rev and not date:
7658 raise error.Abort(
7659 raise error.Abort(
7659 _(b'you must specify a destination'),
7660 _(b'you must specify a destination'),
7660 hint=_(b'for example: hg update ".::"'),
7661 hint=_(b'for example: hg update ".::"'),
7661 )
7662 )
7662
7663
7663 if rev is None or rev == b'':
7664 if rev is None or rev == b'':
7664 rev = node
7665 rev = node
7665
7666
7666 if date and rev is not None:
7667 if date and rev is not None:
7667 raise error.Abort(_(b"you can't specify a revision and a date"))
7668 raise error.Abort(_(b"you can't specify a revision and a date"))
7668
7669
7669 updatecheck = None
7670 updatecheck = None
7670 if check:
7671 if check:
7671 updatecheck = b'abort'
7672 updatecheck = b'abort'
7672 elif merge:
7673 elif merge:
7673 updatecheck = b'none'
7674 updatecheck = b'none'
7674
7675
7675 with repo.wlock():
7676 with repo.wlock():
7676 cmdutil.clearunfinished(repo)
7677 cmdutil.clearunfinished(repo)
7677 if date:
7678 if date:
7678 rev = cmdutil.finddate(ui, repo, date)
7679 rev = cmdutil.finddate(ui, repo, date)
7679
7680
7680 # if we defined a bookmark, we have to remember the original name
7681 # if we defined a bookmark, we have to remember the original name
7681 brev = rev
7682 brev = rev
7682 if rev:
7683 if rev:
7683 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
7684 repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
7684 ctx = scmutil.revsingle(repo, rev, default=None)
7685 ctx = scmutil.revsingle(repo, rev, default=None)
7685 rev = ctx.rev()
7686 rev = ctx.rev()
7686 hidden = ctx.hidden()
7687 hidden = ctx.hidden()
7687 overrides = {(b'ui', b'forcemerge'): opts.get('tool', b'')}
7688 overrides = {(b'ui', b'forcemerge'): opts.get('tool', b'')}
7688 with ui.configoverride(overrides, b'update'):
7689 with ui.configoverride(overrides, b'update'):
7689 ret = hg.updatetotally(
7690 ret = hg.updatetotally(
7690 ui, repo, rev, brev, clean=clean, updatecheck=updatecheck
7691 ui, repo, rev, brev, clean=clean, updatecheck=updatecheck
7691 )
7692 )
7692 if hidden:
7693 if hidden:
7693 ctxstr = ctx.hex()[:12]
7694 ctxstr = ctx.hex()[:12]
7694 ui.warn(_(b"updated to hidden changeset %s\n") % ctxstr)
7695 ui.warn(_(b"updated to hidden changeset %s\n") % ctxstr)
7695
7696
7696 if ctx.obsolete():
7697 if ctx.obsolete():
7697 obsfatemsg = obsutil._getfilteredreason(repo, ctxstr, ctx)
7698 obsfatemsg = obsutil._getfilteredreason(repo, ctxstr, ctx)
7698 ui.warn(b"(%s)\n" % obsfatemsg)
7699 ui.warn(b"(%s)\n" % obsfatemsg)
7699 return ret
7700 return ret
7700
7701
7701
7702
7702 @command(
7703 @command(
7703 b'verify',
7704 b'verify',
7704 [(b'', b'full', False, b'perform more checks (EXPERIMENTAL)')],
7705 [(b'', b'full', False, b'perform more checks (EXPERIMENTAL)')],
7705 helpcategory=command.CATEGORY_MAINTENANCE,
7706 helpcategory=command.CATEGORY_MAINTENANCE,
7706 )
7707 )
7707 def verify(ui, repo, **opts):
7708 def verify(ui, repo, **opts):
7708 """verify the integrity of the repository
7709 """verify the integrity of the repository
7709
7710
7710 Verify the integrity of the current repository.
7711 Verify the integrity of the current repository.
7711
7712
7712 This will perform an extensive check of the repository's
7713 This will perform an extensive check of the repository's
7713 integrity, validating the hashes and checksums of each entry in
7714 integrity, validating the hashes and checksums of each entry in
7714 the changelog, manifest, and tracked files, as well as the
7715 the changelog, manifest, and tracked files, as well as the
7715 integrity of their crosslinks and indices.
7716 integrity of their crosslinks and indices.
7716
7717
7717 Please see https://mercurial-scm.org/wiki/RepositoryCorruption
7718 Please see https://mercurial-scm.org/wiki/RepositoryCorruption
7718 for more information about recovery from corruption of the
7719 for more information about recovery from corruption of the
7719 repository.
7720 repository.
7720
7721
7721 Returns 0 on success, 1 if errors are encountered.
7722 Returns 0 on success, 1 if errors are encountered.
7722 """
7723 """
7723 opts = pycompat.byteskwargs(opts)
7724 opts = pycompat.byteskwargs(opts)
7724
7725
7725 level = None
7726 level = None
7726 if opts[b'full']:
7727 if opts[b'full']:
7727 level = verifymod.VERIFY_FULL
7728 level = verifymod.VERIFY_FULL
7728 return hg.verify(repo, level)
7729 return hg.verify(repo, level)
7729
7730
7730
7731
7731 @command(
7732 @command(
7732 b'version',
7733 b'version',
7733 [] + formatteropts,
7734 [] + formatteropts,
7734 helpcategory=command.CATEGORY_HELP,
7735 helpcategory=command.CATEGORY_HELP,
7735 norepo=True,
7736 norepo=True,
7736 intents={INTENT_READONLY},
7737 intents={INTENT_READONLY},
7737 )
7738 )
7738 def version_(ui, **opts):
7739 def version_(ui, **opts):
7739 """output version and copyright information
7740 """output version and copyright information
7740
7741
7741 .. container:: verbose
7742 .. container:: verbose
7742
7743
7743 Template:
7744 Template:
7744
7745
7745 The following keywords are supported. See also :hg:`help templates`.
7746 The following keywords are supported. See also :hg:`help templates`.
7746
7747
7747 :extensions: List of extensions.
7748 :extensions: List of extensions.
7748 :ver: String. Version number.
7749 :ver: String. Version number.
7749
7750
7750 And each entry of ``{extensions}`` provides the following sub-keywords
7751 And each entry of ``{extensions}`` provides the following sub-keywords
7751 in addition to ``{ver}``.
7752 in addition to ``{ver}``.
7752
7753
7753 :bundled: Boolean. True if included in the release.
7754 :bundled: Boolean. True if included in the release.
7754 :name: String. Extension name.
7755 :name: String. Extension name.
7755 """
7756 """
7756 opts = pycompat.byteskwargs(opts)
7757 opts = pycompat.byteskwargs(opts)
7757 if ui.verbose:
7758 if ui.verbose:
7758 ui.pager(b'version')
7759 ui.pager(b'version')
7759 fm = ui.formatter(b"version", opts)
7760 fm = ui.formatter(b"version", opts)
7760 fm.startitem()
7761 fm.startitem()
7761 fm.write(
7762 fm.write(
7762 b"ver", _(b"Mercurial Distributed SCM (version %s)\n"), util.version()
7763 b"ver", _(b"Mercurial Distributed SCM (version %s)\n"), util.version()
7763 )
7764 )
7764 license = _(
7765 license = _(
7765 b"(see https://mercurial-scm.org for more information)\n"
7766 b"(see https://mercurial-scm.org for more information)\n"
7766 b"\nCopyright (C) 2005-2020 Matt Mackall and others\n"
7767 b"\nCopyright (C) 2005-2020 Matt Mackall and others\n"
7767 b"This is free software; see the source for copying conditions. "
7768 b"This is free software; see the source for copying conditions. "
7768 b"There is NO\nwarranty; "
7769 b"There is NO\nwarranty; "
7769 b"not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
7770 b"not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
7770 )
7771 )
7771 if not ui.quiet:
7772 if not ui.quiet:
7772 fm.plain(license)
7773 fm.plain(license)
7773
7774
7774 if ui.verbose:
7775 if ui.verbose:
7775 fm.plain(_(b"\nEnabled extensions:\n\n"))
7776 fm.plain(_(b"\nEnabled extensions:\n\n"))
7776 # format names and versions into columns
7777 # format names and versions into columns
7777 names = []
7778 names = []
7778 vers = []
7779 vers = []
7779 isinternals = []
7780 isinternals = []
7780 for name, module in sorted(extensions.extensions()):
7781 for name, module in sorted(extensions.extensions()):
7781 names.append(name)
7782 names.append(name)
7782 vers.append(extensions.moduleversion(module) or None)
7783 vers.append(extensions.moduleversion(module) or None)
7783 isinternals.append(extensions.ismoduleinternal(module))
7784 isinternals.append(extensions.ismoduleinternal(module))
7784 fn = fm.nested(b"extensions", tmpl=b'{name}\n')
7785 fn = fm.nested(b"extensions", tmpl=b'{name}\n')
7785 if names:
7786 if names:
7786 namefmt = b" %%-%ds " % max(len(n) for n in names)
7787 namefmt = b" %%-%ds " % max(len(n) for n in names)
7787 places = [_(b"external"), _(b"internal")]
7788 places = [_(b"external"), _(b"internal")]
7788 for n, v, p in zip(names, vers, isinternals):
7789 for n, v, p in zip(names, vers, isinternals):
7789 fn.startitem()
7790 fn.startitem()
7790 fn.condwrite(ui.verbose, b"name", namefmt, n)
7791 fn.condwrite(ui.verbose, b"name", namefmt, n)
7791 if ui.verbose:
7792 if ui.verbose:
7792 fn.plain(b"%s " % places[p])
7793 fn.plain(b"%s " % places[p])
7793 fn.data(bundled=p)
7794 fn.data(bundled=p)
7794 fn.condwrite(ui.verbose and v, b"ver", b"%s", v)
7795 fn.condwrite(ui.verbose and v, b"ver", b"%s", v)
7795 if ui.verbose:
7796 if ui.verbose:
7796 fn.plain(b"\n")
7797 fn.plain(b"\n")
7797 fn.end()
7798 fn.end()
7798 fm.end()
7799 fm.end()
7799
7800
7800
7801
7801 def loadcmdtable(ui, name, cmdtable):
7802 def loadcmdtable(ui, name, cmdtable):
7802 """Load command functions from specified cmdtable
7803 """Load command functions from specified cmdtable
7803 """
7804 """
7804 overrides = [cmd for cmd in cmdtable if cmd in table]
7805 overrides = [cmd for cmd in cmdtable if cmd in table]
7805 if overrides:
7806 if overrides:
7806 ui.warn(
7807 ui.warn(
7807 _(b"extension '%s' overrides commands: %s\n")
7808 _(b"extension '%s' overrides commands: %s\n")
7808 % (name, b" ".join(overrides))
7809 % (name, b" ".join(overrides))
7809 )
7810 )
7810 table.update(cmdtable)
7811 table.update(cmdtable)
General Comments 0
You need to be logged in to leave comments. Login now