##// END OF EJS Templates
scmutil: introduce deprecated alias for revpair()...
Martin von Zweigbergk -
r37268:7c0f40f4 default
parent child Browse files
Show More
@@ -1,427 +1,427 b''
1 # extdiff.py - external diff program support for mercurial
1 # extdiff.py - external diff program support for mercurial
2 #
2 #
3 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
3 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 '''command to allow external programs to compare revisions
8 '''command to allow external programs to compare revisions
9
9
10 The extdiff Mercurial extension allows you to use external programs
10 The extdiff Mercurial extension allows you to use external programs
11 to compare revisions, or revision with working directory. The external
11 to compare revisions, or revision with working directory. The external
12 diff programs are called with a configurable set of options and two
12 diff programs are called with a configurable set of options and two
13 non-option arguments: paths to directories containing snapshots of
13 non-option arguments: paths to directories containing snapshots of
14 files to compare.
14 files to compare.
15
15
16 If there is more than one file being compared and the "child" revision
16 If there is more than one file being compared and the "child" revision
17 is the working directory, any modifications made in the external diff
17 is the working directory, any modifications made in the external diff
18 program will be copied back to the working directory from the temporary
18 program will be copied back to the working directory from the temporary
19 directory.
19 directory.
20
20
21 The extdiff extension also allows you to configure new diff commands, so
21 The extdiff extension also allows you to configure new diff commands, so
22 you do not need to type :hg:`extdiff -p kdiff3` always. ::
22 you do not need to type :hg:`extdiff -p kdiff3` always. ::
23
23
24 [extdiff]
24 [extdiff]
25 # add new command that runs GNU diff(1) in 'context diff' mode
25 # add new command that runs GNU diff(1) in 'context diff' mode
26 cdiff = gdiff -Nprc5
26 cdiff = gdiff -Nprc5
27 ## or the old way:
27 ## or the old way:
28 #cmd.cdiff = gdiff
28 #cmd.cdiff = gdiff
29 #opts.cdiff = -Nprc5
29 #opts.cdiff = -Nprc5
30
30
31 # add new command called meld, runs meld (no need to name twice). If
31 # add new command called meld, runs meld (no need to name twice). If
32 # the meld executable is not available, the meld tool in [merge-tools]
32 # the meld executable is not available, the meld tool in [merge-tools]
33 # will be used, if available
33 # will be used, if available
34 meld =
34 meld =
35
35
36 # add new command called vimdiff, runs gvimdiff with DirDiff plugin
36 # add new command called vimdiff, runs gvimdiff with DirDiff plugin
37 # (see http://www.vim.org/scripts/script.php?script_id=102) Non
37 # (see http://www.vim.org/scripts/script.php?script_id=102) Non
38 # English user, be sure to put "let g:DirDiffDynamicDiffText = 1" in
38 # English user, be sure to put "let g:DirDiffDynamicDiffText = 1" in
39 # your .vimrc
39 # your .vimrc
40 vimdiff = gvim -f "+next" \\
40 vimdiff = gvim -f "+next" \\
41 "+execute 'DirDiff' fnameescape(argv(0)) fnameescape(argv(1))"
41 "+execute 'DirDiff' fnameescape(argv(0)) fnameescape(argv(1))"
42
42
43 Tool arguments can include variables that are expanded at runtime::
43 Tool arguments can include variables that are expanded at runtime::
44
44
45 $parent1, $plabel1 - filename, descriptive label of first parent
45 $parent1, $plabel1 - filename, descriptive label of first parent
46 $child, $clabel - filename, descriptive label of child revision
46 $child, $clabel - filename, descriptive label of child revision
47 $parent2, $plabel2 - filename, descriptive label of second parent
47 $parent2, $plabel2 - filename, descriptive label of second parent
48 $root - repository root
48 $root - repository root
49 $parent is an alias for $parent1.
49 $parent is an alias for $parent1.
50
50
51 The extdiff extension will look in your [diff-tools] and [merge-tools]
51 The extdiff extension will look in your [diff-tools] and [merge-tools]
52 sections for diff tool arguments, when none are specified in [extdiff].
52 sections for diff tool arguments, when none are specified in [extdiff].
53
53
54 ::
54 ::
55
55
56 [extdiff]
56 [extdiff]
57 kdiff3 =
57 kdiff3 =
58
58
59 [diff-tools]
59 [diff-tools]
60 kdiff3.diffargs=--L1 '$plabel1' --L2 '$clabel' $parent $child
60 kdiff3.diffargs=--L1 '$plabel1' --L2 '$clabel' $parent $child
61
61
62 You can use -I/-X and list of file or directory names like normal
62 You can use -I/-X and list of file or directory names like normal
63 :hg:`diff` command. The extdiff extension makes snapshots of only
63 :hg:`diff` command. The extdiff extension makes snapshots of only
64 needed files, so running the external diff program will actually be
64 needed files, so running the external diff program will actually be
65 pretty fast (at least faster than having to compare the entire tree).
65 pretty fast (at least faster than having to compare the entire tree).
66 '''
66 '''
67
67
68 from __future__ import absolute_import
68 from __future__ import absolute_import
69
69
70 import os
70 import os
71 import re
71 import re
72 import shutil
72 import shutil
73 import stat
73 import stat
74 import tempfile
74 import tempfile
75 from mercurial.i18n import _
75 from mercurial.i18n import _
76 from mercurial.node import (
76 from mercurial.node import (
77 nullid,
77 nullid,
78 short,
78 short,
79 )
79 )
80 from mercurial import (
80 from mercurial import (
81 archival,
81 archival,
82 cmdutil,
82 cmdutil,
83 error,
83 error,
84 filemerge,
84 filemerge,
85 pycompat,
85 pycompat,
86 registrar,
86 registrar,
87 scmutil,
87 scmutil,
88 util,
88 util,
89 )
89 )
90 from mercurial.utils import (
90 from mercurial.utils import (
91 procutil,
91 procutil,
92 stringutil,
92 stringutil,
93 )
93 )
94
94
95 cmdtable = {}
95 cmdtable = {}
96 command = registrar.command(cmdtable)
96 command = registrar.command(cmdtable)
97
97
98 configtable = {}
98 configtable = {}
99 configitem = registrar.configitem(configtable)
99 configitem = registrar.configitem(configtable)
100
100
101 configitem('extdiff', br'opts\..*',
101 configitem('extdiff', br'opts\..*',
102 default='',
102 default='',
103 generic=True,
103 generic=True,
104 )
104 )
105
105
106 configitem('diff-tools', br'.*\.diffargs$',
106 configitem('diff-tools', br'.*\.diffargs$',
107 default=None,
107 default=None,
108 generic=True,
108 generic=True,
109 )
109 )
110
110
111 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
111 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
112 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
112 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
113 # be specifying the version(s) of Mercurial they are tested with, or
113 # be specifying the version(s) of Mercurial they are tested with, or
114 # leave the attribute unspecified.
114 # leave the attribute unspecified.
115 testedwith = 'ships-with-hg-core'
115 testedwith = 'ships-with-hg-core'
116
116
117 def snapshot(ui, repo, files, node, tmproot, listsubrepos):
117 def snapshot(ui, repo, files, node, tmproot, listsubrepos):
118 '''snapshot files as of some revision
118 '''snapshot files as of some revision
119 if not using snapshot, -I/-X does not work and recursive diff
119 if not using snapshot, -I/-X does not work and recursive diff
120 in tools like kdiff3 and meld displays too many files.'''
120 in tools like kdiff3 and meld displays too many files.'''
121 dirname = os.path.basename(repo.root)
121 dirname = os.path.basename(repo.root)
122 if dirname == "":
122 if dirname == "":
123 dirname = "root"
123 dirname = "root"
124 if node is not None:
124 if node is not None:
125 dirname = '%s.%s' % (dirname, short(node))
125 dirname = '%s.%s' % (dirname, short(node))
126 base = os.path.join(tmproot, dirname)
126 base = os.path.join(tmproot, dirname)
127 os.mkdir(base)
127 os.mkdir(base)
128 fnsandstat = []
128 fnsandstat = []
129
129
130 if node is not None:
130 if node is not None:
131 ui.note(_('making snapshot of %d files from rev %s\n') %
131 ui.note(_('making snapshot of %d files from rev %s\n') %
132 (len(files), short(node)))
132 (len(files), short(node)))
133 else:
133 else:
134 ui.note(_('making snapshot of %d files from working directory\n') %
134 ui.note(_('making snapshot of %d files from working directory\n') %
135 (len(files)))
135 (len(files)))
136
136
137 if files:
137 if files:
138 repo.ui.setconfig("ui", "archivemeta", False)
138 repo.ui.setconfig("ui", "archivemeta", False)
139
139
140 archival.archive(repo, base, node, 'files',
140 archival.archive(repo, base, node, 'files',
141 matchfn=scmutil.matchfiles(repo, files),
141 matchfn=scmutil.matchfiles(repo, files),
142 subrepos=listsubrepos)
142 subrepos=listsubrepos)
143
143
144 for fn in sorted(files):
144 for fn in sorted(files):
145 wfn = util.pconvert(fn)
145 wfn = util.pconvert(fn)
146 ui.note(' %s\n' % wfn)
146 ui.note(' %s\n' % wfn)
147
147
148 if node is None:
148 if node is None:
149 dest = os.path.join(base, wfn)
149 dest = os.path.join(base, wfn)
150
150
151 fnsandstat.append((dest, repo.wjoin(fn), os.lstat(dest)))
151 fnsandstat.append((dest, repo.wjoin(fn), os.lstat(dest)))
152 return dirname, fnsandstat
152 return dirname, fnsandstat
153
153
154 def dodiff(ui, repo, cmdline, pats, opts):
154 def dodiff(ui, repo, cmdline, pats, opts):
155 '''Do the actual diff:
155 '''Do the actual diff:
156
156
157 - copy to a temp structure if diffing 2 internal revisions
157 - copy to a temp structure if diffing 2 internal revisions
158 - copy to a temp structure if diffing working revision with
158 - copy to a temp structure if diffing working revision with
159 another one and more than 1 file is changed
159 another one and more than 1 file is changed
160 - just invoke the diff for a single file in the working dir
160 - just invoke the diff for a single file in the working dir
161 '''
161 '''
162
162
163 revs = opts.get('rev')
163 revs = opts.get('rev')
164 change = opts.get('change')
164 change = opts.get('change')
165 do3way = '$parent2' in cmdline
165 do3way = '$parent2' in cmdline
166
166
167 if revs and change:
167 if revs and change:
168 msg = _('cannot specify --rev and --change at the same time')
168 msg = _('cannot specify --rev and --change at the same time')
169 raise error.Abort(msg)
169 raise error.Abort(msg)
170 elif change:
170 elif change:
171 node2 = scmutil.revsingle(repo, change, None).node()
171 node2 = scmutil.revsingle(repo, change, None).node()
172 node1a, node1b = repo.changelog.parents(node2)
172 node1a, node1b = repo.changelog.parents(node2)
173 else:
173 else:
174 node1a, node2 = scmutil.revpair(repo, revs)
174 node1a, node2 = scmutil.revpairnodes(repo, revs)
175 if not revs:
175 if not revs:
176 node1b = repo.dirstate.p2()
176 node1b = repo.dirstate.p2()
177 else:
177 else:
178 node1b = nullid
178 node1b = nullid
179
179
180 # Disable 3-way merge if there is only one parent
180 # Disable 3-way merge if there is only one parent
181 if do3way:
181 if do3way:
182 if node1b == nullid:
182 if node1b == nullid:
183 do3way = False
183 do3way = False
184
184
185 subrepos=opts.get('subrepos')
185 subrepos=opts.get('subrepos')
186
186
187 matcher = scmutil.match(repo[node2], pats, opts)
187 matcher = scmutil.match(repo[node2], pats, opts)
188
188
189 if opts.get('patch'):
189 if opts.get('patch'):
190 if subrepos:
190 if subrepos:
191 raise error.Abort(_('--patch cannot be used with --subrepos'))
191 raise error.Abort(_('--patch cannot be used with --subrepos'))
192 if node2 is None:
192 if node2 is None:
193 raise error.Abort(_('--patch requires two revisions'))
193 raise error.Abort(_('--patch requires two revisions'))
194 else:
194 else:
195 mod_a, add_a, rem_a = map(set, repo.status(node1a, node2, matcher,
195 mod_a, add_a, rem_a = map(set, repo.status(node1a, node2, matcher,
196 listsubrepos=subrepos)[:3])
196 listsubrepos=subrepos)[:3])
197 if do3way:
197 if do3way:
198 mod_b, add_b, rem_b = map(set,
198 mod_b, add_b, rem_b = map(set,
199 repo.status(node1b, node2, matcher,
199 repo.status(node1b, node2, matcher,
200 listsubrepos=subrepos)[:3])
200 listsubrepos=subrepos)[:3])
201 else:
201 else:
202 mod_b, add_b, rem_b = set(), set(), set()
202 mod_b, add_b, rem_b = set(), set(), set()
203 modadd = mod_a | add_a | mod_b | add_b
203 modadd = mod_a | add_a | mod_b | add_b
204 common = modadd | rem_a | rem_b
204 common = modadd | rem_a | rem_b
205 if not common:
205 if not common:
206 return 0
206 return 0
207
207
208 tmproot = tempfile.mkdtemp(prefix='extdiff.')
208 tmproot = tempfile.mkdtemp(prefix='extdiff.')
209 try:
209 try:
210 if not opts.get('patch'):
210 if not opts.get('patch'):
211 # Always make a copy of node1a (and node1b, if applicable)
211 # Always make a copy of node1a (and node1b, if applicable)
212 dir1a_files = mod_a | rem_a | ((mod_b | add_b) - add_a)
212 dir1a_files = mod_a | rem_a | ((mod_b | add_b) - add_a)
213 dir1a = snapshot(ui, repo, dir1a_files, node1a, tmproot,
213 dir1a = snapshot(ui, repo, dir1a_files, node1a, tmproot,
214 subrepos)[0]
214 subrepos)[0]
215 rev1a = '@%d' % repo[node1a].rev()
215 rev1a = '@%d' % repo[node1a].rev()
216 if do3way:
216 if do3way:
217 dir1b_files = mod_b | rem_b | ((mod_a | add_a) - add_b)
217 dir1b_files = mod_b | rem_b | ((mod_a | add_a) - add_b)
218 dir1b = snapshot(ui, repo, dir1b_files, node1b, tmproot,
218 dir1b = snapshot(ui, repo, dir1b_files, node1b, tmproot,
219 subrepos)[0]
219 subrepos)[0]
220 rev1b = '@%d' % repo[node1b].rev()
220 rev1b = '@%d' % repo[node1b].rev()
221 else:
221 else:
222 dir1b = None
222 dir1b = None
223 rev1b = ''
223 rev1b = ''
224
224
225 fnsandstat = []
225 fnsandstat = []
226
226
227 # If node2 in not the wc or there is >1 change, copy it
227 # If node2 in not the wc or there is >1 change, copy it
228 dir2root = ''
228 dir2root = ''
229 rev2 = ''
229 rev2 = ''
230 if node2:
230 if node2:
231 dir2 = snapshot(ui, repo, modadd, node2, tmproot, subrepos)[0]
231 dir2 = snapshot(ui, repo, modadd, node2, tmproot, subrepos)[0]
232 rev2 = '@%d' % repo[node2].rev()
232 rev2 = '@%d' % repo[node2].rev()
233 elif len(common) > 1:
233 elif len(common) > 1:
234 #we only actually need to get the files to copy back to
234 #we only actually need to get the files to copy back to
235 #the working dir in this case (because the other cases
235 #the working dir in this case (because the other cases
236 #are: diffing 2 revisions or single file -- in which case
236 #are: diffing 2 revisions or single file -- in which case
237 #the file is already directly passed to the diff tool).
237 #the file is already directly passed to the diff tool).
238 dir2, fnsandstat = snapshot(ui, repo, modadd, None, tmproot,
238 dir2, fnsandstat = snapshot(ui, repo, modadd, None, tmproot,
239 subrepos)
239 subrepos)
240 else:
240 else:
241 # This lets the diff tool open the changed file directly
241 # This lets the diff tool open the changed file directly
242 dir2 = ''
242 dir2 = ''
243 dir2root = repo.root
243 dir2root = repo.root
244
244
245 label1a = rev1a
245 label1a = rev1a
246 label1b = rev1b
246 label1b = rev1b
247 label2 = rev2
247 label2 = rev2
248
248
249 # If only one change, diff the files instead of the directories
249 # If only one change, diff the files instead of the directories
250 # Handle bogus modifies correctly by checking if the files exist
250 # Handle bogus modifies correctly by checking if the files exist
251 if len(common) == 1:
251 if len(common) == 1:
252 common_file = util.localpath(common.pop())
252 common_file = util.localpath(common.pop())
253 dir1a = os.path.join(tmproot, dir1a, common_file)
253 dir1a = os.path.join(tmproot, dir1a, common_file)
254 label1a = common_file + rev1a
254 label1a = common_file + rev1a
255 if not os.path.isfile(dir1a):
255 if not os.path.isfile(dir1a):
256 dir1a = os.devnull
256 dir1a = os.devnull
257 if do3way:
257 if do3way:
258 dir1b = os.path.join(tmproot, dir1b, common_file)
258 dir1b = os.path.join(tmproot, dir1b, common_file)
259 label1b = common_file + rev1b
259 label1b = common_file + rev1b
260 if not os.path.isfile(dir1b):
260 if not os.path.isfile(dir1b):
261 dir1b = os.devnull
261 dir1b = os.devnull
262 dir2 = os.path.join(dir2root, dir2, common_file)
262 dir2 = os.path.join(dir2root, dir2, common_file)
263 label2 = common_file + rev2
263 label2 = common_file + rev2
264 else:
264 else:
265 template = 'hg-%h.patch'
265 template = 'hg-%h.patch'
266 cmdutil.export(repo, [repo[node1a].rev(), repo[node2].rev()],
266 cmdutil.export(repo, [repo[node1a].rev(), repo[node2].rev()],
267 fntemplate=repo.vfs.reljoin(tmproot, template),
267 fntemplate=repo.vfs.reljoin(tmproot, template),
268 match=matcher)
268 match=matcher)
269 label1a = cmdutil.makefilename(repo[node1a], template)
269 label1a = cmdutil.makefilename(repo[node1a], template)
270 label2 = cmdutil.makefilename(repo[node2], template)
270 label2 = cmdutil.makefilename(repo[node2], template)
271 dir1a = repo.vfs.reljoin(tmproot, label1a)
271 dir1a = repo.vfs.reljoin(tmproot, label1a)
272 dir2 = repo.vfs.reljoin(tmproot, label2)
272 dir2 = repo.vfs.reljoin(tmproot, label2)
273 dir1b = None
273 dir1b = None
274 label1b = None
274 label1b = None
275 fnsandstat = []
275 fnsandstat = []
276
276
277 # Function to quote file/dir names in the argument string.
277 # Function to quote file/dir names in the argument string.
278 # When not operating in 3-way mode, an empty string is
278 # When not operating in 3-way mode, an empty string is
279 # returned for parent2
279 # returned for parent2
280 replace = {'parent': dir1a, 'parent1': dir1a, 'parent2': dir1b,
280 replace = {'parent': dir1a, 'parent1': dir1a, 'parent2': dir1b,
281 'plabel1': label1a, 'plabel2': label1b,
281 'plabel1': label1a, 'plabel2': label1b,
282 'clabel': label2, 'child': dir2,
282 'clabel': label2, 'child': dir2,
283 'root': repo.root}
283 'root': repo.root}
284 def quote(match):
284 def quote(match):
285 pre = match.group(2)
285 pre = match.group(2)
286 key = match.group(3)
286 key = match.group(3)
287 if not do3way and key == 'parent2':
287 if not do3way and key == 'parent2':
288 return pre
288 return pre
289 return pre + procutil.shellquote(replace[key])
289 return pre + procutil.shellquote(replace[key])
290
290
291 # Match parent2 first, so 'parent1?' will match both parent1 and parent
291 # Match parent2 first, so 'parent1?' will match both parent1 and parent
292 regex = (br'''(['"]?)([^\s'"$]*)'''
292 regex = (br'''(['"]?)([^\s'"$]*)'''
293 br'\$(parent2|parent1?|child|plabel1|plabel2|clabel|root)\1')
293 br'\$(parent2|parent1?|child|plabel1|plabel2|clabel|root)\1')
294 if not do3way and not re.search(regex, cmdline):
294 if not do3way and not re.search(regex, cmdline):
295 cmdline += ' $parent1 $child'
295 cmdline += ' $parent1 $child'
296 cmdline = re.sub(regex, quote, cmdline)
296 cmdline = re.sub(regex, quote, cmdline)
297
297
298 ui.debug('running %r in %s\n' % (pycompat.bytestr(cmdline), tmproot))
298 ui.debug('running %r in %s\n' % (pycompat.bytestr(cmdline), tmproot))
299 ui.system(cmdline, cwd=tmproot, blockedtag='extdiff')
299 ui.system(cmdline, cwd=tmproot, blockedtag='extdiff')
300
300
301 for copy_fn, working_fn, st in fnsandstat:
301 for copy_fn, working_fn, st in fnsandstat:
302 cpstat = os.lstat(copy_fn)
302 cpstat = os.lstat(copy_fn)
303 # Some tools copy the file and attributes, so mtime may not detect
303 # Some tools copy the file and attributes, so mtime may not detect
304 # all changes. A size check will detect more cases, but not all.
304 # all changes. A size check will detect more cases, but not all.
305 # The only certain way to detect every case is to diff all files,
305 # The only certain way to detect every case is to diff all files,
306 # which could be expensive.
306 # which could be expensive.
307 # copyfile() carries over the permission, so the mode check could
307 # copyfile() carries over the permission, so the mode check could
308 # be in an 'elif' branch, but for the case where the file has
308 # be in an 'elif' branch, but for the case where the file has
309 # changed without affecting mtime or size.
309 # changed without affecting mtime or size.
310 if (cpstat[stat.ST_MTIME] != st[stat.ST_MTIME]
310 if (cpstat[stat.ST_MTIME] != st[stat.ST_MTIME]
311 or cpstat.st_size != st.st_size
311 or cpstat.st_size != st.st_size
312 or (cpstat.st_mode & 0o100) != (st.st_mode & 0o100)):
312 or (cpstat.st_mode & 0o100) != (st.st_mode & 0o100)):
313 ui.debug('file changed while diffing. '
313 ui.debug('file changed while diffing. '
314 'Overwriting: %s (src: %s)\n' % (working_fn, copy_fn))
314 'Overwriting: %s (src: %s)\n' % (working_fn, copy_fn))
315 util.copyfile(copy_fn, working_fn)
315 util.copyfile(copy_fn, working_fn)
316
316
317 return 1
317 return 1
318 finally:
318 finally:
319 ui.note(_('cleaning up temp directory\n'))
319 ui.note(_('cleaning up temp directory\n'))
320 shutil.rmtree(tmproot)
320 shutil.rmtree(tmproot)
321
321
322 extdiffopts = [
322 extdiffopts = [
323 ('o', 'option', [],
323 ('o', 'option', [],
324 _('pass option to comparison program'), _('OPT')),
324 _('pass option to comparison program'), _('OPT')),
325 ('r', 'rev', [], _('revision'), _('REV')),
325 ('r', 'rev', [], _('revision'), _('REV')),
326 ('c', 'change', '', _('change made by revision'), _('REV')),
326 ('c', 'change', '', _('change made by revision'), _('REV')),
327 ('', 'patch', None, _('compare patches for two revisions'))
327 ('', 'patch', None, _('compare patches for two revisions'))
328 ] + cmdutil.walkopts + cmdutil.subrepoopts
328 ] + cmdutil.walkopts + cmdutil.subrepoopts
329
329
330 @command('extdiff',
330 @command('extdiff',
331 [('p', 'program', '', _('comparison program to run'), _('CMD')),
331 [('p', 'program', '', _('comparison program to run'), _('CMD')),
332 ] + extdiffopts,
332 ] + extdiffopts,
333 _('hg extdiff [OPT]... [FILE]...'),
333 _('hg extdiff [OPT]... [FILE]...'),
334 inferrepo=True)
334 inferrepo=True)
335 def extdiff(ui, repo, *pats, **opts):
335 def extdiff(ui, repo, *pats, **opts):
336 '''use external program to diff repository (or selected files)
336 '''use external program to diff repository (or selected files)
337
337
338 Show differences between revisions for the specified files, using
338 Show differences between revisions for the specified files, using
339 an external program. The default program used is diff, with
339 an external program. The default program used is diff, with
340 default options "-Npru".
340 default options "-Npru".
341
341
342 To select a different program, use the -p/--program option. The
342 To select a different program, use the -p/--program option. The
343 program will be passed the names of two directories to compare. To
343 program will be passed the names of two directories to compare. To
344 pass additional options to the program, use -o/--option. These
344 pass additional options to the program, use -o/--option. These
345 will be passed before the names of the directories to compare.
345 will be passed before the names of the directories to compare.
346
346
347 When two revision arguments are given, then changes are shown
347 When two revision arguments are given, then changes are shown
348 between those revisions. If only one revision is specified then
348 between those revisions. If only one revision is specified then
349 that revision is compared to the working directory, and, when no
349 that revision is compared to the working directory, and, when no
350 revisions are specified, the working directory files are compared
350 revisions are specified, the working directory files are compared
351 to its parent.'''
351 to its parent.'''
352 opts = pycompat.byteskwargs(opts)
352 opts = pycompat.byteskwargs(opts)
353 program = opts.get('program')
353 program = opts.get('program')
354 option = opts.get('option')
354 option = opts.get('option')
355 if not program:
355 if not program:
356 program = 'diff'
356 program = 'diff'
357 option = option or ['-Npru']
357 option = option or ['-Npru']
358 cmdline = ' '.join(map(procutil.shellquote, [program] + option))
358 cmdline = ' '.join(map(procutil.shellquote, [program] + option))
359 return dodiff(ui, repo, cmdline, pats, opts)
359 return dodiff(ui, repo, cmdline, pats, opts)
360
360
361 class savedcmd(object):
361 class savedcmd(object):
362 """use external program to diff repository (or selected files)
362 """use external program to diff repository (or selected files)
363
363
364 Show differences between revisions for the specified files, using
364 Show differences between revisions for the specified files, using
365 the following program::
365 the following program::
366
366
367 %(path)s
367 %(path)s
368
368
369 When two revision arguments are given, then changes are shown
369 When two revision arguments are given, then changes are shown
370 between those revisions. If only one revision is specified then
370 between those revisions. If only one revision is specified then
371 that revision is compared to the working directory, and, when no
371 that revision is compared to the working directory, and, when no
372 revisions are specified, the working directory files are compared
372 revisions are specified, the working directory files are compared
373 to its parent.
373 to its parent.
374 """
374 """
375
375
376 def __init__(self, path, cmdline):
376 def __init__(self, path, cmdline):
377 # We can't pass non-ASCII through docstrings (and path is
377 # We can't pass non-ASCII through docstrings (and path is
378 # in an unknown encoding anyway)
378 # in an unknown encoding anyway)
379 docpath = stringutil.escapestr(path)
379 docpath = stringutil.escapestr(path)
380 self.__doc__ %= {r'path': pycompat.sysstr(stringutil.uirepr(docpath))}
380 self.__doc__ %= {r'path': pycompat.sysstr(stringutil.uirepr(docpath))}
381 self._cmdline = cmdline
381 self._cmdline = cmdline
382
382
383 def __call__(self, ui, repo, *pats, **opts):
383 def __call__(self, ui, repo, *pats, **opts):
384 opts = pycompat.byteskwargs(opts)
384 opts = pycompat.byteskwargs(opts)
385 options = ' '.join(map(procutil.shellquote, opts['option']))
385 options = ' '.join(map(procutil.shellquote, opts['option']))
386 if options:
386 if options:
387 options = ' ' + options
387 options = ' ' + options
388 return dodiff(ui, repo, self._cmdline + options, pats, opts)
388 return dodiff(ui, repo, self._cmdline + options, pats, opts)
389
389
390 def uisetup(ui):
390 def uisetup(ui):
391 for cmd, path in ui.configitems('extdiff'):
391 for cmd, path in ui.configitems('extdiff'):
392 path = util.expandpath(path)
392 path = util.expandpath(path)
393 if cmd.startswith('cmd.'):
393 if cmd.startswith('cmd.'):
394 cmd = cmd[4:]
394 cmd = cmd[4:]
395 if not path:
395 if not path:
396 path = procutil.findexe(cmd)
396 path = procutil.findexe(cmd)
397 if path is None:
397 if path is None:
398 path = filemerge.findexternaltool(ui, cmd) or cmd
398 path = filemerge.findexternaltool(ui, cmd) or cmd
399 diffopts = ui.config('extdiff', 'opts.' + cmd)
399 diffopts = ui.config('extdiff', 'opts.' + cmd)
400 cmdline = procutil.shellquote(path)
400 cmdline = procutil.shellquote(path)
401 if diffopts:
401 if diffopts:
402 cmdline += ' ' + diffopts
402 cmdline += ' ' + diffopts
403 elif cmd.startswith('opts.'):
403 elif cmd.startswith('opts.'):
404 continue
404 continue
405 else:
405 else:
406 if path:
406 if path:
407 # case "cmd = path opts"
407 # case "cmd = path opts"
408 cmdline = path
408 cmdline = path
409 diffopts = len(pycompat.shlexsplit(cmdline)) > 1
409 diffopts = len(pycompat.shlexsplit(cmdline)) > 1
410 else:
410 else:
411 # case "cmd ="
411 # case "cmd ="
412 path = procutil.findexe(cmd)
412 path = procutil.findexe(cmd)
413 if path is None:
413 if path is None:
414 path = filemerge.findexternaltool(ui, cmd) or cmd
414 path = filemerge.findexternaltool(ui, cmd) or cmd
415 cmdline = procutil.shellquote(path)
415 cmdline = procutil.shellquote(path)
416 diffopts = False
416 diffopts = False
417 # look for diff arguments in [diff-tools] then [merge-tools]
417 # look for diff arguments in [diff-tools] then [merge-tools]
418 if not diffopts:
418 if not diffopts:
419 args = ui.config('diff-tools', cmd+'.diffargs') or \
419 args = ui.config('diff-tools', cmd+'.diffargs') or \
420 ui.config('merge-tools', cmd+'.diffargs')
420 ui.config('merge-tools', cmd+'.diffargs')
421 if args:
421 if args:
422 cmdline += ' ' + args
422 cmdline += ' ' + args
423 command(cmd, extdiffopts[:], _('hg %s [OPTION]... [FILE]...') % cmd,
423 command(cmd, extdiffopts[:], _('hg %s [OPTION]... [FILE]...') % cmd,
424 inferrepo=True)(savedcmd(path, cmdline))
424 inferrepo=True)(savedcmd(path, cmdline))
425
425
426 # tell hggettext to extract docstrings from these functions:
426 # tell hggettext to extract docstrings from these functions:
427 i18nfunctions = [savedcmd]
427 i18nfunctions = [savedcmd]
@@ -1,5639 +1,5639 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 )
22 )
23 from . import (
23 from . import (
24 archival,
24 archival,
25 bookmarks,
25 bookmarks,
26 bundle2,
26 bundle2,
27 changegroup,
27 changegroup,
28 cmdutil,
28 cmdutil,
29 copies,
29 copies,
30 debugcommands as debugcommandsmod,
30 debugcommands as debugcommandsmod,
31 destutil,
31 destutil,
32 dirstateguard,
32 dirstateguard,
33 discovery,
33 discovery,
34 encoding,
34 encoding,
35 error,
35 error,
36 exchange,
36 exchange,
37 extensions,
37 extensions,
38 formatter,
38 formatter,
39 graphmod,
39 graphmod,
40 hbisect,
40 hbisect,
41 help,
41 help,
42 hg,
42 hg,
43 lock as lockmod,
43 lock as lockmod,
44 logcmdutil,
44 logcmdutil,
45 merge as mergemod,
45 merge as mergemod,
46 obsolete,
46 obsolete,
47 obsutil,
47 obsutil,
48 patch,
48 patch,
49 phases,
49 phases,
50 pycompat,
50 pycompat,
51 rcutil,
51 rcutil,
52 registrar,
52 registrar,
53 revsetlang,
53 revsetlang,
54 rewriteutil,
54 rewriteutil,
55 scmutil,
55 scmutil,
56 server,
56 server,
57 streamclone,
57 streamclone,
58 tags as tagsmod,
58 tags as tagsmod,
59 templatekw,
59 templatekw,
60 ui as uimod,
60 ui as uimod,
61 util,
61 util,
62 wireprotoserver,
62 wireprotoserver,
63 )
63 )
64 from .utils import (
64 from .utils import (
65 dateutil,
65 dateutil,
66 procutil,
66 procutil,
67 stringutil,
67 stringutil,
68 )
68 )
69
69
70 release = lockmod.release
70 release = lockmod.release
71
71
72 table = {}
72 table = {}
73 table.update(debugcommandsmod.command._table)
73 table.update(debugcommandsmod.command._table)
74
74
75 command = registrar.command(table)
75 command = registrar.command(table)
76 readonly = registrar.command.readonly
76 readonly = registrar.command.readonly
77
77
78 # common command options
78 # common command options
79
79
80 globalopts = [
80 globalopts = [
81 ('R', 'repository', '',
81 ('R', 'repository', '',
82 _('repository root directory or name of overlay bundle file'),
82 _('repository root directory or name of overlay bundle file'),
83 _('REPO')),
83 _('REPO')),
84 ('', 'cwd', '',
84 ('', 'cwd', '',
85 _('change working directory'), _('DIR')),
85 _('change working directory'), _('DIR')),
86 ('y', 'noninteractive', None,
86 ('y', 'noninteractive', None,
87 _('do not prompt, automatically pick the first choice for all prompts')),
87 _('do not prompt, automatically pick the first choice for all prompts')),
88 ('q', 'quiet', None, _('suppress output')),
88 ('q', 'quiet', None, _('suppress output')),
89 ('v', 'verbose', None, _('enable additional output')),
89 ('v', 'verbose', None, _('enable additional output')),
90 ('', 'color', '',
90 ('', 'color', '',
91 # i18n: 'always', 'auto', 'never', and 'debug' are keywords
91 # i18n: 'always', 'auto', 'never', and 'debug' are keywords
92 # and should not be translated
92 # and should not be translated
93 _("when to colorize (boolean, always, auto, never, or debug)"),
93 _("when to colorize (boolean, always, auto, never, or debug)"),
94 _('TYPE')),
94 _('TYPE')),
95 ('', 'config', [],
95 ('', 'config', [],
96 _('set/override config option (use \'section.name=value\')'),
96 _('set/override config option (use \'section.name=value\')'),
97 _('CONFIG')),
97 _('CONFIG')),
98 ('', 'debug', None, _('enable debugging output')),
98 ('', 'debug', None, _('enable debugging output')),
99 ('', 'debugger', None, _('start debugger')),
99 ('', 'debugger', None, _('start debugger')),
100 ('', 'encoding', encoding.encoding, _('set the charset encoding'),
100 ('', 'encoding', encoding.encoding, _('set the charset encoding'),
101 _('ENCODE')),
101 _('ENCODE')),
102 ('', 'encodingmode', encoding.encodingmode,
102 ('', 'encodingmode', encoding.encodingmode,
103 _('set the charset encoding mode'), _('MODE')),
103 _('set the charset encoding mode'), _('MODE')),
104 ('', 'traceback', None, _('always print a traceback on exception')),
104 ('', 'traceback', None, _('always print a traceback on exception')),
105 ('', 'time', None, _('time how long the command takes')),
105 ('', 'time', None, _('time how long the command takes')),
106 ('', 'profile', None, _('print command execution profile')),
106 ('', 'profile', None, _('print command execution profile')),
107 ('', 'version', None, _('output version information and exit')),
107 ('', 'version', None, _('output version information and exit')),
108 ('h', 'help', None, _('display help and exit')),
108 ('h', 'help', None, _('display help and exit')),
109 ('', 'hidden', False, _('consider hidden changesets')),
109 ('', 'hidden', False, _('consider hidden changesets')),
110 ('', 'pager', 'auto',
110 ('', 'pager', 'auto',
111 _("when to paginate (boolean, always, auto, or never)"), _('TYPE')),
111 _("when to paginate (boolean, always, auto, or never)"), _('TYPE')),
112 ]
112 ]
113
113
114 dryrunopts = cmdutil.dryrunopts
114 dryrunopts = cmdutil.dryrunopts
115 remoteopts = cmdutil.remoteopts
115 remoteopts = cmdutil.remoteopts
116 walkopts = cmdutil.walkopts
116 walkopts = cmdutil.walkopts
117 commitopts = cmdutil.commitopts
117 commitopts = cmdutil.commitopts
118 commitopts2 = cmdutil.commitopts2
118 commitopts2 = cmdutil.commitopts2
119 formatteropts = cmdutil.formatteropts
119 formatteropts = cmdutil.formatteropts
120 templateopts = cmdutil.templateopts
120 templateopts = cmdutil.templateopts
121 logopts = cmdutil.logopts
121 logopts = cmdutil.logopts
122 diffopts = cmdutil.diffopts
122 diffopts = cmdutil.diffopts
123 diffwsopts = cmdutil.diffwsopts
123 diffwsopts = cmdutil.diffwsopts
124 diffopts2 = cmdutil.diffopts2
124 diffopts2 = cmdutil.diffopts2
125 mergetoolopts = cmdutil.mergetoolopts
125 mergetoolopts = cmdutil.mergetoolopts
126 similarityopts = cmdutil.similarityopts
126 similarityopts = cmdutil.similarityopts
127 subrepoopts = cmdutil.subrepoopts
127 subrepoopts = cmdutil.subrepoopts
128 debugrevlogopts = cmdutil.debugrevlogopts
128 debugrevlogopts = cmdutil.debugrevlogopts
129
129
130 # Commands start here, listed alphabetically
130 # Commands start here, listed alphabetically
131
131
132 @command('^add',
132 @command('^add',
133 walkopts + subrepoopts + dryrunopts,
133 walkopts + subrepoopts + dryrunopts,
134 _('[OPTION]... [FILE]...'),
134 _('[OPTION]... [FILE]...'),
135 inferrepo=True)
135 inferrepo=True)
136 def add(ui, repo, *pats, **opts):
136 def add(ui, repo, *pats, **opts):
137 """add the specified files on the next commit
137 """add the specified files on the next commit
138
138
139 Schedule files to be version controlled and added to the
139 Schedule files to be version controlled and added to the
140 repository.
140 repository.
141
141
142 The files will be added to the repository at the next commit. To
142 The files will be added to the repository at the next commit. To
143 undo an add before that, see :hg:`forget`.
143 undo an add before that, see :hg:`forget`.
144
144
145 If no names are given, add all files to the repository (except
145 If no names are given, add all files to the repository (except
146 files matching ``.hgignore``).
146 files matching ``.hgignore``).
147
147
148 .. container:: verbose
148 .. container:: verbose
149
149
150 Examples:
150 Examples:
151
151
152 - New (unknown) files are added
152 - New (unknown) files are added
153 automatically by :hg:`add`::
153 automatically by :hg:`add`::
154
154
155 $ ls
155 $ ls
156 foo.c
156 foo.c
157 $ hg status
157 $ hg status
158 ? foo.c
158 ? foo.c
159 $ hg add
159 $ hg add
160 adding foo.c
160 adding foo.c
161 $ hg status
161 $ hg status
162 A foo.c
162 A foo.c
163
163
164 - Specific files to be added can be specified::
164 - Specific files to be added can be specified::
165
165
166 $ ls
166 $ ls
167 bar.c foo.c
167 bar.c foo.c
168 $ hg status
168 $ hg status
169 ? bar.c
169 ? bar.c
170 ? foo.c
170 ? foo.c
171 $ hg add bar.c
171 $ hg add bar.c
172 $ hg status
172 $ hg status
173 A bar.c
173 A bar.c
174 ? foo.c
174 ? foo.c
175
175
176 Returns 0 if all files are successfully added.
176 Returns 0 if all files are successfully added.
177 """
177 """
178
178
179 m = scmutil.match(repo[None], pats, pycompat.byteskwargs(opts))
179 m = scmutil.match(repo[None], pats, pycompat.byteskwargs(opts))
180 rejected = cmdutil.add(ui, repo, m, "", False, **opts)
180 rejected = cmdutil.add(ui, repo, m, "", False, **opts)
181 return rejected and 1 or 0
181 return rejected and 1 or 0
182
182
183 @command('addremove',
183 @command('addremove',
184 similarityopts + subrepoopts + walkopts + dryrunopts,
184 similarityopts + subrepoopts + walkopts + dryrunopts,
185 _('[OPTION]... [FILE]...'),
185 _('[OPTION]... [FILE]...'),
186 inferrepo=True)
186 inferrepo=True)
187 def addremove(ui, repo, *pats, **opts):
187 def addremove(ui, repo, *pats, **opts):
188 """add all new files, delete all missing files
188 """add all new files, delete all missing files
189
189
190 Add all new files and remove all missing files from the
190 Add all new files and remove all missing files from the
191 repository.
191 repository.
192
192
193 Unless names are given, new files are ignored if they match any of
193 Unless names are given, new files are ignored if they match any of
194 the patterns in ``.hgignore``. As with add, these changes take
194 the patterns in ``.hgignore``. As with add, these changes take
195 effect at the next commit.
195 effect at the next commit.
196
196
197 Use the -s/--similarity option to detect renamed files. This
197 Use the -s/--similarity option to detect renamed files. This
198 option takes a percentage between 0 (disabled) and 100 (files must
198 option takes a percentage between 0 (disabled) and 100 (files must
199 be identical) as its parameter. With a parameter greater than 0,
199 be identical) as its parameter. With a parameter greater than 0,
200 this compares every removed file with every added file and records
200 this compares every removed file with every added file and records
201 those similar enough as renames. Detecting renamed files this way
201 those similar enough as renames. Detecting renamed files this way
202 can be expensive. After using this option, :hg:`status -C` can be
202 can be expensive. After using this option, :hg:`status -C` can be
203 used to check which files were identified as moved or renamed. If
203 used to check which files were identified as moved or renamed. If
204 not specified, -s/--similarity defaults to 100 and only renames of
204 not specified, -s/--similarity defaults to 100 and only renames of
205 identical files are detected.
205 identical files are detected.
206
206
207 .. container:: verbose
207 .. container:: verbose
208
208
209 Examples:
209 Examples:
210
210
211 - A number of files (bar.c and foo.c) are new,
211 - A number of files (bar.c and foo.c) are new,
212 while foobar.c has been removed (without using :hg:`remove`)
212 while foobar.c has been removed (without using :hg:`remove`)
213 from the repository::
213 from the repository::
214
214
215 $ ls
215 $ ls
216 bar.c foo.c
216 bar.c foo.c
217 $ hg status
217 $ hg status
218 ! foobar.c
218 ! foobar.c
219 ? bar.c
219 ? bar.c
220 ? foo.c
220 ? foo.c
221 $ hg addremove
221 $ hg addremove
222 adding bar.c
222 adding bar.c
223 adding foo.c
223 adding foo.c
224 removing foobar.c
224 removing foobar.c
225 $ hg status
225 $ hg status
226 A bar.c
226 A bar.c
227 A foo.c
227 A foo.c
228 R foobar.c
228 R foobar.c
229
229
230 - A file foobar.c was moved to foo.c without using :hg:`rename`.
230 - A file foobar.c was moved to foo.c without using :hg:`rename`.
231 Afterwards, it was edited slightly::
231 Afterwards, it was edited slightly::
232
232
233 $ ls
233 $ ls
234 foo.c
234 foo.c
235 $ hg status
235 $ hg status
236 ! foobar.c
236 ! foobar.c
237 ? foo.c
237 ? foo.c
238 $ hg addremove --similarity 90
238 $ hg addremove --similarity 90
239 removing foobar.c
239 removing foobar.c
240 adding foo.c
240 adding foo.c
241 recording removal of foobar.c as rename to foo.c (94% similar)
241 recording removal of foobar.c as rename to foo.c (94% similar)
242 $ hg status -C
242 $ hg status -C
243 A foo.c
243 A foo.c
244 foobar.c
244 foobar.c
245 R foobar.c
245 R foobar.c
246
246
247 Returns 0 if all files are successfully added.
247 Returns 0 if all files are successfully added.
248 """
248 """
249 opts = pycompat.byteskwargs(opts)
249 opts = pycompat.byteskwargs(opts)
250 try:
250 try:
251 sim = float(opts.get('similarity') or 100)
251 sim = float(opts.get('similarity') or 100)
252 except ValueError:
252 except ValueError:
253 raise error.Abort(_('similarity must be a number'))
253 raise error.Abort(_('similarity must be a number'))
254 if sim < 0 or sim > 100:
254 if sim < 0 or sim > 100:
255 raise error.Abort(_('similarity must be between 0 and 100'))
255 raise error.Abort(_('similarity must be between 0 and 100'))
256 matcher = scmutil.match(repo[None], pats, opts)
256 matcher = scmutil.match(repo[None], pats, opts)
257 return scmutil.addremove(repo, matcher, "", opts, similarity=sim / 100.0)
257 return scmutil.addremove(repo, matcher, "", opts, similarity=sim / 100.0)
258
258
259 @command('^annotate|blame',
259 @command('^annotate|blame',
260 [('r', 'rev', '', _('annotate the specified revision'), _('REV')),
260 [('r', 'rev', '', _('annotate the specified revision'), _('REV')),
261 ('', 'follow', None,
261 ('', 'follow', None,
262 _('follow copies/renames and list the filename (DEPRECATED)')),
262 _('follow copies/renames and list the filename (DEPRECATED)')),
263 ('', 'no-follow', None, _("don't follow copies and renames")),
263 ('', 'no-follow', None, _("don't follow copies and renames")),
264 ('a', 'text', None, _('treat all files as text')),
264 ('a', 'text', None, _('treat all files as text')),
265 ('u', 'user', None, _('list the author (long with -v)')),
265 ('u', 'user', None, _('list the author (long with -v)')),
266 ('f', 'file', None, _('list the filename')),
266 ('f', 'file', None, _('list the filename')),
267 ('d', 'date', None, _('list the date (short with -q)')),
267 ('d', 'date', None, _('list the date (short with -q)')),
268 ('n', 'number', None, _('list the revision number (default)')),
268 ('n', 'number', None, _('list the revision number (default)')),
269 ('c', 'changeset', None, _('list the changeset')),
269 ('c', 'changeset', None, _('list the changeset')),
270 ('l', 'line-number', None, _('show line number at the first appearance')),
270 ('l', 'line-number', None, _('show line number at the first appearance')),
271 ('', 'skip', [], _('revision to not display (EXPERIMENTAL)'), _('REV')),
271 ('', 'skip', [], _('revision to not display (EXPERIMENTAL)'), _('REV')),
272 ] + diffwsopts + walkopts + formatteropts,
272 ] + diffwsopts + walkopts + formatteropts,
273 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'),
273 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'),
274 inferrepo=True)
274 inferrepo=True)
275 def annotate(ui, repo, *pats, **opts):
275 def annotate(ui, repo, *pats, **opts):
276 """show changeset information by line for each file
276 """show changeset information by line for each file
277
277
278 List changes in files, showing the revision id responsible for
278 List changes in files, showing the revision id responsible for
279 each line.
279 each line.
280
280
281 This command is useful for discovering when a change was made and
281 This command is useful for discovering when a change was made and
282 by whom.
282 by whom.
283
283
284 If you include --file, --user, or --date, the revision number is
284 If you include --file, --user, or --date, the revision number is
285 suppressed unless you also include --number.
285 suppressed unless you also include --number.
286
286
287 Without the -a/--text option, annotate will avoid processing files
287 Without the -a/--text option, annotate will avoid processing files
288 it detects as binary. With -a, annotate will annotate the file
288 it detects as binary. With -a, annotate will annotate the file
289 anyway, although the results will probably be neither useful
289 anyway, although the results will probably be neither useful
290 nor desirable.
290 nor desirable.
291
291
292 Returns 0 on success.
292 Returns 0 on success.
293 """
293 """
294 opts = pycompat.byteskwargs(opts)
294 opts = pycompat.byteskwargs(opts)
295 if not pats:
295 if not pats:
296 raise error.Abort(_('at least one filename or pattern is required'))
296 raise error.Abort(_('at least one filename or pattern is required'))
297
297
298 if opts.get('follow'):
298 if opts.get('follow'):
299 # --follow is deprecated and now just an alias for -f/--file
299 # --follow is deprecated and now just an alias for -f/--file
300 # to mimic the behavior of Mercurial before version 1.5
300 # to mimic the behavior of Mercurial before version 1.5
301 opts['file'] = True
301 opts['file'] = True
302
302
303 rev = opts.get('rev')
303 rev = opts.get('rev')
304 if rev:
304 if rev:
305 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
305 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
306 ctx = scmutil.revsingle(repo, rev)
306 ctx = scmutil.revsingle(repo, rev)
307
307
308 rootfm = ui.formatter('annotate', opts)
308 rootfm = ui.formatter('annotate', opts)
309 if ui.quiet:
309 if ui.quiet:
310 datefunc = dateutil.shortdate
310 datefunc = dateutil.shortdate
311 else:
311 else:
312 datefunc = dateutil.datestr
312 datefunc = dateutil.datestr
313 if ctx.rev() is None:
313 if ctx.rev() is None:
314 def hexfn(node):
314 def hexfn(node):
315 if node is None:
315 if node is None:
316 return None
316 return None
317 else:
317 else:
318 return rootfm.hexfunc(node)
318 return rootfm.hexfunc(node)
319 if opts.get('changeset'):
319 if opts.get('changeset'):
320 # omit "+" suffix which is appended to node hex
320 # omit "+" suffix which is appended to node hex
321 def formatrev(rev):
321 def formatrev(rev):
322 if rev is None:
322 if rev is None:
323 return '%d' % ctx.p1().rev()
323 return '%d' % ctx.p1().rev()
324 else:
324 else:
325 return '%d' % rev
325 return '%d' % rev
326 else:
326 else:
327 def formatrev(rev):
327 def formatrev(rev):
328 if rev is None:
328 if rev is None:
329 return '%d+' % ctx.p1().rev()
329 return '%d+' % ctx.p1().rev()
330 else:
330 else:
331 return '%d ' % rev
331 return '%d ' % rev
332 def formathex(hex):
332 def formathex(hex):
333 if hex is None:
333 if hex is None:
334 return '%s+' % rootfm.hexfunc(ctx.p1().node())
334 return '%s+' % rootfm.hexfunc(ctx.p1().node())
335 else:
335 else:
336 return '%s ' % hex
336 return '%s ' % hex
337 else:
337 else:
338 hexfn = rootfm.hexfunc
338 hexfn = rootfm.hexfunc
339 formatrev = formathex = pycompat.bytestr
339 formatrev = formathex = pycompat.bytestr
340
340
341 opmap = [('user', ' ', lambda x: x.fctx.user(), ui.shortuser),
341 opmap = [('user', ' ', lambda x: x.fctx.user(), ui.shortuser),
342 ('number', ' ', lambda x: x.fctx.rev(), formatrev),
342 ('number', ' ', lambda x: x.fctx.rev(), formatrev),
343 ('changeset', ' ', lambda x: hexfn(x.fctx.node()), formathex),
343 ('changeset', ' ', lambda x: hexfn(x.fctx.node()), formathex),
344 ('date', ' ', lambda x: x.fctx.date(), util.cachefunc(datefunc)),
344 ('date', ' ', lambda x: x.fctx.date(), util.cachefunc(datefunc)),
345 ('file', ' ', lambda x: x.fctx.path(), pycompat.bytestr),
345 ('file', ' ', lambda x: x.fctx.path(), pycompat.bytestr),
346 ('line_number', ':', lambda x: x.lineno, pycompat.bytestr),
346 ('line_number', ':', lambda x: x.lineno, pycompat.bytestr),
347 ]
347 ]
348 fieldnamemap = {'number': 'rev', 'changeset': 'node'}
348 fieldnamemap = {'number': 'rev', 'changeset': 'node'}
349
349
350 if (not opts.get('user') and not opts.get('changeset')
350 if (not opts.get('user') and not opts.get('changeset')
351 and not opts.get('date') and not opts.get('file')):
351 and not opts.get('date') and not opts.get('file')):
352 opts['number'] = True
352 opts['number'] = True
353
353
354 linenumber = opts.get('line_number') is not None
354 linenumber = opts.get('line_number') is not None
355 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
355 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
356 raise error.Abort(_('at least one of -n/-c is required for -l'))
356 raise error.Abort(_('at least one of -n/-c is required for -l'))
357
357
358 ui.pager('annotate')
358 ui.pager('annotate')
359
359
360 if rootfm.isplain():
360 if rootfm.isplain():
361 def makefunc(get, fmt):
361 def makefunc(get, fmt):
362 return lambda x: fmt(get(x))
362 return lambda x: fmt(get(x))
363 else:
363 else:
364 def makefunc(get, fmt):
364 def makefunc(get, fmt):
365 return get
365 return get
366 funcmap = [(makefunc(get, fmt), sep) for op, sep, get, fmt in opmap
366 funcmap = [(makefunc(get, fmt), sep) for op, sep, get, fmt in opmap
367 if opts.get(op)]
367 if opts.get(op)]
368 funcmap[0] = (funcmap[0][0], '') # no separator in front of first column
368 funcmap[0] = (funcmap[0][0], '') # no separator in front of first column
369 fields = ' '.join(fieldnamemap.get(op, op) for op, sep, get, fmt in opmap
369 fields = ' '.join(fieldnamemap.get(op, op) for op, sep, get, fmt in opmap
370 if opts.get(op))
370 if opts.get(op))
371
371
372 def bad(x, y):
372 def bad(x, y):
373 raise error.Abort("%s: %s" % (x, y))
373 raise error.Abort("%s: %s" % (x, y))
374
374
375 m = scmutil.match(ctx, pats, opts, badfn=bad)
375 m = scmutil.match(ctx, pats, opts, badfn=bad)
376
376
377 follow = not opts.get('no_follow')
377 follow = not opts.get('no_follow')
378 diffopts = patch.difffeatureopts(ui, opts, section='annotate',
378 diffopts = patch.difffeatureopts(ui, opts, section='annotate',
379 whitespace=True)
379 whitespace=True)
380 skiprevs = opts.get('skip')
380 skiprevs = opts.get('skip')
381 if skiprevs:
381 if skiprevs:
382 skiprevs = scmutil.revrange(repo, skiprevs)
382 skiprevs = scmutil.revrange(repo, skiprevs)
383
383
384 for abs in ctx.walk(m):
384 for abs in ctx.walk(m):
385 fctx = ctx[abs]
385 fctx = ctx[abs]
386 rootfm.startitem()
386 rootfm.startitem()
387 rootfm.data(abspath=abs, path=m.rel(abs))
387 rootfm.data(abspath=abs, path=m.rel(abs))
388 if not opts.get('text') and fctx.isbinary():
388 if not opts.get('text') and fctx.isbinary():
389 rootfm.plain(_("%s: binary file\n")
389 rootfm.plain(_("%s: binary file\n")
390 % ((pats and m.rel(abs)) or abs))
390 % ((pats and m.rel(abs)) or abs))
391 continue
391 continue
392
392
393 fm = rootfm.nested('lines')
393 fm = rootfm.nested('lines')
394 lines = fctx.annotate(follow=follow, skiprevs=skiprevs,
394 lines = fctx.annotate(follow=follow, skiprevs=skiprevs,
395 diffopts=diffopts)
395 diffopts=diffopts)
396 if not lines:
396 if not lines:
397 fm.end()
397 fm.end()
398 continue
398 continue
399 formats = []
399 formats = []
400 pieces = []
400 pieces = []
401
401
402 for f, sep in funcmap:
402 for f, sep in funcmap:
403 l = [f(n) for n in lines]
403 l = [f(n) for n in lines]
404 if fm.isplain():
404 if fm.isplain():
405 sizes = [encoding.colwidth(x) for x in l]
405 sizes = [encoding.colwidth(x) for x in l]
406 ml = max(sizes)
406 ml = max(sizes)
407 formats.append([sep + ' ' * (ml - w) + '%s' for w in sizes])
407 formats.append([sep + ' ' * (ml - w) + '%s' for w in sizes])
408 else:
408 else:
409 formats.append(['%s' for x in l])
409 formats.append(['%s' for x in l])
410 pieces.append(l)
410 pieces.append(l)
411
411
412 for f, p, n in zip(zip(*formats), zip(*pieces), lines):
412 for f, p, n in zip(zip(*formats), zip(*pieces), lines):
413 fm.startitem()
413 fm.startitem()
414 fm.context(fctx=n.fctx)
414 fm.context(fctx=n.fctx)
415 fm.write(fields, "".join(f), *p)
415 fm.write(fields, "".join(f), *p)
416 if n.skip:
416 if n.skip:
417 fmt = "* %s"
417 fmt = "* %s"
418 else:
418 else:
419 fmt = ": %s"
419 fmt = ": %s"
420 fm.write('line', fmt, n.text)
420 fm.write('line', fmt, n.text)
421
421
422 if not lines[-1].text.endswith('\n'):
422 if not lines[-1].text.endswith('\n'):
423 fm.plain('\n')
423 fm.plain('\n')
424 fm.end()
424 fm.end()
425
425
426 rootfm.end()
426 rootfm.end()
427
427
428 @command('archive',
428 @command('archive',
429 [('', 'no-decode', None, _('do not pass files through decoders')),
429 [('', 'no-decode', None, _('do not pass files through decoders')),
430 ('p', 'prefix', '', _('directory prefix for files in archive'),
430 ('p', 'prefix', '', _('directory prefix for files in archive'),
431 _('PREFIX')),
431 _('PREFIX')),
432 ('r', 'rev', '', _('revision to distribute'), _('REV')),
432 ('r', 'rev', '', _('revision to distribute'), _('REV')),
433 ('t', 'type', '', _('type of distribution to create'), _('TYPE')),
433 ('t', 'type', '', _('type of distribution to create'), _('TYPE')),
434 ] + subrepoopts + walkopts,
434 ] + subrepoopts + walkopts,
435 _('[OPTION]... DEST'))
435 _('[OPTION]... DEST'))
436 def archive(ui, repo, dest, **opts):
436 def archive(ui, repo, dest, **opts):
437 '''create an unversioned archive of a repository revision
437 '''create an unversioned archive of a repository revision
438
438
439 By default, the revision used is the parent of the working
439 By default, the revision used is the parent of the working
440 directory; use -r/--rev to specify a different revision.
440 directory; use -r/--rev to specify a different revision.
441
441
442 The archive type is automatically detected based on file
442 The archive type is automatically detected based on file
443 extension (to override, use -t/--type).
443 extension (to override, use -t/--type).
444
444
445 .. container:: verbose
445 .. container:: verbose
446
446
447 Examples:
447 Examples:
448
448
449 - create a zip file containing the 1.0 release::
449 - create a zip file containing the 1.0 release::
450
450
451 hg archive -r 1.0 project-1.0.zip
451 hg archive -r 1.0 project-1.0.zip
452
452
453 - create a tarball excluding .hg files::
453 - create a tarball excluding .hg files::
454
454
455 hg archive project.tar.gz -X ".hg*"
455 hg archive project.tar.gz -X ".hg*"
456
456
457 Valid types are:
457 Valid types are:
458
458
459 :``files``: a directory full of files (default)
459 :``files``: a directory full of files (default)
460 :``tar``: tar archive, uncompressed
460 :``tar``: tar archive, uncompressed
461 :``tbz2``: tar archive, compressed using bzip2
461 :``tbz2``: tar archive, compressed using bzip2
462 :``tgz``: tar archive, compressed using gzip
462 :``tgz``: tar archive, compressed using gzip
463 :``uzip``: zip archive, uncompressed
463 :``uzip``: zip archive, uncompressed
464 :``zip``: zip archive, compressed using deflate
464 :``zip``: zip archive, compressed using deflate
465
465
466 The exact name of the destination archive or directory is given
466 The exact name of the destination archive or directory is given
467 using a format string; see :hg:`help export` for details.
467 using a format string; see :hg:`help export` for details.
468
468
469 Each member added to an archive file has a directory prefix
469 Each member added to an archive file has a directory prefix
470 prepended. Use -p/--prefix to specify a format string for the
470 prepended. Use -p/--prefix to specify a format string for the
471 prefix. The default is the basename of the archive, with suffixes
471 prefix. The default is the basename of the archive, with suffixes
472 removed.
472 removed.
473
473
474 Returns 0 on success.
474 Returns 0 on success.
475 '''
475 '''
476
476
477 opts = pycompat.byteskwargs(opts)
477 opts = pycompat.byteskwargs(opts)
478 rev = opts.get('rev')
478 rev = opts.get('rev')
479 if rev:
479 if rev:
480 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
480 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
481 ctx = scmutil.revsingle(repo, rev)
481 ctx = scmutil.revsingle(repo, rev)
482 if not ctx:
482 if not ctx:
483 raise error.Abort(_('no working directory: please specify a revision'))
483 raise error.Abort(_('no working directory: please specify a revision'))
484 node = ctx.node()
484 node = ctx.node()
485 dest = cmdutil.makefilename(ctx, dest)
485 dest = cmdutil.makefilename(ctx, dest)
486 if os.path.realpath(dest) == repo.root:
486 if os.path.realpath(dest) == repo.root:
487 raise error.Abort(_('repository root cannot be destination'))
487 raise error.Abort(_('repository root cannot be destination'))
488
488
489 kind = opts.get('type') or archival.guesskind(dest) or 'files'
489 kind = opts.get('type') or archival.guesskind(dest) or 'files'
490 prefix = opts.get('prefix')
490 prefix = opts.get('prefix')
491
491
492 if dest == '-':
492 if dest == '-':
493 if kind == 'files':
493 if kind == 'files':
494 raise error.Abort(_('cannot archive plain files to stdout'))
494 raise error.Abort(_('cannot archive plain files to stdout'))
495 dest = cmdutil.makefileobj(ctx, dest)
495 dest = cmdutil.makefileobj(ctx, dest)
496 if not prefix:
496 if not prefix:
497 prefix = os.path.basename(repo.root) + '-%h'
497 prefix = os.path.basename(repo.root) + '-%h'
498
498
499 prefix = cmdutil.makefilename(ctx, prefix)
499 prefix = cmdutil.makefilename(ctx, prefix)
500 match = scmutil.match(ctx, [], opts)
500 match = scmutil.match(ctx, [], opts)
501 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
501 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
502 match, prefix, subrepos=opts.get('subrepos'))
502 match, prefix, subrepos=opts.get('subrepos'))
503
503
504 @command('backout',
504 @command('backout',
505 [('', 'merge', None, _('merge with old dirstate parent after backout')),
505 [('', 'merge', None, _('merge with old dirstate parent after backout')),
506 ('', 'commit', None,
506 ('', 'commit', None,
507 _('commit if no conflicts were encountered (DEPRECATED)')),
507 _('commit if no conflicts were encountered (DEPRECATED)')),
508 ('', 'no-commit', None, _('do not commit')),
508 ('', 'no-commit', None, _('do not commit')),
509 ('', 'parent', '',
509 ('', 'parent', '',
510 _('parent to choose when backing out merge (DEPRECATED)'), _('REV')),
510 _('parent to choose when backing out merge (DEPRECATED)'), _('REV')),
511 ('r', 'rev', '', _('revision to backout'), _('REV')),
511 ('r', 'rev', '', _('revision to backout'), _('REV')),
512 ('e', 'edit', False, _('invoke editor on commit messages')),
512 ('e', 'edit', False, _('invoke editor on commit messages')),
513 ] + mergetoolopts + walkopts + commitopts + commitopts2,
513 ] + mergetoolopts + walkopts + commitopts + commitopts2,
514 _('[OPTION]... [-r] REV'))
514 _('[OPTION]... [-r] REV'))
515 def backout(ui, repo, node=None, rev=None, **opts):
515 def backout(ui, repo, node=None, rev=None, **opts):
516 '''reverse effect of earlier changeset
516 '''reverse effect of earlier changeset
517
517
518 Prepare a new changeset with the effect of REV undone in the
518 Prepare a new changeset with the effect of REV undone in the
519 current working directory. If no conflicts were encountered,
519 current working directory. If no conflicts were encountered,
520 it will be committed immediately.
520 it will be committed immediately.
521
521
522 If REV is the parent of the working directory, then this new changeset
522 If REV is the parent of the working directory, then this new changeset
523 is committed automatically (unless --no-commit is specified).
523 is committed automatically (unless --no-commit is specified).
524
524
525 .. note::
525 .. note::
526
526
527 :hg:`backout` cannot be used to fix either an unwanted or
527 :hg:`backout` cannot be used to fix either an unwanted or
528 incorrect merge.
528 incorrect merge.
529
529
530 .. container:: verbose
530 .. container:: verbose
531
531
532 Examples:
532 Examples:
533
533
534 - Reverse the effect of the parent of the working directory.
534 - Reverse the effect of the parent of the working directory.
535 This backout will be committed immediately::
535 This backout will be committed immediately::
536
536
537 hg backout -r .
537 hg backout -r .
538
538
539 - Reverse the effect of previous bad revision 23::
539 - Reverse the effect of previous bad revision 23::
540
540
541 hg backout -r 23
541 hg backout -r 23
542
542
543 - Reverse the effect of previous bad revision 23 and
543 - Reverse the effect of previous bad revision 23 and
544 leave changes uncommitted::
544 leave changes uncommitted::
545
545
546 hg backout -r 23 --no-commit
546 hg backout -r 23 --no-commit
547 hg commit -m "Backout revision 23"
547 hg commit -m "Backout revision 23"
548
548
549 By default, the pending changeset will have one parent,
549 By default, the pending changeset will have one parent,
550 maintaining a linear history. With --merge, the pending
550 maintaining a linear history. With --merge, the pending
551 changeset will instead have two parents: the old parent of the
551 changeset will instead have two parents: the old parent of the
552 working directory and a new child of REV that simply undoes REV.
552 working directory and a new child of REV that simply undoes REV.
553
553
554 Before version 1.7, the behavior without --merge was equivalent
554 Before version 1.7, the behavior without --merge was equivalent
555 to specifying --merge followed by :hg:`update --clean .` to
555 to specifying --merge followed by :hg:`update --clean .` to
556 cancel the merge and leave the child of REV as a head to be
556 cancel the merge and leave the child of REV as a head to be
557 merged separately.
557 merged separately.
558
558
559 See :hg:`help dates` for a list of formats valid for -d/--date.
559 See :hg:`help dates` for a list of formats valid for -d/--date.
560
560
561 See :hg:`help revert` for a way to restore files to the state
561 See :hg:`help revert` for a way to restore files to the state
562 of another revision.
562 of another revision.
563
563
564 Returns 0 on success, 1 if nothing to backout or there are unresolved
564 Returns 0 on success, 1 if nothing to backout or there are unresolved
565 files.
565 files.
566 '''
566 '''
567 wlock = lock = None
567 wlock = lock = None
568 try:
568 try:
569 wlock = repo.wlock()
569 wlock = repo.wlock()
570 lock = repo.lock()
570 lock = repo.lock()
571 return _dobackout(ui, repo, node, rev, **opts)
571 return _dobackout(ui, repo, node, rev, **opts)
572 finally:
572 finally:
573 release(lock, wlock)
573 release(lock, wlock)
574
574
575 def _dobackout(ui, repo, node=None, rev=None, **opts):
575 def _dobackout(ui, repo, node=None, rev=None, **opts):
576 opts = pycompat.byteskwargs(opts)
576 opts = pycompat.byteskwargs(opts)
577 if opts.get('commit') and opts.get('no_commit'):
577 if opts.get('commit') and opts.get('no_commit'):
578 raise error.Abort(_("cannot use --commit with --no-commit"))
578 raise error.Abort(_("cannot use --commit with --no-commit"))
579 if opts.get('merge') and opts.get('no_commit'):
579 if opts.get('merge') and opts.get('no_commit'):
580 raise error.Abort(_("cannot use --merge with --no-commit"))
580 raise error.Abort(_("cannot use --merge with --no-commit"))
581
581
582 if rev and node:
582 if rev and node:
583 raise error.Abort(_("please specify just one revision"))
583 raise error.Abort(_("please specify just one revision"))
584
584
585 if not rev:
585 if not rev:
586 rev = node
586 rev = node
587
587
588 if not rev:
588 if not rev:
589 raise error.Abort(_("please specify a revision to backout"))
589 raise error.Abort(_("please specify a revision to backout"))
590
590
591 date = opts.get('date')
591 date = opts.get('date')
592 if date:
592 if date:
593 opts['date'] = dateutil.parsedate(date)
593 opts['date'] = dateutil.parsedate(date)
594
594
595 cmdutil.checkunfinished(repo)
595 cmdutil.checkunfinished(repo)
596 cmdutil.bailifchanged(repo)
596 cmdutil.bailifchanged(repo)
597 node = scmutil.revsingle(repo, rev).node()
597 node = scmutil.revsingle(repo, rev).node()
598
598
599 op1, op2 = repo.dirstate.parents()
599 op1, op2 = repo.dirstate.parents()
600 if not repo.changelog.isancestor(node, op1):
600 if not repo.changelog.isancestor(node, op1):
601 raise error.Abort(_('cannot backout change that is not an ancestor'))
601 raise error.Abort(_('cannot backout change that is not an ancestor'))
602
602
603 p1, p2 = repo.changelog.parents(node)
603 p1, p2 = repo.changelog.parents(node)
604 if p1 == nullid:
604 if p1 == nullid:
605 raise error.Abort(_('cannot backout a change with no parents'))
605 raise error.Abort(_('cannot backout a change with no parents'))
606 if p2 != nullid:
606 if p2 != nullid:
607 if not opts.get('parent'):
607 if not opts.get('parent'):
608 raise error.Abort(_('cannot backout a merge changeset'))
608 raise error.Abort(_('cannot backout a merge changeset'))
609 p = repo.lookup(opts['parent'])
609 p = repo.lookup(opts['parent'])
610 if p not in (p1, p2):
610 if p not in (p1, p2):
611 raise error.Abort(_('%s is not a parent of %s') %
611 raise error.Abort(_('%s is not a parent of %s') %
612 (short(p), short(node)))
612 (short(p), short(node)))
613 parent = p
613 parent = p
614 else:
614 else:
615 if opts.get('parent'):
615 if opts.get('parent'):
616 raise error.Abort(_('cannot use --parent on non-merge changeset'))
616 raise error.Abort(_('cannot use --parent on non-merge changeset'))
617 parent = p1
617 parent = p1
618
618
619 # the backout should appear on the same branch
619 # the backout should appear on the same branch
620 branch = repo.dirstate.branch()
620 branch = repo.dirstate.branch()
621 bheads = repo.branchheads(branch)
621 bheads = repo.branchheads(branch)
622 rctx = scmutil.revsingle(repo, hex(parent))
622 rctx = scmutil.revsingle(repo, hex(parent))
623 if not opts.get('merge') and op1 != node:
623 if not opts.get('merge') and op1 != node:
624 dsguard = dirstateguard.dirstateguard(repo, 'backout')
624 dsguard = dirstateguard.dirstateguard(repo, 'backout')
625 try:
625 try:
626 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
626 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
627 'backout')
627 'backout')
628 stats = mergemod.update(repo, parent, True, True, node, False)
628 stats = mergemod.update(repo, parent, True, True, node, False)
629 repo.setparents(op1, op2)
629 repo.setparents(op1, op2)
630 dsguard.close()
630 dsguard.close()
631 hg._showstats(repo, stats)
631 hg._showstats(repo, stats)
632 if stats.unresolvedcount:
632 if stats.unresolvedcount:
633 repo.ui.status(_("use 'hg resolve' to retry unresolved "
633 repo.ui.status(_("use 'hg resolve' to retry unresolved "
634 "file merges\n"))
634 "file merges\n"))
635 return 1
635 return 1
636 finally:
636 finally:
637 ui.setconfig('ui', 'forcemerge', '', '')
637 ui.setconfig('ui', 'forcemerge', '', '')
638 lockmod.release(dsguard)
638 lockmod.release(dsguard)
639 else:
639 else:
640 hg.clean(repo, node, show_stats=False)
640 hg.clean(repo, node, show_stats=False)
641 repo.dirstate.setbranch(branch)
641 repo.dirstate.setbranch(branch)
642 cmdutil.revert(ui, repo, rctx, repo.dirstate.parents())
642 cmdutil.revert(ui, repo, rctx, repo.dirstate.parents())
643
643
644 if opts.get('no_commit'):
644 if opts.get('no_commit'):
645 msg = _("changeset %s backed out, "
645 msg = _("changeset %s backed out, "
646 "don't forget to commit.\n")
646 "don't forget to commit.\n")
647 ui.status(msg % short(node))
647 ui.status(msg % short(node))
648 return 0
648 return 0
649
649
650 def commitfunc(ui, repo, message, match, opts):
650 def commitfunc(ui, repo, message, match, opts):
651 editform = 'backout'
651 editform = 'backout'
652 e = cmdutil.getcommiteditor(editform=editform,
652 e = cmdutil.getcommiteditor(editform=editform,
653 **pycompat.strkwargs(opts))
653 **pycompat.strkwargs(opts))
654 if not message:
654 if not message:
655 # we don't translate commit messages
655 # we don't translate commit messages
656 message = "Backed out changeset %s" % short(node)
656 message = "Backed out changeset %s" % short(node)
657 e = cmdutil.getcommiteditor(edit=True, editform=editform)
657 e = cmdutil.getcommiteditor(edit=True, editform=editform)
658 return repo.commit(message, opts.get('user'), opts.get('date'),
658 return repo.commit(message, opts.get('user'), opts.get('date'),
659 match, editor=e)
659 match, editor=e)
660 newnode = cmdutil.commit(ui, repo, commitfunc, [], opts)
660 newnode = cmdutil.commit(ui, repo, commitfunc, [], opts)
661 if not newnode:
661 if not newnode:
662 ui.status(_("nothing changed\n"))
662 ui.status(_("nothing changed\n"))
663 return 1
663 return 1
664 cmdutil.commitstatus(repo, newnode, branch, bheads)
664 cmdutil.commitstatus(repo, newnode, branch, bheads)
665
665
666 def nice(node):
666 def nice(node):
667 return '%d:%s' % (repo.changelog.rev(node), short(node))
667 return '%d:%s' % (repo.changelog.rev(node), short(node))
668 ui.status(_('changeset %s backs out changeset %s\n') %
668 ui.status(_('changeset %s backs out changeset %s\n') %
669 (nice(repo.changelog.tip()), nice(node)))
669 (nice(repo.changelog.tip()), nice(node)))
670 if opts.get('merge') and op1 != node:
670 if opts.get('merge') and op1 != node:
671 hg.clean(repo, op1, show_stats=False)
671 hg.clean(repo, op1, show_stats=False)
672 ui.status(_('merging with changeset %s\n')
672 ui.status(_('merging with changeset %s\n')
673 % nice(repo.changelog.tip()))
673 % nice(repo.changelog.tip()))
674 try:
674 try:
675 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
675 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
676 'backout')
676 'backout')
677 return hg.merge(repo, hex(repo.changelog.tip()))
677 return hg.merge(repo, hex(repo.changelog.tip()))
678 finally:
678 finally:
679 ui.setconfig('ui', 'forcemerge', '', '')
679 ui.setconfig('ui', 'forcemerge', '', '')
680 return 0
680 return 0
681
681
682 @command('bisect',
682 @command('bisect',
683 [('r', 'reset', False, _('reset bisect state')),
683 [('r', 'reset', False, _('reset bisect state')),
684 ('g', 'good', False, _('mark changeset good')),
684 ('g', 'good', False, _('mark changeset good')),
685 ('b', 'bad', False, _('mark changeset bad')),
685 ('b', 'bad', False, _('mark changeset bad')),
686 ('s', 'skip', False, _('skip testing changeset')),
686 ('s', 'skip', False, _('skip testing changeset')),
687 ('e', 'extend', False, _('extend the bisect range')),
687 ('e', 'extend', False, _('extend the bisect range')),
688 ('c', 'command', '', _('use command to check changeset state'), _('CMD')),
688 ('c', 'command', '', _('use command to check changeset state'), _('CMD')),
689 ('U', 'noupdate', False, _('do not update to target'))],
689 ('U', 'noupdate', False, _('do not update to target'))],
690 _("[-gbsr] [-U] [-c CMD] [REV]"))
690 _("[-gbsr] [-U] [-c CMD] [REV]"))
691 def bisect(ui, repo, rev=None, extra=None, command=None,
691 def bisect(ui, repo, rev=None, extra=None, command=None,
692 reset=None, good=None, bad=None, skip=None, extend=None,
692 reset=None, good=None, bad=None, skip=None, extend=None,
693 noupdate=None):
693 noupdate=None):
694 """subdivision search of changesets
694 """subdivision search of changesets
695
695
696 This command helps to find changesets which introduce problems. To
696 This command helps to find changesets which introduce problems. To
697 use, mark the earliest changeset you know exhibits the problem as
697 use, mark the earliest changeset you know exhibits the problem as
698 bad, then mark the latest changeset which is free from the problem
698 bad, then mark the latest changeset which is free from the problem
699 as good. Bisect will update your working directory to a revision
699 as good. Bisect will update your working directory to a revision
700 for testing (unless the -U/--noupdate option is specified). Once
700 for testing (unless the -U/--noupdate option is specified). Once
701 you have performed tests, mark the working directory as good or
701 you have performed tests, mark the working directory as good or
702 bad, and bisect will either update to another candidate changeset
702 bad, and bisect will either update to another candidate changeset
703 or announce that it has found the bad revision.
703 or announce that it has found the bad revision.
704
704
705 As a shortcut, you can also use the revision argument to mark a
705 As a shortcut, you can also use the revision argument to mark a
706 revision as good or bad without checking it out first.
706 revision as good or bad without checking it out first.
707
707
708 If you supply a command, it will be used for automatic bisection.
708 If you supply a command, it will be used for automatic bisection.
709 The environment variable HG_NODE will contain the ID of the
709 The environment variable HG_NODE will contain the ID of the
710 changeset being tested. The exit status of the command will be
710 changeset being tested. The exit status of the command will be
711 used to mark revisions as good or bad: status 0 means good, 125
711 used to mark revisions as good or bad: status 0 means good, 125
712 means to skip the revision, 127 (command not found) will abort the
712 means to skip the revision, 127 (command not found) will abort the
713 bisection, and any other non-zero exit status means the revision
713 bisection, and any other non-zero exit status means the revision
714 is bad.
714 is bad.
715
715
716 .. container:: verbose
716 .. container:: verbose
717
717
718 Some examples:
718 Some examples:
719
719
720 - start a bisection with known bad revision 34, and good revision 12::
720 - start a bisection with known bad revision 34, and good revision 12::
721
721
722 hg bisect --bad 34
722 hg bisect --bad 34
723 hg bisect --good 12
723 hg bisect --good 12
724
724
725 - advance the current bisection by marking current revision as good or
725 - advance the current bisection by marking current revision as good or
726 bad::
726 bad::
727
727
728 hg bisect --good
728 hg bisect --good
729 hg bisect --bad
729 hg bisect --bad
730
730
731 - mark the current revision, or a known revision, to be skipped (e.g. if
731 - mark the current revision, or a known revision, to be skipped (e.g. if
732 that revision is not usable because of another issue)::
732 that revision is not usable because of another issue)::
733
733
734 hg bisect --skip
734 hg bisect --skip
735 hg bisect --skip 23
735 hg bisect --skip 23
736
736
737 - skip all revisions that do not touch directories ``foo`` or ``bar``::
737 - skip all revisions that do not touch directories ``foo`` or ``bar``::
738
738
739 hg bisect --skip "!( file('path:foo') & file('path:bar') )"
739 hg bisect --skip "!( file('path:foo') & file('path:bar') )"
740
740
741 - forget the current bisection::
741 - forget the current bisection::
742
742
743 hg bisect --reset
743 hg bisect --reset
744
744
745 - use 'make && make tests' to automatically find the first broken
745 - use 'make && make tests' to automatically find the first broken
746 revision::
746 revision::
747
747
748 hg bisect --reset
748 hg bisect --reset
749 hg bisect --bad 34
749 hg bisect --bad 34
750 hg bisect --good 12
750 hg bisect --good 12
751 hg bisect --command "make && make tests"
751 hg bisect --command "make && make tests"
752
752
753 - see all changesets whose states are already known in the current
753 - see all changesets whose states are already known in the current
754 bisection::
754 bisection::
755
755
756 hg log -r "bisect(pruned)"
756 hg log -r "bisect(pruned)"
757
757
758 - see the changeset currently being bisected (especially useful
758 - see the changeset currently being bisected (especially useful
759 if running with -U/--noupdate)::
759 if running with -U/--noupdate)::
760
760
761 hg log -r "bisect(current)"
761 hg log -r "bisect(current)"
762
762
763 - see all changesets that took part in the current bisection::
763 - see all changesets that took part in the current bisection::
764
764
765 hg log -r "bisect(range)"
765 hg log -r "bisect(range)"
766
766
767 - you can even get a nice graph::
767 - you can even get a nice graph::
768
768
769 hg log --graph -r "bisect(range)"
769 hg log --graph -r "bisect(range)"
770
770
771 See :hg:`help revisions.bisect` for more about the `bisect()` predicate.
771 See :hg:`help revisions.bisect` for more about the `bisect()` predicate.
772
772
773 Returns 0 on success.
773 Returns 0 on success.
774 """
774 """
775 # backward compatibility
775 # backward compatibility
776 if rev in "good bad reset init".split():
776 if rev in "good bad reset init".split():
777 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
777 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
778 cmd, rev, extra = rev, extra, None
778 cmd, rev, extra = rev, extra, None
779 if cmd == "good":
779 if cmd == "good":
780 good = True
780 good = True
781 elif cmd == "bad":
781 elif cmd == "bad":
782 bad = True
782 bad = True
783 else:
783 else:
784 reset = True
784 reset = True
785 elif extra:
785 elif extra:
786 raise error.Abort(_('incompatible arguments'))
786 raise error.Abort(_('incompatible arguments'))
787
787
788 incompatibles = {
788 incompatibles = {
789 '--bad': bad,
789 '--bad': bad,
790 '--command': bool(command),
790 '--command': bool(command),
791 '--extend': extend,
791 '--extend': extend,
792 '--good': good,
792 '--good': good,
793 '--reset': reset,
793 '--reset': reset,
794 '--skip': skip,
794 '--skip': skip,
795 }
795 }
796
796
797 enabled = [x for x in incompatibles if incompatibles[x]]
797 enabled = [x for x in incompatibles if incompatibles[x]]
798
798
799 if len(enabled) > 1:
799 if len(enabled) > 1:
800 raise error.Abort(_('%s and %s are incompatible') %
800 raise error.Abort(_('%s and %s are incompatible') %
801 tuple(sorted(enabled)[0:2]))
801 tuple(sorted(enabled)[0:2]))
802
802
803 if reset:
803 if reset:
804 hbisect.resetstate(repo)
804 hbisect.resetstate(repo)
805 return
805 return
806
806
807 state = hbisect.load_state(repo)
807 state = hbisect.load_state(repo)
808
808
809 # update state
809 # update state
810 if good or bad or skip:
810 if good or bad or skip:
811 if rev:
811 if rev:
812 nodes = [repo.lookup(i) for i in scmutil.revrange(repo, [rev])]
812 nodes = [repo.lookup(i) for i in scmutil.revrange(repo, [rev])]
813 else:
813 else:
814 nodes = [repo.lookup('.')]
814 nodes = [repo.lookup('.')]
815 if good:
815 if good:
816 state['good'] += nodes
816 state['good'] += nodes
817 elif bad:
817 elif bad:
818 state['bad'] += nodes
818 state['bad'] += nodes
819 elif skip:
819 elif skip:
820 state['skip'] += nodes
820 state['skip'] += nodes
821 hbisect.save_state(repo, state)
821 hbisect.save_state(repo, state)
822 if not (state['good'] and state['bad']):
822 if not (state['good'] and state['bad']):
823 return
823 return
824
824
825 def mayupdate(repo, node, show_stats=True):
825 def mayupdate(repo, node, show_stats=True):
826 """common used update sequence"""
826 """common used update sequence"""
827 if noupdate:
827 if noupdate:
828 return
828 return
829 cmdutil.checkunfinished(repo)
829 cmdutil.checkunfinished(repo)
830 cmdutil.bailifchanged(repo)
830 cmdutil.bailifchanged(repo)
831 return hg.clean(repo, node, show_stats=show_stats)
831 return hg.clean(repo, node, show_stats=show_stats)
832
832
833 displayer = logcmdutil.changesetdisplayer(ui, repo, {})
833 displayer = logcmdutil.changesetdisplayer(ui, repo, {})
834
834
835 if command:
835 if command:
836 changesets = 1
836 changesets = 1
837 if noupdate:
837 if noupdate:
838 try:
838 try:
839 node = state['current'][0]
839 node = state['current'][0]
840 except LookupError:
840 except LookupError:
841 raise error.Abort(_('current bisect revision is unknown - '
841 raise error.Abort(_('current bisect revision is unknown - '
842 'start a new bisect to fix'))
842 'start a new bisect to fix'))
843 else:
843 else:
844 node, p2 = repo.dirstate.parents()
844 node, p2 = repo.dirstate.parents()
845 if p2 != nullid:
845 if p2 != nullid:
846 raise error.Abort(_('current bisect revision is a merge'))
846 raise error.Abort(_('current bisect revision is a merge'))
847 if rev:
847 if rev:
848 node = repo[scmutil.revsingle(repo, rev, node)].node()
848 node = repo[scmutil.revsingle(repo, rev, node)].node()
849 try:
849 try:
850 while changesets:
850 while changesets:
851 # update state
851 # update state
852 state['current'] = [node]
852 state['current'] = [node]
853 hbisect.save_state(repo, state)
853 hbisect.save_state(repo, state)
854 status = ui.system(command, environ={'HG_NODE': hex(node)},
854 status = ui.system(command, environ={'HG_NODE': hex(node)},
855 blockedtag='bisect_check')
855 blockedtag='bisect_check')
856 if status == 125:
856 if status == 125:
857 transition = "skip"
857 transition = "skip"
858 elif status == 0:
858 elif status == 0:
859 transition = "good"
859 transition = "good"
860 # status < 0 means process was killed
860 # status < 0 means process was killed
861 elif status == 127:
861 elif status == 127:
862 raise error.Abort(_("failed to execute %s") % command)
862 raise error.Abort(_("failed to execute %s") % command)
863 elif status < 0:
863 elif status < 0:
864 raise error.Abort(_("%s killed") % command)
864 raise error.Abort(_("%s killed") % command)
865 else:
865 else:
866 transition = "bad"
866 transition = "bad"
867 state[transition].append(node)
867 state[transition].append(node)
868 ctx = repo[node]
868 ctx = repo[node]
869 ui.status(_('changeset %d:%s: %s\n') % (ctx.rev(), ctx,
869 ui.status(_('changeset %d:%s: %s\n') % (ctx.rev(), ctx,
870 transition))
870 transition))
871 hbisect.checkstate(state)
871 hbisect.checkstate(state)
872 # bisect
872 # bisect
873 nodes, changesets, bgood = hbisect.bisect(repo, state)
873 nodes, changesets, bgood = hbisect.bisect(repo, state)
874 # update to next check
874 # update to next check
875 node = nodes[0]
875 node = nodes[0]
876 mayupdate(repo, node, show_stats=False)
876 mayupdate(repo, node, show_stats=False)
877 finally:
877 finally:
878 state['current'] = [node]
878 state['current'] = [node]
879 hbisect.save_state(repo, state)
879 hbisect.save_state(repo, state)
880 hbisect.printresult(ui, repo, state, displayer, nodes, bgood)
880 hbisect.printresult(ui, repo, state, displayer, nodes, bgood)
881 return
881 return
882
882
883 hbisect.checkstate(state)
883 hbisect.checkstate(state)
884
884
885 # actually bisect
885 # actually bisect
886 nodes, changesets, good = hbisect.bisect(repo, state)
886 nodes, changesets, good = hbisect.bisect(repo, state)
887 if extend:
887 if extend:
888 if not changesets:
888 if not changesets:
889 extendnode = hbisect.extendrange(repo, state, nodes, good)
889 extendnode = hbisect.extendrange(repo, state, nodes, good)
890 if extendnode is not None:
890 if extendnode is not None:
891 ui.write(_("Extending search to changeset %d:%s\n")
891 ui.write(_("Extending search to changeset %d:%s\n")
892 % (extendnode.rev(), extendnode))
892 % (extendnode.rev(), extendnode))
893 state['current'] = [extendnode.node()]
893 state['current'] = [extendnode.node()]
894 hbisect.save_state(repo, state)
894 hbisect.save_state(repo, state)
895 return mayupdate(repo, extendnode.node())
895 return mayupdate(repo, extendnode.node())
896 raise error.Abort(_("nothing to extend"))
896 raise error.Abort(_("nothing to extend"))
897
897
898 if changesets == 0:
898 if changesets == 0:
899 hbisect.printresult(ui, repo, state, displayer, nodes, good)
899 hbisect.printresult(ui, repo, state, displayer, nodes, good)
900 else:
900 else:
901 assert len(nodes) == 1 # only a single node can be tested next
901 assert len(nodes) == 1 # only a single node can be tested next
902 node = nodes[0]
902 node = nodes[0]
903 # compute the approximate number of remaining tests
903 # compute the approximate number of remaining tests
904 tests, size = 0, 2
904 tests, size = 0, 2
905 while size <= changesets:
905 while size <= changesets:
906 tests, size = tests + 1, size * 2
906 tests, size = tests + 1, size * 2
907 rev = repo.changelog.rev(node)
907 rev = repo.changelog.rev(node)
908 ui.write(_("Testing changeset %d:%s "
908 ui.write(_("Testing changeset %d:%s "
909 "(%d changesets remaining, ~%d tests)\n")
909 "(%d changesets remaining, ~%d tests)\n")
910 % (rev, short(node), changesets, tests))
910 % (rev, short(node), changesets, tests))
911 state['current'] = [node]
911 state['current'] = [node]
912 hbisect.save_state(repo, state)
912 hbisect.save_state(repo, state)
913 return mayupdate(repo, node)
913 return mayupdate(repo, node)
914
914
915 @command('bookmarks|bookmark',
915 @command('bookmarks|bookmark',
916 [('f', 'force', False, _('force')),
916 [('f', 'force', False, _('force')),
917 ('r', 'rev', '', _('revision for bookmark action'), _('REV')),
917 ('r', 'rev', '', _('revision for bookmark action'), _('REV')),
918 ('d', 'delete', False, _('delete a given bookmark')),
918 ('d', 'delete', False, _('delete a given bookmark')),
919 ('m', 'rename', '', _('rename a given bookmark'), _('OLD')),
919 ('m', 'rename', '', _('rename a given bookmark'), _('OLD')),
920 ('i', 'inactive', False, _('mark a bookmark inactive')),
920 ('i', 'inactive', False, _('mark a bookmark inactive')),
921 ] + formatteropts,
921 ] + formatteropts,
922 _('hg bookmarks [OPTIONS]... [NAME]...'))
922 _('hg bookmarks [OPTIONS]... [NAME]...'))
923 def bookmark(ui, repo, *names, **opts):
923 def bookmark(ui, repo, *names, **opts):
924 '''create a new bookmark or list existing bookmarks
924 '''create a new bookmark or list existing bookmarks
925
925
926 Bookmarks are labels on changesets to help track lines of development.
926 Bookmarks are labels on changesets to help track lines of development.
927 Bookmarks are unversioned and can be moved, renamed and deleted.
927 Bookmarks are unversioned and can be moved, renamed and deleted.
928 Deleting or moving a bookmark has no effect on the associated changesets.
928 Deleting or moving a bookmark has no effect on the associated changesets.
929
929
930 Creating or updating to a bookmark causes it to be marked as 'active'.
930 Creating or updating to a bookmark causes it to be marked as 'active'.
931 The active bookmark is indicated with a '*'.
931 The active bookmark is indicated with a '*'.
932 When a commit is made, the active bookmark will advance to the new commit.
932 When a commit is made, the active bookmark will advance to the new commit.
933 A plain :hg:`update` will also advance an active bookmark, if possible.
933 A plain :hg:`update` will also advance an active bookmark, if possible.
934 Updating away from a bookmark will cause it to be deactivated.
934 Updating away from a bookmark will cause it to be deactivated.
935
935
936 Bookmarks can be pushed and pulled between repositories (see
936 Bookmarks can be pushed and pulled between repositories (see
937 :hg:`help push` and :hg:`help pull`). If a shared bookmark has
937 :hg:`help push` and :hg:`help pull`). If a shared bookmark has
938 diverged, a new 'divergent bookmark' of the form 'name@path' will
938 diverged, a new 'divergent bookmark' of the form 'name@path' will
939 be created. Using :hg:`merge` will resolve the divergence.
939 be created. Using :hg:`merge` will resolve the divergence.
940
940
941 Specifying bookmark as '.' to -m or -d options is equivalent to specifying
941 Specifying bookmark as '.' to -m or -d options is equivalent to specifying
942 the active bookmark's name.
942 the active bookmark's name.
943
943
944 A bookmark named '@' has the special property that :hg:`clone` will
944 A bookmark named '@' has the special property that :hg:`clone` will
945 check it out by default if it exists.
945 check it out by default if it exists.
946
946
947 .. container:: verbose
947 .. container:: verbose
948
948
949 Examples:
949 Examples:
950
950
951 - create an active bookmark for a new line of development::
951 - create an active bookmark for a new line of development::
952
952
953 hg book new-feature
953 hg book new-feature
954
954
955 - create an inactive bookmark as a place marker::
955 - create an inactive bookmark as a place marker::
956
956
957 hg book -i reviewed
957 hg book -i reviewed
958
958
959 - create an inactive bookmark on another changeset::
959 - create an inactive bookmark on another changeset::
960
960
961 hg book -r .^ tested
961 hg book -r .^ tested
962
962
963 - rename bookmark turkey to dinner::
963 - rename bookmark turkey to dinner::
964
964
965 hg book -m turkey dinner
965 hg book -m turkey dinner
966
966
967 - move the '@' bookmark from another branch::
967 - move the '@' bookmark from another branch::
968
968
969 hg book -f @
969 hg book -f @
970 '''
970 '''
971 force = opts.get(r'force')
971 force = opts.get(r'force')
972 rev = opts.get(r'rev')
972 rev = opts.get(r'rev')
973 delete = opts.get(r'delete')
973 delete = opts.get(r'delete')
974 rename = opts.get(r'rename')
974 rename = opts.get(r'rename')
975 inactive = opts.get(r'inactive')
975 inactive = opts.get(r'inactive')
976
976
977 if delete and rename:
977 if delete and rename:
978 raise error.Abort(_("--delete and --rename are incompatible"))
978 raise error.Abort(_("--delete and --rename are incompatible"))
979 if delete and rev:
979 if delete and rev:
980 raise error.Abort(_("--rev is incompatible with --delete"))
980 raise error.Abort(_("--rev is incompatible with --delete"))
981 if rename and rev:
981 if rename and rev:
982 raise error.Abort(_("--rev is incompatible with --rename"))
982 raise error.Abort(_("--rev is incompatible with --rename"))
983 if not names and (delete or rev):
983 if not names and (delete or rev):
984 raise error.Abort(_("bookmark name required"))
984 raise error.Abort(_("bookmark name required"))
985
985
986 if delete or rename or names or inactive:
986 if delete or rename or names or inactive:
987 with repo.wlock(), repo.lock(), repo.transaction('bookmark') as tr:
987 with repo.wlock(), repo.lock(), repo.transaction('bookmark') as tr:
988 if delete:
988 if delete:
989 names = pycompat.maplist(repo._bookmarks.expandname, names)
989 names = pycompat.maplist(repo._bookmarks.expandname, names)
990 bookmarks.delete(repo, tr, names)
990 bookmarks.delete(repo, tr, names)
991 elif rename:
991 elif rename:
992 if not names:
992 if not names:
993 raise error.Abort(_("new bookmark name required"))
993 raise error.Abort(_("new bookmark name required"))
994 elif len(names) > 1:
994 elif len(names) > 1:
995 raise error.Abort(_("only one new bookmark name allowed"))
995 raise error.Abort(_("only one new bookmark name allowed"))
996 rename = repo._bookmarks.expandname(rename)
996 rename = repo._bookmarks.expandname(rename)
997 bookmarks.rename(repo, tr, rename, names[0], force, inactive)
997 bookmarks.rename(repo, tr, rename, names[0], force, inactive)
998 elif names:
998 elif names:
999 bookmarks.addbookmarks(repo, tr, names, rev, force, inactive)
999 bookmarks.addbookmarks(repo, tr, names, rev, force, inactive)
1000 elif inactive:
1000 elif inactive:
1001 if len(repo._bookmarks) == 0:
1001 if len(repo._bookmarks) == 0:
1002 ui.status(_("no bookmarks set\n"))
1002 ui.status(_("no bookmarks set\n"))
1003 elif not repo._activebookmark:
1003 elif not repo._activebookmark:
1004 ui.status(_("no active bookmark\n"))
1004 ui.status(_("no active bookmark\n"))
1005 else:
1005 else:
1006 bookmarks.deactivate(repo)
1006 bookmarks.deactivate(repo)
1007 else: # show bookmarks
1007 else: # show bookmarks
1008 bookmarks.printbookmarks(ui, repo, **opts)
1008 bookmarks.printbookmarks(ui, repo, **opts)
1009
1009
1010 @command('branch',
1010 @command('branch',
1011 [('f', 'force', None,
1011 [('f', 'force', None,
1012 _('set branch name even if it shadows an existing branch')),
1012 _('set branch name even if it shadows an existing branch')),
1013 ('C', 'clean', None, _('reset branch name to parent branch name')),
1013 ('C', 'clean', None, _('reset branch name to parent branch name')),
1014 ('r', 'rev', [], _('change branches of the given revs (EXPERIMENTAL)')),
1014 ('r', 'rev', [], _('change branches of the given revs (EXPERIMENTAL)')),
1015 ],
1015 ],
1016 _('[-fC] [NAME]'))
1016 _('[-fC] [NAME]'))
1017 def branch(ui, repo, label=None, **opts):
1017 def branch(ui, repo, label=None, **opts):
1018 """set or show the current branch name
1018 """set or show the current branch name
1019
1019
1020 .. note::
1020 .. note::
1021
1021
1022 Branch names are permanent and global. Use :hg:`bookmark` to create a
1022 Branch names are permanent and global. Use :hg:`bookmark` to create a
1023 light-weight bookmark instead. See :hg:`help glossary` for more
1023 light-weight bookmark instead. See :hg:`help glossary` for more
1024 information about named branches and bookmarks.
1024 information about named branches and bookmarks.
1025
1025
1026 With no argument, show the current branch name. With one argument,
1026 With no argument, show the current branch name. With one argument,
1027 set the working directory branch name (the branch will not exist
1027 set the working directory branch name (the branch will not exist
1028 in the repository until the next commit). Standard practice
1028 in the repository until the next commit). Standard practice
1029 recommends that primary development take place on the 'default'
1029 recommends that primary development take place on the 'default'
1030 branch.
1030 branch.
1031
1031
1032 Unless -f/--force is specified, branch will not let you set a
1032 Unless -f/--force is specified, branch will not let you set a
1033 branch name that already exists.
1033 branch name that already exists.
1034
1034
1035 Use -C/--clean to reset the working directory branch to that of
1035 Use -C/--clean to reset the working directory branch to that of
1036 the parent of the working directory, negating a previous branch
1036 the parent of the working directory, negating a previous branch
1037 change.
1037 change.
1038
1038
1039 Use the command :hg:`update` to switch to an existing branch. Use
1039 Use the command :hg:`update` to switch to an existing branch. Use
1040 :hg:`commit --close-branch` to mark this branch head as closed.
1040 :hg:`commit --close-branch` to mark this branch head as closed.
1041 When all heads of a branch are closed, the branch will be
1041 When all heads of a branch are closed, the branch will be
1042 considered closed.
1042 considered closed.
1043
1043
1044 Returns 0 on success.
1044 Returns 0 on success.
1045 """
1045 """
1046 opts = pycompat.byteskwargs(opts)
1046 opts = pycompat.byteskwargs(opts)
1047 revs = opts.get('rev')
1047 revs = opts.get('rev')
1048 if label:
1048 if label:
1049 label = label.strip()
1049 label = label.strip()
1050
1050
1051 if not opts.get('clean') and not label:
1051 if not opts.get('clean') and not label:
1052 if revs:
1052 if revs:
1053 raise error.Abort(_("no branch name specified for the revisions"))
1053 raise error.Abort(_("no branch name specified for the revisions"))
1054 ui.write("%s\n" % repo.dirstate.branch())
1054 ui.write("%s\n" % repo.dirstate.branch())
1055 return
1055 return
1056
1056
1057 with repo.wlock():
1057 with repo.wlock():
1058 if opts.get('clean'):
1058 if opts.get('clean'):
1059 label = repo[None].p1().branch()
1059 label = repo[None].p1().branch()
1060 repo.dirstate.setbranch(label)
1060 repo.dirstate.setbranch(label)
1061 ui.status(_('reset working directory to branch %s\n') % label)
1061 ui.status(_('reset working directory to branch %s\n') % label)
1062 elif label:
1062 elif label:
1063
1063
1064 scmutil.checknewlabel(repo, label, 'branch')
1064 scmutil.checknewlabel(repo, label, 'branch')
1065 if revs:
1065 if revs:
1066 return cmdutil.changebranch(ui, repo, revs, label)
1066 return cmdutil.changebranch(ui, repo, revs, label)
1067
1067
1068 if not opts.get('force') and label in repo.branchmap():
1068 if not opts.get('force') and label in repo.branchmap():
1069 if label not in [p.branch() for p in repo[None].parents()]:
1069 if label not in [p.branch() for p in repo[None].parents()]:
1070 raise error.Abort(_('a branch of the same name already'
1070 raise error.Abort(_('a branch of the same name already'
1071 ' exists'),
1071 ' exists'),
1072 # i18n: "it" refers to an existing branch
1072 # i18n: "it" refers to an existing branch
1073 hint=_("use 'hg update' to switch to it"))
1073 hint=_("use 'hg update' to switch to it"))
1074
1074
1075 repo.dirstate.setbranch(label)
1075 repo.dirstate.setbranch(label)
1076 ui.status(_('marked working directory as branch %s\n') % label)
1076 ui.status(_('marked working directory as branch %s\n') % label)
1077
1077
1078 # find any open named branches aside from default
1078 # find any open named branches aside from default
1079 others = [n for n, h, t, c in repo.branchmap().iterbranches()
1079 others = [n for n, h, t, c in repo.branchmap().iterbranches()
1080 if n != "default" and not c]
1080 if n != "default" and not c]
1081 if not others:
1081 if not others:
1082 ui.status(_('(branches are permanent and global, '
1082 ui.status(_('(branches are permanent and global, '
1083 'did you want a bookmark?)\n'))
1083 'did you want a bookmark?)\n'))
1084
1084
1085 @command('branches',
1085 @command('branches',
1086 [('a', 'active', False,
1086 [('a', 'active', False,
1087 _('show only branches that have unmerged heads (DEPRECATED)')),
1087 _('show only branches that have unmerged heads (DEPRECATED)')),
1088 ('c', 'closed', False, _('show normal and closed branches')),
1088 ('c', 'closed', False, _('show normal and closed branches')),
1089 ] + formatteropts,
1089 ] + formatteropts,
1090 _('[-c]'), cmdtype=readonly)
1090 _('[-c]'), cmdtype=readonly)
1091 def branches(ui, repo, active=False, closed=False, **opts):
1091 def branches(ui, repo, active=False, closed=False, **opts):
1092 """list repository named branches
1092 """list repository named branches
1093
1093
1094 List the repository's named branches, indicating which ones are
1094 List the repository's named branches, indicating which ones are
1095 inactive. If -c/--closed is specified, also list branches which have
1095 inactive. If -c/--closed is specified, also list branches which have
1096 been marked closed (see :hg:`commit --close-branch`).
1096 been marked closed (see :hg:`commit --close-branch`).
1097
1097
1098 Use the command :hg:`update` to switch to an existing branch.
1098 Use the command :hg:`update` to switch to an existing branch.
1099
1099
1100 Returns 0.
1100 Returns 0.
1101 """
1101 """
1102
1102
1103 opts = pycompat.byteskwargs(opts)
1103 opts = pycompat.byteskwargs(opts)
1104 ui.pager('branches')
1104 ui.pager('branches')
1105 fm = ui.formatter('branches', opts)
1105 fm = ui.formatter('branches', opts)
1106 hexfunc = fm.hexfunc
1106 hexfunc = fm.hexfunc
1107
1107
1108 allheads = set(repo.heads())
1108 allheads = set(repo.heads())
1109 branches = []
1109 branches = []
1110 for tag, heads, tip, isclosed in repo.branchmap().iterbranches():
1110 for tag, heads, tip, isclosed in repo.branchmap().iterbranches():
1111 isactive = False
1111 isactive = False
1112 if not isclosed:
1112 if not isclosed:
1113 openheads = set(repo.branchmap().iteropen(heads))
1113 openheads = set(repo.branchmap().iteropen(heads))
1114 isactive = bool(openheads & allheads)
1114 isactive = bool(openheads & allheads)
1115 branches.append((tag, repo[tip], isactive, not isclosed))
1115 branches.append((tag, repo[tip], isactive, not isclosed))
1116 branches.sort(key=lambda i: (i[2], i[1].rev(), i[0], i[3]),
1116 branches.sort(key=lambda i: (i[2], i[1].rev(), i[0], i[3]),
1117 reverse=True)
1117 reverse=True)
1118
1118
1119 for tag, ctx, isactive, isopen in branches:
1119 for tag, ctx, isactive, isopen in branches:
1120 if active and not isactive:
1120 if active and not isactive:
1121 continue
1121 continue
1122 if isactive:
1122 if isactive:
1123 label = 'branches.active'
1123 label = 'branches.active'
1124 notice = ''
1124 notice = ''
1125 elif not isopen:
1125 elif not isopen:
1126 if not closed:
1126 if not closed:
1127 continue
1127 continue
1128 label = 'branches.closed'
1128 label = 'branches.closed'
1129 notice = _(' (closed)')
1129 notice = _(' (closed)')
1130 else:
1130 else:
1131 label = 'branches.inactive'
1131 label = 'branches.inactive'
1132 notice = _(' (inactive)')
1132 notice = _(' (inactive)')
1133 current = (tag == repo.dirstate.branch())
1133 current = (tag == repo.dirstate.branch())
1134 if current:
1134 if current:
1135 label = 'branches.current'
1135 label = 'branches.current'
1136
1136
1137 fm.startitem()
1137 fm.startitem()
1138 fm.write('branch', '%s', tag, label=label)
1138 fm.write('branch', '%s', tag, label=label)
1139 rev = ctx.rev()
1139 rev = ctx.rev()
1140 padsize = max(31 - len("%d" % rev) - encoding.colwidth(tag), 0)
1140 padsize = max(31 - len("%d" % rev) - encoding.colwidth(tag), 0)
1141 fmt = ' ' * padsize + ' %d:%s'
1141 fmt = ' ' * padsize + ' %d:%s'
1142 fm.condwrite(not ui.quiet, 'rev node', fmt, rev, hexfunc(ctx.node()),
1142 fm.condwrite(not ui.quiet, 'rev node', fmt, rev, hexfunc(ctx.node()),
1143 label='log.changeset changeset.%s' % ctx.phasestr())
1143 label='log.changeset changeset.%s' % ctx.phasestr())
1144 fm.context(ctx=ctx)
1144 fm.context(ctx=ctx)
1145 fm.data(active=isactive, closed=not isopen, current=current)
1145 fm.data(active=isactive, closed=not isopen, current=current)
1146 if not ui.quiet:
1146 if not ui.quiet:
1147 fm.plain(notice)
1147 fm.plain(notice)
1148 fm.plain('\n')
1148 fm.plain('\n')
1149 fm.end()
1149 fm.end()
1150
1150
1151 @command('bundle',
1151 @command('bundle',
1152 [('f', 'force', None, _('run even when the destination is unrelated')),
1152 [('f', 'force', None, _('run even when the destination is unrelated')),
1153 ('r', 'rev', [], _('a changeset intended to be added to the destination'),
1153 ('r', 'rev', [], _('a changeset intended to be added to the destination'),
1154 _('REV')),
1154 _('REV')),
1155 ('b', 'branch', [], _('a specific branch you would like to bundle'),
1155 ('b', 'branch', [], _('a specific branch you would like to bundle'),
1156 _('BRANCH')),
1156 _('BRANCH')),
1157 ('', 'base', [],
1157 ('', 'base', [],
1158 _('a base changeset assumed to be available at the destination'),
1158 _('a base changeset assumed to be available at the destination'),
1159 _('REV')),
1159 _('REV')),
1160 ('a', 'all', None, _('bundle all changesets in the repository')),
1160 ('a', 'all', None, _('bundle all changesets in the repository')),
1161 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE')),
1161 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE')),
1162 ] + remoteopts,
1162 ] + remoteopts,
1163 _('[-f] [-t BUNDLESPEC] [-a] [-r REV]... [--base REV]... FILE [DEST]'))
1163 _('[-f] [-t BUNDLESPEC] [-a] [-r REV]... [--base REV]... FILE [DEST]'))
1164 def bundle(ui, repo, fname, dest=None, **opts):
1164 def bundle(ui, repo, fname, dest=None, **opts):
1165 """create a bundle file
1165 """create a bundle file
1166
1166
1167 Generate a bundle file containing data to be transferred to another
1167 Generate a bundle file containing data to be transferred to another
1168 repository.
1168 repository.
1169
1169
1170 To create a bundle containing all changesets, use -a/--all
1170 To create a bundle containing all changesets, use -a/--all
1171 (or --base null). Otherwise, hg assumes the destination will have
1171 (or --base null). Otherwise, hg assumes the destination will have
1172 all the nodes you specify with --base parameters. Otherwise, hg
1172 all the nodes you specify with --base parameters. Otherwise, hg
1173 will assume the repository has all the nodes in destination, or
1173 will assume the repository has all the nodes in destination, or
1174 default-push/default if no destination is specified, where destination
1174 default-push/default if no destination is specified, where destination
1175 is the repository you provide through DEST option.
1175 is the repository you provide through DEST option.
1176
1176
1177 You can change bundle format with the -t/--type option. See
1177 You can change bundle format with the -t/--type option. See
1178 :hg:`help bundlespec` for documentation on this format. By default,
1178 :hg:`help bundlespec` for documentation on this format. By default,
1179 the most appropriate format is used and compression defaults to
1179 the most appropriate format is used and compression defaults to
1180 bzip2.
1180 bzip2.
1181
1181
1182 The bundle file can then be transferred using conventional means
1182 The bundle file can then be transferred using conventional means
1183 and applied to another repository with the unbundle or pull
1183 and applied to another repository with the unbundle or pull
1184 command. This is useful when direct push and pull are not
1184 command. This is useful when direct push and pull are not
1185 available or when exporting an entire repository is undesirable.
1185 available or when exporting an entire repository is undesirable.
1186
1186
1187 Applying bundles preserves all changeset contents including
1187 Applying bundles preserves all changeset contents including
1188 permissions, copy/rename information, and revision history.
1188 permissions, copy/rename information, and revision history.
1189
1189
1190 Returns 0 on success, 1 if no changes found.
1190 Returns 0 on success, 1 if no changes found.
1191 """
1191 """
1192 opts = pycompat.byteskwargs(opts)
1192 opts = pycompat.byteskwargs(opts)
1193 revs = None
1193 revs = None
1194 if 'rev' in opts:
1194 if 'rev' in opts:
1195 revstrings = opts['rev']
1195 revstrings = opts['rev']
1196 revs = scmutil.revrange(repo, revstrings)
1196 revs = scmutil.revrange(repo, revstrings)
1197 if revstrings and not revs:
1197 if revstrings and not revs:
1198 raise error.Abort(_('no commits to bundle'))
1198 raise error.Abort(_('no commits to bundle'))
1199
1199
1200 bundletype = opts.get('type', 'bzip2').lower()
1200 bundletype = opts.get('type', 'bzip2').lower()
1201 try:
1201 try:
1202 bundlespec = exchange.parsebundlespec(repo, bundletype, strict=False)
1202 bundlespec = exchange.parsebundlespec(repo, bundletype, strict=False)
1203 except error.UnsupportedBundleSpecification as e:
1203 except error.UnsupportedBundleSpecification as e:
1204 raise error.Abort(pycompat.bytestr(e),
1204 raise error.Abort(pycompat.bytestr(e),
1205 hint=_("see 'hg help bundlespec' for supported "
1205 hint=_("see 'hg help bundlespec' for supported "
1206 "values for --type"))
1206 "values for --type"))
1207 cgversion = bundlespec.contentopts["cg.version"]
1207 cgversion = bundlespec.contentopts["cg.version"]
1208
1208
1209 # Packed bundles are a pseudo bundle format for now.
1209 # Packed bundles are a pseudo bundle format for now.
1210 if cgversion == 's1':
1210 if cgversion == 's1':
1211 raise error.Abort(_('packed bundles cannot be produced by "hg bundle"'),
1211 raise error.Abort(_('packed bundles cannot be produced by "hg bundle"'),
1212 hint=_("use 'hg debugcreatestreamclonebundle'"))
1212 hint=_("use 'hg debugcreatestreamclonebundle'"))
1213
1213
1214 if opts.get('all'):
1214 if opts.get('all'):
1215 if dest:
1215 if dest:
1216 raise error.Abort(_("--all is incompatible with specifying "
1216 raise error.Abort(_("--all is incompatible with specifying "
1217 "a destination"))
1217 "a destination"))
1218 if opts.get('base'):
1218 if opts.get('base'):
1219 ui.warn(_("ignoring --base because --all was specified\n"))
1219 ui.warn(_("ignoring --base because --all was specified\n"))
1220 base = ['null']
1220 base = ['null']
1221 else:
1221 else:
1222 base = scmutil.revrange(repo, opts.get('base'))
1222 base = scmutil.revrange(repo, opts.get('base'))
1223 if cgversion not in changegroup.supportedoutgoingversions(repo):
1223 if cgversion not in changegroup.supportedoutgoingversions(repo):
1224 raise error.Abort(_("repository does not support bundle version %s") %
1224 raise error.Abort(_("repository does not support bundle version %s") %
1225 cgversion)
1225 cgversion)
1226
1226
1227 if base:
1227 if base:
1228 if dest:
1228 if dest:
1229 raise error.Abort(_("--base is incompatible with specifying "
1229 raise error.Abort(_("--base is incompatible with specifying "
1230 "a destination"))
1230 "a destination"))
1231 common = [repo.lookup(rev) for rev in base]
1231 common = [repo.lookup(rev) for rev in base]
1232 heads = [repo.lookup(r) for r in revs] if revs else None
1232 heads = [repo.lookup(r) for r in revs] if revs else None
1233 outgoing = discovery.outgoing(repo, common, heads)
1233 outgoing = discovery.outgoing(repo, common, heads)
1234 else:
1234 else:
1235 dest = ui.expandpath(dest or 'default-push', dest or 'default')
1235 dest = ui.expandpath(dest or 'default-push', dest or 'default')
1236 dest, branches = hg.parseurl(dest, opts.get('branch'))
1236 dest, branches = hg.parseurl(dest, opts.get('branch'))
1237 other = hg.peer(repo, opts, dest)
1237 other = hg.peer(repo, opts, dest)
1238 revs, checkout = hg.addbranchrevs(repo, repo, branches, revs)
1238 revs, checkout = hg.addbranchrevs(repo, repo, branches, revs)
1239 heads = revs and map(repo.lookup, revs) or revs
1239 heads = revs and map(repo.lookup, revs) or revs
1240 outgoing = discovery.findcommonoutgoing(repo, other,
1240 outgoing = discovery.findcommonoutgoing(repo, other,
1241 onlyheads=heads,
1241 onlyheads=heads,
1242 force=opts.get('force'),
1242 force=opts.get('force'),
1243 portable=True)
1243 portable=True)
1244
1244
1245 if not outgoing.missing:
1245 if not outgoing.missing:
1246 scmutil.nochangesfound(ui, repo, not base and outgoing.excluded)
1246 scmutil.nochangesfound(ui, repo, not base and outgoing.excluded)
1247 return 1
1247 return 1
1248
1248
1249 bcompression = bundlespec.compression
1249 bcompression = bundlespec.compression
1250 if cgversion == '01': #bundle1
1250 if cgversion == '01': #bundle1
1251 if bcompression is None:
1251 if bcompression is None:
1252 bcompression = 'UN'
1252 bcompression = 'UN'
1253 bversion = 'HG10' + bcompression
1253 bversion = 'HG10' + bcompression
1254 bcompression = None
1254 bcompression = None
1255 elif cgversion in ('02', '03'):
1255 elif cgversion in ('02', '03'):
1256 bversion = 'HG20'
1256 bversion = 'HG20'
1257 else:
1257 else:
1258 raise error.ProgrammingError(
1258 raise error.ProgrammingError(
1259 'bundle: unexpected changegroup version %s' % cgversion)
1259 'bundle: unexpected changegroup version %s' % cgversion)
1260
1260
1261 # TODO compression options should be derived from bundlespec parsing.
1261 # TODO compression options should be derived from bundlespec parsing.
1262 # This is a temporary hack to allow adjusting bundle compression
1262 # This is a temporary hack to allow adjusting bundle compression
1263 # level without a) formalizing the bundlespec changes to declare it
1263 # level without a) formalizing the bundlespec changes to declare it
1264 # b) introducing a command flag.
1264 # b) introducing a command flag.
1265 compopts = {}
1265 compopts = {}
1266 complevel = ui.configint('experimental', 'bundlecomplevel')
1266 complevel = ui.configint('experimental', 'bundlecomplevel')
1267 if complevel is not None:
1267 if complevel is not None:
1268 compopts['level'] = complevel
1268 compopts['level'] = complevel
1269
1269
1270 # Allow overriding the bundling of obsmarker in phases through
1270 # Allow overriding the bundling of obsmarker in phases through
1271 # configuration while we don't have a bundle version that include them
1271 # configuration while we don't have a bundle version that include them
1272 if repo.ui.configbool('experimental', 'evolution.bundle-obsmarker'):
1272 if repo.ui.configbool('experimental', 'evolution.bundle-obsmarker'):
1273 bundlespec.contentopts['obsolescence'] = True
1273 bundlespec.contentopts['obsolescence'] = True
1274 if repo.ui.configbool('experimental', 'bundle-phases'):
1274 if repo.ui.configbool('experimental', 'bundle-phases'):
1275 bundlespec.contentopts['phases'] = True
1275 bundlespec.contentopts['phases'] = True
1276
1276
1277 bundle2.writenewbundle(ui, repo, 'bundle', fname, bversion, outgoing,
1277 bundle2.writenewbundle(ui, repo, 'bundle', fname, bversion, outgoing,
1278 bundlespec.contentopts, compression=bcompression,
1278 bundlespec.contentopts, compression=bcompression,
1279 compopts=compopts)
1279 compopts=compopts)
1280
1280
1281 @command('cat',
1281 @command('cat',
1282 [('o', 'output', '',
1282 [('o', 'output', '',
1283 _('print output to file with formatted name'), _('FORMAT')),
1283 _('print output to file with formatted name'), _('FORMAT')),
1284 ('r', 'rev', '', _('print the given revision'), _('REV')),
1284 ('r', 'rev', '', _('print the given revision'), _('REV')),
1285 ('', 'decode', None, _('apply any matching decode filter')),
1285 ('', 'decode', None, _('apply any matching decode filter')),
1286 ] + walkopts + formatteropts,
1286 ] + walkopts + formatteropts,
1287 _('[OPTION]... FILE...'),
1287 _('[OPTION]... FILE...'),
1288 inferrepo=True, cmdtype=readonly)
1288 inferrepo=True, cmdtype=readonly)
1289 def cat(ui, repo, file1, *pats, **opts):
1289 def cat(ui, repo, file1, *pats, **opts):
1290 """output the current or given revision of files
1290 """output the current or given revision of files
1291
1291
1292 Print the specified files as they were at the given revision. If
1292 Print the specified files as they were at the given revision. If
1293 no revision is given, the parent of the working directory is used.
1293 no revision is given, the parent of the working directory is used.
1294
1294
1295 Output may be to a file, in which case the name of the file is
1295 Output may be to a file, in which case the name of the file is
1296 given using a template string. See :hg:`help templates`. In addition
1296 given using a template string. See :hg:`help templates`. In addition
1297 to the common template keywords, the following formatting rules are
1297 to the common template keywords, the following formatting rules are
1298 supported:
1298 supported:
1299
1299
1300 :``%%``: literal "%" character
1300 :``%%``: literal "%" character
1301 :``%s``: basename of file being printed
1301 :``%s``: basename of file being printed
1302 :``%d``: dirname of file being printed, or '.' if in repository root
1302 :``%d``: dirname of file being printed, or '.' if in repository root
1303 :``%p``: root-relative path name of file being printed
1303 :``%p``: root-relative path name of file being printed
1304 :``%H``: changeset hash (40 hexadecimal digits)
1304 :``%H``: changeset hash (40 hexadecimal digits)
1305 :``%R``: changeset revision number
1305 :``%R``: changeset revision number
1306 :``%h``: short-form changeset hash (12 hexadecimal digits)
1306 :``%h``: short-form changeset hash (12 hexadecimal digits)
1307 :``%r``: zero-padded changeset revision number
1307 :``%r``: zero-padded changeset revision number
1308 :``%b``: basename of the exporting repository
1308 :``%b``: basename of the exporting repository
1309 :``\\``: literal "\\" character
1309 :``\\``: literal "\\" character
1310
1310
1311 Returns 0 on success.
1311 Returns 0 on success.
1312 """
1312 """
1313 opts = pycompat.byteskwargs(opts)
1313 opts = pycompat.byteskwargs(opts)
1314 rev = opts.get('rev')
1314 rev = opts.get('rev')
1315 if rev:
1315 if rev:
1316 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
1316 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
1317 ctx = scmutil.revsingle(repo, rev)
1317 ctx = scmutil.revsingle(repo, rev)
1318 m = scmutil.match(ctx, (file1,) + pats, opts)
1318 m = scmutil.match(ctx, (file1,) + pats, opts)
1319 fntemplate = opts.pop('output', '')
1319 fntemplate = opts.pop('output', '')
1320 if cmdutil.isstdiofilename(fntemplate):
1320 if cmdutil.isstdiofilename(fntemplate):
1321 fntemplate = ''
1321 fntemplate = ''
1322
1322
1323 if fntemplate:
1323 if fntemplate:
1324 fm = formatter.nullformatter(ui, 'cat')
1324 fm = formatter.nullformatter(ui, 'cat')
1325 else:
1325 else:
1326 ui.pager('cat')
1326 ui.pager('cat')
1327 fm = ui.formatter('cat', opts)
1327 fm = ui.formatter('cat', opts)
1328 with fm:
1328 with fm:
1329 return cmdutil.cat(ui, repo, ctx, m, fm, fntemplate, '',
1329 return cmdutil.cat(ui, repo, ctx, m, fm, fntemplate, '',
1330 **pycompat.strkwargs(opts))
1330 **pycompat.strkwargs(opts))
1331
1331
1332 @command('^clone',
1332 @command('^clone',
1333 [('U', 'noupdate', None, _('the clone will include an empty working '
1333 [('U', 'noupdate', None, _('the clone will include an empty working '
1334 'directory (only a repository)')),
1334 'directory (only a repository)')),
1335 ('u', 'updaterev', '', _('revision, tag, or branch to check out'),
1335 ('u', 'updaterev', '', _('revision, tag, or branch to check out'),
1336 _('REV')),
1336 _('REV')),
1337 ('r', 'rev', [], _('do not clone everything, but include this changeset'
1337 ('r', 'rev', [], _('do not clone everything, but include this changeset'
1338 ' and its ancestors'), _('REV')),
1338 ' and its ancestors'), _('REV')),
1339 ('b', 'branch', [], _('do not clone everything, but include this branch\'s'
1339 ('b', 'branch', [], _('do not clone everything, but include this branch\'s'
1340 ' changesets and their ancestors'), _('BRANCH')),
1340 ' changesets and their ancestors'), _('BRANCH')),
1341 ('', 'pull', None, _('use pull protocol to copy metadata')),
1341 ('', 'pull', None, _('use pull protocol to copy metadata')),
1342 ('', 'uncompressed', None,
1342 ('', 'uncompressed', None,
1343 _('an alias to --stream (DEPRECATED)')),
1343 _('an alias to --stream (DEPRECATED)')),
1344 ('', 'stream', None,
1344 ('', 'stream', None,
1345 _('clone with minimal data processing')),
1345 _('clone with minimal data processing')),
1346 ] + remoteopts,
1346 ] + remoteopts,
1347 _('[OPTION]... SOURCE [DEST]'),
1347 _('[OPTION]... SOURCE [DEST]'),
1348 norepo=True)
1348 norepo=True)
1349 def clone(ui, source, dest=None, **opts):
1349 def clone(ui, source, dest=None, **opts):
1350 """make a copy of an existing repository
1350 """make a copy of an existing repository
1351
1351
1352 Create a copy of an existing repository in a new directory.
1352 Create a copy of an existing repository in a new directory.
1353
1353
1354 If no destination directory name is specified, it defaults to the
1354 If no destination directory name is specified, it defaults to the
1355 basename of the source.
1355 basename of the source.
1356
1356
1357 The location of the source is added to the new repository's
1357 The location of the source is added to the new repository's
1358 ``.hg/hgrc`` file, as the default to be used for future pulls.
1358 ``.hg/hgrc`` file, as the default to be used for future pulls.
1359
1359
1360 Only local paths and ``ssh://`` URLs are supported as
1360 Only local paths and ``ssh://`` URLs are supported as
1361 destinations. For ``ssh://`` destinations, no working directory or
1361 destinations. For ``ssh://`` destinations, no working directory or
1362 ``.hg/hgrc`` will be created on the remote side.
1362 ``.hg/hgrc`` will be created on the remote side.
1363
1363
1364 If the source repository has a bookmark called '@' set, that
1364 If the source repository has a bookmark called '@' set, that
1365 revision will be checked out in the new repository by default.
1365 revision will be checked out in the new repository by default.
1366
1366
1367 To check out a particular version, use -u/--update, or
1367 To check out a particular version, use -u/--update, or
1368 -U/--noupdate to create a clone with no working directory.
1368 -U/--noupdate to create a clone with no working directory.
1369
1369
1370 To pull only a subset of changesets, specify one or more revisions
1370 To pull only a subset of changesets, specify one or more revisions
1371 identifiers with -r/--rev or branches with -b/--branch. The
1371 identifiers with -r/--rev or branches with -b/--branch. The
1372 resulting clone will contain only the specified changesets and
1372 resulting clone will contain only the specified changesets and
1373 their ancestors. These options (or 'clone src#rev dest') imply
1373 their ancestors. These options (or 'clone src#rev dest') imply
1374 --pull, even for local source repositories.
1374 --pull, even for local source repositories.
1375
1375
1376 In normal clone mode, the remote normalizes repository data into a common
1376 In normal clone mode, the remote normalizes repository data into a common
1377 exchange format and the receiving end translates this data into its local
1377 exchange format and the receiving end translates this data into its local
1378 storage format. --stream activates a different clone mode that essentially
1378 storage format. --stream activates a different clone mode that essentially
1379 copies repository files from the remote with minimal data processing. This
1379 copies repository files from the remote with minimal data processing. This
1380 significantly reduces the CPU cost of a clone both remotely and locally.
1380 significantly reduces the CPU cost of a clone both remotely and locally.
1381 However, it often increases the transferred data size by 30-40%. This can
1381 However, it often increases the transferred data size by 30-40%. This can
1382 result in substantially faster clones where I/O throughput is plentiful,
1382 result in substantially faster clones where I/O throughput is plentiful,
1383 especially for larger repositories. A side-effect of --stream clones is
1383 especially for larger repositories. A side-effect of --stream clones is
1384 that storage settings and requirements on the remote are applied locally:
1384 that storage settings and requirements on the remote are applied locally:
1385 a modern client may inherit legacy or inefficient storage used by the
1385 a modern client may inherit legacy or inefficient storage used by the
1386 remote or a legacy Mercurial client may not be able to clone from a
1386 remote or a legacy Mercurial client may not be able to clone from a
1387 modern Mercurial remote.
1387 modern Mercurial remote.
1388
1388
1389 .. note::
1389 .. note::
1390
1390
1391 Specifying a tag will include the tagged changeset but not the
1391 Specifying a tag will include the tagged changeset but not the
1392 changeset containing the tag.
1392 changeset containing the tag.
1393
1393
1394 .. container:: verbose
1394 .. container:: verbose
1395
1395
1396 For efficiency, hardlinks are used for cloning whenever the
1396 For efficiency, hardlinks are used for cloning whenever the
1397 source and destination are on the same filesystem (note this
1397 source and destination are on the same filesystem (note this
1398 applies only to the repository data, not to the working
1398 applies only to the repository data, not to the working
1399 directory). Some filesystems, such as AFS, implement hardlinking
1399 directory). Some filesystems, such as AFS, implement hardlinking
1400 incorrectly, but do not report errors. In these cases, use the
1400 incorrectly, but do not report errors. In these cases, use the
1401 --pull option to avoid hardlinking.
1401 --pull option to avoid hardlinking.
1402
1402
1403 Mercurial will update the working directory to the first applicable
1403 Mercurial will update the working directory to the first applicable
1404 revision from this list:
1404 revision from this list:
1405
1405
1406 a) null if -U or the source repository has no changesets
1406 a) null if -U or the source repository has no changesets
1407 b) if -u . and the source repository is local, the first parent of
1407 b) if -u . and the source repository is local, the first parent of
1408 the source repository's working directory
1408 the source repository's working directory
1409 c) the changeset specified with -u (if a branch name, this means the
1409 c) the changeset specified with -u (if a branch name, this means the
1410 latest head of that branch)
1410 latest head of that branch)
1411 d) the changeset specified with -r
1411 d) the changeset specified with -r
1412 e) the tipmost head specified with -b
1412 e) the tipmost head specified with -b
1413 f) the tipmost head specified with the url#branch source syntax
1413 f) the tipmost head specified with the url#branch source syntax
1414 g) the revision marked with the '@' bookmark, if present
1414 g) the revision marked with the '@' bookmark, if present
1415 h) the tipmost head of the default branch
1415 h) the tipmost head of the default branch
1416 i) tip
1416 i) tip
1417
1417
1418 When cloning from servers that support it, Mercurial may fetch
1418 When cloning from servers that support it, Mercurial may fetch
1419 pre-generated data from a server-advertised URL. When this is done,
1419 pre-generated data from a server-advertised URL. When this is done,
1420 hooks operating on incoming changesets and changegroups may fire twice,
1420 hooks operating on incoming changesets and changegroups may fire twice,
1421 once for the bundle fetched from the URL and another for any additional
1421 once for the bundle fetched from the URL and another for any additional
1422 data not fetched from this URL. In addition, if an error occurs, the
1422 data not fetched from this URL. In addition, if an error occurs, the
1423 repository may be rolled back to a partial clone. This behavior may
1423 repository may be rolled back to a partial clone. This behavior may
1424 change in future releases. See :hg:`help -e clonebundles` for more.
1424 change in future releases. See :hg:`help -e clonebundles` for more.
1425
1425
1426 Examples:
1426 Examples:
1427
1427
1428 - clone a remote repository to a new directory named hg/::
1428 - clone a remote repository to a new directory named hg/::
1429
1429
1430 hg clone https://www.mercurial-scm.org/repo/hg/
1430 hg clone https://www.mercurial-scm.org/repo/hg/
1431
1431
1432 - create a lightweight local clone::
1432 - create a lightweight local clone::
1433
1433
1434 hg clone project/ project-feature/
1434 hg clone project/ project-feature/
1435
1435
1436 - clone from an absolute path on an ssh server (note double-slash)::
1436 - clone from an absolute path on an ssh server (note double-slash)::
1437
1437
1438 hg clone ssh://user@server//home/projects/alpha/
1438 hg clone ssh://user@server//home/projects/alpha/
1439
1439
1440 - do a streaming clone while checking out a specified version::
1440 - do a streaming clone while checking out a specified version::
1441
1441
1442 hg clone --stream http://server/repo -u 1.5
1442 hg clone --stream http://server/repo -u 1.5
1443
1443
1444 - create a repository without changesets after a particular revision::
1444 - create a repository without changesets after a particular revision::
1445
1445
1446 hg clone -r 04e544 experimental/ good/
1446 hg clone -r 04e544 experimental/ good/
1447
1447
1448 - clone (and track) a particular named branch::
1448 - clone (and track) a particular named branch::
1449
1449
1450 hg clone https://www.mercurial-scm.org/repo/hg/#stable
1450 hg clone https://www.mercurial-scm.org/repo/hg/#stable
1451
1451
1452 See :hg:`help urls` for details on specifying URLs.
1452 See :hg:`help urls` for details on specifying URLs.
1453
1453
1454 Returns 0 on success.
1454 Returns 0 on success.
1455 """
1455 """
1456 opts = pycompat.byteskwargs(opts)
1456 opts = pycompat.byteskwargs(opts)
1457 if opts.get('noupdate') and opts.get('updaterev'):
1457 if opts.get('noupdate') and opts.get('updaterev'):
1458 raise error.Abort(_("cannot specify both --noupdate and --updaterev"))
1458 raise error.Abort(_("cannot specify both --noupdate and --updaterev"))
1459
1459
1460 r = hg.clone(ui, opts, source, dest,
1460 r = hg.clone(ui, opts, source, dest,
1461 pull=opts.get('pull'),
1461 pull=opts.get('pull'),
1462 stream=opts.get('stream') or opts.get('uncompressed'),
1462 stream=opts.get('stream') or opts.get('uncompressed'),
1463 rev=opts.get('rev'),
1463 rev=opts.get('rev'),
1464 update=opts.get('updaterev') or not opts.get('noupdate'),
1464 update=opts.get('updaterev') or not opts.get('noupdate'),
1465 branch=opts.get('branch'),
1465 branch=opts.get('branch'),
1466 shareopts=opts.get('shareopts'))
1466 shareopts=opts.get('shareopts'))
1467
1467
1468 return r is None
1468 return r is None
1469
1469
1470 @command('^commit|ci',
1470 @command('^commit|ci',
1471 [('A', 'addremove', None,
1471 [('A', 'addremove', None,
1472 _('mark new/missing files as added/removed before committing')),
1472 _('mark new/missing files as added/removed before committing')),
1473 ('', 'close-branch', None,
1473 ('', 'close-branch', None,
1474 _('mark a branch head as closed')),
1474 _('mark a branch head as closed')),
1475 ('', 'amend', None, _('amend the parent of the working directory')),
1475 ('', 'amend', None, _('amend the parent of the working directory')),
1476 ('s', 'secret', None, _('use the secret phase for committing')),
1476 ('s', 'secret', None, _('use the secret phase for committing')),
1477 ('e', 'edit', None, _('invoke editor on commit messages')),
1477 ('e', 'edit', None, _('invoke editor on commit messages')),
1478 ('i', 'interactive', None, _('use interactive mode')),
1478 ('i', 'interactive', None, _('use interactive mode')),
1479 ] + walkopts + commitopts + commitopts2 + subrepoopts,
1479 ] + walkopts + commitopts + commitopts2 + subrepoopts,
1480 _('[OPTION]... [FILE]...'),
1480 _('[OPTION]... [FILE]...'),
1481 inferrepo=True)
1481 inferrepo=True)
1482 def commit(ui, repo, *pats, **opts):
1482 def commit(ui, repo, *pats, **opts):
1483 """commit the specified files or all outstanding changes
1483 """commit the specified files or all outstanding changes
1484
1484
1485 Commit changes to the given files into the repository. Unlike a
1485 Commit changes to the given files into the repository. Unlike a
1486 centralized SCM, this operation is a local operation. See
1486 centralized SCM, this operation is a local operation. See
1487 :hg:`push` for a way to actively distribute your changes.
1487 :hg:`push` for a way to actively distribute your changes.
1488
1488
1489 If a list of files is omitted, all changes reported by :hg:`status`
1489 If a list of files is omitted, all changes reported by :hg:`status`
1490 will be committed.
1490 will be committed.
1491
1491
1492 If you are committing the result of a merge, do not provide any
1492 If you are committing the result of a merge, do not provide any
1493 filenames or -I/-X filters.
1493 filenames or -I/-X filters.
1494
1494
1495 If no commit message is specified, Mercurial starts your
1495 If no commit message is specified, Mercurial starts your
1496 configured editor where you can enter a message. In case your
1496 configured editor where you can enter a message. In case your
1497 commit fails, you will find a backup of your message in
1497 commit fails, you will find a backup of your message in
1498 ``.hg/last-message.txt``.
1498 ``.hg/last-message.txt``.
1499
1499
1500 The --close-branch flag can be used to mark the current branch
1500 The --close-branch flag can be used to mark the current branch
1501 head closed. When all heads of a branch are closed, the branch
1501 head closed. When all heads of a branch are closed, the branch
1502 will be considered closed and no longer listed.
1502 will be considered closed and no longer listed.
1503
1503
1504 The --amend flag can be used to amend the parent of the
1504 The --amend flag can be used to amend the parent of the
1505 working directory with a new commit that contains the changes
1505 working directory with a new commit that contains the changes
1506 in the parent in addition to those currently reported by :hg:`status`,
1506 in the parent in addition to those currently reported by :hg:`status`,
1507 if there are any. The old commit is stored in a backup bundle in
1507 if there are any. The old commit is stored in a backup bundle in
1508 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
1508 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
1509 on how to restore it).
1509 on how to restore it).
1510
1510
1511 Message, user and date are taken from the amended commit unless
1511 Message, user and date are taken from the amended commit unless
1512 specified. When a message isn't specified on the command line,
1512 specified. When a message isn't specified on the command line,
1513 the editor will open with the message of the amended commit.
1513 the editor will open with the message of the amended commit.
1514
1514
1515 It is not possible to amend public changesets (see :hg:`help phases`)
1515 It is not possible to amend public changesets (see :hg:`help phases`)
1516 or changesets that have children.
1516 or changesets that have children.
1517
1517
1518 See :hg:`help dates` for a list of formats valid for -d/--date.
1518 See :hg:`help dates` for a list of formats valid for -d/--date.
1519
1519
1520 Returns 0 on success, 1 if nothing changed.
1520 Returns 0 on success, 1 if nothing changed.
1521
1521
1522 .. container:: verbose
1522 .. container:: verbose
1523
1523
1524 Examples:
1524 Examples:
1525
1525
1526 - commit all files ending in .py::
1526 - commit all files ending in .py::
1527
1527
1528 hg commit --include "set:**.py"
1528 hg commit --include "set:**.py"
1529
1529
1530 - commit all non-binary files::
1530 - commit all non-binary files::
1531
1531
1532 hg commit --exclude "set:binary()"
1532 hg commit --exclude "set:binary()"
1533
1533
1534 - amend the current commit and set the date to now::
1534 - amend the current commit and set the date to now::
1535
1535
1536 hg commit --amend --date now
1536 hg commit --amend --date now
1537 """
1537 """
1538 wlock = lock = None
1538 wlock = lock = None
1539 try:
1539 try:
1540 wlock = repo.wlock()
1540 wlock = repo.wlock()
1541 lock = repo.lock()
1541 lock = repo.lock()
1542 return _docommit(ui, repo, *pats, **opts)
1542 return _docommit(ui, repo, *pats, **opts)
1543 finally:
1543 finally:
1544 release(lock, wlock)
1544 release(lock, wlock)
1545
1545
1546 def _docommit(ui, repo, *pats, **opts):
1546 def _docommit(ui, repo, *pats, **opts):
1547 if opts.get(r'interactive'):
1547 if opts.get(r'interactive'):
1548 opts.pop(r'interactive')
1548 opts.pop(r'interactive')
1549 ret = cmdutil.dorecord(ui, repo, commit, None, False,
1549 ret = cmdutil.dorecord(ui, repo, commit, None, False,
1550 cmdutil.recordfilter, *pats,
1550 cmdutil.recordfilter, *pats,
1551 **opts)
1551 **opts)
1552 # ret can be 0 (no changes to record) or the value returned by
1552 # ret can be 0 (no changes to record) or the value returned by
1553 # commit(), 1 if nothing changed or None on success.
1553 # commit(), 1 if nothing changed or None on success.
1554 return 1 if ret == 0 else ret
1554 return 1 if ret == 0 else ret
1555
1555
1556 opts = pycompat.byteskwargs(opts)
1556 opts = pycompat.byteskwargs(opts)
1557 if opts.get('subrepos'):
1557 if opts.get('subrepos'):
1558 if opts.get('amend'):
1558 if opts.get('amend'):
1559 raise error.Abort(_('cannot amend with --subrepos'))
1559 raise error.Abort(_('cannot amend with --subrepos'))
1560 # Let --subrepos on the command line override config setting.
1560 # Let --subrepos on the command line override config setting.
1561 ui.setconfig('ui', 'commitsubrepos', True, 'commit')
1561 ui.setconfig('ui', 'commitsubrepos', True, 'commit')
1562
1562
1563 cmdutil.checkunfinished(repo, commit=True)
1563 cmdutil.checkunfinished(repo, commit=True)
1564
1564
1565 branch = repo[None].branch()
1565 branch = repo[None].branch()
1566 bheads = repo.branchheads(branch)
1566 bheads = repo.branchheads(branch)
1567
1567
1568 extra = {}
1568 extra = {}
1569 if opts.get('close_branch'):
1569 if opts.get('close_branch'):
1570 extra['close'] = '1'
1570 extra['close'] = '1'
1571
1571
1572 if not bheads:
1572 if not bheads:
1573 raise error.Abort(_('can only close branch heads'))
1573 raise error.Abort(_('can only close branch heads'))
1574 elif opts.get('amend'):
1574 elif opts.get('amend'):
1575 if repo[None].parents()[0].p1().branch() != branch and \
1575 if repo[None].parents()[0].p1().branch() != branch and \
1576 repo[None].parents()[0].p2().branch() != branch:
1576 repo[None].parents()[0].p2().branch() != branch:
1577 raise error.Abort(_('can only close branch heads'))
1577 raise error.Abort(_('can only close branch heads'))
1578
1578
1579 if opts.get('amend'):
1579 if opts.get('amend'):
1580 if ui.configbool('ui', 'commitsubrepos'):
1580 if ui.configbool('ui', 'commitsubrepos'):
1581 raise error.Abort(_('cannot amend with ui.commitsubrepos enabled'))
1581 raise error.Abort(_('cannot amend with ui.commitsubrepos enabled'))
1582
1582
1583 old = repo['.']
1583 old = repo['.']
1584 rewriteutil.precheck(repo, [old.rev()], 'amend')
1584 rewriteutil.precheck(repo, [old.rev()], 'amend')
1585
1585
1586 # Currently histedit gets confused if an amend happens while histedit
1586 # Currently histedit gets confused if an amend happens while histedit
1587 # is in progress. Since we have a checkunfinished command, we are
1587 # is in progress. Since we have a checkunfinished command, we are
1588 # temporarily honoring it.
1588 # temporarily honoring it.
1589 #
1589 #
1590 # Note: eventually this guard will be removed. Please do not expect
1590 # Note: eventually this guard will be removed. Please do not expect
1591 # this behavior to remain.
1591 # this behavior to remain.
1592 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
1592 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
1593 cmdutil.checkunfinished(repo)
1593 cmdutil.checkunfinished(repo)
1594
1594
1595 node = cmdutil.amend(ui, repo, old, extra, pats, opts)
1595 node = cmdutil.amend(ui, repo, old, extra, pats, opts)
1596 if node == old.node():
1596 if node == old.node():
1597 ui.status(_("nothing changed\n"))
1597 ui.status(_("nothing changed\n"))
1598 return 1
1598 return 1
1599 else:
1599 else:
1600 def commitfunc(ui, repo, message, match, opts):
1600 def commitfunc(ui, repo, message, match, opts):
1601 overrides = {}
1601 overrides = {}
1602 if opts.get('secret'):
1602 if opts.get('secret'):
1603 overrides[('phases', 'new-commit')] = 'secret'
1603 overrides[('phases', 'new-commit')] = 'secret'
1604
1604
1605 baseui = repo.baseui
1605 baseui = repo.baseui
1606 with baseui.configoverride(overrides, 'commit'):
1606 with baseui.configoverride(overrides, 'commit'):
1607 with ui.configoverride(overrides, 'commit'):
1607 with ui.configoverride(overrides, 'commit'):
1608 editform = cmdutil.mergeeditform(repo[None],
1608 editform = cmdutil.mergeeditform(repo[None],
1609 'commit.normal')
1609 'commit.normal')
1610 editor = cmdutil.getcommiteditor(
1610 editor = cmdutil.getcommiteditor(
1611 editform=editform, **pycompat.strkwargs(opts))
1611 editform=editform, **pycompat.strkwargs(opts))
1612 return repo.commit(message,
1612 return repo.commit(message,
1613 opts.get('user'),
1613 opts.get('user'),
1614 opts.get('date'),
1614 opts.get('date'),
1615 match,
1615 match,
1616 editor=editor,
1616 editor=editor,
1617 extra=extra)
1617 extra=extra)
1618
1618
1619 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
1619 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
1620
1620
1621 if not node:
1621 if not node:
1622 stat = cmdutil.postcommitstatus(repo, pats, opts)
1622 stat = cmdutil.postcommitstatus(repo, pats, opts)
1623 if stat[3]:
1623 if stat[3]:
1624 ui.status(_("nothing changed (%d missing files, see "
1624 ui.status(_("nothing changed (%d missing files, see "
1625 "'hg status')\n") % len(stat[3]))
1625 "'hg status')\n") % len(stat[3]))
1626 else:
1626 else:
1627 ui.status(_("nothing changed\n"))
1627 ui.status(_("nothing changed\n"))
1628 return 1
1628 return 1
1629
1629
1630 cmdutil.commitstatus(repo, node, branch, bheads, opts)
1630 cmdutil.commitstatus(repo, node, branch, bheads, opts)
1631
1631
1632 @command('config|showconfig|debugconfig',
1632 @command('config|showconfig|debugconfig',
1633 [('u', 'untrusted', None, _('show untrusted configuration options')),
1633 [('u', 'untrusted', None, _('show untrusted configuration options')),
1634 ('e', 'edit', None, _('edit user config')),
1634 ('e', 'edit', None, _('edit user config')),
1635 ('l', 'local', None, _('edit repository config')),
1635 ('l', 'local', None, _('edit repository config')),
1636 ('g', 'global', None, _('edit global config'))] + formatteropts,
1636 ('g', 'global', None, _('edit global config'))] + formatteropts,
1637 _('[-u] [NAME]...'),
1637 _('[-u] [NAME]...'),
1638 optionalrepo=True, cmdtype=readonly)
1638 optionalrepo=True, cmdtype=readonly)
1639 def config(ui, repo, *values, **opts):
1639 def config(ui, repo, *values, **opts):
1640 """show combined config settings from all hgrc files
1640 """show combined config settings from all hgrc files
1641
1641
1642 With no arguments, print names and values of all config items.
1642 With no arguments, print names and values of all config items.
1643
1643
1644 With one argument of the form section.name, print just the value
1644 With one argument of the form section.name, print just the value
1645 of that config item.
1645 of that config item.
1646
1646
1647 With multiple arguments, print names and values of all config
1647 With multiple arguments, print names and values of all config
1648 items with matching section names or section.names.
1648 items with matching section names or section.names.
1649
1649
1650 With --edit, start an editor on the user-level config file. With
1650 With --edit, start an editor on the user-level config file. With
1651 --global, edit the system-wide config file. With --local, edit the
1651 --global, edit the system-wide config file. With --local, edit the
1652 repository-level config file.
1652 repository-level config file.
1653
1653
1654 With --debug, the source (filename and line number) is printed
1654 With --debug, the source (filename and line number) is printed
1655 for each config item.
1655 for each config item.
1656
1656
1657 See :hg:`help config` for more information about config files.
1657 See :hg:`help config` for more information about config files.
1658
1658
1659 Returns 0 on success, 1 if NAME does not exist.
1659 Returns 0 on success, 1 if NAME does not exist.
1660
1660
1661 """
1661 """
1662
1662
1663 opts = pycompat.byteskwargs(opts)
1663 opts = pycompat.byteskwargs(opts)
1664 if opts.get('edit') or opts.get('local') or opts.get('global'):
1664 if opts.get('edit') or opts.get('local') or opts.get('global'):
1665 if opts.get('local') and opts.get('global'):
1665 if opts.get('local') and opts.get('global'):
1666 raise error.Abort(_("can't use --local and --global together"))
1666 raise error.Abort(_("can't use --local and --global together"))
1667
1667
1668 if opts.get('local'):
1668 if opts.get('local'):
1669 if not repo:
1669 if not repo:
1670 raise error.Abort(_("can't use --local outside a repository"))
1670 raise error.Abort(_("can't use --local outside a repository"))
1671 paths = [repo.vfs.join('hgrc')]
1671 paths = [repo.vfs.join('hgrc')]
1672 elif opts.get('global'):
1672 elif opts.get('global'):
1673 paths = rcutil.systemrcpath()
1673 paths = rcutil.systemrcpath()
1674 else:
1674 else:
1675 paths = rcutil.userrcpath()
1675 paths = rcutil.userrcpath()
1676
1676
1677 for f in paths:
1677 for f in paths:
1678 if os.path.exists(f):
1678 if os.path.exists(f):
1679 break
1679 break
1680 else:
1680 else:
1681 if opts.get('global'):
1681 if opts.get('global'):
1682 samplehgrc = uimod.samplehgrcs['global']
1682 samplehgrc = uimod.samplehgrcs['global']
1683 elif opts.get('local'):
1683 elif opts.get('local'):
1684 samplehgrc = uimod.samplehgrcs['local']
1684 samplehgrc = uimod.samplehgrcs['local']
1685 else:
1685 else:
1686 samplehgrc = uimod.samplehgrcs['user']
1686 samplehgrc = uimod.samplehgrcs['user']
1687
1687
1688 f = paths[0]
1688 f = paths[0]
1689 fp = open(f, "wb")
1689 fp = open(f, "wb")
1690 fp.write(util.tonativeeol(samplehgrc))
1690 fp.write(util.tonativeeol(samplehgrc))
1691 fp.close()
1691 fp.close()
1692
1692
1693 editor = ui.geteditor()
1693 editor = ui.geteditor()
1694 ui.system("%s \"%s\"" % (editor, f),
1694 ui.system("%s \"%s\"" % (editor, f),
1695 onerr=error.Abort, errprefix=_("edit failed"),
1695 onerr=error.Abort, errprefix=_("edit failed"),
1696 blockedtag='config_edit')
1696 blockedtag='config_edit')
1697 return
1697 return
1698 ui.pager('config')
1698 ui.pager('config')
1699 fm = ui.formatter('config', opts)
1699 fm = ui.formatter('config', opts)
1700 for t, f in rcutil.rccomponents():
1700 for t, f in rcutil.rccomponents():
1701 if t == 'path':
1701 if t == 'path':
1702 ui.debug('read config from: %s\n' % f)
1702 ui.debug('read config from: %s\n' % f)
1703 elif t == 'items':
1703 elif t == 'items':
1704 for section, name, value, source in f:
1704 for section, name, value, source in f:
1705 ui.debug('set config by: %s\n' % source)
1705 ui.debug('set config by: %s\n' % source)
1706 else:
1706 else:
1707 raise error.ProgrammingError('unknown rctype: %s' % t)
1707 raise error.ProgrammingError('unknown rctype: %s' % t)
1708 untrusted = bool(opts.get('untrusted'))
1708 untrusted = bool(opts.get('untrusted'))
1709
1709
1710 selsections = selentries = []
1710 selsections = selentries = []
1711 if values:
1711 if values:
1712 selsections = [v for v in values if '.' not in v]
1712 selsections = [v for v in values if '.' not in v]
1713 selentries = [v for v in values if '.' in v]
1713 selentries = [v for v in values if '.' in v]
1714 uniquesel = (len(selentries) == 1 and not selsections)
1714 uniquesel = (len(selentries) == 1 and not selsections)
1715 selsections = set(selsections)
1715 selsections = set(selsections)
1716 selentries = set(selentries)
1716 selentries = set(selentries)
1717
1717
1718 matched = False
1718 matched = False
1719 for section, name, value in ui.walkconfig(untrusted=untrusted):
1719 for section, name, value in ui.walkconfig(untrusted=untrusted):
1720 source = ui.configsource(section, name, untrusted)
1720 source = ui.configsource(section, name, untrusted)
1721 value = pycompat.bytestr(value)
1721 value = pycompat.bytestr(value)
1722 if fm.isplain():
1722 if fm.isplain():
1723 source = source or 'none'
1723 source = source or 'none'
1724 value = value.replace('\n', '\\n')
1724 value = value.replace('\n', '\\n')
1725 entryname = section + '.' + name
1725 entryname = section + '.' + name
1726 if values and not (section in selsections or entryname in selentries):
1726 if values and not (section in selsections or entryname in selentries):
1727 continue
1727 continue
1728 fm.startitem()
1728 fm.startitem()
1729 fm.condwrite(ui.debugflag, 'source', '%s: ', source)
1729 fm.condwrite(ui.debugflag, 'source', '%s: ', source)
1730 if uniquesel:
1730 if uniquesel:
1731 fm.data(name=entryname)
1731 fm.data(name=entryname)
1732 fm.write('value', '%s\n', value)
1732 fm.write('value', '%s\n', value)
1733 else:
1733 else:
1734 fm.write('name value', '%s=%s\n', entryname, value)
1734 fm.write('name value', '%s=%s\n', entryname, value)
1735 matched = True
1735 matched = True
1736 fm.end()
1736 fm.end()
1737 if matched:
1737 if matched:
1738 return 0
1738 return 0
1739 return 1
1739 return 1
1740
1740
1741 @command('copy|cp',
1741 @command('copy|cp',
1742 [('A', 'after', None, _('record a copy that has already occurred')),
1742 [('A', 'after', None, _('record a copy that has already occurred')),
1743 ('f', 'force', None, _('forcibly copy over an existing managed file')),
1743 ('f', 'force', None, _('forcibly copy over an existing managed file')),
1744 ] + walkopts + dryrunopts,
1744 ] + walkopts + dryrunopts,
1745 _('[OPTION]... [SOURCE]... DEST'))
1745 _('[OPTION]... [SOURCE]... DEST'))
1746 def copy(ui, repo, *pats, **opts):
1746 def copy(ui, repo, *pats, **opts):
1747 """mark files as copied for the next commit
1747 """mark files as copied for the next commit
1748
1748
1749 Mark dest as having copies of source files. If dest is a
1749 Mark dest as having copies of source files. If dest is a
1750 directory, copies are put in that directory. If dest is a file,
1750 directory, copies are put in that directory. If dest is a file,
1751 the source must be a single file.
1751 the source must be a single file.
1752
1752
1753 By default, this command copies the contents of files as they
1753 By default, this command copies the contents of files as they
1754 exist in the working directory. If invoked with -A/--after, the
1754 exist in the working directory. If invoked with -A/--after, the
1755 operation is recorded, but no copying is performed.
1755 operation is recorded, but no copying is performed.
1756
1756
1757 This command takes effect with the next commit. To undo a copy
1757 This command takes effect with the next commit. To undo a copy
1758 before that, see :hg:`revert`.
1758 before that, see :hg:`revert`.
1759
1759
1760 Returns 0 on success, 1 if errors are encountered.
1760 Returns 0 on success, 1 if errors are encountered.
1761 """
1761 """
1762 opts = pycompat.byteskwargs(opts)
1762 opts = pycompat.byteskwargs(opts)
1763 with repo.wlock(False):
1763 with repo.wlock(False):
1764 return cmdutil.copy(ui, repo, pats, opts)
1764 return cmdutil.copy(ui, repo, pats, opts)
1765
1765
1766 @command('debugcommands', [], _('[COMMAND]'), norepo=True)
1766 @command('debugcommands', [], _('[COMMAND]'), norepo=True)
1767 def debugcommands(ui, cmd='', *args):
1767 def debugcommands(ui, cmd='', *args):
1768 """list all available commands and options"""
1768 """list all available commands and options"""
1769 for cmd, vals in sorted(table.iteritems()):
1769 for cmd, vals in sorted(table.iteritems()):
1770 cmd = cmd.split('|')[0].strip('^')
1770 cmd = cmd.split('|')[0].strip('^')
1771 opts = ', '.join([i[1] for i in vals[1]])
1771 opts = ', '.join([i[1] for i in vals[1]])
1772 ui.write('%s: %s\n' % (cmd, opts))
1772 ui.write('%s: %s\n' % (cmd, opts))
1773
1773
1774 @command('debugcomplete',
1774 @command('debugcomplete',
1775 [('o', 'options', None, _('show the command options'))],
1775 [('o', 'options', None, _('show the command options'))],
1776 _('[-o] CMD'),
1776 _('[-o] CMD'),
1777 norepo=True)
1777 norepo=True)
1778 def debugcomplete(ui, cmd='', **opts):
1778 def debugcomplete(ui, cmd='', **opts):
1779 """returns the completion list associated with the given command"""
1779 """returns the completion list associated with the given command"""
1780
1780
1781 if opts.get(r'options'):
1781 if opts.get(r'options'):
1782 options = []
1782 options = []
1783 otables = [globalopts]
1783 otables = [globalopts]
1784 if cmd:
1784 if cmd:
1785 aliases, entry = cmdutil.findcmd(cmd, table, False)
1785 aliases, entry = cmdutil.findcmd(cmd, table, False)
1786 otables.append(entry[1])
1786 otables.append(entry[1])
1787 for t in otables:
1787 for t in otables:
1788 for o in t:
1788 for o in t:
1789 if "(DEPRECATED)" in o[3]:
1789 if "(DEPRECATED)" in o[3]:
1790 continue
1790 continue
1791 if o[0]:
1791 if o[0]:
1792 options.append('-%s' % o[0])
1792 options.append('-%s' % o[0])
1793 options.append('--%s' % o[1])
1793 options.append('--%s' % o[1])
1794 ui.write("%s\n" % "\n".join(options))
1794 ui.write("%s\n" % "\n".join(options))
1795 return
1795 return
1796
1796
1797 cmdlist, unused_allcmds = cmdutil.findpossible(cmd, table)
1797 cmdlist, unused_allcmds = cmdutil.findpossible(cmd, table)
1798 if ui.verbose:
1798 if ui.verbose:
1799 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
1799 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
1800 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
1800 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
1801
1801
1802 @command('^diff',
1802 @command('^diff',
1803 [('r', 'rev', [], _('revision'), _('REV')),
1803 [('r', 'rev', [], _('revision'), _('REV')),
1804 ('c', 'change', '', _('change made by revision'), _('REV'))
1804 ('c', 'change', '', _('change made by revision'), _('REV'))
1805 ] + diffopts + diffopts2 + walkopts + subrepoopts,
1805 ] + diffopts + diffopts2 + walkopts + subrepoopts,
1806 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'),
1806 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'),
1807 inferrepo=True, cmdtype=readonly)
1807 inferrepo=True, cmdtype=readonly)
1808 def diff(ui, repo, *pats, **opts):
1808 def diff(ui, repo, *pats, **opts):
1809 """diff repository (or selected files)
1809 """diff repository (or selected files)
1810
1810
1811 Show differences between revisions for the specified files.
1811 Show differences between revisions for the specified files.
1812
1812
1813 Differences between files are shown using the unified diff format.
1813 Differences between files are shown using the unified diff format.
1814
1814
1815 .. note::
1815 .. note::
1816
1816
1817 :hg:`diff` may generate unexpected results for merges, as it will
1817 :hg:`diff` may generate unexpected results for merges, as it will
1818 default to comparing against the working directory's first
1818 default to comparing against the working directory's first
1819 parent changeset if no revisions are specified.
1819 parent changeset if no revisions are specified.
1820
1820
1821 When two revision arguments are given, then changes are shown
1821 When two revision arguments are given, then changes are shown
1822 between those revisions. If only one revision is specified then
1822 between those revisions. If only one revision is specified then
1823 that revision is compared to the working directory, and, when no
1823 that revision is compared to the working directory, and, when no
1824 revisions are specified, the working directory files are compared
1824 revisions are specified, the working directory files are compared
1825 to its first parent.
1825 to its first parent.
1826
1826
1827 Alternatively you can specify -c/--change with a revision to see
1827 Alternatively you can specify -c/--change with a revision to see
1828 the changes in that changeset relative to its first parent.
1828 the changes in that changeset relative to its first parent.
1829
1829
1830 Without the -a/--text option, diff will avoid generating diffs of
1830 Without the -a/--text option, diff will avoid generating diffs of
1831 files it detects as binary. With -a, diff will generate a diff
1831 files it detects as binary. With -a, diff will generate a diff
1832 anyway, probably with undesirable results.
1832 anyway, probably with undesirable results.
1833
1833
1834 Use the -g/--git option to generate diffs in the git extended diff
1834 Use the -g/--git option to generate diffs in the git extended diff
1835 format. For more information, read :hg:`help diffs`.
1835 format. For more information, read :hg:`help diffs`.
1836
1836
1837 .. container:: verbose
1837 .. container:: verbose
1838
1838
1839 Examples:
1839 Examples:
1840
1840
1841 - compare a file in the current working directory to its parent::
1841 - compare a file in the current working directory to its parent::
1842
1842
1843 hg diff foo.c
1843 hg diff foo.c
1844
1844
1845 - compare two historical versions of a directory, with rename info::
1845 - compare two historical versions of a directory, with rename info::
1846
1846
1847 hg diff --git -r 1.0:1.2 lib/
1847 hg diff --git -r 1.0:1.2 lib/
1848
1848
1849 - get change stats relative to the last change on some date::
1849 - get change stats relative to the last change on some date::
1850
1850
1851 hg diff --stat -r "date('may 2')"
1851 hg diff --stat -r "date('may 2')"
1852
1852
1853 - diff all newly-added files that contain a keyword::
1853 - diff all newly-added files that contain a keyword::
1854
1854
1855 hg diff "set:added() and grep(GNU)"
1855 hg diff "set:added() and grep(GNU)"
1856
1856
1857 - compare a revision and its parents::
1857 - compare a revision and its parents::
1858
1858
1859 hg diff -c 9353 # compare against first parent
1859 hg diff -c 9353 # compare against first parent
1860 hg diff -r 9353^:9353 # same using revset syntax
1860 hg diff -r 9353^:9353 # same using revset syntax
1861 hg diff -r 9353^2:9353 # compare against the second parent
1861 hg diff -r 9353^2:9353 # compare against the second parent
1862
1862
1863 Returns 0 on success.
1863 Returns 0 on success.
1864 """
1864 """
1865
1865
1866 opts = pycompat.byteskwargs(opts)
1866 opts = pycompat.byteskwargs(opts)
1867 revs = opts.get('rev')
1867 revs = opts.get('rev')
1868 change = opts.get('change')
1868 change = opts.get('change')
1869 stat = opts.get('stat')
1869 stat = opts.get('stat')
1870 reverse = opts.get('reverse')
1870 reverse = opts.get('reverse')
1871
1871
1872 if revs and change:
1872 if revs and change:
1873 msg = _('cannot specify --rev and --change at the same time')
1873 msg = _('cannot specify --rev and --change at the same time')
1874 raise error.Abort(msg)
1874 raise error.Abort(msg)
1875 elif change:
1875 elif change:
1876 repo = scmutil.unhidehashlikerevs(repo, [change], 'nowarn')
1876 repo = scmutil.unhidehashlikerevs(repo, [change], 'nowarn')
1877 node2 = scmutil.revsingle(repo, change, None).node()
1877 node2 = scmutil.revsingle(repo, change, None).node()
1878 node1 = repo[node2].p1().node()
1878 node1 = repo[node2].p1().node()
1879 else:
1879 else:
1880 repo = scmutil.unhidehashlikerevs(repo, revs, 'nowarn')
1880 repo = scmutil.unhidehashlikerevs(repo, revs, 'nowarn')
1881 node1, node2 = scmutil.revpair(repo, revs)
1881 node1, node2 = scmutil.revpairnodes(repo, revs)
1882
1882
1883 if reverse:
1883 if reverse:
1884 node1, node2 = node2, node1
1884 node1, node2 = node2, node1
1885
1885
1886 diffopts = patch.diffallopts(ui, opts)
1886 diffopts = patch.diffallopts(ui, opts)
1887 m = scmutil.match(repo[node2], pats, opts)
1887 m = scmutil.match(repo[node2], pats, opts)
1888 ui.pager('diff')
1888 ui.pager('diff')
1889 logcmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
1889 logcmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
1890 listsubrepos=opts.get('subrepos'),
1890 listsubrepos=opts.get('subrepos'),
1891 root=opts.get('root'))
1891 root=opts.get('root'))
1892
1892
1893 @command('^export',
1893 @command('^export',
1894 [('o', 'output', '',
1894 [('o', 'output', '',
1895 _('print output to file with formatted name'), _('FORMAT')),
1895 _('print output to file with formatted name'), _('FORMAT')),
1896 ('', 'switch-parent', None, _('diff against the second parent')),
1896 ('', 'switch-parent', None, _('diff against the second parent')),
1897 ('r', 'rev', [], _('revisions to export'), _('REV')),
1897 ('r', 'rev', [], _('revisions to export'), _('REV')),
1898 ] + diffopts,
1898 ] + diffopts,
1899 _('[OPTION]... [-o OUTFILESPEC] [-r] [REV]...'), cmdtype=readonly)
1899 _('[OPTION]... [-o OUTFILESPEC] [-r] [REV]...'), cmdtype=readonly)
1900 def export(ui, repo, *changesets, **opts):
1900 def export(ui, repo, *changesets, **opts):
1901 """dump the header and diffs for one or more changesets
1901 """dump the header and diffs for one or more changesets
1902
1902
1903 Print the changeset header and diffs for one or more revisions.
1903 Print the changeset header and diffs for one or more revisions.
1904 If no revision is given, the parent of the working directory is used.
1904 If no revision is given, the parent of the working directory is used.
1905
1905
1906 The information shown in the changeset header is: author, date,
1906 The information shown in the changeset header is: author, date,
1907 branch name (if non-default), changeset hash, parent(s) and commit
1907 branch name (if non-default), changeset hash, parent(s) and commit
1908 comment.
1908 comment.
1909
1909
1910 .. note::
1910 .. note::
1911
1911
1912 :hg:`export` may generate unexpected diff output for merge
1912 :hg:`export` may generate unexpected diff output for merge
1913 changesets, as it will compare the merge changeset against its
1913 changesets, as it will compare the merge changeset against its
1914 first parent only.
1914 first parent only.
1915
1915
1916 Output may be to a file, in which case the name of the file is
1916 Output may be to a file, in which case the name of the file is
1917 given using a template string. See :hg:`help templates`. In addition
1917 given using a template string. See :hg:`help templates`. In addition
1918 to the common template keywords, the following formatting rules are
1918 to the common template keywords, the following formatting rules are
1919 supported:
1919 supported:
1920
1920
1921 :``%%``: literal "%" character
1921 :``%%``: literal "%" character
1922 :``%H``: changeset hash (40 hexadecimal digits)
1922 :``%H``: changeset hash (40 hexadecimal digits)
1923 :``%N``: number of patches being generated
1923 :``%N``: number of patches being generated
1924 :``%R``: changeset revision number
1924 :``%R``: changeset revision number
1925 :``%b``: basename of the exporting repository
1925 :``%b``: basename of the exporting repository
1926 :``%h``: short-form changeset hash (12 hexadecimal digits)
1926 :``%h``: short-form changeset hash (12 hexadecimal digits)
1927 :``%m``: first line of the commit message (only alphanumeric characters)
1927 :``%m``: first line of the commit message (only alphanumeric characters)
1928 :``%n``: zero-padded sequence number, starting at 1
1928 :``%n``: zero-padded sequence number, starting at 1
1929 :``%r``: zero-padded changeset revision number
1929 :``%r``: zero-padded changeset revision number
1930 :``\\``: literal "\\" character
1930 :``\\``: literal "\\" character
1931
1931
1932 Without the -a/--text option, export will avoid generating diffs
1932 Without the -a/--text option, export will avoid generating diffs
1933 of files it detects as binary. With -a, export will generate a
1933 of files it detects as binary. With -a, export will generate a
1934 diff anyway, probably with undesirable results.
1934 diff anyway, probably with undesirable results.
1935
1935
1936 Use the -g/--git option to generate diffs in the git extended diff
1936 Use the -g/--git option to generate diffs in the git extended diff
1937 format. See :hg:`help diffs` for more information.
1937 format. See :hg:`help diffs` for more information.
1938
1938
1939 With the --switch-parent option, the diff will be against the
1939 With the --switch-parent option, the diff will be against the
1940 second parent. It can be useful to review a merge.
1940 second parent. It can be useful to review a merge.
1941
1941
1942 .. container:: verbose
1942 .. container:: verbose
1943
1943
1944 Examples:
1944 Examples:
1945
1945
1946 - use export and import to transplant a bugfix to the current
1946 - use export and import to transplant a bugfix to the current
1947 branch::
1947 branch::
1948
1948
1949 hg export -r 9353 | hg import -
1949 hg export -r 9353 | hg import -
1950
1950
1951 - export all the changesets between two revisions to a file with
1951 - export all the changesets between two revisions to a file with
1952 rename information::
1952 rename information::
1953
1953
1954 hg export --git -r 123:150 > changes.txt
1954 hg export --git -r 123:150 > changes.txt
1955
1955
1956 - split outgoing changes into a series of patches with
1956 - split outgoing changes into a series of patches with
1957 descriptive names::
1957 descriptive names::
1958
1958
1959 hg export -r "outgoing()" -o "%n-%m.patch"
1959 hg export -r "outgoing()" -o "%n-%m.patch"
1960
1960
1961 Returns 0 on success.
1961 Returns 0 on success.
1962 """
1962 """
1963 opts = pycompat.byteskwargs(opts)
1963 opts = pycompat.byteskwargs(opts)
1964 changesets += tuple(opts.get('rev', []))
1964 changesets += tuple(opts.get('rev', []))
1965 if not changesets:
1965 if not changesets:
1966 changesets = ['.']
1966 changesets = ['.']
1967 repo = scmutil.unhidehashlikerevs(repo, changesets, 'nowarn')
1967 repo = scmutil.unhidehashlikerevs(repo, changesets, 'nowarn')
1968 revs = scmutil.revrange(repo, changesets)
1968 revs = scmutil.revrange(repo, changesets)
1969 if not revs:
1969 if not revs:
1970 raise error.Abort(_("export requires at least one changeset"))
1970 raise error.Abort(_("export requires at least one changeset"))
1971 if len(revs) > 1:
1971 if len(revs) > 1:
1972 ui.note(_('exporting patches:\n'))
1972 ui.note(_('exporting patches:\n'))
1973 else:
1973 else:
1974 ui.note(_('exporting patch:\n'))
1974 ui.note(_('exporting patch:\n'))
1975 ui.pager('export')
1975 ui.pager('export')
1976 cmdutil.export(repo, revs, fntemplate=opts.get('output'),
1976 cmdutil.export(repo, revs, fntemplate=opts.get('output'),
1977 switch_parent=opts.get('switch_parent'),
1977 switch_parent=opts.get('switch_parent'),
1978 opts=patch.diffallopts(ui, opts))
1978 opts=patch.diffallopts(ui, opts))
1979
1979
1980 @command('files',
1980 @command('files',
1981 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
1981 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
1982 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
1982 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
1983 ] + walkopts + formatteropts + subrepoopts,
1983 ] + walkopts + formatteropts + subrepoopts,
1984 _('[OPTION]... [FILE]...'), cmdtype=readonly)
1984 _('[OPTION]... [FILE]...'), cmdtype=readonly)
1985 def files(ui, repo, *pats, **opts):
1985 def files(ui, repo, *pats, **opts):
1986 """list tracked files
1986 """list tracked files
1987
1987
1988 Print files under Mercurial control in the working directory or
1988 Print files under Mercurial control in the working directory or
1989 specified revision for given files (excluding removed files).
1989 specified revision for given files (excluding removed files).
1990 Files can be specified as filenames or filesets.
1990 Files can be specified as filenames or filesets.
1991
1991
1992 If no files are given to match, this command prints the names
1992 If no files are given to match, this command prints the names
1993 of all files under Mercurial control.
1993 of all files under Mercurial control.
1994
1994
1995 .. container:: verbose
1995 .. container:: verbose
1996
1996
1997 Examples:
1997 Examples:
1998
1998
1999 - list all files under the current directory::
1999 - list all files under the current directory::
2000
2000
2001 hg files .
2001 hg files .
2002
2002
2003 - shows sizes and flags for current revision::
2003 - shows sizes and flags for current revision::
2004
2004
2005 hg files -vr .
2005 hg files -vr .
2006
2006
2007 - list all files named README::
2007 - list all files named README::
2008
2008
2009 hg files -I "**/README"
2009 hg files -I "**/README"
2010
2010
2011 - list all binary files::
2011 - list all binary files::
2012
2012
2013 hg files "set:binary()"
2013 hg files "set:binary()"
2014
2014
2015 - find files containing a regular expression::
2015 - find files containing a regular expression::
2016
2016
2017 hg files "set:grep('bob')"
2017 hg files "set:grep('bob')"
2018
2018
2019 - search tracked file contents with xargs and grep::
2019 - search tracked file contents with xargs and grep::
2020
2020
2021 hg files -0 | xargs -0 grep foo
2021 hg files -0 | xargs -0 grep foo
2022
2022
2023 See :hg:`help patterns` and :hg:`help filesets` for more information
2023 See :hg:`help patterns` and :hg:`help filesets` for more information
2024 on specifying file patterns.
2024 on specifying file patterns.
2025
2025
2026 Returns 0 if a match is found, 1 otherwise.
2026 Returns 0 if a match is found, 1 otherwise.
2027
2027
2028 """
2028 """
2029
2029
2030 opts = pycompat.byteskwargs(opts)
2030 opts = pycompat.byteskwargs(opts)
2031 rev = opts.get('rev')
2031 rev = opts.get('rev')
2032 if rev:
2032 if rev:
2033 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
2033 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
2034 ctx = scmutil.revsingle(repo, rev, None)
2034 ctx = scmutil.revsingle(repo, rev, None)
2035
2035
2036 end = '\n'
2036 end = '\n'
2037 if opts.get('print0'):
2037 if opts.get('print0'):
2038 end = '\0'
2038 end = '\0'
2039 fmt = '%s' + end
2039 fmt = '%s' + end
2040
2040
2041 m = scmutil.match(ctx, pats, opts)
2041 m = scmutil.match(ctx, pats, opts)
2042 ui.pager('files')
2042 ui.pager('files')
2043 with ui.formatter('files', opts) as fm:
2043 with ui.formatter('files', opts) as fm:
2044 return cmdutil.files(ui, ctx, m, fm, fmt, opts.get('subrepos'))
2044 return cmdutil.files(ui, ctx, m, fm, fmt, opts.get('subrepos'))
2045
2045
2046 @command(
2046 @command(
2047 '^forget',
2047 '^forget',
2048 walkopts + dryrunopts,
2048 walkopts + dryrunopts,
2049 _('[OPTION]... FILE...'), inferrepo=True)
2049 _('[OPTION]... FILE...'), inferrepo=True)
2050 def forget(ui, repo, *pats, **opts):
2050 def forget(ui, repo, *pats, **opts):
2051 """forget the specified files on the next commit
2051 """forget the specified files on the next commit
2052
2052
2053 Mark the specified files so they will no longer be tracked
2053 Mark the specified files so they will no longer be tracked
2054 after the next commit.
2054 after the next commit.
2055
2055
2056 This only removes files from the current branch, not from the
2056 This only removes files from the current branch, not from the
2057 entire project history, and it does not delete them from the
2057 entire project history, and it does not delete them from the
2058 working directory.
2058 working directory.
2059
2059
2060 To delete the file from the working directory, see :hg:`remove`.
2060 To delete the file from the working directory, see :hg:`remove`.
2061
2061
2062 To undo a forget before the next commit, see :hg:`add`.
2062 To undo a forget before the next commit, see :hg:`add`.
2063
2063
2064 .. container:: verbose
2064 .. container:: verbose
2065
2065
2066 Examples:
2066 Examples:
2067
2067
2068 - forget newly-added binary files::
2068 - forget newly-added binary files::
2069
2069
2070 hg forget "set:added() and binary()"
2070 hg forget "set:added() and binary()"
2071
2071
2072 - forget files that would be excluded by .hgignore::
2072 - forget files that would be excluded by .hgignore::
2073
2073
2074 hg forget "set:hgignore()"
2074 hg forget "set:hgignore()"
2075
2075
2076 Returns 0 on success.
2076 Returns 0 on success.
2077 """
2077 """
2078
2078
2079 opts = pycompat.byteskwargs(opts)
2079 opts = pycompat.byteskwargs(opts)
2080 if not pats:
2080 if not pats:
2081 raise error.Abort(_('no files specified'))
2081 raise error.Abort(_('no files specified'))
2082
2082
2083 m = scmutil.match(repo[None], pats, opts)
2083 m = scmutil.match(repo[None], pats, opts)
2084 dryrun = opts.get(r'dry_run')
2084 dryrun = opts.get(r'dry_run')
2085 rejected = cmdutil.forget(ui, repo, m, prefix="",
2085 rejected = cmdutil.forget(ui, repo, m, prefix="",
2086 explicitonly=False, dryrun=dryrun)[0]
2086 explicitonly=False, dryrun=dryrun)[0]
2087 return rejected and 1 or 0
2087 return rejected and 1 or 0
2088
2088
2089 @command(
2089 @command(
2090 'graft',
2090 'graft',
2091 [('r', 'rev', [], _('revisions to graft'), _('REV')),
2091 [('r', 'rev', [], _('revisions to graft'), _('REV')),
2092 ('c', 'continue', False, _('resume interrupted graft')),
2092 ('c', 'continue', False, _('resume interrupted graft')),
2093 ('e', 'edit', False, _('invoke editor on commit messages')),
2093 ('e', 'edit', False, _('invoke editor on commit messages')),
2094 ('', 'log', None, _('append graft info to log message')),
2094 ('', 'log', None, _('append graft info to log message')),
2095 ('f', 'force', False, _('force graft')),
2095 ('f', 'force', False, _('force graft')),
2096 ('D', 'currentdate', False,
2096 ('D', 'currentdate', False,
2097 _('record the current date as commit date')),
2097 _('record the current date as commit date')),
2098 ('U', 'currentuser', False,
2098 ('U', 'currentuser', False,
2099 _('record the current user as committer'), _('DATE'))]
2099 _('record the current user as committer'), _('DATE'))]
2100 + commitopts2 + mergetoolopts + dryrunopts,
2100 + commitopts2 + mergetoolopts + dryrunopts,
2101 _('[OPTION]... [-r REV]... REV...'))
2101 _('[OPTION]... [-r REV]... REV...'))
2102 def graft(ui, repo, *revs, **opts):
2102 def graft(ui, repo, *revs, **opts):
2103 '''copy changes from other branches onto the current branch
2103 '''copy changes from other branches onto the current branch
2104
2104
2105 This command uses Mercurial's merge logic to copy individual
2105 This command uses Mercurial's merge logic to copy individual
2106 changes from other branches without merging branches in the
2106 changes from other branches without merging branches in the
2107 history graph. This is sometimes known as 'backporting' or
2107 history graph. This is sometimes known as 'backporting' or
2108 'cherry-picking'. By default, graft will copy user, date, and
2108 'cherry-picking'. By default, graft will copy user, date, and
2109 description from the source changesets.
2109 description from the source changesets.
2110
2110
2111 Changesets that are ancestors of the current revision, that have
2111 Changesets that are ancestors of the current revision, that have
2112 already been grafted, or that are merges will be skipped.
2112 already been grafted, or that are merges will be skipped.
2113
2113
2114 If --log is specified, log messages will have a comment appended
2114 If --log is specified, log messages will have a comment appended
2115 of the form::
2115 of the form::
2116
2116
2117 (grafted from CHANGESETHASH)
2117 (grafted from CHANGESETHASH)
2118
2118
2119 If --force is specified, revisions will be grafted even if they
2119 If --force is specified, revisions will be grafted even if they
2120 are already ancestors of, or have been grafted to, the destination.
2120 are already ancestors of, or have been grafted to, the destination.
2121 This is useful when the revisions have since been backed out.
2121 This is useful when the revisions have since been backed out.
2122
2122
2123 If a graft merge results in conflicts, the graft process is
2123 If a graft merge results in conflicts, the graft process is
2124 interrupted so that the current merge can be manually resolved.
2124 interrupted so that the current merge can be manually resolved.
2125 Once all conflicts are addressed, the graft process can be
2125 Once all conflicts are addressed, the graft process can be
2126 continued with the -c/--continue option.
2126 continued with the -c/--continue option.
2127
2127
2128 .. note::
2128 .. note::
2129
2129
2130 The -c/--continue option does not reapply earlier options, except
2130 The -c/--continue option does not reapply earlier options, except
2131 for --force.
2131 for --force.
2132
2132
2133 .. container:: verbose
2133 .. container:: verbose
2134
2134
2135 Examples:
2135 Examples:
2136
2136
2137 - copy a single change to the stable branch and edit its description::
2137 - copy a single change to the stable branch and edit its description::
2138
2138
2139 hg update stable
2139 hg update stable
2140 hg graft --edit 9393
2140 hg graft --edit 9393
2141
2141
2142 - graft a range of changesets with one exception, updating dates::
2142 - graft a range of changesets with one exception, updating dates::
2143
2143
2144 hg graft -D "2085::2093 and not 2091"
2144 hg graft -D "2085::2093 and not 2091"
2145
2145
2146 - continue a graft after resolving conflicts::
2146 - continue a graft after resolving conflicts::
2147
2147
2148 hg graft -c
2148 hg graft -c
2149
2149
2150 - show the source of a grafted changeset::
2150 - show the source of a grafted changeset::
2151
2151
2152 hg log --debug -r .
2152 hg log --debug -r .
2153
2153
2154 - show revisions sorted by date::
2154 - show revisions sorted by date::
2155
2155
2156 hg log -r "sort(all(), date)"
2156 hg log -r "sort(all(), date)"
2157
2157
2158 See :hg:`help revisions` for more about specifying revisions.
2158 See :hg:`help revisions` for more about specifying revisions.
2159
2159
2160 Returns 0 on successful completion.
2160 Returns 0 on successful completion.
2161 '''
2161 '''
2162 with repo.wlock():
2162 with repo.wlock():
2163 return _dograft(ui, repo, *revs, **opts)
2163 return _dograft(ui, repo, *revs, **opts)
2164
2164
2165 def _dograft(ui, repo, *revs, **opts):
2165 def _dograft(ui, repo, *revs, **opts):
2166 opts = pycompat.byteskwargs(opts)
2166 opts = pycompat.byteskwargs(opts)
2167 if revs and opts.get('rev'):
2167 if revs and opts.get('rev'):
2168 ui.warn(_('warning: inconsistent use of --rev might give unexpected '
2168 ui.warn(_('warning: inconsistent use of --rev might give unexpected '
2169 'revision ordering!\n'))
2169 'revision ordering!\n'))
2170
2170
2171 revs = list(revs)
2171 revs = list(revs)
2172 revs.extend(opts.get('rev'))
2172 revs.extend(opts.get('rev'))
2173
2173
2174 if not opts.get('user') and opts.get('currentuser'):
2174 if not opts.get('user') and opts.get('currentuser'):
2175 opts['user'] = ui.username()
2175 opts['user'] = ui.username()
2176 if not opts.get('date') and opts.get('currentdate'):
2176 if not opts.get('date') and opts.get('currentdate'):
2177 opts['date'] = "%d %d" % dateutil.makedate()
2177 opts['date'] = "%d %d" % dateutil.makedate()
2178
2178
2179 editor = cmdutil.getcommiteditor(editform='graft',
2179 editor = cmdutil.getcommiteditor(editform='graft',
2180 **pycompat.strkwargs(opts))
2180 **pycompat.strkwargs(opts))
2181
2181
2182 cont = False
2182 cont = False
2183 if opts.get('continue'):
2183 if opts.get('continue'):
2184 cont = True
2184 cont = True
2185 if revs:
2185 if revs:
2186 raise error.Abort(_("can't specify --continue and revisions"))
2186 raise error.Abort(_("can't specify --continue and revisions"))
2187 # read in unfinished revisions
2187 # read in unfinished revisions
2188 try:
2188 try:
2189 nodes = repo.vfs.read('graftstate').splitlines()
2189 nodes = repo.vfs.read('graftstate').splitlines()
2190 revs = [repo[node].rev() for node in nodes]
2190 revs = [repo[node].rev() for node in nodes]
2191 except IOError as inst:
2191 except IOError as inst:
2192 if inst.errno != errno.ENOENT:
2192 if inst.errno != errno.ENOENT:
2193 raise
2193 raise
2194 cmdutil.wrongtooltocontinue(repo, _('graft'))
2194 cmdutil.wrongtooltocontinue(repo, _('graft'))
2195 else:
2195 else:
2196 if not revs:
2196 if not revs:
2197 raise error.Abort(_('no revisions specified'))
2197 raise error.Abort(_('no revisions specified'))
2198 cmdutil.checkunfinished(repo)
2198 cmdutil.checkunfinished(repo)
2199 cmdutil.bailifchanged(repo)
2199 cmdutil.bailifchanged(repo)
2200 revs = scmutil.revrange(repo, revs)
2200 revs = scmutil.revrange(repo, revs)
2201
2201
2202 skipped = set()
2202 skipped = set()
2203 # check for merges
2203 # check for merges
2204 for rev in repo.revs('%ld and merge()', revs):
2204 for rev in repo.revs('%ld and merge()', revs):
2205 ui.warn(_('skipping ungraftable merge revision %d\n') % rev)
2205 ui.warn(_('skipping ungraftable merge revision %d\n') % rev)
2206 skipped.add(rev)
2206 skipped.add(rev)
2207 revs = [r for r in revs if r not in skipped]
2207 revs = [r for r in revs if r not in skipped]
2208 if not revs:
2208 if not revs:
2209 return -1
2209 return -1
2210
2210
2211 # Don't check in the --continue case, in effect retaining --force across
2211 # Don't check in the --continue case, in effect retaining --force across
2212 # --continues. That's because without --force, any revisions we decided to
2212 # --continues. That's because without --force, any revisions we decided to
2213 # skip would have been filtered out here, so they wouldn't have made their
2213 # skip would have been filtered out here, so they wouldn't have made their
2214 # way to the graftstate. With --force, any revisions we would have otherwise
2214 # way to the graftstate. With --force, any revisions we would have otherwise
2215 # skipped would not have been filtered out, and if they hadn't been applied
2215 # skipped would not have been filtered out, and if they hadn't been applied
2216 # already, they'd have been in the graftstate.
2216 # already, they'd have been in the graftstate.
2217 if not (cont or opts.get('force')):
2217 if not (cont or opts.get('force')):
2218 # check for ancestors of dest branch
2218 # check for ancestors of dest branch
2219 crev = repo['.'].rev()
2219 crev = repo['.'].rev()
2220 ancestors = repo.changelog.ancestors([crev], inclusive=True)
2220 ancestors = repo.changelog.ancestors([crev], inclusive=True)
2221 # XXX make this lazy in the future
2221 # XXX make this lazy in the future
2222 # don't mutate while iterating, create a copy
2222 # don't mutate while iterating, create a copy
2223 for rev in list(revs):
2223 for rev in list(revs):
2224 if rev in ancestors:
2224 if rev in ancestors:
2225 ui.warn(_('skipping ancestor revision %d:%s\n') %
2225 ui.warn(_('skipping ancestor revision %d:%s\n') %
2226 (rev, repo[rev]))
2226 (rev, repo[rev]))
2227 # XXX remove on list is slow
2227 # XXX remove on list is slow
2228 revs.remove(rev)
2228 revs.remove(rev)
2229 if not revs:
2229 if not revs:
2230 return -1
2230 return -1
2231
2231
2232 # analyze revs for earlier grafts
2232 # analyze revs for earlier grafts
2233 ids = {}
2233 ids = {}
2234 for ctx in repo.set("%ld", revs):
2234 for ctx in repo.set("%ld", revs):
2235 ids[ctx.hex()] = ctx.rev()
2235 ids[ctx.hex()] = ctx.rev()
2236 n = ctx.extra().get('source')
2236 n = ctx.extra().get('source')
2237 if n:
2237 if n:
2238 ids[n] = ctx.rev()
2238 ids[n] = ctx.rev()
2239
2239
2240 # check ancestors for earlier grafts
2240 # check ancestors for earlier grafts
2241 ui.debug('scanning for duplicate grafts\n')
2241 ui.debug('scanning for duplicate grafts\n')
2242
2242
2243 # The only changesets we can be sure doesn't contain grafts of any
2243 # The only changesets we can be sure doesn't contain grafts of any
2244 # revs, are the ones that are common ancestors of *all* revs:
2244 # revs, are the ones that are common ancestors of *all* revs:
2245 for rev in repo.revs('only(%d,ancestor(%ld))', crev, revs):
2245 for rev in repo.revs('only(%d,ancestor(%ld))', crev, revs):
2246 ctx = repo[rev]
2246 ctx = repo[rev]
2247 n = ctx.extra().get('source')
2247 n = ctx.extra().get('source')
2248 if n in ids:
2248 if n in ids:
2249 try:
2249 try:
2250 r = repo[n].rev()
2250 r = repo[n].rev()
2251 except error.RepoLookupError:
2251 except error.RepoLookupError:
2252 r = None
2252 r = None
2253 if r in revs:
2253 if r in revs:
2254 ui.warn(_('skipping revision %d:%s '
2254 ui.warn(_('skipping revision %d:%s '
2255 '(already grafted to %d:%s)\n')
2255 '(already grafted to %d:%s)\n')
2256 % (r, repo[r], rev, ctx))
2256 % (r, repo[r], rev, ctx))
2257 revs.remove(r)
2257 revs.remove(r)
2258 elif ids[n] in revs:
2258 elif ids[n] in revs:
2259 if r is None:
2259 if r is None:
2260 ui.warn(_('skipping already grafted revision %d:%s '
2260 ui.warn(_('skipping already grafted revision %d:%s '
2261 '(%d:%s also has unknown origin %s)\n')
2261 '(%d:%s also has unknown origin %s)\n')
2262 % (ids[n], repo[ids[n]], rev, ctx, n[:12]))
2262 % (ids[n], repo[ids[n]], rev, ctx, n[:12]))
2263 else:
2263 else:
2264 ui.warn(_('skipping already grafted revision %d:%s '
2264 ui.warn(_('skipping already grafted revision %d:%s '
2265 '(%d:%s also has origin %d:%s)\n')
2265 '(%d:%s also has origin %d:%s)\n')
2266 % (ids[n], repo[ids[n]], rev, ctx, r, n[:12]))
2266 % (ids[n], repo[ids[n]], rev, ctx, r, n[:12]))
2267 revs.remove(ids[n])
2267 revs.remove(ids[n])
2268 elif ctx.hex() in ids:
2268 elif ctx.hex() in ids:
2269 r = ids[ctx.hex()]
2269 r = ids[ctx.hex()]
2270 ui.warn(_('skipping already grafted revision %d:%s '
2270 ui.warn(_('skipping already grafted revision %d:%s '
2271 '(was grafted from %d:%s)\n') %
2271 '(was grafted from %d:%s)\n') %
2272 (r, repo[r], rev, ctx))
2272 (r, repo[r], rev, ctx))
2273 revs.remove(r)
2273 revs.remove(r)
2274 if not revs:
2274 if not revs:
2275 return -1
2275 return -1
2276
2276
2277 for pos, ctx in enumerate(repo.set("%ld", revs)):
2277 for pos, ctx in enumerate(repo.set("%ld", revs)):
2278 desc = '%d:%s "%s"' % (ctx.rev(), ctx,
2278 desc = '%d:%s "%s"' % (ctx.rev(), ctx,
2279 ctx.description().split('\n', 1)[0])
2279 ctx.description().split('\n', 1)[0])
2280 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
2280 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
2281 if names:
2281 if names:
2282 desc += ' (%s)' % ' '.join(names)
2282 desc += ' (%s)' % ' '.join(names)
2283 ui.status(_('grafting %s\n') % desc)
2283 ui.status(_('grafting %s\n') % desc)
2284 if opts.get('dry_run'):
2284 if opts.get('dry_run'):
2285 continue
2285 continue
2286
2286
2287 source = ctx.extra().get('source')
2287 source = ctx.extra().get('source')
2288 extra = {}
2288 extra = {}
2289 if source:
2289 if source:
2290 extra['source'] = source
2290 extra['source'] = source
2291 extra['intermediate-source'] = ctx.hex()
2291 extra['intermediate-source'] = ctx.hex()
2292 else:
2292 else:
2293 extra['source'] = ctx.hex()
2293 extra['source'] = ctx.hex()
2294 user = ctx.user()
2294 user = ctx.user()
2295 if opts.get('user'):
2295 if opts.get('user'):
2296 user = opts['user']
2296 user = opts['user']
2297 date = ctx.date()
2297 date = ctx.date()
2298 if opts.get('date'):
2298 if opts.get('date'):
2299 date = opts['date']
2299 date = opts['date']
2300 message = ctx.description()
2300 message = ctx.description()
2301 if opts.get('log'):
2301 if opts.get('log'):
2302 message += '\n(grafted from %s)' % ctx.hex()
2302 message += '\n(grafted from %s)' % ctx.hex()
2303
2303
2304 # we don't merge the first commit when continuing
2304 # we don't merge the first commit when continuing
2305 if not cont:
2305 if not cont:
2306 # perform the graft merge with p1(rev) as 'ancestor'
2306 # perform the graft merge with p1(rev) as 'ancestor'
2307 try:
2307 try:
2308 # ui.forcemerge is an internal variable, do not document
2308 # ui.forcemerge is an internal variable, do not document
2309 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
2309 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
2310 'graft')
2310 'graft')
2311 stats = mergemod.graft(repo, ctx, ctx.p1(),
2311 stats = mergemod.graft(repo, ctx, ctx.p1(),
2312 ['local', 'graft'])
2312 ['local', 'graft'])
2313 finally:
2313 finally:
2314 repo.ui.setconfig('ui', 'forcemerge', '', 'graft')
2314 repo.ui.setconfig('ui', 'forcemerge', '', 'graft')
2315 # report any conflicts
2315 # report any conflicts
2316 if stats.unresolvedcount > 0:
2316 if stats.unresolvedcount > 0:
2317 # write out state for --continue
2317 # write out state for --continue
2318 nodelines = [repo[rev].hex() + "\n" for rev in revs[pos:]]
2318 nodelines = [repo[rev].hex() + "\n" for rev in revs[pos:]]
2319 repo.vfs.write('graftstate', ''.join(nodelines))
2319 repo.vfs.write('graftstate', ''.join(nodelines))
2320 extra = ''
2320 extra = ''
2321 if opts.get('user'):
2321 if opts.get('user'):
2322 extra += ' --user %s' % procutil.shellquote(opts['user'])
2322 extra += ' --user %s' % procutil.shellquote(opts['user'])
2323 if opts.get('date'):
2323 if opts.get('date'):
2324 extra += ' --date %s' % procutil.shellquote(opts['date'])
2324 extra += ' --date %s' % procutil.shellquote(opts['date'])
2325 if opts.get('log'):
2325 if opts.get('log'):
2326 extra += ' --log'
2326 extra += ' --log'
2327 hint=_("use 'hg resolve' and 'hg graft --continue%s'") % extra
2327 hint=_("use 'hg resolve' and 'hg graft --continue%s'") % extra
2328 raise error.Abort(
2328 raise error.Abort(
2329 _("unresolved conflicts, can't continue"),
2329 _("unresolved conflicts, can't continue"),
2330 hint=hint)
2330 hint=hint)
2331 else:
2331 else:
2332 cont = False
2332 cont = False
2333
2333
2334 # commit
2334 # commit
2335 node = repo.commit(text=message, user=user,
2335 node = repo.commit(text=message, user=user,
2336 date=date, extra=extra, editor=editor)
2336 date=date, extra=extra, editor=editor)
2337 if node is None:
2337 if node is None:
2338 ui.warn(
2338 ui.warn(
2339 _('note: graft of %d:%s created no changes to commit\n') %
2339 _('note: graft of %d:%s created no changes to commit\n') %
2340 (ctx.rev(), ctx))
2340 (ctx.rev(), ctx))
2341
2341
2342 # remove state when we complete successfully
2342 # remove state when we complete successfully
2343 if not opts.get('dry_run'):
2343 if not opts.get('dry_run'):
2344 repo.vfs.unlinkpath('graftstate', ignoremissing=True)
2344 repo.vfs.unlinkpath('graftstate', ignoremissing=True)
2345
2345
2346 return 0
2346 return 0
2347
2347
2348 @command('grep',
2348 @command('grep',
2349 [('0', 'print0', None, _('end fields with NUL')),
2349 [('0', 'print0', None, _('end fields with NUL')),
2350 ('', 'all', None, _('print all revisions that match')),
2350 ('', 'all', None, _('print all revisions that match')),
2351 ('a', 'text', None, _('treat all files as text')),
2351 ('a', 'text', None, _('treat all files as text')),
2352 ('f', 'follow', None,
2352 ('f', 'follow', None,
2353 _('follow changeset history,'
2353 _('follow changeset history,'
2354 ' or file history across copies and renames')),
2354 ' or file history across copies and renames')),
2355 ('i', 'ignore-case', None, _('ignore case when matching')),
2355 ('i', 'ignore-case', None, _('ignore case when matching')),
2356 ('l', 'files-with-matches', None,
2356 ('l', 'files-with-matches', None,
2357 _('print only filenames and revisions that match')),
2357 _('print only filenames and revisions that match')),
2358 ('n', 'line-number', None, _('print matching line numbers')),
2358 ('n', 'line-number', None, _('print matching line numbers')),
2359 ('r', 'rev', [],
2359 ('r', 'rev', [],
2360 _('only search files changed within revision range'), _('REV')),
2360 _('only search files changed within revision range'), _('REV')),
2361 ('u', 'user', None, _('list the author (long with -v)')),
2361 ('u', 'user', None, _('list the author (long with -v)')),
2362 ('d', 'date', None, _('list the date (short with -q)')),
2362 ('d', 'date', None, _('list the date (short with -q)')),
2363 ] + formatteropts + walkopts,
2363 ] + formatteropts + walkopts,
2364 _('[OPTION]... PATTERN [FILE]...'),
2364 _('[OPTION]... PATTERN [FILE]...'),
2365 inferrepo=True, cmdtype=readonly)
2365 inferrepo=True, cmdtype=readonly)
2366 def grep(ui, repo, pattern, *pats, **opts):
2366 def grep(ui, repo, pattern, *pats, **opts):
2367 """search revision history for a pattern in specified files
2367 """search revision history for a pattern in specified files
2368
2368
2369 Search revision history for a regular expression in the specified
2369 Search revision history for a regular expression in the specified
2370 files or the entire project.
2370 files or the entire project.
2371
2371
2372 By default, grep prints the most recent revision number for each
2372 By default, grep prints the most recent revision number for each
2373 file in which it finds a match. To get it to print every revision
2373 file in which it finds a match. To get it to print every revision
2374 that contains a change in match status ("-" for a match that becomes
2374 that contains a change in match status ("-" for a match that becomes
2375 a non-match, or "+" for a non-match that becomes a match), use the
2375 a non-match, or "+" for a non-match that becomes a match), use the
2376 --all flag.
2376 --all flag.
2377
2377
2378 PATTERN can be any Python (roughly Perl-compatible) regular
2378 PATTERN can be any Python (roughly Perl-compatible) regular
2379 expression.
2379 expression.
2380
2380
2381 If no FILEs are specified (and -f/--follow isn't set), all files in
2381 If no FILEs are specified (and -f/--follow isn't set), all files in
2382 the repository are searched, including those that don't exist in the
2382 the repository are searched, including those that don't exist in the
2383 current branch or have been deleted in a prior changeset.
2383 current branch or have been deleted in a prior changeset.
2384
2384
2385 Returns 0 if a match is found, 1 otherwise.
2385 Returns 0 if a match is found, 1 otherwise.
2386 """
2386 """
2387 opts = pycompat.byteskwargs(opts)
2387 opts = pycompat.byteskwargs(opts)
2388 reflags = re.M
2388 reflags = re.M
2389 if opts.get('ignore_case'):
2389 if opts.get('ignore_case'):
2390 reflags |= re.I
2390 reflags |= re.I
2391 try:
2391 try:
2392 regexp = util.re.compile(pattern, reflags)
2392 regexp = util.re.compile(pattern, reflags)
2393 except re.error as inst:
2393 except re.error as inst:
2394 ui.warn(_("grep: invalid match pattern: %s\n") % pycompat.bytestr(inst))
2394 ui.warn(_("grep: invalid match pattern: %s\n") % pycompat.bytestr(inst))
2395 return 1
2395 return 1
2396 sep, eol = ':', '\n'
2396 sep, eol = ':', '\n'
2397 if opts.get('print0'):
2397 if opts.get('print0'):
2398 sep = eol = '\0'
2398 sep = eol = '\0'
2399
2399
2400 getfile = util.lrucachefunc(repo.file)
2400 getfile = util.lrucachefunc(repo.file)
2401
2401
2402 def matchlines(body):
2402 def matchlines(body):
2403 begin = 0
2403 begin = 0
2404 linenum = 0
2404 linenum = 0
2405 while begin < len(body):
2405 while begin < len(body):
2406 match = regexp.search(body, begin)
2406 match = regexp.search(body, begin)
2407 if not match:
2407 if not match:
2408 break
2408 break
2409 mstart, mend = match.span()
2409 mstart, mend = match.span()
2410 linenum += body.count('\n', begin, mstart) + 1
2410 linenum += body.count('\n', begin, mstart) + 1
2411 lstart = body.rfind('\n', begin, mstart) + 1 or begin
2411 lstart = body.rfind('\n', begin, mstart) + 1 or begin
2412 begin = body.find('\n', mend) + 1 or len(body) + 1
2412 begin = body.find('\n', mend) + 1 or len(body) + 1
2413 lend = begin - 1
2413 lend = begin - 1
2414 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
2414 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
2415
2415
2416 class linestate(object):
2416 class linestate(object):
2417 def __init__(self, line, linenum, colstart, colend):
2417 def __init__(self, line, linenum, colstart, colend):
2418 self.line = line
2418 self.line = line
2419 self.linenum = linenum
2419 self.linenum = linenum
2420 self.colstart = colstart
2420 self.colstart = colstart
2421 self.colend = colend
2421 self.colend = colend
2422
2422
2423 def __hash__(self):
2423 def __hash__(self):
2424 return hash((self.linenum, self.line))
2424 return hash((self.linenum, self.line))
2425
2425
2426 def __eq__(self, other):
2426 def __eq__(self, other):
2427 return self.line == other.line
2427 return self.line == other.line
2428
2428
2429 def findpos(self):
2429 def findpos(self):
2430 """Iterate all (start, end) indices of matches"""
2430 """Iterate all (start, end) indices of matches"""
2431 yield self.colstart, self.colend
2431 yield self.colstart, self.colend
2432 p = self.colend
2432 p = self.colend
2433 while p < len(self.line):
2433 while p < len(self.line):
2434 m = regexp.search(self.line, p)
2434 m = regexp.search(self.line, p)
2435 if not m:
2435 if not m:
2436 break
2436 break
2437 yield m.span()
2437 yield m.span()
2438 p = m.end()
2438 p = m.end()
2439
2439
2440 matches = {}
2440 matches = {}
2441 copies = {}
2441 copies = {}
2442 def grepbody(fn, rev, body):
2442 def grepbody(fn, rev, body):
2443 matches[rev].setdefault(fn, [])
2443 matches[rev].setdefault(fn, [])
2444 m = matches[rev][fn]
2444 m = matches[rev][fn]
2445 for lnum, cstart, cend, line in matchlines(body):
2445 for lnum, cstart, cend, line in matchlines(body):
2446 s = linestate(line, lnum, cstart, cend)
2446 s = linestate(line, lnum, cstart, cend)
2447 m.append(s)
2447 m.append(s)
2448
2448
2449 def difflinestates(a, b):
2449 def difflinestates(a, b):
2450 sm = difflib.SequenceMatcher(None, a, b)
2450 sm = difflib.SequenceMatcher(None, a, b)
2451 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
2451 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
2452 if tag == 'insert':
2452 if tag == 'insert':
2453 for i in xrange(blo, bhi):
2453 for i in xrange(blo, bhi):
2454 yield ('+', b[i])
2454 yield ('+', b[i])
2455 elif tag == 'delete':
2455 elif tag == 'delete':
2456 for i in xrange(alo, ahi):
2456 for i in xrange(alo, ahi):
2457 yield ('-', a[i])
2457 yield ('-', a[i])
2458 elif tag == 'replace':
2458 elif tag == 'replace':
2459 for i in xrange(alo, ahi):
2459 for i in xrange(alo, ahi):
2460 yield ('-', a[i])
2460 yield ('-', a[i])
2461 for i in xrange(blo, bhi):
2461 for i in xrange(blo, bhi):
2462 yield ('+', b[i])
2462 yield ('+', b[i])
2463
2463
2464 def display(fm, fn, ctx, pstates, states):
2464 def display(fm, fn, ctx, pstates, states):
2465 rev = ctx.rev()
2465 rev = ctx.rev()
2466 if fm.isplain():
2466 if fm.isplain():
2467 formatuser = ui.shortuser
2467 formatuser = ui.shortuser
2468 else:
2468 else:
2469 formatuser = str
2469 formatuser = str
2470 if ui.quiet:
2470 if ui.quiet:
2471 datefmt = '%Y-%m-%d'
2471 datefmt = '%Y-%m-%d'
2472 else:
2472 else:
2473 datefmt = '%a %b %d %H:%M:%S %Y %1%2'
2473 datefmt = '%a %b %d %H:%M:%S %Y %1%2'
2474 found = False
2474 found = False
2475 @util.cachefunc
2475 @util.cachefunc
2476 def binary():
2476 def binary():
2477 flog = getfile(fn)
2477 flog = getfile(fn)
2478 return stringutil.binary(flog.read(ctx.filenode(fn)))
2478 return stringutil.binary(flog.read(ctx.filenode(fn)))
2479
2479
2480 fieldnamemap = {'filename': 'file', 'linenumber': 'line_number'}
2480 fieldnamemap = {'filename': 'file', 'linenumber': 'line_number'}
2481 if opts.get('all'):
2481 if opts.get('all'):
2482 iter = difflinestates(pstates, states)
2482 iter = difflinestates(pstates, states)
2483 else:
2483 else:
2484 iter = [('', l) for l in states]
2484 iter = [('', l) for l in states]
2485 for change, l in iter:
2485 for change, l in iter:
2486 fm.startitem()
2486 fm.startitem()
2487 fm.data(node=fm.hexfunc(ctx.node()))
2487 fm.data(node=fm.hexfunc(ctx.node()))
2488 cols = [
2488 cols = [
2489 ('filename', fn, True),
2489 ('filename', fn, True),
2490 ('rev', rev, True),
2490 ('rev', rev, True),
2491 ('linenumber', l.linenum, opts.get('line_number')),
2491 ('linenumber', l.linenum, opts.get('line_number')),
2492 ]
2492 ]
2493 if opts.get('all'):
2493 if opts.get('all'):
2494 cols.append(('change', change, True))
2494 cols.append(('change', change, True))
2495 cols.extend([
2495 cols.extend([
2496 ('user', formatuser(ctx.user()), opts.get('user')),
2496 ('user', formatuser(ctx.user()), opts.get('user')),
2497 ('date', fm.formatdate(ctx.date(), datefmt), opts.get('date')),
2497 ('date', fm.formatdate(ctx.date(), datefmt), opts.get('date')),
2498 ])
2498 ])
2499 lastcol = next(name for name, data, cond in reversed(cols) if cond)
2499 lastcol = next(name for name, data, cond in reversed(cols) if cond)
2500 for name, data, cond in cols:
2500 for name, data, cond in cols:
2501 field = fieldnamemap.get(name, name)
2501 field = fieldnamemap.get(name, name)
2502 fm.condwrite(cond, field, '%s', data, label='grep.%s' % name)
2502 fm.condwrite(cond, field, '%s', data, label='grep.%s' % name)
2503 if cond and name != lastcol:
2503 if cond and name != lastcol:
2504 fm.plain(sep, label='grep.sep')
2504 fm.plain(sep, label='grep.sep')
2505 if not opts.get('files_with_matches'):
2505 if not opts.get('files_with_matches'):
2506 fm.plain(sep, label='grep.sep')
2506 fm.plain(sep, label='grep.sep')
2507 if not opts.get('text') and binary():
2507 if not opts.get('text') and binary():
2508 fm.plain(_(" Binary file matches"))
2508 fm.plain(_(" Binary file matches"))
2509 else:
2509 else:
2510 displaymatches(fm.nested('texts'), l)
2510 displaymatches(fm.nested('texts'), l)
2511 fm.plain(eol)
2511 fm.plain(eol)
2512 found = True
2512 found = True
2513 if opts.get('files_with_matches'):
2513 if opts.get('files_with_matches'):
2514 break
2514 break
2515 return found
2515 return found
2516
2516
2517 def displaymatches(fm, l):
2517 def displaymatches(fm, l):
2518 p = 0
2518 p = 0
2519 for s, e in l.findpos():
2519 for s, e in l.findpos():
2520 if p < s:
2520 if p < s:
2521 fm.startitem()
2521 fm.startitem()
2522 fm.write('text', '%s', l.line[p:s])
2522 fm.write('text', '%s', l.line[p:s])
2523 fm.data(matched=False)
2523 fm.data(matched=False)
2524 fm.startitem()
2524 fm.startitem()
2525 fm.write('text', '%s', l.line[s:e], label='grep.match')
2525 fm.write('text', '%s', l.line[s:e], label='grep.match')
2526 fm.data(matched=True)
2526 fm.data(matched=True)
2527 p = e
2527 p = e
2528 if p < len(l.line):
2528 if p < len(l.line):
2529 fm.startitem()
2529 fm.startitem()
2530 fm.write('text', '%s', l.line[p:])
2530 fm.write('text', '%s', l.line[p:])
2531 fm.data(matched=False)
2531 fm.data(matched=False)
2532 fm.end()
2532 fm.end()
2533
2533
2534 skip = {}
2534 skip = {}
2535 revfiles = {}
2535 revfiles = {}
2536 match = scmutil.match(repo[None], pats, opts)
2536 match = scmutil.match(repo[None], pats, opts)
2537 found = False
2537 found = False
2538 follow = opts.get('follow')
2538 follow = opts.get('follow')
2539
2539
2540 def prep(ctx, fns):
2540 def prep(ctx, fns):
2541 rev = ctx.rev()
2541 rev = ctx.rev()
2542 pctx = ctx.p1()
2542 pctx = ctx.p1()
2543 parent = pctx.rev()
2543 parent = pctx.rev()
2544 matches.setdefault(rev, {})
2544 matches.setdefault(rev, {})
2545 matches.setdefault(parent, {})
2545 matches.setdefault(parent, {})
2546 files = revfiles.setdefault(rev, [])
2546 files = revfiles.setdefault(rev, [])
2547 for fn in fns:
2547 for fn in fns:
2548 flog = getfile(fn)
2548 flog = getfile(fn)
2549 try:
2549 try:
2550 fnode = ctx.filenode(fn)
2550 fnode = ctx.filenode(fn)
2551 except error.LookupError:
2551 except error.LookupError:
2552 continue
2552 continue
2553
2553
2554 copied = flog.renamed(fnode)
2554 copied = flog.renamed(fnode)
2555 copy = follow and copied and copied[0]
2555 copy = follow and copied and copied[0]
2556 if copy:
2556 if copy:
2557 copies.setdefault(rev, {})[fn] = copy
2557 copies.setdefault(rev, {})[fn] = copy
2558 if fn in skip:
2558 if fn in skip:
2559 if copy:
2559 if copy:
2560 skip[copy] = True
2560 skip[copy] = True
2561 continue
2561 continue
2562 files.append(fn)
2562 files.append(fn)
2563
2563
2564 if fn not in matches[rev]:
2564 if fn not in matches[rev]:
2565 grepbody(fn, rev, flog.read(fnode))
2565 grepbody(fn, rev, flog.read(fnode))
2566
2566
2567 pfn = copy or fn
2567 pfn = copy or fn
2568 if pfn not in matches[parent]:
2568 if pfn not in matches[parent]:
2569 try:
2569 try:
2570 fnode = pctx.filenode(pfn)
2570 fnode = pctx.filenode(pfn)
2571 grepbody(pfn, parent, flog.read(fnode))
2571 grepbody(pfn, parent, flog.read(fnode))
2572 except error.LookupError:
2572 except error.LookupError:
2573 pass
2573 pass
2574
2574
2575 ui.pager('grep')
2575 ui.pager('grep')
2576 fm = ui.formatter('grep', opts)
2576 fm = ui.formatter('grep', opts)
2577 for ctx in cmdutil.walkchangerevs(repo, match, opts, prep):
2577 for ctx in cmdutil.walkchangerevs(repo, match, opts, prep):
2578 rev = ctx.rev()
2578 rev = ctx.rev()
2579 parent = ctx.p1().rev()
2579 parent = ctx.p1().rev()
2580 for fn in sorted(revfiles.get(rev, [])):
2580 for fn in sorted(revfiles.get(rev, [])):
2581 states = matches[rev][fn]
2581 states = matches[rev][fn]
2582 copy = copies.get(rev, {}).get(fn)
2582 copy = copies.get(rev, {}).get(fn)
2583 if fn in skip:
2583 if fn in skip:
2584 if copy:
2584 if copy:
2585 skip[copy] = True
2585 skip[copy] = True
2586 continue
2586 continue
2587 pstates = matches.get(parent, {}).get(copy or fn, [])
2587 pstates = matches.get(parent, {}).get(copy or fn, [])
2588 if pstates or states:
2588 if pstates or states:
2589 r = display(fm, fn, ctx, pstates, states)
2589 r = display(fm, fn, ctx, pstates, states)
2590 found = found or r
2590 found = found or r
2591 if r and not opts.get('all'):
2591 if r and not opts.get('all'):
2592 skip[fn] = True
2592 skip[fn] = True
2593 if copy:
2593 if copy:
2594 skip[copy] = True
2594 skip[copy] = True
2595 del revfiles[rev]
2595 del revfiles[rev]
2596 # We will keep the matches dict for the duration of the window
2596 # We will keep the matches dict for the duration of the window
2597 # clear the matches dict once the window is over
2597 # clear the matches dict once the window is over
2598 if not revfiles:
2598 if not revfiles:
2599 matches.clear()
2599 matches.clear()
2600 fm.end()
2600 fm.end()
2601
2601
2602 return not found
2602 return not found
2603
2603
2604 @command('heads',
2604 @command('heads',
2605 [('r', 'rev', '',
2605 [('r', 'rev', '',
2606 _('show only heads which are descendants of STARTREV'), _('STARTREV')),
2606 _('show only heads which are descendants of STARTREV'), _('STARTREV')),
2607 ('t', 'topo', False, _('show topological heads only')),
2607 ('t', 'topo', False, _('show topological heads only')),
2608 ('a', 'active', False, _('show active branchheads only (DEPRECATED)')),
2608 ('a', 'active', False, _('show active branchheads only (DEPRECATED)')),
2609 ('c', 'closed', False, _('show normal and closed branch heads')),
2609 ('c', 'closed', False, _('show normal and closed branch heads')),
2610 ] + templateopts,
2610 ] + templateopts,
2611 _('[-ct] [-r STARTREV] [REV]...'), cmdtype=readonly)
2611 _('[-ct] [-r STARTREV] [REV]...'), cmdtype=readonly)
2612 def heads(ui, repo, *branchrevs, **opts):
2612 def heads(ui, repo, *branchrevs, **opts):
2613 """show branch heads
2613 """show branch heads
2614
2614
2615 With no arguments, show all open branch heads in the repository.
2615 With no arguments, show all open branch heads in the repository.
2616 Branch heads are changesets that have no descendants on the
2616 Branch heads are changesets that have no descendants on the
2617 same branch. They are where development generally takes place and
2617 same branch. They are where development generally takes place and
2618 are the usual targets for update and merge operations.
2618 are the usual targets for update and merge operations.
2619
2619
2620 If one or more REVs are given, only open branch heads on the
2620 If one or more REVs are given, only open branch heads on the
2621 branches associated with the specified changesets are shown. This
2621 branches associated with the specified changesets are shown. This
2622 means that you can use :hg:`heads .` to see the heads on the
2622 means that you can use :hg:`heads .` to see the heads on the
2623 currently checked-out branch.
2623 currently checked-out branch.
2624
2624
2625 If -c/--closed is specified, also show branch heads marked closed
2625 If -c/--closed is specified, also show branch heads marked closed
2626 (see :hg:`commit --close-branch`).
2626 (see :hg:`commit --close-branch`).
2627
2627
2628 If STARTREV is specified, only those heads that are descendants of
2628 If STARTREV is specified, only those heads that are descendants of
2629 STARTREV will be displayed.
2629 STARTREV will be displayed.
2630
2630
2631 If -t/--topo is specified, named branch mechanics will be ignored and only
2631 If -t/--topo is specified, named branch mechanics will be ignored and only
2632 topological heads (changesets with no children) will be shown.
2632 topological heads (changesets with no children) will be shown.
2633
2633
2634 Returns 0 if matching heads are found, 1 if not.
2634 Returns 0 if matching heads are found, 1 if not.
2635 """
2635 """
2636
2636
2637 opts = pycompat.byteskwargs(opts)
2637 opts = pycompat.byteskwargs(opts)
2638 start = None
2638 start = None
2639 rev = opts.get('rev')
2639 rev = opts.get('rev')
2640 if rev:
2640 if rev:
2641 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
2641 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
2642 start = scmutil.revsingle(repo, rev, None).node()
2642 start = scmutil.revsingle(repo, rev, None).node()
2643
2643
2644 if opts.get('topo'):
2644 if opts.get('topo'):
2645 heads = [repo[h] for h in repo.heads(start)]
2645 heads = [repo[h] for h in repo.heads(start)]
2646 else:
2646 else:
2647 heads = []
2647 heads = []
2648 for branch in repo.branchmap():
2648 for branch in repo.branchmap():
2649 heads += repo.branchheads(branch, start, opts.get('closed'))
2649 heads += repo.branchheads(branch, start, opts.get('closed'))
2650 heads = [repo[h] for h in heads]
2650 heads = [repo[h] for h in heads]
2651
2651
2652 if branchrevs:
2652 if branchrevs:
2653 branches = set(repo[br].branch() for br in branchrevs)
2653 branches = set(repo[br].branch() for br in branchrevs)
2654 heads = [h for h in heads if h.branch() in branches]
2654 heads = [h for h in heads if h.branch() in branches]
2655
2655
2656 if opts.get('active') and branchrevs:
2656 if opts.get('active') and branchrevs:
2657 dagheads = repo.heads(start)
2657 dagheads = repo.heads(start)
2658 heads = [h for h in heads if h.node() in dagheads]
2658 heads = [h for h in heads if h.node() in dagheads]
2659
2659
2660 if branchrevs:
2660 if branchrevs:
2661 haveheads = set(h.branch() for h in heads)
2661 haveheads = set(h.branch() for h in heads)
2662 if branches - haveheads:
2662 if branches - haveheads:
2663 headless = ', '.join(b for b in branches - haveheads)
2663 headless = ', '.join(b for b in branches - haveheads)
2664 msg = _('no open branch heads found on branches %s')
2664 msg = _('no open branch heads found on branches %s')
2665 if opts.get('rev'):
2665 if opts.get('rev'):
2666 msg += _(' (started at %s)') % opts['rev']
2666 msg += _(' (started at %s)') % opts['rev']
2667 ui.warn((msg + '\n') % headless)
2667 ui.warn((msg + '\n') % headless)
2668
2668
2669 if not heads:
2669 if not heads:
2670 return 1
2670 return 1
2671
2671
2672 ui.pager('heads')
2672 ui.pager('heads')
2673 heads = sorted(heads, key=lambda x: -x.rev())
2673 heads = sorted(heads, key=lambda x: -x.rev())
2674 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
2674 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
2675 for ctx in heads:
2675 for ctx in heads:
2676 displayer.show(ctx)
2676 displayer.show(ctx)
2677 displayer.close()
2677 displayer.close()
2678
2678
2679 @command('help',
2679 @command('help',
2680 [('e', 'extension', None, _('show only help for extensions')),
2680 [('e', 'extension', None, _('show only help for extensions')),
2681 ('c', 'command', None, _('show only help for commands')),
2681 ('c', 'command', None, _('show only help for commands')),
2682 ('k', 'keyword', None, _('show topics matching keyword')),
2682 ('k', 'keyword', None, _('show topics matching keyword')),
2683 ('s', 'system', [], _('show help for specific platform(s)')),
2683 ('s', 'system', [], _('show help for specific platform(s)')),
2684 ],
2684 ],
2685 _('[-ecks] [TOPIC]'),
2685 _('[-ecks] [TOPIC]'),
2686 norepo=True, cmdtype=readonly)
2686 norepo=True, cmdtype=readonly)
2687 def help_(ui, name=None, **opts):
2687 def help_(ui, name=None, **opts):
2688 """show help for a given topic or a help overview
2688 """show help for a given topic or a help overview
2689
2689
2690 With no arguments, print a list of commands with short help messages.
2690 With no arguments, print a list of commands with short help messages.
2691
2691
2692 Given a topic, extension, or command name, print help for that
2692 Given a topic, extension, or command name, print help for that
2693 topic.
2693 topic.
2694
2694
2695 Returns 0 if successful.
2695 Returns 0 if successful.
2696 """
2696 """
2697
2697
2698 keep = opts.get(r'system') or []
2698 keep = opts.get(r'system') or []
2699 if len(keep) == 0:
2699 if len(keep) == 0:
2700 if pycompat.sysplatform.startswith('win'):
2700 if pycompat.sysplatform.startswith('win'):
2701 keep.append('windows')
2701 keep.append('windows')
2702 elif pycompat.sysplatform == 'OpenVMS':
2702 elif pycompat.sysplatform == 'OpenVMS':
2703 keep.append('vms')
2703 keep.append('vms')
2704 elif pycompat.sysplatform == 'plan9':
2704 elif pycompat.sysplatform == 'plan9':
2705 keep.append('plan9')
2705 keep.append('plan9')
2706 else:
2706 else:
2707 keep.append('unix')
2707 keep.append('unix')
2708 keep.append(pycompat.sysplatform.lower())
2708 keep.append(pycompat.sysplatform.lower())
2709 if ui.verbose:
2709 if ui.verbose:
2710 keep.append('verbose')
2710 keep.append('verbose')
2711
2711
2712 commands = sys.modules[__name__]
2712 commands = sys.modules[__name__]
2713 formatted = help.formattedhelp(ui, commands, name, keep=keep, **opts)
2713 formatted = help.formattedhelp(ui, commands, name, keep=keep, **opts)
2714 ui.pager('help')
2714 ui.pager('help')
2715 ui.write(formatted)
2715 ui.write(formatted)
2716
2716
2717
2717
2718 @command('identify|id',
2718 @command('identify|id',
2719 [('r', 'rev', '',
2719 [('r', 'rev', '',
2720 _('identify the specified revision'), _('REV')),
2720 _('identify the specified revision'), _('REV')),
2721 ('n', 'num', None, _('show local revision number')),
2721 ('n', 'num', None, _('show local revision number')),
2722 ('i', 'id', None, _('show global revision id')),
2722 ('i', 'id', None, _('show global revision id')),
2723 ('b', 'branch', None, _('show branch')),
2723 ('b', 'branch', None, _('show branch')),
2724 ('t', 'tags', None, _('show tags')),
2724 ('t', 'tags', None, _('show tags')),
2725 ('B', 'bookmarks', None, _('show bookmarks')),
2725 ('B', 'bookmarks', None, _('show bookmarks')),
2726 ] + remoteopts + formatteropts,
2726 ] + remoteopts + formatteropts,
2727 _('[-nibtB] [-r REV] [SOURCE]'),
2727 _('[-nibtB] [-r REV] [SOURCE]'),
2728 optionalrepo=True, cmdtype=readonly)
2728 optionalrepo=True, cmdtype=readonly)
2729 def identify(ui, repo, source=None, rev=None,
2729 def identify(ui, repo, source=None, rev=None,
2730 num=None, id=None, branch=None, tags=None, bookmarks=None, **opts):
2730 num=None, id=None, branch=None, tags=None, bookmarks=None, **opts):
2731 """identify the working directory or specified revision
2731 """identify the working directory or specified revision
2732
2732
2733 Print a summary identifying the repository state at REV using one or
2733 Print a summary identifying the repository state at REV using one or
2734 two parent hash identifiers, followed by a "+" if the working
2734 two parent hash identifiers, followed by a "+" if the working
2735 directory has uncommitted changes, the branch name (if not default),
2735 directory has uncommitted changes, the branch name (if not default),
2736 a list of tags, and a list of bookmarks.
2736 a list of tags, and a list of bookmarks.
2737
2737
2738 When REV is not given, print a summary of the current state of the
2738 When REV is not given, print a summary of the current state of the
2739 repository including the working directory. Specify -r. to get information
2739 repository including the working directory. Specify -r. to get information
2740 of the working directory parent without scanning uncommitted changes.
2740 of the working directory parent without scanning uncommitted changes.
2741
2741
2742 Specifying a path to a repository root or Mercurial bundle will
2742 Specifying a path to a repository root or Mercurial bundle will
2743 cause lookup to operate on that repository/bundle.
2743 cause lookup to operate on that repository/bundle.
2744
2744
2745 .. container:: verbose
2745 .. container:: verbose
2746
2746
2747 Examples:
2747 Examples:
2748
2748
2749 - generate a build identifier for the working directory::
2749 - generate a build identifier for the working directory::
2750
2750
2751 hg id --id > build-id.dat
2751 hg id --id > build-id.dat
2752
2752
2753 - find the revision corresponding to a tag::
2753 - find the revision corresponding to a tag::
2754
2754
2755 hg id -n -r 1.3
2755 hg id -n -r 1.3
2756
2756
2757 - check the most recent revision of a remote repository::
2757 - check the most recent revision of a remote repository::
2758
2758
2759 hg id -r tip https://www.mercurial-scm.org/repo/hg/
2759 hg id -r tip https://www.mercurial-scm.org/repo/hg/
2760
2760
2761 See :hg:`log` for generating more information about specific revisions,
2761 See :hg:`log` for generating more information about specific revisions,
2762 including full hash identifiers.
2762 including full hash identifiers.
2763
2763
2764 Returns 0 if successful.
2764 Returns 0 if successful.
2765 """
2765 """
2766
2766
2767 opts = pycompat.byteskwargs(opts)
2767 opts = pycompat.byteskwargs(opts)
2768 if not repo and not source:
2768 if not repo and not source:
2769 raise error.Abort(_("there is no Mercurial repository here "
2769 raise error.Abort(_("there is no Mercurial repository here "
2770 "(.hg not found)"))
2770 "(.hg not found)"))
2771
2771
2772 if ui.debugflag:
2772 if ui.debugflag:
2773 hexfunc = hex
2773 hexfunc = hex
2774 else:
2774 else:
2775 hexfunc = short
2775 hexfunc = short
2776 default = not (num or id or branch or tags or bookmarks)
2776 default = not (num or id or branch or tags or bookmarks)
2777 output = []
2777 output = []
2778 revs = []
2778 revs = []
2779
2779
2780 if source:
2780 if source:
2781 source, branches = hg.parseurl(ui.expandpath(source))
2781 source, branches = hg.parseurl(ui.expandpath(source))
2782 peer = hg.peer(repo or ui, opts, source) # only pass ui when no repo
2782 peer = hg.peer(repo or ui, opts, source) # only pass ui when no repo
2783 repo = peer.local()
2783 repo = peer.local()
2784 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
2784 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
2785
2785
2786 fm = ui.formatter('identify', opts)
2786 fm = ui.formatter('identify', opts)
2787 fm.startitem()
2787 fm.startitem()
2788
2788
2789 if not repo:
2789 if not repo:
2790 if num or branch or tags:
2790 if num or branch or tags:
2791 raise error.Abort(
2791 raise error.Abort(
2792 _("can't query remote revision number, branch, or tags"))
2792 _("can't query remote revision number, branch, or tags"))
2793 if not rev and revs:
2793 if not rev and revs:
2794 rev = revs[0]
2794 rev = revs[0]
2795 if not rev:
2795 if not rev:
2796 rev = "tip"
2796 rev = "tip"
2797
2797
2798 remoterev = peer.lookup(rev)
2798 remoterev = peer.lookup(rev)
2799 hexrev = hexfunc(remoterev)
2799 hexrev = hexfunc(remoterev)
2800 if default or id:
2800 if default or id:
2801 output = [hexrev]
2801 output = [hexrev]
2802 fm.data(id=hexrev)
2802 fm.data(id=hexrev)
2803
2803
2804 def getbms():
2804 def getbms():
2805 bms = []
2805 bms = []
2806
2806
2807 if 'bookmarks' in peer.listkeys('namespaces'):
2807 if 'bookmarks' in peer.listkeys('namespaces'):
2808 hexremoterev = hex(remoterev)
2808 hexremoterev = hex(remoterev)
2809 bms = [bm for bm, bmr in peer.listkeys('bookmarks').iteritems()
2809 bms = [bm for bm, bmr in peer.listkeys('bookmarks').iteritems()
2810 if bmr == hexremoterev]
2810 if bmr == hexremoterev]
2811
2811
2812 return sorted(bms)
2812 return sorted(bms)
2813
2813
2814 bms = getbms()
2814 bms = getbms()
2815 if bookmarks:
2815 if bookmarks:
2816 output.extend(bms)
2816 output.extend(bms)
2817 elif default and not ui.quiet:
2817 elif default and not ui.quiet:
2818 # multiple bookmarks for a single parent separated by '/'
2818 # multiple bookmarks for a single parent separated by '/'
2819 bm = '/'.join(bms)
2819 bm = '/'.join(bms)
2820 if bm:
2820 if bm:
2821 output.append(bm)
2821 output.append(bm)
2822
2822
2823 fm.data(node=hex(remoterev))
2823 fm.data(node=hex(remoterev))
2824 fm.data(bookmarks=fm.formatlist(bms, name='bookmark'))
2824 fm.data(bookmarks=fm.formatlist(bms, name='bookmark'))
2825 else:
2825 else:
2826 if rev:
2826 if rev:
2827 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
2827 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
2828 ctx = scmutil.revsingle(repo, rev, None)
2828 ctx = scmutil.revsingle(repo, rev, None)
2829
2829
2830 if ctx.rev() is None:
2830 if ctx.rev() is None:
2831 ctx = repo[None]
2831 ctx = repo[None]
2832 parents = ctx.parents()
2832 parents = ctx.parents()
2833 taglist = []
2833 taglist = []
2834 for p in parents:
2834 for p in parents:
2835 taglist.extend(p.tags())
2835 taglist.extend(p.tags())
2836
2836
2837 dirty = ""
2837 dirty = ""
2838 if ctx.dirty(missing=True, merge=False, branch=False):
2838 if ctx.dirty(missing=True, merge=False, branch=False):
2839 dirty = '+'
2839 dirty = '+'
2840 fm.data(dirty=dirty)
2840 fm.data(dirty=dirty)
2841
2841
2842 hexoutput = [hexfunc(p.node()) for p in parents]
2842 hexoutput = [hexfunc(p.node()) for p in parents]
2843 if default or id:
2843 if default or id:
2844 output = ["%s%s" % ('+'.join(hexoutput), dirty)]
2844 output = ["%s%s" % ('+'.join(hexoutput), dirty)]
2845 fm.data(id="%s%s" % ('+'.join(hexoutput), dirty))
2845 fm.data(id="%s%s" % ('+'.join(hexoutput), dirty))
2846
2846
2847 if num:
2847 if num:
2848 numoutput = ["%d" % p.rev() for p in parents]
2848 numoutput = ["%d" % p.rev() for p in parents]
2849 output.append("%s%s" % ('+'.join(numoutput), dirty))
2849 output.append("%s%s" % ('+'.join(numoutput), dirty))
2850
2850
2851 fn = fm.nested('parents')
2851 fn = fm.nested('parents')
2852 for p in parents:
2852 for p in parents:
2853 fn.startitem()
2853 fn.startitem()
2854 fn.data(rev=p.rev())
2854 fn.data(rev=p.rev())
2855 fn.data(node=p.hex())
2855 fn.data(node=p.hex())
2856 fn.context(ctx=p)
2856 fn.context(ctx=p)
2857 fn.end()
2857 fn.end()
2858 else:
2858 else:
2859 hexoutput = hexfunc(ctx.node())
2859 hexoutput = hexfunc(ctx.node())
2860 if default or id:
2860 if default or id:
2861 output = [hexoutput]
2861 output = [hexoutput]
2862 fm.data(id=hexoutput)
2862 fm.data(id=hexoutput)
2863
2863
2864 if num:
2864 if num:
2865 output.append(pycompat.bytestr(ctx.rev()))
2865 output.append(pycompat.bytestr(ctx.rev()))
2866 taglist = ctx.tags()
2866 taglist = ctx.tags()
2867
2867
2868 if default and not ui.quiet:
2868 if default and not ui.quiet:
2869 b = ctx.branch()
2869 b = ctx.branch()
2870 if b != 'default':
2870 if b != 'default':
2871 output.append("(%s)" % b)
2871 output.append("(%s)" % b)
2872
2872
2873 # multiple tags for a single parent separated by '/'
2873 # multiple tags for a single parent separated by '/'
2874 t = '/'.join(taglist)
2874 t = '/'.join(taglist)
2875 if t:
2875 if t:
2876 output.append(t)
2876 output.append(t)
2877
2877
2878 # multiple bookmarks for a single parent separated by '/'
2878 # multiple bookmarks for a single parent separated by '/'
2879 bm = '/'.join(ctx.bookmarks())
2879 bm = '/'.join(ctx.bookmarks())
2880 if bm:
2880 if bm:
2881 output.append(bm)
2881 output.append(bm)
2882 else:
2882 else:
2883 if branch:
2883 if branch:
2884 output.append(ctx.branch())
2884 output.append(ctx.branch())
2885
2885
2886 if tags:
2886 if tags:
2887 output.extend(taglist)
2887 output.extend(taglist)
2888
2888
2889 if bookmarks:
2889 if bookmarks:
2890 output.extend(ctx.bookmarks())
2890 output.extend(ctx.bookmarks())
2891
2891
2892 fm.data(node=ctx.hex())
2892 fm.data(node=ctx.hex())
2893 fm.data(branch=ctx.branch())
2893 fm.data(branch=ctx.branch())
2894 fm.data(tags=fm.formatlist(taglist, name='tag', sep=':'))
2894 fm.data(tags=fm.formatlist(taglist, name='tag', sep=':'))
2895 fm.data(bookmarks=fm.formatlist(ctx.bookmarks(), name='bookmark'))
2895 fm.data(bookmarks=fm.formatlist(ctx.bookmarks(), name='bookmark'))
2896 fm.context(ctx=ctx)
2896 fm.context(ctx=ctx)
2897
2897
2898 fm.plain("%s\n" % ' '.join(output))
2898 fm.plain("%s\n" % ' '.join(output))
2899 fm.end()
2899 fm.end()
2900
2900
2901 @command('import|patch',
2901 @command('import|patch',
2902 [('p', 'strip', 1,
2902 [('p', 'strip', 1,
2903 _('directory strip option for patch. This has the same '
2903 _('directory strip option for patch. This has the same '
2904 'meaning as the corresponding patch option'), _('NUM')),
2904 'meaning as the corresponding patch option'), _('NUM')),
2905 ('b', 'base', '', _('base path (DEPRECATED)'), _('PATH')),
2905 ('b', 'base', '', _('base path (DEPRECATED)'), _('PATH')),
2906 ('e', 'edit', False, _('invoke editor on commit messages')),
2906 ('e', 'edit', False, _('invoke editor on commit messages')),
2907 ('f', 'force', None,
2907 ('f', 'force', None,
2908 _('skip check for outstanding uncommitted changes (DEPRECATED)')),
2908 _('skip check for outstanding uncommitted changes (DEPRECATED)')),
2909 ('', 'no-commit', None,
2909 ('', 'no-commit', None,
2910 _("don't commit, just update the working directory")),
2910 _("don't commit, just update the working directory")),
2911 ('', 'bypass', None,
2911 ('', 'bypass', None,
2912 _("apply patch without touching the working directory")),
2912 _("apply patch without touching the working directory")),
2913 ('', 'partial', None,
2913 ('', 'partial', None,
2914 _('commit even if some hunks fail')),
2914 _('commit even if some hunks fail')),
2915 ('', 'exact', None,
2915 ('', 'exact', None,
2916 _('abort if patch would apply lossily')),
2916 _('abort if patch would apply lossily')),
2917 ('', 'prefix', '',
2917 ('', 'prefix', '',
2918 _('apply patch to subdirectory'), _('DIR')),
2918 _('apply patch to subdirectory'), _('DIR')),
2919 ('', 'import-branch', None,
2919 ('', 'import-branch', None,
2920 _('use any branch information in patch (implied by --exact)'))] +
2920 _('use any branch information in patch (implied by --exact)'))] +
2921 commitopts + commitopts2 + similarityopts,
2921 commitopts + commitopts2 + similarityopts,
2922 _('[OPTION]... PATCH...'))
2922 _('[OPTION]... PATCH...'))
2923 def import_(ui, repo, patch1=None, *patches, **opts):
2923 def import_(ui, repo, patch1=None, *patches, **opts):
2924 """import an ordered set of patches
2924 """import an ordered set of patches
2925
2925
2926 Import a list of patches and commit them individually (unless
2926 Import a list of patches and commit them individually (unless
2927 --no-commit is specified).
2927 --no-commit is specified).
2928
2928
2929 To read a patch from standard input (stdin), use "-" as the patch
2929 To read a patch from standard input (stdin), use "-" as the patch
2930 name. If a URL is specified, the patch will be downloaded from
2930 name. If a URL is specified, the patch will be downloaded from
2931 there.
2931 there.
2932
2932
2933 Import first applies changes to the working directory (unless
2933 Import first applies changes to the working directory (unless
2934 --bypass is specified), import will abort if there are outstanding
2934 --bypass is specified), import will abort if there are outstanding
2935 changes.
2935 changes.
2936
2936
2937 Use --bypass to apply and commit patches directly to the
2937 Use --bypass to apply and commit patches directly to the
2938 repository, without affecting the working directory. Without
2938 repository, without affecting the working directory. Without
2939 --exact, patches will be applied on top of the working directory
2939 --exact, patches will be applied on top of the working directory
2940 parent revision.
2940 parent revision.
2941
2941
2942 You can import a patch straight from a mail message. Even patches
2942 You can import a patch straight from a mail message. Even patches
2943 as attachments work (to use the body part, it must have type
2943 as attachments work (to use the body part, it must have type
2944 text/plain or text/x-patch). From and Subject headers of email
2944 text/plain or text/x-patch). From and Subject headers of email
2945 message are used as default committer and commit message. All
2945 message are used as default committer and commit message. All
2946 text/plain body parts before first diff are added to the commit
2946 text/plain body parts before first diff are added to the commit
2947 message.
2947 message.
2948
2948
2949 If the imported patch was generated by :hg:`export`, user and
2949 If the imported patch was generated by :hg:`export`, user and
2950 description from patch override values from message headers and
2950 description from patch override values from message headers and
2951 body. Values given on command line with -m/--message and -u/--user
2951 body. Values given on command line with -m/--message and -u/--user
2952 override these.
2952 override these.
2953
2953
2954 If --exact is specified, import will set the working directory to
2954 If --exact is specified, import will set the working directory to
2955 the parent of each patch before applying it, and will abort if the
2955 the parent of each patch before applying it, and will abort if the
2956 resulting changeset has a different ID than the one recorded in
2956 resulting changeset has a different ID than the one recorded in
2957 the patch. This will guard against various ways that portable
2957 the patch. This will guard against various ways that portable
2958 patch formats and mail systems might fail to transfer Mercurial
2958 patch formats and mail systems might fail to transfer Mercurial
2959 data or metadata. See :hg:`bundle` for lossless transmission.
2959 data or metadata. See :hg:`bundle` for lossless transmission.
2960
2960
2961 Use --partial to ensure a changeset will be created from the patch
2961 Use --partial to ensure a changeset will be created from the patch
2962 even if some hunks fail to apply. Hunks that fail to apply will be
2962 even if some hunks fail to apply. Hunks that fail to apply will be
2963 written to a <target-file>.rej file. Conflicts can then be resolved
2963 written to a <target-file>.rej file. Conflicts can then be resolved
2964 by hand before :hg:`commit --amend` is run to update the created
2964 by hand before :hg:`commit --amend` is run to update the created
2965 changeset. This flag exists to let people import patches that
2965 changeset. This flag exists to let people import patches that
2966 partially apply without losing the associated metadata (author,
2966 partially apply without losing the associated metadata (author,
2967 date, description, ...).
2967 date, description, ...).
2968
2968
2969 .. note::
2969 .. note::
2970
2970
2971 When no hunks apply cleanly, :hg:`import --partial` will create
2971 When no hunks apply cleanly, :hg:`import --partial` will create
2972 an empty changeset, importing only the patch metadata.
2972 an empty changeset, importing only the patch metadata.
2973
2973
2974 With -s/--similarity, hg will attempt to discover renames and
2974 With -s/--similarity, hg will attempt to discover renames and
2975 copies in the patch in the same way as :hg:`addremove`.
2975 copies in the patch in the same way as :hg:`addremove`.
2976
2976
2977 It is possible to use external patch programs to perform the patch
2977 It is possible to use external patch programs to perform the patch
2978 by setting the ``ui.patch`` configuration option. For the default
2978 by setting the ``ui.patch`` configuration option. For the default
2979 internal tool, the fuzz can also be configured via ``patch.fuzz``.
2979 internal tool, the fuzz can also be configured via ``patch.fuzz``.
2980 See :hg:`help config` for more information about configuration
2980 See :hg:`help config` for more information about configuration
2981 files and how to use these options.
2981 files and how to use these options.
2982
2982
2983 See :hg:`help dates` for a list of formats valid for -d/--date.
2983 See :hg:`help dates` for a list of formats valid for -d/--date.
2984
2984
2985 .. container:: verbose
2985 .. container:: verbose
2986
2986
2987 Examples:
2987 Examples:
2988
2988
2989 - import a traditional patch from a website and detect renames::
2989 - import a traditional patch from a website and detect renames::
2990
2990
2991 hg import -s 80 http://example.com/bugfix.patch
2991 hg import -s 80 http://example.com/bugfix.patch
2992
2992
2993 - import a changeset from an hgweb server::
2993 - import a changeset from an hgweb server::
2994
2994
2995 hg import https://www.mercurial-scm.org/repo/hg/rev/5ca8c111e9aa
2995 hg import https://www.mercurial-scm.org/repo/hg/rev/5ca8c111e9aa
2996
2996
2997 - import all the patches in an Unix-style mbox::
2997 - import all the patches in an Unix-style mbox::
2998
2998
2999 hg import incoming-patches.mbox
2999 hg import incoming-patches.mbox
3000
3000
3001 - import patches from stdin::
3001 - import patches from stdin::
3002
3002
3003 hg import -
3003 hg import -
3004
3004
3005 - attempt to exactly restore an exported changeset (not always
3005 - attempt to exactly restore an exported changeset (not always
3006 possible)::
3006 possible)::
3007
3007
3008 hg import --exact proposed-fix.patch
3008 hg import --exact proposed-fix.patch
3009
3009
3010 - use an external tool to apply a patch which is too fuzzy for
3010 - use an external tool to apply a patch which is too fuzzy for
3011 the default internal tool.
3011 the default internal tool.
3012
3012
3013 hg import --config ui.patch="patch --merge" fuzzy.patch
3013 hg import --config ui.patch="patch --merge" fuzzy.patch
3014
3014
3015 - change the default fuzzing from 2 to a less strict 7
3015 - change the default fuzzing from 2 to a less strict 7
3016
3016
3017 hg import --config ui.fuzz=7 fuzz.patch
3017 hg import --config ui.fuzz=7 fuzz.patch
3018
3018
3019 Returns 0 on success, 1 on partial success (see --partial).
3019 Returns 0 on success, 1 on partial success (see --partial).
3020 """
3020 """
3021
3021
3022 opts = pycompat.byteskwargs(opts)
3022 opts = pycompat.byteskwargs(opts)
3023 if not patch1:
3023 if not patch1:
3024 raise error.Abort(_('need at least one patch to import'))
3024 raise error.Abort(_('need at least one patch to import'))
3025
3025
3026 patches = (patch1,) + patches
3026 patches = (patch1,) + patches
3027
3027
3028 date = opts.get('date')
3028 date = opts.get('date')
3029 if date:
3029 if date:
3030 opts['date'] = dateutil.parsedate(date)
3030 opts['date'] = dateutil.parsedate(date)
3031
3031
3032 exact = opts.get('exact')
3032 exact = opts.get('exact')
3033 update = not opts.get('bypass')
3033 update = not opts.get('bypass')
3034 if not update and opts.get('no_commit'):
3034 if not update and opts.get('no_commit'):
3035 raise error.Abort(_('cannot use --no-commit with --bypass'))
3035 raise error.Abort(_('cannot use --no-commit with --bypass'))
3036 try:
3036 try:
3037 sim = float(opts.get('similarity') or 0)
3037 sim = float(opts.get('similarity') or 0)
3038 except ValueError:
3038 except ValueError:
3039 raise error.Abort(_('similarity must be a number'))
3039 raise error.Abort(_('similarity must be a number'))
3040 if sim < 0 or sim > 100:
3040 if sim < 0 or sim > 100:
3041 raise error.Abort(_('similarity must be between 0 and 100'))
3041 raise error.Abort(_('similarity must be between 0 and 100'))
3042 if sim and not update:
3042 if sim and not update:
3043 raise error.Abort(_('cannot use --similarity with --bypass'))
3043 raise error.Abort(_('cannot use --similarity with --bypass'))
3044 if exact:
3044 if exact:
3045 if opts.get('edit'):
3045 if opts.get('edit'):
3046 raise error.Abort(_('cannot use --exact with --edit'))
3046 raise error.Abort(_('cannot use --exact with --edit'))
3047 if opts.get('prefix'):
3047 if opts.get('prefix'):
3048 raise error.Abort(_('cannot use --exact with --prefix'))
3048 raise error.Abort(_('cannot use --exact with --prefix'))
3049
3049
3050 base = opts["base"]
3050 base = opts["base"]
3051 wlock = dsguard = lock = tr = None
3051 wlock = dsguard = lock = tr = None
3052 msgs = []
3052 msgs = []
3053 ret = 0
3053 ret = 0
3054
3054
3055
3055
3056 try:
3056 try:
3057 wlock = repo.wlock()
3057 wlock = repo.wlock()
3058
3058
3059 if update:
3059 if update:
3060 cmdutil.checkunfinished(repo)
3060 cmdutil.checkunfinished(repo)
3061 if (exact or not opts.get('force')):
3061 if (exact or not opts.get('force')):
3062 cmdutil.bailifchanged(repo)
3062 cmdutil.bailifchanged(repo)
3063
3063
3064 if not opts.get('no_commit'):
3064 if not opts.get('no_commit'):
3065 lock = repo.lock()
3065 lock = repo.lock()
3066 tr = repo.transaction('import')
3066 tr = repo.transaction('import')
3067 else:
3067 else:
3068 dsguard = dirstateguard.dirstateguard(repo, 'import')
3068 dsguard = dirstateguard.dirstateguard(repo, 'import')
3069 parents = repo[None].parents()
3069 parents = repo[None].parents()
3070 for patchurl in patches:
3070 for patchurl in patches:
3071 if patchurl == '-':
3071 if patchurl == '-':
3072 ui.status(_('applying patch from stdin\n'))
3072 ui.status(_('applying patch from stdin\n'))
3073 patchfile = ui.fin
3073 patchfile = ui.fin
3074 patchurl = 'stdin' # for error message
3074 patchurl = 'stdin' # for error message
3075 else:
3075 else:
3076 patchurl = os.path.join(base, patchurl)
3076 patchurl = os.path.join(base, patchurl)
3077 ui.status(_('applying %s\n') % patchurl)
3077 ui.status(_('applying %s\n') % patchurl)
3078 patchfile = hg.openpath(ui, patchurl)
3078 patchfile = hg.openpath(ui, patchurl)
3079
3079
3080 haspatch = False
3080 haspatch = False
3081 for hunk in patch.split(patchfile):
3081 for hunk in patch.split(patchfile):
3082 (msg, node, rej) = cmdutil.tryimportone(ui, repo, hunk,
3082 (msg, node, rej) = cmdutil.tryimportone(ui, repo, hunk,
3083 parents, opts,
3083 parents, opts,
3084 msgs, hg.clean)
3084 msgs, hg.clean)
3085 if msg:
3085 if msg:
3086 haspatch = True
3086 haspatch = True
3087 ui.note(msg + '\n')
3087 ui.note(msg + '\n')
3088 if update or exact:
3088 if update or exact:
3089 parents = repo[None].parents()
3089 parents = repo[None].parents()
3090 else:
3090 else:
3091 parents = [repo[node]]
3091 parents = [repo[node]]
3092 if rej:
3092 if rej:
3093 ui.write_err(_("patch applied partially\n"))
3093 ui.write_err(_("patch applied partially\n"))
3094 ui.write_err(_("(fix the .rej files and run "
3094 ui.write_err(_("(fix the .rej files and run "
3095 "`hg commit --amend`)\n"))
3095 "`hg commit --amend`)\n"))
3096 ret = 1
3096 ret = 1
3097 break
3097 break
3098
3098
3099 if not haspatch:
3099 if not haspatch:
3100 raise error.Abort(_('%s: no diffs found') % patchurl)
3100 raise error.Abort(_('%s: no diffs found') % patchurl)
3101
3101
3102 if tr:
3102 if tr:
3103 tr.close()
3103 tr.close()
3104 if msgs:
3104 if msgs:
3105 repo.savecommitmessage('\n* * *\n'.join(msgs))
3105 repo.savecommitmessage('\n* * *\n'.join(msgs))
3106 if dsguard:
3106 if dsguard:
3107 dsguard.close()
3107 dsguard.close()
3108 return ret
3108 return ret
3109 finally:
3109 finally:
3110 if tr:
3110 if tr:
3111 tr.release()
3111 tr.release()
3112 release(lock, dsguard, wlock)
3112 release(lock, dsguard, wlock)
3113
3113
3114 @command('incoming|in',
3114 @command('incoming|in',
3115 [('f', 'force', None,
3115 [('f', 'force', None,
3116 _('run even if remote repository is unrelated')),
3116 _('run even if remote repository is unrelated')),
3117 ('n', 'newest-first', None, _('show newest record first')),
3117 ('n', 'newest-first', None, _('show newest record first')),
3118 ('', 'bundle', '',
3118 ('', 'bundle', '',
3119 _('file to store the bundles into'), _('FILE')),
3119 _('file to store the bundles into'), _('FILE')),
3120 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
3120 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
3121 ('B', 'bookmarks', False, _("compare bookmarks")),
3121 ('B', 'bookmarks', False, _("compare bookmarks")),
3122 ('b', 'branch', [],
3122 ('b', 'branch', [],
3123 _('a specific branch you would like to pull'), _('BRANCH')),
3123 _('a specific branch you would like to pull'), _('BRANCH')),
3124 ] + logopts + remoteopts + subrepoopts,
3124 ] + logopts + remoteopts + subrepoopts,
3125 _('[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'))
3125 _('[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'))
3126 def incoming(ui, repo, source="default", **opts):
3126 def incoming(ui, repo, source="default", **opts):
3127 """show new changesets found in source
3127 """show new changesets found in source
3128
3128
3129 Show new changesets found in the specified path/URL or the default
3129 Show new changesets found in the specified path/URL or the default
3130 pull location. These are the changesets that would have been pulled
3130 pull location. These are the changesets that would have been pulled
3131 by :hg:`pull` at the time you issued this command.
3131 by :hg:`pull` at the time you issued this command.
3132
3132
3133 See pull for valid source format details.
3133 See pull for valid source format details.
3134
3134
3135 .. container:: verbose
3135 .. container:: verbose
3136
3136
3137 With -B/--bookmarks, the result of bookmark comparison between
3137 With -B/--bookmarks, the result of bookmark comparison between
3138 local and remote repositories is displayed. With -v/--verbose,
3138 local and remote repositories is displayed. With -v/--verbose,
3139 status is also displayed for each bookmark like below::
3139 status is also displayed for each bookmark like below::
3140
3140
3141 BM1 01234567890a added
3141 BM1 01234567890a added
3142 BM2 1234567890ab advanced
3142 BM2 1234567890ab advanced
3143 BM3 234567890abc diverged
3143 BM3 234567890abc diverged
3144 BM4 34567890abcd changed
3144 BM4 34567890abcd changed
3145
3145
3146 The action taken locally when pulling depends on the
3146 The action taken locally when pulling depends on the
3147 status of each bookmark:
3147 status of each bookmark:
3148
3148
3149 :``added``: pull will create it
3149 :``added``: pull will create it
3150 :``advanced``: pull will update it
3150 :``advanced``: pull will update it
3151 :``diverged``: pull will create a divergent bookmark
3151 :``diverged``: pull will create a divergent bookmark
3152 :``changed``: result depends on remote changesets
3152 :``changed``: result depends on remote changesets
3153
3153
3154 From the point of view of pulling behavior, bookmark
3154 From the point of view of pulling behavior, bookmark
3155 existing only in the remote repository are treated as ``added``,
3155 existing only in the remote repository are treated as ``added``,
3156 even if it is in fact locally deleted.
3156 even if it is in fact locally deleted.
3157
3157
3158 .. container:: verbose
3158 .. container:: verbose
3159
3159
3160 For remote repository, using --bundle avoids downloading the
3160 For remote repository, using --bundle avoids downloading the
3161 changesets twice if the incoming is followed by a pull.
3161 changesets twice if the incoming is followed by a pull.
3162
3162
3163 Examples:
3163 Examples:
3164
3164
3165 - show incoming changes with patches and full description::
3165 - show incoming changes with patches and full description::
3166
3166
3167 hg incoming -vp
3167 hg incoming -vp
3168
3168
3169 - show incoming changes excluding merges, store a bundle::
3169 - show incoming changes excluding merges, store a bundle::
3170
3170
3171 hg in -vpM --bundle incoming.hg
3171 hg in -vpM --bundle incoming.hg
3172 hg pull incoming.hg
3172 hg pull incoming.hg
3173
3173
3174 - briefly list changes inside a bundle::
3174 - briefly list changes inside a bundle::
3175
3175
3176 hg in changes.hg -T "{desc|firstline}\\n"
3176 hg in changes.hg -T "{desc|firstline}\\n"
3177
3177
3178 Returns 0 if there are incoming changes, 1 otherwise.
3178 Returns 0 if there are incoming changes, 1 otherwise.
3179 """
3179 """
3180 opts = pycompat.byteskwargs(opts)
3180 opts = pycompat.byteskwargs(opts)
3181 if opts.get('graph'):
3181 if opts.get('graph'):
3182 logcmdutil.checkunsupportedgraphflags([], opts)
3182 logcmdutil.checkunsupportedgraphflags([], opts)
3183 def display(other, chlist, displayer):
3183 def display(other, chlist, displayer):
3184 revdag = logcmdutil.graphrevs(other, chlist, opts)
3184 revdag = logcmdutil.graphrevs(other, chlist, opts)
3185 logcmdutil.displaygraph(ui, repo, revdag, displayer,
3185 logcmdutil.displaygraph(ui, repo, revdag, displayer,
3186 graphmod.asciiedges)
3186 graphmod.asciiedges)
3187
3187
3188 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
3188 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
3189 return 0
3189 return 0
3190
3190
3191 if opts.get('bundle') and opts.get('subrepos'):
3191 if opts.get('bundle') and opts.get('subrepos'):
3192 raise error.Abort(_('cannot combine --bundle and --subrepos'))
3192 raise error.Abort(_('cannot combine --bundle and --subrepos'))
3193
3193
3194 if opts.get('bookmarks'):
3194 if opts.get('bookmarks'):
3195 source, branches = hg.parseurl(ui.expandpath(source),
3195 source, branches = hg.parseurl(ui.expandpath(source),
3196 opts.get('branch'))
3196 opts.get('branch'))
3197 other = hg.peer(repo, opts, source)
3197 other = hg.peer(repo, opts, source)
3198 if 'bookmarks' not in other.listkeys('namespaces'):
3198 if 'bookmarks' not in other.listkeys('namespaces'):
3199 ui.warn(_("remote doesn't support bookmarks\n"))
3199 ui.warn(_("remote doesn't support bookmarks\n"))
3200 return 0
3200 return 0
3201 ui.pager('incoming')
3201 ui.pager('incoming')
3202 ui.status(_('comparing with %s\n') % util.hidepassword(source))
3202 ui.status(_('comparing with %s\n') % util.hidepassword(source))
3203 return bookmarks.incoming(ui, repo, other)
3203 return bookmarks.incoming(ui, repo, other)
3204
3204
3205 repo._subtoppath = ui.expandpath(source)
3205 repo._subtoppath = ui.expandpath(source)
3206 try:
3206 try:
3207 return hg.incoming(ui, repo, source, opts)
3207 return hg.incoming(ui, repo, source, opts)
3208 finally:
3208 finally:
3209 del repo._subtoppath
3209 del repo._subtoppath
3210
3210
3211
3211
3212 @command('^init', remoteopts, _('[-e CMD] [--remotecmd CMD] [DEST]'),
3212 @command('^init', remoteopts, _('[-e CMD] [--remotecmd CMD] [DEST]'),
3213 norepo=True)
3213 norepo=True)
3214 def init(ui, dest=".", **opts):
3214 def init(ui, dest=".", **opts):
3215 """create a new repository in the given directory
3215 """create a new repository in the given directory
3216
3216
3217 Initialize a new repository in the given directory. If the given
3217 Initialize a new repository in the given directory. If the given
3218 directory does not exist, it will be created.
3218 directory does not exist, it will be created.
3219
3219
3220 If no directory is given, the current directory is used.
3220 If no directory is given, the current directory is used.
3221
3221
3222 It is possible to specify an ``ssh://`` URL as the destination.
3222 It is possible to specify an ``ssh://`` URL as the destination.
3223 See :hg:`help urls` for more information.
3223 See :hg:`help urls` for more information.
3224
3224
3225 Returns 0 on success.
3225 Returns 0 on success.
3226 """
3226 """
3227 opts = pycompat.byteskwargs(opts)
3227 opts = pycompat.byteskwargs(opts)
3228 hg.peer(ui, opts, ui.expandpath(dest), create=True)
3228 hg.peer(ui, opts, ui.expandpath(dest), create=True)
3229
3229
3230 @command('locate',
3230 @command('locate',
3231 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
3231 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
3232 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
3232 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
3233 ('f', 'fullpath', None, _('print complete paths from the filesystem root')),
3233 ('f', 'fullpath', None, _('print complete paths from the filesystem root')),
3234 ] + walkopts,
3234 ] + walkopts,
3235 _('[OPTION]... [PATTERN]...'))
3235 _('[OPTION]... [PATTERN]...'))
3236 def locate(ui, repo, *pats, **opts):
3236 def locate(ui, repo, *pats, **opts):
3237 """locate files matching specific patterns (DEPRECATED)
3237 """locate files matching specific patterns (DEPRECATED)
3238
3238
3239 Print files under Mercurial control in the working directory whose
3239 Print files under Mercurial control in the working directory whose
3240 names match the given patterns.
3240 names match the given patterns.
3241
3241
3242 By default, this command searches all directories in the working
3242 By default, this command searches all directories in the working
3243 directory. To search just the current directory and its
3243 directory. To search just the current directory and its
3244 subdirectories, use "--include .".
3244 subdirectories, use "--include .".
3245
3245
3246 If no patterns are given to match, this command prints the names
3246 If no patterns are given to match, this command prints the names
3247 of all files under Mercurial control in the working directory.
3247 of all files under Mercurial control in the working directory.
3248
3248
3249 If you want to feed the output of this command into the "xargs"
3249 If you want to feed the output of this command into the "xargs"
3250 command, use the -0 option to both this command and "xargs". This
3250 command, use the -0 option to both this command and "xargs". This
3251 will avoid the problem of "xargs" treating single filenames that
3251 will avoid the problem of "xargs" treating single filenames that
3252 contain whitespace as multiple filenames.
3252 contain whitespace as multiple filenames.
3253
3253
3254 See :hg:`help files` for a more versatile command.
3254 See :hg:`help files` for a more versatile command.
3255
3255
3256 Returns 0 if a match is found, 1 otherwise.
3256 Returns 0 if a match is found, 1 otherwise.
3257 """
3257 """
3258 opts = pycompat.byteskwargs(opts)
3258 opts = pycompat.byteskwargs(opts)
3259 if opts.get('print0'):
3259 if opts.get('print0'):
3260 end = '\0'
3260 end = '\0'
3261 else:
3261 else:
3262 end = '\n'
3262 end = '\n'
3263 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
3263 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
3264
3264
3265 ret = 1
3265 ret = 1
3266 m = scmutil.match(ctx, pats, opts, default='relglob',
3266 m = scmutil.match(ctx, pats, opts, default='relglob',
3267 badfn=lambda x, y: False)
3267 badfn=lambda x, y: False)
3268
3268
3269 ui.pager('locate')
3269 ui.pager('locate')
3270 for abs in ctx.matches(m):
3270 for abs in ctx.matches(m):
3271 if opts.get('fullpath'):
3271 if opts.get('fullpath'):
3272 ui.write(repo.wjoin(abs), end)
3272 ui.write(repo.wjoin(abs), end)
3273 else:
3273 else:
3274 ui.write(((pats and m.rel(abs)) or abs), end)
3274 ui.write(((pats and m.rel(abs)) or abs), end)
3275 ret = 0
3275 ret = 0
3276
3276
3277 return ret
3277 return ret
3278
3278
3279 @command('^log|history',
3279 @command('^log|history',
3280 [('f', 'follow', None,
3280 [('f', 'follow', None,
3281 _('follow changeset history, or file history across copies and renames')),
3281 _('follow changeset history, or file history across copies and renames')),
3282 ('', 'follow-first', None,
3282 ('', 'follow-first', None,
3283 _('only follow the first parent of merge changesets (DEPRECATED)')),
3283 _('only follow the first parent of merge changesets (DEPRECATED)')),
3284 ('d', 'date', '', _('show revisions matching date spec'), _('DATE')),
3284 ('d', 'date', '', _('show revisions matching date spec'), _('DATE')),
3285 ('C', 'copies', None, _('show copied files')),
3285 ('C', 'copies', None, _('show copied files')),
3286 ('k', 'keyword', [],
3286 ('k', 'keyword', [],
3287 _('do case-insensitive search for a given text'), _('TEXT')),
3287 _('do case-insensitive search for a given text'), _('TEXT')),
3288 ('r', 'rev', [], _('show the specified revision or revset'), _('REV')),
3288 ('r', 'rev', [], _('show the specified revision or revset'), _('REV')),
3289 ('L', 'line-range', [],
3289 ('L', 'line-range', [],
3290 _('follow line range of specified file (EXPERIMENTAL)'),
3290 _('follow line range of specified file (EXPERIMENTAL)'),
3291 _('FILE,RANGE')),
3291 _('FILE,RANGE')),
3292 ('', 'removed', None, _('include revisions where files were removed')),
3292 ('', 'removed', None, _('include revisions where files were removed')),
3293 ('m', 'only-merges', None, _('show only merges (DEPRECATED)')),
3293 ('m', 'only-merges', None, _('show only merges (DEPRECATED)')),
3294 ('u', 'user', [], _('revisions committed by user'), _('USER')),
3294 ('u', 'user', [], _('revisions committed by user'), _('USER')),
3295 ('', 'only-branch', [],
3295 ('', 'only-branch', [],
3296 _('show only changesets within the given named branch (DEPRECATED)'),
3296 _('show only changesets within the given named branch (DEPRECATED)'),
3297 _('BRANCH')),
3297 _('BRANCH')),
3298 ('b', 'branch', [],
3298 ('b', 'branch', [],
3299 _('show changesets within the given named branch'), _('BRANCH')),
3299 _('show changesets within the given named branch'), _('BRANCH')),
3300 ('P', 'prune', [],
3300 ('P', 'prune', [],
3301 _('do not display revision or any of its ancestors'), _('REV')),
3301 _('do not display revision or any of its ancestors'), _('REV')),
3302 ] + logopts + walkopts,
3302 ] + logopts + walkopts,
3303 _('[OPTION]... [FILE]'),
3303 _('[OPTION]... [FILE]'),
3304 inferrepo=True, cmdtype=readonly)
3304 inferrepo=True, cmdtype=readonly)
3305 def log(ui, repo, *pats, **opts):
3305 def log(ui, repo, *pats, **opts):
3306 """show revision history of entire repository or files
3306 """show revision history of entire repository or files
3307
3307
3308 Print the revision history of the specified files or the entire
3308 Print the revision history of the specified files or the entire
3309 project.
3309 project.
3310
3310
3311 If no revision range is specified, the default is ``tip:0`` unless
3311 If no revision range is specified, the default is ``tip:0`` unless
3312 --follow is set, in which case the working directory parent is
3312 --follow is set, in which case the working directory parent is
3313 used as the starting revision.
3313 used as the starting revision.
3314
3314
3315 File history is shown without following rename or copy history of
3315 File history is shown without following rename or copy history of
3316 files. Use -f/--follow with a filename to follow history across
3316 files. Use -f/--follow with a filename to follow history across
3317 renames and copies. --follow without a filename will only show
3317 renames and copies. --follow without a filename will only show
3318 ancestors of the starting revision.
3318 ancestors of the starting revision.
3319
3319
3320 By default this command prints revision number and changeset id,
3320 By default this command prints revision number and changeset id,
3321 tags, non-trivial parents, user, date and time, and a summary for
3321 tags, non-trivial parents, user, date and time, and a summary for
3322 each commit. When the -v/--verbose switch is used, the list of
3322 each commit. When the -v/--verbose switch is used, the list of
3323 changed files and full commit message are shown.
3323 changed files and full commit message are shown.
3324
3324
3325 With --graph the revisions are shown as an ASCII art DAG with the most
3325 With --graph the revisions are shown as an ASCII art DAG with the most
3326 recent changeset at the top.
3326 recent changeset at the top.
3327 'o' is a changeset, '@' is a working directory parent, '_' closes a branch,
3327 'o' is a changeset, '@' is a working directory parent, '_' closes a branch,
3328 'x' is obsolete, '*' is unstable, and '+' represents a fork where the
3328 'x' is obsolete, '*' is unstable, and '+' represents a fork where the
3329 changeset from the lines below is a parent of the 'o' merge on the same
3329 changeset from the lines below is a parent of the 'o' merge on the same
3330 line.
3330 line.
3331 Paths in the DAG are represented with '|', '/' and so forth. ':' in place
3331 Paths in the DAG are represented with '|', '/' and so forth. ':' in place
3332 of a '|' indicates one or more revisions in a path are omitted.
3332 of a '|' indicates one or more revisions in a path are omitted.
3333
3333
3334 .. container:: verbose
3334 .. container:: verbose
3335
3335
3336 Use -L/--line-range FILE,M:N options to follow the history of lines
3336 Use -L/--line-range FILE,M:N options to follow the history of lines
3337 from M to N in FILE. With -p/--patch only diff hunks affecting
3337 from M to N in FILE. With -p/--patch only diff hunks affecting
3338 specified line range will be shown. This option requires --follow;
3338 specified line range will be shown. This option requires --follow;
3339 it can be specified multiple times. Currently, this option is not
3339 it can be specified multiple times. Currently, this option is not
3340 compatible with --graph. This option is experimental.
3340 compatible with --graph. This option is experimental.
3341
3341
3342 .. note::
3342 .. note::
3343
3343
3344 :hg:`log --patch` may generate unexpected diff output for merge
3344 :hg:`log --patch` may generate unexpected diff output for merge
3345 changesets, as it will only compare the merge changeset against
3345 changesets, as it will only compare the merge changeset against
3346 its first parent. Also, only files different from BOTH parents
3346 its first parent. Also, only files different from BOTH parents
3347 will appear in files:.
3347 will appear in files:.
3348
3348
3349 .. note::
3349 .. note::
3350
3350
3351 For performance reasons, :hg:`log FILE` may omit duplicate changes
3351 For performance reasons, :hg:`log FILE` may omit duplicate changes
3352 made on branches and will not show removals or mode changes. To
3352 made on branches and will not show removals or mode changes. To
3353 see all such changes, use the --removed switch.
3353 see all such changes, use the --removed switch.
3354
3354
3355 .. container:: verbose
3355 .. container:: verbose
3356
3356
3357 .. note::
3357 .. note::
3358
3358
3359 The history resulting from -L/--line-range options depends on diff
3359 The history resulting from -L/--line-range options depends on diff
3360 options; for instance if white-spaces are ignored, respective changes
3360 options; for instance if white-spaces are ignored, respective changes
3361 with only white-spaces in specified line range will not be listed.
3361 with only white-spaces in specified line range will not be listed.
3362
3362
3363 .. container:: verbose
3363 .. container:: verbose
3364
3364
3365 Some examples:
3365 Some examples:
3366
3366
3367 - changesets with full descriptions and file lists::
3367 - changesets with full descriptions and file lists::
3368
3368
3369 hg log -v
3369 hg log -v
3370
3370
3371 - changesets ancestral to the working directory::
3371 - changesets ancestral to the working directory::
3372
3372
3373 hg log -f
3373 hg log -f
3374
3374
3375 - last 10 commits on the current branch::
3375 - last 10 commits on the current branch::
3376
3376
3377 hg log -l 10 -b .
3377 hg log -l 10 -b .
3378
3378
3379 - changesets showing all modifications of a file, including removals::
3379 - changesets showing all modifications of a file, including removals::
3380
3380
3381 hg log --removed file.c
3381 hg log --removed file.c
3382
3382
3383 - all changesets that touch a directory, with diffs, excluding merges::
3383 - all changesets that touch a directory, with diffs, excluding merges::
3384
3384
3385 hg log -Mp lib/
3385 hg log -Mp lib/
3386
3386
3387 - all revision numbers that match a keyword::
3387 - all revision numbers that match a keyword::
3388
3388
3389 hg log -k bug --template "{rev}\\n"
3389 hg log -k bug --template "{rev}\\n"
3390
3390
3391 - the full hash identifier of the working directory parent::
3391 - the full hash identifier of the working directory parent::
3392
3392
3393 hg log -r . --template "{node}\\n"
3393 hg log -r . --template "{node}\\n"
3394
3394
3395 - list available log templates::
3395 - list available log templates::
3396
3396
3397 hg log -T list
3397 hg log -T list
3398
3398
3399 - check if a given changeset is included in a tagged release::
3399 - check if a given changeset is included in a tagged release::
3400
3400
3401 hg log -r "a21ccf and ancestor(1.9)"
3401 hg log -r "a21ccf and ancestor(1.9)"
3402
3402
3403 - find all changesets by some user in a date range::
3403 - find all changesets by some user in a date range::
3404
3404
3405 hg log -k alice -d "may 2008 to jul 2008"
3405 hg log -k alice -d "may 2008 to jul 2008"
3406
3406
3407 - summary of all changesets after the last tag::
3407 - summary of all changesets after the last tag::
3408
3408
3409 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
3409 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
3410
3410
3411 - changesets touching lines 13 to 23 for file.c::
3411 - changesets touching lines 13 to 23 for file.c::
3412
3412
3413 hg log -L file.c,13:23
3413 hg log -L file.c,13:23
3414
3414
3415 - changesets touching lines 13 to 23 for file.c and lines 2 to 6 of
3415 - changesets touching lines 13 to 23 for file.c and lines 2 to 6 of
3416 main.c with patch::
3416 main.c with patch::
3417
3417
3418 hg log -L file.c,13:23 -L main.c,2:6 -p
3418 hg log -L file.c,13:23 -L main.c,2:6 -p
3419
3419
3420 See :hg:`help dates` for a list of formats valid for -d/--date.
3420 See :hg:`help dates` for a list of formats valid for -d/--date.
3421
3421
3422 See :hg:`help revisions` for more about specifying and ordering
3422 See :hg:`help revisions` for more about specifying and ordering
3423 revisions.
3423 revisions.
3424
3424
3425 See :hg:`help templates` for more about pre-packaged styles and
3425 See :hg:`help templates` for more about pre-packaged styles and
3426 specifying custom templates. The default template used by the log
3426 specifying custom templates. The default template used by the log
3427 command can be customized via the ``ui.logtemplate`` configuration
3427 command can be customized via the ``ui.logtemplate`` configuration
3428 setting.
3428 setting.
3429
3429
3430 Returns 0 on success.
3430 Returns 0 on success.
3431
3431
3432 """
3432 """
3433 opts = pycompat.byteskwargs(opts)
3433 opts = pycompat.byteskwargs(opts)
3434 linerange = opts.get('line_range')
3434 linerange = opts.get('line_range')
3435
3435
3436 if linerange and not opts.get('follow'):
3436 if linerange and not opts.get('follow'):
3437 raise error.Abort(_('--line-range requires --follow'))
3437 raise error.Abort(_('--line-range requires --follow'))
3438
3438
3439 if linerange and pats:
3439 if linerange and pats:
3440 # TODO: take pats as patterns with no line-range filter
3440 # TODO: take pats as patterns with no line-range filter
3441 raise error.Abort(
3441 raise error.Abort(
3442 _('FILE arguments are not compatible with --line-range option')
3442 _('FILE arguments are not compatible with --line-range option')
3443 )
3443 )
3444
3444
3445 repo = scmutil.unhidehashlikerevs(repo, opts.get('rev'), 'nowarn')
3445 repo = scmutil.unhidehashlikerevs(repo, opts.get('rev'), 'nowarn')
3446 revs, differ = logcmdutil.getrevs(repo, pats, opts)
3446 revs, differ = logcmdutil.getrevs(repo, pats, opts)
3447 if linerange:
3447 if linerange:
3448 # TODO: should follow file history from logcmdutil._initialrevs(),
3448 # TODO: should follow file history from logcmdutil._initialrevs(),
3449 # then filter the result by logcmdutil._makerevset() and --limit
3449 # then filter the result by logcmdutil._makerevset() and --limit
3450 revs, differ = logcmdutil.getlinerangerevs(repo, revs, opts)
3450 revs, differ = logcmdutil.getlinerangerevs(repo, revs, opts)
3451
3451
3452 getrenamed = None
3452 getrenamed = None
3453 if opts.get('copies'):
3453 if opts.get('copies'):
3454 endrev = None
3454 endrev = None
3455 if opts.get('rev'):
3455 if opts.get('rev'):
3456 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
3456 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
3457 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
3457 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
3458
3458
3459 ui.pager('log')
3459 ui.pager('log')
3460 displayer = logcmdutil.changesetdisplayer(ui, repo, opts, differ,
3460 displayer = logcmdutil.changesetdisplayer(ui, repo, opts, differ,
3461 buffered=True)
3461 buffered=True)
3462 if opts.get('graph'):
3462 if opts.get('graph'):
3463 displayfn = logcmdutil.displaygraphrevs
3463 displayfn = logcmdutil.displaygraphrevs
3464 else:
3464 else:
3465 displayfn = logcmdutil.displayrevs
3465 displayfn = logcmdutil.displayrevs
3466 displayfn(ui, repo, revs, displayer, getrenamed)
3466 displayfn(ui, repo, revs, displayer, getrenamed)
3467
3467
3468 @command('manifest',
3468 @command('manifest',
3469 [('r', 'rev', '', _('revision to display'), _('REV')),
3469 [('r', 'rev', '', _('revision to display'), _('REV')),
3470 ('', 'all', False, _("list files from all revisions"))]
3470 ('', 'all', False, _("list files from all revisions"))]
3471 + formatteropts,
3471 + formatteropts,
3472 _('[-r REV]'), cmdtype=readonly)
3472 _('[-r REV]'), cmdtype=readonly)
3473 def manifest(ui, repo, node=None, rev=None, **opts):
3473 def manifest(ui, repo, node=None, rev=None, **opts):
3474 """output the current or given revision of the project manifest
3474 """output the current or given revision of the project manifest
3475
3475
3476 Print a list of version controlled files for the given revision.
3476 Print a list of version controlled files for the given revision.
3477 If no revision is given, the first parent of the working directory
3477 If no revision is given, the first parent of the working directory
3478 is used, or the null revision if no revision is checked out.
3478 is used, or the null revision if no revision is checked out.
3479
3479
3480 With -v, print file permissions, symlink and executable bits.
3480 With -v, print file permissions, symlink and executable bits.
3481 With --debug, print file revision hashes.
3481 With --debug, print file revision hashes.
3482
3482
3483 If option --all is specified, the list of all files from all revisions
3483 If option --all is specified, the list of all files from all revisions
3484 is printed. This includes deleted and renamed files.
3484 is printed. This includes deleted and renamed files.
3485
3485
3486 Returns 0 on success.
3486 Returns 0 on success.
3487 """
3487 """
3488 opts = pycompat.byteskwargs(opts)
3488 opts = pycompat.byteskwargs(opts)
3489 fm = ui.formatter('manifest', opts)
3489 fm = ui.formatter('manifest', opts)
3490
3490
3491 if opts.get('all'):
3491 if opts.get('all'):
3492 if rev or node:
3492 if rev or node:
3493 raise error.Abort(_("can't specify a revision with --all"))
3493 raise error.Abort(_("can't specify a revision with --all"))
3494
3494
3495 res = []
3495 res = []
3496 prefix = "data/"
3496 prefix = "data/"
3497 suffix = ".i"
3497 suffix = ".i"
3498 plen = len(prefix)
3498 plen = len(prefix)
3499 slen = len(suffix)
3499 slen = len(suffix)
3500 with repo.lock():
3500 with repo.lock():
3501 for fn, b, size in repo.store.datafiles():
3501 for fn, b, size in repo.store.datafiles():
3502 if size != 0 and fn[-slen:] == suffix and fn[:plen] == prefix:
3502 if size != 0 and fn[-slen:] == suffix and fn[:plen] == prefix:
3503 res.append(fn[plen:-slen])
3503 res.append(fn[plen:-slen])
3504 ui.pager('manifest')
3504 ui.pager('manifest')
3505 for f in res:
3505 for f in res:
3506 fm.startitem()
3506 fm.startitem()
3507 fm.write("path", '%s\n', f)
3507 fm.write("path", '%s\n', f)
3508 fm.end()
3508 fm.end()
3509 return
3509 return
3510
3510
3511 if rev and node:
3511 if rev and node:
3512 raise error.Abort(_("please specify just one revision"))
3512 raise error.Abort(_("please specify just one revision"))
3513
3513
3514 if not node:
3514 if not node:
3515 node = rev
3515 node = rev
3516
3516
3517 char = {'l': '@', 'x': '*', '': '', 't': 'd'}
3517 char = {'l': '@', 'x': '*', '': '', 't': 'd'}
3518 mode = {'l': '644', 'x': '755', '': '644', 't': '755'}
3518 mode = {'l': '644', 'x': '755', '': '644', 't': '755'}
3519 if node:
3519 if node:
3520 repo = scmutil.unhidehashlikerevs(repo, [node], 'nowarn')
3520 repo = scmutil.unhidehashlikerevs(repo, [node], 'nowarn')
3521 ctx = scmutil.revsingle(repo, node)
3521 ctx = scmutil.revsingle(repo, node)
3522 mf = ctx.manifest()
3522 mf = ctx.manifest()
3523 ui.pager('manifest')
3523 ui.pager('manifest')
3524 for f in ctx:
3524 for f in ctx:
3525 fm.startitem()
3525 fm.startitem()
3526 fl = ctx[f].flags()
3526 fl = ctx[f].flags()
3527 fm.condwrite(ui.debugflag, 'hash', '%s ', hex(mf[f]))
3527 fm.condwrite(ui.debugflag, 'hash', '%s ', hex(mf[f]))
3528 fm.condwrite(ui.verbose, 'mode type', '%s %1s ', mode[fl], char[fl])
3528 fm.condwrite(ui.verbose, 'mode type', '%s %1s ', mode[fl], char[fl])
3529 fm.write('path', '%s\n', f)
3529 fm.write('path', '%s\n', f)
3530 fm.end()
3530 fm.end()
3531
3531
3532 @command('^merge',
3532 @command('^merge',
3533 [('f', 'force', None,
3533 [('f', 'force', None,
3534 _('force a merge including outstanding changes (DEPRECATED)')),
3534 _('force a merge including outstanding changes (DEPRECATED)')),
3535 ('r', 'rev', '', _('revision to merge'), _('REV')),
3535 ('r', 'rev', '', _('revision to merge'), _('REV')),
3536 ('P', 'preview', None,
3536 ('P', 'preview', None,
3537 _('review revisions to merge (no merge is performed)')),
3537 _('review revisions to merge (no merge is performed)')),
3538 ('', 'abort', None, _('abort the ongoing merge')),
3538 ('', 'abort', None, _('abort the ongoing merge')),
3539 ] + mergetoolopts,
3539 ] + mergetoolopts,
3540 _('[-P] [[-r] REV]'))
3540 _('[-P] [[-r] REV]'))
3541 def merge(ui, repo, node=None, **opts):
3541 def merge(ui, repo, node=None, **opts):
3542 """merge another revision into working directory
3542 """merge another revision into working directory
3543
3543
3544 The current working directory is updated with all changes made in
3544 The current working directory is updated with all changes made in
3545 the requested revision since the last common predecessor revision.
3545 the requested revision since the last common predecessor revision.
3546
3546
3547 Files that changed between either parent are marked as changed for
3547 Files that changed between either parent are marked as changed for
3548 the next commit and a commit must be performed before any further
3548 the next commit and a commit must be performed before any further
3549 updates to the repository are allowed. The next commit will have
3549 updates to the repository are allowed. The next commit will have
3550 two parents.
3550 two parents.
3551
3551
3552 ``--tool`` can be used to specify the merge tool used for file
3552 ``--tool`` can be used to specify the merge tool used for file
3553 merges. It overrides the HGMERGE environment variable and your
3553 merges. It overrides the HGMERGE environment variable and your
3554 configuration files. See :hg:`help merge-tools` for options.
3554 configuration files. See :hg:`help merge-tools` for options.
3555
3555
3556 If no revision is specified, the working directory's parent is a
3556 If no revision is specified, the working directory's parent is a
3557 head revision, and the current branch contains exactly one other
3557 head revision, and the current branch contains exactly one other
3558 head, the other head is merged with by default. Otherwise, an
3558 head, the other head is merged with by default. Otherwise, an
3559 explicit revision with which to merge with must be provided.
3559 explicit revision with which to merge with must be provided.
3560
3560
3561 See :hg:`help resolve` for information on handling file conflicts.
3561 See :hg:`help resolve` for information on handling file conflicts.
3562
3562
3563 To undo an uncommitted merge, use :hg:`merge --abort` which
3563 To undo an uncommitted merge, use :hg:`merge --abort` which
3564 will check out a clean copy of the original merge parent, losing
3564 will check out a clean copy of the original merge parent, losing
3565 all changes.
3565 all changes.
3566
3566
3567 Returns 0 on success, 1 if there are unresolved files.
3567 Returns 0 on success, 1 if there are unresolved files.
3568 """
3568 """
3569
3569
3570 opts = pycompat.byteskwargs(opts)
3570 opts = pycompat.byteskwargs(opts)
3571 abort = opts.get('abort')
3571 abort = opts.get('abort')
3572 if abort and repo.dirstate.p2() == nullid:
3572 if abort and repo.dirstate.p2() == nullid:
3573 cmdutil.wrongtooltocontinue(repo, _('merge'))
3573 cmdutil.wrongtooltocontinue(repo, _('merge'))
3574 if abort:
3574 if abort:
3575 if node:
3575 if node:
3576 raise error.Abort(_("cannot specify a node with --abort"))
3576 raise error.Abort(_("cannot specify a node with --abort"))
3577 if opts.get('rev'):
3577 if opts.get('rev'):
3578 raise error.Abort(_("cannot specify both --rev and --abort"))
3578 raise error.Abort(_("cannot specify both --rev and --abort"))
3579 if opts.get('preview'):
3579 if opts.get('preview'):
3580 raise error.Abort(_("cannot specify --preview with --abort"))
3580 raise error.Abort(_("cannot specify --preview with --abort"))
3581 if opts.get('rev') and node:
3581 if opts.get('rev') and node:
3582 raise error.Abort(_("please specify just one revision"))
3582 raise error.Abort(_("please specify just one revision"))
3583 if not node:
3583 if not node:
3584 node = opts.get('rev')
3584 node = opts.get('rev')
3585
3585
3586 if node:
3586 if node:
3587 node = scmutil.revsingle(repo, node).node()
3587 node = scmutil.revsingle(repo, node).node()
3588
3588
3589 if not node and not abort:
3589 if not node and not abort:
3590 node = repo[destutil.destmerge(repo)].node()
3590 node = repo[destutil.destmerge(repo)].node()
3591
3591
3592 if opts.get('preview'):
3592 if opts.get('preview'):
3593 # find nodes that are ancestors of p2 but not of p1
3593 # find nodes that are ancestors of p2 but not of p1
3594 p1 = repo.lookup('.')
3594 p1 = repo.lookup('.')
3595 p2 = repo.lookup(node)
3595 p2 = repo.lookup(node)
3596 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
3596 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
3597
3597
3598 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
3598 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
3599 for node in nodes:
3599 for node in nodes:
3600 displayer.show(repo[node])
3600 displayer.show(repo[node])
3601 displayer.close()
3601 displayer.close()
3602 return 0
3602 return 0
3603
3603
3604 try:
3604 try:
3605 # ui.forcemerge is an internal variable, do not document
3605 # ui.forcemerge is an internal variable, do not document
3606 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''), 'merge')
3606 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''), 'merge')
3607 force = opts.get('force')
3607 force = opts.get('force')
3608 labels = ['working copy', 'merge rev']
3608 labels = ['working copy', 'merge rev']
3609 return hg.merge(repo, node, force=force, mergeforce=force,
3609 return hg.merge(repo, node, force=force, mergeforce=force,
3610 labels=labels, abort=abort)
3610 labels=labels, abort=abort)
3611 finally:
3611 finally:
3612 ui.setconfig('ui', 'forcemerge', '', 'merge')
3612 ui.setconfig('ui', 'forcemerge', '', 'merge')
3613
3613
3614 @command('outgoing|out',
3614 @command('outgoing|out',
3615 [('f', 'force', None, _('run even when the destination is unrelated')),
3615 [('f', 'force', None, _('run even when the destination is unrelated')),
3616 ('r', 'rev', [],
3616 ('r', 'rev', [],
3617 _('a changeset intended to be included in the destination'), _('REV')),
3617 _('a changeset intended to be included in the destination'), _('REV')),
3618 ('n', 'newest-first', None, _('show newest record first')),
3618 ('n', 'newest-first', None, _('show newest record first')),
3619 ('B', 'bookmarks', False, _('compare bookmarks')),
3619 ('B', 'bookmarks', False, _('compare bookmarks')),
3620 ('b', 'branch', [], _('a specific branch you would like to push'),
3620 ('b', 'branch', [], _('a specific branch you would like to push'),
3621 _('BRANCH')),
3621 _('BRANCH')),
3622 ] + logopts + remoteopts + subrepoopts,
3622 ] + logopts + remoteopts + subrepoopts,
3623 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]'))
3623 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]'))
3624 def outgoing(ui, repo, dest=None, **opts):
3624 def outgoing(ui, repo, dest=None, **opts):
3625 """show changesets not found in the destination
3625 """show changesets not found in the destination
3626
3626
3627 Show changesets not found in the specified destination repository
3627 Show changesets not found in the specified destination repository
3628 or the default push location. These are the changesets that would
3628 or the default push location. These are the changesets that would
3629 be pushed if a push was requested.
3629 be pushed if a push was requested.
3630
3630
3631 See pull for details of valid destination formats.
3631 See pull for details of valid destination formats.
3632
3632
3633 .. container:: verbose
3633 .. container:: verbose
3634
3634
3635 With -B/--bookmarks, the result of bookmark comparison between
3635 With -B/--bookmarks, the result of bookmark comparison between
3636 local and remote repositories is displayed. With -v/--verbose,
3636 local and remote repositories is displayed. With -v/--verbose,
3637 status is also displayed for each bookmark like below::
3637 status is also displayed for each bookmark like below::
3638
3638
3639 BM1 01234567890a added
3639 BM1 01234567890a added
3640 BM2 deleted
3640 BM2 deleted
3641 BM3 234567890abc advanced
3641 BM3 234567890abc advanced
3642 BM4 34567890abcd diverged
3642 BM4 34567890abcd diverged
3643 BM5 4567890abcde changed
3643 BM5 4567890abcde changed
3644
3644
3645 The action taken when pushing depends on the
3645 The action taken when pushing depends on the
3646 status of each bookmark:
3646 status of each bookmark:
3647
3647
3648 :``added``: push with ``-B`` will create it
3648 :``added``: push with ``-B`` will create it
3649 :``deleted``: push with ``-B`` will delete it
3649 :``deleted``: push with ``-B`` will delete it
3650 :``advanced``: push will update it
3650 :``advanced``: push will update it
3651 :``diverged``: push with ``-B`` will update it
3651 :``diverged``: push with ``-B`` will update it
3652 :``changed``: push with ``-B`` will update it
3652 :``changed``: push with ``-B`` will update it
3653
3653
3654 From the point of view of pushing behavior, bookmarks
3654 From the point of view of pushing behavior, bookmarks
3655 existing only in the remote repository are treated as
3655 existing only in the remote repository are treated as
3656 ``deleted``, even if it is in fact added remotely.
3656 ``deleted``, even if it is in fact added remotely.
3657
3657
3658 Returns 0 if there are outgoing changes, 1 otherwise.
3658 Returns 0 if there are outgoing changes, 1 otherwise.
3659 """
3659 """
3660 opts = pycompat.byteskwargs(opts)
3660 opts = pycompat.byteskwargs(opts)
3661 if opts.get('graph'):
3661 if opts.get('graph'):
3662 logcmdutil.checkunsupportedgraphflags([], opts)
3662 logcmdutil.checkunsupportedgraphflags([], opts)
3663 o, other = hg._outgoing(ui, repo, dest, opts)
3663 o, other = hg._outgoing(ui, repo, dest, opts)
3664 if not o:
3664 if not o:
3665 cmdutil.outgoinghooks(ui, repo, other, opts, o)
3665 cmdutil.outgoinghooks(ui, repo, other, opts, o)
3666 return
3666 return
3667
3667
3668 revdag = logcmdutil.graphrevs(repo, o, opts)
3668 revdag = logcmdutil.graphrevs(repo, o, opts)
3669 ui.pager('outgoing')
3669 ui.pager('outgoing')
3670 displayer = logcmdutil.changesetdisplayer(ui, repo, opts, buffered=True)
3670 displayer = logcmdutil.changesetdisplayer(ui, repo, opts, buffered=True)
3671 logcmdutil.displaygraph(ui, repo, revdag, displayer,
3671 logcmdutil.displaygraph(ui, repo, revdag, displayer,
3672 graphmod.asciiedges)
3672 graphmod.asciiedges)
3673 cmdutil.outgoinghooks(ui, repo, other, opts, o)
3673 cmdutil.outgoinghooks(ui, repo, other, opts, o)
3674 return 0
3674 return 0
3675
3675
3676 if opts.get('bookmarks'):
3676 if opts.get('bookmarks'):
3677 dest = ui.expandpath(dest or 'default-push', dest or 'default')
3677 dest = ui.expandpath(dest or 'default-push', dest or 'default')
3678 dest, branches = hg.parseurl(dest, opts.get('branch'))
3678 dest, branches = hg.parseurl(dest, opts.get('branch'))
3679 other = hg.peer(repo, opts, dest)
3679 other = hg.peer(repo, opts, dest)
3680 if 'bookmarks' not in other.listkeys('namespaces'):
3680 if 'bookmarks' not in other.listkeys('namespaces'):
3681 ui.warn(_("remote doesn't support bookmarks\n"))
3681 ui.warn(_("remote doesn't support bookmarks\n"))
3682 return 0
3682 return 0
3683 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
3683 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
3684 ui.pager('outgoing')
3684 ui.pager('outgoing')
3685 return bookmarks.outgoing(ui, repo, other)
3685 return bookmarks.outgoing(ui, repo, other)
3686
3686
3687 repo._subtoppath = ui.expandpath(dest or 'default-push', dest or 'default')
3687 repo._subtoppath = ui.expandpath(dest or 'default-push', dest or 'default')
3688 try:
3688 try:
3689 return hg.outgoing(ui, repo, dest, opts)
3689 return hg.outgoing(ui, repo, dest, opts)
3690 finally:
3690 finally:
3691 del repo._subtoppath
3691 del repo._subtoppath
3692
3692
3693 @command('parents',
3693 @command('parents',
3694 [('r', 'rev', '', _('show parents of the specified revision'), _('REV')),
3694 [('r', 'rev', '', _('show parents of the specified revision'), _('REV')),
3695 ] + templateopts,
3695 ] + templateopts,
3696 _('[-r REV] [FILE]'),
3696 _('[-r REV] [FILE]'),
3697 inferrepo=True)
3697 inferrepo=True)
3698 def parents(ui, repo, file_=None, **opts):
3698 def parents(ui, repo, file_=None, **opts):
3699 """show the parents of the working directory or revision (DEPRECATED)
3699 """show the parents of the working directory or revision (DEPRECATED)
3700
3700
3701 Print the working directory's parent revisions. If a revision is
3701 Print the working directory's parent revisions. If a revision is
3702 given via -r/--rev, the parent of that revision will be printed.
3702 given via -r/--rev, the parent of that revision will be printed.
3703 If a file argument is given, the revision in which the file was
3703 If a file argument is given, the revision in which the file was
3704 last changed (before the working directory revision or the
3704 last changed (before the working directory revision or the
3705 argument to --rev if given) is printed.
3705 argument to --rev if given) is printed.
3706
3706
3707 This command is equivalent to::
3707 This command is equivalent to::
3708
3708
3709 hg log -r "p1()+p2()" or
3709 hg log -r "p1()+p2()" or
3710 hg log -r "p1(REV)+p2(REV)" or
3710 hg log -r "p1(REV)+p2(REV)" or
3711 hg log -r "max(::p1() and file(FILE))+max(::p2() and file(FILE))" or
3711 hg log -r "max(::p1() and file(FILE))+max(::p2() and file(FILE))" or
3712 hg log -r "max(::p1(REV) and file(FILE))+max(::p2(REV) and file(FILE))"
3712 hg log -r "max(::p1(REV) and file(FILE))+max(::p2(REV) and file(FILE))"
3713
3713
3714 See :hg:`summary` and :hg:`help revsets` for related information.
3714 See :hg:`summary` and :hg:`help revsets` for related information.
3715
3715
3716 Returns 0 on success.
3716 Returns 0 on success.
3717 """
3717 """
3718
3718
3719 opts = pycompat.byteskwargs(opts)
3719 opts = pycompat.byteskwargs(opts)
3720 rev = opts.get('rev')
3720 rev = opts.get('rev')
3721 if rev:
3721 if rev:
3722 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
3722 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
3723 ctx = scmutil.revsingle(repo, rev, None)
3723 ctx = scmutil.revsingle(repo, rev, None)
3724
3724
3725 if file_:
3725 if file_:
3726 m = scmutil.match(ctx, (file_,), opts)
3726 m = scmutil.match(ctx, (file_,), opts)
3727 if m.anypats() or len(m.files()) != 1:
3727 if m.anypats() or len(m.files()) != 1:
3728 raise error.Abort(_('can only specify an explicit filename'))
3728 raise error.Abort(_('can only specify an explicit filename'))
3729 file_ = m.files()[0]
3729 file_ = m.files()[0]
3730 filenodes = []
3730 filenodes = []
3731 for cp in ctx.parents():
3731 for cp in ctx.parents():
3732 if not cp:
3732 if not cp:
3733 continue
3733 continue
3734 try:
3734 try:
3735 filenodes.append(cp.filenode(file_))
3735 filenodes.append(cp.filenode(file_))
3736 except error.LookupError:
3736 except error.LookupError:
3737 pass
3737 pass
3738 if not filenodes:
3738 if not filenodes:
3739 raise error.Abort(_("'%s' not found in manifest!") % file_)
3739 raise error.Abort(_("'%s' not found in manifest!") % file_)
3740 p = []
3740 p = []
3741 for fn in filenodes:
3741 for fn in filenodes:
3742 fctx = repo.filectx(file_, fileid=fn)
3742 fctx = repo.filectx(file_, fileid=fn)
3743 p.append(fctx.node())
3743 p.append(fctx.node())
3744 else:
3744 else:
3745 p = [cp.node() for cp in ctx.parents()]
3745 p = [cp.node() for cp in ctx.parents()]
3746
3746
3747 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
3747 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
3748 for n in p:
3748 for n in p:
3749 if n != nullid:
3749 if n != nullid:
3750 displayer.show(repo[n])
3750 displayer.show(repo[n])
3751 displayer.close()
3751 displayer.close()
3752
3752
3753 @command('paths', formatteropts, _('[NAME]'), optionalrepo=True,
3753 @command('paths', formatteropts, _('[NAME]'), optionalrepo=True,
3754 cmdtype=readonly)
3754 cmdtype=readonly)
3755 def paths(ui, repo, search=None, **opts):
3755 def paths(ui, repo, search=None, **opts):
3756 """show aliases for remote repositories
3756 """show aliases for remote repositories
3757
3757
3758 Show definition of symbolic path name NAME. If no name is given,
3758 Show definition of symbolic path name NAME. If no name is given,
3759 show definition of all available names.
3759 show definition of all available names.
3760
3760
3761 Option -q/--quiet suppresses all output when searching for NAME
3761 Option -q/--quiet suppresses all output when searching for NAME
3762 and shows only the path names when listing all definitions.
3762 and shows only the path names when listing all definitions.
3763
3763
3764 Path names are defined in the [paths] section of your
3764 Path names are defined in the [paths] section of your
3765 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
3765 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
3766 repository, ``.hg/hgrc`` is used, too.
3766 repository, ``.hg/hgrc`` is used, too.
3767
3767
3768 The path names ``default`` and ``default-push`` have a special
3768 The path names ``default`` and ``default-push`` have a special
3769 meaning. When performing a push or pull operation, they are used
3769 meaning. When performing a push or pull operation, they are used
3770 as fallbacks if no location is specified on the command-line.
3770 as fallbacks if no location is specified on the command-line.
3771 When ``default-push`` is set, it will be used for push and
3771 When ``default-push`` is set, it will be used for push and
3772 ``default`` will be used for pull; otherwise ``default`` is used
3772 ``default`` will be used for pull; otherwise ``default`` is used
3773 as the fallback for both. When cloning a repository, the clone
3773 as the fallback for both. When cloning a repository, the clone
3774 source is written as ``default`` in ``.hg/hgrc``.
3774 source is written as ``default`` in ``.hg/hgrc``.
3775
3775
3776 .. note::
3776 .. note::
3777
3777
3778 ``default`` and ``default-push`` apply to all inbound (e.g.
3778 ``default`` and ``default-push`` apply to all inbound (e.g.
3779 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email`
3779 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email`
3780 and :hg:`bundle`) operations.
3780 and :hg:`bundle`) operations.
3781
3781
3782 See :hg:`help urls` for more information.
3782 See :hg:`help urls` for more information.
3783
3783
3784 Returns 0 on success.
3784 Returns 0 on success.
3785 """
3785 """
3786
3786
3787 opts = pycompat.byteskwargs(opts)
3787 opts = pycompat.byteskwargs(opts)
3788 ui.pager('paths')
3788 ui.pager('paths')
3789 if search:
3789 if search:
3790 pathitems = [(name, path) for name, path in ui.paths.iteritems()
3790 pathitems = [(name, path) for name, path in ui.paths.iteritems()
3791 if name == search]
3791 if name == search]
3792 else:
3792 else:
3793 pathitems = sorted(ui.paths.iteritems())
3793 pathitems = sorted(ui.paths.iteritems())
3794
3794
3795 fm = ui.formatter('paths', opts)
3795 fm = ui.formatter('paths', opts)
3796 if fm.isplain():
3796 if fm.isplain():
3797 hidepassword = util.hidepassword
3797 hidepassword = util.hidepassword
3798 else:
3798 else:
3799 hidepassword = bytes
3799 hidepassword = bytes
3800 if ui.quiet:
3800 if ui.quiet:
3801 namefmt = '%s\n'
3801 namefmt = '%s\n'
3802 else:
3802 else:
3803 namefmt = '%s = '
3803 namefmt = '%s = '
3804 showsubopts = not search and not ui.quiet
3804 showsubopts = not search and not ui.quiet
3805
3805
3806 for name, path in pathitems:
3806 for name, path in pathitems:
3807 fm.startitem()
3807 fm.startitem()
3808 fm.condwrite(not search, 'name', namefmt, name)
3808 fm.condwrite(not search, 'name', namefmt, name)
3809 fm.condwrite(not ui.quiet, 'url', '%s\n', hidepassword(path.rawloc))
3809 fm.condwrite(not ui.quiet, 'url', '%s\n', hidepassword(path.rawloc))
3810 for subopt, value in sorted(path.suboptions.items()):
3810 for subopt, value in sorted(path.suboptions.items()):
3811 assert subopt not in ('name', 'url')
3811 assert subopt not in ('name', 'url')
3812 if showsubopts:
3812 if showsubopts:
3813 fm.plain('%s:%s = ' % (name, subopt))
3813 fm.plain('%s:%s = ' % (name, subopt))
3814 fm.condwrite(showsubopts, subopt, '%s\n', value)
3814 fm.condwrite(showsubopts, subopt, '%s\n', value)
3815
3815
3816 fm.end()
3816 fm.end()
3817
3817
3818 if search and not pathitems:
3818 if search and not pathitems:
3819 if not ui.quiet:
3819 if not ui.quiet:
3820 ui.warn(_("not found!\n"))
3820 ui.warn(_("not found!\n"))
3821 return 1
3821 return 1
3822 else:
3822 else:
3823 return 0
3823 return 0
3824
3824
3825 @command('phase',
3825 @command('phase',
3826 [('p', 'public', False, _('set changeset phase to public')),
3826 [('p', 'public', False, _('set changeset phase to public')),
3827 ('d', 'draft', False, _('set changeset phase to draft')),
3827 ('d', 'draft', False, _('set changeset phase to draft')),
3828 ('s', 'secret', False, _('set changeset phase to secret')),
3828 ('s', 'secret', False, _('set changeset phase to secret')),
3829 ('f', 'force', False, _('allow to move boundary backward')),
3829 ('f', 'force', False, _('allow to move boundary backward')),
3830 ('r', 'rev', [], _('target revision'), _('REV')),
3830 ('r', 'rev', [], _('target revision'), _('REV')),
3831 ],
3831 ],
3832 _('[-p|-d|-s] [-f] [-r] [REV...]'))
3832 _('[-p|-d|-s] [-f] [-r] [REV...]'))
3833 def phase(ui, repo, *revs, **opts):
3833 def phase(ui, repo, *revs, **opts):
3834 """set or show the current phase name
3834 """set or show the current phase name
3835
3835
3836 With no argument, show the phase name of the current revision(s).
3836 With no argument, show the phase name of the current revision(s).
3837
3837
3838 With one of -p/--public, -d/--draft or -s/--secret, change the
3838 With one of -p/--public, -d/--draft or -s/--secret, change the
3839 phase value of the specified revisions.
3839 phase value of the specified revisions.
3840
3840
3841 Unless -f/--force is specified, :hg:`phase` won't move changesets from a
3841 Unless -f/--force is specified, :hg:`phase` won't move changesets from a
3842 lower phase to a higher phase. Phases are ordered as follows::
3842 lower phase to a higher phase. Phases are ordered as follows::
3843
3843
3844 public < draft < secret
3844 public < draft < secret
3845
3845
3846 Returns 0 on success, 1 if some phases could not be changed.
3846 Returns 0 on success, 1 if some phases could not be changed.
3847
3847
3848 (For more information about the phases concept, see :hg:`help phases`.)
3848 (For more information about the phases concept, see :hg:`help phases`.)
3849 """
3849 """
3850 opts = pycompat.byteskwargs(opts)
3850 opts = pycompat.byteskwargs(opts)
3851 # search for a unique phase argument
3851 # search for a unique phase argument
3852 targetphase = None
3852 targetphase = None
3853 for idx, name in enumerate(phases.phasenames):
3853 for idx, name in enumerate(phases.phasenames):
3854 if opts[name]:
3854 if opts[name]:
3855 if targetphase is not None:
3855 if targetphase is not None:
3856 raise error.Abort(_('only one phase can be specified'))
3856 raise error.Abort(_('only one phase can be specified'))
3857 targetphase = idx
3857 targetphase = idx
3858
3858
3859 # look for specified revision
3859 # look for specified revision
3860 revs = list(revs)
3860 revs = list(revs)
3861 revs.extend(opts['rev'])
3861 revs.extend(opts['rev'])
3862 if not revs:
3862 if not revs:
3863 # display both parents as the second parent phase can influence
3863 # display both parents as the second parent phase can influence
3864 # the phase of a merge commit
3864 # the phase of a merge commit
3865 revs = [c.rev() for c in repo[None].parents()]
3865 revs = [c.rev() for c in repo[None].parents()]
3866
3866
3867 revs = scmutil.revrange(repo, revs)
3867 revs = scmutil.revrange(repo, revs)
3868
3868
3869 ret = 0
3869 ret = 0
3870 if targetphase is None:
3870 if targetphase is None:
3871 # display
3871 # display
3872 for r in revs:
3872 for r in revs:
3873 ctx = repo[r]
3873 ctx = repo[r]
3874 ui.write('%i: %s\n' % (ctx.rev(), ctx.phasestr()))
3874 ui.write('%i: %s\n' % (ctx.rev(), ctx.phasestr()))
3875 else:
3875 else:
3876 with repo.lock(), repo.transaction("phase") as tr:
3876 with repo.lock(), repo.transaction("phase") as tr:
3877 # set phase
3877 # set phase
3878 if not revs:
3878 if not revs:
3879 raise error.Abort(_('empty revision set'))
3879 raise error.Abort(_('empty revision set'))
3880 nodes = [repo[r].node() for r in revs]
3880 nodes = [repo[r].node() for r in revs]
3881 # moving revision from public to draft may hide them
3881 # moving revision from public to draft may hide them
3882 # We have to check result on an unfiltered repository
3882 # We have to check result on an unfiltered repository
3883 unfi = repo.unfiltered()
3883 unfi = repo.unfiltered()
3884 getphase = unfi._phasecache.phase
3884 getphase = unfi._phasecache.phase
3885 olddata = [getphase(unfi, r) for r in unfi]
3885 olddata = [getphase(unfi, r) for r in unfi]
3886 phases.advanceboundary(repo, tr, targetphase, nodes)
3886 phases.advanceboundary(repo, tr, targetphase, nodes)
3887 if opts['force']:
3887 if opts['force']:
3888 phases.retractboundary(repo, tr, targetphase, nodes)
3888 phases.retractboundary(repo, tr, targetphase, nodes)
3889 getphase = unfi._phasecache.phase
3889 getphase = unfi._phasecache.phase
3890 newdata = [getphase(unfi, r) for r in unfi]
3890 newdata = [getphase(unfi, r) for r in unfi]
3891 changes = sum(newdata[r] != olddata[r] for r in unfi)
3891 changes = sum(newdata[r] != olddata[r] for r in unfi)
3892 cl = unfi.changelog
3892 cl = unfi.changelog
3893 rejected = [n for n in nodes
3893 rejected = [n for n in nodes
3894 if newdata[cl.rev(n)] < targetphase]
3894 if newdata[cl.rev(n)] < targetphase]
3895 if rejected:
3895 if rejected:
3896 ui.warn(_('cannot move %i changesets to a higher '
3896 ui.warn(_('cannot move %i changesets to a higher '
3897 'phase, use --force\n') % len(rejected))
3897 'phase, use --force\n') % len(rejected))
3898 ret = 1
3898 ret = 1
3899 if changes:
3899 if changes:
3900 msg = _('phase changed for %i changesets\n') % changes
3900 msg = _('phase changed for %i changesets\n') % changes
3901 if ret:
3901 if ret:
3902 ui.status(msg)
3902 ui.status(msg)
3903 else:
3903 else:
3904 ui.note(msg)
3904 ui.note(msg)
3905 else:
3905 else:
3906 ui.warn(_('no phases changed\n'))
3906 ui.warn(_('no phases changed\n'))
3907 return ret
3907 return ret
3908
3908
3909 def postincoming(ui, repo, modheads, optupdate, checkout, brev):
3909 def postincoming(ui, repo, modheads, optupdate, checkout, brev):
3910 """Run after a changegroup has been added via pull/unbundle
3910 """Run after a changegroup has been added via pull/unbundle
3911
3911
3912 This takes arguments below:
3912 This takes arguments below:
3913
3913
3914 :modheads: change of heads by pull/unbundle
3914 :modheads: change of heads by pull/unbundle
3915 :optupdate: updating working directory is needed or not
3915 :optupdate: updating working directory is needed or not
3916 :checkout: update destination revision (or None to default destination)
3916 :checkout: update destination revision (or None to default destination)
3917 :brev: a name, which might be a bookmark to be activated after updating
3917 :brev: a name, which might be a bookmark to be activated after updating
3918 """
3918 """
3919 if modheads == 0:
3919 if modheads == 0:
3920 return
3920 return
3921 if optupdate:
3921 if optupdate:
3922 try:
3922 try:
3923 return hg.updatetotally(ui, repo, checkout, brev)
3923 return hg.updatetotally(ui, repo, checkout, brev)
3924 except error.UpdateAbort as inst:
3924 except error.UpdateAbort as inst:
3925 msg = _("not updating: %s") % stringutil.forcebytestr(inst)
3925 msg = _("not updating: %s") % stringutil.forcebytestr(inst)
3926 hint = inst.hint
3926 hint = inst.hint
3927 raise error.UpdateAbort(msg, hint=hint)
3927 raise error.UpdateAbort(msg, hint=hint)
3928 if modheads > 1:
3928 if modheads > 1:
3929 currentbranchheads = len(repo.branchheads())
3929 currentbranchheads = len(repo.branchheads())
3930 if currentbranchheads == modheads:
3930 if currentbranchheads == modheads:
3931 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
3931 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
3932 elif currentbranchheads > 1:
3932 elif currentbranchheads > 1:
3933 ui.status(_("(run 'hg heads .' to see heads, 'hg merge' to "
3933 ui.status(_("(run 'hg heads .' to see heads, 'hg merge' to "
3934 "merge)\n"))
3934 "merge)\n"))
3935 else:
3935 else:
3936 ui.status(_("(run 'hg heads' to see heads)\n"))
3936 ui.status(_("(run 'hg heads' to see heads)\n"))
3937 elif not ui.configbool('commands', 'update.requiredest'):
3937 elif not ui.configbool('commands', 'update.requiredest'):
3938 ui.status(_("(run 'hg update' to get a working copy)\n"))
3938 ui.status(_("(run 'hg update' to get a working copy)\n"))
3939
3939
3940 @command('^pull',
3940 @command('^pull',
3941 [('u', 'update', None,
3941 [('u', 'update', None,
3942 _('update to new branch head if new descendants were pulled')),
3942 _('update to new branch head if new descendants were pulled')),
3943 ('f', 'force', None, _('run even when remote repository is unrelated')),
3943 ('f', 'force', None, _('run even when remote repository is unrelated')),
3944 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
3944 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
3945 ('B', 'bookmark', [], _("bookmark to pull"), _('BOOKMARK')),
3945 ('B', 'bookmark', [], _("bookmark to pull"), _('BOOKMARK')),
3946 ('b', 'branch', [], _('a specific branch you would like to pull'),
3946 ('b', 'branch', [], _('a specific branch you would like to pull'),
3947 _('BRANCH')),
3947 _('BRANCH')),
3948 ] + remoteopts,
3948 ] + remoteopts,
3949 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'))
3949 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'))
3950 def pull(ui, repo, source="default", **opts):
3950 def pull(ui, repo, source="default", **opts):
3951 """pull changes from the specified source
3951 """pull changes from the specified source
3952
3952
3953 Pull changes from a remote repository to a local one.
3953 Pull changes from a remote repository to a local one.
3954
3954
3955 This finds all changes from the repository at the specified path
3955 This finds all changes from the repository at the specified path
3956 or URL and adds them to a local repository (the current one unless
3956 or URL and adds them to a local repository (the current one unless
3957 -R is specified). By default, this does not update the copy of the
3957 -R is specified). By default, this does not update the copy of the
3958 project in the working directory.
3958 project in the working directory.
3959
3959
3960 Use :hg:`incoming` if you want to see what would have been added
3960 Use :hg:`incoming` if you want to see what would have been added
3961 by a pull at the time you issued this command. If you then decide
3961 by a pull at the time you issued this command. If you then decide
3962 to add those changes to the repository, you should use :hg:`pull
3962 to add those changes to the repository, you should use :hg:`pull
3963 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
3963 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
3964
3964
3965 If SOURCE is omitted, the 'default' path will be used.
3965 If SOURCE is omitted, the 'default' path will be used.
3966 See :hg:`help urls` for more information.
3966 See :hg:`help urls` for more information.
3967
3967
3968 Specifying bookmark as ``.`` is equivalent to specifying the active
3968 Specifying bookmark as ``.`` is equivalent to specifying the active
3969 bookmark's name.
3969 bookmark's name.
3970
3970
3971 Returns 0 on success, 1 if an update had unresolved files.
3971 Returns 0 on success, 1 if an update had unresolved files.
3972 """
3972 """
3973
3973
3974 opts = pycompat.byteskwargs(opts)
3974 opts = pycompat.byteskwargs(opts)
3975 if ui.configbool('commands', 'update.requiredest') and opts.get('update'):
3975 if ui.configbool('commands', 'update.requiredest') and opts.get('update'):
3976 msg = _('update destination required by configuration')
3976 msg = _('update destination required by configuration')
3977 hint = _('use hg pull followed by hg update DEST')
3977 hint = _('use hg pull followed by hg update DEST')
3978 raise error.Abort(msg, hint=hint)
3978 raise error.Abort(msg, hint=hint)
3979
3979
3980 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
3980 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
3981 ui.status(_('pulling from %s\n') % util.hidepassword(source))
3981 ui.status(_('pulling from %s\n') % util.hidepassword(source))
3982 other = hg.peer(repo, opts, source)
3982 other = hg.peer(repo, opts, source)
3983 try:
3983 try:
3984 revs, checkout = hg.addbranchrevs(repo, other, branches,
3984 revs, checkout = hg.addbranchrevs(repo, other, branches,
3985 opts.get('rev'))
3985 opts.get('rev'))
3986
3986
3987
3987
3988 pullopargs = {}
3988 pullopargs = {}
3989 if opts.get('bookmark'):
3989 if opts.get('bookmark'):
3990 if not revs:
3990 if not revs:
3991 revs = []
3991 revs = []
3992 # The list of bookmark used here is not the one used to actually
3992 # The list of bookmark used here is not the one used to actually
3993 # update the bookmark name. This can result in the revision pulled
3993 # update the bookmark name. This can result in the revision pulled
3994 # not ending up with the name of the bookmark because of a race
3994 # not ending up with the name of the bookmark because of a race
3995 # condition on the server. (See issue 4689 for details)
3995 # condition on the server. (See issue 4689 for details)
3996 remotebookmarks = other.listkeys('bookmarks')
3996 remotebookmarks = other.listkeys('bookmarks')
3997 remotebookmarks = bookmarks.unhexlifybookmarks(remotebookmarks)
3997 remotebookmarks = bookmarks.unhexlifybookmarks(remotebookmarks)
3998 pullopargs['remotebookmarks'] = remotebookmarks
3998 pullopargs['remotebookmarks'] = remotebookmarks
3999 for b in opts['bookmark']:
3999 for b in opts['bookmark']:
4000 b = repo._bookmarks.expandname(b)
4000 b = repo._bookmarks.expandname(b)
4001 if b not in remotebookmarks:
4001 if b not in remotebookmarks:
4002 raise error.Abort(_('remote bookmark %s not found!') % b)
4002 raise error.Abort(_('remote bookmark %s not found!') % b)
4003 revs.append(hex(remotebookmarks[b]))
4003 revs.append(hex(remotebookmarks[b]))
4004
4004
4005 if revs:
4005 if revs:
4006 try:
4006 try:
4007 # When 'rev' is a bookmark name, we cannot guarantee that it
4007 # When 'rev' is a bookmark name, we cannot guarantee that it
4008 # will be updated with that name because of a race condition
4008 # will be updated with that name because of a race condition
4009 # server side. (See issue 4689 for details)
4009 # server side. (See issue 4689 for details)
4010 oldrevs = revs
4010 oldrevs = revs
4011 revs = [] # actually, nodes
4011 revs = [] # actually, nodes
4012 for r in oldrevs:
4012 for r in oldrevs:
4013 node = other.lookup(r)
4013 node = other.lookup(r)
4014 revs.append(node)
4014 revs.append(node)
4015 if r == checkout:
4015 if r == checkout:
4016 checkout = node
4016 checkout = node
4017 except error.CapabilityError:
4017 except error.CapabilityError:
4018 err = _("other repository doesn't support revision lookup, "
4018 err = _("other repository doesn't support revision lookup, "
4019 "so a rev cannot be specified.")
4019 "so a rev cannot be specified.")
4020 raise error.Abort(err)
4020 raise error.Abort(err)
4021
4021
4022 wlock = util.nullcontextmanager()
4022 wlock = util.nullcontextmanager()
4023 if opts.get('update'):
4023 if opts.get('update'):
4024 wlock = repo.wlock()
4024 wlock = repo.wlock()
4025 with wlock:
4025 with wlock:
4026 pullopargs.update(opts.get('opargs', {}))
4026 pullopargs.update(opts.get('opargs', {}))
4027 modheads = exchange.pull(repo, other, heads=revs,
4027 modheads = exchange.pull(repo, other, heads=revs,
4028 force=opts.get('force'),
4028 force=opts.get('force'),
4029 bookmarks=opts.get('bookmark', ()),
4029 bookmarks=opts.get('bookmark', ()),
4030 opargs=pullopargs).cgresult
4030 opargs=pullopargs).cgresult
4031
4031
4032 # brev is a name, which might be a bookmark to be activated at
4032 # brev is a name, which might be a bookmark to be activated at
4033 # the end of the update. In other words, it is an explicit
4033 # the end of the update. In other words, it is an explicit
4034 # destination of the update
4034 # destination of the update
4035 brev = None
4035 brev = None
4036
4036
4037 if checkout:
4037 if checkout:
4038 checkout = "%d" % repo.changelog.rev(checkout)
4038 checkout = "%d" % repo.changelog.rev(checkout)
4039
4039
4040 # order below depends on implementation of
4040 # order below depends on implementation of
4041 # hg.addbranchrevs(). opts['bookmark'] is ignored,
4041 # hg.addbranchrevs(). opts['bookmark'] is ignored,
4042 # because 'checkout' is determined without it.
4042 # because 'checkout' is determined without it.
4043 if opts.get('rev'):
4043 if opts.get('rev'):
4044 brev = opts['rev'][0]
4044 brev = opts['rev'][0]
4045 elif opts.get('branch'):
4045 elif opts.get('branch'):
4046 brev = opts['branch'][0]
4046 brev = opts['branch'][0]
4047 else:
4047 else:
4048 brev = branches[0]
4048 brev = branches[0]
4049 repo._subtoppath = source
4049 repo._subtoppath = source
4050 try:
4050 try:
4051 ret = postincoming(ui, repo, modheads, opts.get('update'),
4051 ret = postincoming(ui, repo, modheads, opts.get('update'),
4052 checkout, brev)
4052 checkout, brev)
4053
4053
4054 finally:
4054 finally:
4055 del repo._subtoppath
4055 del repo._subtoppath
4056
4056
4057 finally:
4057 finally:
4058 other.close()
4058 other.close()
4059 return ret
4059 return ret
4060
4060
4061 @command('^push',
4061 @command('^push',
4062 [('f', 'force', None, _('force push')),
4062 [('f', 'force', None, _('force push')),
4063 ('r', 'rev', [],
4063 ('r', 'rev', [],
4064 _('a changeset intended to be included in the destination'),
4064 _('a changeset intended to be included in the destination'),
4065 _('REV')),
4065 _('REV')),
4066 ('B', 'bookmark', [], _("bookmark to push"), _('BOOKMARK')),
4066 ('B', 'bookmark', [], _("bookmark to push"), _('BOOKMARK')),
4067 ('b', 'branch', [],
4067 ('b', 'branch', [],
4068 _('a specific branch you would like to push'), _('BRANCH')),
4068 _('a specific branch you would like to push'), _('BRANCH')),
4069 ('', 'new-branch', False, _('allow pushing a new branch')),
4069 ('', 'new-branch', False, _('allow pushing a new branch')),
4070 ('', 'pushvars', [], _('variables that can be sent to server (ADVANCED)')),
4070 ('', 'pushvars', [], _('variables that can be sent to server (ADVANCED)')),
4071 ] + remoteopts,
4071 ] + remoteopts,
4072 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'))
4072 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'))
4073 def push(ui, repo, dest=None, **opts):
4073 def push(ui, repo, dest=None, **opts):
4074 """push changes to the specified destination
4074 """push changes to the specified destination
4075
4075
4076 Push changesets from the local repository to the specified
4076 Push changesets from the local repository to the specified
4077 destination.
4077 destination.
4078
4078
4079 This operation is symmetrical to pull: it is identical to a pull
4079 This operation is symmetrical to pull: it is identical to a pull
4080 in the destination repository from the current one.
4080 in the destination repository from the current one.
4081
4081
4082 By default, push will not allow creation of new heads at the
4082 By default, push will not allow creation of new heads at the
4083 destination, since multiple heads would make it unclear which head
4083 destination, since multiple heads would make it unclear which head
4084 to use. In this situation, it is recommended to pull and merge
4084 to use. In this situation, it is recommended to pull and merge
4085 before pushing.
4085 before pushing.
4086
4086
4087 Use --new-branch if you want to allow push to create a new named
4087 Use --new-branch if you want to allow push to create a new named
4088 branch that is not present at the destination. This allows you to
4088 branch that is not present at the destination. This allows you to
4089 only create a new branch without forcing other changes.
4089 only create a new branch without forcing other changes.
4090
4090
4091 .. note::
4091 .. note::
4092
4092
4093 Extra care should be taken with the -f/--force option,
4093 Extra care should be taken with the -f/--force option,
4094 which will push all new heads on all branches, an action which will
4094 which will push all new heads on all branches, an action which will
4095 almost always cause confusion for collaborators.
4095 almost always cause confusion for collaborators.
4096
4096
4097 If -r/--rev is used, the specified revision and all its ancestors
4097 If -r/--rev is used, the specified revision and all its ancestors
4098 will be pushed to the remote repository.
4098 will be pushed to the remote repository.
4099
4099
4100 If -B/--bookmark is used, the specified bookmarked revision, its
4100 If -B/--bookmark is used, the specified bookmarked revision, its
4101 ancestors, and the bookmark will be pushed to the remote
4101 ancestors, and the bookmark will be pushed to the remote
4102 repository. Specifying ``.`` is equivalent to specifying the active
4102 repository. Specifying ``.`` is equivalent to specifying the active
4103 bookmark's name.
4103 bookmark's name.
4104
4104
4105 Please see :hg:`help urls` for important details about ``ssh://``
4105 Please see :hg:`help urls` for important details about ``ssh://``
4106 URLs. If DESTINATION is omitted, a default path will be used.
4106 URLs. If DESTINATION is omitted, a default path will be used.
4107
4107
4108 .. container:: verbose
4108 .. container:: verbose
4109
4109
4110 The --pushvars option sends strings to the server that become
4110 The --pushvars option sends strings to the server that become
4111 environment variables prepended with ``HG_USERVAR_``. For example,
4111 environment variables prepended with ``HG_USERVAR_``. For example,
4112 ``--pushvars ENABLE_FEATURE=true``, provides the server side hooks with
4112 ``--pushvars ENABLE_FEATURE=true``, provides the server side hooks with
4113 ``HG_USERVAR_ENABLE_FEATURE=true`` as part of their environment.
4113 ``HG_USERVAR_ENABLE_FEATURE=true`` as part of their environment.
4114
4114
4115 pushvars can provide for user-overridable hooks as well as set debug
4115 pushvars can provide for user-overridable hooks as well as set debug
4116 levels. One example is having a hook that blocks commits containing
4116 levels. One example is having a hook that blocks commits containing
4117 conflict markers, but enables the user to override the hook if the file
4117 conflict markers, but enables the user to override the hook if the file
4118 is using conflict markers for testing purposes or the file format has
4118 is using conflict markers for testing purposes or the file format has
4119 strings that look like conflict markers.
4119 strings that look like conflict markers.
4120
4120
4121 By default, servers will ignore `--pushvars`. To enable it add the
4121 By default, servers will ignore `--pushvars`. To enable it add the
4122 following to your configuration file::
4122 following to your configuration file::
4123
4123
4124 [push]
4124 [push]
4125 pushvars.server = true
4125 pushvars.server = true
4126
4126
4127 Returns 0 if push was successful, 1 if nothing to push.
4127 Returns 0 if push was successful, 1 if nothing to push.
4128 """
4128 """
4129
4129
4130 opts = pycompat.byteskwargs(opts)
4130 opts = pycompat.byteskwargs(opts)
4131 if opts.get('bookmark'):
4131 if opts.get('bookmark'):
4132 ui.setconfig('bookmarks', 'pushing', opts['bookmark'], 'push')
4132 ui.setconfig('bookmarks', 'pushing', opts['bookmark'], 'push')
4133 for b in opts['bookmark']:
4133 for b in opts['bookmark']:
4134 # translate -B options to -r so changesets get pushed
4134 # translate -B options to -r so changesets get pushed
4135 b = repo._bookmarks.expandname(b)
4135 b = repo._bookmarks.expandname(b)
4136 if b in repo._bookmarks:
4136 if b in repo._bookmarks:
4137 opts.setdefault('rev', []).append(b)
4137 opts.setdefault('rev', []).append(b)
4138 else:
4138 else:
4139 # if we try to push a deleted bookmark, translate it to null
4139 # if we try to push a deleted bookmark, translate it to null
4140 # this lets simultaneous -r, -b options continue working
4140 # this lets simultaneous -r, -b options continue working
4141 opts.setdefault('rev', []).append("null")
4141 opts.setdefault('rev', []).append("null")
4142
4142
4143 path = ui.paths.getpath(dest, default=('default-push', 'default'))
4143 path = ui.paths.getpath(dest, default=('default-push', 'default'))
4144 if not path:
4144 if not path:
4145 raise error.Abort(_('default repository not configured!'),
4145 raise error.Abort(_('default repository not configured!'),
4146 hint=_("see 'hg help config.paths'"))
4146 hint=_("see 'hg help config.paths'"))
4147 dest = path.pushloc or path.loc
4147 dest = path.pushloc or path.loc
4148 branches = (path.branch, opts.get('branch') or [])
4148 branches = (path.branch, opts.get('branch') or [])
4149 ui.status(_('pushing to %s\n') % util.hidepassword(dest))
4149 ui.status(_('pushing to %s\n') % util.hidepassword(dest))
4150 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
4150 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
4151 other = hg.peer(repo, opts, dest)
4151 other = hg.peer(repo, opts, dest)
4152
4152
4153 if revs:
4153 if revs:
4154 revs = [repo.lookup(r) for r in scmutil.revrange(repo, revs)]
4154 revs = [repo.lookup(r) for r in scmutil.revrange(repo, revs)]
4155 if not revs:
4155 if not revs:
4156 raise error.Abort(_("specified revisions evaluate to an empty set"),
4156 raise error.Abort(_("specified revisions evaluate to an empty set"),
4157 hint=_("use different revision arguments"))
4157 hint=_("use different revision arguments"))
4158 elif path.pushrev:
4158 elif path.pushrev:
4159 # It doesn't make any sense to specify ancestor revisions. So limit
4159 # It doesn't make any sense to specify ancestor revisions. So limit
4160 # to DAG heads to make discovery simpler.
4160 # to DAG heads to make discovery simpler.
4161 expr = revsetlang.formatspec('heads(%r)', path.pushrev)
4161 expr = revsetlang.formatspec('heads(%r)', path.pushrev)
4162 revs = scmutil.revrange(repo, [expr])
4162 revs = scmutil.revrange(repo, [expr])
4163 revs = [repo[rev].node() for rev in revs]
4163 revs = [repo[rev].node() for rev in revs]
4164 if not revs:
4164 if not revs:
4165 raise error.Abort(_('default push revset for path evaluates to an '
4165 raise error.Abort(_('default push revset for path evaluates to an '
4166 'empty set'))
4166 'empty set'))
4167
4167
4168 repo._subtoppath = dest
4168 repo._subtoppath = dest
4169 try:
4169 try:
4170 # push subrepos depth-first for coherent ordering
4170 # push subrepos depth-first for coherent ordering
4171 c = repo['.']
4171 c = repo['.']
4172 subs = c.substate # only repos that are committed
4172 subs = c.substate # only repos that are committed
4173 for s in sorted(subs):
4173 for s in sorted(subs):
4174 result = c.sub(s).push(opts)
4174 result = c.sub(s).push(opts)
4175 if result == 0:
4175 if result == 0:
4176 return not result
4176 return not result
4177 finally:
4177 finally:
4178 del repo._subtoppath
4178 del repo._subtoppath
4179
4179
4180 opargs = dict(opts.get('opargs', {})) # copy opargs since we may mutate it
4180 opargs = dict(opts.get('opargs', {})) # copy opargs since we may mutate it
4181 opargs.setdefault('pushvars', []).extend(opts.get('pushvars', []))
4181 opargs.setdefault('pushvars', []).extend(opts.get('pushvars', []))
4182
4182
4183 pushop = exchange.push(repo, other, opts.get('force'), revs=revs,
4183 pushop = exchange.push(repo, other, opts.get('force'), revs=revs,
4184 newbranch=opts.get('new_branch'),
4184 newbranch=opts.get('new_branch'),
4185 bookmarks=opts.get('bookmark', ()),
4185 bookmarks=opts.get('bookmark', ()),
4186 opargs=opargs)
4186 opargs=opargs)
4187
4187
4188 result = not pushop.cgresult
4188 result = not pushop.cgresult
4189
4189
4190 if pushop.bkresult is not None:
4190 if pushop.bkresult is not None:
4191 if pushop.bkresult == 2:
4191 if pushop.bkresult == 2:
4192 result = 2
4192 result = 2
4193 elif not result and pushop.bkresult:
4193 elif not result and pushop.bkresult:
4194 result = 2
4194 result = 2
4195
4195
4196 return result
4196 return result
4197
4197
4198 @command('recover', [])
4198 @command('recover', [])
4199 def recover(ui, repo):
4199 def recover(ui, repo):
4200 """roll back an interrupted transaction
4200 """roll back an interrupted transaction
4201
4201
4202 Recover from an interrupted commit or pull.
4202 Recover from an interrupted commit or pull.
4203
4203
4204 This command tries to fix the repository status after an
4204 This command tries to fix the repository status after an
4205 interrupted operation. It should only be necessary when Mercurial
4205 interrupted operation. It should only be necessary when Mercurial
4206 suggests it.
4206 suggests it.
4207
4207
4208 Returns 0 if successful, 1 if nothing to recover or verify fails.
4208 Returns 0 if successful, 1 if nothing to recover or verify fails.
4209 """
4209 """
4210 if repo.recover():
4210 if repo.recover():
4211 return hg.verify(repo)
4211 return hg.verify(repo)
4212 return 1
4212 return 1
4213
4213
4214 @command('^remove|rm',
4214 @command('^remove|rm',
4215 [('A', 'after', None, _('record delete for missing files')),
4215 [('A', 'after', None, _('record delete for missing files')),
4216 ('f', 'force', None,
4216 ('f', 'force', None,
4217 _('forget added files, delete modified files')),
4217 _('forget added files, delete modified files')),
4218 ] + subrepoopts + walkopts + dryrunopts,
4218 ] + subrepoopts + walkopts + dryrunopts,
4219 _('[OPTION]... FILE...'),
4219 _('[OPTION]... FILE...'),
4220 inferrepo=True)
4220 inferrepo=True)
4221 def remove(ui, repo, *pats, **opts):
4221 def remove(ui, repo, *pats, **opts):
4222 """remove the specified files on the next commit
4222 """remove the specified files on the next commit
4223
4223
4224 Schedule the indicated files for removal from the current branch.
4224 Schedule the indicated files for removal from the current branch.
4225
4225
4226 This command schedules the files to be removed at the next commit.
4226 This command schedules the files to be removed at the next commit.
4227 To undo a remove before that, see :hg:`revert`. To undo added
4227 To undo a remove before that, see :hg:`revert`. To undo added
4228 files, see :hg:`forget`.
4228 files, see :hg:`forget`.
4229
4229
4230 .. container:: verbose
4230 .. container:: verbose
4231
4231
4232 -A/--after can be used to remove only files that have already
4232 -A/--after can be used to remove only files that have already
4233 been deleted, -f/--force can be used to force deletion, and -Af
4233 been deleted, -f/--force can be used to force deletion, and -Af
4234 can be used to remove files from the next revision without
4234 can be used to remove files from the next revision without
4235 deleting them from the working directory.
4235 deleting them from the working directory.
4236
4236
4237 The following table details the behavior of remove for different
4237 The following table details the behavior of remove for different
4238 file states (columns) and option combinations (rows). The file
4238 file states (columns) and option combinations (rows). The file
4239 states are Added [A], Clean [C], Modified [M] and Missing [!]
4239 states are Added [A], Clean [C], Modified [M] and Missing [!]
4240 (as reported by :hg:`status`). The actions are Warn, Remove
4240 (as reported by :hg:`status`). The actions are Warn, Remove
4241 (from branch) and Delete (from disk):
4241 (from branch) and Delete (from disk):
4242
4242
4243 ========= == == == ==
4243 ========= == == == ==
4244 opt/state A C M !
4244 opt/state A C M !
4245 ========= == == == ==
4245 ========= == == == ==
4246 none W RD W R
4246 none W RD W R
4247 -f R RD RD R
4247 -f R RD RD R
4248 -A W W W R
4248 -A W W W R
4249 -Af R R R R
4249 -Af R R R R
4250 ========= == == == ==
4250 ========= == == == ==
4251
4251
4252 .. note::
4252 .. note::
4253
4253
4254 :hg:`remove` never deletes files in Added [A] state from the
4254 :hg:`remove` never deletes files in Added [A] state from the
4255 working directory, not even if ``--force`` is specified.
4255 working directory, not even if ``--force`` is specified.
4256
4256
4257 Returns 0 on success, 1 if any warnings encountered.
4257 Returns 0 on success, 1 if any warnings encountered.
4258 """
4258 """
4259
4259
4260 opts = pycompat.byteskwargs(opts)
4260 opts = pycompat.byteskwargs(opts)
4261 after, force = opts.get('after'), opts.get('force')
4261 after, force = opts.get('after'), opts.get('force')
4262 dryrun = opts.get('dry_run')
4262 dryrun = opts.get('dry_run')
4263 if not pats and not after:
4263 if not pats and not after:
4264 raise error.Abort(_('no files specified'))
4264 raise error.Abort(_('no files specified'))
4265
4265
4266 m = scmutil.match(repo[None], pats, opts)
4266 m = scmutil.match(repo[None], pats, opts)
4267 subrepos = opts.get('subrepos')
4267 subrepos = opts.get('subrepos')
4268 return cmdutil.remove(ui, repo, m, "", after, force, subrepos,
4268 return cmdutil.remove(ui, repo, m, "", after, force, subrepos,
4269 dryrun=dryrun)
4269 dryrun=dryrun)
4270
4270
4271 @command('rename|move|mv',
4271 @command('rename|move|mv',
4272 [('A', 'after', None, _('record a rename that has already occurred')),
4272 [('A', 'after', None, _('record a rename that has already occurred')),
4273 ('f', 'force', None, _('forcibly copy over an existing managed file')),
4273 ('f', 'force', None, _('forcibly copy over an existing managed file')),
4274 ] + walkopts + dryrunopts,
4274 ] + walkopts + dryrunopts,
4275 _('[OPTION]... SOURCE... DEST'))
4275 _('[OPTION]... SOURCE... DEST'))
4276 def rename(ui, repo, *pats, **opts):
4276 def rename(ui, repo, *pats, **opts):
4277 """rename files; equivalent of copy + remove
4277 """rename files; equivalent of copy + remove
4278
4278
4279 Mark dest as copies of sources; mark sources for deletion. If dest
4279 Mark dest as copies of sources; mark sources for deletion. If dest
4280 is a directory, copies are put in that directory. If dest is a
4280 is a directory, copies are put in that directory. If dest is a
4281 file, there can only be one source.
4281 file, there can only be one source.
4282
4282
4283 By default, this command copies the contents of files as they
4283 By default, this command copies the contents of files as they
4284 exist in the working directory. If invoked with -A/--after, the
4284 exist in the working directory. If invoked with -A/--after, the
4285 operation is recorded, but no copying is performed.
4285 operation is recorded, but no copying is performed.
4286
4286
4287 This command takes effect at the next commit. To undo a rename
4287 This command takes effect at the next commit. To undo a rename
4288 before that, see :hg:`revert`.
4288 before that, see :hg:`revert`.
4289
4289
4290 Returns 0 on success, 1 if errors are encountered.
4290 Returns 0 on success, 1 if errors are encountered.
4291 """
4291 """
4292 opts = pycompat.byteskwargs(opts)
4292 opts = pycompat.byteskwargs(opts)
4293 with repo.wlock(False):
4293 with repo.wlock(False):
4294 return cmdutil.copy(ui, repo, pats, opts, rename=True)
4294 return cmdutil.copy(ui, repo, pats, opts, rename=True)
4295
4295
4296 @command('resolve',
4296 @command('resolve',
4297 [('a', 'all', None, _('select all unresolved files')),
4297 [('a', 'all', None, _('select all unresolved files')),
4298 ('l', 'list', None, _('list state of files needing merge')),
4298 ('l', 'list', None, _('list state of files needing merge')),
4299 ('m', 'mark', None, _('mark files as resolved')),
4299 ('m', 'mark', None, _('mark files as resolved')),
4300 ('u', 'unmark', None, _('mark files as unresolved')),
4300 ('u', 'unmark', None, _('mark files as unresolved')),
4301 ('n', 'no-status', None, _('hide status prefix'))]
4301 ('n', 'no-status', None, _('hide status prefix'))]
4302 + mergetoolopts + walkopts + formatteropts,
4302 + mergetoolopts + walkopts + formatteropts,
4303 _('[OPTION]... [FILE]...'),
4303 _('[OPTION]... [FILE]...'),
4304 inferrepo=True)
4304 inferrepo=True)
4305 def resolve(ui, repo, *pats, **opts):
4305 def resolve(ui, repo, *pats, **opts):
4306 """redo merges or set/view the merge status of files
4306 """redo merges or set/view the merge status of files
4307
4307
4308 Merges with unresolved conflicts are often the result of
4308 Merges with unresolved conflicts are often the result of
4309 non-interactive merging using the ``internal:merge`` configuration
4309 non-interactive merging using the ``internal:merge`` configuration
4310 setting, or a command-line merge tool like ``diff3``. The resolve
4310 setting, or a command-line merge tool like ``diff3``. The resolve
4311 command is used to manage the files involved in a merge, after
4311 command is used to manage the files involved in a merge, after
4312 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
4312 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
4313 working directory must have two parents). See :hg:`help
4313 working directory must have two parents). See :hg:`help
4314 merge-tools` for information on configuring merge tools.
4314 merge-tools` for information on configuring merge tools.
4315
4315
4316 The resolve command can be used in the following ways:
4316 The resolve command can be used in the following ways:
4317
4317
4318 - :hg:`resolve [--tool TOOL] FILE...`: attempt to re-merge the specified
4318 - :hg:`resolve [--tool TOOL] FILE...`: attempt to re-merge the specified
4319 files, discarding any previous merge attempts. Re-merging is not
4319 files, discarding any previous merge attempts. Re-merging is not
4320 performed for files already marked as resolved. Use ``--all/-a``
4320 performed for files already marked as resolved. Use ``--all/-a``
4321 to select all unresolved files. ``--tool`` can be used to specify
4321 to select all unresolved files. ``--tool`` can be used to specify
4322 the merge tool used for the given files. It overrides the HGMERGE
4322 the merge tool used for the given files. It overrides the HGMERGE
4323 environment variable and your configuration files. Previous file
4323 environment variable and your configuration files. Previous file
4324 contents are saved with a ``.orig`` suffix.
4324 contents are saved with a ``.orig`` suffix.
4325
4325
4326 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
4326 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
4327 (e.g. after having manually fixed-up the files). The default is
4327 (e.g. after having manually fixed-up the files). The default is
4328 to mark all unresolved files.
4328 to mark all unresolved files.
4329
4329
4330 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
4330 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
4331 default is to mark all resolved files.
4331 default is to mark all resolved files.
4332
4332
4333 - :hg:`resolve -l`: list files which had or still have conflicts.
4333 - :hg:`resolve -l`: list files which had or still have conflicts.
4334 In the printed list, ``U`` = unresolved and ``R`` = resolved.
4334 In the printed list, ``U`` = unresolved and ``R`` = resolved.
4335 You can use ``set:unresolved()`` or ``set:resolved()`` to filter
4335 You can use ``set:unresolved()`` or ``set:resolved()`` to filter
4336 the list. See :hg:`help filesets` for details.
4336 the list. See :hg:`help filesets` for details.
4337
4337
4338 .. note::
4338 .. note::
4339
4339
4340 Mercurial will not let you commit files with unresolved merge
4340 Mercurial will not let you commit files with unresolved merge
4341 conflicts. You must use :hg:`resolve -m ...` before you can
4341 conflicts. You must use :hg:`resolve -m ...` before you can
4342 commit after a conflicting merge.
4342 commit after a conflicting merge.
4343
4343
4344 Returns 0 on success, 1 if any files fail a resolve attempt.
4344 Returns 0 on success, 1 if any files fail a resolve attempt.
4345 """
4345 """
4346
4346
4347 opts = pycompat.byteskwargs(opts)
4347 opts = pycompat.byteskwargs(opts)
4348 flaglist = 'all mark unmark list no_status'.split()
4348 flaglist = 'all mark unmark list no_status'.split()
4349 all, mark, unmark, show, nostatus = \
4349 all, mark, unmark, show, nostatus = \
4350 [opts.get(o) for o in flaglist]
4350 [opts.get(o) for o in flaglist]
4351
4351
4352 if (show and (mark or unmark)) or (mark and unmark):
4352 if (show and (mark or unmark)) or (mark and unmark):
4353 raise error.Abort(_("too many options specified"))
4353 raise error.Abort(_("too many options specified"))
4354 if pats and all:
4354 if pats and all:
4355 raise error.Abort(_("can't specify --all and patterns"))
4355 raise error.Abort(_("can't specify --all and patterns"))
4356 if not (all or pats or show or mark or unmark):
4356 if not (all or pats or show or mark or unmark):
4357 raise error.Abort(_('no files or directories specified'),
4357 raise error.Abort(_('no files or directories specified'),
4358 hint=('use --all to re-merge all unresolved files'))
4358 hint=('use --all to re-merge all unresolved files'))
4359
4359
4360 if show:
4360 if show:
4361 ui.pager('resolve')
4361 ui.pager('resolve')
4362 fm = ui.formatter('resolve', opts)
4362 fm = ui.formatter('resolve', opts)
4363 ms = mergemod.mergestate.read(repo)
4363 ms = mergemod.mergestate.read(repo)
4364 m = scmutil.match(repo[None], pats, opts)
4364 m = scmutil.match(repo[None], pats, opts)
4365
4365
4366 # Labels and keys based on merge state. Unresolved path conflicts show
4366 # Labels and keys based on merge state. Unresolved path conflicts show
4367 # as 'P'. Resolved path conflicts show as 'R', the same as normal
4367 # as 'P'. Resolved path conflicts show as 'R', the same as normal
4368 # resolved conflicts.
4368 # resolved conflicts.
4369 mergestateinfo = {
4369 mergestateinfo = {
4370 mergemod.MERGE_RECORD_UNRESOLVED: ('resolve.unresolved', 'U'),
4370 mergemod.MERGE_RECORD_UNRESOLVED: ('resolve.unresolved', 'U'),
4371 mergemod.MERGE_RECORD_RESOLVED: ('resolve.resolved', 'R'),
4371 mergemod.MERGE_RECORD_RESOLVED: ('resolve.resolved', 'R'),
4372 mergemod.MERGE_RECORD_UNRESOLVED_PATH: ('resolve.unresolved', 'P'),
4372 mergemod.MERGE_RECORD_UNRESOLVED_PATH: ('resolve.unresolved', 'P'),
4373 mergemod.MERGE_RECORD_RESOLVED_PATH: ('resolve.resolved', 'R'),
4373 mergemod.MERGE_RECORD_RESOLVED_PATH: ('resolve.resolved', 'R'),
4374 mergemod.MERGE_RECORD_DRIVER_RESOLVED: ('resolve.driverresolved',
4374 mergemod.MERGE_RECORD_DRIVER_RESOLVED: ('resolve.driverresolved',
4375 'D'),
4375 'D'),
4376 }
4376 }
4377
4377
4378 for f in ms:
4378 for f in ms:
4379 if not m(f):
4379 if not m(f):
4380 continue
4380 continue
4381
4381
4382 label, key = mergestateinfo[ms[f]]
4382 label, key = mergestateinfo[ms[f]]
4383 fm.startitem()
4383 fm.startitem()
4384 fm.condwrite(not nostatus, 'status', '%s ', key, label=label)
4384 fm.condwrite(not nostatus, 'status', '%s ', key, label=label)
4385 fm.write('path', '%s\n', f, label=label)
4385 fm.write('path', '%s\n', f, label=label)
4386 fm.end()
4386 fm.end()
4387 return 0
4387 return 0
4388
4388
4389 with repo.wlock():
4389 with repo.wlock():
4390 ms = mergemod.mergestate.read(repo)
4390 ms = mergemod.mergestate.read(repo)
4391
4391
4392 if not (ms.active() or repo.dirstate.p2() != nullid):
4392 if not (ms.active() or repo.dirstate.p2() != nullid):
4393 raise error.Abort(
4393 raise error.Abort(
4394 _('resolve command not applicable when not merging'))
4394 _('resolve command not applicable when not merging'))
4395
4395
4396 wctx = repo[None]
4396 wctx = repo[None]
4397
4397
4398 if (ms.mergedriver
4398 if (ms.mergedriver
4399 and ms.mdstate() == mergemod.MERGE_DRIVER_STATE_UNMARKED):
4399 and ms.mdstate() == mergemod.MERGE_DRIVER_STATE_UNMARKED):
4400 proceed = mergemod.driverpreprocess(repo, ms, wctx)
4400 proceed = mergemod.driverpreprocess(repo, ms, wctx)
4401 ms.commit()
4401 ms.commit()
4402 # allow mark and unmark to go through
4402 # allow mark and unmark to go through
4403 if not mark and not unmark and not proceed:
4403 if not mark and not unmark and not proceed:
4404 return 1
4404 return 1
4405
4405
4406 m = scmutil.match(wctx, pats, opts)
4406 m = scmutil.match(wctx, pats, opts)
4407 ret = 0
4407 ret = 0
4408 didwork = False
4408 didwork = False
4409 runconclude = False
4409 runconclude = False
4410
4410
4411 tocomplete = []
4411 tocomplete = []
4412 for f in ms:
4412 for f in ms:
4413 if not m(f):
4413 if not m(f):
4414 continue
4414 continue
4415
4415
4416 didwork = True
4416 didwork = True
4417
4417
4418 # don't let driver-resolved files be marked, and run the conclude
4418 # don't let driver-resolved files be marked, and run the conclude
4419 # step if asked to resolve
4419 # step if asked to resolve
4420 if ms[f] == mergemod.MERGE_RECORD_DRIVER_RESOLVED:
4420 if ms[f] == mergemod.MERGE_RECORD_DRIVER_RESOLVED:
4421 exact = m.exact(f)
4421 exact = m.exact(f)
4422 if mark:
4422 if mark:
4423 if exact:
4423 if exact:
4424 ui.warn(_('not marking %s as it is driver-resolved\n')
4424 ui.warn(_('not marking %s as it is driver-resolved\n')
4425 % f)
4425 % f)
4426 elif unmark:
4426 elif unmark:
4427 if exact:
4427 if exact:
4428 ui.warn(_('not unmarking %s as it is driver-resolved\n')
4428 ui.warn(_('not unmarking %s as it is driver-resolved\n')
4429 % f)
4429 % f)
4430 else:
4430 else:
4431 runconclude = True
4431 runconclude = True
4432 continue
4432 continue
4433
4433
4434 # path conflicts must be resolved manually
4434 # path conflicts must be resolved manually
4435 if ms[f] in (mergemod.MERGE_RECORD_UNRESOLVED_PATH,
4435 if ms[f] in (mergemod.MERGE_RECORD_UNRESOLVED_PATH,
4436 mergemod.MERGE_RECORD_RESOLVED_PATH):
4436 mergemod.MERGE_RECORD_RESOLVED_PATH):
4437 if mark:
4437 if mark:
4438 ms.mark(f, mergemod.MERGE_RECORD_RESOLVED_PATH)
4438 ms.mark(f, mergemod.MERGE_RECORD_RESOLVED_PATH)
4439 elif unmark:
4439 elif unmark:
4440 ms.mark(f, mergemod.MERGE_RECORD_UNRESOLVED_PATH)
4440 ms.mark(f, mergemod.MERGE_RECORD_UNRESOLVED_PATH)
4441 elif ms[f] == mergemod.MERGE_RECORD_UNRESOLVED_PATH:
4441 elif ms[f] == mergemod.MERGE_RECORD_UNRESOLVED_PATH:
4442 ui.warn(_('%s: path conflict must be resolved manually\n')
4442 ui.warn(_('%s: path conflict must be resolved manually\n')
4443 % f)
4443 % f)
4444 continue
4444 continue
4445
4445
4446 if mark:
4446 if mark:
4447 ms.mark(f, mergemod.MERGE_RECORD_RESOLVED)
4447 ms.mark(f, mergemod.MERGE_RECORD_RESOLVED)
4448 elif unmark:
4448 elif unmark:
4449 ms.mark(f, mergemod.MERGE_RECORD_UNRESOLVED)
4449 ms.mark(f, mergemod.MERGE_RECORD_UNRESOLVED)
4450 else:
4450 else:
4451 # backup pre-resolve (merge uses .orig for its own purposes)
4451 # backup pre-resolve (merge uses .orig for its own purposes)
4452 a = repo.wjoin(f)
4452 a = repo.wjoin(f)
4453 try:
4453 try:
4454 util.copyfile(a, a + ".resolve")
4454 util.copyfile(a, a + ".resolve")
4455 except (IOError, OSError) as inst:
4455 except (IOError, OSError) as inst:
4456 if inst.errno != errno.ENOENT:
4456 if inst.errno != errno.ENOENT:
4457 raise
4457 raise
4458
4458
4459 try:
4459 try:
4460 # preresolve file
4460 # preresolve file
4461 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
4461 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
4462 'resolve')
4462 'resolve')
4463 complete, r = ms.preresolve(f, wctx)
4463 complete, r = ms.preresolve(f, wctx)
4464 if not complete:
4464 if not complete:
4465 tocomplete.append(f)
4465 tocomplete.append(f)
4466 elif r:
4466 elif r:
4467 ret = 1
4467 ret = 1
4468 finally:
4468 finally:
4469 ui.setconfig('ui', 'forcemerge', '', 'resolve')
4469 ui.setconfig('ui', 'forcemerge', '', 'resolve')
4470 ms.commit()
4470 ms.commit()
4471
4471
4472 # replace filemerge's .orig file with our resolve file, but only
4472 # replace filemerge's .orig file with our resolve file, but only
4473 # for merges that are complete
4473 # for merges that are complete
4474 if complete:
4474 if complete:
4475 try:
4475 try:
4476 util.rename(a + ".resolve",
4476 util.rename(a + ".resolve",
4477 scmutil.origpath(ui, repo, a))
4477 scmutil.origpath(ui, repo, a))
4478 except OSError as inst:
4478 except OSError as inst:
4479 if inst.errno != errno.ENOENT:
4479 if inst.errno != errno.ENOENT:
4480 raise
4480 raise
4481
4481
4482 for f in tocomplete:
4482 for f in tocomplete:
4483 try:
4483 try:
4484 # resolve file
4484 # resolve file
4485 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
4485 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
4486 'resolve')
4486 'resolve')
4487 r = ms.resolve(f, wctx)
4487 r = ms.resolve(f, wctx)
4488 if r:
4488 if r:
4489 ret = 1
4489 ret = 1
4490 finally:
4490 finally:
4491 ui.setconfig('ui', 'forcemerge', '', 'resolve')
4491 ui.setconfig('ui', 'forcemerge', '', 'resolve')
4492 ms.commit()
4492 ms.commit()
4493
4493
4494 # replace filemerge's .orig file with our resolve file
4494 # replace filemerge's .orig file with our resolve file
4495 a = repo.wjoin(f)
4495 a = repo.wjoin(f)
4496 try:
4496 try:
4497 util.rename(a + ".resolve", scmutil.origpath(ui, repo, a))
4497 util.rename(a + ".resolve", scmutil.origpath(ui, repo, a))
4498 except OSError as inst:
4498 except OSError as inst:
4499 if inst.errno != errno.ENOENT:
4499 if inst.errno != errno.ENOENT:
4500 raise
4500 raise
4501
4501
4502 ms.commit()
4502 ms.commit()
4503 ms.recordactions()
4503 ms.recordactions()
4504
4504
4505 if not didwork and pats:
4505 if not didwork and pats:
4506 hint = None
4506 hint = None
4507 if not any([p for p in pats if p.find(':') >= 0]):
4507 if not any([p for p in pats if p.find(':') >= 0]):
4508 pats = ['path:%s' % p for p in pats]
4508 pats = ['path:%s' % p for p in pats]
4509 m = scmutil.match(wctx, pats, opts)
4509 m = scmutil.match(wctx, pats, opts)
4510 for f in ms:
4510 for f in ms:
4511 if not m(f):
4511 if not m(f):
4512 continue
4512 continue
4513 flags = ''.join(['-%s ' % o[0:1] for o in flaglist
4513 flags = ''.join(['-%s ' % o[0:1] for o in flaglist
4514 if opts.get(o)])
4514 if opts.get(o)])
4515 hint = _("(try: hg resolve %s%s)\n") % (
4515 hint = _("(try: hg resolve %s%s)\n") % (
4516 flags,
4516 flags,
4517 ' '.join(pats))
4517 ' '.join(pats))
4518 break
4518 break
4519 ui.warn(_("arguments do not match paths that need resolving\n"))
4519 ui.warn(_("arguments do not match paths that need resolving\n"))
4520 if hint:
4520 if hint:
4521 ui.warn(hint)
4521 ui.warn(hint)
4522 elif ms.mergedriver and ms.mdstate() != 's':
4522 elif ms.mergedriver and ms.mdstate() != 's':
4523 # run conclude step when either a driver-resolved file is requested
4523 # run conclude step when either a driver-resolved file is requested
4524 # or there are no driver-resolved files
4524 # or there are no driver-resolved files
4525 # we can't use 'ret' to determine whether any files are unresolved
4525 # we can't use 'ret' to determine whether any files are unresolved
4526 # because we might not have tried to resolve some
4526 # because we might not have tried to resolve some
4527 if ((runconclude or not list(ms.driverresolved()))
4527 if ((runconclude or not list(ms.driverresolved()))
4528 and not list(ms.unresolved())):
4528 and not list(ms.unresolved())):
4529 proceed = mergemod.driverconclude(repo, ms, wctx)
4529 proceed = mergemod.driverconclude(repo, ms, wctx)
4530 ms.commit()
4530 ms.commit()
4531 if not proceed:
4531 if not proceed:
4532 return 1
4532 return 1
4533
4533
4534 # Nudge users into finishing an unfinished operation
4534 # Nudge users into finishing an unfinished operation
4535 unresolvedf = list(ms.unresolved())
4535 unresolvedf = list(ms.unresolved())
4536 driverresolvedf = list(ms.driverresolved())
4536 driverresolvedf = list(ms.driverresolved())
4537 if not unresolvedf and not driverresolvedf:
4537 if not unresolvedf and not driverresolvedf:
4538 ui.status(_('(no more unresolved files)\n'))
4538 ui.status(_('(no more unresolved files)\n'))
4539 cmdutil.checkafterresolved(repo)
4539 cmdutil.checkafterresolved(repo)
4540 elif not unresolvedf:
4540 elif not unresolvedf:
4541 ui.status(_('(no more unresolved files -- '
4541 ui.status(_('(no more unresolved files -- '
4542 'run "hg resolve --all" to conclude)\n'))
4542 'run "hg resolve --all" to conclude)\n'))
4543
4543
4544 return ret
4544 return ret
4545
4545
4546 @command('revert',
4546 @command('revert',
4547 [('a', 'all', None, _('revert all changes when no arguments given')),
4547 [('a', 'all', None, _('revert all changes when no arguments given')),
4548 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
4548 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
4549 ('r', 'rev', '', _('revert to the specified revision'), _('REV')),
4549 ('r', 'rev', '', _('revert to the specified revision'), _('REV')),
4550 ('C', 'no-backup', None, _('do not save backup copies of files')),
4550 ('C', 'no-backup', None, _('do not save backup copies of files')),
4551 ('i', 'interactive', None, _('interactively select the changes')),
4551 ('i', 'interactive', None, _('interactively select the changes')),
4552 ] + walkopts + dryrunopts,
4552 ] + walkopts + dryrunopts,
4553 _('[OPTION]... [-r REV] [NAME]...'))
4553 _('[OPTION]... [-r REV] [NAME]...'))
4554 def revert(ui, repo, *pats, **opts):
4554 def revert(ui, repo, *pats, **opts):
4555 """restore files to their checkout state
4555 """restore files to their checkout state
4556
4556
4557 .. note::
4557 .. note::
4558
4558
4559 To check out earlier revisions, you should use :hg:`update REV`.
4559 To check out earlier revisions, you should use :hg:`update REV`.
4560 To cancel an uncommitted merge (and lose your changes),
4560 To cancel an uncommitted merge (and lose your changes),
4561 use :hg:`merge --abort`.
4561 use :hg:`merge --abort`.
4562
4562
4563 With no revision specified, revert the specified files or directories
4563 With no revision specified, revert the specified files or directories
4564 to the contents they had in the parent of the working directory.
4564 to the contents they had in the parent of the working directory.
4565 This restores the contents of files to an unmodified
4565 This restores the contents of files to an unmodified
4566 state and unschedules adds, removes, copies, and renames. If the
4566 state and unschedules adds, removes, copies, and renames. If the
4567 working directory has two parents, you must explicitly specify a
4567 working directory has two parents, you must explicitly specify a
4568 revision.
4568 revision.
4569
4569
4570 Using the -r/--rev or -d/--date options, revert the given files or
4570 Using the -r/--rev or -d/--date options, revert the given files or
4571 directories to their states as of a specific revision. Because
4571 directories to their states as of a specific revision. Because
4572 revert does not change the working directory parents, this will
4572 revert does not change the working directory parents, this will
4573 cause these files to appear modified. This can be helpful to "back
4573 cause these files to appear modified. This can be helpful to "back
4574 out" some or all of an earlier change. See :hg:`backout` for a
4574 out" some or all of an earlier change. See :hg:`backout` for a
4575 related method.
4575 related method.
4576
4576
4577 Modified files are saved with a .orig suffix before reverting.
4577 Modified files are saved with a .orig suffix before reverting.
4578 To disable these backups, use --no-backup. It is possible to store
4578 To disable these backups, use --no-backup. It is possible to store
4579 the backup files in a custom directory relative to the root of the
4579 the backup files in a custom directory relative to the root of the
4580 repository by setting the ``ui.origbackuppath`` configuration
4580 repository by setting the ``ui.origbackuppath`` configuration
4581 option.
4581 option.
4582
4582
4583 See :hg:`help dates` for a list of formats valid for -d/--date.
4583 See :hg:`help dates` for a list of formats valid for -d/--date.
4584
4584
4585 See :hg:`help backout` for a way to reverse the effect of an
4585 See :hg:`help backout` for a way to reverse the effect of an
4586 earlier changeset.
4586 earlier changeset.
4587
4587
4588 Returns 0 on success.
4588 Returns 0 on success.
4589 """
4589 """
4590
4590
4591 opts = pycompat.byteskwargs(opts)
4591 opts = pycompat.byteskwargs(opts)
4592 if opts.get("date"):
4592 if opts.get("date"):
4593 if opts.get("rev"):
4593 if opts.get("rev"):
4594 raise error.Abort(_("you can't specify a revision and a date"))
4594 raise error.Abort(_("you can't specify a revision and a date"))
4595 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
4595 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
4596
4596
4597 parent, p2 = repo.dirstate.parents()
4597 parent, p2 = repo.dirstate.parents()
4598 if not opts.get('rev') and p2 != nullid:
4598 if not opts.get('rev') and p2 != nullid:
4599 # revert after merge is a trap for new users (issue2915)
4599 # revert after merge is a trap for new users (issue2915)
4600 raise error.Abort(_('uncommitted merge with no revision specified'),
4600 raise error.Abort(_('uncommitted merge with no revision specified'),
4601 hint=_("use 'hg update' or see 'hg help revert'"))
4601 hint=_("use 'hg update' or see 'hg help revert'"))
4602
4602
4603 rev = opts.get('rev')
4603 rev = opts.get('rev')
4604 if rev:
4604 if rev:
4605 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
4605 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
4606 ctx = scmutil.revsingle(repo, rev)
4606 ctx = scmutil.revsingle(repo, rev)
4607
4607
4608 if (not (pats or opts.get('include') or opts.get('exclude') or
4608 if (not (pats or opts.get('include') or opts.get('exclude') or
4609 opts.get('all') or opts.get('interactive'))):
4609 opts.get('all') or opts.get('interactive'))):
4610 msg = _("no files or directories specified")
4610 msg = _("no files or directories specified")
4611 if p2 != nullid:
4611 if p2 != nullid:
4612 hint = _("uncommitted merge, use --all to discard all changes,"
4612 hint = _("uncommitted merge, use --all to discard all changes,"
4613 " or 'hg update -C .' to abort the merge")
4613 " or 'hg update -C .' to abort the merge")
4614 raise error.Abort(msg, hint=hint)
4614 raise error.Abort(msg, hint=hint)
4615 dirty = any(repo.status())
4615 dirty = any(repo.status())
4616 node = ctx.node()
4616 node = ctx.node()
4617 if node != parent:
4617 if node != parent:
4618 if dirty:
4618 if dirty:
4619 hint = _("uncommitted changes, use --all to discard all"
4619 hint = _("uncommitted changes, use --all to discard all"
4620 " changes, or 'hg update %s' to update") % ctx.rev()
4620 " changes, or 'hg update %s' to update") % ctx.rev()
4621 else:
4621 else:
4622 hint = _("use --all to revert all files,"
4622 hint = _("use --all to revert all files,"
4623 " or 'hg update %s' to update") % ctx.rev()
4623 " or 'hg update %s' to update") % ctx.rev()
4624 elif dirty:
4624 elif dirty:
4625 hint = _("uncommitted changes, use --all to discard all changes")
4625 hint = _("uncommitted changes, use --all to discard all changes")
4626 else:
4626 else:
4627 hint = _("use --all to revert all files")
4627 hint = _("use --all to revert all files")
4628 raise error.Abort(msg, hint=hint)
4628 raise error.Abort(msg, hint=hint)
4629
4629
4630 return cmdutil.revert(ui, repo, ctx, (parent, p2), *pats,
4630 return cmdutil.revert(ui, repo, ctx, (parent, p2), *pats,
4631 **pycompat.strkwargs(opts))
4631 **pycompat.strkwargs(opts))
4632
4632
4633 @command('rollback', dryrunopts +
4633 @command('rollback', dryrunopts +
4634 [('f', 'force', False, _('ignore safety measures'))])
4634 [('f', 'force', False, _('ignore safety measures'))])
4635 def rollback(ui, repo, **opts):
4635 def rollback(ui, repo, **opts):
4636 """roll back the last transaction (DANGEROUS) (DEPRECATED)
4636 """roll back the last transaction (DANGEROUS) (DEPRECATED)
4637
4637
4638 Please use :hg:`commit --amend` instead of rollback to correct
4638 Please use :hg:`commit --amend` instead of rollback to correct
4639 mistakes in the last commit.
4639 mistakes in the last commit.
4640
4640
4641 This command should be used with care. There is only one level of
4641 This command should be used with care. There is only one level of
4642 rollback, and there is no way to undo a rollback. It will also
4642 rollback, and there is no way to undo a rollback. It will also
4643 restore the dirstate at the time of the last transaction, losing
4643 restore the dirstate at the time of the last transaction, losing
4644 any dirstate changes since that time. This command does not alter
4644 any dirstate changes since that time. This command does not alter
4645 the working directory.
4645 the working directory.
4646
4646
4647 Transactions are used to encapsulate the effects of all commands
4647 Transactions are used to encapsulate the effects of all commands
4648 that create new changesets or propagate existing changesets into a
4648 that create new changesets or propagate existing changesets into a
4649 repository.
4649 repository.
4650
4650
4651 .. container:: verbose
4651 .. container:: verbose
4652
4652
4653 For example, the following commands are transactional, and their
4653 For example, the following commands are transactional, and their
4654 effects can be rolled back:
4654 effects can be rolled back:
4655
4655
4656 - commit
4656 - commit
4657 - import
4657 - import
4658 - pull
4658 - pull
4659 - push (with this repository as the destination)
4659 - push (with this repository as the destination)
4660 - unbundle
4660 - unbundle
4661
4661
4662 To avoid permanent data loss, rollback will refuse to rollback a
4662 To avoid permanent data loss, rollback will refuse to rollback a
4663 commit transaction if it isn't checked out. Use --force to
4663 commit transaction if it isn't checked out. Use --force to
4664 override this protection.
4664 override this protection.
4665
4665
4666 The rollback command can be entirely disabled by setting the
4666 The rollback command can be entirely disabled by setting the
4667 ``ui.rollback`` configuration setting to false. If you're here
4667 ``ui.rollback`` configuration setting to false. If you're here
4668 because you want to use rollback and it's disabled, you can
4668 because you want to use rollback and it's disabled, you can
4669 re-enable the command by setting ``ui.rollback`` to true.
4669 re-enable the command by setting ``ui.rollback`` to true.
4670
4670
4671 This command is not intended for use on public repositories. Once
4671 This command is not intended for use on public repositories. Once
4672 changes are visible for pull by other users, rolling a transaction
4672 changes are visible for pull by other users, rolling a transaction
4673 back locally is ineffective (someone else may already have pulled
4673 back locally is ineffective (someone else may already have pulled
4674 the changes). Furthermore, a race is possible with readers of the
4674 the changes). Furthermore, a race is possible with readers of the
4675 repository; for example an in-progress pull from the repository
4675 repository; for example an in-progress pull from the repository
4676 may fail if a rollback is performed.
4676 may fail if a rollback is performed.
4677
4677
4678 Returns 0 on success, 1 if no rollback data is available.
4678 Returns 0 on success, 1 if no rollback data is available.
4679 """
4679 """
4680 if not ui.configbool('ui', 'rollback'):
4680 if not ui.configbool('ui', 'rollback'):
4681 raise error.Abort(_('rollback is disabled because it is unsafe'),
4681 raise error.Abort(_('rollback is disabled because it is unsafe'),
4682 hint=('see `hg help -v rollback` for information'))
4682 hint=('see `hg help -v rollback` for information'))
4683 return repo.rollback(dryrun=opts.get(r'dry_run'),
4683 return repo.rollback(dryrun=opts.get(r'dry_run'),
4684 force=opts.get(r'force'))
4684 force=opts.get(r'force'))
4685
4685
4686 @command('root', [], cmdtype=readonly)
4686 @command('root', [], cmdtype=readonly)
4687 def root(ui, repo):
4687 def root(ui, repo):
4688 """print the root (top) of the current working directory
4688 """print the root (top) of the current working directory
4689
4689
4690 Print the root directory of the current repository.
4690 Print the root directory of the current repository.
4691
4691
4692 Returns 0 on success.
4692 Returns 0 on success.
4693 """
4693 """
4694 ui.write(repo.root + "\n")
4694 ui.write(repo.root + "\n")
4695
4695
4696 @command('^serve',
4696 @command('^serve',
4697 [('A', 'accesslog', '', _('name of access log file to write to'),
4697 [('A', 'accesslog', '', _('name of access log file to write to'),
4698 _('FILE')),
4698 _('FILE')),
4699 ('d', 'daemon', None, _('run server in background')),
4699 ('d', 'daemon', None, _('run server in background')),
4700 ('', 'daemon-postexec', [], _('used internally by daemon mode')),
4700 ('', 'daemon-postexec', [], _('used internally by daemon mode')),
4701 ('E', 'errorlog', '', _('name of error log file to write to'), _('FILE')),
4701 ('E', 'errorlog', '', _('name of error log file to write to'), _('FILE')),
4702 # use string type, then we can check if something was passed
4702 # use string type, then we can check if something was passed
4703 ('p', 'port', '', _('port to listen on (default: 8000)'), _('PORT')),
4703 ('p', 'port', '', _('port to listen on (default: 8000)'), _('PORT')),
4704 ('a', 'address', '', _('address to listen on (default: all interfaces)'),
4704 ('a', 'address', '', _('address to listen on (default: all interfaces)'),
4705 _('ADDR')),
4705 _('ADDR')),
4706 ('', 'prefix', '', _('prefix path to serve from (default: server root)'),
4706 ('', 'prefix', '', _('prefix path to serve from (default: server root)'),
4707 _('PREFIX')),
4707 _('PREFIX')),
4708 ('n', 'name', '',
4708 ('n', 'name', '',
4709 _('name to show in web pages (default: working directory)'), _('NAME')),
4709 _('name to show in web pages (default: working directory)'), _('NAME')),
4710 ('', 'web-conf', '',
4710 ('', 'web-conf', '',
4711 _("name of the hgweb config file (see 'hg help hgweb')"), _('FILE')),
4711 _("name of the hgweb config file (see 'hg help hgweb')"), _('FILE')),
4712 ('', 'webdir-conf', '', _('name of the hgweb config file (DEPRECATED)'),
4712 ('', 'webdir-conf', '', _('name of the hgweb config file (DEPRECATED)'),
4713 _('FILE')),
4713 _('FILE')),
4714 ('', 'pid-file', '', _('name of file to write process ID to'), _('FILE')),
4714 ('', 'pid-file', '', _('name of file to write process ID to'), _('FILE')),
4715 ('', 'stdio', None, _('for remote clients (ADVANCED)')),
4715 ('', 'stdio', None, _('for remote clients (ADVANCED)')),
4716 ('', 'cmdserver', '', _('for remote clients (ADVANCED)'), _('MODE')),
4716 ('', 'cmdserver', '', _('for remote clients (ADVANCED)'), _('MODE')),
4717 ('t', 'templates', '', _('web templates to use'), _('TEMPLATE')),
4717 ('t', 'templates', '', _('web templates to use'), _('TEMPLATE')),
4718 ('', 'style', '', _('template style to use'), _('STYLE')),
4718 ('', 'style', '', _('template style to use'), _('STYLE')),
4719 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
4719 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
4720 ('', 'certificate', '', _('SSL certificate file'), _('FILE'))]
4720 ('', 'certificate', '', _('SSL certificate file'), _('FILE'))]
4721 + subrepoopts,
4721 + subrepoopts,
4722 _('[OPTION]...'),
4722 _('[OPTION]...'),
4723 optionalrepo=True)
4723 optionalrepo=True)
4724 def serve(ui, repo, **opts):
4724 def serve(ui, repo, **opts):
4725 """start stand-alone webserver
4725 """start stand-alone webserver
4726
4726
4727 Start a local HTTP repository browser and pull server. You can use
4727 Start a local HTTP repository browser and pull server. You can use
4728 this for ad-hoc sharing and browsing of repositories. It is
4728 this for ad-hoc sharing and browsing of repositories. It is
4729 recommended to use a real web server to serve a repository for
4729 recommended to use a real web server to serve a repository for
4730 longer periods of time.
4730 longer periods of time.
4731
4731
4732 Please note that the server does not implement access control.
4732 Please note that the server does not implement access control.
4733 This means that, by default, anybody can read from the server and
4733 This means that, by default, anybody can read from the server and
4734 nobody can write to it by default. Set the ``web.allow-push``
4734 nobody can write to it by default. Set the ``web.allow-push``
4735 option to ``*`` to allow everybody to push to the server. You
4735 option to ``*`` to allow everybody to push to the server. You
4736 should use a real web server if you need to authenticate users.
4736 should use a real web server if you need to authenticate users.
4737
4737
4738 By default, the server logs accesses to stdout and errors to
4738 By default, the server logs accesses to stdout and errors to
4739 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
4739 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
4740 files.
4740 files.
4741
4741
4742 To have the server choose a free port number to listen on, specify
4742 To have the server choose a free port number to listen on, specify
4743 a port number of 0; in this case, the server will print the port
4743 a port number of 0; in this case, the server will print the port
4744 number it uses.
4744 number it uses.
4745
4745
4746 Returns 0 on success.
4746 Returns 0 on success.
4747 """
4747 """
4748
4748
4749 opts = pycompat.byteskwargs(opts)
4749 opts = pycompat.byteskwargs(opts)
4750 if opts["stdio"] and opts["cmdserver"]:
4750 if opts["stdio"] and opts["cmdserver"]:
4751 raise error.Abort(_("cannot use --stdio with --cmdserver"))
4751 raise error.Abort(_("cannot use --stdio with --cmdserver"))
4752
4752
4753 if opts["stdio"]:
4753 if opts["stdio"]:
4754 if repo is None:
4754 if repo is None:
4755 raise error.RepoError(_("there is no Mercurial repository here"
4755 raise error.RepoError(_("there is no Mercurial repository here"
4756 " (.hg not found)"))
4756 " (.hg not found)"))
4757 s = wireprotoserver.sshserver(ui, repo)
4757 s = wireprotoserver.sshserver(ui, repo)
4758 s.serve_forever()
4758 s.serve_forever()
4759
4759
4760 service = server.createservice(ui, repo, opts)
4760 service = server.createservice(ui, repo, opts)
4761 return server.runservice(opts, initfn=service.init, runfn=service.run)
4761 return server.runservice(opts, initfn=service.init, runfn=service.run)
4762
4762
4763 @command('^status|st',
4763 @command('^status|st',
4764 [('A', 'all', None, _('show status of all files')),
4764 [('A', 'all', None, _('show status of all files')),
4765 ('m', 'modified', None, _('show only modified files')),
4765 ('m', 'modified', None, _('show only modified files')),
4766 ('a', 'added', None, _('show only added files')),
4766 ('a', 'added', None, _('show only added files')),
4767 ('r', 'removed', None, _('show only removed files')),
4767 ('r', 'removed', None, _('show only removed files')),
4768 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
4768 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
4769 ('c', 'clean', None, _('show only files without changes')),
4769 ('c', 'clean', None, _('show only files without changes')),
4770 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
4770 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
4771 ('i', 'ignored', None, _('show only ignored files')),
4771 ('i', 'ignored', None, _('show only ignored files')),
4772 ('n', 'no-status', None, _('hide status prefix')),
4772 ('n', 'no-status', None, _('hide status prefix')),
4773 ('t', 'terse', '', _('show the terse output (EXPERIMENTAL)')),
4773 ('t', 'terse', '', _('show the terse output (EXPERIMENTAL)')),
4774 ('C', 'copies', None, _('show source of copied files')),
4774 ('C', 'copies', None, _('show source of copied files')),
4775 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
4775 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
4776 ('', 'rev', [], _('show difference from revision'), _('REV')),
4776 ('', 'rev', [], _('show difference from revision'), _('REV')),
4777 ('', 'change', '', _('list the changed files of a revision'), _('REV')),
4777 ('', 'change', '', _('list the changed files of a revision'), _('REV')),
4778 ] + walkopts + subrepoopts + formatteropts,
4778 ] + walkopts + subrepoopts + formatteropts,
4779 _('[OPTION]... [FILE]...'),
4779 _('[OPTION]... [FILE]...'),
4780 inferrepo=True, cmdtype=readonly)
4780 inferrepo=True, cmdtype=readonly)
4781 def status(ui, repo, *pats, **opts):
4781 def status(ui, repo, *pats, **opts):
4782 """show changed files in the working directory
4782 """show changed files in the working directory
4783
4783
4784 Show status of files in the repository. If names are given, only
4784 Show status of files in the repository. If names are given, only
4785 files that match are shown. Files that are clean or ignored or
4785 files that match are shown. Files that are clean or ignored or
4786 the source of a copy/move operation, are not listed unless
4786 the source of a copy/move operation, are not listed unless
4787 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
4787 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
4788 Unless options described with "show only ..." are given, the
4788 Unless options described with "show only ..." are given, the
4789 options -mardu are used.
4789 options -mardu are used.
4790
4790
4791 Option -q/--quiet hides untracked (unknown and ignored) files
4791 Option -q/--quiet hides untracked (unknown and ignored) files
4792 unless explicitly requested with -u/--unknown or -i/--ignored.
4792 unless explicitly requested with -u/--unknown or -i/--ignored.
4793
4793
4794 .. note::
4794 .. note::
4795
4795
4796 :hg:`status` may appear to disagree with diff if permissions have
4796 :hg:`status` may appear to disagree with diff if permissions have
4797 changed or a merge has occurred. The standard diff format does
4797 changed or a merge has occurred. The standard diff format does
4798 not report permission changes and diff only reports changes
4798 not report permission changes and diff only reports changes
4799 relative to one merge parent.
4799 relative to one merge parent.
4800
4800
4801 If one revision is given, it is used as the base revision.
4801 If one revision is given, it is used as the base revision.
4802 If two revisions are given, the differences between them are
4802 If two revisions are given, the differences between them are
4803 shown. The --change option can also be used as a shortcut to list
4803 shown. The --change option can also be used as a shortcut to list
4804 the changed files of a revision from its first parent.
4804 the changed files of a revision from its first parent.
4805
4805
4806 The codes used to show the status of files are::
4806 The codes used to show the status of files are::
4807
4807
4808 M = modified
4808 M = modified
4809 A = added
4809 A = added
4810 R = removed
4810 R = removed
4811 C = clean
4811 C = clean
4812 ! = missing (deleted by non-hg command, but still tracked)
4812 ! = missing (deleted by non-hg command, but still tracked)
4813 ? = not tracked
4813 ? = not tracked
4814 I = ignored
4814 I = ignored
4815 = origin of the previous file (with --copies)
4815 = origin of the previous file (with --copies)
4816
4816
4817 .. container:: verbose
4817 .. container:: verbose
4818
4818
4819 The -t/--terse option abbreviates the output by showing only the directory
4819 The -t/--terse option abbreviates the output by showing only the directory
4820 name if all the files in it share the same status. The option takes an
4820 name if all the files in it share the same status. The option takes an
4821 argument indicating the statuses to abbreviate: 'm' for 'modified', 'a'
4821 argument indicating the statuses to abbreviate: 'm' for 'modified', 'a'
4822 for 'added', 'r' for 'removed', 'd' for 'deleted', 'u' for 'unknown', 'i'
4822 for 'added', 'r' for 'removed', 'd' for 'deleted', 'u' for 'unknown', 'i'
4823 for 'ignored' and 'c' for clean.
4823 for 'ignored' and 'c' for clean.
4824
4824
4825 It abbreviates only those statuses which are passed. Note that clean and
4825 It abbreviates only those statuses which are passed. Note that clean and
4826 ignored files are not displayed with '--terse ic' unless the -c/--clean
4826 ignored files are not displayed with '--terse ic' unless the -c/--clean
4827 and -i/--ignored options are also used.
4827 and -i/--ignored options are also used.
4828
4828
4829 The -v/--verbose option shows information when the repository is in an
4829 The -v/--verbose option shows information when the repository is in an
4830 unfinished merge, shelve, rebase state etc. You can have this behavior
4830 unfinished merge, shelve, rebase state etc. You can have this behavior
4831 turned on by default by enabling the ``commands.status.verbose`` option.
4831 turned on by default by enabling the ``commands.status.verbose`` option.
4832
4832
4833 You can skip displaying some of these states by setting
4833 You can skip displaying some of these states by setting
4834 ``commands.status.skipstates`` to one or more of: 'bisect', 'graft',
4834 ``commands.status.skipstates`` to one or more of: 'bisect', 'graft',
4835 'histedit', 'merge', 'rebase', or 'unshelve'.
4835 'histedit', 'merge', 'rebase', or 'unshelve'.
4836
4836
4837 Examples:
4837 Examples:
4838
4838
4839 - show changes in the working directory relative to a
4839 - show changes in the working directory relative to a
4840 changeset::
4840 changeset::
4841
4841
4842 hg status --rev 9353
4842 hg status --rev 9353
4843
4843
4844 - show changes in the working directory relative to the
4844 - show changes in the working directory relative to the
4845 current directory (see :hg:`help patterns` for more information)::
4845 current directory (see :hg:`help patterns` for more information)::
4846
4846
4847 hg status re:
4847 hg status re:
4848
4848
4849 - show all changes including copies in an existing changeset::
4849 - show all changes including copies in an existing changeset::
4850
4850
4851 hg status --copies --change 9353
4851 hg status --copies --change 9353
4852
4852
4853 - get a NUL separated list of added files, suitable for xargs::
4853 - get a NUL separated list of added files, suitable for xargs::
4854
4854
4855 hg status -an0
4855 hg status -an0
4856
4856
4857 - show more information about the repository status, abbreviating
4857 - show more information about the repository status, abbreviating
4858 added, removed, modified, deleted, and untracked paths::
4858 added, removed, modified, deleted, and untracked paths::
4859
4859
4860 hg status -v -t mardu
4860 hg status -v -t mardu
4861
4861
4862 Returns 0 on success.
4862 Returns 0 on success.
4863
4863
4864 """
4864 """
4865
4865
4866 opts = pycompat.byteskwargs(opts)
4866 opts = pycompat.byteskwargs(opts)
4867 revs = opts.get('rev')
4867 revs = opts.get('rev')
4868 change = opts.get('change')
4868 change = opts.get('change')
4869 terse = opts.get('terse')
4869 terse = opts.get('terse')
4870
4870
4871 if revs and change:
4871 if revs and change:
4872 msg = _('cannot specify --rev and --change at the same time')
4872 msg = _('cannot specify --rev and --change at the same time')
4873 raise error.Abort(msg)
4873 raise error.Abort(msg)
4874 elif revs and terse:
4874 elif revs and terse:
4875 msg = _('cannot use --terse with --rev')
4875 msg = _('cannot use --terse with --rev')
4876 raise error.Abort(msg)
4876 raise error.Abort(msg)
4877 elif change:
4877 elif change:
4878 repo = scmutil.unhidehashlikerevs(repo, [change], 'nowarn')
4878 repo = scmutil.unhidehashlikerevs(repo, [change], 'nowarn')
4879 node2 = scmutil.revsingle(repo, change, None).node()
4879 node2 = scmutil.revsingle(repo, change, None).node()
4880 node1 = repo[node2].p1().node()
4880 node1 = repo[node2].p1().node()
4881 else:
4881 else:
4882 repo = scmutil.unhidehashlikerevs(repo, revs, 'nowarn')
4882 repo = scmutil.unhidehashlikerevs(repo, revs, 'nowarn')
4883 node1, node2 = scmutil.revpair(repo, revs)
4883 node1, node2 = scmutil.revpairnodes(repo, revs)
4884
4884
4885 if pats or ui.configbool('commands', 'status.relative'):
4885 if pats or ui.configbool('commands', 'status.relative'):
4886 cwd = repo.getcwd()
4886 cwd = repo.getcwd()
4887 else:
4887 else:
4888 cwd = ''
4888 cwd = ''
4889
4889
4890 if opts.get('print0'):
4890 if opts.get('print0'):
4891 end = '\0'
4891 end = '\0'
4892 else:
4892 else:
4893 end = '\n'
4893 end = '\n'
4894 copy = {}
4894 copy = {}
4895 states = 'modified added removed deleted unknown ignored clean'.split()
4895 states = 'modified added removed deleted unknown ignored clean'.split()
4896 show = [k for k in states if opts.get(k)]
4896 show = [k for k in states if opts.get(k)]
4897 if opts.get('all'):
4897 if opts.get('all'):
4898 show += ui.quiet and (states[:4] + ['clean']) or states
4898 show += ui.quiet and (states[:4] + ['clean']) or states
4899
4899
4900 if not show:
4900 if not show:
4901 if ui.quiet:
4901 if ui.quiet:
4902 show = states[:4]
4902 show = states[:4]
4903 else:
4903 else:
4904 show = states[:5]
4904 show = states[:5]
4905
4905
4906 m = scmutil.match(repo[node2], pats, opts)
4906 m = scmutil.match(repo[node2], pats, opts)
4907 if terse:
4907 if terse:
4908 # we need to compute clean and unknown to terse
4908 # we need to compute clean and unknown to terse
4909 stat = repo.status(node1, node2, m,
4909 stat = repo.status(node1, node2, m,
4910 'ignored' in show or 'i' in terse,
4910 'ignored' in show or 'i' in terse,
4911 True, True, opts.get('subrepos'))
4911 True, True, opts.get('subrepos'))
4912
4912
4913 stat = cmdutil.tersedir(stat, terse)
4913 stat = cmdutil.tersedir(stat, terse)
4914 else:
4914 else:
4915 stat = repo.status(node1, node2, m,
4915 stat = repo.status(node1, node2, m,
4916 'ignored' in show, 'clean' in show,
4916 'ignored' in show, 'clean' in show,
4917 'unknown' in show, opts.get('subrepos'))
4917 'unknown' in show, opts.get('subrepos'))
4918
4918
4919 changestates = zip(states, pycompat.iterbytestr('MAR!?IC'), stat)
4919 changestates = zip(states, pycompat.iterbytestr('MAR!?IC'), stat)
4920
4920
4921 if (opts.get('all') or opts.get('copies')
4921 if (opts.get('all') or opts.get('copies')
4922 or ui.configbool('ui', 'statuscopies')) and not opts.get('no_status'):
4922 or ui.configbool('ui', 'statuscopies')) and not opts.get('no_status'):
4923 copy = copies.pathcopies(repo[node1], repo[node2], m)
4923 copy = copies.pathcopies(repo[node1], repo[node2], m)
4924
4924
4925 ui.pager('status')
4925 ui.pager('status')
4926 fm = ui.formatter('status', opts)
4926 fm = ui.formatter('status', opts)
4927 fmt = '%s' + end
4927 fmt = '%s' + end
4928 showchar = not opts.get('no_status')
4928 showchar = not opts.get('no_status')
4929
4929
4930 for state, char, files in changestates:
4930 for state, char, files in changestates:
4931 if state in show:
4931 if state in show:
4932 label = 'status.' + state
4932 label = 'status.' + state
4933 for f in files:
4933 for f in files:
4934 fm.startitem()
4934 fm.startitem()
4935 fm.condwrite(showchar, 'status', '%s ', char, label=label)
4935 fm.condwrite(showchar, 'status', '%s ', char, label=label)
4936 fm.write('path', fmt, repo.pathto(f, cwd), label=label)
4936 fm.write('path', fmt, repo.pathto(f, cwd), label=label)
4937 if f in copy:
4937 if f in copy:
4938 fm.write("copy", ' %s' + end, repo.pathto(copy[f], cwd),
4938 fm.write("copy", ' %s' + end, repo.pathto(copy[f], cwd),
4939 label='status.copied')
4939 label='status.copied')
4940
4940
4941 if ((ui.verbose or ui.configbool('commands', 'status.verbose'))
4941 if ((ui.verbose or ui.configbool('commands', 'status.verbose'))
4942 and not ui.plain()):
4942 and not ui.plain()):
4943 cmdutil.morestatus(repo, fm)
4943 cmdutil.morestatus(repo, fm)
4944 fm.end()
4944 fm.end()
4945
4945
4946 @command('^summary|sum',
4946 @command('^summary|sum',
4947 [('', 'remote', None, _('check for push and pull'))],
4947 [('', 'remote', None, _('check for push and pull'))],
4948 '[--remote]', cmdtype=readonly)
4948 '[--remote]', cmdtype=readonly)
4949 def summary(ui, repo, **opts):
4949 def summary(ui, repo, **opts):
4950 """summarize working directory state
4950 """summarize working directory state
4951
4951
4952 This generates a brief summary of the working directory state,
4952 This generates a brief summary of the working directory state,
4953 including parents, branch, commit status, phase and available updates.
4953 including parents, branch, commit status, phase and available updates.
4954
4954
4955 With the --remote option, this will check the default paths for
4955 With the --remote option, this will check the default paths for
4956 incoming and outgoing changes. This can be time-consuming.
4956 incoming and outgoing changes. This can be time-consuming.
4957
4957
4958 Returns 0 on success.
4958 Returns 0 on success.
4959 """
4959 """
4960
4960
4961 opts = pycompat.byteskwargs(opts)
4961 opts = pycompat.byteskwargs(opts)
4962 ui.pager('summary')
4962 ui.pager('summary')
4963 ctx = repo[None]
4963 ctx = repo[None]
4964 parents = ctx.parents()
4964 parents = ctx.parents()
4965 pnode = parents[0].node()
4965 pnode = parents[0].node()
4966 marks = []
4966 marks = []
4967
4967
4968 ms = None
4968 ms = None
4969 try:
4969 try:
4970 ms = mergemod.mergestate.read(repo)
4970 ms = mergemod.mergestate.read(repo)
4971 except error.UnsupportedMergeRecords as e:
4971 except error.UnsupportedMergeRecords as e:
4972 s = ' '.join(e.recordtypes)
4972 s = ' '.join(e.recordtypes)
4973 ui.warn(
4973 ui.warn(
4974 _('warning: merge state has unsupported record types: %s\n') % s)
4974 _('warning: merge state has unsupported record types: %s\n') % s)
4975 unresolved = []
4975 unresolved = []
4976 else:
4976 else:
4977 unresolved = list(ms.unresolved())
4977 unresolved = list(ms.unresolved())
4978
4978
4979 for p in parents:
4979 for p in parents:
4980 # label with log.changeset (instead of log.parent) since this
4980 # label with log.changeset (instead of log.parent) since this
4981 # shows a working directory parent *changeset*:
4981 # shows a working directory parent *changeset*:
4982 # i18n: column positioning for "hg summary"
4982 # i18n: column positioning for "hg summary"
4983 ui.write(_('parent: %d:%s ') % (p.rev(), p),
4983 ui.write(_('parent: %d:%s ') % (p.rev(), p),
4984 label=logcmdutil.changesetlabels(p))
4984 label=logcmdutil.changesetlabels(p))
4985 ui.write(' '.join(p.tags()), label='log.tag')
4985 ui.write(' '.join(p.tags()), label='log.tag')
4986 if p.bookmarks():
4986 if p.bookmarks():
4987 marks.extend(p.bookmarks())
4987 marks.extend(p.bookmarks())
4988 if p.rev() == -1:
4988 if p.rev() == -1:
4989 if not len(repo):
4989 if not len(repo):
4990 ui.write(_(' (empty repository)'))
4990 ui.write(_(' (empty repository)'))
4991 else:
4991 else:
4992 ui.write(_(' (no revision checked out)'))
4992 ui.write(_(' (no revision checked out)'))
4993 if p.obsolete():
4993 if p.obsolete():
4994 ui.write(_(' (obsolete)'))
4994 ui.write(_(' (obsolete)'))
4995 if p.isunstable():
4995 if p.isunstable():
4996 instabilities = (ui.label(instability, 'trouble.%s' % instability)
4996 instabilities = (ui.label(instability, 'trouble.%s' % instability)
4997 for instability in p.instabilities())
4997 for instability in p.instabilities())
4998 ui.write(' ('
4998 ui.write(' ('
4999 + ', '.join(instabilities)
4999 + ', '.join(instabilities)
5000 + ')')
5000 + ')')
5001 ui.write('\n')
5001 ui.write('\n')
5002 if p.description():
5002 if p.description():
5003 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
5003 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
5004 label='log.summary')
5004 label='log.summary')
5005
5005
5006 branch = ctx.branch()
5006 branch = ctx.branch()
5007 bheads = repo.branchheads(branch)
5007 bheads = repo.branchheads(branch)
5008 # i18n: column positioning for "hg summary"
5008 # i18n: column positioning for "hg summary"
5009 m = _('branch: %s\n') % branch
5009 m = _('branch: %s\n') % branch
5010 if branch != 'default':
5010 if branch != 'default':
5011 ui.write(m, label='log.branch')
5011 ui.write(m, label='log.branch')
5012 else:
5012 else:
5013 ui.status(m, label='log.branch')
5013 ui.status(m, label='log.branch')
5014
5014
5015 if marks:
5015 if marks:
5016 active = repo._activebookmark
5016 active = repo._activebookmark
5017 # i18n: column positioning for "hg summary"
5017 # i18n: column positioning for "hg summary"
5018 ui.write(_('bookmarks:'), label='log.bookmark')
5018 ui.write(_('bookmarks:'), label='log.bookmark')
5019 if active is not None:
5019 if active is not None:
5020 if active in marks:
5020 if active in marks:
5021 ui.write(' *' + active, label=bookmarks.activebookmarklabel)
5021 ui.write(' *' + active, label=bookmarks.activebookmarklabel)
5022 marks.remove(active)
5022 marks.remove(active)
5023 else:
5023 else:
5024 ui.write(' [%s]' % active, label=bookmarks.activebookmarklabel)
5024 ui.write(' [%s]' % active, label=bookmarks.activebookmarklabel)
5025 for m in marks:
5025 for m in marks:
5026 ui.write(' ' + m, label='log.bookmark')
5026 ui.write(' ' + m, label='log.bookmark')
5027 ui.write('\n', label='log.bookmark')
5027 ui.write('\n', label='log.bookmark')
5028
5028
5029 status = repo.status(unknown=True)
5029 status = repo.status(unknown=True)
5030
5030
5031 c = repo.dirstate.copies()
5031 c = repo.dirstate.copies()
5032 copied, renamed = [], []
5032 copied, renamed = [], []
5033 for d, s in c.iteritems():
5033 for d, s in c.iteritems():
5034 if s in status.removed:
5034 if s in status.removed:
5035 status.removed.remove(s)
5035 status.removed.remove(s)
5036 renamed.append(d)
5036 renamed.append(d)
5037 else:
5037 else:
5038 copied.append(d)
5038 copied.append(d)
5039 if d in status.added:
5039 if d in status.added:
5040 status.added.remove(d)
5040 status.added.remove(d)
5041
5041
5042 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
5042 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
5043
5043
5044 labels = [(ui.label(_('%d modified'), 'status.modified'), status.modified),
5044 labels = [(ui.label(_('%d modified'), 'status.modified'), status.modified),
5045 (ui.label(_('%d added'), 'status.added'), status.added),
5045 (ui.label(_('%d added'), 'status.added'), status.added),
5046 (ui.label(_('%d removed'), 'status.removed'), status.removed),
5046 (ui.label(_('%d removed'), 'status.removed'), status.removed),
5047 (ui.label(_('%d renamed'), 'status.copied'), renamed),
5047 (ui.label(_('%d renamed'), 'status.copied'), renamed),
5048 (ui.label(_('%d copied'), 'status.copied'), copied),
5048 (ui.label(_('%d copied'), 'status.copied'), copied),
5049 (ui.label(_('%d deleted'), 'status.deleted'), status.deleted),
5049 (ui.label(_('%d deleted'), 'status.deleted'), status.deleted),
5050 (ui.label(_('%d unknown'), 'status.unknown'), status.unknown),
5050 (ui.label(_('%d unknown'), 'status.unknown'), status.unknown),
5051 (ui.label(_('%d unresolved'), 'resolve.unresolved'), unresolved),
5051 (ui.label(_('%d unresolved'), 'resolve.unresolved'), unresolved),
5052 (ui.label(_('%d subrepos'), 'status.modified'), subs)]
5052 (ui.label(_('%d subrepos'), 'status.modified'), subs)]
5053 t = []
5053 t = []
5054 for l, s in labels:
5054 for l, s in labels:
5055 if s:
5055 if s:
5056 t.append(l % len(s))
5056 t.append(l % len(s))
5057
5057
5058 t = ', '.join(t)
5058 t = ', '.join(t)
5059 cleanworkdir = False
5059 cleanworkdir = False
5060
5060
5061 if repo.vfs.exists('graftstate'):
5061 if repo.vfs.exists('graftstate'):
5062 t += _(' (graft in progress)')
5062 t += _(' (graft in progress)')
5063 if repo.vfs.exists('updatestate'):
5063 if repo.vfs.exists('updatestate'):
5064 t += _(' (interrupted update)')
5064 t += _(' (interrupted update)')
5065 elif len(parents) > 1:
5065 elif len(parents) > 1:
5066 t += _(' (merge)')
5066 t += _(' (merge)')
5067 elif branch != parents[0].branch():
5067 elif branch != parents[0].branch():
5068 t += _(' (new branch)')
5068 t += _(' (new branch)')
5069 elif (parents[0].closesbranch() and
5069 elif (parents[0].closesbranch() and
5070 pnode in repo.branchheads(branch, closed=True)):
5070 pnode in repo.branchheads(branch, closed=True)):
5071 t += _(' (head closed)')
5071 t += _(' (head closed)')
5072 elif not (status.modified or status.added or status.removed or renamed or
5072 elif not (status.modified or status.added or status.removed or renamed or
5073 copied or subs):
5073 copied or subs):
5074 t += _(' (clean)')
5074 t += _(' (clean)')
5075 cleanworkdir = True
5075 cleanworkdir = True
5076 elif pnode not in bheads:
5076 elif pnode not in bheads:
5077 t += _(' (new branch head)')
5077 t += _(' (new branch head)')
5078
5078
5079 if parents:
5079 if parents:
5080 pendingphase = max(p.phase() for p in parents)
5080 pendingphase = max(p.phase() for p in parents)
5081 else:
5081 else:
5082 pendingphase = phases.public
5082 pendingphase = phases.public
5083
5083
5084 if pendingphase > phases.newcommitphase(ui):
5084 if pendingphase > phases.newcommitphase(ui):
5085 t += ' (%s)' % phases.phasenames[pendingphase]
5085 t += ' (%s)' % phases.phasenames[pendingphase]
5086
5086
5087 if cleanworkdir:
5087 if cleanworkdir:
5088 # i18n: column positioning for "hg summary"
5088 # i18n: column positioning for "hg summary"
5089 ui.status(_('commit: %s\n') % t.strip())
5089 ui.status(_('commit: %s\n') % t.strip())
5090 else:
5090 else:
5091 # i18n: column positioning for "hg summary"
5091 # i18n: column positioning for "hg summary"
5092 ui.write(_('commit: %s\n') % t.strip())
5092 ui.write(_('commit: %s\n') % t.strip())
5093
5093
5094 # all ancestors of branch heads - all ancestors of parent = new csets
5094 # all ancestors of branch heads - all ancestors of parent = new csets
5095 new = len(repo.changelog.findmissing([pctx.node() for pctx in parents],
5095 new = len(repo.changelog.findmissing([pctx.node() for pctx in parents],
5096 bheads))
5096 bheads))
5097
5097
5098 if new == 0:
5098 if new == 0:
5099 # i18n: column positioning for "hg summary"
5099 # i18n: column positioning for "hg summary"
5100 ui.status(_('update: (current)\n'))
5100 ui.status(_('update: (current)\n'))
5101 elif pnode not in bheads:
5101 elif pnode not in bheads:
5102 # i18n: column positioning for "hg summary"
5102 # i18n: column positioning for "hg summary"
5103 ui.write(_('update: %d new changesets (update)\n') % new)
5103 ui.write(_('update: %d new changesets (update)\n') % new)
5104 else:
5104 else:
5105 # i18n: column positioning for "hg summary"
5105 # i18n: column positioning for "hg summary"
5106 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
5106 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
5107 (new, len(bheads)))
5107 (new, len(bheads)))
5108
5108
5109 t = []
5109 t = []
5110 draft = len(repo.revs('draft()'))
5110 draft = len(repo.revs('draft()'))
5111 if draft:
5111 if draft:
5112 t.append(_('%d draft') % draft)
5112 t.append(_('%d draft') % draft)
5113 secret = len(repo.revs('secret()'))
5113 secret = len(repo.revs('secret()'))
5114 if secret:
5114 if secret:
5115 t.append(_('%d secret') % secret)
5115 t.append(_('%d secret') % secret)
5116
5116
5117 if draft or secret:
5117 if draft or secret:
5118 ui.status(_('phases: %s\n') % ', '.join(t))
5118 ui.status(_('phases: %s\n') % ', '.join(t))
5119
5119
5120 if obsolete.isenabled(repo, obsolete.createmarkersopt):
5120 if obsolete.isenabled(repo, obsolete.createmarkersopt):
5121 for trouble in ("orphan", "contentdivergent", "phasedivergent"):
5121 for trouble in ("orphan", "contentdivergent", "phasedivergent"):
5122 numtrouble = len(repo.revs(trouble + "()"))
5122 numtrouble = len(repo.revs(trouble + "()"))
5123 # We write all the possibilities to ease translation
5123 # We write all the possibilities to ease translation
5124 troublemsg = {
5124 troublemsg = {
5125 "orphan": _("orphan: %d changesets"),
5125 "orphan": _("orphan: %d changesets"),
5126 "contentdivergent": _("content-divergent: %d changesets"),
5126 "contentdivergent": _("content-divergent: %d changesets"),
5127 "phasedivergent": _("phase-divergent: %d changesets"),
5127 "phasedivergent": _("phase-divergent: %d changesets"),
5128 }
5128 }
5129 if numtrouble > 0:
5129 if numtrouble > 0:
5130 ui.status(troublemsg[trouble] % numtrouble + "\n")
5130 ui.status(troublemsg[trouble] % numtrouble + "\n")
5131
5131
5132 cmdutil.summaryhooks(ui, repo)
5132 cmdutil.summaryhooks(ui, repo)
5133
5133
5134 if opts.get('remote'):
5134 if opts.get('remote'):
5135 needsincoming, needsoutgoing = True, True
5135 needsincoming, needsoutgoing = True, True
5136 else:
5136 else:
5137 needsincoming, needsoutgoing = False, False
5137 needsincoming, needsoutgoing = False, False
5138 for i, o in cmdutil.summaryremotehooks(ui, repo, opts, None):
5138 for i, o in cmdutil.summaryremotehooks(ui, repo, opts, None):
5139 if i:
5139 if i:
5140 needsincoming = True
5140 needsincoming = True
5141 if o:
5141 if o:
5142 needsoutgoing = True
5142 needsoutgoing = True
5143 if not needsincoming and not needsoutgoing:
5143 if not needsincoming and not needsoutgoing:
5144 return
5144 return
5145
5145
5146 def getincoming():
5146 def getincoming():
5147 source, branches = hg.parseurl(ui.expandpath('default'))
5147 source, branches = hg.parseurl(ui.expandpath('default'))
5148 sbranch = branches[0]
5148 sbranch = branches[0]
5149 try:
5149 try:
5150 other = hg.peer(repo, {}, source)
5150 other = hg.peer(repo, {}, source)
5151 except error.RepoError:
5151 except error.RepoError:
5152 if opts.get('remote'):
5152 if opts.get('remote'):
5153 raise
5153 raise
5154 return source, sbranch, None, None, None
5154 return source, sbranch, None, None, None
5155 revs, checkout = hg.addbranchrevs(repo, other, branches, None)
5155 revs, checkout = hg.addbranchrevs(repo, other, branches, None)
5156 if revs:
5156 if revs:
5157 revs = [other.lookup(rev) for rev in revs]
5157 revs = [other.lookup(rev) for rev in revs]
5158 ui.debug('comparing with %s\n' % util.hidepassword(source))
5158 ui.debug('comparing with %s\n' % util.hidepassword(source))
5159 repo.ui.pushbuffer()
5159 repo.ui.pushbuffer()
5160 commoninc = discovery.findcommonincoming(repo, other, heads=revs)
5160 commoninc = discovery.findcommonincoming(repo, other, heads=revs)
5161 repo.ui.popbuffer()
5161 repo.ui.popbuffer()
5162 return source, sbranch, other, commoninc, commoninc[1]
5162 return source, sbranch, other, commoninc, commoninc[1]
5163
5163
5164 if needsincoming:
5164 if needsincoming:
5165 source, sbranch, sother, commoninc, incoming = getincoming()
5165 source, sbranch, sother, commoninc, incoming = getincoming()
5166 else:
5166 else:
5167 source = sbranch = sother = commoninc = incoming = None
5167 source = sbranch = sother = commoninc = incoming = None
5168
5168
5169 def getoutgoing():
5169 def getoutgoing():
5170 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
5170 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
5171 dbranch = branches[0]
5171 dbranch = branches[0]
5172 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
5172 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
5173 if source != dest:
5173 if source != dest:
5174 try:
5174 try:
5175 dother = hg.peer(repo, {}, dest)
5175 dother = hg.peer(repo, {}, dest)
5176 except error.RepoError:
5176 except error.RepoError:
5177 if opts.get('remote'):
5177 if opts.get('remote'):
5178 raise
5178 raise
5179 return dest, dbranch, None, None
5179 return dest, dbranch, None, None
5180 ui.debug('comparing with %s\n' % util.hidepassword(dest))
5180 ui.debug('comparing with %s\n' % util.hidepassword(dest))
5181 elif sother is None:
5181 elif sother is None:
5182 # there is no explicit destination peer, but source one is invalid
5182 # there is no explicit destination peer, but source one is invalid
5183 return dest, dbranch, None, None
5183 return dest, dbranch, None, None
5184 else:
5184 else:
5185 dother = sother
5185 dother = sother
5186 if (source != dest or (sbranch is not None and sbranch != dbranch)):
5186 if (source != dest or (sbranch is not None and sbranch != dbranch)):
5187 common = None
5187 common = None
5188 else:
5188 else:
5189 common = commoninc
5189 common = commoninc
5190 if revs:
5190 if revs:
5191 revs = [repo.lookup(rev) for rev in revs]
5191 revs = [repo.lookup(rev) for rev in revs]
5192 repo.ui.pushbuffer()
5192 repo.ui.pushbuffer()
5193 outgoing = discovery.findcommonoutgoing(repo, dother, onlyheads=revs,
5193 outgoing = discovery.findcommonoutgoing(repo, dother, onlyheads=revs,
5194 commoninc=common)
5194 commoninc=common)
5195 repo.ui.popbuffer()
5195 repo.ui.popbuffer()
5196 return dest, dbranch, dother, outgoing
5196 return dest, dbranch, dother, outgoing
5197
5197
5198 if needsoutgoing:
5198 if needsoutgoing:
5199 dest, dbranch, dother, outgoing = getoutgoing()
5199 dest, dbranch, dother, outgoing = getoutgoing()
5200 else:
5200 else:
5201 dest = dbranch = dother = outgoing = None
5201 dest = dbranch = dother = outgoing = None
5202
5202
5203 if opts.get('remote'):
5203 if opts.get('remote'):
5204 t = []
5204 t = []
5205 if incoming:
5205 if incoming:
5206 t.append(_('1 or more incoming'))
5206 t.append(_('1 or more incoming'))
5207 o = outgoing.missing
5207 o = outgoing.missing
5208 if o:
5208 if o:
5209 t.append(_('%d outgoing') % len(o))
5209 t.append(_('%d outgoing') % len(o))
5210 other = dother or sother
5210 other = dother or sother
5211 if 'bookmarks' in other.listkeys('namespaces'):
5211 if 'bookmarks' in other.listkeys('namespaces'):
5212 counts = bookmarks.summary(repo, other)
5212 counts = bookmarks.summary(repo, other)
5213 if counts[0] > 0:
5213 if counts[0] > 0:
5214 t.append(_('%d incoming bookmarks') % counts[0])
5214 t.append(_('%d incoming bookmarks') % counts[0])
5215 if counts[1] > 0:
5215 if counts[1] > 0:
5216 t.append(_('%d outgoing bookmarks') % counts[1])
5216 t.append(_('%d outgoing bookmarks') % counts[1])
5217
5217
5218 if t:
5218 if t:
5219 # i18n: column positioning for "hg summary"
5219 # i18n: column positioning for "hg summary"
5220 ui.write(_('remote: %s\n') % (', '.join(t)))
5220 ui.write(_('remote: %s\n') % (', '.join(t)))
5221 else:
5221 else:
5222 # i18n: column positioning for "hg summary"
5222 # i18n: column positioning for "hg summary"
5223 ui.status(_('remote: (synced)\n'))
5223 ui.status(_('remote: (synced)\n'))
5224
5224
5225 cmdutil.summaryremotehooks(ui, repo, opts,
5225 cmdutil.summaryremotehooks(ui, repo, opts,
5226 ((source, sbranch, sother, commoninc),
5226 ((source, sbranch, sother, commoninc),
5227 (dest, dbranch, dother, outgoing)))
5227 (dest, dbranch, dother, outgoing)))
5228
5228
5229 @command('tag',
5229 @command('tag',
5230 [('f', 'force', None, _('force tag')),
5230 [('f', 'force', None, _('force tag')),
5231 ('l', 'local', None, _('make the tag local')),
5231 ('l', 'local', None, _('make the tag local')),
5232 ('r', 'rev', '', _('revision to tag'), _('REV')),
5232 ('r', 'rev', '', _('revision to tag'), _('REV')),
5233 ('', 'remove', None, _('remove a tag')),
5233 ('', 'remove', None, _('remove a tag')),
5234 # -l/--local is already there, commitopts cannot be used
5234 # -l/--local is already there, commitopts cannot be used
5235 ('e', 'edit', None, _('invoke editor on commit messages')),
5235 ('e', 'edit', None, _('invoke editor on commit messages')),
5236 ('m', 'message', '', _('use text as commit message'), _('TEXT')),
5236 ('m', 'message', '', _('use text as commit message'), _('TEXT')),
5237 ] + commitopts2,
5237 ] + commitopts2,
5238 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'))
5238 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'))
5239 def tag(ui, repo, name1, *names, **opts):
5239 def tag(ui, repo, name1, *names, **opts):
5240 """add one or more tags for the current or given revision
5240 """add one or more tags for the current or given revision
5241
5241
5242 Name a particular revision using <name>.
5242 Name a particular revision using <name>.
5243
5243
5244 Tags are used to name particular revisions of the repository and are
5244 Tags are used to name particular revisions of the repository and are
5245 very useful to compare different revisions, to go back to significant
5245 very useful to compare different revisions, to go back to significant
5246 earlier versions or to mark branch points as releases, etc. Changing
5246 earlier versions or to mark branch points as releases, etc. Changing
5247 an existing tag is normally disallowed; use -f/--force to override.
5247 an existing tag is normally disallowed; use -f/--force to override.
5248
5248
5249 If no revision is given, the parent of the working directory is
5249 If no revision is given, the parent of the working directory is
5250 used.
5250 used.
5251
5251
5252 To facilitate version control, distribution, and merging of tags,
5252 To facilitate version control, distribution, and merging of tags,
5253 they are stored as a file named ".hgtags" which is managed similarly
5253 they are stored as a file named ".hgtags" which is managed similarly
5254 to other project files and can be hand-edited if necessary. This
5254 to other project files and can be hand-edited if necessary. This
5255 also means that tagging creates a new commit. The file
5255 also means that tagging creates a new commit. The file
5256 ".hg/localtags" is used for local tags (not shared among
5256 ".hg/localtags" is used for local tags (not shared among
5257 repositories).
5257 repositories).
5258
5258
5259 Tag commits are usually made at the head of a branch. If the parent
5259 Tag commits are usually made at the head of a branch. If the parent
5260 of the working directory is not a branch head, :hg:`tag` aborts; use
5260 of the working directory is not a branch head, :hg:`tag` aborts; use
5261 -f/--force to force the tag commit to be based on a non-head
5261 -f/--force to force the tag commit to be based on a non-head
5262 changeset.
5262 changeset.
5263
5263
5264 See :hg:`help dates` for a list of formats valid for -d/--date.
5264 See :hg:`help dates` for a list of formats valid for -d/--date.
5265
5265
5266 Since tag names have priority over branch names during revision
5266 Since tag names have priority over branch names during revision
5267 lookup, using an existing branch name as a tag name is discouraged.
5267 lookup, using an existing branch name as a tag name is discouraged.
5268
5268
5269 Returns 0 on success.
5269 Returns 0 on success.
5270 """
5270 """
5271 opts = pycompat.byteskwargs(opts)
5271 opts = pycompat.byteskwargs(opts)
5272 wlock = lock = None
5272 wlock = lock = None
5273 try:
5273 try:
5274 wlock = repo.wlock()
5274 wlock = repo.wlock()
5275 lock = repo.lock()
5275 lock = repo.lock()
5276 rev_ = "."
5276 rev_ = "."
5277 names = [t.strip() for t in (name1,) + names]
5277 names = [t.strip() for t in (name1,) + names]
5278 if len(names) != len(set(names)):
5278 if len(names) != len(set(names)):
5279 raise error.Abort(_('tag names must be unique'))
5279 raise error.Abort(_('tag names must be unique'))
5280 for n in names:
5280 for n in names:
5281 scmutil.checknewlabel(repo, n, 'tag')
5281 scmutil.checknewlabel(repo, n, 'tag')
5282 if not n:
5282 if not n:
5283 raise error.Abort(_('tag names cannot consist entirely of '
5283 raise error.Abort(_('tag names cannot consist entirely of '
5284 'whitespace'))
5284 'whitespace'))
5285 if opts.get('rev') and opts.get('remove'):
5285 if opts.get('rev') and opts.get('remove'):
5286 raise error.Abort(_("--rev and --remove are incompatible"))
5286 raise error.Abort(_("--rev and --remove are incompatible"))
5287 if opts.get('rev'):
5287 if opts.get('rev'):
5288 rev_ = opts['rev']
5288 rev_ = opts['rev']
5289 message = opts.get('message')
5289 message = opts.get('message')
5290 if opts.get('remove'):
5290 if opts.get('remove'):
5291 if opts.get('local'):
5291 if opts.get('local'):
5292 expectedtype = 'local'
5292 expectedtype = 'local'
5293 else:
5293 else:
5294 expectedtype = 'global'
5294 expectedtype = 'global'
5295
5295
5296 for n in names:
5296 for n in names:
5297 if not repo.tagtype(n):
5297 if not repo.tagtype(n):
5298 raise error.Abort(_("tag '%s' does not exist") % n)
5298 raise error.Abort(_("tag '%s' does not exist") % n)
5299 if repo.tagtype(n) != expectedtype:
5299 if repo.tagtype(n) != expectedtype:
5300 if expectedtype == 'global':
5300 if expectedtype == 'global':
5301 raise error.Abort(_("tag '%s' is not a global tag") % n)
5301 raise error.Abort(_("tag '%s' is not a global tag") % n)
5302 else:
5302 else:
5303 raise error.Abort(_("tag '%s' is not a local tag") % n)
5303 raise error.Abort(_("tag '%s' is not a local tag") % n)
5304 rev_ = 'null'
5304 rev_ = 'null'
5305 if not message:
5305 if not message:
5306 # we don't translate commit messages
5306 # we don't translate commit messages
5307 message = 'Removed tag %s' % ', '.join(names)
5307 message = 'Removed tag %s' % ', '.join(names)
5308 elif not opts.get('force'):
5308 elif not opts.get('force'):
5309 for n in names:
5309 for n in names:
5310 if n in repo.tags():
5310 if n in repo.tags():
5311 raise error.Abort(_("tag '%s' already exists "
5311 raise error.Abort(_("tag '%s' already exists "
5312 "(use -f to force)") % n)
5312 "(use -f to force)") % n)
5313 if not opts.get('local'):
5313 if not opts.get('local'):
5314 p1, p2 = repo.dirstate.parents()
5314 p1, p2 = repo.dirstate.parents()
5315 if p2 != nullid:
5315 if p2 != nullid:
5316 raise error.Abort(_('uncommitted merge'))
5316 raise error.Abort(_('uncommitted merge'))
5317 bheads = repo.branchheads()
5317 bheads = repo.branchheads()
5318 if not opts.get('force') and bheads and p1 not in bheads:
5318 if not opts.get('force') and bheads and p1 not in bheads:
5319 raise error.Abort(_('working directory is not at a branch head '
5319 raise error.Abort(_('working directory is not at a branch head '
5320 '(use -f to force)'))
5320 '(use -f to force)'))
5321 node = scmutil.revsingle(repo, rev_).node()
5321 node = scmutil.revsingle(repo, rev_).node()
5322
5322
5323 if not message:
5323 if not message:
5324 # we don't translate commit messages
5324 # we don't translate commit messages
5325 message = ('Added tag %s for changeset %s' %
5325 message = ('Added tag %s for changeset %s' %
5326 (', '.join(names), short(node)))
5326 (', '.join(names), short(node)))
5327
5327
5328 date = opts.get('date')
5328 date = opts.get('date')
5329 if date:
5329 if date:
5330 date = dateutil.parsedate(date)
5330 date = dateutil.parsedate(date)
5331
5331
5332 if opts.get('remove'):
5332 if opts.get('remove'):
5333 editform = 'tag.remove'
5333 editform = 'tag.remove'
5334 else:
5334 else:
5335 editform = 'tag.add'
5335 editform = 'tag.add'
5336 editor = cmdutil.getcommiteditor(editform=editform,
5336 editor = cmdutil.getcommiteditor(editform=editform,
5337 **pycompat.strkwargs(opts))
5337 **pycompat.strkwargs(opts))
5338
5338
5339 # don't allow tagging the null rev
5339 # don't allow tagging the null rev
5340 if (not opts.get('remove') and
5340 if (not opts.get('remove') and
5341 scmutil.revsingle(repo, rev_).rev() == nullrev):
5341 scmutil.revsingle(repo, rev_).rev() == nullrev):
5342 raise error.Abort(_("cannot tag null revision"))
5342 raise error.Abort(_("cannot tag null revision"))
5343
5343
5344 tagsmod.tag(repo, names, node, message, opts.get('local'),
5344 tagsmod.tag(repo, names, node, message, opts.get('local'),
5345 opts.get('user'), date, editor=editor)
5345 opts.get('user'), date, editor=editor)
5346 finally:
5346 finally:
5347 release(lock, wlock)
5347 release(lock, wlock)
5348
5348
5349 @command('tags', formatteropts, '', cmdtype=readonly)
5349 @command('tags', formatteropts, '', cmdtype=readonly)
5350 def tags(ui, repo, **opts):
5350 def tags(ui, repo, **opts):
5351 """list repository tags
5351 """list repository tags
5352
5352
5353 This lists both regular and local tags. When the -v/--verbose
5353 This lists both regular and local tags. When the -v/--verbose
5354 switch is used, a third column "local" is printed for local tags.
5354 switch is used, a third column "local" is printed for local tags.
5355 When the -q/--quiet switch is used, only the tag name is printed.
5355 When the -q/--quiet switch is used, only the tag name is printed.
5356
5356
5357 Returns 0 on success.
5357 Returns 0 on success.
5358 """
5358 """
5359
5359
5360 opts = pycompat.byteskwargs(opts)
5360 opts = pycompat.byteskwargs(opts)
5361 ui.pager('tags')
5361 ui.pager('tags')
5362 fm = ui.formatter('tags', opts)
5362 fm = ui.formatter('tags', opts)
5363 hexfunc = fm.hexfunc
5363 hexfunc = fm.hexfunc
5364 tagtype = ""
5364 tagtype = ""
5365
5365
5366 for t, n in reversed(repo.tagslist()):
5366 for t, n in reversed(repo.tagslist()):
5367 hn = hexfunc(n)
5367 hn = hexfunc(n)
5368 label = 'tags.normal'
5368 label = 'tags.normal'
5369 tagtype = ''
5369 tagtype = ''
5370 if repo.tagtype(t) == 'local':
5370 if repo.tagtype(t) == 'local':
5371 label = 'tags.local'
5371 label = 'tags.local'
5372 tagtype = 'local'
5372 tagtype = 'local'
5373
5373
5374 fm.startitem()
5374 fm.startitem()
5375 fm.write('tag', '%s', t, label=label)
5375 fm.write('tag', '%s', t, label=label)
5376 fmt = " " * (30 - encoding.colwidth(t)) + ' %5d:%s'
5376 fmt = " " * (30 - encoding.colwidth(t)) + ' %5d:%s'
5377 fm.condwrite(not ui.quiet, 'rev node', fmt,
5377 fm.condwrite(not ui.quiet, 'rev node', fmt,
5378 repo.changelog.rev(n), hn, label=label)
5378 repo.changelog.rev(n), hn, label=label)
5379 fm.condwrite(ui.verbose and tagtype, 'type', ' %s',
5379 fm.condwrite(ui.verbose and tagtype, 'type', ' %s',
5380 tagtype, label=label)
5380 tagtype, label=label)
5381 fm.plain('\n')
5381 fm.plain('\n')
5382 fm.end()
5382 fm.end()
5383
5383
5384 @command('tip',
5384 @command('tip',
5385 [('p', 'patch', None, _('show patch')),
5385 [('p', 'patch', None, _('show patch')),
5386 ('g', 'git', None, _('use git extended diff format')),
5386 ('g', 'git', None, _('use git extended diff format')),
5387 ] + templateopts,
5387 ] + templateopts,
5388 _('[-p] [-g]'))
5388 _('[-p] [-g]'))
5389 def tip(ui, repo, **opts):
5389 def tip(ui, repo, **opts):
5390 """show the tip revision (DEPRECATED)
5390 """show the tip revision (DEPRECATED)
5391
5391
5392 The tip revision (usually just called the tip) is the changeset
5392 The tip revision (usually just called the tip) is the changeset
5393 most recently added to the repository (and therefore the most
5393 most recently added to the repository (and therefore the most
5394 recently changed head).
5394 recently changed head).
5395
5395
5396 If you have just made a commit, that commit will be the tip. If
5396 If you have just made a commit, that commit will be the tip. If
5397 you have just pulled changes from another repository, the tip of
5397 you have just pulled changes from another repository, the tip of
5398 that repository becomes the current tip. The "tip" tag is special
5398 that repository becomes the current tip. The "tip" tag is special
5399 and cannot be renamed or assigned to a different changeset.
5399 and cannot be renamed or assigned to a different changeset.
5400
5400
5401 This command is deprecated, please use :hg:`heads` instead.
5401 This command is deprecated, please use :hg:`heads` instead.
5402
5402
5403 Returns 0 on success.
5403 Returns 0 on success.
5404 """
5404 """
5405 opts = pycompat.byteskwargs(opts)
5405 opts = pycompat.byteskwargs(opts)
5406 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
5406 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
5407 displayer.show(repo['tip'])
5407 displayer.show(repo['tip'])
5408 displayer.close()
5408 displayer.close()
5409
5409
5410 @command('unbundle',
5410 @command('unbundle',
5411 [('u', 'update', None,
5411 [('u', 'update', None,
5412 _('update to new branch head if changesets were unbundled'))],
5412 _('update to new branch head if changesets were unbundled'))],
5413 _('[-u] FILE...'))
5413 _('[-u] FILE...'))
5414 def unbundle(ui, repo, fname1, *fnames, **opts):
5414 def unbundle(ui, repo, fname1, *fnames, **opts):
5415 """apply one or more bundle files
5415 """apply one or more bundle files
5416
5416
5417 Apply one or more bundle files generated by :hg:`bundle`.
5417 Apply one or more bundle files generated by :hg:`bundle`.
5418
5418
5419 Returns 0 on success, 1 if an update has unresolved files.
5419 Returns 0 on success, 1 if an update has unresolved files.
5420 """
5420 """
5421 fnames = (fname1,) + fnames
5421 fnames = (fname1,) + fnames
5422
5422
5423 with repo.lock():
5423 with repo.lock():
5424 for fname in fnames:
5424 for fname in fnames:
5425 f = hg.openpath(ui, fname)
5425 f = hg.openpath(ui, fname)
5426 gen = exchange.readbundle(ui, f, fname)
5426 gen = exchange.readbundle(ui, f, fname)
5427 if isinstance(gen, streamclone.streamcloneapplier):
5427 if isinstance(gen, streamclone.streamcloneapplier):
5428 raise error.Abort(
5428 raise error.Abort(
5429 _('packed bundles cannot be applied with '
5429 _('packed bundles cannot be applied with '
5430 '"hg unbundle"'),
5430 '"hg unbundle"'),
5431 hint=_('use "hg debugapplystreamclonebundle"'))
5431 hint=_('use "hg debugapplystreamclonebundle"'))
5432 url = 'bundle:' + fname
5432 url = 'bundle:' + fname
5433 try:
5433 try:
5434 txnname = 'unbundle'
5434 txnname = 'unbundle'
5435 if not isinstance(gen, bundle2.unbundle20):
5435 if not isinstance(gen, bundle2.unbundle20):
5436 txnname = 'unbundle\n%s' % util.hidepassword(url)
5436 txnname = 'unbundle\n%s' % util.hidepassword(url)
5437 with repo.transaction(txnname) as tr:
5437 with repo.transaction(txnname) as tr:
5438 op = bundle2.applybundle(repo, gen, tr, source='unbundle',
5438 op = bundle2.applybundle(repo, gen, tr, source='unbundle',
5439 url=url)
5439 url=url)
5440 except error.BundleUnknownFeatureError as exc:
5440 except error.BundleUnknownFeatureError as exc:
5441 raise error.Abort(
5441 raise error.Abort(
5442 _('%s: unknown bundle feature, %s') % (fname, exc),
5442 _('%s: unknown bundle feature, %s') % (fname, exc),
5443 hint=_("see https://mercurial-scm.org/"
5443 hint=_("see https://mercurial-scm.org/"
5444 "wiki/BundleFeature for more "
5444 "wiki/BundleFeature for more "
5445 "information"))
5445 "information"))
5446 modheads = bundle2.combinechangegroupresults(op)
5446 modheads = bundle2.combinechangegroupresults(op)
5447
5447
5448 return postincoming(ui, repo, modheads, opts.get(r'update'), None, None)
5448 return postincoming(ui, repo, modheads, opts.get(r'update'), None, None)
5449
5449
5450 @command('^update|up|checkout|co',
5450 @command('^update|up|checkout|co',
5451 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
5451 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
5452 ('c', 'check', None, _('require clean working directory')),
5452 ('c', 'check', None, _('require clean working directory')),
5453 ('m', 'merge', None, _('merge uncommitted changes')),
5453 ('m', 'merge', None, _('merge uncommitted changes')),
5454 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
5454 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
5455 ('r', 'rev', '', _('revision'), _('REV'))
5455 ('r', 'rev', '', _('revision'), _('REV'))
5456 ] + mergetoolopts,
5456 ] + mergetoolopts,
5457 _('[-C|-c|-m] [-d DATE] [[-r] REV]'))
5457 _('[-C|-c|-m] [-d DATE] [[-r] REV]'))
5458 def update(ui, repo, node=None, **opts):
5458 def update(ui, repo, node=None, **opts):
5459 """update working directory (or switch revisions)
5459 """update working directory (or switch revisions)
5460
5460
5461 Update the repository's working directory to the specified
5461 Update the repository's working directory to the specified
5462 changeset. If no changeset is specified, update to the tip of the
5462 changeset. If no changeset is specified, update to the tip of the
5463 current named branch and move the active bookmark (see :hg:`help
5463 current named branch and move the active bookmark (see :hg:`help
5464 bookmarks`).
5464 bookmarks`).
5465
5465
5466 Update sets the working directory's parent revision to the specified
5466 Update sets the working directory's parent revision to the specified
5467 changeset (see :hg:`help parents`).
5467 changeset (see :hg:`help parents`).
5468
5468
5469 If the changeset is not a descendant or ancestor of the working
5469 If the changeset is not a descendant or ancestor of the working
5470 directory's parent and there are uncommitted changes, the update is
5470 directory's parent and there are uncommitted changes, the update is
5471 aborted. With the -c/--check option, the working directory is checked
5471 aborted. With the -c/--check option, the working directory is checked
5472 for uncommitted changes; if none are found, the working directory is
5472 for uncommitted changes; if none are found, the working directory is
5473 updated to the specified changeset.
5473 updated to the specified changeset.
5474
5474
5475 .. container:: verbose
5475 .. container:: verbose
5476
5476
5477 The -C/--clean, -c/--check, and -m/--merge options control what
5477 The -C/--clean, -c/--check, and -m/--merge options control what
5478 happens if the working directory contains uncommitted changes.
5478 happens if the working directory contains uncommitted changes.
5479 At most of one of them can be specified.
5479 At most of one of them can be specified.
5480
5480
5481 1. If no option is specified, and if
5481 1. If no option is specified, and if
5482 the requested changeset is an ancestor or descendant of
5482 the requested changeset is an ancestor or descendant of
5483 the working directory's parent, the uncommitted changes
5483 the working directory's parent, the uncommitted changes
5484 are merged into the requested changeset and the merged
5484 are merged into the requested changeset and the merged
5485 result is left uncommitted. If the requested changeset is
5485 result is left uncommitted. If the requested changeset is
5486 not an ancestor or descendant (that is, it is on another
5486 not an ancestor or descendant (that is, it is on another
5487 branch), the update is aborted and the uncommitted changes
5487 branch), the update is aborted and the uncommitted changes
5488 are preserved.
5488 are preserved.
5489
5489
5490 2. With the -m/--merge option, the update is allowed even if the
5490 2. With the -m/--merge option, the update is allowed even if the
5491 requested changeset is not an ancestor or descendant of
5491 requested changeset is not an ancestor or descendant of
5492 the working directory's parent.
5492 the working directory's parent.
5493
5493
5494 3. With the -c/--check option, the update is aborted and the
5494 3. With the -c/--check option, the update is aborted and the
5495 uncommitted changes are preserved.
5495 uncommitted changes are preserved.
5496
5496
5497 4. With the -C/--clean option, uncommitted changes are discarded and
5497 4. With the -C/--clean option, uncommitted changes are discarded and
5498 the working directory is updated to the requested changeset.
5498 the working directory is updated to the requested changeset.
5499
5499
5500 To cancel an uncommitted merge (and lose your changes), use
5500 To cancel an uncommitted merge (and lose your changes), use
5501 :hg:`merge --abort`.
5501 :hg:`merge --abort`.
5502
5502
5503 Use null as the changeset to remove the working directory (like
5503 Use null as the changeset to remove the working directory (like
5504 :hg:`clone -U`).
5504 :hg:`clone -U`).
5505
5505
5506 If you want to revert just one file to an older revision, use
5506 If you want to revert just one file to an older revision, use
5507 :hg:`revert [-r REV] NAME`.
5507 :hg:`revert [-r REV] NAME`.
5508
5508
5509 See :hg:`help dates` for a list of formats valid for -d/--date.
5509 See :hg:`help dates` for a list of formats valid for -d/--date.
5510
5510
5511 Returns 0 on success, 1 if there are unresolved files.
5511 Returns 0 on success, 1 if there are unresolved files.
5512 """
5512 """
5513 rev = opts.get(r'rev')
5513 rev = opts.get(r'rev')
5514 date = opts.get(r'date')
5514 date = opts.get(r'date')
5515 clean = opts.get(r'clean')
5515 clean = opts.get(r'clean')
5516 check = opts.get(r'check')
5516 check = opts.get(r'check')
5517 merge = opts.get(r'merge')
5517 merge = opts.get(r'merge')
5518 if rev and node:
5518 if rev and node:
5519 raise error.Abort(_("please specify just one revision"))
5519 raise error.Abort(_("please specify just one revision"))
5520
5520
5521 if ui.configbool('commands', 'update.requiredest'):
5521 if ui.configbool('commands', 'update.requiredest'):
5522 if not node and not rev and not date:
5522 if not node and not rev and not date:
5523 raise error.Abort(_('you must specify a destination'),
5523 raise error.Abort(_('you must specify a destination'),
5524 hint=_('for example: hg update ".::"'))
5524 hint=_('for example: hg update ".::"'))
5525
5525
5526 if rev is None or rev == '':
5526 if rev is None or rev == '':
5527 rev = node
5527 rev = node
5528
5528
5529 if date and rev is not None:
5529 if date and rev is not None:
5530 raise error.Abort(_("you can't specify a revision and a date"))
5530 raise error.Abort(_("you can't specify a revision and a date"))
5531
5531
5532 if len([x for x in (clean, check, merge) if x]) > 1:
5532 if len([x for x in (clean, check, merge) if x]) > 1:
5533 raise error.Abort(_("can only specify one of -C/--clean, -c/--check, "
5533 raise error.Abort(_("can only specify one of -C/--clean, -c/--check, "
5534 "or -m/--merge"))
5534 "or -m/--merge"))
5535
5535
5536 updatecheck = None
5536 updatecheck = None
5537 if check:
5537 if check:
5538 updatecheck = 'abort'
5538 updatecheck = 'abort'
5539 elif merge:
5539 elif merge:
5540 updatecheck = 'none'
5540 updatecheck = 'none'
5541
5541
5542 with repo.wlock():
5542 with repo.wlock():
5543 cmdutil.clearunfinished(repo)
5543 cmdutil.clearunfinished(repo)
5544
5544
5545 if date:
5545 if date:
5546 rev = cmdutil.finddate(ui, repo, date)
5546 rev = cmdutil.finddate(ui, repo, date)
5547
5547
5548 # if we defined a bookmark, we have to remember the original name
5548 # if we defined a bookmark, we have to remember the original name
5549 brev = rev
5549 brev = rev
5550 if rev:
5550 if rev:
5551 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
5551 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
5552 ctx = scmutil.revsingle(repo, rev, rev)
5552 ctx = scmutil.revsingle(repo, rev, rev)
5553 rev = ctx.rev()
5553 rev = ctx.rev()
5554 if ctx.hidden():
5554 if ctx.hidden():
5555 ctxstr = ctx.hex()[:12]
5555 ctxstr = ctx.hex()[:12]
5556 ui.warn(_("updating to a hidden changeset %s\n") % ctxstr)
5556 ui.warn(_("updating to a hidden changeset %s\n") % ctxstr)
5557
5557
5558 if ctx.obsolete():
5558 if ctx.obsolete():
5559 obsfatemsg = obsutil._getfilteredreason(repo, ctxstr, ctx)
5559 obsfatemsg = obsutil._getfilteredreason(repo, ctxstr, ctx)
5560 ui.warn("(%s)\n" % obsfatemsg)
5560 ui.warn("(%s)\n" % obsfatemsg)
5561
5561
5562 repo.ui.setconfig('ui', 'forcemerge', opts.get(r'tool'), 'update')
5562 repo.ui.setconfig('ui', 'forcemerge', opts.get(r'tool'), 'update')
5563
5563
5564 return hg.updatetotally(ui, repo, rev, brev, clean=clean,
5564 return hg.updatetotally(ui, repo, rev, brev, clean=clean,
5565 updatecheck=updatecheck)
5565 updatecheck=updatecheck)
5566
5566
5567 @command('verify', [])
5567 @command('verify', [])
5568 def verify(ui, repo):
5568 def verify(ui, repo):
5569 """verify the integrity of the repository
5569 """verify the integrity of the repository
5570
5570
5571 Verify the integrity of the current repository.
5571 Verify the integrity of the current repository.
5572
5572
5573 This will perform an extensive check of the repository's
5573 This will perform an extensive check of the repository's
5574 integrity, validating the hashes and checksums of each entry in
5574 integrity, validating the hashes and checksums of each entry in
5575 the changelog, manifest, and tracked files, as well as the
5575 the changelog, manifest, and tracked files, as well as the
5576 integrity of their crosslinks and indices.
5576 integrity of their crosslinks and indices.
5577
5577
5578 Please see https://mercurial-scm.org/wiki/RepositoryCorruption
5578 Please see https://mercurial-scm.org/wiki/RepositoryCorruption
5579 for more information about recovery from corruption of the
5579 for more information about recovery from corruption of the
5580 repository.
5580 repository.
5581
5581
5582 Returns 0 on success, 1 if errors are encountered.
5582 Returns 0 on success, 1 if errors are encountered.
5583 """
5583 """
5584 return hg.verify(repo)
5584 return hg.verify(repo)
5585
5585
5586 @command('version', [] + formatteropts, norepo=True, cmdtype=readonly)
5586 @command('version', [] + formatteropts, norepo=True, cmdtype=readonly)
5587 def version_(ui, **opts):
5587 def version_(ui, **opts):
5588 """output version and copyright information"""
5588 """output version and copyright information"""
5589 opts = pycompat.byteskwargs(opts)
5589 opts = pycompat.byteskwargs(opts)
5590 if ui.verbose:
5590 if ui.verbose:
5591 ui.pager('version')
5591 ui.pager('version')
5592 fm = ui.formatter("version", opts)
5592 fm = ui.formatter("version", opts)
5593 fm.startitem()
5593 fm.startitem()
5594 fm.write("ver", _("Mercurial Distributed SCM (version %s)\n"),
5594 fm.write("ver", _("Mercurial Distributed SCM (version %s)\n"),
5595 util.version())
5595 util.version())
5596 license = _(
5596 license = _(
5597 "(see https://mercurial-scm.org for more information)\n"
5597 "(see https://mercurial-scm.org for more information)\n"
5598 "\nCopyright (C) 2005-2018 Matt Mackall and others\n"
5598 "\nCopyright (C) 2005-2018 Matt Mackall and others\n"
5599 "This is free software; see the source for copying conditions. "
5599 "This is free software; see the source for copying conditions. "
5600 "There is NO\nwarranty; "
5600 "There is NO\nwarranty; "
5601 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
5601 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
5602 )
5602 )
5603 if not ui.quiet:
5603 if not ui.quiet:
5604 fm.plain(license)
5604 fm.plain(license)
5605
5605
5606 if ui.verbose:
5606 if ui.verbose:
5607 fm.plain(_("\nEnabled extensions:\n\n"))
5607 fm.plain(_("\nEnabled extensions:\n\n"))
5608 # format names and versions into columns
5608 # format names and versions into columns
5609 names = []
5609 names = []
5610 vers = []
5610 vers = []
5611 isinternals = []
5611 isinternals = []
5612 for name, module in extensions.extensions():
5612 for name, module in extensions.extensions():
5613 names.append(name)
5613 names.append(name)
5614 vers.append(extensions.moduleversion(module) or None)
5614 vers.append(extensions.moduleversion(module) or None)
5615 isinternals.append(extensions.ismoduleinternal(module))
5615 isinternals.append(extensions.ismoduleinternal(module))
5616 fn = fm.nested("extensions")
5616 fn = fm.nested("extensions")
5617 if names:
5617 if names:
5618 namefmt = " %%-%ds " % max(len(n) for n in names)
5618 namefmt = " %%-%ds " % max(len(n) for n in names)
5619 places = [_("external"), _("internal")]
5619 places = [_("external"), _("internal")]
5620 for n, v, p in zip(names, vers, isinternals):
5620 for n, v, p in zip(names, vers, isinternals):
5621 fn.startitem()
5621 fn.startitem()
5622 fn.condwrite(ui.verbose, "name", namefmt, n)
5622 fn.condwrite(ui.verbose, "name", namefmt, n)
5623 if ui.verbose:
5623 if ui.verbose:
5624 fn.plain("%s " % places[p])
5624 fn.plain("%s " % places[p])
5625 fn.data(bundled=p)
5625 fn.data(bundled=p)
5626 fn.condwrite(ui.verbose and v, "ver", "%s", v)
5626 fn.condwrite(ui.verbose and v, "ver", "%s", v)
5627 if ui.verbose:
5627 if ui.verbose:
5628 fn.plain("\n")
5628 fn.plain("\n")
5629 fn.end()
5629 fn.end()
5630 fm.end()
5630 fm.end()
5631
5631
5632 def loadcmdtable(ui, name, cmdtable):
5632 def loadcmdtable(ui, name, cmdtable):
5633 """Load command functions from specified cmdtable
5633 """Load command functions from specified cmdtable
5634 """
5634 """
5635 overrides = [cmd for cmd in cmdtable if cmd in table]
5635 overrides = [cmd for cmd in cmdtable if cmd in table]
5636 if overrides:
5636 if overrides:
5637 ui.warn(_("extension '%s' overrides commands: %s\n")
5637 ui.warn(_("extension '%s' overrides commands: %s\n")
5638 % (name, " ".join(overrides)))
5638 % (name, " ".join(overrides)))
5639 table.update(cmdtable)
5639 table.update(cmdtable)
@@ -1,664 +1,664 b''
1 # fileset.py - file set queries for mercurial
1 # fileset.py - file set queries for mercurial
2 #
2 #
3 # Copyright 2010 Matt Mackall <mpm@selenic.com>
3 # Copyright 2010 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 re
10 import re
11
11
12 from .i18n import _
12 from .i18n import _
13 from . import (
13 from . import (
14 error,
14 error,
15 match as matchmod,
15 match as matchmod,
16 merge,
16 merge,
17 parser,
17 parser,
18 pycompat,
18 pycompat,
19 registrar,
19 registrar,
20 scmutil,
20 scmutil,
21 util,
21 util,
22 )
22 )
23 from .utils import (
23 from .utils import (
24 stringutil,
24 stringutil,
25 )
25 )
26
26
27 elements = {
27 elements = {
28 # token-type: binding-strength, primary, prefix, infix, suffix
28 # token-type: binding-strength, primary, prefix, infix, suffix
29 "(": (20, None, ("group", 1, ")"), ("func", 1, ")"), None),
29 "(": (20, None, ("group", 1, ")"), ("func", 1, ")"), None),
30 ":": (15, None, None, ("kindpat", 15), None),
30 ":": (15, None, None, ("kindpat", 15), None),
31 "-": (5, None, ("negate", 19), ("minus", 5), None),
31 "-": (5, None, ("negate", 19), ("minus", 5), None),
32 "not": (10, None, ("not", 10), None, None),
32 "not": (10, None, ("not", 10), None, None),
33 "!": (10, None, ("not", 10), None, None),
33 "!": (10, None, ("not", 10), None, None),
34 "and": (5, None, None, ("and", 5), None),
34 "and": (5, None, None, ("and", 5), None),
35 "&": (5, None, None, ("and", 5), None),
35 "&": (5, None, None, ("and", 5), None),
36 "or": (4, None, None, ("or", 4), None),
36 "or": (4, None, None, ("or", 4), None),
37 "|": (4, None, None, ("or", 4), None),
37 "|": (4, None, None, ("or", 4), None),
38 "+": (4, None, None, ("or", 4), None),
38 "+": (4, None, None, ("or", 4), None),
39 ",": (2, None, None, ("list", 2), None),
39 ",": (2, None, None, ("list", 2), None),
40 ")": (0, None, None, None, None),
40 ")": (0, None, None, None, None),
41 "symbol": (0, "symbol", None, None, None),
41 "symbol": (0, "symbol", None, None, None),
42 "string": (0, "string", None, None, None),
42 "string": (0, "string", None, None, None),
43 "end": (0, None, None, None, None),
43 "end": (0, None, None, None, None),
44 }
44 }
45
45
46 keywords = {'and', 'or', 'not'}
46 keywords = {'and', 'or', 'not'}
47
47
48 globchars = ".*{}[]?/\\_"
48 globchars = ".*{}[]?/\\_"
49
49
50 def tokenize(program):
50 def tokenize(program):
51 pos, l = 0, len(program)
51 pos, l = 0, len(program)
52 program = pycompat.bytestr(program)
52 program = pycompat.bytestr(program)
53 while pos < l:
53 while pos < l:
54 c = program[pos]
54 c = program[pos]
55 if c.isspace(): # skip inter-token whitespace
55 if c.isspace(): # skip inter-token whitespace
56 pass
56 pass
57 elif c in "(),-:|&+!": # handle simple operators
57 elif c in "(),-:|&+!": # handle simple operators
58 yield (c, None, pos)
58 yield (c, None, pos)
59 elif (c in '"\'' or c == 'r' and
59 elif (c in '"\'' or c == 'r' and
60 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
60 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
61 if c == 'r':
61 if c == 'r':
62 pos += 1
62 pos += 1
63 c = program[pos]
63 c = program[pos]
64 decode = lambda x: x
64 decode = lambda x: x
65 else:
65 else:
66 decode = parser.unescapestr
66 decode = parser.unescapestr
67 pos += 1
67 pos += 1
68 s = pos
68 s = pos
69 while pos < l: # find closing quote
69 while pos < l: # find closing quote
70 d = program[pos]
70 d = program[pos]
71 if d == '\\': # skip over escaped characters
71 if d == '\\': # skip over escaped characters
72 pos += 2
72 pos += 2
73 continue
73 continue
74 if d == c:
74 if d == c:
75 yield ('string', decode(program[s:pos]), s)
75 yield ('string', decode(program[s:pos]), s)
76 break
76 break
77 pos += 1
77 pos += 1
78 else:
78 else:
79 raise error.ParseError(_("unterminated string"), s)
79 raise error.ParseError(_("unterminated string"), s)
80 elif c.isalnum() or c in globchars or ord(c) > 127:
80 elif c.isalnum() or c in globchars or ord(c) > 127:
81 # gather up a symbol/keyword
81 # gather up a symbol/keyword
82 s = pos
82 s = pos
83 pos += 1
83 pos += 1
84 while pos < l: # find end of symbol
84 while pos < l: # find end of symbol
85 d = program[pos]
85 d = program[pos]
86 if not (d.isalnum() or d in globchars or ord(d) > 127):
86 if not (d.isalnum() or d in globchars or ord(d) > 127):
87 break
87 break
88 pos += 1
88 pos += 1
89 sym = program[s:pos]
89 sym = program[s:pos]
90 if sym in keywords: # operator keywords
90 if sym in keywords: # operator keywords
91 yield (sym, None, s)
91 yield (sym, None, s)
92 else:
92 else:
93 yield ('symbol', sym, s)
93 yield ('symbol', sym, s)
94 pos -= 1
94 pos -= 1
95 else:
95 else:
96 raise error.ParseError(_("syntax error"), pos)
96 raise error.ParseError(_("syntax error"), pos)
97 pos += 1
97 pos += 1
98 yield ('end', None, pos)
98 yield ('end', None, pos)
99
99
100 def parse(expr):
100 def parse(expr):
101 p = parser.parser(elements)
101 p = parser.parser(elements)
102 tree, pos = p.parse(tokenize(expr))
102 tree, pos = p.parse(tokenize(expr))
103 if pos != len(expr):
103 if pos != len(expr):
104 raise error.ParseError(_("invalid token"), pos)
104 raise error.ParseError(_("invalid token"), pos)
105 return tree
105 return tree
106
106
107 def getsymbol(x):
107 def getsymbol(x):
108 if x and x[0] == 'symbol':
108 if x and x[0] == 'symbol':
109 return x[1]
109 return x[1]
110 raise error.ParseError(_('not a symbol'))
110 raise error.ParseError(_('not a symbol'))
111
111
112 def getstring(x, err):
112 def getstring(x, err):
113 if x and (x[0] == 'string' or x[0] == 'symbol'):
113 if x and (x[0] == 'string' or x[0] == 'symbol'):
114 return x[1]
114 return x[1]
115 raise error.ParseError(err)
115 raise error.ParseError(err)
116
116
117 def _getkindpat(x, y, allkinds, err):
117 def _getkindpat(x, y, allkinds, err):
118 kind = getsymbol(x)
118 kind = getsymbol(x)
119 pat = getstring(y, err)
119 pat = getstring(y, err)
120 if kind not in allkinds:
120 if kind not in allkinds:
121 raise error.ParseError(_("invalid pattern kind: %s") % kind)
121 raise error.ParseError(_("invalid pattern kind: %s") % kind)
122 return '%s:%s' % (kind, pat)
122 return '%s:%s' % (kind, pat)
123
123
124 def getpattern(x, allkinds, err):
124 def getpattern(x, allkinds, err):
125 if x and x[0] == 'kindpat':
125 if x and x[0] == 'kindpat':
126 return _getkindpat(x[1], x[2], allkinds, err)
126 return _getkindpat(x[1], x[2], allkinds, err)
127 return getstring(x, err)
127 return getstring(x, err)
128
128
129 def getset(mctx, x):
129 def getset(mctx, x):
130 if not x:
130 if not x:
131 raise error.ParseError(_("missing argument"))
131 raise error.ParseError(_("missing argument"))
132 return methods[x[0]](mctx, *x[1:])
132 return methods[x[0]](mctx, *x[1:])
133
133
134 def stringset(mctx, x):
134 def stringset(mctx, x):
135 m = mctx.matcher([x])
135 m = mctx.matcher([x])
136 return [f for f in mctx.subset if m(f)]
136 return [f for f in mctx.subset if m(f)]
137
137
138 def kindpatset(mctx, x, y):
138 def kindpatset(mctx, x, y):
139 return stringset(mctx, _getkindpat(x, y, matchmod.allpatternkinds,
139 return stringset(mctx, _getkindpat(x, y, matchmod.allpatternkinds,
140 _("pattern must be a string")))
140 _("pattern must be a string")))
141
141
142 def andset(mctx, x, y):
142 def andset(mctx, x, y):
143 return getset(mctx.narrow(getset(mctx, x)), y)
143 return getset(mctx.narrow(getset(mctx, x)), y)
144
144
145 def orset(mctx, x, y):
145 def orset(mctx, x, y):
146 # needs optimizing
146 # needs optimizing
147 xl = getset(mctx, x)
147 xl = getset(mctx, x)
148 yl = getset(mctx, y)
148 yl = getset(mctx, y)
149 return xl + [f for f in yl if f not in xl]
149 return xl + [f for f in yl if f not in xl]
150
150
151 def notset(mctx, x):
151 def notset(mctx, x):
152 s = set(getset(mctx, x))
152 s = set(getset(mctx, x))
153 return [r for r in mctx.subset if r not in s]
153 return [r for r in mctx.subset if r not in s]
154
154
155 def minusset(mctx, x, y):
155 def minusset(mctx, x, y):
156 xl = getset(mctx, x)
156 xl = getset(mctx, x)
157 yl = set(getset(mctx, y))
157 yl = set(getset(mctx, y))
158 return [f for f in xl if f not in yl]
158 return [f for f in xl if f not in yl]
159
159
160 def negateset(mctx, x):
160 def negateset(mctx, x):
161 raise error.ParseError(_("can't use negate operator in this context"))
161 raise error.ParseError(_("can't use negate operator in this context"))
162
162
163 def listset(mctx, a, b):
163 def listset(mctx, a, b):
164 raise error.ParseError(_("can't use a list in this context"),
164 raise error.ParseError(_("can't use a list in this context"),
165 hint=_('see hg help "filesets.x or y"'))
165 hint=_('see hg help "filesets.x or y"'))
166
166
167 # symbols are callable like:
167 # symbols are callable like:
168 # fun(mctx, x)
168 # fun(mctx, x)
169 # with:
169 # with:
170 # mctx - current matchctx instance
170 # mctx - current matchctx instance
171 # x - argument in tree form
171 # x - argument in tree form
172 symbols = {}
172 symbols = {}
173
173
174 # filesets using matchctx.status()
174 # filesets using matchctx.status()
175 _statuscallers = set()
175 _statuscallers = set()
176
176
177 # filesets using matchctx.existing()
177 # filesets using matchctx.existing()
178 _existingcallers = set()
178 _existingcallers = set()
179
179
180 predicate = registrar.filesetpredicate()
180 predicate = registrar.filesetpredicate()
181
181
182 @predicate('modified()', callstatus=True)
182 @predicate('modified()', callstatus=True)
183 def modified(mctx, x):
183 def modified(mctx, x):
184 """File that is modified according to :hg:`status`.
184 """File that is modified according to :hg:`status`.
185 """
185 """
186 # i18n: "modified" is a keyword
186 # i18n: "modified" is a keyword
187 getargs(x, 0, 0, _("modified takes no arguments"))
187 getargs(x, 0, 0, _("modified takes no arguments"))
188 s = set(mctx.status().modified)
188 s = set(mctx.status().modified)
189 return [f for f in mctx.subset if f in s]
189 return [f for f in mctx.subset if f in s]
190
190
191 @predicate('added()', callstatus=True)
191 @predicate('added()', callstatus=True)
192 def added(mctx, x):
192 def added(mctx, x):
193 """File that is added according to :hg:`status`.
193 """File that is added according to :hg:`status`.
194 """
194 """
195 # i18n: "added" is a keyword
195 # i18n: "added" is a keyword
196 getargs(x, 0, 0, _("added takes no arguments"))
196 getargs(x, 0, 0, _("added takes no arguments"))
197 s = set(mctx.status().added)
197 s = set(mctx.status().added)
198 return [f for f in mctx.subset if f in s]
198 return [f for f in mctx.subset if f in s]
199
199
200 @predicate('removed()', callstatus=True)
200 @predicate('removed()', callstatus=True)
201 def removed(mctx, x):
201 def removed(mctx, x):
202 """File that is removed according to :hg:`status`.
202 """File that is removed according to :hg:`status`.
203 """
203 """
204 # i18n: "removed" is a keyword
204 # i18n: "removed" is a keyword
205 getargs(x, 0, 0, _("removed takes no arguments"))
205 getargs(x, 0, 0, _("removed takes no arguments"))
206 s = set(mctx.status().removed)
206 s = set(mctx.status().removed)
207 return [f for f in mctx.subset if f in s]
207 return [f for f in mctx.subset if f in s]
208
208
209 @predicate('deleted()', callstatus=True)
209 @predicate('deleted()', callstatus=True)
210 def deleted(mctx, x):
210 def deleted(mctx, x):
211 """Alias for ``missing()``.
211 """Alias for ``missing()``.
212 """
212 """
213 # i18n: "deleted" is a keyword
213 # i18n: "deleted" is a keyword
214 getargs(x, 0, 0, _("deleted takes no arguments"))
214 getargs(x, 0, 0, _("deleted takes no arguments"))
215 s = set(mctx.status().deleted)
215 s = set(mctx.status().deleted)
216 return [f for f in mctx.subset if f in s]
216 return [f for f in mctx.subset if f in s]
217
217
218 @predicate('missing()', callstatus=True)
218 @predicate('missing()', callstatus=True)
219 def missing(mctx, x):
219 def missing(mctx, x):
220 """File that is missing according to :hg:`status`.
220 """File that is missing according to :hg:`status`.
221 """
221 """
222 # i18n: "missing" is a keyword
222 # i18n: "missing" is a keyword
223 getargs(x, 0, 0, _("missing takes no arguments"))
223 getargs(x, 0, 0, _("missing takes no arguments"))
224 s = set(mctx.status().deleted)
224 s = set(mctx.status().deleted)
225 return [f for f in mctx.subset if f in s]
225 return [f for f in mctx.subset if f in s]
226
226
227 @predicate('unknown()', callstatus=True)
227 @predicate('unknown()', callstatus=True)
228 def unknown(mctx, x):
228 def unknown(mctx, x):
229 """File that is unknown according to :hg:`status`. These files will only be
229 """File that is unknown according to :hg:`status`. These files will only be
230 considered if this predicate is used.
230 considered if this predicate is used.
231 """
231 """
232 # i18n: "unknown" is a keyword
232 # i18n: "unknown" is a keyword
233 getargs(x, 0, 0, _("unknown takes no arguments"))
233 getargs(x, 0, 0, _("unknown takes no arguments"))
234 s = set(mctx.status().unknown)
234 s = set(mctx.status().unknown)
235 return [f for f in mctx.subset if f in s]
235 return [f for f in mctx.subset if f in s]
236
236
237 @predicate('ignored()', callstatus=True)
237 @predicate('ignored()', callstatus=True)
238 def ignored(mctx, x):
238 def ignored(mctx, x):
239 """File that is ignored according to :hg:`status`. These files will only be
239 """File that is ignored according to :hg:`status`. These files will only be
240 considered if this predicate is used.
240 considered if this predicate is used.
241 """
241 """
242 # i18n: "ignored" is a keyword
242 # i18n: "ignored" is a keyword
243 getargs(x, 0, 0, _("ignored takes no arguments"))
243 getargs(x, 0, 0, _("ignored takes no arguments"))
244 s = set(mctx.status().ignored)
244 s = set(mctx.status().ignored)
245 return [f for f in mctx.subset if f in s]
245 return [f for f in mctx.subset if f in s]
246
246
247 @predicate('clean()', callstatus=True)
247 @predicate('clean()', callstatus=True)
248 def clean(mctx, x):
248 def clean(mctx, x):
249 """File that is clean according to :hg:`status`.
249 """File that is clean according to :hg:`status`.
250 """
250 """
251 # i18n: "clean" is a keyword
251 # i18n: "clean" is a keyword
252 getargs(x, 0, 0, _("clean takes no arguments"))
252 getargs(x, 0, 0, _("clean takes no arguments"))
253 s = set(mctx.status().clean)
253 s = set(mctx.status().clean)
254 return [f for f in mctx.subset if f in s]
254 return [f for f in mctx.subset if f in s]
255
255
256 def func(mctx, a, b):
256 def func(mctx, a, b):
257 funcname = getsymbol(a)
257 funcname = getsymbol(a)
258 if funcname in symbols:
258 if funcname in symbols:
259 enabled = mctx._existingenabled
259 enabled = mctx._existingenabled
260 mctx._existingenabled = funcname in _existingcallers
260 mctx._existingenabled = funcname in _existingcallers
261 try:
261 try:
262 return symbols[funcname](mctx, b)
262 return symbols[funcname](mctx, b)
263 finally:
263 finally:
264 mctx._existingenabled = enabled
264 mctx._existingenabled = enabled
265
265
266 keep = lambda fn: getattr(fn, '__doc__', None) is not None
266 keep = lambda fn: getattr(fn, '__doc__', None) is not None
267
267
268 syms = [s for (s, fn) in symbols.items() if keep(fn)]
268 syms = [s for (s, fn) in symbols.items() if keep(fn)]
269 raise error.UnknownIdentifier(funcname, syms)
269 raise error.UnknownIdentifier(funcname, syms)
270
270
271 def getlist(x):
271 def getlist(x):
272 if not x:
272 if not x:
273 return []
273 return []
274 if x[0] == 'list':
274 if x[0] == 'list':
275 return getlist(x[1]) + [x[2]]
275 return getlist(x[1]) + [x[2]]
276 return [x]
276 return [x]
277
277
278 def getargs(x, min, max, err):
278 def getargs(x, min, max, err):
279 l = getlist(x)
279 l = getlist(x)
280 if len(l) < min or len(l) > max:
280 if len(l) < min or len(l) > max:
281 raise error.ParseError(err)
281 raise error.ParseError(err)
282 return l
282 return l
283
283
284 @predicate('binary()', callexisting=True)
284 @predicate('binary()', callexisting=True)
285 def binary(mctx, x):
285 def binary(mctx, x):
286 """File that appears to be binary (contains NUL bytes).
286 """File that appears to be binary (contains NUL bytes).
287 """
287 """
288 # i18n: "binary" is a keyword
288 # i18n: "binary" is a keyword
289 getargs(x, 0, 0, _("binary takes no arguments"))
289 getargs(x, 0, 0, _("binary takes no arguments"))
290 return [f for f in mctx.existing() if mctx.ctx[f].isbinary()]
290 return [f for f in mctx.existing() if mctx.ctx[f].isbinary()]
291
291
292 @predicate('exec()', callexisting=True)
292 @predicate('exec()', callexisting=True)
293 def exec_(mctx, x):
293 def exec_(mctx, x):
294 """File that is marked as executable.
294 """File that is marked as executable.
295 """
295 """
296 # i18n: "exec" is a keyword
296 # i18n: "exec" is a keyword
297 getargs(x, 0, 0, _("exec takes no arguments"))
297 getargs(x, 0, 0, _("exec takes no arguments"))
298 return [f for f in mctx.existing() if mctx.ctx.flags(f) == 'x']
298 return [f for f in mctx.existing() if mctx.ctx.flags(f) == 'x']
299
299
300 @predicate('symlink()', callexisting=True)
300 @predicate('symlink()', callexisting=True)
301 def symlink(mctx, x):
301 def symlink(mctx, x):
302 """File that is marked as a symlink.
302 """File that is marked as a symlink.
303 """
303 """
304 # i18n: "symlink" is a keyword
304 # i18n: "symlink" is a keyword
305 getargs(x, 0, 0, _("symlink takes no arguments"))
305 getargs(x, 0, 0, _("symlink takes no arguments"))
306 return [f for f in mctx.existing() if mctx.ctx.flags(f) == 'l']
306 return [f for f in mctx.existing() if mctx.ctx.flags(f) == 'l']
307
307
308 @predicate('resolved()')
308 @predicate('resolved()')
309 def resolved(mctx, x):
309 def resolved(mctx, x):
310 """File that is marked resolved according to :hg:`resolve -l`.
310 """File that is marked resolved according to :hg:`resolve -l`.
311 """
311 """
312 # i18n: "resolved" is a keyword
312 # i18n: "resolved" is a keyword
313 getargs(x, 0, 0, _("resolved takes no arguments"))
313 getargs(x, 0, 0, _("resolved takes no arguments"))
314 if mctx.ctx.rev() is not None:
314 if mctx.ctx.rev() is not None:
315 return []
315 return []
316 ms = merge.mergestate.read(mctx.ctx.repo())
316 ms = merge.mergestate.read(mctx.ctx.repo())
317 return [f for f in mctx.subset if f in ms and ms[f] == 'r']
317 return [f for f in mctx.subset if f in ms and ms[f] == 'r']
318
318
319 @predicate('unresolved()')
319 @predicate('unresolved()')
320 def unresolved(mctx, x):
320 def unresolved(mctx, x):
321 """File that is marked unresolved according to :hg:`resolve -l`.
321 """File that is marked unresolved according to :hg:`resolve -l`.
322 """
322 """
323 # i18n: "unresolved" is a keyword
323 # i18n: "unresolved" is a keyword
324 getargs(x, 0, 0, _("unresolved takes no arguments"))
324 getargs(x, 0, 0, _("unresolved takes no arguments"))
325 if mctx.ctx.rev() is not None:
325 if mctx.ctx.rev() is not None:
326 return []
326 return []
327 ms = merge.mergestate.read(mctx.ctx.repo())
327 ms = merge.mergestate.read(mctx.ctx.repo())
328 return [f for f in mctx.subset if f in ms and ms[f] == 'u']
328 return [f for f in mctx.subset if f in ms and ms[f] == 'u']
329
329
330 @predicate('hgignore()')
330 @predicate('hgignore()')
331 def hgignore(mctx, x):
331 def hgignore(mctx, x):
332 """File that matches the active .hgignore pattern.
332 """File that matches the active .hgignore pattern.
333 """
333 """
334 # i18n: "hgignore" is a keyword
334 # i18n: "hgignore" is a keyword
335 getargs(x, 0, 0, _("hgignore takes no arguments"))
335 getargs(x, 0, 0, _("hgignore takes no arguments"))
336 ignore = mctx.ctx.repo().dirstate._ignore
336 ignore = mctx.ctx.repo().dirstate._ignore
337 return [f for f in mctx.subset if ignore(f)]
337 return [f for f in mctx.subset if ignore(f)]
338
338
339 @predicate('portable()')
339 @predicate('portable()')
340 def portable(mctx, x):
340 def portable(mctx, x):
341 """File that has a portable name. (This doesn't include filenames with case
341 """File that has a portable name. (This doesn't include filenames with case
342 collisions.)
342 collisions.)
343 """
343 """
344 # i18n: "portable" is a keyword
344 # i18n: "portable" is a keyword
345 getargs(x, 0, 0, _("portable takes no arguments"))
345 getargs(x, 0, 0, _("portable takes no arguments"))
346 checkwinfilename = util.checkwinfilename
346 checkwinfilename = util.checkwinfilename
347 return [f for f in mctx.subset if checkwinfilename(f) is None]
347 return [f for f in mctx.subset if checkwinfilename(f) is None]
348
348
349 @predicate('grep(regex)', callexisting=True)
349 @predicate('grep(regex)', callexisting=True)
350 def grep(mctx, x):
350 def grep(mctx, x):
351 """File contains the given regular expression.
351 """File contains the given regular expression.
352 """
352 """
353 try:
353 try:
354 # i18n: "grep" is a keyword
354 # i18n: "grep" is a keyword
355 r = re.compile(getstring(x, _("grep requires a pattern")))
355 r = re.compile(getstring(x, _("grep requires a pattern")))
356 except re.error as e:
356 except re.error as e:
357 raise error.ParseError(_('invalid match pattern: %s') % e)
357 raise error.ParseError(_('invalid match pattern: %s') % e)
358 return [f for f in mctx.existing() if r.search(mctx.ctx[f].data())]
358 return [f for f in mctx.existing() if r.search(mctx.ctx[f].data())]
359
359
360 def _sizetomax(s):
360 def _sizetomax(s):
361 try:
361 try:
362 s = s.strip().lower()
362 s = s.strip().lower()
363 for k, v in util._sizeunits:
363 for k, v in util._sizeunits:
364 if s.endswith(k):
364 if s.endswith(k):
365 # max(4k) = 5k - 1, max(4.5k) = 4.6k - 1
365 # max(4k) = 5k - 1, max(4.5k) = 4.6k - 1
366 n = s[:-len(k)]
366 n = s[:-len(k)]
367 inc = 1.0
367 inc = 1.0
368 if "." in n:
368 if "." in n:
369 inc /= 10 ** len(n.split(".")[1])
369 inc /= 10 ** len(n.split(".")[1])
370 return int((float(n) + inc) * v) - 1
370 return int((float(n) + inc) * v) - 1
371 # no extension, this is a precise value
371 # no extension, this is a precise value
372 return int(s)
372 return int(s)
373 except ValueError:
373 except ValueError:
374 raise error.ParseError(_("couldn't parse size: %s") % s)
374 raise error.ParseError(_("couldn't parse size: %s") % s)
375
375
376 def sizematcher(x):
376 def sizematcher(x):
377 """Return a function(size) -> bool from the ``size()`` expression"""
377 """Return a function(size) -> bool from the ``size()`` expression"""
378
378
379 # i18n: "size" is a keyword
379 # i18n: "size" is a keyword
380 expr = getstring(x, _("size requires an expression")).strip()
380 expr = getstring(x, _("size requires an expression")).strip()
381 if '-' in expr: # do we have a range?
381 if '-' in expr: # do we have a range?
382 a, b = expr.split('-', 1)
382 a, b = expr.split('-', 1)
383 a = util.sizetoint(a)
383 a = util.sizetoint(a)
384 b = util.sizetoint(b)
384 b = util.sizetoint(b)
385 return lambda x: x >= a and x <= b
385 return lambda x: x >= a and x <= b
386 elif expr.startswith("<="):
386 elif expr.startswith("<="):
387 a = util.sizetoint(expr[2:])
387 a = util.sizetoint(expr[2:])
388 return lambda x: x <= a
388 return lambda x: x <= a
389 elif expr.startswith("<"):
389 elif expr.startswith("<"):
390 a = util.sizetoint(expr[1:])
390 a = util.sizetoint(expr[1:])
391 return lambda x: x < a
391 return lambda x: x < a
392 elif expr.startswith(">="):
392 elif expr.startswith(">="):
393 a = util.sizetoint(expr[2:])
393 a = util.sizetoint(expr[2:])
394 return lambda x: x >= a
394 return lambda x: x >= a
395 elif expr.startswith(">"):
395 elif expr.startswith(">"):
396 a = util.sizetoint(expr[1:])
396 a = util.sizetoint(expr[1:])
397 return lambda x: x > a
397 return lambda x: x > a
398 else:
398 else:
399 a = util.sizetoint(expr)
399 a = util.sizetoint(expr)
400 b = _sizetomax(expr)
400 b = _sizetomax(expr)
401 return lambda x: x >= a and x <= b
401 return lambda x: x >= a and x <= b
402
402
403 @predicate('size(expression)', callexisting=True)
403 @predicate('size(expression)', callexisting=True)
404 def size(mctx, x):
404 def size(mctx, x):
405 """File size matches the given expression. Examples:
405 """File size matches the given expression. Examples:
406
406
407 - size('1k') - files from 1024 to 2047 bytes
407 - size('1k') - files from 1024 to 2047 bytes
408 - size('< 20k') - files less than 20480 bytes
408 - size('< 20k') - files less than 20480 bytes
409 - size('>= .5MB') - files at least 524288 bytes
409 - size('>= .5MB') - files at least 524288 bytes
410 - size('4k - 1MB') - files from 4096 bytes to 1048576 bytes
410 - size('4k - 1MB') - files from 4096 bytes to 1048576 bytes
411 """
411 """
412 m = sizematcher(x)
412 m = sizematcher(x)
413 return [f for f in mctx.existing() if m(mctx.ctx[f].size())]
413 return [f for f in mctx.existing() if m(mctx.ctx[f].size())]
414
414
415 @predicate('encoding(name)', callexisting=True)
415 @predicate('encoding(name)', callexisting=True)
416 def encoding(mctx, x):
416 def encoding(mctx, x):
417 """File can be successfully decoded with the given character
417 """File can be successfully decoded with the given character
418 encoding. May not be useful for encodings other than ASCII and
418 encoding. May not be useful for encodings other than ASCII and
419 UTF-8.
419 UTF-8.
420 """
420 """
421
421
422 # i18n: "encoding" is a keyword
422 # i18n: "encoding" is a keyword
423 enc = getstring(x, _("encoding requires an encoding name"))
423 enc = getstring(x, _("encoding requires an encoding name"))
424
424
425 s = []
425 s = []
426 for f in mctx.existing():
426 for f in mctx.existing():
427 d = mctx.ctx[f].data()
427 d = mctx.ctx[f].data()
428 try:
428 try:
429 d.decode(enc)
429 d.decode(enc)
430 except LookupError:
430 except LookupError:
431 raise error.Abort(_("unknown encoding '%s'") % enc)
431 raise error.Abort(_("unknown encoding '%s'") % enc)
432 except UnicodeDecodeError:
432 except UnicodeDecodeError:
433 continue
433 continue
434 s.append(f)
434 s.append(f)
435
435
436 return s
436 return s
437
437
438 @predicate('eol(style)', callexisting=True)
438 @predicate('eol(style)', callexisting=True)
439 def eol(mctx, x):
439 def eol(mctx, x):
440 """File contains newlines of the given style (dos, unix, mac). Binary
440 """File contains newlines of the given style (dos, unix, mac). Binary
441 files are excluded, files with mixed line endings match multiple
441 files are excluded, files with mixed line endings match multiple
442 styles.
442 styles.
443 """
443 """
444
444
445 # i18n: "eol" is a keyword
445 # i18n: "eol" is a keyword
446 enc = getstring(x, _("eol requires a style name"))
446 enc = getstring(x, _("eol requires a style name"))
447
447
448 s = []
448 s = []
449 for f in mctx.existing():
449 for f in mctx.existing():
450 d = mctx.ctx[f].data()
450 d = mctx.ctx[f].data()
451 if stringutil.binary(d):
451 if stringutil.binary(d):
452 continue
452 continue
453 if (enc == 'dos' or enc == 'win') and '\r\n' in d:
453 if (enc == 'dos' or enc == 'win') and '\r\n' in d:
454 s.append(f)
454 s.append(f)
455 elif enc == 'unix' and re.search('(?<!\r)\n', d):
455 elif enc == 'unix' and re.search('(?<!\r)\n', d):
456 s.append(f)
456 s.append(f)
457 elif enc == 'mac' and re.search('\r(?!\n)', d):
457 elif enc == 'mac' and re.search('\r(?!\n)', d):
458 s.append(f)
458 s.append(f)
459 return s
459 return s
460
460
461 @predicate('copied()')
461 @predicate('copied()')
462 def copied(mctx, x):
462 def copied(mctx, x):
463 """File that is recorded as being copied.
463 """File that is recorded as being copied.
464 """
464 """
465 # i18n: "copied" is a keyword
465 # i18n: "copied" is a keyword
466 getargs(x, 0, 0, _("copied takes no arguments"))
466 getargs(x, 0, 0, _("copied takes no arguments"))
467 s = []
467 s = []
468 for f in mctx.subset:
468 for f in mctx.subset:
469 if f in mctx.ctx:
469 if f in mctx.ctx:
470 p = mctx.ctx[f].parents()
470 p = mctx.ctx[f].parents()
471 if p and p[0].path() != f:
471 if p and p[0].path() != f:
472 s.append(f)
472 s.append(f)
473 return s
473 return s
474
474
475 @predicate('revs(revs, pattern)')
475 @predicate('revs(revs, pattern)')
476 def revs(mctx, x):
476 def revs(mctx, x):
477 """Evaluate set in the specified revisions. If the revset match multiple
477 """Evaluate set in the specified revisions. If the revset match multiple
478 revs, this will return file matching pattern in any of the revision.
478 revs, this will return file matching pattern in any of the revision.
479 """
479 """
480 # i18n: "revs" is a keyword
480 # i18n: "revs" is a keyword
481 r, x = getargs(x, 2, 2, _("revs takes two arguments"))
481 r, x = getargs(x, 2, 2, _("revs takes two arguments"))
482 # i18n: "revs" is a keyword
482 # i18n: "revs" is a keyword
483 revspec = getstring(r, _("first argument to revs must be a revision"))
483 revspec = getstring(r, _("first argument to revs must be a revision"))
484 repo = mctx.ctx.repo()
484 repo = mctx.ctx.repo()
485 revs = scmutil.revrange(repo, [revspec])
485 revs = scmutil.revrange(repo, [revspec])
486
486
487 found = set()
487 found = set()
488 result = []
488 result = []
489 for r in revs:
489 for r in revs:
490 ctx = repo[r]
490 ctx = repo[r]
491 for f in getset(mctx.switch(ctx, _buildstatus(ctx, x)), x):
491 for f in getset(mctx.switch(ctx, _buildstatus(ctx, x)), x):
492 if f not in found:
492 if f not in found:
493 found.add(f)
493 found.add(f)
494 result.append(f)
494 result.append(f)
495 return result
495 return result
496
496
497 @predicate('status(base, rev, pattern)')
497 @predicate('status(base, rev, pattern)')
498 def status(mctx, x):
498 def status(mctx, x):
499 """Evaluate predicate using status change between ``base`` and
499 """Evaluate predicate using status change between ``base`` and
500 ``rev``. Examples:
500 ``rev``. Examples:
501
501
502 - ``status(3, 7, added())`` - matches files added from "3" to "7"
502 - ``status(3, 7, added())`` - matches files added from "3" to "7"
503 """
503 """
504 repo = mctx.ctx.repo()
504 repo = mctx.ctx.repo()
505 # i18n: "status" is a keyword
505 # i18n: "status" is a keyword
506 b, r, x = getargs(x, 3, 3, _("status takes three arguments"))
506 b, r, x = getargs(x, 3, 3, _("status takes three arguments"))
507 # i18n: "status" is a keyword
507 # i18n: "status" is a keyword
508 baseerr = _("first argument to status must be a revision")
508 baseerr = _("first argument to status must be a revision")
509 baserevspec = getstring(b, baseerr)
509 baserevspec = getstring(b, baseerr)
510 if not baserevspec:
510 if not baserevspec:
511 raise error.ParseError(baseerr)
511 raise error.ParseError(baseerr)
512 reverr = _("second argument to status must be a revision")
512 reverr = _("second argument to status must be a revision")
513 revspec = getstring(r, reverr)
513 revspec = getstring(r, reverr)
514 if not revspec:
514 if not revspec:
515 raise error.ParseError(reverr)
515 raise error.ParseError(reverr)
516 basenode, node = scmutil.revpair(repo, [baserevspec, revspec])
516 basenode, node = scmutil.revpairnodes(repo, [baserevspec, revspec])
517 basectx = repo[basenode]
517 basectx = repo[basenode]
518 ctx = repo[node]
518 ctx = repo[node]
519 return getset(mctx.switch(ctx, _buildstatus(ctx, x, basectx=basectx)), x)
519 return getset(mctx.switch(ctx, _buildstatus(ctx, x, basectx=basectx)), x)
520
520
521 @predicate('subrepo([pattern])')
521 @predicate('subrepo([pattern])')
522 def subrepo(mctx, x):
522 def subrepo(mctx, x):
523 """Subrepositories whose paths match the given pattern.
523 """Subrepositories whose paths match the given pattern.
524 """
524 """
525 # i18n: "subrepo" is a keyword
525 # i18n: "subrepo" is a keyword
526 getargs(x, 0, 1, _("subrepo takes at most one argument"))
526 getargs(x, 0, 1, _("subrepo takes at most one argument"))
527 ctx = mctx.ctx
527 ctx = mctx.ctx
528 sstate = sorted(ctx.substate)
528 sstate = sorted(ctx.substate)
529 if x:
529 if x:
530 pat = getpattern(x, matchmod.allpatternkinds,
530 pat = getpattern(x, matchmod.allpatternkinds,
531 # i18n: "subrepo" is a keyword
531 # i18n: "subrepo" is a keyword
532 _("subrepo requires a pattern or no arguments"))
532 _("subrepo requires a pattern or no arguments"))
533 fast = not matchmod.patkind(pat)
533 fast = not matchmod.patkind(pat)
534 if fast:
534 if fast:
535 def m(s):
535 def m(s):
536 return (s == pat)
536 return (s == pat)
537 else:
537 else:
538 m = matchmod.match(ctx.repo().root, '', [pat], ctx=ctx)
538 m = matchmod.match(ctx.repo().root, '', [pat], ctx=ctx)
539 return [sub for sub in sstate if m(sub)]
539 return [sub for sub in sstate if m(sub)]
540 else:
540 else:
541 return [sub for sub in sstate]
541 return [sub for sub in sstate]
542
542
543 methods = {
543 methods = {
544 'string': stringset,
544 'string': stringset,
545 'symbol': stringset,
545 'symbol': stringset,
546 'kindpat': kindpatset,
546 'kindpat': kindpatset,
547 'and': andset,
547 'and': andset,
548 'or': orset,
548 'or': orset,
549 'minus': minusset,
549 'minus': minusset,
550 'negate': negateset,
550 'negate': negateset,
551 'list': listset,
551 'list': listset,
552 'group': getset,
552 'group': getset,
553 'not': notset,
553 'not': notset,
554 'func': func,
554 'func': func,
555 }
555 }
556
556
557 class matchctx(object):
557 class matchctx(object):
558 def __init__(self, ctx, subset, status=None):
558 def __init__(self, ctx, subset, status=None):
559 self.ctx = ctx
559 self.ctx = ctx
560 self.subset = subset
560 self.subset = subset
561 self._status = status
561 self._status = status
562 self._existingenabled = False
562 self._existingenabled = False
563 def status(self):
563 def status(self):
564 return self._status
564 return self._status
565 def matcher(self, patterns):
565 def matcher(self, patterns):
566 return self.ctx.match(patterns)
566 return self.ctx.match(patterns)
567 def filter(self, files):
567 def filter(self, files):
568 return [f for f in files if f in self.subset]
568 return [f for f in files if f in self.subset]
569 def existing(self):
569 def existing(self):
570 assert self._existingenabled, 'unexpected existing() invocation'
570 assert self._existingenabled, 'unexpected existing() invocation'
571 if self._status is not None:
571 if self._status is not None:
572 removed = set(self._status[3])
572 removed = set(self._status[3])
573 unknown = set(self._status[4] + self._status[5])
573 unknown = set(self._status[4] + self._status[5])
574 else:
574 else:
575 removed = set()
575 removed = set()
576 unknown = set()
576 unknown = set()
577 return (f for f in self.subset
577 return (f for f in self.subset
578 if (f in self.ctx and f not in removed) or f in unknown)
578 if (f in self.ctx and f not in removed) or f in unknown)
579 def narrow(self, files):
579 def narrow(self, files):
580 return matchctx(self.ctx, self.filter(files), self._status)
580 return matchctx(self.ctx, self.filter(files), self._status)
581 def switch(self, ctx, status=None):
581 def switch(self, ctx, status=None):
582 subset = self.filter(_buildsubset(ctx, status))
582 subset = self.filter(_buildsubset(ctx, status))
583 return matchctx(ctx, subset, status)
583 return matchctx(ctx, subset, status)
584
584
585 class fullmatchctx(matchctx):
585 class fullmatchctx(matchctx):
586 """A match context where any files in any revisions should be valid"""
586 """A match context where any files in any revisions should be valid"""
587
587
588 def __init__(self, ctx, status=None):
588 def __init__(self, ctx, status=None):
589 subset = _buildsubset(ctx, status)
589 subset = _buildsubset(ctx, status)
590 super(fullmatchctx, self).__init__(ctx, subset, status)
590 super(fullmatchctx, self).__init__(ctx, subset, status)
591 def switch(self, ctx, status=None):
591 def switch(self, ctx, status=None):
592 return fullmatchctx(ctx, status)
592 return fullmatchctx(ctx, status)
593
593
594 # filesets using matchctx.switch()
594 # filesets using matchctx.switch()
595 _switchcallers = [
595 _switchcallers = [
596 'revs',
596 'revs',
597 'status',
597 'status',
598 ]
598 ]
599
599
600 def _intree(funcs, tree):
600 def _intree(funcs, tree):
601 if isinstance(tree, tuple):
601 if isinstance(tree, tuple):
602 if tree[0] == 'func' and tree[1][0] == 'symbol':
602 if tree[0] == 'func' and tree[1][0] == 'symbol':
603 if tree[1][1] in funcs:
603 if tree[1][1] in funcs:
604 return True
604 return True
605 if tree[1][1] in _switchcallers:
605 if tree[1][1] in _switchcallers:
606 # arguments won't be evaluated in the current context
606 # arguments won't be evaluated in the current context
607 return False
607 return False
608 for s in tree[1:]:
608 for s in tree[1:]:
609 if _intree(funcs, s):
609 if _intree(funcs, s):
610 return True
610 return True
611 return False
611 return False
612
612
613 def _buildsubset(ctx, status):
613 def _buildsubset(ctx, status):
614 if status:
614 if status:
615 subset = []
615 subset = []
616 for c in status:
616 for c in status:
617 subset.extend(c)
617 subset.extend(c)
618 return subset
618 return subset
619 else:
619 else:
620 return list(ctx.walk(ctx.match([])))
620 return list(ctx.walk(ctx.match([])))
621
621
622 def getfileset(ctx, expr):
622 def getfileset(ctx, expr):
623 tree = parse(expr)
623 tree = parse(expr)
624 return getset(fullmatchctx(ctx, _buildstatus(ctx, tree)), tree)
624 return getset(fullmatchctx(ctx, _buildstatus(ctx, tree)), tree)
625
625
626 def _buildstatus(ctx, tree, basectx=None):
626 def _buildstatus(ctx, tree, basectx=None):
627 # do we need status info?
627 # do we need status info?
628
628
629 # temporaty boolean to simplify the next conditional
629 # temporaty boolean to simplify the next conditional
630 purewdir = ctx.rev() is None and basectx is None
630 purewdir = ctx.rev() is None and basectx is None
631
631
632 if (_intree(_statuscallers, tree) or
632 if (_intree(_statuscallers, tree) or
633 # Using matchctx.existing() on a workingctx requires us to check
633 # Using matchctx.existing() on a workingctx requires us to check
634 # for deleted files.
634 # for deleted files.
635 (purewdir and _intree(_existingcallers, tree))):
635 (purewdir and _intree(_existingcallers, tree))):
636 unknown = _intree(['unknown'], tree)
636 unknown = _intree(['unknown'], tree)
637 ignored = _intree(['ignored'], tree)
637 ignored = _intree(['ignored'], tree)
638
638
639 r = ctx.repo()
639 r = ctx.repo()
640 if basectx is None:
640 if basectx is None:
641 basectx = ctx.p1()
641 basectx = ctx.p1()
642 return r.status(basectx, ctx,
642 return r.status(basectx, ctx,
643 unknown=unknown, ignored=ignored, clean=True)
643 unknown=unknown, ignored=ignored, clean=True)
644 else:
644 else:
645 return None
645 return None
646
646
647 def prettyformat(tree):
647 def prettyformat(tree):
648 return parser.prettyformat(tree, ('string', 'symbol'))
648 return parser.prettyformat(tree, ('string', 'symbol'))
649
649
650 def loadpredicate(ui, extname, registrarobj):
650 def loadpredicate(ui, extname, registrarobj):
651 """Load fileset predicates from specified registrarobj
651 """Load fileset predicates from specified registrarobj
652 """
652 """
653 for name, func in registrarobj._table.iteritems():
653 for name, func in registrarobj._table.iteritems():
654 symbols[name] = func
654 symbols[name] = func
655 if func._callstatus:
655 if func._callstatus:
656 _statuscallers.add(name)
656 _statuscallers.add(name)
657 if func._callexisting:
657 if func._callexisting:
658 _existingcallers.add(name)
658 _existingcallers.add(name)
659
659
660 # load built-in predicates explicitly to setup _statuscallers/_existingcallers
660 # load built-in predicates explicitly to setup _statuscallers/_existingcallers
661 loadpredicate(None, None, predicate)
661 loadpredicate(None, None, predicate)
662
662
663 # tell hggettext to extract docstrings from these functions:
663 # tell hggettext to extract docstrings from these functions:
664 i18nfunctions = symbols.values()
664 i18nfunctions = symbols.values()
@@ -1,1431 +1,1434 b''
1 # scmutil.py - Mercurial core utility functions
1 # scmutil.py - Mercurial core utility functions
2 #
2 #
3 # Copyright Matt Mackall <mpm@selenic.com>
3 # Copyright 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 errno
10 import errno
11 import glob
11 import glob
12 import hashlib
12 import hashlib
13 import os
13 import os
14 import re
14 import re
15 import socket
15 import socket
16 import subprocess
16 import subprocess
17 import weakref
17 import weakref
18
18
19 from .i18n import _
19 from .i18n import _
20 from .node import (
20 from .node import (
21 hex,
21 hex,
22 nullid,
22 nullid,
23 short,
23 short,
24 wdirid,
24 wdirid,
25 wdirrev,
25 wdirrev,
26 )
26 )
27
27
28 from . import (
28 from . import (
29 encoding,
29 encoding,
30 error,
30 error,
31 match as matchmod,
31 match as matchmod,
32 obsolete,
32 obsolete,
33 obsutil,
33 obsutil,
34 pathutil,
34 pathutil,
35 phases,
35 phases,
36 pycompat,
36 pycompat,
37 revsetlang,
37 revsetlang,
38 similar,
38 similar,
39 url,
39 url,
40 util,
40 util,
41 vfs,
41 vfs,
42 )
42 )
43
43
44 from .utils import (
44 from .utils import (
45 procutil,
45 procutil,
46 stringutil,
46 stringutil,
47 )
47 )
48
48
49 if pycompat.iswindows:
49 if pycompat.iswindows:
50 from . import scmwindows as scmplatform
50 from . import scmwindows as scmplatform
51 else:
51 else:
52 from . import scmposix as scmplatform
52 from . import scmposix as scmplatform
53
53
54 termsize = scmplatform.termsize
54 termsize = scmplatform.termsize
55
55
56 class status(tuple):
56 class status(tuple):
57 '''Named tuple with a list of files per status. The 'deleted', 'unknown'
57 '''Named tuple with a list of files per status. The 'deleted', 'unknown'
58 and 'ignored' properties are only relevant to the working copy.
58 and 'ignored' properties are only relevant to the working copy.
59 '''
59 '''
60
60
61 __slots__ = ()
61 __slots__ = ()
62
62
63 def __new__(cls, modified, added, removed, deleted, unknown, ignored,
63 def __new__(cls, modified, added, removed, deleted, unknown, ignored,
64 clean):
64 clean):
65 return tuple.__new__(cls, (modified, added, removed, deleted, unknown,
65 return tuple.__new__(cls, (modified, added, removed, deleted, unknown,
66 ignored, clean))
66 ignored, clean))
67
67
68 @property
68 @property
69 def modified(self):
69 def modified(self):
70 '''files that have been modified'''
70 '''files that have been modified'''
71 return self[0]
71 return self[0]
72
72
73 @property
73 @property
74 def added(self):
74 def added(self):
75 '''files that have been added'''
75 '''files that have been added'''
76 return self[1]
76 return self[1]
77
77
78 @property
78 @property
79 def removed(self):
79 def removed(self):
80 '''files that have been removed'''
80 '''files that have been removed'''
81 return self[2]
81 return self[2]
82
82
83 @property
83 @property
84 def deleted(self):
84 def deleted(self):
85 '''files that are in the dirstate, but have been deleted from the
85 '''files that are in the dirstate, but have been deleted from the
86 working copy (aka "missing")
86 working copy (aka "missing")
87 '''
87 '''
88 return self[3]
88 return self[3]
89
89
90 @property
90 @property
91 def unknown(self):
91 def unknown(self):
92 '''files not in the dirstate that are not ignored'''
92 '''files not in the dirstate that are not ignored'''
93 return self[4]
93 return self[4]
94
94
95 @property
95 @property
96 def ignored(self):
96 def ignored(self):
97 '''files not in the dirstate that are ignored (by _dirignore())'''
97 '''files not in the dirstate that are ignored (by _dirignore())'''
98 return self[5]
98 return self[5]
99
99
100 @property
100 @property
101 def clean(self):
101 def clean(self):
102 '''files that have not been modified'''
102 '''files that have not been modified'''
103 return self[6]
103 return self[6]
104
104
105 def __repr__(self, *args, **kwargs):
105 def __repr__(self, *args, **kwargs):
106 return (('<status modified=%r, added=%r, removed=%r, deleted=%r, '
106 return (('<status modified=%r, added=%r, removed=%r, deleted=%r, '
107 'unknown=%r, ignored=%r, clean=%r>') % self)
107 'unknown=%r, ignored=%r, clean=%r>') % self)
108
108
109 def itersubrepos(ctx1, ctx2):
109 def itersubrepos(ctx1, ctx2):
110 """find subrepos in ctx1 or ctx2"""
110 """find subrepos in ctx1 or ctx2"""
111 # Create a (subpath, ctx) mapping where we prefer subpaths from
111 # Create a (subpath, ctx) mapping where we prefer subpaths from
112 # ctx1. The subpaths from ctx2 are important when the .hgsub file
112 # ctx1. The subpaths from ctx2 are important when the .hgsub file
113 # has been modified (in ctx2) but not yet committed (in ctx1).
113 # has been modified (in ctx2) but not yet committed (in ctx1).
114 subpaths = dict.fromkeys(ctx2.substate, ctx2)
114 subpaths = dict.fromkeys(ctx2.substate, ctx2)
115 subpaths.update(dict.fromkeys(ctx1.substate, ctx1))
115 subpaths.update(dict.fromkeys(ctx1.substate, ctx1))
116
116
117 missing = set()
117 missing = set()
118
118
119 for subpath in ctx2.substate:
119 for subpath in ctx2.substate:
120 if subpath not in ctx1.substate:
120 if subpath not in ctx1.substate:
121 del subpaths[subpath]
121 del subpaths[subpath]
122 missing.add(subpath)
122 missing.add(subpath)
123
123
124 for subpath, ctx in sorted(subpaths.iteritems()):
124 for subpath, ctx in sorted(subpaths.iteritems()):
125 yield subpath, ctx.sub(subpath)
125 yield subpath, ctx.sub(subpath)
126
126
127 # Yield an empty subrepo based on ctx1 for anything only in ctx2. That way,
127 # Yield an empty subrepo based on ctx1 for anything only in ctx2. That way,
128 # status and diff will have an accurate result when it does
128 # status and diff will have an accurate result when it does
129 # 'sub.{status|diff}(rev2)'. Otherwise, the ctx2 subrepo is compared
129 # 'sub.{status|diff}(rev2)'. Otherwise, the ctx2 subrepo is compared
130 # against itself.
130 # against itself.
131 for subpath in missing:
131 for subpath in missing:
132 yield subpath, ctx2.nullsub(subpath, ctx1)
132 yield subpath, ctx2.nullsub(subpath, ctx1)
133
133
134 def nochangesfound(ui, repo, excluded=None):
134 def nochangesfound(ui, repo, excluded=None):
135 '''Report no changes for push/pull, excluded is None or a list of
135 '''Report no changes for push/pull, excluded is None or a list of
136 nodes excluded from the push/pull.
136 nodes excluded from the push/pull.
137 '''
137 '''
138 secretlist = []
138 secretlist = []
139 if excluded:
139 if excluded:
140 for n in excluded:
140 for n in excluded:
141 ctx = repo[n]
141 ctx = repo[n]
142 if ctx.phase() >= phases.secret and not ctx.extinct():
142 if ctx.phase() >= phases.secret and not ctx.extinct():
143 secretlist.append(n)
143 secretlist.append(n)
144
144
145 if secretlist:
145 if secretlist:
146 ui.status(_("no changes found (ignored %d secret changesets)\n")
146 ui.status(_("no changes found (ignored %d secret changesets)\n")
147 % len(secretlist))
147 % len(secretlist))
148 else:
148 else:
149 ui.status(_("no changes found\n"))
149 ui.status(_("no changes found\n"))
150
150
151 def callcatch(ui, func):
151 def callcatch(ui, func):
152 """call func() with global exception handling
152 """call func() with global exception handling
153
153
154 return func() if no exception happens. otherwise do some error handling
154 return func() if no exception happens. otherwise do some error handling
155 and return an exit code accordingly. does not handle all exceptions.
155 and return an exit code accordingly. does not handle all exceptions.
156 """
156 """
157 try:
157 try:
158 try:
158 try:
159 return func()
159 return func()
160 except: # re-raises
160 except: # re-raises
161 ui.traceback()
161 ui.traceback()
162 raise
162 raise
163 # Global exception handling, alphabetically
163 # Global exception handling, alphabetically
164 # Mercurial-specific first, followed by built-in and library exceptions
164 # Mercurial-specific first, followed by built-in and library exceptions
165 except error.LockHeld as inst:
165 except error.LockHeld as inst:
166 if inst.errno == errno.ETIMEDOUT:
166 if inst.errno == errno.ETIMEDOUT:
167 reason = _('timed out waiting for lock held by %r') % inst.locker
167 reason = _('timed out waiting for lock held by %r') % inst.locker
168 else:
168 else:
169 reason = _('lock held by %r') % inst.locker
169 reason = _('lock held by %r') % inst.locker
170 ui.warn(_("abort: %s: %s\n")
170 ui.warn(_("abort: %s: %s\n")
171 % (inst.desc or stringutil.forcebytestr(inst.filename), reason))
171 % (inst.desc or stringutil.forcebytestr(inst.filename), reason))
172 if not inst.locker:
172 if not inst.locker:
173 ui.warn(_("(lock might be very busy)\n"))
173 ui.warn(_("(lock might be very busy)\n"))
174 except error.LockUnavailable as inst:
174 except error.LockUnavailable as inst:
175 ui.warn(_("abort: could not lock %s: %s\n") %
175 ui.warn(_("abort: could not lock %s: %s\n") %
176 (inst.desc or stringutil.forcebytestr(inst.filename),
176 (inst.desc or stringutil.forcebytestr(inst.filename),
177 encoding.strtolocal(inst.strerror)))
177 encoding.strtolocal(inst.strerror)))
178 except error.OutOfBandError as inst:
178 except error.OutOfBandError as inst:
179 if inst.args:
179 if inst.args:
180 msg = _("abort: remote error:\n")
180 msg = _("abort: remote error:\n")
181 else:
181 else:
182 msg = _("abort: remote error\n")
182 msg = _("abort: remote error\n")
183 ui.warn(msg)
183 ui.warn(msg)
184 if inst.args:
184 if inst.args:
185 ui.warn(''.join(inst.args))
185 ui.warn(''.join(inst.args))
186 if inst.hint:
186 if inst.hint:
187 ui.warn('(%s)\n' % inst.hint)
187 ui.warn('(%s)\n' % inst.hint)
188 except error.RepoError as inst:
188 except error.RepoError as inst:
189 ui.warn(_("abort: %s!\n") % inst)
189 ui.warn(_("abort: %s!\n") % inst)
190 if inst.hint:
190 if inst.hint:
191 ui.warn(_("(%s)\n") % inst.hint)
191 ui.warn(_("(%s)\n") % inst.hint)
192 except error.ResponseError as inst:
192 except error.ResponseError as inst:
193 ui.warn(_("abort: %s") % inst.args[0])
193 ui.warn(_("abort: %s") % inst.args[0])
194 msg = inst.args[1]
194 msg = inst.args[1]
195 if isinstance(msg, type(u'')):
195 if isinstance(msg, type(u'')):
196 msg = pycompat.sysbytes(msg)
196 msg = pycompat.sysbytes(msg)
197 if not isinstance(msg, bytes):
197 if not isinstance(msg, bytes):
198 ui.warn(" %r\n" % (msg,))
198 ui.warn(" %r\n" % (msg,))
199 elif not msg:
199 elif not msg:
200 ui.warn(_(" empty string\n"))
200 ui.warn(_(" empty string\n"))
201 else:
201 else:
202 ui.warn("\n%r\n" % stringutil.ellipsis(msg))
202 ui.warn("\n%r\n" % stringutil.ellipsis(msg))
203 except error.CensoredNodeError as inst:
203 except error.CensoredNodeError as inst:
204 ui.warn(_("abort: file censored %s!\n") % inst)
204 ui.warn(_("abort: file censored %s!\n") % inst)
205 except error.RevlogError as inst:
205 except error.RevlogError as inst:
206 ui.warn(_("abort: %s!\n") % inst)
206 ui.warn(_("abort: %s!\n") % inst)
207 except error.InterventionRequired as inst:
207 except error.InterventionRequired as inst:
208 ui.warn("%s\n" % inst)
208 ui.warn("%s\n" % inst)
209 if inst.hint:
209 if inst.hint:
210 ui.warn(_("(%s)\n") % inst.hint)
210 ui.warn(_("(%s)\n") % inst.hint)
211 return 1
211 return 1
212 except error.WdirUnsupported:
212 except error.WdirUnsupported:
213 ui.warn(_("abort: working directory revision cannot be specified\n"))
213 ui.warn(_("abort: working directory revision cannot be specified\n"))
214 except error.Abort as inst:
214 except error.Abort as inst:
215 ui.warn(_("abort: %s\n") % inst)
215 ui.warn(_("abort: %s\n") % inst)
216 if inst.hint:
216 if inst.hint:
217 ui.warn(_("(%s)\n") % inst.hint)
217 ui.warn(_("(%s)\n") % inst.hint)
218 except ImportError as inst:
218 except ImportError as inst:
219 ui.warn(_("abort: %s!\n") % stringutil.forcebytestr(inst))
219 ui.warn(_("abort: %s!\n") % stringutil.forcebytestr(inst))
220 m = stringutil.forcebytestr(inst).split()[-1]
220 m = stringutil.forcebytestr(inst).split()[-1]
221 if m in "mpatch bdiff".split():
221 if m in "mpatch bdiff".split():
222 ui.warn(_("(did you forget to compile extensions?)\n"))
222 ui.warn(_("(did you forget to compile extensions?)\n"))
223 elif m in "zlib".split():
223 elif m in "zlib".split():
224 ui.warn(_("(is your Python install correct?)\n"))
224 ui.warn(_("(is your Python install correct?)\n"))
225 except IOError as inst:
225 except IOError as inst:
226 if util.safehasattr(inst, "code"):
226 if util.safehasattr(inst, "code"):
227 ui.warn(_("abort: %s\n") % stringutil.forcebytestr(inst))
227 ui.warn(_("abort: %s\n") % stringutil.forcebytestr(inst))
228 elif util.safehasattr(inst, "reason"):
228 elif util.safehasattr(inst, "reason"):
229 try: # usually it is in the form (errno, strerror)
229 try: # usually it is in the form (errno, strerror)
230 reason = inst.reason.args[1]
230 reason = inst.reason.args[1]
231 except (AttributeError, IndexError):
231 except (AttributeError, IndexError):
232 # it might be anything, for example a string
232 # it might be anything, for example a string
233 reason = inst.reason
233 reason = inst.reason
234 if isinstance(reason, unicode):
234 if isinstance(reason, unicode):
235 # SSLError of Python 2.7.9 contains a unicode
235 # SSLError of Python 2.7.9 contains a unicode
236 reason = encoding.unitolocal(reason)
236 reason = encoding.unitolocal(reason)
237 ui.warn(_("abort: error: %s\n") % reason)
237 ui.warn(_("abort: error: %s\n") % reason)
238 elif (util.safehasattr(inst, "args")
238 elif (util.safehasattr(inst, "args")
239 and inst.args and inst.args[0] == errno.EPIPE):
239 and inst.args and inst.args[0] == errno.EPIPE):
240 pass
240 pass
241 elif getattr(inst, "strerror", None):
241 elif getattr(inst, "strerror", None):
242 if getattr(inst, "filename", None):
242 if getattr(inst, "filename", None):
243 ui.warn(_("abort: %s: %s\n") % (
243 ui.warn(_("abort: %s: %s\n") % (
244 encoding.strtolocal(inst.strerror),
244 encoding.strtolocal(inst.strerror),
245 stringutil.forcebytestr(inst.filename)))
245 stringutil.forcebytestr(inst.filename)))
246 else:
246 else:
247 ui.warn(_("abort: %s\n") % encoding.strtolocal(inst.strerror))
247 ui.warn(_("abort: %s\n") % encoding.strtolocal(inst.strerror))
248 else:
248 else:
249 raise
249 raise
250 except OSError as inst:
250 except OSError as inst:
251 if getattr(inst, "filename", None) is not None:
251 if getattr(inst, "filename", None) is not None:
252 ui.warn(_("abort: %s: '%s'\n") % (
252 ui.warn(_("abort: %s: '%s'\n") % (
253 encoding.strtolocal(inst.strerror),
253 encoding.strtolocal(inst.strerror),
254 stringutil.forcebytestr(inst.filename)))
254 stringutil.forcebytestr(inst.filename)))
255 else:
255 else:
256 ui.warn(_("abort: %s\n") % encoding.strtolocal(inst.strerror))
256 ui.warn(_("abort: %s\n") % encoding.strtolocal(inst.strerror))
257 except MemoryError:
257 except MemoryError:
258 ui.warn(_("abort: out of memory\n"))
258 ui.warn(_("abort: out of memory\n"))
259 except SystemExit as inst:
259 except SystemExit as inst:
260 # Commands shouldn't sys.exit directly, but give a return code.
260 # Commands shouldn't sys.exit directly, but give a return code.
261 # Just in case catch this and and pass exit code to caller.
261 # Just in case catch this and and pass exit code to caller.
262 return inst.code
262 return inst.code
263 except socket.error as inst:
263 except socket.error as inst:
264 ui.warn(_("abort: %s\n") % stringutil.forcebytestr(inst.args[-1]))
264 ui.warn(_("abort: %s\n") % stringutil.forcebytestr(inst.args[-1]))
265
265
266 return -1
266 return -1
267
267
268 def checknewlabel(repo, lbl, kind):
268 def checknewlabel(repo, lbl, kind):
269 # Do not use the "kind" parameter in ui output.
269 # Do not use the "kind" parameter in ui output.
270 # It makes strings difficult to translate.
270 # It makes strings difficult to translate.
271 if lbl in ['tip', '.', 'null']:
271 if lbl in ['tip', '.', 'null']:
272 raise error.Abort(_("the name '%s' is reserved") % lbl)
272 raise error.Abort(_("the name '%s' is reserved") % lbl)
273 for c in (':', '\0', '\n', '\r'):
273 for c in (':', '\0', '\n', '\r'):
274 if c in lbl:
274 if c in lbl:
275 raise error.Abort(
275 raise error.Abort(
276 _("%r cannot be used in a name") % pycompat.bytestr(c))
276 _("%r cannot be used in a name") % pycompat.bytestr(c))
277 try:
277 try:
278 int(lbl)
278 int(lbl)
279 raise error.Abort(_("cannot use an integer as a name"))
279 raise error.Abort(_("cannot use an integer as a name"))
280 except ValueError:
280 except ValueError:
281 pass
281 pass
282 if lbl.strip() != lbl:
282 if lbl.strip() != lbl:
283 raise error.Abort(_("leading or trailing whitespace in name %r") % lbl)
283 raise error.Abort(_("leading or trailing whitespace in name %r") % lbl)
284
284
285 def checkfilename(f):
285 def checkfilename(f):
286 '''Check that the filename f is an acceptable filename for a tracked file'''
286 '''Check that the filename f is an acceptable filename for a tracked file'''
287 if '\r' in f or '\n' in f:
287 if '\r' in f or '\n' in f:
288 raise error.Abort(_("'\\n' and '\\r' disallowed in filenames: %r") % f)
288 raise error.Abort(_("'\\n' and '\\r' disallowed in filenames: %r") % f)
289
289
290 def checkportable(ui, f):
290 def checkportable(ui, f):
291 '''Check if filename f is portable and warn or abort depending on config'''
291 '''Check if filename f is portable and warn or abort depending on config'''
292 checkfilename(f)
292 checkfilename(f)
293 abort, warn = checkportabilityalert(ui)
293 abort, warn = checkportabilityalert(ui)
294 if abort or warn:
294 if abort or warn:
295 msg = util.checkwinfilename(f)
295 msg = util.checkwinfilename(f)
296 if msg:
296 if msg:
297 msg = "%s: %s" % (msg, procutil.shellquote(f))
297 msg = "%s: %s" % (msg, procutil.shellquote(f))
298 if abort:
298 if abort:
299 raise error.Abort(msg)
299 raise error.Abort(msg)
300 ui.warn(_("warning: %s\n") % msg)
300 ui.warn(_("warning: %s\n") % msg)
301
301
302 def checkportabilityalert(ui):
302 def checkportabilityalert(ui):
303 '''check if the user's config requests nothing, a warning, or abort for
303 '''check if the user's config requests nothing, a warning, or abort for
304 non-portable filenames'''
304 non-portable filenames'''
305 val = ui.config('ui', 'portablefilenames')
305 val = ui.config('ui', 'portablefilenames')
306 lval = val.lower()
306 lval = val.lower()
307 bval = stringutil.parsebool(val)
307 bval = stringutil.parsebool(val)
308 abort = pycompat.iswindows or lval == 'abort'
308 abort = pycompat.iswindows or lval == 'abort'
309 warn = bval or lval == 'warn'
309 warn = bval or lval == 'warn'
310 if bval is None and not (warn or abort or lval == 'ignore'):
310 if bval is None and not (warn or abort or lval == 'ignore'):
311 raise error.ConfigError(
311 raise error.ConfigError(
312 _("ui.portablefilenames value is invalid ('%s')") % val)
312 _("ui.portablefilenames value is invalid ('%s')") % val)
313 return abort, warn
313 return abort, warn
314
314
315 class casecollisionauditor(object):
315 class casecollisionauditor(object):
316 def __init__(self, ui, abort, dirstate):
316 def __init__(self, ui, abort, dirstate):
317 self._ui = ui
317 self._ui = ui
318 self._abort = abort
318 self._abort = abort
319 allfiles = '\0'.join(dirstate._map)
319 allfiles = '\0'.join(dirstate._map)
320 self._loweredfiles = set(encoding.lower(allfiles).split('\0'))
320 self._loweredfiles = set(encoding.lower(allfiles).split('\0'))
321 self._dirstate = dirstate
321 self._dirstate = dirstate
322 # The purpose of _newfiles is so that we don't complain about
322 # The purpose of _newfiles is so that we don't complain about
323 # case collisions if someone were to call this object with the
323 # case collisions if someone were to call this object with the
324 # same filename twice.
324 # same filename twice.
325 self._newfiles = set()
325 self._newfiles = set()
326
326
327 def __call__(self, f):
327 def __call__(self, f):
328 if f in self._newfiles:
328 if f in self._newfiles:
329 return
329 return
330 fl = encoding.lower(f)
330 fl = encoding.lower(f)
331 if fl in self._loweredfiles and f not in self._dirstate:
331 if fl in self._loweredfiles and f not in self._dirstate:
332 msg = _('possible case-folding collision for %s') % f
332 msg = _('possible case-folding collision for %s') % f
333 if self._abort:
333 if self._abort:
334 raise error.Abort(msg)
334 raise error.Abort(msg)
335 self._ui.warn(_("warning: %s\n") % msg)
335 self._ui.warn(_("warning: %s\n") % msg)
336 self._loweredfiles.add(fl)
336 self._loweredfiles.add(fl)
337 self._newfiles.add(f)
337 self._newfiles.add(f)
338
338
339 def filteredhash(repo, maxrev):
339 def filteredhash(repo, maxrev):
340 """build hash of filtered revisions in the current repoview.
340 """build hash of filtered revisions in the current repoview.
341
341
342 Multiple caches perform up-to-date validation by checking that the
342 Multiple caches perform up-to-date validation by checking that the
343 tiprev and tipnode stored in the cache file match the current repository.
343 tiprev and tipnode stored in the cache file match the current repository.
344 However, this is not sufficient for validating repoviews because the set
344 However, this is not sufficient for validating repoviews because the set
345 of revisions in the view may change without the repository tiprev and
345 of revisions in the view may change without the repository tiprev and
346 tipnode changing.
346 tipnode changing.
347
347
348 This function hashes all the revs filtered from the view and returns
348 This function hashes all the revs filtered from the view and returns
349 that SHA-1 digest.
349 that SHA-1 digest.
350 """
350 """
351 cl = repo.changelog
351 cl = repo.changelog
352 if not cl.filteredrevs:
352 if not cl.filteredrevs:
353 return None
353 return None
354 key = None
354 key = None
355 revs = sorted(r for r in cl.filteredrevs if r <= maxrev)
355 revs = sorted(r for r in cl.filteredrevs if r <= maxrev)
356 if revs:
356 if revs:
357 s = hashlib.sha1()
357 s = hashlib.sha1()
358 for rev in revs:
358 for rev in revs:
359 s.update('%d;' % rev)
359 s.update('%d;' % rev)
360 key = s.digest()
360 key = s.digest()
361 return key
361 return key
362
362
363 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
363 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
364 '''yield every hg repository under path, always recursively.
364 '''yield every hg repository under path, always recursively.
365 The recurse flag will only control recursion into repo working dirs'''
365 The recurse flag will only control recursion into repo working dirs'''
366 def errhandler(err):
366 def errhandler(err):
367 if err.filename == path:
367 if err.filename == path:
368 raise err
368 raise err
369 samestat = getattr(os.path, 'samestat', None)
369 samestat = getattr(os.path, 'samestat', None)
370 if followsym and samestat is not None:
370 if followsym and samestat is not None:
371 def adddir(dirlst, dirname):
371 def adddir(dirlst, dirname):
372 dirstat = os.stat(dirname)
372 dirstat = os.stat(dirname)
373 match = any(samestat(dirstat, lstdirstat) for lstdirstat in dirlst)
373 match = any(samestat(dirstat, lstdirstat) for lstdirstat in dirlst)
374 if not match:
374 if not match:
375 dirlst.append(dirstat)
375 dirlst.append(dirstat)
376 return not match
376 return not match
377 else:
377 else:
378 followsym = False
378 followsym = False
379
379
380 if (seen_dirs is None) and followsym:
380 if (seen_dirs is None) and followsym:
381 seen_dirs = []
381 seen_dirs = []
382 adddir(seen_dirs, path)
382 adddir(seen_dirs, path)
383 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
383 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
384 dirs.sort()
384 dirs.sort()
385 if '.hg' in dirs:
385 if '.hg' in dirs:
386 yield root # found a repository
386 yield root # found a repository
387 qroot = os.path.join(root, '.hg', 'patches')
387 qroot = os.path.join(root, '.hg', 'patches')
388 if os.path.isdir(os.path.join(qroot, '.hg')):
388 if os.path.isdir(os.path.join(qroot, '.hg')):
389 yield qroot # we have a patch queue repo here
389 yield qroot # we have a patch queue repo here
390 if recurse:
390 if recurse:
391 # avoid recursing inside the .hg directory
391 # avoid recursing inside the .hg directory
392 dirs.remove('.hg')
392 dirs.remove('.hg')
393 else:
393 else:
394 dirs[:] = [] # don't descend further
394 dirs[:] = [] # don't descend further
395 elif followsym:
395 elif followsym:
396 newdirs = []
396 newdirs = []
397 for d in dirs:
397 for d in dirs:
398 fname = os.path.join(root, d)
398 fname = os.path.join(root, d)
399 if adddir(seen_dirs, fname):
399 if adddir(seen_dirs, fname):
400 if os.path.islink(fname):
400 if os.path.islink(fname):
401 for hgname in walkrepos(fname, True, seen_dirs):
401 for hgname in walkrepos(fname, True, seen_dirs):
402 yield hgname
402 yield hgname
403 else:
403 else:
404 newdirs.append(d)
404 newdirs.append(d)
405 dirs[:] = newdirs
405 dirs[:] = newdirs
406
406
407 def binnode(ctx):
407 def binnode(ctx):
408 """Return binary node id for a given basectx"""
408 """Return binary node id for a given basectx"""
409 node = ctx.node()
409 node = ctx.node()
410 if node is None:
410 if node is None:
411 return wdirid
411 return wdirid
412 return node
412 return node
413
413
414 def intrev(ctx):
414 def intrev(ctx):
415 """Return integer for a given basectx that can be used in comparison or
415 """Return integer for a given basectx that can be used in comparison or
416 arithmetic operation"""
416 arithmetic operation"""
417 rev = ctx.rev()
417 rev = ctx.rev()
418 if rev is None:
418 if rev is None:
419 return wdirrev
419 return wdirrev
420 return rev
420 return rev
421
421
422 def formatchangeid(ctx):
422 def formatchangeid(ctx):
423 """Format changectx as '{rev}:{node|formatnode}', which is the default
423 """Format changectx as '{rev}:{node|formatnode}', which is the default
424 template provided by logcmdutil.changesettemplater"""
424 template provided by logcmdutil.changesettemplater"""
425 repo = ctx.repo()
425 repo = ctx.repo()
426 return formatrevnode(repo.ui, intrev(ctx), binnode(ctx))
426 return formatrevnode(repo.ui, intrev(ctx), binnode(ctx))
427
427
428 def formatrevnode(ui, rev, node):
428 def formatrevnode(ui, rev, node):
429 """Format given revision and node depending on the current verbosity"""
429 """Format given revision and node depending on the current verbosity"""
430 if ui.debugflag:
430 if ui.debugflag:
431 hexfunc = hex
431 hexfunc = hex
432 else:
432 else:
433 hexfunc = short
433 hexfunc = short
434 return '%d:%s' % (rev, hexfunc(node))
434 return '%d:%s' % (rev, hexfunc(node))
435
435
436 def revsingle(repo, revspec, default='.', localalias=None):
436 def revsingle(repo, revspec, default='.', localalias=None):
437 if not revspec and revspec != 0:
437 if not revspec and revspec != 0:
438 return repo[default]
438 return repo[default]
439
439
440 l = revrange(repo, [revspec], localalias=localalias)
440 l = revrange(repo, [revspec], localalias=localalias)
441 if not l:
441 if not l:
442 raise error.Abort(_('empty revision set'))
442 raise error.Abort(_('empty revision set'))
443 return repo[l.last()]
443 return repo[l.last()]
444
444
445 def _pairspec(revspec):
445 def _pairspec(revspec):
446 tree = revsetlang.parse(revspec)
446 tree = revsetlang.parse(revspec)
447 return tree and tree[0] in ('range', 'rangepre', 'rangepost', 'rangeall')
447 return tree and tree[0] in ('range', 'rangepre', 'rangepost', 'rangeall')
448
448
449 def revpairnodes(repo, revs):
450 return revpair(repo, revs)
451
449 def revpair(repo, revs):
452 def revpair(repo, revs):
450 if not revs:
453 if not revs:
451 return repo.dirstate.p1(), None
454 return repo.dirstate.p1(), None
452
455
453 l = revrange(repo, revs)
456 l = revrange(repo, revs)
454
457
455 if not l:
458 if not l:
456 first = second = None
459 first = second = None
457 elif l.isascending():
460 elif l.isascending():
458 first = l.min()
461 first = l.min()
459 second = l.max()
462 second = l.max()
460 elif l.isdescending():
463 elif l.isdescending():
461 first = l.max()
464 first = l.max()
462 second = l.min()
465 second = l.min()
463 else:
466 else:
464 first = l.first()
467 first = l.first()
465 second = l.last()
468 second = l.last()
466
469
467 if first is None:
470 if first is None:
468 raise error.Abort(_('empty revision range'))
471 raise error.Abort(_('empty revision range'))
469 if (first == second and len(revs) >= 2
472 if (first == second and len(revs) >= 2
470 and not all(revrange(repo, [r]) for r in revs)):
473 and not all(revrange(repo, [r]) for r in revs)):
471 raise error.Abort(_('empty revision on one side of range'))
474 raise error.Abort(_('empty revision on one side of range'))
472
475
473 # if top-level is range expression, the result must always be a pair
476 # if top-level is range expression, the result must always be a pair
474 if first == second and len(revs) == 1 and not _pairspec(revs[0]):
477 if first == second and len(revs) == 1 and not _pairspec(revs[0]):
475 return repo.lookup(first), None
478 return repo.lookup(first), None
476
479
477 return repo.lookup(first), repo.lookup(second)
480 return repo.lookup(first), repo.lookup(second)
478
481
479 def revrange(repo, specs, localalias=None):
482 def revrange(repo, specs, localalias=None):
480 """Execute 1 to many revsets and return the union.
483 """Execute 1 to many revsets and return the union.
481
484
482 This is the preferred mechanism for executing revsets using user-specified
485 This is the preferred mechanism for executing revsets using user-specified
483 config options, such as revset aliases.
486 config options, such as revset aliases.
484
487
485 The revsets specified by ``specs`` will be executed via a chained ``OR``
488 The revsets specified by ``specs`` will be executed via a chained ``OR``
486 expression. If ``specs`` is empty, an empty result is returned.
489 expression. If ``specs`` is empty, an empty result is returned.
487
490
488 ``specs`` can contain integers, in which case they are assumed to be
491 ``specs`` can contain integers, in which case they are assumed to be
489 revision numbers.
492 revision numbers.
490
493
491 It is assumed the revsets are already formatted. If you have arguments
494 It is assumed the revsets are already formatted. If you have arguments
492 that need to be expanded in the revset, call ``revsetlang.formatspec()``
495 that need to be expanded in the revset, call ``revsetlang.formatspec()``
493 and pass the result as an element of ``specs``.
496 and pass the result as an element of ``specs``.
494
497
495 Specifying a single revset is allowed.
498 Specifying a single revset is allowed.
496
499
497 Returns a ``revset.abstractsmartset`` which is a list-like interface over
500 Returns a ``revset.abstractsmartset`` which is a list-like interface over
498 integer revisions.
501 integer revisions.
499 """
502 """
500 allspecs = []
503 allspecs = []
501 for spec in specs:
504 for spec in specs:
502 if isinstance(spec, int):
505 if isinstance(spec, int):
503 spec = revsetlang.formatspec('rev(%d)', spec)
506 spec = revsetlang.formatspec('rev(%d)', spec)
504 allspecs.append(spec)
507 allspecs.append(spec)
505 return repo.anyrevs(allspecs, user=True, localalias=localalias)
508 return repo.anyrevs(allspecs, user=True, localalias=localalias)
506
509
507 def meaningfulparents(repo, ctx):
510 def meaningfulparents(repo, ctx):
508 """Return list of meaningful (or all if debug) parentrevs for rev.
511 """Return list of meaningful (or all if debug) parentrevs for rev.
509
512
510 For merges (two non-nullrev revisions) both parents are meaningful.
513 For merges (two non-nullrev revisions) both parents are meaningful.
511 Otherwise the first parent revision is considered meaningful if it
514 Otherwise the first parent revision is considered meaningful if it
512 is not the preceding revision.
515 is not the preceding revision.
513 """
516 """
514 parents = ctx.parents()
517 parents = ctx.parents()
515 if len(parents) > 1:
518 if len(parents) > 1:
516 return parents
519 return parents
517 if repo.ui.debugflag:
520 if repo.ui.debugflag:
518 return [parents[0], repo['null']]
521 return [parents[0], repo['null']]
519 if parents[0].rev() >= intrev(ctx) - 1:
522 if parents[0].rev() >= intrev(ctx) - 1:
520 return []
523 return []
521 return parents
524 return parents
522
525
523 def expandpats(pats):
526 def expandpats(pats):
524 '''Expand bare globs when running on windows.
527 '''Expand bare globs when running on windows.
525 On posix we assume it already has already been done by sh.'''
528 On posix we assume it already has already been done by sh.'''
526 if not util.expandglobs:
529 if not util.expandglobs:
527 return list(pats)
530 return list(pats)
528 ret = []
531 ret = []
529 for kindpat in pats:
532 for kindpat in pats:
530 kind, pat = matchmod._patsplit(kindpat, None)
533 kind, pat = matchmod._patsplit(kindpat, None)
531 if kind is None:
534 if kind is None:
532 try:
535 try:
533 globbed = glob.glob(pat)
536 globbed = glob.glob(pat)
534 except re.error:
537 except re.error:
535 globbed = [pat]
538 globbed = [pat]
536 if globbed:
539 if globbed:
537 ret.extend(globbed)
540 ret.extend(globbed)
538 continue
541 continue
539 ret.append(kindpat)
542 ret.append(kindpat)
540 return ret
543 return ret
541
544
542 def matchandpats(ctx, pats=(), opts=None, globbed=False, default='relpath',
545 def matchandpats(ctx, pats=(), opts=None, globbed=False, default='relpath',
543 badfn=None):
546 badfn=None):
544 '''Return a matcher and the patterns that were used.
547 '''Return a matcher and the patterns that were used.
545 The matcher will warn about bad matches, unless an alternate badfn callback
548 The matcher will warn about bad matches, unless an alternate badfn callback
546 is provided.'''
549 is provided.'''
547 if pats == ("",):
550 if pats == ("",):
548 pats = []
551 pats = []
549 if opts is None:
552 if opts is None:
550 opts = {}
553 opts = {}
551 if not globbed and default == 'relpath':
554 if not globbed and default == 'relpath':
552 pats = expandpats(pats or [])
555 pats = expandpats(pats or [])
553
556
554 def bad(f, msg):
557 def bad(f, msg):
555 ctx.repo().ui.warn("%s: %s\n" % (m.rel(f), msg))
558 ctx.repo().ui.warn("%s: %s\n" % (m.rel(f), msg))
556
559
557 if badfn is None:
560 if badfn is None:
558 badfn = bad
561 badfn = bad
559
562
560 m = ctx.match(pats, opts.get('include'), opts.get('exclude'),
563 m = ctx.match(pats, opts.get('include'), opts.get('exclude'),
561 default, listsubrepos=opts.get('subrepos'), badfn=badfn)
564 default, listsubrepos=opts.get('subrepos'), badfn=badfn)
562
565
563 if m.always():
566 if m.always():
564 pats = []
567 pats = []
565 return m, pats
568 return m, pats
566
569
567 def match(ctx, pats=(), opts=None, globbed=False, default='relpath',
570 def match(ctx, pats=(), opts=None, globbed=False, default='relpath',
568 badfn=None):
571 badfn=None):
569 '''Return a matcher that will warn about bad matches.'''
572 '''Return a matcher that will warn about bad matches.'''
570 return matchandpats(ctx, pats, opts, globbed, default, badfn=badfn)[0]
573 return matchandpats(ctx, pats, opts, globbed, default, badfn=badfn)[0]
571
574
572 def matchall(repo):
575 def matchall(repo):
573 '''Return a matcher that will efficiently match everything.'''
576 '''Return a matcher that will efficiently match everything.'''
574 return matchmod.always(repo.root, repo.getcwd())
577 return matchmod.always(repo.root, repo.getcwd())
575
578
576 def matchfiles(repo, files, badfn=None):
579 def matchfiles(repo, files, badfn=None):
577 '''Return a matcher that will efficiently match exactly these files.'''
580 '''Return a matcher that will efficiently match exactly these files.'''
578 return matchmod.exact(repo.root, repo.getcwd(), files, badfn=badfn)
581 return matchmod.exact(repo.root, repo.getcwd(), files, badfn=badfn)
579
582
580 def parsefollowlinespattern(repo, rev, pat, msg):
583 def parsefollowlinespattern(repo, rev, pat, msg):
581 """Return a file name from `pat` pattern suitable for usage in followlines
584 """Return a file name from `pat` pattern suitable for usage in followlines
582 logic.
585 logic.
583 """
586 """
584 if not matchmod.patkind(pat):
587 if not matchmod.patkind(pat):
585 return pathutil.canonpath(repo.root, repo.getcwd(), pat)
588 return pathutil.canonpath(repo.root, repo.getcwd(), pat)
586 else:
589 else:
587 ctx = repo[rev]
590 ctx = repo[rev]
588 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=ctx)
591 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=ctx)
589 files = [f for f in ctx if m(f)]
592 files = [f for f in ctx if m(f)]
590 if len(files) != 1:
593 if len(files) != 1:
591 raise error.ParseError(msg)
594 raise error.ParseError(msg)
592 return files[0]
595 return files[0]
593
596
594 def origpath(ui, repo, filepath):
597 def origpath(ui, repo, filepath):
595 '''customize where .orig files are created
598 '''customize where .orig files are created
596
599
597 Fetch user defined path from config file: [ui] origbackuppath = <path>
600 Fetch user defined path from config file: [ui] origbackuppath = <path>
598 Fall back to default (filepath with .orig suffix) if not specified
601 Fall back to default (filepath with .orig suffix) if not specified
599 '''
602 '''
600 origbackuppath = ui.config('ui', 'origbackuppath')
603 origbackuppath = ui.config('ui', 'origbackuppath')
601 if not origbackuppath:
604 if not origbackuppath:
602 return filepath + ".orig"
605 return filepath + ".orig"
603
606
604 # Convert filepath from an absolute path into a path inside the repo.
607 # Convert filepath from an absolute path into a path inside the repo.
605 filepathfromroot = util.normpath(os.path.relpath(filepath,
608 filepathfromroot = util.normpath(os.path.relpath(filepath,
606 start=repo.root))
609 start=repo.root))
607
610
608 origvfs = vfs.vfs(repo.wjoin(origbackuppath))
611 origvfs = vfs.vfs(repo.wjoin(origbackuppath))
609 origbackupdir = origvfs.dirname(filepathfromroot)
612 origbackupdir = origvfs.dirname(filepathfromroot)
610 if not origvfs.isdir(origbackupdir) or origvfs.islink(origbackupdir):
613 if not origvfs.isdir(origbackupdir) or origvfs.islink(origbackupdir):
611 ui.note(_('creating directory: %s\n') % origvfs.join(origbackupdir))
614 ui.note(_('creating directory: %s\n') % origvfs.join(origbackupdir))
612
615
613 # Remove any files that conflict with the backup file's path
616 # Remove any files that conflict with the backup file's path
614 for f in reversed(list(util.finddirs(filepathfromroot))):
617 for f in reversed(list(util.finddirs(filepathfromroot))):
615 if origvfs.isfileorlink(f):
618 if origvfs.isfileorlink(f):
616 ui.note(_('removing conflicting file: %s\n')
619 ui.note(_('removing conflicting file: %s\n')
617 % origvfs.join(f))
620 % origvfs.join(f))
618 origvfs.unlink(f)
621 origvfs.unlink(f)
619 break
622 break
620
623
621 origvfs.makedirs(origbackupdir)
624 origvfs.makedirs(origbackupdir)
622
625
623 if origvfs.isdir(filepathfromroot) and not origvfs.islink(filepathfromroot):
626 if origvfs.isdir(filepathfromroot) and not origvfs.islink(filepathfromroot):
624 ui.note(_('removing conflicting directory: %s\n')
627 ui.note(_('removing conflicting directory: %s\n')
625 % origvfs.join(filepathfromroot))
628 % origvfs.join(filepathfromroot))
626 origvfs.rmtree(filepathfromroot, forcibly=True)
629 origvfs.rmtree(filepathfromroot, forcibly=True)
627
630
628 return origvfs.join(filepathfromroot)
631 return origvfs.join(filepathfromroot)
629
632
630 class _containsnode(object):
633 class _containsnode(object):
631 """proxy __contains__(node) to container.__contains__ which accepts revs"""
634 """proxy __contains__(node) to container.__contains__ which accepts revs"""
632
635
633 def __init__(self, repo, revcontainer):
636 def __init__(self, repo, revcontainer):
634 self._torev = repo.changelog.rev
637 self._torev = repo.changelog.rev
635 self._revcontains = revcontainer.__contains__
638 self._revcontains = revcontainer.__contains__
636
639
637 def __contains__(self, node):
640 def __contains__(self, node):
638 return self._revcontains(self._torev(node))
641 return self._revcontains(self._torev(node))
639
642
640 def cleanupnodes(repo, replacements, operation, moves=None, metadata=None):
643 def cleanupnodes(repo, replacements, operation, moves=None, metadata=None):
641 """do common cleanups when old nodes are replaced by new nodes
644 """do common cleanups when old nodes are replaced by new nodes
642
645
643 That includes writing obsmarkers or stripping nodes, and moving bookmarks.
646 That includes writing obsmarkers or stripping nodes, and moving bookmarks.
644 (we might also want to move working directory parent in the future)
647 (we might also want to move working directory parent in the future)
645
648
646 By default, bookmark moves are calculated automatically from 'replacements',
649 By default, bookmark moves are calculated automatically from 'replacements',
647 but 'moves' can be used to override that. Also, 'moves' may include
650 but 'moves' can be used to override that. Also, 'moves' may include
648 additional bookmark moves that should not have associated obsmarkers.
651 additional bookmark moves that should not have associated obsmarkers.
649
652
650 replacements is {oldnode: [newnode]} or a iterable of nodes if they do not
653 replacements is {oldnode: [newnode]} or a iterable of nodes if they do not
651 have replacements. operation is a string, like "rebase".
654 have replacements. operation is a string, like "rebase".
652
655
653 metadata is dictionary containing metadata to be stored in obsmarker if
656 metadata is dictionary containing metadata to be stored in obsmarker if
654 obsolescence is enabled.
657 obsolescence is enabled.
655 """
658 """
656 if not replacements and not moves:
659 if not replacements and not moves:
657 return
660 return
658
661
659 # translate mapping's other forms
662 # translate mapping's other forms
660 if not util.safehasattr(replacements, 'items'):
663 if not util.safehasattr(replacements, 'items'):
661 replacements = {n: () for n in replacements}
664 replacements = {n: () for n in replacements}
662
665
663 # Calculate bookmark movements
666 # Calculate bookmark movements
664 if moves is None:
667 if moves is None:
665 moves = {}
668 moves = {}
666 # Unfiltered repo is needed since nodes in replacements might be hidden.
669 # Unfiltered repo is needed since nodes in replacements might be hidden.
667 unfi = repo.unfiltered()
670 unfi = repo.unfiltered()
668 for oldnode, newnodes in replacements.items():
671 for oldnode, newnodes in replacements.items():
669 if oldnode in moves:
672 if oldnode in moves:
670 continue
673 continue
671 if len(newnodes) > 1:
674 if len(newnodes) > 1:
672 # usually a split, take the one with biggest rev number
675 # usually a split, take the one with biggest rev number
673 newnode = next(unfi.set('max(%ln)', newnodes)).node()
676 newnode = next(unfi.set('max(%ln)', newnodes)).node()
674 elif len(newnodes) == 0:
677 elif len(newnodes) == 0:
675 # move bookmark backwards
678 # move bookmark backwards
676 roots = list(unfi.set('max((::%n) - %ln)', oldnode,
679 roots = list(unfi.set('max((::%n) - %ln)', oldnode,
677 list(replacements)))
680 list(replacements)))
678 if roots:
681 if roots:
679 newnode = roots[0].node()
682 newnode = roots[0].node()
680 else:
683 else:
681 newnode = nullid
684 newnode = nullid
682 else:
685 else:
683 newnode = newnodes[0]
686 newnode = newnodes[0]
684 moves[oldnode] = newnode
687 moves[oldnode] = newnode
685
688
686 with repo.transaction('cleanup') as tr:
689 with repo.transaction('cleanup') as tr:
687 # Move bookmarks
690 # Move bookmarks
688 bmarks = repo._bookmarks
691 bmarks = repo._bookmarks
689 bmarkchanges = []
692 bmarkchanges = []
690 allnewnodes = [n for ns in replacements.values() for n in ns]
693 allnewnodes = [n for ns in replacements.values() for n in ns]
691 for oldnode, newnode in moves.items():
694 for oldnode, newnode in moves.items():
692 oldbmarks = repo.nodebookmarks(oldnode)
695 oldbmarks = repo.nodebookmarks(oldnode)
693 if not oldbmarks:
696 if not oldbmarks:
694 continue
697 continue
695 from . import bookmarks # avoid import cycle
698 from . import bookmarks # avoid import cycle
696 repo.ui.debug('moving bookmarks %r from %s to %s\n' %
699 repo.ui.debug('moving bookmarks %r from %s to %s\n' %
697 (util.rapply(pycompat.maybebytestr, oldbmarks),
700 (util.rapply(pycompat.maybebytestr, oldbmarks),
698 hex(oldnode), hex(newnode)))
701 hex(oldnode), hex(newnode)))
699 # Delete divergent bookmarks being parents of related newnodes
702 # Delete divergent bookmarks being parents of related newnodes
700 deleterevs = repo.revs('parents(roots(%ln & (::%n))) - parents(%n)',
703 deleterevs = repo.revs('parents(roots(%ln & (::%n))) - parents(%n)',
701 allnewnodes, newnode, oldnode)
704 allnewnodes, newnode, oldnode)
702 deletenodes = _containsnode(repo, deleterevs)
705 deletenodes = _containsnode(repo, deleterevs)
703 for name in oldbmarks:
706 for name in oldbmarks:
704 bmarkchanges.append((name, newnode))
707 bmarkchanges.append((name, newnode))
705 for b in bookmarks.divergent2delete(repo, deletenodes, name):
708 for b in bookmarks.divergent2delete(repo, deletenodes, name):
706 bmarkchanges.append((b, None))
709 bmarkchanges.append((b, None))
707
710
708 if bmarkchanges:
711 if bmarkchanges:
709 bmarks.applychanges(repo, tr, bmarkchanges)
712 bmarks.applychanges(repo, tr, bmarkchanges)
710
713
711 # Obsolete or strip nodes
714 # Obsolete or strip nodes
712 if obsolete.isenabled(repo, obsolete.createmarkersopt):
715 if obsolete.isenabled(repo, obsolete.createmarkersopt):
713 # If a node is already obsoleted, and we want to obsolete it
716 # If a node is already obsoleted, and we want to obsolete it
714 # without a successor, skip that obssolete request since it's
717 # without a successor, skip that obssolete request since it's
715 # unnecessary. That's the "if s or not isobs(n)" check below.
718 # unnecessary. That's the "if s or not isobs(n)" check below.
716 # Also sort the node in topology order, that might be useful for
719 # Also sort the node in topology order, that might be useful for
717 # some obsstore logic.
720 # some obsstore logic.
718 # NOTE: the filtering and sorting might belong to createmarkers.
721 # NOTE: the filtering and sorting might belong to createmarkers.
719 isobs = unfi.obsstore.successors.__contains__
722 isobs = unfi.obsstore.successors.__contains__
720 torev = unfi.changelog.rev
723 torev = unfi.changelog.rev
721 sortfunc = lambda ns: torev(ns[0])
724 sortfunc = lambda ns: torev(ns[0])
722 rels = [(unfi[n], tuple(unfi[m] for m in s))
725 rels = [(unfi[n], tuple(unfi[m] for m in s))
723 for n, s in sorted(replacements.items(), key=sortfunc)
726 for n, s in sorted(replacements.items(), key=sortfunc)
724 if s or not isobs(n)]
727 if s or not isobs(n)]
725 if rels:
728 if rels:
726 obsolete.createmarkers(repo, rels, operation=operation,
729 obsolete.createmarkers(repo, rels, operation=operation,
727 metadata=metadata)
730 metadata=metadata)
728 else:
731 else:
729 from . import repair # avoid import cycle
732 from . import repair # avoid import cycle
730 tostrip = list(replacements)
733 tostrip = list(replacements)
731 if tostrip:
734 if tostrip:
732 repair.delayedstrip(repo.ui, repo, tostrip, operation)
735 repair.delayedstrip(repo.ui, repo, tostrip, operation)
733
736
734 def addremove(repo, matcher, prefix, opts=None, dry_run=None, similarity=None):
737 def addremove(repo, matcher, prefix, opts=None, dry_run=None, similarity=None):
735 if opts is None:
738 if opts is None:
736 opts = {}
739 opts = {}
737 m = matcher
740 m = matcher
738 if dry_run is None:
741 if dry_run is None:
739 dry_run = opts.get('dry_run')
742 dry_run = opts.get('dry_run')
740 if similarity is None:
743 if similarity is None:
741 similarity = float(opts.get('similarity') or 0)
744 similarity = float(opts.get('similarity') or 0)
742
745
743 ret = 0
746 ret = 0
744 join = lambda f: os.path.join(prefix, f)
747 join = lambda f: os.path.join(prefix, f)
745
748
746 wctx = repo[None]
749 wctx = repo[None]
747 for subpath in sorted(wctx.substate):
750 for subpath in sorted(wctx.substate):
748 submatch = matchmod.subdirmatcher(subpath, m)
751 submatch = matchmod.subdirmatcher(subpath, m)
749 if opts.get('subrepos') or m.exact(subpath) or any(submatch.files()):
752 if opts.get('subrepos') or m.exact(subpath) or any(submatch.files()):
750 sub = wctx.sub(subpath)
753 sub = wctx.sub(subpath)
751 try:
754 try:
752 if sub.addremove(submatch, prefix, opts, dry_run, similarity):
755 if sub.addremove(submatch, prefix, opts, dry_run, similarity):
753 ret = 1
756 ret = 1
754 except error.LookupError:
757 except error.LookupError:
755 repo.ui.status(_("skipping missing subrepository: %s\n")
758 repo.ui.status(_("skipping missing subrepository: %s\n")
756 % join(subpath))
759 % join(subpath))
757
760
758 rejected = []
761 rejected = []
759 def badfn(f, msg):
762 def badfn(f, msg):
760 if f in m.files():
763 if f in m.files():
761 m.bad(f, msg)
764 m.bad(f, msg)
762 rejected.append(f)
765 rejected.append(f)
763
766
764 badmatch = matchmod.badmatch(m, badfn)
767 badmatch = matchmod.badmatch(m, badfn)
765 added, unknown, deleted, removed, forgotten = _interestingfiles(repo,
768 added, unknown, deleted, removed, forgotten = _interestingfiles(repo,
766 badmatch)
769 badmatch)
767
770
768 unknownset = set(unknown + forgotten)
771 unknownset = set(unknown + forgotten)
769 toprint = unknownset.copy()
772 toprint = unknownset.copy()
770 toprint.update(deleted)
773 toprint.update(deleted)
771 for abs in sorted(toprint):
774 for abs in sorted(toprint):
772 if repo.ui.verbose or not m.exact(abs):
775 if repo.ui.verbose or not m.exact(abs):
773 if abs in unknownset:
776 if abs in unknownset:
774 status = _('adding %s\n') % m.uipath(abs)
777 status = _('adding %s\n') % m.uipath(abs)
775 else:
778 else:
776 status = _('removing %s\n') % m.uipath(abs)
779 status = _('removing %s\n') % m.uipath(abs)
777 repo.ui.status(status)
780 repo.ui.status(status)
778
781
779 renames = _findrenames(repo, m, added + unknown, removed + deleted,
782 renames = _findrenames(repo, m, added + unknown, removed + deleted,
780 similarity)
783 similarity)
781
784
782 if not dry_run:
785 if not dry_run:
783 _markchanges(repo, unknown + forgotten, deleted, renames)
786 _markchanges(repo, unknown + forgotten, deleted, renames)
784
787
785 for f in rejected:
788 for f in rejected:
786 if f in m.files():
789 if f in m.files():
787 return 1
790 return 1
788 return ret
791 return ret
789
792
790 def marktouched(repo, files, similarity=0.0):
793 def marktouched(repo, files, similarity=0.0):
791 '''Assert that files have somehow been operated upon. files are relative to
794 '''Assert that files have somehow been operated upon. files are relative to
792 the repo root.'''
795 the repo root.'''
793 m = matchfiles(repo, files, badfn=lambda x, y: rejected.append(x))
796 m = matchfiles(repo, files, badfn=lambda x, y: rejected.append(x))
794 rejected = []
797 rejected = []
795
798
796 added, unknown, deleted, removed, forgotten = _interestingfiles(repo, m)
799 added, unknown, deleted, removed, forgotten = _interestingfiles(repo, m)
797
800
798 if repo.ui.verbose:
801 if repo.ui.verbose:
799 unknownset = set(unknown + forgotten)
802 unknownset = set(unknown + forgotten)
800 toprint = unknownset.copy()
803 toprint = unknownset.copy()
801 toprint.update(deleted)
804 toprint.update(deleted)
802 for abs in sorted(toprint):
805 for abs in sorted(toprint):
803 if abs in unknownset:
806 if abs in unknownset:
804 status = _('adding %s\n') % abs
807 status = _('adding %s\n') % abs
805 else:
808 else:
806 status = _('removing %s\n') % abs
809 status = _('removing %s\n') % abs
807 repo.ui.status(status)
810 repo.ui.status(status)
808
811
809 renames = _findrenames(repo, m, added + unknown, removed + deleted,
812 renames = _findrenames(repo, m, added + unknown, removed + deleted,
810 similarity)
813 similarity)
811
814
812 _markchanges(repo, unknown + forgotten, deleted, renames)
815 _markchanges(repo, unknown + forgotten, deleted, renames)
813
816
814 for f in rejected:
817 for f in rejected:
815 if f in m.files():
818 if f in m.files():
816 return 1
819 return 1
817 return 0
820 return 0
818
821
819 def _interestingfiles(repo, matcher):
822 def _interestingfiles(repo, matcher):
820 '''Walk dirstate with matcher, looking for files that addremove would care
823 '''Walk dirstate with matcher, looking for files that addremove would care
821 about.
824 about.
822
825
823 This is different from dirstate.status because it doesn't care about
826 This is different from dirstate.status because it doesn't care about
824 whether files are modified or clean.'''
827 whether files are modified or clean.'''
825 added, unknown, deleted, removed, forgotten = [], [], [], [], []
828 added, unknown, deleted, removed, forgotten = [], [], [], [], []
826 audit_path = pathutil.pathauditor(repo.root, cached=True)
829 audit_path = pathutil.pathauditor(repo.root, cached=True)
827
830
828 ctx = repo[None]
831 ctx = repo[None]
829 dirstate = repo.dirstate
832 dirstate = repo.dirstate
830 walkresults = dirstate.walk(matcher, subrepos=sorted(ctx.substate),
833 walkresults = dirstate.walk(matcher, subrepos=sorted(ctx.substate),
831 unknown=True, ignored=False, full=False)
834 unknown=True, ignored=False, full=False)
832 for abs, st in walkresults.iteritems():
835 for abs, st in walkresults.iteritems():
833 dstate = dirstate[abs]
836 dstate = dirstate[abs]
834 if dstate == '?' and audit_path.check(abs):
837 if dstate == '?' and audit_path.check(abs):
835 unknown.append(abs)
838 unknown.append(abs)
836 elif dstate != 'r' and not st:
839 elif dstate != 'r' and not st:
837 deleted.append(abs)
840 deleted.append(abs)
838 elif dstate == 'r' and st:
841 elif dstate == 'r' and st:
839 forgotten.append(abs)
842 forgotten.append(abs)
840 # for finding renames
843 # for finding renames
841 elif dstate == 'r' and not st:
844 elif dstate == 'r' and not st:
842 removed.append(abs)
845 removed.append(abs)
843 elif dstate == 'a':
846 elif dstate == 'a':
844 added.append(abs)
847 added.append(abs)
845
848
846 return added, unknown, deleted, removed, forgotten
849 return added, unknown, deleted, removed, forgotten
847
850
848 def _findrenames(repo, matcher, added, removed, similarity):
851 def _findrenames(repo, matcher, added, removed, similarity):
849 '''Find renames from removed files to added ones.'''
852 '''Find renames from removed files to added ones.'''
850 renames = {}
853 renames = {}
851 if similarity > 0:
854 if similarity > 0:
852 for old, new, score in similar.findrenames(repo, added, removed,
855 for old, new, score in similar.findrenames(repo, added, removed,
853 similarity):
856 similarity):
854 if (repo.ui.verbose or not matcher.exact(old)
857 if (repo.ui.verbose or not matcher.exact(old)
855 or not matcher.exact(new)):
858 or not matcher.exact(new)):
856 repo.ui.status(_('recording removal of %s as rename to %s '
859 repo.ui.status(_('recording removal of %s as rename to %s '
857 '(%d%% similar)\n') %
860 '(%d%% similar)\n') %
858 (matcher.rel(old), matcher.rel(new),
861 (matcher.rel(old), matcher.rel(new),
859 score * 100))
862 score * 100))
860 renames[new] = old
863 renames[new] = old
861 return renames
864 return renames
862
865
863 def _markchanges(repo, unknown, deleted, renames):
866 def _markchanges(repo, unknown, deleted, renames):
864 '''Marks the files in unknown as added, the files in deleted as removed,
867 '''Marks the files in unknown as added, the files in deleted as removed,
865 and the files in renames as copied.'''
868 and the files in renames as copied.'''
866 wctx = repo[None]
869 wctx = repo[None]
867 with repo.wlock():
870 with repo.wlock():
868 wctx.forget(deleted)
871 wctx.forget(deleted)
869 wctx.add(unknown)
872 wctx.add(unknown)
870 for new, old in renames.iteritems():
873 for new, old in renames.iteritems():
871 wctx.copy(old, new)
874 wctx.copy(old, new)
872
875
873 def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None):
876 def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None):
874 """Update the dirstate to reflect the intent of copying src to dst. For
877 """Update the dirstate to reflect the intent of copying src to dst. For
875 different reasons it might not end with dst being marked as copied from src.
878 different reasons it might not end with dst being marked as copied from src.
876 """
879 """
877 origsrc = repo.dirstate.copied(src) or src
880 origsrc = repo.dirstate.copied(src) or src
878 if dst == origsrc: # copying back a copy?
881 if dst == origsrc: # copying back a copy?
879 if repo.dirstate[dst] not in 'mn' and not dryrun:
882 if repo.dirstate[dst] not in 'mn' and not dryrun:
880 repo.dirstate.normallookup(dst)
883 repo.dirstate.normallookup(dst)
881 else:
884 else:
882 if repo.dirstate[origsrc] == 'a' and origsrc == src:
885 if repo.dirstate[origsrc] == 'a' and origsrc == src:
883 if not ui.quiet:
886 if not ui.quiet:
884 ui.warn(_("%s has not been committed yet, so no copy "
887 ui.warn(_("%s has not been committed yet, so no copy "
885 "data will be stored for %s.\n")
888 "data will be stored for %s.\n")
886 % (repo.pathto(origsrc, cwd), repo.pathto(dst, cwd)))
889 % (repo.pathto(origsrc, cwd), repo.pathto(dst, cwd)))
887 if repo.dirstate[dst] in '?r' and not dryrun:
890 if repo.dirstate[dst] in '?r' and not dryrun:
888 wctx.add([dst])
891 wctx.add([dst])
889 elif not dryrun:
892 elif not dryrun:
890 wctx.copy(origsrc, dst)
893 wctx.copy(origsrc, dst)
891
894
892 def readrequires(opener, supported):
895 def readrequires(opener, supported):
893 '''Reads and parses .hg/requires and checks if all entries found
896 '''Reads and parses .hg/requires and checks if all entries found
894 are in the list of supported features.'''
897 are in the list of supported features.'''
895 requirements = set(opener.read("requires").splitlines())
898 requirements = set(opener.read("requires").splitlines())
896 missings = []
899 missings = []
897 for r in requirements:
900 for r in requirements:
898 if r not in supported:
901 if r not in supported:
899 if not r or not r[0:1].isalnum():
902 if not r or not r[0:1].isalnum():
900 raise error.RequirementError(_(".hg/requires file is corrupt"))
903 raise error.RequirementError(_(".hg/requires file is corrupt"))
901 missings.append(r)
904 missings.append(r)
902 missings.sort()
905 missings.sort()
903 if missings:
906 if missings:
904 raise error.RequirementError(
907 raise error.RequirementError(
905 _("repository requires features unknown to this Mercurial: %s")
908 _("repository requires features unknown to this Mercurial: %s")
906 % " ".join(missings),
909 % " ".join(missings),
907 hint=_("see https://mercurial-scm.org/wiki/MissingRequirement"
910 hint=_("see https://mercurial-scm.org/wiki/MissingRequirement"
908 " for more information"))
911 " for more information"))
909 return requirements
912 return requirements
910
913
911 def writerequires(opener, requirements):
914 def writerequires(opener, requirements):
912 with opener('requires', 'w') as fp:
915 with opener('requires', 'w') as fp:
913 for r in sorted(requirements):
916 for r in sorted(requirements):
914 fp.write("%s\n" % r)
917 fp.write("%s\n" % r)
915
918
916 class filecachesubentry(object):
919 class filecachesubentry(object):
917 def __init__(self, path, stat):
920 def __init__(self, path, stat):
918 self.path = path
921 self.path = path
919 self.cachestat = None
922 self.cachestat = None
920 self._cacheable = None
923 self._cacheable = None
921
924
922 if stat:
925 if stat:
923 self.cachestat = filecachesubentry.stat(self.path)
926 self.cachestat = filecachesubentry.stat(self.path)
924
927
925 if self.cachestat:
928 if self.cachestat:
926 self._cacheable = self.cachestat.cacheable()
929 self._cacheable = self.cachestat.cacheable()
927 else:
930 else:
928 # None means we don't know yet
931 # None means we don't know yet
929 self._cacheable = None
932 self._cacheable = None
930
933
931 def refresh(self):
934 def refresh(self):
932 if self.cacheable():
935 if self.cacheable():
933 self.cachestat = filecachesubentry.stat(self.path)
936 self.cachestat = filecachesubentry.stat(self.path)
934
937
935 def cacheable(self):
938 def cacheable(self):
936 if self._cacheable is not None:
939 if self._cacheable is not None:
937 return self._cacheable
940 return self._cacheable
938
941
939 # we don't know yet, assume it is for now
942 # we don't know yet, assume it is for now
940 return True
943 return True
941
944
942 def changed(self):
945 def changed(self):
943 # no point in going further if we can't cache it
946 # no point in going further if we can't cache it
944 if not self.cacheable():
947 if not self.cacheable():
945 return True
948 return True
946
949
947 newstat = filecachesubentry.stat(self.path)
950 newstat = filecachesubentry.stat(self.path)
948
951
949 # we may not know if it's cacheable yet, check again now
952 # we may not know if it's cacheable yet, check again now
950 if newstat and self._cacheable is None:
953 if newstat and self._cacheable is None:
951 self._cacheable = newstat.cacheable()
954 self._cacheable = newstat.cacheable()
952
955
953 # check again
956 # check again
954 if not self._cacheable:
957 if not self._cacheable:
955 return True
958 return True
956
959
957 if self.cachestat != newstat:
960 if self.cachestat != newstat:
958 self.cachestat = newstat
961 self.cachestat = newstat
959 return True
962 return True
960 else:
963 else:
961 return False
964 return False
962
965
963 @staticmethod
966 @staticmethod
964 def stat(path):
967 def stat(path):
965 try:
968 try:
966 return util.cachestat(path)
969 return util.cachestat(path)
967 except OSError as e:
970 except OSError as e:
968 if e.errno != errno.ENOENT:
971 if e.errno != errno.ENOENT:
969 raise
972 raise
970
973
971 class filecacheentry(object):
974 class filecacheentry(object):
972 def __init__(self, paths, stat=True):
975 def __init__(self, paths, stat=True):
973 self._entries = []
976 self._entries = []
974 for path in paths:
977 for path in paths:
975 self._entries.append(filecachesubentry(path, stat))
978 self._entries.append(filecachesubentry(path, stat))
976
979
977 def changed(self):
980 def changed(self):
978 '''true if any entry has changed'''
981 '''true if any entry has changed'''
979 for entry in self._entries:
982 for entry in self._entries:
980 if entry.changed():
983 if entry.changed():
981 return True
984 return True
982 return False
985 return False
983
986
984 def refresh(self):
987 def refresh(self):
985 for entry in self._entries:
988 for entry in self._entries:
986 entry.refresh()
989 entry.refresh()
987
990
988 class filecache(object):
991 class filecache(object):
989 '''A property like decorator that tracks files under .hg/ for updates.
992 '''A property like decorator that tracks files under .hg/ for updates.
990
993
991 Records stat info when called in _filecache.
994 Records stat info when called in _filecache.
992
995
993 On subsequent calls, compares old stat info with new info, and recreates the
996 On subsequent calls, compares old stat info with new info, and recreates the
994 object when any of the files changes, updating the new stat info in
997 object when any of the files changes, updating the new stat info in
995 _filecache.
998 _filecache.
996
999
997 Mercurial either atomic renames or appends for files under .hg,
1000 Mercurial either atomic renames or appends for files under .hg,
998 so to ensure the cache is reliable we need the filesystem to be able
1001 so to ensure the cache is reliable we need the filesystem to be able
999 to tell us if a file has been replaced. If it can't, we fallback to
1002 to tell us if a file has been replaced. If it can't, we fallback to
1000 recreating the object on every call (essentially the same behavior as
1003 recreating the object on every call (essentially the same behavior as
1001 propertycache).
1004 propertycache).
1002
1005
1003 '''
1006 '''
1004 def __init__(self, *paths):
1007 def __init__(self, *paths):
1005 self.paths = paths
1008 self.paths = paths
1006
1009
1007 def join(self, obj, fname):
1010 def join(self, obj, fname):
1008 """Used to compute the runtime path of a cached file.
1011 """Used to compute the runtime path of a cached file.
1009
1012
1010 Users should subclass filecache and provide their own version of this
1013 Users should subclass filecache and provide their own version of this
1011 function to call the appropriate join function on 'obj' (an instance
1014 function to call the appropriate join function on 'obj' (an instance
1012 of the class that its member function was decorated).
1015 of the class that its member function was decorated).
1013 """
1016 """
1014 raise NotImplementedError
1017 raise NotImplementedError
1015
1018
1016 def __call__(self, func):
1019 def __call__(self, func):
1017 self.func = func
1020 self.func = func
1018 self.name = func.__name__.encode('ascii')
1021 self.name = func.__name__.encode('ascii')
1019 return self
1022 return self
1020
1023
1021 def __get__(self, obj, type=None):
1024 def __get__(self, obj, type=None):
1022 # if accessed on the class, return the descriptor itself.
1025 # if accessed on the class, return the descriptor itself.
1023 if obj is None:
1026 if obj is None:
1024 return self
1027 return self
1025 # do we need to check if the file changed?
1028 # do we need to check if the file changed?
1026 if self.name in obj.__dict__:
1029 if self.name in obj.__dict__:
1027 assert self.name in obj._filecache, self.name
1030 assert self.name in obj._filecache, self.name
1028 return obj.__dict__[self.name]
1031 return obj.__dict__[self.name]
1029
1032
1030 entry = obj._filecache.get(self.name)
1033 entry = obj._filecache.get(self.name)
1031
1034
1032 if entry:
1035 if entry:
1033 if entry.changed():
1036 if entry.changed():
1034 entry.obj = self.func(obj)
1037 entry.obj = self.func(obj)
1035 else:
1038 else:
1036 paths = [self.join(obj, path) for path in self.paths]
1039 paths = [self.join(obj, path) for path in self.paths]
1037
1040
1038 # We stat -before- creating the object so our cache doesn't lie if
1041 # We stat -before- creating the object so our cache doesn't lie if
1039 # a writer modified between the time we read and stat
1042 # a writer modified between the time we read and stat
1040 entry = filecacheentry(paths, True)
1043 entry = filecacheentry(paths, True)
1041 entry.obj = self.func(obj)
1044 entry.obj = self.func(obj)
1042
1045
1043 obj._filecache[self.name] = entry
1046 obj._filecache[self.name] = entry
1044
1047
1045 obj.__dict__[self.name] = entry.obj
1048 obj.__dict__[self.name] = entry.obj
1046 return entry.obj
1049 return entry.obj
1047
1050
1048 def __set__(self, obj, value):
1051 def __set__(self, obj, value):
1049 if self.name not in obj._filecache:
1052 if self.name not in obj._filecache:
1050 # we add an entry for the missing value because X in __dict__
1053 # we add an entry for the missing value because X in __dict__
1051 # implies X in _filecache
1054 # implies X in _filecache
1052 paths = [self.join(obj, path) for path in self.paths]
1055 paths = [self.join(obj, path) for path in self.paths]
1053 ce = filecacheentry(paths, False)
1056 ce = filecacheentry(paths, False)
1054 obj._filecache[self.name] = ce
1057 obj._filecache[self.name] = ce
1055 else:
1058 else:
1056 ce = obj._filecache[self.name]
1059 ce = obj._filecache[self.name]
1057
1060
1058 ce.obj = value # update cached copy
1061 ce.obj = value # update cached copy
1059 obj.__dict__[self.name] = value # update copy returned by obj.x
1062 obj.__dict__[self.name] = value # update copy returned by obj.x
1060
1063
1061 def __delete__(self, obj):
1064 def __delete__(self, obj):
1062 try:
1065 try:
1063 del obj.__dict__[self.name]
1066 del obj.__dict__[self.name]
1064 except KeyError:
1067 except KeyError:
1065 raise AttributeError(self.name)
1068 raise AttributeError(self.name)
1066
1069
1067 def extdatasource(repo, source):
1070 def extdatasource(repo, source):
1068 """Gather a map of rev -> value dict from the specified source
1071 """Gather a map of rev -> value dict from the specified source
1069
1072
1070 A source spec is treated as a URL, with a special case shell: type
1073 A source spec is treated as a URL, with a special case shell: type
1071 for parsing the output from a shell command.
1074 for parsing the output from a shell command.
1072
1075
1073 The data is parsed as a series of newline-separated records where
1076 The data is parsed as a series of newline-separated records where
1074 each record is a revision specifier optionally followed by a space
1077 each record is a revision specifier optionally followed by a space
1075 and a freeform string value. If the revision is known locally, it
1078 and a freeform string value. If the revision is known locally, it
1076 is converted to a rev, otherwise the record is skipped.
1079 is converted to a rev, otherwise the record is skipped.
1077
1080
1078 Note that both key and value are treated as UTF-8 and converted to
1081 Note that both key and value are treated as UTF-8 and converted to
1079 the local encoding. This allows uniformity between local and
1082 the local encoding. This allows uniformity between local and
1080 remote data sources.
1083 remote data sources.
1081 """
1084 """
1082
1085
1083 spec = repo.ui.config("extdata", source)
1086 spec = repo.ui.config("extdata", source)
1084 if not spec:
1087 if not spec:
1085 raise error.Abort(_("unknown extdata source '%s'") % source)
1088 raise error.Abort(_("unknown extdata source '%s'") % source)
1086
1089
1087 data = {}
1090 data = {}
1088 src = proc = None
1091 src = proc = None
1089 try:
1092 try:
1090 if spec.startswith("shell:"):
1093 if spec.startswith("shell:"):
1091 # external commands should be run relative to the repo root
1094 # external commands should be run relative to the repo root
1092 cmd = spec[6:]
1095 cmd = spec[6:]
1093 proc = subprocess.Popen(cmd, shell=True, bufsize=-1,
1096 proc = subprocess.Popen(cmd, shell=True, bufsize=-1,
1094 close_fds=procutil.closefds,
1097 close_fds=procutil.closefds,
1095 stdout=subprocess.PIPE, cwd=repo.root)
1098 stdout=subprocess.PIPE, cwd=repo.root)
1096 src = proc.stdout
1099 src = proc.stdout
1097 else:
1100 else:
1098 # treat as a URL or file
1101 # treat as a URL or file
1099 src = url.open(repo.ui, spec)
1102 src = url.open(repo.ui, spec)
1100 for l in src:
1103 for l in src:
1101 if " " in l:
1104 if " " in l:
1102 k, v = l.strip().split(" ", 1)
1105 k, v = l.strip().split(" ", 1)
1103 else:
1106 else:
1104 k, v = l.strip(), ""
1107 k, v = l.strip(), ""
1105
1108
1106 k = encoding.tolocal(k)
1109 k = encoding.tolocal(k)
1107 try:
1110 try:
1108 data[repo[k].rev()] = encoding.tolocal(v)
1111 data[repo[k].rev()] = encoding.tolocal(v)
1109 except (error.LookupError, error.RepoLookupError):
1112 except (error.LookupError, error.RepoLookupError):
1110 pass # we ignore data for nodes that don't exist locally
1113 pass # we ignore data for nodes that don't exist locally
1111 finally:
1114 finally:
1112 if proc:
1115 if proc:
1113 proc.communicate()
1116 proc.communicate()
1114 if src:
1117 if src:
1115 src.close()
1118 src.close()
1116 if proc and proc.returncode != 0:
1119 if proc and proc.returncode != 0:
1117 raise error.Abort(_("extdata command '%s' failed: %s")
1120 raise error.Abort(_("extdata command '%s' failed: %s")
1118 % (cmd, procutil.explainexit(proc.returncode)[0]))
1121 % (cmd, procutil.explainexit(proc.returncode)[0]))
1119
1122
1120 return data
1123 return data
1121
1124
1122 def _locksub(repo, lock, envvar, cmd, environ=None, *args, **kwargs):
1125 def _locksub(repo, lock, envvar, cmd, environ=None, *args, **kwargs):
1123 if lock is None:
1126 if lock is None:
1124 raise error.LockInheritanceContractViolation(
1127 raise error.LockInheritanceContractViolation(
1125 'lock can only be inherited while held')
1128 'lock can only be inherited while held')
1126 if environ is None:
1129 if environ is None:
1127 environ = {}
1130 environ = {}
1128 with lock.inherit() as locker:
1131 with lock.inherit() as locker:
1129 environ[envvar] = locker
1132 environ[envvar] = locker
1130 return repo.ui.system(cmd, environ=environ, *args, **kwargs)
1133 return repo.ui.system(cmd, environ=environ, *args, **kwargs)
1131
1134
1132 def wlocksub(repo, cmd, *args, **kwargs):
1135 def wlocksub(repo, cmd, *args, **kwargs):
1133 """run cmd as a subprocess that allows inheriting repo's wlock
1136 """run cmd as a subprocess that allows inheriting repo's wlock
1134
1137
1135 This can only be called while the wlock is held. This takes all the
1138 This can only be called while the wlock is held. This takes all the
1136 arguments that ui.system does, and returns the exit code of the
1139 arguments that ui.system does, and returns the exit code of the
1137 subprocess."""
1140 subprocess."""
1138 return _locksub(repo, repo.currentwlock(), 'HG_WLOCK_LOCKER', cmd, *args,
1141 return _locksub(repo, repo.currentwlock(), 'HG_WLOCK_LOCKER', cmd, *args,
1139 **kwargs)
1142 **kwargs)
1140
1143
1141 def gdinitconfig(ui):
1144 def gdinitconfig(ui):
1142 """helper function to know if a repo should be created as general delta
1145 """helper function to know if a repo should be created as general delta
1143 """
1146 """
1144 # experimental config: format.generaldelta
1147 # experimental config: format.generaldelta
1145 return (ui.configbool('format', 'generaldelta')
1148 return (ui.configbool('format', 'generaldelta')
1146 or ui.configbool('format', 'usegeneraldelta'))
1149 or ui.configbool('format', 'usegeneraldelta'))
1147
1150
1148 def gddeltaconfig(ui):
1151 def gddeltaconfig(ui):
1149 """helper function to know if incoming delta should be optimised
1152 """helper function to know if incoming delta should be optimised
1150 """
1153 """
1151 # experimental config: format.generaldelta
1154 # experimental config: format.generaldelta
1152 return ui.configbool('format', 'generaldelta')
1155 return ui.configbool('format', 'generaldelta')
1153
1156
1154 class simplekeyvaluefile(object):
1157 class simplekeyvaluefile(object):
1155 """A simple file with key=value lines
1158 """A simple file with key=value lines
1156
1159
1157 Keys must be alphanumerics and start with a letter, values must not
1160 Keys must be alphanumerics and start with a letter, values must not
1158 contain '\n' characters"""
1161 contain '\n' characters"""
1159 firstlinekey = '__firstline'
1162 firstlinekey = '__firstline'
1160
1163
1161 def __init__(self, vfs, path, keys=None):
1164 def __init__(self, vfs, path, keys=None):
1162 self.vfs = vfs
1165 self.vfs = vfs
1163 self.path = path
1166 self.path = path
1164
1167
1165 def read(self, firstlinenonkeyval=False):
1168 def read(self, firstlinenonkeyval=False):
1166 """Read the contents of a simple key-value file
1169 """Read the contents of a simple key-value file
1167
1170
1168 'firstlinenonkeyval' indicates whether the first line of file should
1171 'firstlinenonkeyval' indicates whether the first line of file should
1169 be treated as a key-value pair or reuturned fully under the
1172 be treated as a key-value pair or reuturned fully under the
1170 __firstline key."""
1173 __firstline key."""
1171 lines = self.vfs.readlines(self.path)
1174 lines = self.vfs.readlines(self.path)
1172 d = {}
1175 d = {}
1173 if firstlinenonkeyval:
1176 if firstlinenonkeyval:
1174 if not lines:
1177 if not lines:
1175 e = _("empty simplekeyvalue file")
1178 e = _("empty simplekeyvalue file")
1176 raise error.CorruptedState(e)
1179 raise error.CorruptedState(e)
1177 # we don't want to include '\n' in the __firstline
1180 # we don't want to include '\n' in the __firstline
1178 d[self.firstlinekey] = lines[0][:-1]
1181 d[self.firstlinekey] = lines[0][:-1]
1179 del lines[0]
1182 del lines[0]
1180
1183
1181 try:
1184 try:
1182 # the 'if line.strip()' part prevents us from failing on empty
1185 # the 'if line.strip()' part prevents us from failing on empty
1183 # lines which only contain '\n' therefore are not skipped
1186 # lines which only contain '\n' therefore are not skipped
1184 # by 'if line'
1187 # by 'if line'
1185 updatedict = dict(line[:-1].split('=', 1) for line in lines
1188 updatedict = dict(line[:-1].split('=', 1) for line in lines
1186 if line.strip())
1189 if line.strip())
1187 if self.firstlinekey in updatedict:
1190 if self.firstlinekey in updatedict:
1188 e = _("%r can't be used as a key")
1191 e = _("%r can't be used as a key")
1189 raise error.CorruptedState(e % self.firstlinekey)
1192 raise error.CorruptedState(e % self.firstlinekey)
1190 d.update(updatedict)
1193 d.update(updatedict)
1191 except ValueError as e:
1194 except ValueError as e:
1192 raise error.CorruptedState(str(e))
1195 raise error.CorruptedState(str(e))
1193 return d
1196 return d
1194
1197
1195 def write(self, data, firstline=None):
1198 def write(self, data, firstline=None):
1196 """Write key=>value mapping to a file
1199 """Write key=>value mapping to a file
1197 data is a dict. Keys must be alphanumerical and start with a letter.
1200 data is a dict. Keys must be alphanumerical and start with a letter.
1198 Values must not contain newline characters.
1201 Values must not contain newline characters.
1199
1202
1200 If 'firstline' is not None, it is written to file before
1203 If 'firstline' is not None, it is written to file before
1201 everything else, as it is, not in a key=value form"""
1204 everything else, as it is, not in a key=value form"""
1202 lines = []
1205 lines = []
1203 if firstline is not None:
1206 if firstline is not None:
1204 lines.append('%s\n' % firstline)
1207 lines.append('%s\n' % firstline)
1205
1208
1206 for k, v in data.items():
1209 for k, v in data.items():
1207 if k == self.firstlinekey:
1210 if k == self.firstlinekey:
1208 e = "key name '%s' is reserved" % self.firstlinekey
1211 e = "key name '%s' is reserved" % self.firstlinekey
1209 raise error.ProgrammingError(e)
1212 raise error.ProgrammingError(e)
1210 if not k[0:1].isalpha():
1213 if not k[0:1].isalpha():
1211 e = "keys must start with a letter in a key-value file"
1214 e = "keys must start with a letter in a key-value file"
1212 raise error.ProgrammingError(e)
1215 raise error.ProgrammingError(e)
1213 if not k.isalnum():
1216 if not k.isalnum():
1214 e = "invalid key name in a simple key-value file"
1217 e = "invalid key name in a simple key-value file"
1215 raise error.ProgrammingError(e)
1218 raise error.ProgrammingError(e)
1216 if '\n' in v:
1219 if '\n' in v:
1217 e = "invalid value in a simple key-value file"
1220 e = "invalid value in a simple key-value file"
1218 raise error.ProgrammingError(e)
1221 raise error.ProgrammingError(e)
1219 lines.append("%s=%s\n" % (k, v))
1222 lines.append("%s=%s\n" % (k, v))
1220 with self.vfs(self.path, mode='wb', atomictemp=True) as fp:
1223 with self.vfs(self.path, mode='wb', atomictemp=True) as fp:
1221 fp.write(''.join(lines))
1224 fp.write(''.join(lines))
1222
1225
1223 _reportobsoletedsource = [
1226 _reportobsoletedsource = [
1224 'debugobsolete',
1227 'debugobsolete',
1225 'pull',
1228 'pull',
1226 'push',
1229 'push',
1227 'serve',
1230 'serve',
1228 'unbundle',
1231 'unbundle',
1229 ]
1232 ]
1230
1233
1231 _reportnewcssource = [
1234 _reportnewcssource = [
1232 'pull',
1235 'pull',
1233 'unbundle',
1236 'unbundle',
1234 ]
1237 ]
1235
1238
1236 # a list of (repo, ctx, files) functions called by various commands to allow
1239 # a list of (repo, ctx, files) functions called by various commands to allow
1237 # extensions to ensure the corresponding files are available locally, before the
1240 # extensions to ensure the corresponding files are available locally, before the
1238 # command uses them.
1241 # command uses them.
1239 fileprefetchhooks = util.hooks()
1242 fileprefetchhooks = util.hooks()
1240
1243
1241 # A marker that tells the evolve extension to suppress its own reporting
1244 # A marker that tells the evolve extension to suppress its own reporting
1242 _reportstroubledchangesets = True
1245 _reportstroubledchangesets = True
1243
1246
1244 def registersummarycallback(repo, otr, txnname=''):
1247 def registersummarycallback(repo, otr, txnname=''):
1245 """register a callback to issue a summary after the transaction is closed
1248 """register a callback to issue a summary after the transaction is closed
1246 """
1249 """
1247 def txmatch(sources):
1250 def txmatch(sources):
1248 return any(txnname.startswith(source) for source in sources)
1251 return any(txnname.startswith(source) for source in sources)
1249
1252
1250 categories = []
1253 categories = []
1251
1254
1252 def reportsummary(func):
1255 def reportsummary(func):
1253 """decorator for report callbacks."""
1256 """decorator for report callbacks."""
1254 # The repoview life cycle is shorter than the one of the actual
1257 # The repoview life cycle is shorter than the one of the actual
1255 # underlying repository. So the filtered object can die before the
1258 # underlying repository. So the filtered object can die before the
1256 # weakref is used leading to troubles. We keep a reference to the
1259 # weakref is used leading to troubles. We keep a reference to the
1257 # unfiltered object and restore the filtering when retrieving the
1260 # unfiltered object and restore the filtering when retrieving the
1258 # repository through the weakref.
1261 # repository through the weakref.
1259 filtername = repo.filtername
1262 filtername = repo.filtername
1260 reporef = weakref.ref(repo.unfiltered())
1263 reporef = weakref.ref(repo.unfiltered())
1261 def wrapped(tr):
1264 def wrapped(tr):
1262 repo = reporef()
1265 repo = reporef()
1263 if filtername:
1266 if filtername:
1264 repo = repo.filtered(filtername)
1267 repo = repo.filtered(filtername)
1265 func(repo, tr)
1268 func(repo, tr)
1266 newcat = '%02i-txnreport' % len(categories)
1269 newcat = '%02i-txnreport' % len(categories)
1267 otr.addpostclose(newcat, wrapped)
1270 otr.addpostclose(newcat, wrapped)
1268 categories.append(newcat)
1271 categories.append(newcat)
1269 return wrapped
1272 return wrapped
1270
1273
1271 if txmatch(_reportobsoletedsource):
1274 if txmatch(_reportobsoletedsource):
1272 @reportsummary
1275 @reportsummary
1273 def reportobsoleted(repo, tr):
1276 def reportobsoleted(repo, tr):
1274 obsoleted = obsutil.getobsoleted(repo, tr)
1277 obsoleted = obsutil.getobsoleted(repo, tr)
1275 if obsoleted:
1278 if obsoleted:
1276 repo.ui.status(_('obsoleted %i changesets\n')
1279 repo.ui.status(_('obsoleted %i changesets\n')
1277 % len(obsoleted))
1280 % len(obsoleted))
1278
1281
1279 if (obsolete.isenabled(repo, obsolete.createmarkersopt) and
1282 if (obsolete.isenabled(repo, obsolete.createmarkersopt) and
1280 repo.ui.configbool('experimental', 'evolution.report-instabilities')):
1283 repo.ui.configbool('experimental', 'evolution.report-instabilities')):
1281 instabilitytypes = [
1284 instabilitytypes = [
1282 ('orphan', 'orphan'),
1285 ('orphan', 'orphan'),
1283 ('phase-divergent', 'phasedivergent'),
1286 ('phase-divergent', 'phasedivergent'),
1284 ('content-divergent', 'contentdivergent'),
1287 ('content-divergent', 'contentdivergent'),
1285 ]
1288 ]
1286
1289
1287 def getinstabilitycounts(repo):
1290 def getinstabilitycounts(repo):
1288 filtered = repo.changelog.filteredrevs
1291 filtered = repo.changelog.filteredrevs
1289 counts = {}
1292 counts = {}
1290 for instability, revset in instabilitytypes:
1293 for instability, revset in instabilitytypes:
1291 counts[instability] = len(set(obsolete.getrevs(repo, revset)) -
1294 counts[instability] = len(set(obsolete.getrevs(repo, revset)) -
1292 filtered)
1295 filtered)
1293 return counts
1296 return counts
1294
1297
1295 oldinstabilitycounts = getinstabilitycounts(repo)
1298 oldinstabilitycounts = getinstabilitycounts(repo)
1296 @reportsummary
1299 @reportsummary
1297 def reportnewinstabilities(repo, tr):
1300 def reportnewinstabilities(repo, tr):
1298 newinstabilitycounts = getinstabilitycounts(repo)
1301 newinstabilitycounts = getinstabilitycounts(repo)
1299 for instability, revset in instabilitytypes:
1302 for instability, revset in instabilitytypes:
1300 delta = (newinstabilitycounts[instability] -
1303 delta = (newinstabilitycounts[instability] -
1301 oldinstabilitycounts[instability])
1304 oldinstabilitycounts[instability])
1302 if delta > 0:
1305 if delta > 0:
1303 repo.ui.warn(_('%i new %s changesets\n') %
1306 repo.ui.warn(_('%i new %s changesets\n') %
1304 (delta, instability))
1307 (delta, instability))
1305
1308
1306 if txmatch(_reportnewcssource):
1309 if txmatch(_reportnewcssource):
1307 @reportsummary
1310 @reportsummary
1308 def reportnewcs(repo, tr):
1311 def reportnewcs(repo, tr):
1309 """Report the range of new revisions pulled/unbundled."""
1312 """Report the range of new revisions pulled/unbundled."""
1310 newrevs = tr.changes.get('revs', xrange(0, 0))
1313 newrevs = tr.changes.get('revs', xrange(0, 0))
1311 if not newrevs:
1314 if not newrevs:
1312 return
1315 return
1313
1316
1314 # Compute the bounds of new revisions' range, excluding obsoletes.
1317 # Compute the bounds of new revisions' range, excluding obsoletes.
1315 unfi = repo.unfiltered()
1318 unfi = repo.unfiltered()
1316 revs = unfi.revs('%ld and not obsolete()', newrevs)
1319 revs = unfi.revs('%ld and not obsolete()', newrevs)
1317 if not revs:
1320 if not revs:
1318 # Got only obsoletes.
1321 # Got only obsoletes.
1319 return
1322 return
1320 minrev, maxrev = repo[revs.min()], repo[revs.max()]
1323 minrev, maxrev = repo[revs.min()], repo[revs.max()]
1321
1324
1322 if minrev == maxrev:
1325 if minrev == maxrev:
1323 revrange = minrev
1326 revrange = minrev
1324 else:
1327 else:
1325 revrange = '%s:%s' % (minrev, maxrev)
1328 revrange = '%s:%s' % (minrev, maxrev)
1326 repo.ui.status(_('new changesets %s\n') % revrange)
1329 repo.ui.status(_('new changesets %s\n') % revrange)
1327
1330
1328 def nodesummaries(repo, nodes, maxnumnodes=4):
1331 def nodesummaries(repo, nodes, maxnumnodes=4):
1329 if len(nodes) <= maxnumnodes or repo.ui.verbose:
1332 if len(nodes) <= maxnumnodes or repo.ui.verbose:
1330 return ' '.join(short(h) for h in nodes)
1333 return ' '.join(short(h) for h in nodes)
1331 first = ' '.join(short(h) for h in nodes[:maxnumnodes])
1334 first = ' '.join(short(h) for h in nodes[:maxnumnodes])
1332 return _("%s and %d others") % (first, len(nodes) - maxnumnodes)
1335 return _("%s and %d others") % (first, len(nodes) - maxnumnodes)
1333
1336
1334 def enforcesinglehead(repo, tr, desc):
1337 def enforcesinglehead(repo, tr, desc):
1335 """check that no named branch has multiple heads"""
1338 """check that no named branch has multiple heads"""
1336 if desc in ('strip', 'repair'):
1339 if desc in ('strip', 'repair'):
1337 # skip the logic during strip
1340 # skip the logic during strip
1338 return
1341 return
1339 visible = repo.filtered('visible')
1342 visible = repo.filtered('visible')
1340 # possible improvement: we could restrict the check to affected branch
1343 # possible improvement: we could restrict the check to affected branch
1341 for name, heads in visible.branchmap().iteritems():
1344 for name, heads in visible.branchmap().iteritems():
1342 if len(heads) > 1:
1345 if len(heads) > 1:
1343 msg = _('rejecting multiple heads on branch "%s"')
1346 msg = _('rejecting multiple heads on branch "%s"')
1344 msg %= name
1347 msg %= name
1345 hint = _('%d heads: %s')
1348 hint = _('%d heads: %s')
1346 hint %= (len(heads), nodesummaries(repo, heads))
1349 hint %= (len(heads), nodesummaries(repo, heads))
1347 raise error.Abort(msg, hint=hint)
1350 raise error.Abort(msg, hint=hint)
1348
1351
1349 def wrapconvertsink(sink):
1352 def wrapconvertsink(sink):
1350 """Allow extensions to wrap the sink returned by convcmd.convertsink()
1353 """Allow extensions to wrap the sink returned by convcmd.convertsink()
1351 before it is used, whether or not the convert extension was formally loaded.
1354 before it is used, whether or not the convert extension was formally loaded.
1352 """
1355 """
1353 return sink
1356 return sink
1354
1357
1355 def unhidehashlikerevs(repo, specs, hiddentype):
1358 def unhidehashlikerevs(repo, specs, hiddentype):
1356 """parse the user specs and unhide changesets whose hash or revision number
1359 """parse the user specs and unhide changesets whose hash or revision number
1357 is passed.
1360 is passed.
1358
1361
1359 hiddentype can be: 1) 'warn': warn while unhiding changesets
1362 hiddentype can be: 1) 'warn': warn while unhiding changesets
1360 2) 'nowarn': don't warn while unhiding changesets
1363 2) 'nowarn': don't warn while unhiding changesets
1361
1364
1362 returns a repo object with the required changesets unhidden
1365 returns a repo object with the required changesets unhidden
1363 """
1366 """
1364 if not repo.filtername or not repo.ui.configbool('experimental',
1367 if not repo.filtername or not repo.ui.configbool('experimental',
1365 'directaccess'):
1368 'directaccess'):
1366 return repo
1369 return repo
1367
1370
1368 if repo.filtername not in ('visible', 'visible-hidden'):
1371 if repo.filtername not in ('visible', 'visible-hidden'):
1369 return repo
1372 return repo
1370
1373
1371 symbols = set()
1374 symbols = set()
1372 for spec in specs:
1375 for spec in specs:
1373 try:
1376 try:
1374 tree = revsetlang.parse(spec)
1377 tree = revsetlang.parse(spec)
1375 except error.ParseError: # will be reported by scmutil.revrange()
1378 except error.ParseError: # will be reported by scmutil.revrange()
1376 continue
1379 continue
1377
1380
1378 symbols.update(revsetlang.gethashlikesymbols(tree))
1381 symbols.update(revsetlang.gethashlikesymbols(tree))
1379
1382
1380 if not symbols:
1383 if not symbols:
1381 return repo
1384 return repo
1382
1385
1383 revs = _getrevsfromsymbols(repo, symbols)
1386 revs = _getrevsfromsymbols(repo, symbols)
1384
1387
1385 if not revs:
1388 if not revs:
1386 return repo
1389 return repo
1387
1390
1388 if hiddentype == 'warn':
1391 if hiddentype == 'warn':
1389 unfi = repo.unfiltered()
1392 unfi = repo.unfiltered()
1390 revstr = ", ".join([pycompat.bytestr(unfi[l]) for l in revs])
1393 revstr = ", ".join([pycompat.bytestr(unfi[l]) for l in revs])
1391 repo.ui.warn(_("warning: accessing hidden changesets for write "
1394 repo.ui.warn(_("warning: accessing hidden changesets for write "
1392 "operation: %s\n") % revstr)
1395 "operation: %s\n") % revstr)
1393
1396
1394 # we have to use new filtername to separate branch/tags cache until we can
1397 # we have to use new filtername to separate branch/tags cache until we can
1395 # disbale these cache when revisions are dynamically pinned.
1398 # disbale these cache when revisions are dynamically pinned.
1396 return repo.filtered('visible-hidden', revs)
1399 return repo.filtered('visible-hidden', revs)
1397
1400
1398 def _getrevsfromsymbols(repo, symbols):
1401 def _getrevsfromsymbols(repo, symbols):
1399 """parse the list of symbols and returns a set of revision numbers of hidden
1402 """parse the list of symbols and returns a set of revision numbers of hidden
1400 changesets present in symbols"""
1403 changesets present in symbols"""
1401 revs = set()
1404 revs = set()
1402 unfi = repo.unfiltered()
1405 unfi = repo.unfiltered()
1403 unficl = unfi.changelog
1406 unficl = unfi.changelog
1404 cl = repo.changelog
1407 cl = repo.changelog
1405 tiprev = len(unficl)
1408 tiprev = len(unficl)
1406 pmatch = unficl._partialmatch
1409 pmatch = unficl._partialmatch
1407 allowrevnums = repo.ui.configbool('experimental', 'directaccess.revnums')
1410 allowrevnums = repo.ui.configbool('experimental', 'directaccess.revnums')
1408 for s in symbols:
1411 for s in symbols:
1409 try:
1412 try:
1410 n = int(s)
1413 n = int(s)
1411 if n <= tiprev:
1414 if n <= tiprev:
1412 if not allowrevnums:
1415 if not allowrevnums:
1413 continue
1416 continue
1414 else:
1417 else:
1415 if n not in cl:
1418 if n not in cl:
1416 revs.add(n)
1419 revs.add(n)
1417 continue
1420 continue
1418 except ValueError:
1421 except ValueError:
1419 pass
1422 pass
1420
1423
1421 try:
1424 try:
1422 s = pmatch(s)
1425 s = pmatch(s)
1423 except (error.LookupError, error.WdirUnsupported):
1426 except (error.LookupError, error.WdirUnsupported):
1424 s = None
1427 s = None
1425
1428
1426 if s is not None:
1429 if s is not None:
1427 rev = unficl.rev(s)
1430 rev = unficl.rev(s)
1428 if rev not in cl:
1431 if rev not in cl:
1429 revs.add(rev)
1432 revs.add(rev)
1430
1433
1431 return revs
1434 return revs
@@ -1,50 +1,50 b''
1 # Extension dedicated to test patch.diff() upgrade modes
1 # Extension dedicated to test patch.diff() upgrade modes
2
2
3 from __future__ import absolute_import
3 from __future__ import absolute_import
4
4
5 from mercurial import (
5 from mercurial import (
6 error,
6 error,
7 patch,
7 patch,
8 registrar,
8 registrar,
9 scmutil,
9 scmutil,
10 )
10 )
11
11
12 cmdtable = {}
12 cmdtable = {}
13 command = registrar.command(cmdtable)
13 command = registrar.command(cmdtable)
14
14
15 @command(b'autodiff',
15 @command(b'autodiff',
16 [(b'', b'git', b'', b'git upgrade mode (yes/no/auto/warn/abort)')],
16 [(b'', b'git', b'', b'git upgrade mode (yes/no/auto/warn/abort)')],
17 b'[OPTION]... [FILE]...')
17 b'[OPTION]... [FILE]...')
18 def autodiff(ui, repo, *pats, **opts):
18 def autodiff(ui, repo, *pats, **opts):
19 diffopts = patch.difffeatureopts(ui, opts)
19 diffopts = patch.difffeatureopts(ui, opts)
20 git = opts.get(b'git', b'no')
20 git = opts.get(b'git', b'no')
21 brokenfiles = set()
21 brokenfiles = set()
22 losedatafn = None
22 losedatafn = None
23 if git in (b'yes', b'no'):
23 if git in (b'yes', b'no'):
24 diffopts.git = git == b'yes'
24 diffopts.git = git == b'yes'
25 diffopts.upgrade = False
25 diffopts.upgrade = False
26 elif git == b'auto':
26 elif git == b'auto':
27 diffopts.git = False
27 diffopts.git = False
28 diffopts.upgrade = True
28 diffopts.upgrade = True
29 elif git == b'warn':
29 elif git == b'warn':
30 diffopts.git = False
30 diffopts.git = False
31 diffopts.upgrade = True
31 diffopts.upgrade = True
32 def losedatafn(fn=None, **kwargs):
32 def losedatafn(fn=None, **kwargs):
33 brokenfiles.add(fn)
33 brokenfiles.add(fn)
34 return True
34 return True
35 elif git == b'abort':
35 elif git == b'abort':
36 diffopts.git = False
36 diffopts.git = False
37 diffopts.upgrade = True
37 diffopts.upgrade = True
38 def losedatafn(fn=None, **kwargs):
38 def losedatafn(fn=None, **kwargs):
39 raise error.Abort(b'losing data for %s' % fn)
39 raise error.Abort(b'losing data for %s' % fn)
40 else:
40 else:
41 raise error.Abort(b'--git must be yes, no or auto')
41 raise error.Abort(b'--git must be yes, no or auto')
42
42
43 node1, node2 = scmutil.revpair(repo, [])
43 node1, node2 = scmutil.revpairnodes(repo, [])
44 m = scmutil.match(repo[node2], pats, opts)
44 m = scmutil.match(repo[node2], pats, opts)
45 it = patch.diff(repo, node1, node2, match=m, opts=diffopts,
45 it = patch.diff(repo, node1, node2, match=m, opts=diffopts,
46 losedatafn=losedatafn)
46 losedatafn=losedatafn)
47 for chunk in it:
47 for chunk in it:
48 ui.write(chunk)
48 ui.write(chunk)
49 for fn in sorted(brokenfiles):
49 for fn in sorted(brokenfiles):
50 ui.write((b'data lost for: %s\n' % fn))
50 ui.write((b'data lost for: %s\n' % fn))
General Comments 0
You need to be logged in to leave comments. Login now