##// END OF EJS Templates
subrepo: use root-repo-relative path from `hg files` with ui.relative-paths=no...
Martin von Zweigbergk -
r41898:4fecf4f6 default draft
parent child Browse files
Show More
@@ -1,3351 +1,3352
1 # cmdutil.py - help for command processing in mercurial
1 # cmdutil.py - help for command processing in mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import errno
10 import errno
11 import os
11 import os
12 import re
12 import re
13
13
14 from .i18n import _
14 from .i18n import _
15 from .node import (
15 from .node import (
16 hex,
16 hex,
17 nullid,
17 nullid,
18 nullrev,
18 nullrev,
19 short,
19 short,
20 )
20 )
21
21
22 from . import (
22 from . import (
23 bookmarks,
23 bookmarks,
24 changelog,
24 changelog,
25 copies,
25 copies,
26 crecord as crecordmod,
26 crecord as crecordmod,
27 dirstateguard,
27 dirstateguard,
28 encoding,
28 encoding,
29 error,
29 error,
30 formatter,
30 formatter,
31 logcmdutil,
31 logcmdutil,
32 match as matchmod,
32 match as matchmod,
33 merge as mergemod,
33 merge as mergemod,
34 mergeutil,
34 mergeutil,
35 obsolete,
35 obsolete,
36 patch,
36 patch,
37 pathutil,
37 pathutil,
38 phases,
38 phases,
39 pycompat,
39 pycompat,
40 revlog,
40 revlog,
41 rewriteutil,
41 rewriteutil,
42 scmutil,
42 scmutil,
43 smartset,
43 smartset,
44 subrepoutil,
44 subrepoutil,
45 templatekw,
45 templatekw,
46 templater,
46 templater,
47 util,
47 util,
48 vfs as vfsmod,
48 vfs as vfsmod,
49 )
49 )
50
50
51 from .utils import (
51 from .utils import (
52 dateutil,
52 dateutil,
53 stringutil,
53 stringutil,
54 )
54 )
55
55
56 stringio = util.stringio
56 stringio = util.stringio
57
57
58 # templates of common command options
58 # templates of common command options
59
59
60 dryrunopts = [
60 dryrunopts = [
61 ('n', 'dry-run', None,
61 ('n', 'dry-run', None,
62 _('do not perform actions, just print output')),
62 _('do not perform actions, just print output')),
63 ]
63 ]
64
64
65 confirmopts = [
65 confirmopts = [
66 ('', 'confirm', None,
66 ('', 'confirm', None,
67 _('ask before applying actions')),
67 _('ask before applying actions')),
68 ]
68 ]
69
69
70 remoteopts = [
70 remoteopts = [
71 ('e', 'ssh', '',
71 ('e', 'ssh', '',
72 _('specify ssh command to use'), _('CMD')),
72 _('specify ssh command to use'), _('CMD')),
73 ('', 'remotecmd', '',
73 ('', 'remotecmd', '',
74 _('specify hg command to run on the remote side'), _('CMD')),
74 _('specify hg command to run on the remote side'), _('CMD')),
75 ('', 'insecure', None,
75 ('', 'insecure', None,
76 _('do not verify server certificate (ignoring web.cacerts config)')),
76 _('do not verify server certificate (ignoring web.cacerts config)')),
77 ]
77 ]
78
78
79 walkopts = [
79 walkopts = [
80 ('I', 'include', [],
80 ('I', 'include', [],
81 _('include names matching the given patterns'), _('PATTERN')),
81 _('include names matching the given patterns'), _('PATTERN')),
82 ('X', 'exclude', [],
82 ('X', 'exclude', [],
83 _('exclude names matching the given patterns'), _('PATTERN')),
83 _('exclude names matching the given patterns'), _('PATTERN')),
84 ]
84 ]
85
85
86 commitopts = [
86 commitopts = [
87 ('m', 'message', '',
87 ('m', 'message', '',
88 _('use text as commit message'), _('TEXT')),
88 _('use text as commit message'), _('TEXT')),
89 ('l', 'logfile', '',
89 ('l', 'logfile', '',
90 _('read commit message from file'), _('FILE')),
90 _('read commit message from file'), _('FILE')),
91 ]
91 ]
92
92
93 commitopts2 = [
93 commitopts2 = [
94 ('d', 'date', '',
94 ('d', 'date', '',
95 _('record the specified date as commit date'), _('DATE')),
95 _('record the specified date as commit date'), _('DATE')),
96 ('u', 'user', '',
96 ('u', 'user', '',
97 _('record the specified user as committer'), _('USER')),
97 _('record the specified user as committer'), _('USER')),
98 ]
98 ]
99
99
100 formatteropts = [
100 formatteropts = [
101 ('T', 'template', '',
101 ('T', 'template', '',
102 _('display with template'), _('TEMPLATE')),
102 _('display with template'), _('TEMPLATE')),
103 ]
103 ]
104
104
105 templateopts = [
105 templateopts = [
106 ('', 'style', '',
106 ('', 'style', '',
107 _('display using template map file (DEPRECATED)'), _('STYLE')),
107 _('display using template map file (DEPRECATED)'), _('STYLE')),
108 ('T', 'template', '',
108 ('T', 'template', '',
109 _('display with template'), _('TEMPLATE')),
109 _('display with template'), _('TEMPLATE')),
110 ]
110 ]
111
111
112 logopts = [
112 logopts = [
113 ('p', 'patch', None, _('show patch')),
113 ('p', 'patch', None, _('show patch')),
114 ('g', 'git', None, _('use git extended diff format')),
114 ('g', 'git', None, _('use git extended diff format')),
115 ('l', 'limit', '',
115 ('l', 'limit', '',
116 _('limit number of changes displayed'), _('NUM')),
116 _('limit number of changes displayed'), _('NUM')),
117 ('M', 'no-merges', None, _('do not show merges')),
117 ('M', 'no-merges', None, _('do not show merges')),
118 ('', 'stat', None, _('output diffstat-style summary of changes')),
118 ('', 'stat', None, _('output diffstat-style summary of changes')),
119 ('G', 'graph', None, _("show the revision DAG")),
119 ('G', 'graph', None, _("show the revision DAG")),
120 ] + templateopts
120 ] + templateopts
121
121
122 diffopts = [
122 diffopts = [
123 ('a', 'text', None, _('treat all files as text')),
123 ('a', 'text', None, _('treat all files as text')),
124 ('g', 'git', None, _('use git extended diff format')),
124 ('g', 'git', None, _('use git extended diff format')),
125 ('', 'binary', None, _('generate binary diffs in git mode (default)')),
125 ('', 'binary', None, _('generate binary diffs in git mode (default)')),
126 ('', 'nodates', None, _('omit dates from diff headers'))
126 ('', 'nodates', None, _('omit dates from diff headers'))
127 ]
127 ]
128
128
129 diffwsopts = [
129 diffwsopts = [
130 ('w', 'ignore-all-space', None,
130 ('w', 'ignore-all-space', None,
131 _('ignore white space when comparing lines')),
131 _('ignore white space when comparing lines')),
132 ('b', 'ignore-space-change', None,
132 ('b', 'ignore-space-change', None,
133 _('ignore changes in the amount of white space')),
133 _('ignore changes in the amount of white space')),
134 ('B', 'ignore-blank-lines', None,
134 ('B', 'ignore-blank-lines', None,
135 _('ignore changes whose lines are all blank')),
135 _('ignore changes whose lines are all blank')),
136 ('Z', 'ignore-space-at-eol', None,
136 ('Z', 'ignore-space-at-eol', None,
137 _('ignore changes in whitespace at EOL')),
137 _('ignore changes in whitespace at EOL')),
138 ]
138 ]
139
139
140 diffopts2 = [
140 diffopts2 = [
141 ('', 'noprefix', None, _('omit a/ and b/ prefixes from filenames')),
141 ('', 'noprefix', None, _('omit a/ and b/ prefixes from filenames')),
142 ('p', 'show-function', None, _('show which function each change is in')),
142 ('p', 'show-function', None, _('show which function each change is in')),
143 ('', 'reverse', None, _('produce a diff that undoes the changes')),
143 ('', 'reverse', None, _('produce a diff that undoes the changes')),
144 ] + diffwsopts + [
144 ] + diffwsopts + [
145 ('U', 'unified', '',
145 ('U', 'unified', '',
146 _('number of lines of context to show'), _('NUM')),
146 _('number of lines of context to show'), _('NUM')),
147 ('', 'stat', None, _('output diffstat-style summary of changes')),
147 ('', 'stat', None, _('output diffstat-style summary of changes')),
148 ('', 'root', '', _('produce diffs relative to subdirectory'), _('DIR')),
148 ('', 'root', '', _('produce diffs relative to subdirectory'), _('DIR')),
149 ]
149 ]
150
150
151 mergetoolopts = [
151 mergetoolopts = [
152 ('t', 'tool', '', _('specify merge tool'), _('TOOL')),
152 ('t', 'tool', '', _('specify merge tool'), _('TOOL')),
153 ]
153 ]
154
154
155 similarityopts = [
155 similarityopts = [
156 ('s', 'similarity', '',
156 ('s', 'similarity', '',
157 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
157 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
158 ]
158 ]
159
159
160 subrepoopts = [
160 subrepoopts = [
161 ('S', 'subrepos', None,
161 ('S', 'subrepos', None,
162 _('recurse into subrepositories'))
162 _('recurse into subrepositories'))
163 ]
163 ]
164
164
165 debugrevlogopts = [
165 debugrevlogopts = [
166 ('c', 'changelog', False, _('open changelog')),
166 ('c', 'changelog', False, _('open changelog')),
167 ('m', 'manifest', False, _('open manifest')),
167 ('m', 'manifest', False, _('open manifest')),
168 ('', 'dir', '', _('open directory manifest')),
168 ('', 'dir', '', _('open directory manifest')),
169 ]
169 ]
170
170
171 # special string such that everything below this line will be ingored in the
171 # special string such that everything below this line will be ingored in the
172 # editor text
172 # editor text
173 _linebelow = "^HG: ------------------------ >8 ------------------------$"
173 _linebelow = "^HG: ------------------------ >8 ------------------------$"
174
174
175 def ishunk(x):
175 def ishunk(x):
176 hunkclasses = (crecordmod.uihunk, patch.recordhunk)
176 hunkclasses = (crecordmod.uihunk, patch.recordhunk)
177 return isinstance(x, hunkclasses)
177 return isinstance(x, hunkclasses)
178
178
179 def newandmodified(chunks, originalchunks):
179 def newandmodified(chunks, originalchunks):
180 newlyaddedandmodifiedfiles = set()
180 newlyaddedandmodifiedfiles = set()
181 for chunk in chunks:
181 for chunk in chunks:
182 if ishunk(chunk) and chunk.header.isnewfile() and chunk not in \
182 if ishunk(chunk) and chunk.header.isnewfile() and chunk not in \
183 originalchunks:
183 originalchunks:
184 newlyaddedandmodifiedfiles.add(chunk.header.filename())
184 newlyaddedandmodifiedfiles.add(chunk.header.filename())
185 return newlyaddedandmodifiedfiles
185 return newlyaddedandmodifiedfiles
186
186
187 def parsealiases(cmd):
187 def parsealiases(cmd):
188 return cmd.split("|")
188 return cmd.split("|")
189
189
190 def setupwrapcolorwrite(ui):
190 def setupwrapcolorwrite(ui):
191 # wrap ui.write so diff output can be labeled/colorized
191 # wrap ui.write so diff output can be labeled/colorized
192 def wrapwrite(orig, *args, **kw):
192 def wrapwrite(orig, *args, **kw):
193 label = kw.pop(r'label', '')
193 label = kw.pop(r'label', '')
194 for chunk, l in patch.difflabel(lambda: args):
194 for chunk, l in patch.difflabel(lambda: args):
195 orig(chunk, label=label + l)
195 orig(chunk, label=label + l)
196
196
197 oldwrite = ui.write
197 oldwrite = ui.write
198 def wrap(*args, **kwargs):
198 def wrap(*args, **kwargs):
199 return wrapwrite(oldwrite, *args, **kwargs)
199 return wrapwrite(oldwrite, *args, **kwargs)
200 setattr(ui, 'write', wrap)
200 setattr(ui, 'write', wrap)
201 return oldwrite
201 return oldwrite
202
202
203 def filterchunks(ui, originalhunks, usecurses, testfile, operation=None):
203 def filterchunks(ui, originalhunks, usecurses, testfile, operation=None):
204 try:
204 try:
205 if usecurses:
205 if usecurses:
206 if testfile:
206 if testfile:
207 recordfn = crecordmod.testdecorator(
207 recordfn = crecordmod.testdecorator(
208 testfile, crecordmod.testchunkselector)
208 testfile, crecordmod.testchunkselector)
209 else:
209 else:
210 recordfn = crecordmod.chunkselector
210 recordfn = crecordmod.chunkselector
211
211
212 return crecordmod.filterpatch(ui, originalhunks, recordfn,
212 return crecordmod.filterpatch(ui, originalhunks, recordfn,
213 operation)
213 operation)
214 except crecordmod.fallbackerror as e:
214 except crecordmod.fallbackerror as e:
215 ui.warn('%s\n' % e.message)
215 ui.warn('%s\n' % e.message)
216 ui.warn(_('falling back to text mode\n'))
216 ui.warn(_('falling back to text mode\n'))
217
217
218 return patch.filterpatch(ui, originalhunks, operation)
218 return patch.filterpatch(ui, originalhunks, operation)
219
219
220 def recordfilter(ui, originalhunks, operation=None):
220 def recordfilter(ui, originalhunks, operation=None):
221 """ Prompts the user to filter the originalhunks and return a list of
221 """ Prompts the user to filter the originalhunks and return a list of
222 selected hunks.
222 selected hunks.
223 *operation* is used for to build ui messages to indicate the user what
223 *operation* is used for to build ui messages to indicate the user what
224 kind of filtering they are doing: reverting, committing, shelving, etc.
224 kind of filtering they are doing: reverting, committing, shelving, etc.
225 (see patch.filterpatch).
225 (see patch.filterpatch).
226 """
226 """
227 usecurses = crecordmod.checkcurses(ui)
227 usecurses = crecordmod.checkcurses(ui)
228 testfile = ui.config('experimental', 'crecordtest')
228 testfile = ui.config('experimental', 'crecordtest')
229 oldwrite = setupwrapcolorwrite(ui)
229 oldwrite = setupwrapcolorwrite(ui)
230 try:
230 try:
231 newchunks, newopts = filterchunks(ui, originalhunks, usecurses,
231 newchunks, newopts = filterchunks(ui, originalhunks, usecurses,
232 testfile, operation)
232 testfile, operation)
233 finally:
233 finally:
234 ui.write = oldwrite
234 ui.write = oldwrite
235 return newchunks, newopts
235 return newchunks, newopts
236
236
237 def dorecord(ui, repo, commitfunc, cmdsuggest, backupall,
237 def dorecord(ui, repo, commitfunc, cmdsuggest, backupall,
238 filterfn, *pats, **opts):
238 filterfn, *pats, **opts):
239 opts = pycompat.byteskwargs(opts)
239 opts = pycompat.byteskwargs(opts)
240 if not ui.interactive():
240 if not ui.interactive():
241 if cmdsuggest:
241 if cmdsuggest:
242 msg = _('running non-interactively, use %s instead') % cmdsuggest
242 msg = _('running non-interactively, use %s instead') % cmdsuggest
243 else:
243 else:
244 msg = _('running non-interactively')
244 msg = _('running non-interactively')
245 raise error.Abort(msg)
245 raise error.Abort(msg)
246
246
247 # make sure username is set before going interactive
247 # make sure username is set before going interactive
248 if not opts.get('user'):
248 if not opts.get('user'):
249 ui.username() # raise exception, username not provided
249 ui.username() # raise exception, username not provided
250
250
251 def recordfunc(ui, repo, message, match, opts):
251 def recordfunc(ui, repo, message, match, opts):
252 """This is generic record driver.
252 """This is generic record driver.
253
253
254 Its job is to interactively filter local changes, and
254 Its job is to interactively filter local changes, and
255 accordingly prepare working directory into a state in which the
255 accordingly prepare working directory into a state in which the
256 job can be delegated to a non-interactive commit command such as
256 job can be delegated to a non-interactive commit command such as
257 'commit' or 'qrefresh'.
257 'commit' or 'qrefresh'.
258
258
259 After the actual job is done by non-interactive command, the
259 After the actual job is done by non-interactive command, the
260 working directory is restored to its original state.
260 working directory is restored to its original state.
261
261
262 In the end we'll record interesting changes, and everything else
262 In the end we'll record interesting changes, and everything else
263 will be left in place, so the user can continue working.
263 will be left in place, so the user can continue working.
264 """
264 """
265
265
266 checkunfinished(repo, commit=True)
266 checkunfinished(repo, commit=True)
267 wctx = repo[None]
267 wctx = repo[None]
268 merge = len(wctx.parents()) > 1
268 merge = len(wctx.parents()) > 1
269 if merge:
269 if merge:
270 raise error.Abort(_('cannot partially commit a merge '
270 raise error.Abort(_('cannot partially commit a merge '
271 '(use "hg commit" instead)'))
271 '(use "hg commit" instead)'))
272
272
273 def fail(f, msg):
273 def fail(f, msg):
274 raise error.Abort('%s: %s' % (f, msg))
274 raise error.Abort('%s: %s' % (f, msg))
275
275
276 force = opts.get('force')
276 force = opts.get('force')
277 if not force:
277 if not force:
278 vdirs = []
278 vdirs = []
279 match.explicitdir = vdirs.append
279 match.explicitdir = vdirs.append
280 match.bad = fail
280 match.bad = fail
281
281
282 status = repo.status(match=match)
282 status = repo.status(match=match)
283 if not force:
283 if not force:
284 repo.checkcommitpatterns(wctx, vdirs, match, status, fail)
284 repo.checkcommitpatterns(wctx, vdirs, match, status, fail)
285 diffopts = patch.difffeatureopts(ui, opts=opts, whitespace=True,
285 diffopts = patch.difffeatureopts(ui, opts=opts, whitespace=True,
286 section='commands',
286 section='commands',
287 configprefix='commit.interactive.')
287 configprefix='commit.interactive.')
288 diffopts.nodates = True
288 diffopts.nodates = True
289 diffopts.git = True
289 diffopts.git = True
290 diffopts.showfunc = True
290 diffopts.showfunc = True
291 originaldiff = patch.diff(repo, changes=status, opts=diffopts)
291 originaldiff = patch.diff(repo, changes=status, opts=diffopts)
292 originalchunks = patch.parsepatch(originaldiff)
292 originalchunks = patch.parsepatch(originaldiff)
293
293
294 # 1. filter patch, since we are intending to apply subset of it
294 # 1. filter patch, since we are intending to apply subset of it
295 try:
295 try:
296 chunks, newopts = filterfn(ui, originalchunks)
296 chunks, newopts = filterfn(ui, originalchunks)
297 except error.PatchError as err:
297 except error.PatchError as err:
298 raise error.Abort(_('error parsing patch: %s') % err)
298 raise error.Abort(_('error parsing patch: %s') % err)
299 opts.update(newopts)
299 opts.update(newopts)
300
300
301 # We need to keep a backup of files that have been newly added and
301 # We need to keep a backup of files that have been newly added and
302 # modified during the recording process because there is a previous
302 # modified during the recording process because there is a previous
303 # version without the edit in the workdir
303 # version without the edit in the workdir
304 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
304 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
305 contenders = set()
305 contenders = set()
306 for h in chunks:
306 for h in chunks:
307 try:
307 try:
308 contenders.update(set(h.files()))
308 contenders.update(set(h.files()))
309 except AttributeError:
309 except AttributeError:
310 pass
310 pass
311
311
312 changed = status.modified + status.added + status.removed
312 changed = status.modified + status.added + status.removed
313 newfiles = [f for f in changed if f in contenders]
313 newfiles = [f for f in changed if f in contenders]
314 if not newfiles:
314 if not newfiles:
315 ui.status(_('no changes to record\n'))
315 ui.status(_('no changes to record\n'))
316 return 0
316 return 0
317
317
318 modified = set(status.modified)
318 modified = set(status.modified)
319
319
320 # 2. backup changed files, so we can restore them in the end
320 # 2. backup changed files, so we can restore them in the end
321
321
322 if backupall:
322 if backupall:
323 tobackup = changed
323 tobackup = changed
324 else:
324 else:
325 tobackup = [f for f in newfiles if f in modified or f in \
325 tobackup = [f for f in newfiles if f in modified or f in \
326 newlyaddedandmodifiedfiles]
326 newlyaddedandmodifiedfiles]
327 backups = {}
327 backups = {}
328 if tobackup:
328 if tobackup:
329 backupdir = repo.vfs.join('record-backups')
329 backupdir = repo.vfs.join('record-backups')
330 try:
330 try:
331 os.mkdir(backupdir)
331 os.mkdir(backupdir)
332 except OSError as err:
332 except OSError as err:
333 if err.errno != errno.EEXIST:
333 if err.errno != errno.EEXIST:
334 raise
334 raise
335 try:
335 try:
336 # backup continues
336 # backup continues
337 for f in tobackup:
337 for f in tobackup:
338 fd, tmpname = pycompat.mkstemp(prefix=f.replace('/', '_') + '.',
338 fd, tmpname = pycompat.mkstemp(prefix=f.replace('/', '_') + '.',
339 dir=backupdir)
339 dir=backupdir)
340 os.close(fd)
340 os.close(fd)
341 ui.debug('backup %r as %r\n' % (f, tmpname))
341 ui.debug('backup %r as %r\n' % (f, tmpname))
342 util.copyfile(repo.wjoin(f), tmpname, copystat=True)
342 util.copyfile(repo.wjoin(f), tmpname, copystat=True)
343 backups[f] = tmpname
343 backups[f] = tmpname
344
344
345 fp = stringio()
345 fp = stringio()
346 for c in chunks:
346 for c in chunks:
347 fname = c.filename()
347 fname = c.filename()
348 if fname in backups:
348 if fname in backups:
349 c.write(fp)
349 c.write(fp)
350 dopatch = fp.tell()
350 dopatch = fp.tell()
351 fp.seek(0)
351 fp.seek(0)
352
352
353 # 2.5 optionally review / modify patch in text editor
353 # 2.5 optionally review / modify patch in text editor
354 if opts.get('review', False):
354 if opts.get('review', False):
355 patchtext = (crecordmod.diffhelptext
355 patchtext = (crecordmod.diffhelptext
356 + crecordmod.patchhelptext
356 + crecordmod.patchhelptext
357 + fp.read())
357 + fp.read())
358 reviewedpatch = ui.edit(patchtext, "",
358 reviewedpatch = ui.edit(patchtext, "",
359 action="diff",
359 action="diff",
360 repopath=repo.path)
360 repopath=repo.path)
361 fp.truncate(0)
361 fp.truncate(0)
362 fp.write(reviewedpatch)
362 fp.write(reviewedpatch)
363 fp.seek(0)
363 fp.seek(0)
364
364
365 [os.unlink(repo.wjoin(c)) for c in newlyaddedandmodifiedfiles]
365 [os.unlink(repo.wjoin(c)) for c in newlyaddedandmodifiedfiles]
366 # 3a. apply filtered patch to clean repo (clean)
366 # 3a. apply filtered patch to clean repo (clean)
367 if backups:
367 if backups:
368 # Equivalent to hg.revert
368 # Equivalent to hg.revert
369 m = scmutil.matchfiles(repo, backups.keys())
369 m = scmutil.matchfiles(repo, backups.keys())
370 mergemod.update(repo, repo.dirstate.p1(), branchmerge=False,
370 mergemod.update(repo, repo.dirstate.p1(), branchmerge=False,
371 force=True, matcher=m)
371 force=True, matcher=m)
372
372
373 # 3b. (apply)
373 # 3b. (apply)
374 if dopatch:
374 if dopatch:
375 try:
375 try:
376 ui.debug('applying patch\n')
376 ui.debug('applying patch\n')
377 ui.debug(fp.getvalue())
377 ui.debug(fp.getvalue())
378 patch.internalpatch(ui, repo, fp, 1, eolmode=None)
378 patch.internalpatch(ui, repo, fp, 1, eolmode=None)
379 except error.PatchError as err:
379 except error.PatchError as err:
380 raise error.Abort(pycompat.bytestr(err))
380 raise error.Abort(pycompat.bytestr(err))
381 del fp
381 del fp
382
382
383 # 4. We prepared working directory according to filtered
383 # 4. We prepared working directory according to filtered
384 # patch. Now is the time to delegate the job to
384 # patch. Now is the time to delegate the job to
385 # commit/qrefresh or the like!
385 # commit/qrefresh or the like!
386
386
387 # Make all of the pathnames absolute.
387 # Make all of the pathnames absolute.
388 newfiles = [repo.wjoin(nf) for nf in newfiles]
388 newfiles = [repo.wjoin(nf) for nf in newfiles]
389 return commitfunc(ui, repo, *newfiles, **pycompat.strkwargs(opts))
389 return commitfunc(ui, repo, *newfiles, **pycompat.strkwargs(opts))
390 finally:
390 finally:
391 # 5. finally restore backed-up files
391 # 5. finally restore backed-up files
392 try:
392 try:
393 dirstate = repo.dirstate
393 dirstate = repo.dirstate
394 for realname, tmpname in backups.iteritems():
394 for realname, tmpname in backups.iteritems():
395 ui.debug('restoring %r to %r\n' % (tmpname, realname))
395 ui.debug('restoring %r to %r\n' % (tmpname, realname))
396
396
397 if dirstate[realname] == 'n':
397 if dirstate[realname] == 'n':
398 # without normallookup, restoring timestamp
398 # without normallookup, restoring timestamp
399 # may cause partially committed files
399 # may cause partially committed files
400 # to be treated as unmodified
400 # to be treated as unmodified
401 dirstate.normallookup(realname)
401 dirstate.normallookup(realname)
402
402
403 # copystat=True here and above are a hack to trick any
403 # copystat=True here and above are a hack to trick any
404 # editors that have f open that we haven't modified them.
404 # editors that have f open that we haven't modified them.
405 #
405 #
406 # Also note that this racy as an editor could notice the
406 # Also note that this racy as an editor could notice the
407 # file's mtime before we've finished writing it.
407 # file's mtime before we've finished writing it.
408 util.copyfile(tmpname, repo.wjoin(realname), copystat=True)
408 util.copyfile(tmpname, repo.wjoin(realname), copystat=True)
409 os.unlink(tmpname)
409 os.unlink(tmpname)
410 if tobackup:
410 if tobackup:
411 os.rmdir(backupdir)
411 os.rmdir(backupdir)
412 except OSError:
412 except OSError:
413 pass
413 pass
414
414
415 def recordinwlock(ui, repo, message, match, opts):
415 def recordinwlock(ui, repo, message, match, opts):
416 with repo.wlock():
416 with repo.wlock():
417 return recordfunc(ui, repo, message, match, opts)
417 return recordfunc(ui, repo, message, match, opts)
418
418
419 return commit(ui, repo, recordinwlock, pats, opts)
419 return commit(ui, repo, recordinwlock, pats, opts)
420
420
421 class dirnode(object):
421 class dirnode(object):
422 """
422 """
423 Represent a directory in user working copy with information required for
423 Represent a directory in user working copy with information required for
424 the purpose of tersing its status.
424 the purpose of tersing its status.
425
425
426 path is the path to the directory, without a trailing '/'
426 path is the path to the directory, without a trailing '/'
427
427
428 statuses is a set of statuses of all files in this directory (this includes
428 statuses is a set of statuses of all files in this directory (this includes
429 all the files in all the subdirectories too)
429 all the files in all the subdirectories too)
430
430
431 files is a list of files which are direct child of this directory
431 files is a list of files which are direct child of this directory
432
432
433 subdirs is a dictionary of sub-directory name as the key and it's own
433 subdirs is a dictionary of sub-directory name as the key and it's own
434 dirnode object as the value
434 dirnode object as the value
435 """
435 """
436
436
437 def __init__(self, dirpath):
437 def __init__(self, dirpath):
438 self.path = dirpath
438 self.path = dirpath
439 self.statuses = set([])
439 self.statuses = set([])
440 self.files = []
440 self.files = []
441 self.subdirs = {}
441 self.subdirs = {}
442
442
443 def _addfileindir(self, filename, status):
443 def _addfileindir(self, filename, status):
444 """Add a file in this directory as a direct child."""
444 """Add a file in this directory as a direct child."""
445 self.files.append((filename, status))
445 self.files.append((filename, status))
446
446
447 def addfile(self, filename, status):
447 def addfile(self, filename, status):
448 """
448 """
449 Add a file to this directory or to its direct parent directory.
449 Add a file to this directory or to its direct parent directory.
450
450
451 If the file is not direct child of this directory, we traverse to the
451 If the file is not direct child of this directory, we traverse to the
452 directory of which this file is a direct child of and add the file
452 directory of which this file is a direct child of and add the file
453 there.
453 there.
454 """
454 """
455
455
456 # the filename contains a path separator, it means it's not the direct
456 # the filename contains a path separator, it means it's not the direct
457 # child of this directory
457 # child of this directory
458 if '/' in filename:
458 if '/' in filename:
459 subdir, filep = filename.split('/', 1)
459 subdir, filep = filename.split('/', 1)
460
460
461 # does the dirnode object for subdir exists
461 # does the dirnode object for subdir exists
462 if subdir not in self.subdirs:
462 if subdir not in self.subdirs:
463 subdirpath = pathutil.join(self.path, subdir)
463 subdirpath = pathutil.join(self.path, subdir)
464 self.subdirs[subdir] = dirnode(subdirpath)
464 self.subdirs[subdir] = dirnode(subdirpath)
465
465
466 # try adding the file in subdir
466 # try adding the file in subdir
467 self.subdirs[subdir].addfile(filep, status)
467 self.subdirs[subdir].addfile(filep, status)
468
468
469 else:
469 else:
470 self._addfileindir(filename, status)
470 self._addfileindir(filename, status)
471
471
472 if status not in self.statuses:
472 if status not in self.statuses:
473 self.statuses.add(status)
473 self.statuses.add(status)
474
474
475 def iterfilepaths(self):
475 def iterfilepaths(self):
476 """Yield (status, path) for files directly under this directory."""
476 """Yield (status, path) for files directly under this directory."""
477 for f, st in self.files:
477 for f, st in self.files:
478 yield st, pathutil.join(self.path, f)
478 yield st, pathutil.join(self.path, f)
479
479
480 def tersewalk(self, terseargs):
480 def tersewalk(self, terseargs):
481 """
481 """
482 Yield (status, path) obtained by processing the status of this
482 Yield (status, path) obtained by processing the status of this
483 dirnode.
483 dirnode.
484
484
485 terseargs is the string of arguments passed by the user with `--terse`
485 terseargs is the string of arguments passed by the user with `--terse`
486 flag.
486 flag.
487
487
488 Following are the cases which can happen:
488 Following are the cases which can happen:
489
489
490 1) All the files in the directory (including all the files in its
490 1) All the files in the directory (including all the files in its
491 subdirectories) share the same status and the user has asked us to terse
491 subdirectories) share the same status and the user has asked us to terse
492 that status. -> yield (status, dirpath). dirpath will end in '/'.
492 that status. -> yield (status, dirpath). dirpath will end in '/'.
493
493
494 2) Otherwise, we do following:
494 2) Otherwise, we do following:
495
495
496 a) Yield (status, filepath) for all the files which are in this
496 a) Yield (status, filepath) for all the files which are in this
497 directory (only the ones in this directory, not the subdirs)
497 directory (only the ones in this directory, not the subdirs)
498
498
499 b) Recurse the function on all the subdirectories of this
499 b) Recurse the function on all the subdirectories of this
500 directory
500 directory
501 """
501 """
502
502
503 if len(self.statuses) == 1:
503 if len(self.statuses) == 1:
504 onlyst = self.statuses.pop()
504 onlyst = self.statuses.pop()
505
505
506 # Making sure we terse only when the status abbreviation is
506 # Making sure we terse only when the status abbreviation is
507 # passed as terse argument
507 # passed as terse argument
508 if onlyst in terseargs:
508 if onlyst in terseargs:
509 yield onlyst, self.path + '/'
509 yield onlyst, self.path + '/'
510 return
510 return
511
511
512 # add the files to status list
512 # add the files to status list
513 for st, fpath in self.iterfilepaths():
513 for st, fpath in self.iterfilepaths():
514 yield st, fpath
514 yield st, fpath
515
515
516 #recurse on the subdirs
516 #recurse on the subdirs
517 for dirobj in self.subdirs.values():
517 for dirobj in self.subdirs.values():
518 for st, fpath in dirobj.tersewalk(terseargs):
518 for st, fpath in dirobj.tersewalk(terseargs):
519 yield st, fpath
519 yield st, fpath
520
520
521 def tersedir(statuslist, terseargs):
521 def tersedir(statuslist, terseargs):
522 """
522 """
523 Terse the status if all the files in a directory shares the same status.
523 Terse the status if all the files in a directory shares the same status.
524
524
525 statuslist is scmutil.status() object which contains a list of files for
525 statuslist is scmutil.status() object which contains a list of files for
526 each status.
526 each status.
527 terseargs is string which is passed by the user as the argument to `--terse`
527 terseargs is string which is passed by the user as the argument to `--terse`
528 flag.
528 flag.
529
529
530 The function makes a tree of objects of dirnode class, and at each node it
530 The function makes a tree of objects of dirnode class, and at each node it
531 stores the information required to know whether we can terse a certain
531 stores the information required to know whether we can terse a certain
532 directory or not.
532 directory or not.
533 """
533 """
534 # the order matters here as that is used to produce final list
534 # the order matters here as that is used to produce final list
535 allst = ('m', 'a', 'r', 'd', 'u', 'i', 'c')
535 allst = ('m', 'a', 'r', 'd', 'u', 'i', 'c')
536
536
537 # checking the argument validity
537 # checking the argument validity
538 for s in pycompat.bytestr(terseargs):
538 for s in pycompat.bytestr(terseargs):
539 if s not in allst:
539 if s not in allst:
540 raise error.Abort(_("'%s' not recognized") % s)
540 raise error.Abort(_("'%s' not recognized") % s)
541
541
542 # creating a dirnode object for the root of the repo
542 # creating a dirnode object for the root of the repo
543 rootobj = dirnode('')
543 rootobj = dirnode('')
544 pstatus = ('modified', 'added', 'deleted', 'clean', 'unknown',
544 pstatus = ('modified', 'added', 'deleted', 'clean', 'unknown',
545 'ignored', 'removed')
545 'ignored', 'removed')
546
546
547 tersedict = {}
547 tersedict = {}
548 for attrname in pstatus:
548 for attrname in pstatus:
549 statuschar = attrname[0:1]
549 statuschar = attrname[0:1]
550 for f in getattr(statuslist, attrname):
550 for f in getattr(statuslist, attrname):
551 rootobj.addfile(f, statuschar)
551 rootobj.addfile(f, statuschar)
552 tersedict[statuschar] = []
552 tersedict[statuschar] = []
553
553
554 # we won't be tersing the root dir, so add files in it
554 # we won't be tersing the root dir, so add files in it
555 for st, fpath in rootobj.iterfilepaths():
555 for st, fpath in rootobj.iterfilepaths():
556 tersedict[st].append(fpath)
556 tersedict[st].append(fpath)
557
557
558 # process each sub-directory and build tersedict
558 # process each sub-directory and build tersedict
559 for subdir in rootobj.subdirs.values():
559 for subdir in rootobj.subdirs.values():
560 for st, f in subdir.tersewalk(terseargs):
560 for st, f in subdir.tersewalk(terseargs):
561 tersedict[st].append(f)
561 tersedict[st].append(f)
562
562
563 tersedlist = []
563 tersedlist = []
564 for st in allst:
564 for st in allst:
565 tersedict[st].sort()
565 tersedict[st].sort()
566 tersedlist.append(tersedict[st])
566 tersedlist.append(tersedict[st])
567
567
568 return tersedlist
568 return tersedlist
569
569
570 def _commentlines(raw):
570 def _commentlines(raw):
571 '''Surround lineswith a comment char and a new line'''
571 '''Surround lineswith a comment char and a new line'''
572 lines = raw.splitlines()
572 lines = raw.splitlines()
573 commentedlines = ['# %s' % line for line in lines]
573 commentedlines = ['# %s' % line for line in lines]
574 return '\n'.join(commentedlines) + '\n'
574 return '\n'.join(commentedlines) + '\n'
575
575
576 def _conflictsmsg(repo):
576 def _conflictsmsg(repo):
577 mergestate = mergemod.mergestate.read(repo)
577 mergestate = mergemod.mergestate.read(repo)
578 if not mergestate.active():
578 if not mergestate.active():
579 return
579 return
580
580
581 m = scmutil.match(repo[None])
581 m = scmutil.match(repo[None])
582 unresolvedlist = [f for f in mergestate.unresolved() if m(f)]
582 unresolvedlist = [f for f in mergestate.unresolved() if m(f)]
583 if unresolvedlist:
583 if unresolvedlist:
584 mergeliststr = '\n'.join(
584 mergeliststr = '\n'.join(
585 [' %s' % util.pathto(repo.root, encoding.getcwd(), path)
585 [' %s' % util.pathto(repo.root, encoding.getcwd(), path)
586 for path in sorted(unresolvedlist)])
586 for path in sorted(unresolvedlist)])
587 msg = _('''Unresolved merge conflicts:
587 msg = _('''Unresolved merge conflicts:
588
588
589 %s
589 %s
590
590
591 To mark files as resolved: hg resolve --mark FILE''') % mergeliststr
591 To mark files as resolved: hg resolve --mark FILE''') % mergeliststr
592 else:
592 else:
593 msg = _('No unresolved merge conflicts.')
593 msg = _('No unresolved merge conflicts.')
594
594
595 return _commentlines(msg)
595 return _commentlines(msg)
596
596
597 def _helpmessage(continuecmd, abortcmd):
597 def _helpmessage(continuecmd, abortcmd):
598 msg = _('To continue: %s\n'
598 msg = _('To continue: %s\n'
599 'To abort: %s') % (continuecmd, abortcmd)
599 'To abort: %s') % (continuecmd, abortcmd)
600 return _commentlines(msg)
600 return _commentlines(msg)
601
601
602 def _rebasemsg():
602 def _rebasemsg():
603 return _helpmessage('hg rebase --continue', 'hg rebase --abort')
603 return _helpmessage('hg rebase --continue', 'hg rebase --abort')
604
604
605 def _histeditmsg():
605 def _histeditmsg():
606 return _helpmessage('hg histedit --continue', 'hg histedit --abort')
606 return _helpmessage('hg histedit --continue', 'hg histedit --abort')
607
607
608 def _unshelvemsg():
608 def _unshelvemsg():
609 return _helpmessage('hg unshelve --continue', 'hg unshelve --abort')
609 return _helpmessage('hg unshelve --continue', 'hg unshelve --abort')
610
610
611 def _graftmsg():
611 def _graftmsg():
612 return _helpmessage('hg graft --continue', 'hg graft --abort')
612 return _helpmessage('hg graft --continue', 'hg graft --abort')
613
613
614 def _mergemsg():
614 def _mergemsg():
615 return _helpmessage('hg commit', 'hg merge --abort')
615 return _helpmessage('hg commit', 'hg merge --abort')
616
616
617 def _bisectmsg():
617 def _bisectmsg():
618 msg = _('To mark the changeset good: hg bisect --good\n'
618 msg = _('To mark the changeset good: hg bisect --good\n'
619 'To mark the changeset bad: hg bisect --bad\n'
619 'To mark the changeset bad: hg bisect --bad\n'
620 'To abort: hg bisect --reset\n')
620 'To abort: hg bisect --reset\n')
621 return _commentlines(msg)
621 return _commentlines(msg)
622
622
623 def fileexistspredicate(filename):
623 def fileexistspredicate(filename):
624 return lambda repo: repo.vfs.exists(filename)
624 return lambda repo: repo.vfs.exists(filename)
625
625
626 def _mergepredicate(repo):
626 def _mergepredicate(repo):
627 return len(repo[None].parents()) > 1
627 return len(repo[None].parents()) > 1
628
628
629 STATES = (
629 STATES = (
630 # (state, predicate to detect states, helpful message function)
630 # (state, predicate to detect states, helpful message function)
631 ('histedit', fileexistspredicate('histedit-state'), _histeditmsg),
631 ('histedit', fileexistspredicate('histedit-state'), _histeditmsg),
632 ('bisect', fileexistspredicate('bisect.state'), _bisectmsg),
632 ('bisect', fileexistspredicate('bisect.state'), _bisectmsg),
633 ('graft', fileexistspredicate('graftstate'), _graftmsg),
633 ('graft', fileexistspredicate('graftstate'), _graftmsg),
634 ('unshelve', fileexistspredicate('shelvedstate'), _unshelvemsg),
634 ('unshelve', fileexistspredicate('shelvedstate'), _unshelvemsg),
635 ('rebase', fileexistspredicate('rebasestate'), _rebasemsg),
635 ('rebase', fileexistspredicate('rebasestate'), _rebasemsg),
636 # The merge state is part of a list that will be iterated over.
636 # The merge state is part of a list that will be iterated over.
637 # They need to be last because some of the other unfinished states may also
637 # They need to be last because some of the other unfinished states may also
638 # be in a merge or update state (eg. rebase, histedit, graft, etc).
638 # be in a merge or update state (eg. rebase, histedit, graft, etc).
639 # We want those to have priority.
639 # We want those to have priority.
640 ('merge', _mergepredicate, _mergemsg),
640 ('merge', _mergepredicate, _mergemsg),
641 )
641 )
642
642
643 def _getrepostate(repo):
643 def _getrepostate(repo):
644 # experimental config: commands.status.skipstates
644 # experimental config: commands.status.skipstates
645 skip = set(repo.ui.configlist('commands', 'status.skipstates'))
645 skip = set(repo.ui.configlist('commands', 'status.skipstates'))
646 for state, statedetectionpredicate, msgfn in STATES:
646 for state, statedetectionpredicate, msgfn in STATES:
647 if state in skip:
647 if state in skip:
648 continue
648 continue
649 if statedetectionpredicate(repo):
649 if statedetectionpredicate(repo):
650 return (state, statedetectionpredicate, msgfn)
650 return (state, statedetectionpredicate, msgfn)
651
651
652 def morestatus(repo, fm):
652 def morestatus(repo, fm):
653 statetuple = _getrepostate(repo)
653 statetuple = _getrepostate(repo)
654 label = 'status.morestatus'
654 label = 'status.morestatus'
655 if statetuple:
655 if statetuple:
656 state, statedetectionpredicate, helpfulmsg = statetuple
656 state, statedetectionpredicate, helpfulmsg = statetuple
657 statemsg = _('The repository is in an unfinished *%s* state.') % state
657 statemsg = _('The repository is in an unfinished *%s* state.') % state
658 fm.plain('%s\n' % _commentlines(statemsg), label=label)
658 fm.plain('%s\n' % _commentlines(statemsg), label=label)
659 conmsg = _conflictsmsg(repo)
659 conmsg = _conflictsmsg(repo)
660 if conmsg:
660 if conmsg:
661 fm.plain('%s\n' % conmsg, label=label)
661 fm.plain('%s\n' % conmsg, label=label)
662 if helpfulmsg:
662 if helpfulmsg:
663 helpmsg = helpfulmsg()
663 helpmsg = helpfulmsg()
664 fm.plain('%s\n' % helpmsg, label=label)
664 fm.plain('%s\n' % helpmsg, label=label)
665
665
666 def findpossible(cmd, table, strict=False):
666 def findpossible(cmd, table, strict=False):
667 """
667 """
668 Return cmd -> (aliases, command table entry)
668 Return cmd -> (aliases, command table entry)
669 for each matching command.
669 for each matching command.
670 Return debug commands (or their aliases) only if no normal command matches.
670 Return debug commands (or their aliases) only if no normal command matches.
671 """
671 """
672 choice = {}
672 choice = {}
673 debugchoice = {}
673 debugchoice = {}
674
674
675 if cmd in table:
675 if cmd in table:
676 # short-circuit exact matches, "log" alias beats "log|history"
676 # short-circuit exact matches, "log" alias beats "log|history"
677 keys = [cmd]
677 keys = [cmd]
678 else:
678 else:
679 keys = table.keys()
679 keys = table.keys()
680
680
681 allcmds = []
681 allcmds = []
682 for e in keys:
682 for e in keys:
683 aliases = parsealiases(e)
683 aliases = parsealiases(e)
684 allcmds.extend(aliases)
684 allcmds.extend(aliases)
685 found = None
685 found = None
686 if cmd in aliases:
686 if cmd in aliases:
687 found = cmd
687 found = cmd
688 elif not strict:
688 elif not strict:
689 for a in aliases:
689 for a in aliases:
690 if a.startswith(cmd):
690 if a.startswith(cmd):
691 found = a
691 found = a
692 break
692 break
693 if found is not None:
693 if found is not None:
694 if aliases[0].startswith("debug") or found.startswith("debug"):
694 if aliases[0].startswith("debug") or found.startswith("debug"):
695 debugchoice[found] = (aliases, table[e])
695 debugchoice[found] = (aliases, table[e])
696 else:
696 else:
697 choice[found] = (aliases, table[e])
697 choice[found] = (aliases, table[e])
698
698
699 if not choice and debugchoice:
699 if not choice and debugchoice:
700 choice = debugchoice
700 choice = debugchoice
701
701
702 return choice, allcmds
702 return choice, allcmds
703
703
704 def findcmd(cmd, table, strict=True):
704 def findcmd(cmd, table, strict=True):
705 """Return (aliases, command table entry) for command string."""
705 """Return (aliases, command table entry) for command string."""
706 choice, allcmds = findpossible(cmd, table, strict)
706 choice, allcmds = findpossible(cmd, table, strict)
707
707
708 if cmd in choice:
708 if cmd in choice:
709 return choice[cmd]
709 return choice[cmd]
710
710
711 if len(choice) > 1:
711 if len(choice) > 1:
712 clist = sorted(choice)
712 clist = sorted(choice)
713 raise error.AmbiguousCommand(cmd, clist)
713 raise error.AmbiguousCommand(cmd, clist)
714
714
715 if choice:
715 if choice:
716 return list(choice.values())[0]
716 return list(choice.values())[0]
717
717
718 raise error.UnknownCommand(cmd, allcmds)
718 raise error.UnknownCommand(cmd, allcmds)
719
719
720 def changebranch(ui, repo, revs, label):
720 def changebranch(ui, repo, revs, label):
721 """ Change the branch name of given revs to label """
721 """ Change the branch name of given revs to label """
722
722
723 with repo.wlock(), repo.lock(), repo.transaction('branches'):
723 with repo.wlock(), repo.lock(), repo.transaction('branches'):
724 # abort in case of uncommitted merge or dirty wdir
724 # abort in case of uncommitted merge or dirty wdir
725 bailifchanged(repo)
725 bailifchanged(repo)
726 revs = scmutil.revrange(repo, revs)
726 revs = scmutil.revrange(repo, revs)
727 if not revs:
727 if not revs:
728 raise error.Abort("empty revision set")
728 raise error.Abort("empty revision set")
729 roots = repo.revs('roots(%ld)', revs)
729 roots = repo.revs('roots(%ld)', revs)
730 if len(roots) > 1:
730 if len(roots) > 1:
731 raise error.Abort(_("cannot change branch of non-linear revisions"))
731 raise error.Abort(_("cannot change branch of non-linear revisions"))
732 rewriteutil.precheck(repo, revs, 'change branch of')
732 rewriteutil.precheck(repo, revs, 'change branch of')
733
733
734 root = repo[roots.first()]
734 root = repo[roots.first()]
735 rpb = {parent.branch() for parent in root.parents()}
735 rpb = {parent.branch() for parent in root.parents()}
736 if label not in rpb and label in repo.branchmap():
736 if label not in rpb and label in repo.branchmap():
737 raise error.Abort(_("a branch of the same name already exists"))
737 raise error.Abort(_("a branch of the same name already exists"))
738
738
739 if repo.revs('obsolete() and %ld', revs):
739 if repo.revs('obsolete() and %ld', revs):
740 raise error.Abort(_("cannot change branch of a obsolete changeset"))
740 raise error.Abort(_("cannot change branch of a obsolete changeset"))
741
741
742 # make sure only topological heads
742 # make sure only topological heads
743 if repo.revs('heads(%ld) - head()', revs):
743 if repo.revs('heads(%ld) - head()', revs):
744 raise error.Abort(_("cannot change branch in middle of a stack"))
744 raise error.Abort(_("cannot change branch in middle of a stack"))
745
745
746 replacements = {}
746 replacements = {}
747 # avoid import cycle mercurial.cmdutil -> mercurial.context ->
747 # avoid import cycle mercurial.cmdutil -> mercurial.context ->
748 # mercurial.subrepo -> mercurial.cmdutil
748 # mercurial.subrepo -> mercurial.cmdutil
749 from . import context
749 from . import context
750 for rev in revs:
750 for rev in revs:
751 ctx = repo[rev]
751 ctx = repo[rev]
752 oldbranch = ctx.branch()
752 oldbranch = ctx.branch()
753 # check if ctx has same branch
753 # check if ctx has same branch
754 if oldbranch == label:
754 if oldbranch == label:
755 continue
755 continue
756
756
757 def filectxfn(repo, newctx, path):
757 def filectxfn(repo, newctx, path):
758 try:
758 try:
759 return ctx[path]
759 return ctx[path]
760 except error.ManifestLookupError:
760 except error.ManifestLookupError:
761 return None
761 return None
762
762
763 ui.debug("changing branch of '%s' from '%s' to '%s'\n"
763 ui.debug("changing branch of '%s' from '%s' to '%s'\n"
764 % (hex(ctx.node()), oldbranch, label))
764 % (hex(ctx.node()), oldbranch, label))
765 extra = ctx.extra()
765 extra = ctx.extra()
766 extra['branch_change'] = hex(ctx.node())
766 extra['branch_change'] = hex(ctx.node())
767 # While changing branch of set of linear commits, make sure that
767 # While changing branch of set of linear commits, make sure that
768 # we base our commits on new parent rather than old parent which
768 # we base our commits on new parent rather than old parent which
769 # was obsoleted while changing the branch
769 # was obsoleted while changing the branch
770 p1 = ctx.p1().node()
770 p1 = ctx.p1().node()
771 p2 = ctx.p2().node()
771 p2 = ctx.p2().node()
772 if p1 in replacements:
772 if p1 in replacements:
773 p1 = replacements[p1][0]
773 p1 = replacements[p1][0]
774 if p2 in replacements:
774 if p2 in replacements:
775 p2 = replacements[p2][0]
775 p2 = replacements[p2][0]
776
776
777 mc = context.memctx(repo, (p1, p2),
777 mc = context.memctx(repo, (p1, p2),
778 ctx.description(),
778 ctx.description(),
779 ctx.files(),
779 ctx.files(),
780 filectxfn,
780 filectxfn,
781 user=ctx.user(),
781 user=ctx.user(),
782 date=ctx.date(),
782 date=ctx.date(),
783 extra=extra,
783 extra=extra,
784 branch=label)
784 branch=label)
785
785
786 newnode = repo.commitctx(mc)
786 newnode = repo.commitctx(mc)
787 replacements[ctx.node()] = (newnode,)
787 replacements[ctx.node()] = (newnode,)
788 ui.debug('new node id is %s\n' % hex(newnode))
788 ui.debug('new node id is %s\n' % hex(newnode))
789
789
790 # create obsmarkers and move bookmarks
790 # create obsmarkers and move bookmarks
791 scmutil.cleanupnodes(repo, replacements, 'branch-change', fixphase=True)
791 scmutil.cleanupnodes(repo, replacements, 'branch-change', fixphase=True)
792
792
793 # move the working copy too
793 # move the working copy too
794 wctx = repo[None]
794 wctx = repo[None]
795 # in-progress merge is a bit too complex for now.
795 # in-progress merge is a bit too complex for now.
796 if len(wctx.parents()) == 1:
796 if len(wctx.parents()) == 1:
797 newid = replacements.get(wctx.p1().node())
797 newid = replacements.get(wctx.p1().node())
798 if newid is not None:
798 if newid is not None:
799 # avoid import cycle mercurial.cmdutil -> mercurial.hg ->
799 # avoid import cycle mercurial.cmdutil -> mercurial.hg ->
800 # mercurial.cmdutil
800 # mercurial.cmdutil
801 from . import hg
801 from . import hg
802 hg.update(repo, newid[0], quietempty=True)
802 hg.update(repo, newid[0], quietempty=True)
803
803
804 ui.status(_("changed branch on %d changesets\n") % len(replacements))
804 ui.status(_("changed branch on %d changesets\n") % len(replacements))
805
805
806 def findrepo(p):
806 def findrepo(p):
807 while not os.path.isdir(os.path.join(p, ".hg")):
807 while not os.path.isdir(os.path.join(p, ".hg")):
808 oldp, p = p, os.path.dirname(p)
808 oldp, p = p, os.path.dirname(p)
809 if p == oldp:
809 if p == oldp:
810 return None
810 return None
811
811
812 return p
812 return p
813
813
814 def bailifchanged(repo, merge=True, hint=None):
814 def bailifchanged(repo, merge=True, hint=None):
815 """ enforce the precondition that working directory must be clean.
815 """ enforce the precondition that working directory must be clean.
816
816
817 'merge' can be set to false if a pending uncommitted merge should be
817 'merge' can be set to false if a pending uncommitted merge should be
818 ignored (such as when 'update --check' runs).
818 ignored (such as when 'update --check' runs).
819
819
820 'hint' is the usual hint given to Abort exception.
820 'hint' is the usual hint given to Abort exception.
821 """
821 """
822
822
823 if merge and repo.dirstate.p2() != nullid:
823 if merge and repo.dirstate.p2() != nullid:
824 raise error.Abort(_('outstanding uncommitted merge'), hint=hint)
824 raise error.Abort(_('outstanding uncommitted merge'), hint=hint)
825 modified, added, removed, deleted = repo.status()[:4]
825 modified, added, removed, deleted = repo.status()[:4]
826 if modified or added or removed or deleted:
826 if modified or added or removed or deleted:
827 raise error.Abort(_('uncommitted changes'), hint=hint)
827 raise error.Abort(_('uncommitted changes'), hint=hint)
828 ctx = repo[None]
828 ctx = repo[None]
829 for s in sorted(ctx.substate):
829 for s in sorted(ctx.substate):
830 ctx.sub(s).bailifchanged(hint=hint)
830 ctx.sub(s).bailifchanged(hint=hint)
831
831
832 def logmessage(ui, opts):
832 def logmessage(ui, opts):
833 """ get the log message according to -m and -l option """
833 """ get the log message according to -m and -l option """
834 message = opts.get('message')
834 message = opts.get('message')
835 logfile = opts.get('logfile')
835 logfile = opts.get('logfile')
836
836
837 if message and logfile:
837 if message and logfile:
838 raise error.Abort(_('options --message and --logfile are mutually '
838 raise error.Abort(_('options --message and --logfile are mutually '
839 'exclusive'))
839 'exclusive'))
840 if not message and logfile:
840 if not message and logfile:
841 try:
841 try:
842 if isstdiofilename(logfile):
842 if isstdiofilename(logfile):
843 message = ui.fin.read()
843 message = ui.fin.read()
844 else:
844 else:
845 message = '\n'.join(util.readfile(logfile).splitlines())
845 message = '\n'.join(util.readfile(logfile).splitlines())
846 except IOError as inst:
846 except IOError as inst:
847 raise error.Abort(_("can't read commit message '%s': %s") %
847 raise error.Abort(_("can't read commit message '%s': %s") %
848 (logfile, encoding.strtolocal(inst.strerror)))
848 (logfile, encoding.strtolocal(inst.strerror)))
849 return message
849 return message
850
850
851 def mergeeditform(ctxorbool, baseformname):
851 def mergeeditform(ctxorbool, baseformname):
852 """return appropriate editform name (referencing a committemplate)
852 """return appropriate editform name (referencing a committemplate)
853
853
854 'ctxorbool' is either a ctx to be committed, or a bool indicating whether
854 'ctxorbool' is either a ctx to be committed, or a bool indicating whether
855 merging is committed.
855 merging is committed.
856
856
857 This returns baseformname with '.merge' appended if it is a merge,
857 This returns baseformname with '.merge' appended if it is a merge,
858 otherwise '.normal' is appended.
858 otherwise '.normal' is appended.
859 """
859 """
860 if isinstance(ctxorbool, bool):
860 if isinstance(ctxorbool, bool):
861 if ctxorbool:
861 if ctxorbool:
862 return baseformname + ".merge"
862 return baseformname + ".merge"
863 elif len(ctxorbool.parents()) > 1:
863 elif len(ctxorbool.parents()) > 1:
864 return baseformname + ".merge"
864 return baseformname + ".merge"
865
865
866 return baseformname + ".normal"
866 return baseformname + ".normal"
867
867
868 def getcommiteditor(edit=False, finishdesc=None, extramsg=None,
868 def getcommiteditor(edit=False, finishdesc=None, extramsg=None,
869 editform='', **opts):
869 editform='', **opts):
870 """get appropriate commit message editor according to '--edit' option
870 """get appropriate commit message editor according to '--edit' option
871
871
872 'finishdesc' is a function to be called with edited commit message
872 'finishdesc' is a function to be called with edited commit message
873 (= 'description' of the new changeset) just after editing, but
873 (= 'description' of the new changeset) just after editing, but
874 before checking empty-ness. It should return actual text to be
874 before checking empty-ness. It should return actual text to be
875 stored into history. This allows to change description before
875 stored into history. This allows to change description before
876 storing.
876 storing.
877
877
878 'extramsg' is a extra message to be shown in the editor instead of
878 'extramsg' is a extra message to be shown in the editor instead of
879 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
879 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
880 is automatically added.
880 is automatically added.
881
881
882 'editform' is a dot-separated list of names, to distinguish
882 'editform' is a dot-separated list of names, to distinguish
883 the purpose of commit text editing.
883 the purpose of commit text editing.
884
884
885 'getcommiteditor' returns 'commitforceeditor' regardless of
885 'getcommiteditor' returns 'commitforceeditor' regardless of
886 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
886 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
887 they are specific for usage in MQ.
887 they are specific for usage in MQ.
888 """
888 """
889 if edit or finishdesc or extramsg:
889 if edit or finishdesc or extramsg:
890 return lambda r, c, s: commitforceeditor(r, c, s,
890 return lambda r, c, s: commitforceeditor(r, c, s,
891 finishdesc=finishdesc,
891 finishdesc=finishdesc,
892 extramsg=extramsg,
892 extramsg=extramsg,
893 editform=editform)
893 editform=editform)
894 elif editform:
894 elif editform:
895 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
895 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
896 else:
896 else:
897 return commiteditor
897 return commiteditor
898
898
899 def _escapecommandtemplate(tmpl):
899 def _escapecommandtemplate(tmpl):
900 parts = []
900 parts = []
901 for typ, start, end in templater.scantemplate(tmpl, raw=True):
901 for typ, start, end in templater.scantemplate(tmpl, raw=True):
902 if typ == b'string':
902 if typ == b'string':
903 parts.append(stringutil.escapestr(tmpl[start:end]))
903 parts.append(stringutil.escapestr(tmpl[start:end]))
904 else:
904 else:
905 parts.append(tmpl[start:end])
905 parts.append(tmpl[start:end])
906 return b''.join(parts)
906 return b''.join(parts)
907
907
908 def rendercommandtemplate(ui, tmpl, props):
908 def rendercommandtemplate(ui, tmpl, props):
909 r"""Expand a literal template 'tmpl' in a way suitable for command line
909 r"""Expand a literal template 'tmpl' in a way suitable for command line
910
910
911 '\' in outermost string is not taken as an escape character because it
911 '\' in outermost string is not taken as an escape character because it
912 is a directory separator on Windows.
912 is a directory separator on Windows.
913
913
914 >>> from . import ui as uimod
914 >>> from . import ui as uimod
915 >>> ui = uimod.ui()
915 >>> ui = uimod.ui()
916 >>> rendercommandtemplate(ui, b'c:\\{path}', {b'path': b'foo'})
916 >>> rendercommandtemplate(ui, b'c:\\{path}', {b'path': b'foo'})
917 'c:\\foo'
917 'c:\\foo'
918 >>> rendercommandtemplate(ui, b'{"c:\\{path}"}', {'path': b'foo'})
918 >>> rendercommandtemplate(ui, b'{"c:\\{path}"}', {'path': b'foo'})
919 'c:{path}'
919 'c:{path}'
920 """
920 """
921 if not tmpl:
921 if not tmpl:
922 return tmpl
922 return tmpl
923 t = formatter.maketemplater(ui, _escapecommandtemplate(tmpl))
923 t = formatter.maketemplater(ui, _escapecommandtemplate(tmpl))
924 return t.renderdefault(props)
924 return t.renderdefault(props)
925
925
926 def rendertemplate(ctx, tmpl, props=None):
926 def rendertemplate(ctx, tmpl, props=None):
927 """Expand a literal template 'tmpl' byte-string against one changeset
927 """Expand a literal template 'tmpl' byte-string against one changeset
928
928
929 Each props item must be a stringify-able value or a callable returning
929 Each props item must be a stringify-able value or a callable returning
930 such value, i.e. no bare list nor dict should be passed.
930 such value, i.e. no bare list nor dict should be passed.
931 """
931 """
932 repo = ctx.repo()
932 repo = ctx.repo()
933 tres = formatter.templateresources(repo.ui, repo)
933 tres = formatter.templateresources(repo.ui, repo)
934 t = formatter.maketemplater(repo.ui, tmpl, defaults=templatekw.keywords,
934 t = formatter.maketemplater(repo.ui, tmpl, defaults=templatekw.keywords,
935 resources=tres)
935 resources=tres)
936 mapping = {'ctx': ctx}
936 mapping = {'ctx': ctx}
937 if props:
937 if props:
938 mapping.update(props)
938 mapping.update(props)
939 return t.renderdefault(mapping)
939 return t.renderdefault(mapping)
940
940
941 def _buildfntemplate(pat, total=None, seqno=None, revwidth=None, pathname=None):
941 def _buildfntemplate(pat, total=None, seqno=None, revwidth=None, pathname=None):
942 r"""Convert old-style filename format string to template string
942 r"""Convert old-style filename format string to template string
943
943
944 >>> _buildfntemplate(b'foo-%b-%n.patch', seqno=0)
944 >>> _buildfntemplate(b'foo-%b-%n.patch', seqno=0)
945 'foo-{reporoot|basename}-{seqno}.patch'
945 'foo-{reporoot|basename}-{seqno}.patch'
946 >>> _buildfntemplate(b'%R{tags % "{tag}"}%H')
946 >>> _buildfntemplate(b'%R{tags % "{tag}"}%H')
947 '{rev}{tags % "{tag}"}{node}'
947 '{rev}{tags % "{tag}"}{node}'
948
948
949 '\' in outermost strings has to be escaped because it is a directory
949 '\' in outermost strings has to be escaped because it is a directory
950 separator on Windows:
950 separator on Windows:
951
951
952 >>> _buildfntemplate(b'c:\\tmp\\%R\\%n.patch', seqno=0)
952 >>> _buildfntemplate(b'c:\\tmp\\%R\\%n.patch', seqno=0)
953 'c:\\\\tmp\\\\{rev}\\\\{seqno}.patch'
953 'c:\\\\tmp\\\\{rev}\\\\{seqno}.patch'
954 >>> _buildfntemplate(b'\\\\foo\\bar.patch')
954 >>> _buildfntemplate(b'\\\\foo\\bar.patch')
955 '\\\\\\\\foo\\\\bar.patch'
955 '\\\\\\\\foo\\\\bar.patch'
956 >>> _buildfntemplate(b'\\{tags % "{tag}"}')
956 >>> _buildfntemplate(b'\\{tags % "{tag}"}')
957 '\\\\{tags % "{tag}"}'
957 '\\\\{tags % "{tag}"}'
958
958
959 but inner strings follow the template rules (i.e. '\' is taken as an
959 but inner strings follow the template rules (i.e. '\' is taken as an
960 escape character):
960 escape character):
961
961
962 >>> _buildfntemplate(br'{"c:\tmp"}', seqno=0)
962 >>> _buildfntemplate(br'{"c:\tmp"}', seqno=0)
963 '{"c:\\tmp"}'
963 '{"c:\\tmp"}'
964 """
964 """
965 expander = {
965 expander = {
966 b'H': b'{node}',
966 b'H': b'{node}',
967 b'R': b'{rev}',
967 b'R': b'{rev}',
968 b'h': b'{node|short}',
968 b'h': b'{node|short}',
969 b'm': br'{sub(r"[^\w]", "_", desc|firstline)}',
969 b'm': br'{sub(r"[^\w]", "_", desc|firstline)}',
970 b'r': b'{if(revwidth, pad(rev, revwidth, "0", left=True), rev)}',
970 b'r': b'{if(revwidth, pad(rev, revwidth, "0", left=True), rev)}',
971 b'%': b'%',
971 b'%': b'%',
972 b'b': b'{reporoot|basename}',
972 b'b': b'{reporoot|basename}',
973 }
973 }
974 if total is not None:
974 if total is not None:
975 expander[b'N'] = b'{total}'
975 expander[b'N'] = b'{total}'
976 if seqno is not None:
976 if seqno is not None:
977 expander[b'n'] = b'{seqno}'
977 expander[b'n'] = b'{seqno}'
978 if total is not None and seqno is not None:
978 if total is not None and seqno is not None:
979 expander[b'n'] = b'{pad(seqno, total|stringify|count, "0", left=True)}'
979 expander[b'n'] = b'{pad(seqno, total|stringify|count, "0", left=True)}'
980 if pathname is not None:
980 if pathname is not None:
981 expander[b's'] = b'{pathname|basename}'
981 expander[b's'] = b'{pathname|basename}'
982 expander[b'd'] = b'{if(pathname|dirname, pathname|dirname, ".")}'
982 expander[b'd'] = b'{if(pathname|dirname, pathname|dirname, ".")}'
983 expander[b'p'] = b'{pathname}'
983 expander[b'p'] = b'{pathname}'
984
984
985 newname = []
985 newname = []
986 for typ, start, end in templater.scantemplate(pat, raw=True):
986 for typ, start, end in templater.scantemplate(pat, raw=True):
987 if typ != b'string':
987 if typ != b'string':
988 newname.append(pat[start:end])
988 newname.append(pat[start:end])
989 continue
989 continue
990 i = start
990 i = start
991 while i < end:
991 while i < end:
992 n = pat.find(b'%', i, end)
992 n = pat.find(b'%', i, end)
993 if n < 0:
993 if n < 0:
994 newname.append(stringutil.escapestr(pat[i:end]))
994 newname.append(stringutil.escapestr(pat[i:end]))
995 break
995 break
996 newname.append(stringutil.escapestr(pat[i:n]))
996 newname.append(stringutil.escapestr(pat[i:n]))
997 if n + 2 > end:
997 if n + 2 > end:
998 raise error.Abort(_("incomplete format spec in output "
998 raise error.Abort(_("incomplete format spec in output "
999 "filename"))
999 "filename"))
1000 c = pat[n + 1:n + 2]
1000 c = pat[n + 1:n + 2]
1001 i = n + 2
1001 i = n + 2
1002 try:
1002 try:
1003 newname.append(expander[c])
1003 newname.append(expander[c])
1004 except KeyError:
1004 except KeyError:
1005 raise error.Abort(_("invalid format spec '%%%s' in output "
1005 raise error.Abort(_("invalid format spec '%%%s' in output "
1006 "filename") % c)
1006 "filename") % c)
1007 return ''.join(newname)
1007 return ''.join(newname)
1008
1008
1009 def makefilename(ctx, pat, **props):
1009 def makefilename(ctx, pat, **props):
1010 if not pat:
1010 if not pat:
1011 return pat
1011 return pat
1012 tmpl = _buildfntemplate(pat, **props)
1012 tmpl = _buildfntemplate(pat, **props)
1013 # BUG: alias expansion shouldn't be made against template fragments
1013 # BUG: alias expansion shouldn't be made against template fragments
1014 # rewritten from %-format strings, but we have no easy way to partially
1014 # rewritten from %-format strings, but we have no easy way to partially
1015 # disable the expansion.
1015 # disable the expansion.
1016 return rendertemplate(ctx, tmpl, pycompat.byteskwargs(props))
1016 return rendertemplate(ctx, tmpl, pycompat.byteskwargs(props))
1017
1017
1018 def isstdiofilename(pat):
1018 def isstdiofilename(pat):
1019 """True if the given pat looks like a filename denoting stdin/stdout"""
1019 """True if the given pat looks like a filename denoting stdin/stdout"""
1020 return not pat or pat == '-'
1020 return not pat or pat == '-'
1021
1021
1022 class _unclosablefile(object):
1022 class _unclosablefile(object):
1023 def __init__(self, fp):
1023 def __init__(self, fp):
1024 self._fp = fp
1024 self._fp = fp
1025
1025
1026 def close(self):
1026 def close(self):
1027 pass
1027 pass
1028
1028
1029 def __iter__(self):
1029 def __iter__(self):
1030 return iter(self._fp)
1030 return iter(self._fp)
1031
1031
1032 def __getattr__(self, attr):
1032 def __getattr__(self, attr):
1033 return getattr(self._fp, attr)
1033 return getattr(self._fp, attr)
1034
1034
1035 def __enter__(self):
1035 def __enter__(self):
1036 return self
1036 return self
1037
1037
1038 def __exit__(self, exc_type, exc_value, exc_tb):
1038 def __exit__(self, exc_type, exc_value, exc_tb):
1039 pass
1039 pass
1040
1040
1041 def makefileobj(ctx, pat, mode='wb', **props):
1041 def makefileobj(ctx, pat, mode='wb', **props):
1042 writable = mode not in ('r', 'rb')
1042 writable = mode not in ('r', 'rb')
1043
1043
1044 if isstdiofilename(pat):
1044 if isstdiofilename(pat):
1045 repo = ctx.repo()
1045 repo = ctx.repo()
1046 if writable:
1046 if writable:
1047 fp = repo.ui.fout
1047 fp = repo.ui.fout
1048 else:
1048 else:
1049 fp = repo.ui.fin
1049 fp = repo.ui.fin
1050 return _unclosablefile(fp)
1050 return _unclosablefile(fp)
1051 fn = makefilename(ctx, pat, **props)
1051 fn = makefilename(ctx, pat, **props)
1052 return open(fn, mode)
1052 return open(fn, mode)
1053
1053
1054 def openstorage(repo, cmd, file_, opts, returnrevlog=False):
1054 def openstorage(repo, cmd, file_, opts, returnrevlog=False):
1055 """opens the changelog, manifest, a filelog or a given revlog"""
1055 """opens the changelog, manifest, a filelog or a given revlog"""
1056 cl = opts['changelog']
1056 cl = opts['changelog']
1057 mf = opts['manifest']
1057 mf = opts['manifest']
1058 dir = opts['dir']
1058 dir = opts['dir']
1059 msg = None
1059 msg = None
1060 if cl and mf:
1060 if cl and mf:
1061 msg = _('cannot specify --changelog and --manifest at the same time')
1061 msg = _('cannot specify --changelog and --manifest at the same time')
1062 elif cl and dir:
1062 elif cl and dir:
1063 msg = _('cannot specify --changelog and --dir at the same time')
1063 msg = _('cannot specify --changelog and --dir at the same time')
1064 elif cl or mf or dir:
1064 elif cl or mf or dir:
1065 if file_:
1065 if file_:
1066 msg = _('cannot specify filename with --changelog or --manifest')
1066 msg = _('cannot specify filename with --changelog or --manifest')
1067 elif not repo:
1067 elif not repo:
1068 msg = _('cannot specify --changelog or --manifest or --dir '
1068 msg = _('cannot specify --changelog or --manifest or --dir '
1069 'without a repository')
1069 'without a repository')
1070 if msg:
1070 if msg:
1071 raise error.Abort(msg)
1071 raise error.Abort(msg)
1072
1072
1073 r = None
1073 r = None
1074 if repo:
1074 if repo:
1075 if cl:
1075 if cl:
1076 r = repo.unfiltered().changelog
1076 r = repo.unfiltered().changelog
1077 elif dir:
1077 elif dir:
1078 if 'treemanifest' not in repo.requirements:
1078 if 'treemanifest' not in repo.requirements:
1079 raise error.Abort(_("--dir can only be used on repos with "
1079 raise error.Abort(_("--dir can only be used on repos with "
1080 "treemanifest enabled"))
1080 "treemanifest enabled"))
1081 if not dir.endswith('/'):
1081 if not dir.endswith('/'):
1082 dir = dir + '/'
1082 dir = dir + '/'
1083 dirlog = repo.manifestlog.getstorage(dir)
1083 dirlog = repo.manifestlog.getstorage(dir)
1084 if len(dirlog):
1084 if len(dirlog):
1085 r = dirlog
1085 r = dirlog
1086 elif mf:
1086 elif mf:
1087 r = repo.manifestlog.getstorage(b'')
1087 r = repo.manifestlog.getstorage(b'')
1088 elif file_:
1088 elif file_:
1089 filelog = repo.file(file_)
1089 filelog = repo.file(file_)
1090 if len(filelog):
1090 if len(filelog):
1091 r = filelog
1091 r = filelog
1092
1092
1093 # Not all storage may be revlogs. If requested, try to return an actual
1093 # Not all storage may be revlogs. If requested, try to return an actual
1094 # revlog instance.
1094 # revlog instance.
1095 if returnrevlog:
1095 if returnrevlog:
1096 if isinstance(r, revlog.revlog):
1096 if isinstance(r, revlog.revlog):
1097 pass
1097 pass
1098 elif util.safehasattr(r, '_revlog'):
1098 elif util.safehasattr(r, '_revlog'):
1099 r = r._revlog
1099 r = r._revlog
1100 elif r is not None:
1100 elif r is not None:
1101 raise error.Abort(_('%r does not appear to be a revlog') % r)
1101 raise error.Abort(_('%r does not appear to be a revlog') % r)
1102
1102
1103 if not r:
1103 if not r:
1104 if not returnrevlog:
1104 if not returnrevlog:
1105 raise error.Abort(_('cannot give path to non-revlog'))
1105 raise error.Abort(_('cannot give path to non-revlog'))
1106
1106
1107 if not file_:
1107 if not file_:
1108 raise error.CommandError(cmd, _('invalid arguments'))
1108 raise error.CommandError(cmd, _('invalid arguments'))
1109 if not os.path.isfile(file_):
1109 if not os.path.isfile(file_):
1110 raise error.Abort(_("revlog '%s' not found") % file_)
1110 raise error.Abort(_("revlog '%s' not found") % file_)
1111 r = revlog.revlog(vfsmod.vfs(encoding.getcwd(), audit=False),
1111 r = revlog.revlog(vfsmod.vfs(encoding.getcwd(), audit=False),
1112 file_[:-2] + ".i")
1112 file_[:-2] + ".i")
1113 return r
1113 return r
1114
1114
1115 def openrevlog(repo, cmd, file_, opts):
1115 def openrevlog(repo, cmd, file_, opts):
1116 """Obtain a revlog backing storage of an item.
1116 """Obtain a revlog backing storage of an item.
1117
1117
1118 This is similar to ``openstorage()`` except it always returns a revlog.
1118 This is similar to ``openstorage()`` except it always returns a revlog.
1119
1119
1120 In most cases, a caller cares about the main storage object - not the
1120 In most cases, a caller cares about the main storage object - not the
1121 revlog backing it. Therefore, this function should only be used by code
1121 revlog backing it. Therefore, this function should only be used by code
1122 that needs to examine low-level revlog implementation details. e.g. debug
1122 that needs to examine low-level revlog implementation details. e.g. debug
1123 commands.
1123 commands.
1124 """
1124 """
1125 return openstorage(repo, cmd, file_, opts, returnrevlog=True)
1125 return openstorage(repo, cmd, file_, opts, returnrevlog=True)
1126
1126
1127 def copy(ui, repo, pats, opts, rename=False):
1127 def copy(ui, repo, pats, opts, rename=False):
1128 # called with the repo lock held
1128 # called with the repo lock held
1129 #
1129 #
1130 # hgsep => pathname that uses "/" to separate directories
1130 # hgsep => pathname that uses "/" to separate directories
1131 # ossep => pathname that uses os.sep to separate directories
1131 # ossep => pathname that uses os.sep to separate directories
1132 cwd = repo.getcwd()
1132 cwd = repo.getcwd()
1133 targets = {}
1133 targets = {}
1134 after = opts.get("after")
1134 after = opts.get("after")
1135 dryrun = opts.get("dry_run")
1135 dryrun = opts.get("dry_run")
1136 wctx = repo[None]
1136 wctx = repo[None]
1137
1137
1138 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
1138 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
1139 def walkpat(pat):
1139 def walkpat(pat):
1140 srcs = []
1140 srcs = []
1141 if after:
1141 if after:
1142 badstates = '?'
1142 badstates = '?'
1143 else:
1143 else:
1144 badstates = '?r'
1144 badstates = '?r'
1145 m = scmutil.match(wctx, [pat], opts, globbed=True)
1145 m = scmutil.match(wctx, [pat], opts, globbed=True)
1146 for abs in wctx.walk(m):
1146 for abs in wctx.walk(m):
1147 state = repo.dirstate[abs]
1147 state = repo.dirstate[abs]
1148 rel = uipathfn(abs)
1148 rel = uipathfn(abs)
1149 exact = m.exact(abs)
1149 exact = m.exact(abs)
1150 if state in badstates:
1150 if state in badstates:
1151 if exact and state == '?':
1151 if exact and state == '?':
1152 ui.warn(_('%s: not copying - file is not managed\n') % rel)
1152 ui.warn(_('%s: not copying - file is not managed\n') % rel)
1153 if exact and state == 'r':
1153 if exact and state == 'r':
1154 ui.warn(_('%s: not copying - file has been marked for'
1154 ui.warn(_('%s: not copying - file has been marked for'
1155 ' remove\n') % rel)
1155 ' remove\n') % rel)
1156 continue
1156 continue
1157 # abs: hgsep
1157 # abs: hgsep
1158 # rel: ossep
1158 # rel: ossep
1159 srcs.append((abs, rel, exact))
1159 srcs.append((abs, rel, exact))
1160 return srcs
1160 return srcs
1161
1161
1162 # abssrc: hgsep
1162 # abssrc: hgsep
1163 # relsrc: ossep
1163 # relsrc: ossep
1164 # otarget: ossep
1164 # otarget: ossep
1165 def copyfile(abssrc, relsrc, otarget, exact):
1165 def copyfile(abssrc, relsrc, otarget, exact):
1166 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
1166 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
1167 if '/' in abstarget:
1167 if '/' in abstarget:
1168 # We cannot normalize abstarget itself, this would prevent
1168 # We cannot normalize abstarget itself, this would prevent
1169 # case only renames, like a => A.
1169 # case only renames, like a => A.
1170 abspath, absname = abstarget.rsplit('/', 1)
1170 abspath, absname = abstarget.rsplit('/', 1)
1171 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
1171 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
1172 reltarget = repo.pathto(abstarget, cwd)
1172 reltarget = repo.pathto(abstarget, cwd)
1173 target = repo.wjoin(abstarget)
1173 target = repo.wjoin(abstarget)
1174 src = repo.wjoin(abssrc)
1174 src = repo.wjoin(abssrc)
1175 state = repo.dirstate[abstarget]
1175 state = repo.dirstate[abstarget]
1176
1176
1177 scmutil.checkportable(ui, abstarget)
1177 scmutil.checkportable(ui, abstarget)
1178
1178
1179 # check for collisions
1179 # check for collisions
1180 prevsrc = targets.get(abstarget)
1180 prevsrc = targets.get(abstarget)
1181 if prevsrc is not None:
1181 if prevsrc is not None:
1182 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
1182 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
1183 (reltarget, repo.pathto(abssrc, cwd),
1183 (reltarget, repo.pathto(abssrc, cwd),
1184 repo.pathto(prevsrc, cwd)))
1184 repo.pathto(prevsrc, cwd)))
1185 return True # report a failure
1185 return True # report a failure
1186
1186
1187 # check for overwrites
1187 # check for overwrites
1188 exists = os.path.lexists(target)
1188 exists = os.path.lexists(target)
1189 samefile = False
1189 samefile = False
1190 if exists and abssrc != abstarget:
1190 if exists and abssrc != abstarget:
1191 if (repo.dirstate.normalize(abssrc) ==
1191 if (repo.dirstate.normalize(abssrc) ==
1192 repo.dirstate.normalize(abstarget)):
1192 repo.dirstate.normalize(abstarget)):
1193 if not rename:
1193 if not rename:
1194 ui.warn(_("%s: can't copy - same file\n") % reltarget)
1194 ui.warn(_("%s: can't copy - same file\n") % reltarget)
1195 return True # report a failure
1195 return True # report a failure
1196 exists = False
1196 exists = False
1197 samefile = True
1197 samefile = True
1198
1198
1199 if not after and exists or after and state in 'mn':
1199 if not after and exists or after and state in 'mn':
1200 if not opts['force']:
1200 if not opts['force']:
1201 if state in 'mn':
1201 if state in 'mn':
1202 msg = _('%s: not overwriting - file already committed\n')
1202 msg = _('%s: not overwriting - file already committed\n')
1203 if after:
1203 if after:
1204 flags = '--after --force'
1204 flags = '--after --force'
1205 else:
1205 else:
1206 flags = '--force'
1206 flags = '--force'
1207 if rename:
1207 if rename:
1208 hint = _("('hg rename %s' to replace the file by "
1208 hint = _("('hg rename %s' to replace the file by "
1209 'recording a rename)\n') % flags
1209 'recording a rename)\n') % flags
1210 else:
1210 else:
1211 hint = _("('hg copy %s' to replace the file by "
1211 hint = _("('hg copy %s' to replace the file by "
1212 'recording a copy)\n') % flags
1212 'recording a copy)\n') % flags
1213 else:
1213 else:
1214 msg = _('%s: not overwriting - file exists\n')
1214 msg = _('%s: not overwriting - file exists\n')
1215 if rename:
1215 if rename:
1216 hint = _("('hg rename --after' to record the rename)\n")
1216 hint = _("('hg rename --after' to record the rename)\n")
1217 else:
1217 else:
1218 hint = _("('hg copy --after' to record the copy)\n")
1218 hint = _("('hg copy --after' to record the copy)\n")
1219 ui.warn(msg % reltarget)
1219 ui.warn(msg % reltarget)
1220 ui.warn(hint)
1220 ui.warn(hint)
1221 return True # report a failure
1221 return True # report a failure
1222
1222
1223 if after:
1223 if after:
1224 if not exists:
1224 if not exists:
1225 if rename:
1225 if rename:
1226 ui.warn(_('%s: not recording move - %s does not exist\n') %
1226 ui.warn(_('%s: not recording move - %s does not exist\n') %
1227 (relsrc, reltarget))
1227 (relsrc, reltarget))
1228 else:
1228 else:
1229 ui.warn(_('%s: not recording copy - %s does not exist\n') %
1229 ui.warn(_('%s: not recording copy - %s does not exist\n') %
1230 (relsrc, reltarget))
1230 (relsrc, reltarget))
1231 return True # report a failure
1231 return True # report a failure
1232 elif not dryrun:
1232 elif not dryrun:
1233 try:
1233 try:
1234 if exists:
1234 if exists:
1235 os.unlink(target)
1235 os.unlink(target)
1236 targetdir = os.path.dirname(target) or '.'
1236 targetdir = os.path.dirname(target) or '.'
1237 if not os.path.isdir(targetdir):
1237 if not os.path.isdir(targetdir):
1238 os.makedirs(targetdir)
1238 os.makedirs(targetdir)
1239 if samefile:
1239 if samefile:
1240 tmp = target + "~hgrename"
1240 tmp = target + "~hgrename"
1241 os.rename(src, tmp)
1241 os.rename(src, tmp)
1242 os.rename(tmp, target)
1242 os.rename(tmp, target)
1243 else:
1243 else:
1244 # Preserve stat info on renames, not on copies; this matches
1244 # Preserve stat info on renames, not on copies; this matches
1245 # Linux CLI behavior.
1245 # Linux CLI behavior.
1246 util.copyfile(src, target, copystat=rename)
1246 util.copyfile(src, target, copystat=rename)
1247 srcexists = True
1247 srcexists = True
1248 except IOError as inst:
1248 except IOError as inst:
1249 if inst.errno == errno.ENOENT:
1249 if inst.errno == errno.ENOENT:
1250 ui.warn(_('%s: deleted in working directory\n') % relsrc)
1250 ui.warn(_('%s: deleted in working directory\n') % relsrc)
1251 srcexists = False
1251 srcexists = False
1252 else:
1252 else:
1253 ui.warn(_('%s: cannot copy - %s\n') %
1253 ui.warn(_('%s: cannot copy - %s\n') %
1254 (relsrc, encoding.strtolocal(inst.strerror)))
1254 (relsrc, encoding.strtolocal(inst.strerror)))
1255 return True # report a failure
1255 return True # report a failure
1256
1256
1257 if ui.verbose or not exact:
1257 if ui.verbose or not exact:
1258 if rename:
1258 if rename:
1259 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
1259 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
1260 else:
1260 else:
1261 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
1261 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
1262
1262
1263 targets[abstarget] = abssrc
1263 targets[abstarget] = abssrc
1264
1264
1265 # fix up dirstate
1265 # fix up dirstate
1266 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
1266 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
1267 dryrun=dryrun, cwd=cwd)
1267 dryrun=dryrun, cwd=cwd)
1268 if rename and not dryrun:
1268 if rename and not dryrun:
1269 if not after and srcexists and not samefile:
1269 if not after and srcexists and not samefile:
1270 rmdir = repo.ui.configbool('experimental', 'removeemptydirs')
1270 rmdir = repo.ui.configbool('experimental', 'removeemptydirs')
1271 repo.wvfs.unlinkpath(abssrc, rmdir=rmdir)
1271 repo.wvfs.unlinkpath(abssrc, rmdir=rmdir)
1272 wctx.forget([abssrc])
1272 wctx.forget([abssrc])
1273
1273
1274 # pat: ossep
1274 # pat: ossep
1275 # dest ossep
1275 # dest ossep
1276 # srcs: list of (hgsep, hgsep, ossep, bool)
1276 # srcs: list of (hgsep, hgsep, ossep, bool)
1277 # return: function that takes hgsep and returns ossep
1277 # return: function that takes hgsep and returns ossep
1278 def targetpathfn(pat, dest, srcs):
1278 def targetpathfn(pat, dest, srcs):
1279 if os.path.isdir(pat):
1279 if os.path.isdir(pat):
1280 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1280 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1281 abspfx = util.localpath(abspfx)
1281 abspfx = util.localpath(abspfx)
1282 if destdirexists:
1282 if destdirexists:
1283 striplen = len(os.path.split(abspfx)[0])
1283 striplen = len(os.path.split(abspfx)[0])
1284 else:
1284 else:
1285 striplen = len(abspfx)
1285 striplen = len(abspfx)
1286 if striplen:
1286 if striplen:
1287 striplen += len(pycompat.ossep)
1287 striplen += len(pycompat.ossep)
1288 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
1288 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
1289 elif destdirexists:
1289 elif destdirexists:
1290 res = lambda p: os.path.join(dest,
1290 res = lambda p: os.path.join(dest,
1291 os.path.basename(util.localpath(p)))
1291 os.path.basename(util.localpath(p)))
1292 else:
1292 else:
1293 res = lambda p: dest
1293 res = lambda p: dest
1294 return res
1294 return res
1295
1295
1296 # pat: ossep
1296 # pat: ossep
1297 # dest ossep
1297 # dest ossep
1298 # srcs: list of (hgsep, hgsep, ossep, bool)
1298 # srcs: list of (hgsep, hgsep, ossep, bool)
1299 # return: function that takes hgsep and returns ossep
1299 # return: function that takes hgsep and returns ossep
1300 def targetpathafterfn(pat, dest, srcs):
1300 def targetpathafterfn(pat, dest, srcs):
1301 if matchmod.patkind(pat):
1301 if matchmod.patkind(pat):
1302 # a mercurial pattern
1302 # a mercurial pattern
1303 res = lambda p: os.path.join(dest,
1303 res = lambda p: os.path.join(dest,
1304 os.path.basename(util.localpath(p)))
1304 os.path.basename(util.localpath(p)))
1305 else:
1305 else:
1306 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1306 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1307 if len(abspfx) < len(srcs[0][0]):
1307 if len(abspfx) < len(srcs[0][0]):
1308 # A directory. Either the target path contains the last
1308 # A directory. Either the target path contains the last
1309 # component of the source path or it does not.
1309 # component of the source path or it does not.
1310 def evalpath(striplen):
1310 def evalpath(striplen):
1311 score = 0
1311 score = 0
1312 for s in srcs:
1312 for s in srcs:
1313 t = os.path.join(dest, util.localpath(s[0])[striplen:])
1313 t = os.path.join(dest, util.localpath(s[0])[striplen:])
1314 if os.path.lexists(t):
1314 if os.path.lexists(t):
1315 score += 1
1315 score += 1
1316 return score
1316 return score
1317
1317
1318 abspfx = util.localpath(abspfx)
1318 abspfx = util.localpath(abspfx)
1319 striplen = len(abspfx)
1319 striplen = len(abspfx)
1320 if striplen:
1320 if striplen:
1321 striplen += len(pycompat.ossep)
1321 striplen += len(pycompat.ossep)
1322 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
1322 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
1323 score = evalpath(striplen)
1323 score = evalpath(striplen)
1324 striplen1 = len(os.path.split(abspfx)[0])
1324 striplen1 = len(os.path.split(abspfx)[0])
1325 if striplen1:
1325 if striplen1:
1326 striplen1 += len(pycompat.ossep)
1326 striplen1 += len(pycompat.ossep)
1327 if evalpath(striplen1) > score:
1327 if evalpath(striplen1) > score:
1328 striplen = striplen1
1328 striplen = striplen1
1329 res = lambda p: os.path.join(dest,
1329 res = lambda p: os.path.join(dest,
1330 util.localpath(p)[striplen:])
1330 util.localpath(p)[striplen:])
1331 else:
1331 else:
1332 # a file
1332 # a file
1333 if destdirexists:
1333 if destdirexists:
1334 res = lambda p: os.path.join(dest,
1334 res = lambda p: os.path.join(dest,
1335 os.path.basename(util.localpath(p)))
1335 os.path.basename(util.localpath(p)))
1336 else:
1336 else:
1337 res = lambda p: dest
1337 res = lambda p: dest
1338 return res
1338 return res
1339
1339
1340 pats = scmutil.expandpats(pats)
1340 pats = scmutil.expandpats(pats)
1341 if not pats:
1341 if not pats:
1342 raise error.Abort(_('no source or destination specified'))
1342 raise error.Abort(_('no source or destination specified'))
1343 if len(pats) == 1:
1343 if len(pats) == 1:
1344 raise error.Abort(_('no destination specified'))
1344 raise error.Abort(_('no destination specified'))
1345 dest = pats.pop()
1345 dest = pats.pop()
1346 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
1346 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
1347 if not destdirexists:
1347 if not destdirexists:
1348 if len(pats) > 1 or matchmod.patkind(pats[0]):
1348 if len(pats) > 1 or matchmod.patkind(pats[0]):
1349 raise error.Abort(_('with multiple sources, destination must be an '
1349 raise error.Abort(_('with multiple sources, destination must be an '
1350 'existing directory'))
1350 'existing directory'))
1351 if util.endswithsep(dest):
1351 if util.endswithsep(dest):
1352 raise error.Abort(_('destination %s is not a directory') % dest)
1352 raise error.Abort(_('destination %s is not a directory') % dest)
1353
1353
1354 tfn = targetpathfn
1354 tfn = targetpathfn
1355 if after:
1355 if after:
1356 tfn = targetpathafterfn
1356 tfn = targetpathafterfn
1357 copylist = []
1357 copylist = []
1358 for pat in pats:
1358 for pat in pats:
1359 srcs = walkpat(pat)
1359 srcs = walkpat(pat)
1360 if not srcs:
1360 if not srcs:
1361 continue
1361 continue
1362 copylist.append((tfn(pat, dest, srcs), srcs))
1362 copylist.append((tfn(pat, dest, srcs), srcs))
1363 if not copylist:
1363 if not copylist:
1364 raise error.Abort(_('no files to copy'))
1364 raise error.Abort(_('no files to copy'))
1365
1365
1366 errors = 0
1366 errors = 0
1367 for targetpath, srcs in copylist:
1367 for targetpath, srcs in copylist:
1368 for abssrc, relsrc, exact in srcs:
1368 for abssrc, relsrc, exact in srcs:
1369 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
1369 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
1370 errors += 1
1370 errors += 1
1371
1371
1372 return errors != 0
1372 return errors != 0
1373
1373
1374 ## facility to let extension process additional data into an import patch
1374 ## facility to let extension process additional data into an import patch
1375 # list of identifier to be executed in order
1375 # list of identifier to be executed in order
1376 extrapreimport = [] # run before commit
1376 extrapreimport = [] # run before commit
1377 extrapostimport = [] # run after commit
1377 extrapostimport = [] # run after commit
1378 # mapping from identifier to actual import function
1378 # mapping from identifier to actual import function
1379 #
1379 #
1380 # 'preimport' are run before the commit is made and are provided the following
1380 # 'preimport' are run before the commit is made and are provided the following
1381 # arguments:
1381 # arguments:
1382 # - repo: the localrepository instance,
1382 # - repo: the localrepository instance,
1383 # - patchdata: data extracted from patch header (cf m.patch.patchheadermap),
1383 # - patchdata: data extracted from patch header (cf m.patch.patchheadermap),
1384 # - extra: the future extra dictionary of the changeset, please mutate it,
1384 # - extra: the future extra dictionary of the changeset, please mutate it,
1385 # - opts: the import options.
1385 # - opts: the import options.
1386 # XXX ideally, we would just pass an ctx ready to be computed, that would allow
1386 # XXX ideally, we would just pass an ctx ready to be computed, that would allow
1387 # mutation of in memory commit and more. Feel free to rework the code to get
1387 # mutation of in memory commit and more. Feel free to rework the code to get
1388 # there.
1388 # there.
1389 extrapreimportmap = {}
1389 extrapreimportmap = {}
1390 # 'postimport' are run after the commit is made and are provided the following
1390 # 'postimport' are run after the commit is made and are provided the following
1391 # argument:
1391 # argument:
1392 # - ctx: the changectx created by import.
1392 # - ctx: the changectx created by import.
1393 extrapostimportmap = {}
1393 extrapostimportmap = {}
1394
1394
1395 def tryimportone(ui, repo, patchdata, parents, opts, msgs, updatefunc):
1395 def tryimportone(ui, repo, patchdata, parents, opts, msgs, updatefunc):
1396 """Utility function used by commands.import to import a single patch
1396 """Utility function used by commands.import to import a single patch
1397
1397
1398 This function is explicitly defined here to help the evolve extension to
1398 This function is explicitly defined here to help the evolve extension to
1399 wrap this part of the import logic.
1399 wrap this part of the import logic.
1400
1400
1401 The API is currently a bit ugly because it a simple code translation from
1401 The API is currently a bit ugly because it a simple code translation from
1402 the import command. Feel free to make it better.
1402 the import command. Feel free to make it better.
1403
1403
1404 :patchdata: a dictionary containing parsed patch data (such as from
1404 :patchdata: a dictionary containing parsed patch data (such as from
1405 ``patch.extract()``)
1405 ``patch.extract()``)
1406 :parents: nodes that will be parent of the created commit
1406 :parents: nodes that will be parent of the created commit
1407 :opts: the full dict of option passed to the import command
1407 :opts: the full dict of option passed to the import command
1408 :msgs: list to save commit message to.
1408 :msgs: list to save commit message to.
1409 (used in case we need to save it when failing)
1409 (used in case we need to save it when failing)
1410 :updatefunc: a function that update a repo to a given node
1410 :updatefunc: a function that update a repo to a given node
1411 updatefunc(<repo>, <node>)
1411 updatefunc(<repo>, <node>)
1412 """
1412 """
1413 # avoid cycle context -> subrepo -> cmdutil
1413 # avoid cycle context -> subrepo -> cmdutil
1414 from . import context
1414 from . import context
1415
1415
1416 tmpname = patchdata.get('filename')
1416 tmpname = patchdata.get('filename')
1417 message = patchdata.get('message')
1417 message = patchdata.get('message')
1418 user = opts.get('user') or patchdata.get('user')
1418 user = opts.get('user') or patchdata.get('user')
1419 date = opts.get('date') or patchdata.get('date')
1419 date = opts.get('date') or patchdata.get('date')
1420 branch = patchdata.get('branch')
1420 branch = patchdata.get('branch')
1421 nodeid = patchdata.get('nodeid')
1421 nodeid = patchdata.get('nodeid')
1422 p1 = patchdata.get('p1')
1422 p1 = patchdata.get('p1')
1423 p2 = patchdata.get('p2')
1423 p2 = patchdata.get('p2')
1424
1424
1425 nocommit = opts.get('no_commit')
1425 nocommit = opts.get('no_commit')
1426 importbranch = opts.get('import_branch')
1426 importbranch = opts.get('import_branch')
1427 update = not opts.get('bypass')
1427 update = not opts.get('bypass')
1428 strip = opts["strip"]
1428 strip = opts["strip"]
1429 prefix = opts["prefix"]
1429 prefix = opts["prefix"]
1430 sim = float(opts.get('similarity') or 0)
1430 sim = float(opts.get('similarity') or 0)
1431
1431
1432 if not tmpname:
1432 if not tmpname:
1433 return None, None, False
1433 return None, None, False
1434
1434
1435 rejects = False
1435 rejects = False
1436
1436
1437 cmdline_message = logmessage(ui, opts)
1437 cmdline_message = logmessage(ui, opts)
1438 if cmdline_message:
1438 if cmdline_message:
1439 # pickup the cmdline msg
1439 # pickup the cmdline msg
1440 message = cmdline_message
1440 message = cmdline_message
1441 elif message:
1441 elif message:
1442 # pickup the patch msg
1442 # pickup the patch msg
1443 message = message.strip()
1443 message = message.strip()
1444 else:
1444 else:
1445 # launch the editor
1445 # launch the editor
1446 message = None
1446 message = None
1447 ui.debug('message:\n%s\n' % (message or ''))
1447 ui.debug('message:\n%s\n' % (message or ''))
1448
1448
1449 if len(parents) == 1:
1449 if len(parents) == 1:
1450 parents.append(repo[nullid])
1450 parents.append(repo[nullid])
1451 if opts.get('exact'):
1451 if opts.get('exact'):
1452 if not nodeid or not p1:
1452 if not nodeid or not p1:
1453 raise error.Abort(_('not a Mercurial patch'))
1453 raise error.Abort(_('not a Mercurial patch'))
1454 p1 = repo[p1]
1454 p1 = repo[p1]
1455 p2 = repo[p2 or nullid]
1455 p2 = repo[p2 or nullid]
1456 elif p2:
1456 elif p2:
1457 try:
1457 try:
1458 p1 = repo[p1]
1458 p1 = repo[p1]
1459 p2 = repo[p2]
1459 p2 = repo[p2]
1460 # Without any options, consider p2 only if the
1460 # Without any options, consider p2 only if the
1461 # patch is being applied on top of the recorded
1461 # patch is being applied on top of the recorded
1462 # first parent.
1462 # first parent.
1463 if p1 != parents[0]:
1463 if p1 != parents[0]:
1464 p1 = parents[0]
1464 p1 = parents[0]
1465 p2 = repo[nullid]
1465 p2 = repo[nullid]
1466 except error.RepoError:
1466 except error.RepoError:
1467 p1, p2 = parents
1467 p1, p2 = parents
1468 if p2.node() == nullid:
1468 if p2.node() == nullid:
1469 ui.warn(_("warning: import the patch as a normal revision\n"
1469 ui.warn(_("warning: import the patch as a normal revision\n"
1470 "(use --exact to import the patch as a merge)\n"))
1470 "(use --exact to import the patch as a merge)\n"))
1471 else:
1471 else:
1472 p1, p2 = parents
1472 p1, p2 = parents
1473
1473
1474 n = None
1474 n = None
1475 if update:
1475 if update:
1476 if p1 != parents[0]:
1476 if p1 != parents[0]:
1477 updatefunc(repo, p1.node())
1477 updatefunc(repo, p1.node())
1478 if p2 != parents[1]:
1478 if p2 != parents[1]:
1479 repo.setparents(p1.node(), p2.node())
1479 repo.setparents(p1.node(), p2.node())
1480
1480
1481 if opts.get('exact') or importbranch:
1481 if opts.get('exact') or importbranch:
1482 repo.dirstate.setbranch(branch or 'default')
1482 repo.dirstate.setbranch(branch or 'default')
1483
1483
1484 partial = opts.get('partial', False)
1484 partial = opts.get('partial', False)
1485 files = set()
1485 files = set()
1486 try:
1486 try:
1487 patch.patch(ui, repo, tmpname, strip=strip, prefix=prefix,
1487 patch.patch(ui, repo, tmpname, strip=strip, prefix=prefix,
1488 files=files, eolmode=None, similarity=sim / 100.0)
1488 files=files, eolmode=None, similarity=sim / 100.0)
1489 except error.PatchError as e:
1489 except error.PatchError as e:
1490 if not partial:
1490 if not partial:
1491 raise error.Abort(pycompat.bytestr(e))
1491 raise error.Abort(pycompat.bytestr(e))
1492 if partial:
1492 if partial:
1493 rejects = True
1493 rejects = True
1494
1494
1495 files = list(files)
1495 files = list(files)
1496 if nocommit:
1496 if nocommit:
1497 if message:
1497 if message:
1498 msgs.append(message)
1498 msgs.append(message)
1499 else:
1499 else:
1500 if opts.get('exact') or p2:
1500 if opts.get('exact') or p2:
1501 # If you got here, you either use --force and know what
1501 # If you got here, you either use --force and know what
1502 # you are doing or used --exact or a merge patch while
1502 # you are doing or used --exact or a merge patch while
1503 # being updated to its first parent.
1503 # being updated to its first parent.
1504 m = None
1504 m = None
1505 else:
1505 else:
1506 m = scmutil.matchfiles(repo, files or [])
1506 m = scmutil.matchfiles(repo, files or [])
1507 editform = mergeeditform(repo[None], 'import.normal')
1507 editform = mergeeditform(repo[None], 'import.normal')
1508 if opts.get('exact'):
1508 if opts.get('exact'):
1509 editor = None
1509 editor = None
1510 else:
1510 else:
1511 editor = getcommiteditor(editform=editform,
1511 editor = getcommiteditor(editform=editform,
1512 **pycompat.strkwargs(opts))
1512 **pycompat.strkwargs(opts))
1513 extra = {}
1513 extra = {}
1514 for idfunc in extrapreimport:
1514 for idfunc in extrapreimport:
1515 extrapreimportmap[idfunc](repo, patchdata, extra, opts)
1515 extrapreimportmap[idfunc](repo, patchdata, extra, opts)
1516 overrides = {}
1516 overrides = {}
1517 if partial:
1517 if partial:
1518 overrides[('ui', 'allowemptycommit')] = True
1518 overrides[('ui', 'allowemptycommit')] = True
1519 with repo.ui.configoverride(overrides, 'import'):
1519 with repo.ui.configoverride(overrides, 'import'):
1520 n = repo.commit(message, user,
1520 n = repo.commit(message, user,
1521 date, match=m,
1521 date, match=m,
1522 editor=editor, extra=extra)
1522 editor=editor, extra=extra)
1523 for idfunc in extrapostimport:
1523 for idfunc in extrapostimport:
1524 extrapostimportmap[idfunc](repo[n])
1524 extrapostimportmap[idfunc](repo[n])
1525 else:
1525 else:
1526 if opts.get('exact') or importbranch:
1526 if opts.get('exact') or importbranch:
1527 branch = branch or 'default'
1527 branch = branch or 'default'
1528 else:
1528 else:
1529 branch = p1.branch()
1529 branch = p1.branch()
1530 store = patch.filestore()
1530 store = patch.filestore()
1531 try:
1531 try:
1532 files = set()
1532 files = set()
1533 try:
1533 try:
1534 patch.patchrepo(ui, repo, p1, store, tmpname, strip, prefix,
1534 patch.patchrepo(ui, repo, p1, store, tmpname, strip, prefix,
1535 files, eolmode=None)
1535 files, eolmode=None)
1536 except error.PatchError as e:
1536 except error.PatchError as e:
1537 raise error.Abort(stringutil.forcebytestr(e))
1537 raise error.Abort(stringutil.forcebytestr(e))
1538 if opts.get('exact'):
1538 if opts.get('exact'):
1539 editor = None
1539 editor = None
1540 else:
1540 else:
1541 editor = getcommiteditor(editform='import.bypass')
1541 editor = getcommiteditor(editform='import.bypass')
1542 memctx = context.memctx(repo, (p1.node(), p2.node()),
1542 memctx = context.memctx(repo, (p1.node(), p2.node()),
1543 message,
1543 message,
1544 files=files,
1544 files=files,
1545 filectxfn=store,
1545 filectxfn=store,
1546 user=user,
1546 user=user,
1547 date=date,
1547 date=date,
1548 branch=branch,
1548 branch=branch,
1549 editor=editor)
1549 editor=editor)
1550 n = memctx.commit()
1550 n = memctx.commit()
1551 finally:
1551 finally:
1552 store.close()
1552 store.close()
1553 if opts.get('exact') and nocommit:
1553 if opts.get('exact') and nocommit:
1554 # --exact with --no-commit is still useful in that it does merge
1554 # --exact with --no-commit is still useful in that it does merge
1555 # and branch bits
1555 # and branch bits
1556 ui.warn(_("warning: can't check exact import with --no-commit\n"))
1556 ui.warn(_("warning: can't check exact import with --no-commit\n"))
1557 elif opts.get('exact') and (not n or hex(n) != nodeid):
1557 elif opts.get('exact') and (not n or hex(n) != nodeid):
1558 raise error.Abort(_('patch is damaged or loses information'))
1558 raise error.Abort(_('patch is damaged or loses information'))
1559 msg = _('applied to working directory')
1559 msg = _('applied to working directory')
1560 if n:
1560 if n:
1561 # i18n: refers to a short changeset id
1561 # i18n: refers to a short changeset id
1562 msg = _('created %s') % short(n)
1562 msg = _('created %s') % short(n)
1563 return msg, n, rejects
1563 return msg, n, rejects
1564
1564
1565 # facility to let extensions include additional data in an exported patch
1565 # facility to let extensions include additional data in an exported patch
1566 # list of identifiers to be executed in order
1566 # list of identifiers to be executed in order
1567 extraexport = []
1567 extraexport = []
1568 # mapping from identifier to actual export function
1568 # mapping from identifier to actual export function
1569 # function as to return a string to be added to the header or None
1569 # function as to return a string to be added to the header or None
1570 # it is given two arguments (sequencenumber, changectx)
1570 # it is given two arguments (sequencenumber, changectx)
1571 extraexportmap = {}
1571 extraexportmap = {}
1572
1572
1573 def _exportsingle(repo, ctx, fm, match, switch_parent, seqno, diffopts):
1573 def _exportsingle(repo, ctx, fm, match, switch_parent, seqno, diffopts):
1574 node = scmutil.binnode(ctx)
1574 node = scmutil.binnode(ctx)
1575 parents = [p.node() for p in ctx.parents() if p]
1575 parents = [p.node() for p in ctx.parents() if p]
1576 branch = ctx.branch()
1576 branch = ctx.branch()
1577 if switch_parent:
1577 if switch_parent:
1578 parents.reverse()
1578 parents.reverse()
1579
1579
1580 if parents:
1580 if parents:
1581 prev = parents[0]
1581 prev = parents[0]
1582 else:
1582 else:
1583 prev = nullid
1583 prev = nullid
1584
1584
1585 fm.context(ctx=ctx)
1585 fm.context(ctx=ctx)
1586 fm.plain('# HG changeset patch\n')
1586 fm.plain('# HG changeset patch\n')
1587 fm.write('user', '# User %s\n', ctx.user())
1587 fm.write('user', '# User %s\n', ctx.user())
1588 fm.plain('# Date %d %d\n' % ctx.date())
1588 fm.plain('# Date %d %d\n' % ctx.date())
1589 fm.write('date', '# %s\n', fm.formatdate(ctx.date()))
1589 fm.write('date', '# %s\n', fm.formatdate(ctx.date()))
1590 fm.condwrite(branch and branch != 'default',
1590 fm.condwrite(branch and branch != 'default',
1591 'branch', '# Branch %s\n', branch)
1591 'branch', '# Branch %s\n', branch)
1592 fm.write('node', '# Node ID %s\n', hex(node))
1592 fm.write('node', '# Node ID %s\n', hex(node))
1593 fm.plain('# Parent %s\n' % hex(prev))
1593 fm.plain('# Parent %s\n' % hex(prev))
1594 if len(parents) > 1:
1594 if len(parents) > 1:
1595 fm.plain('# Parent %s\n' % hex(parents[1]))
1595 fm.plain('# Parent %s\n' % hex(parents[1]))
1596 fm.data(parents=fm.formatlist(pycompat.maplist(hex, parents), name='node'))
1596 fm.data(parents=fm.formatlist(pycompat.maplist(hex, parents), name='node'))
1597
1597
1598 # TODO: redesign extraexportmap function to support formatter
1598 # TODO: redesign extraexportmap function to support formatter
1599 for headerid in extraexport:
1599 for headerid in extraexport:
1600 header = extraexportmap[headerid](seqno, ctx)
1600 header = extraexportmap[headerid](seqno, ctx)
1601 if header is not None:
1601 if header is not None:
1602 fm.plain('# %s\n' % header)
1602 fm.plain('# %s\n' % header)
1603
1603
1604 fm.write('desc', '%s\n', ctx.description().rstrip())
1604 fm.write('desc', '%s\n', ctx.description().rstrip())
1605 fm.plain('\n')
1605 fm.plain('\n')
1606
1606
1607 if fm.isplain():
1607 if fm.isplain():
1608 chunkiter = patch.diffui(repo, prev, node, match, opts=diffopts)
1608 chunkiter = patch.diffui(repo, prev, node, match, opts=diffopts)
1609 for chunk, label in chunkiter:
1609 for chunk, label in chunkiter:
1610 fm.plain(chunk, label=label)
1610 fm.plain(chunk, label=label)
1611 else:
1611 else:
1612 chunkiter = patch.diff(repo, prev, node, match, opts=diffopts)
1612 chunkiter = patch.diff(repo, prev, node, match, opts=diffopts)
1613 # TODO: make it structured?
1613 # TODO: make it structured?
1614 fm.data(diff=b''.join(chunkiter))
1614 fm.data(diff=b''.join(chunkiter))
1615
1615
1616 def _exportfile(repo, revs, fm, dest, switch_parent, diffopts, match):
1616 def _exportfile(repo, revs, fm, dest, switch_parent, diffopts, match):
1617 """Export changesets to stdout or a single file"""
1617 """Export changesets to stdout or a single file"""
1618 for seqno, rev in enumerate(revs, 1):
1618 for seqno, rev in enumerate(revs, 1):
1619 ctx = repo[rev]
1619 ctx = repo[rev]
1620 if not dest.startswith('<'):
1620 if not dest.startswith('<'):
1621 repo.ui.note("%s\n" % dest)
1621 repo.ui.note("%s\n" % dest)
1622 fm.startitem()
1622 fm.startitem()
1623 _exportsingle(repo, ctx, fm, match, switch_parent, seqno, diffopts)
1623 _exportsingle(repo, ctx, fm, match, switch_parent, seqno, diffopts)
1624
1624
1625 def _exportfntemplate(repo, revs, basefm, fntemplate, switch_parent, diffopts,
1625 def _exportfntemplate(repo, revs, basefm, fntemplate, switch_parent, diffopts,
1626 match):
1626 match):
1627 """Export changesets to possibly multiple files"""
1627 """Export changesets to possibly multiple files"""
1628 total = len(revs)
1628 total = len(revs)
1629 revwidth = max(len(str(rev)) for rev in revs)
1629 revwidth = max(len(str(rev)) for rev in revs)
1630 filemap = util.sortdict() # filename: [(seqno, rev), ...]
1630 filemap = util.sortdict() # filename: [(seqno, rev), ...]
1631
1631
1632 for seqno, rev in enumerate(revs, 1):
1632 for seqno, rev in enumerate(revs, 1):
1633 ctx = repo[rev]
1633 ctx = repo[rev]
1634 dest = makefilename(ctx, fntemplate,
1634 dest = makefilename(ctx, fntemplate,
1635 total=total, seqno=seqno, revwidth=revwidth)
1635 total=total, seqno=seqno, revwidth=revwidth)
1636 filemap.setdefault(dest, []).append((seqno, rev))
1636 filemap.setdefault(dest, []).append((seqno, rev))
1637
1637
1638 for dest in filemap:
1638 for dest in filemap:
1639 with formatter.maybereopen(basefm, dest) as fm:
1639 with formatter.maybereopen(basefm, dest) as fm:
1640 repo.ui.note("%s\n" % dest)
1640 repo.ui.note("%s\n" % dest)
1641 for seqno, rev in filemap[dest]:
1641 for seqno, rev in filemap[dest]:
1642 fm.startitem()
1642 fm.startitem()
1643 ctx = repo[rev]
1643 ctx = repo[rev]
1644 _exportsingle(repo, ctx, fm, match, switch_parent, seqno,
1644 _exportsingle(repo, ctx, fm, match, switch_parent, seqno,
1645 diffopts)
1645 diffopts)
1646
1646
1647 def export(repo, revs, basefm, fntemplate='hg-%h.patch', switch_parent=False,
1647 def export(repo, revs, basefm, fntemplate='hg-%h.patch', switch_parent=False,
1648 opts=None, match=None):
1648 opts=None, match=None):
1649 '''export changesets as hg patches
1649 '''export changesets as hg patches
1650
1650
1651 Args:
1651 Args:
1652 repo: The repository from which we're exporting revisions.
1652 repo: The repository from which we're exporting revisions.
1653 revs: A list of revisions to export as revision numbers.
1653 revs: A list of revisions to export as revision numbers.
1654 basefm: A formatter to which patches should be written.
1654 basefm: A formatter to which patches should be written.
1655 fntemplate: An optional string to use for generating patch file names.
1655 fntemplate: An optional string to use for generating patch file names.
1656 switch_parent: If True, show diffs against second parent when not nullid.
1656 switch_parent: If True, show diffs against second parent when not nullid.
1657 Default is false, which always shows diff against p1.
1657 Default is false, which always shows diff against p1.
1658 opts: diff options to use for generating the patch.
1658 opts: diff options to use for generating the patch.
1659 match: If specified, only export changes to files matching this matcher.
1659 match: If specified, only export changes to files matching this matcher.
1660
1660
1661 Returns:
1661 Returns:
1662 Nothing.
1662 Nothing.
1663
1663
1664 Side Effect:
1664 Side Effect:
1665 "HG Changeset Patch" data is emitted to one of the following
1665 "HG Changeset Patch" data is emitted to one of the following
1666 destinations:
1666 destinations:
1667 fntemplate specified: Each rev is written to a unique file named using
1667 fntemplate specified: Each rev is written to a unique file named using
1668 the given template.
1668 the given template.
1669 Otherwise: All revs will be written to basefm.
1669 Otherwise: All revs will be written to basefm.
1670 '''
1670 '''
1671 scmutil.prefetchfiles(repo, revs, match)
1671 scmutil.prefetchfiles(repo, revs, match)
1672
1672
1673 if not fntemplate:
1673 if not fntemplate:
1674 _exportfile(repo, revs, basefm, '<unnamed>', switch_parent, opts, match)
1674 _exportfile(repo, revs, basefm, '<unnamed>', switch_parent, opts, match)
1675 else:
1675 else:
1676 _exportfntemplate(repo, revs, basefm, fntemplate, switch_parent, opts,
1676 _exportfntemplate(repo, revs, basefm, fntemplate, switch_parent, opts,
1677 match)
1677 match)
1678
1678
1679 def exportfile(repo, revs, fp, switch_parent=False, opts=None, match=None):
1679 def exportfile(repo, revs, fp, switch_parent=False, opts=None, match=None):
1680 """Export changesets to the given file stream"""
1680 """Export changesets to the given file stream"""
1681 scmutil.prefetchfiles(repo, revs, match)
1681 scmutil.prefetchfiles(repo, revs, match)
1682
1682
1683 dest = getattr(fp, 'name', '<unnamed>')
1683 dest = getattr(fp, 'name', '<unnamed>')
1684 with formatter.formatter(repo.ui, fp, 'export', {}) as fm:
1684 with formatter.formatter(repo.ui, fp, 'export', {}) as fm:
1685 _exportfile(repo, revs, fm, dest, switch_parent, opts, match)
1685 _exportfile(repo, revs, fm, dest, switch_parent, opts, match)
1686
1686
1687 def showmarker(fm, marker, index=None):
1687 def showmarker(fm, marker, index=None):
1688 """utility function to display obsolescence marker in a readable way
1688 """utility function to display obsolescence marker in a readable way
1689
1689
1690 To be used by debug function."""
1690 To be used by debug function."""
1691 if index is not None:
1691 if index is not None:
1692 fm.write('index', '%i ', index)
1692 fm.write('index', '%i ', index)
1693 fm.write('prednode', '%s ', hex(marker.prednode()))
1693 fm.write('prednode', '%s ', hex(marker.prednode()))
1694 succs = marker.succnodes()
1694 succs = marker.succnodes()
1695 fm.condwrite(succs, 'succnodes', '%s ',
1695 fm.condwrite(succs, 'succnodes', '%s ',
1696 fm.formatlist(map(hex, succs), name='node'))
1696 fm.formatlist(map(hex, succs), name='node'))
1697 fm.write('flag', '%X ', marker.flags())
1697 fm.write('flag', '%X ', marker.flags())
1698 parents = marker.parentnodes()
1698 parents = marker.parentnodes()
1699 if parents is not None:
1699 if parents is not None:
1700 fm.write('parentnodes', '{%s} ',
1700 fm.write('parentnodes', '{%s} ',
1701 fm.formatlist(map(hex, parents), name='node', sep=', '))
1701 fm.formatlist(map(hex, parents), name='node', sep=', '))
1702 fm.write('date', '(%s) ', fm.formatdate(marker.date()))
1702 fm.write('date', '(%s) ', fm.formatdate(marker.date()))
1703 meta = marker.metadata().copy()
1703 meta = marker.metadata().copy()
1704 meta.pop('date', None)
1704 meta.pop('date', None)
1705 smeta = pycompat.rapply(pycompat.maybebytestr, meta)
1705 smeta = pycompat.rapply(pycompat.maybebytestr, meta)
1706 fm.write('metadata', '{%s}', fm.formatdict(smeta, fmt='%r: %r', sep=', '))
1706 fm.write('metadata', '{%s}', fm.formatdict(smeta, fmt='%r: %r', sep=', '))
1707 fm.plain('\n')
1707 fm.plain('\n')
1708
1708
1709 def finddate(ui, repo, date):
1709 def finddate(ui, repo, date):
1710 """Find the tipmost changeset that matches the given date spec"""
1710 """Find the tipmost changeset that matches the given date spec"""
1711
1711
1712 df = dateutil.matchdate(date)
1712 df = dateutil.matchdate(date)
1713 m = scmutil.matchall(repo)
1713 m = scmutil.matchall(repo)
1714 results = {}
1714 results = {}
1715
1715
1716 def prep(ctx, fns):
1716 def prep(ctx, fns):
1717 d = ctx.date()
1717 d = ctx.date()
1718 if df(d[0]):
1718 if df(d[0]):
1719 results[ctx.rev()] = d
1719 results[ctx.rev()] = d
1720
1720
1721 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1721 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1722 rev = ctx.rev()
1722 rev = ctx.rev()
1723 if rev in results:
1723 if rev in results:
1724 ui.status(_("found revision %s from %s\n") %
1724 ui.status(_("found revision %s from %s\n") %
1725 (rev, dateutil.datestr(results[rev])))
1725 (rev, dateutil.datestr(results[rev])))
1726 return '%d' % rev
1726 return '%d' % rev
1727
1727
1728 raise error.Abort(_("revision matching date not found"))
1728 raise error.Abort(_("revision matching date not found"))
1729
1729
1730 def increasingwindows(windowsize=8, sizelimit=512):
1730 def increasingwindows(windowsize=8, sizelimit=512):
1731 while True:
1731 while True:
1732 yield windowsize
1732 yield windowsize
1733 if windowsize < sizelimit:
1733 if windowsize < sizelimit:
1734 windowsize *= 2
1734 windowsize *= 2
1735
1735
1736 def _walkrevs(repo, opts):
1736 def _walkrevs(repo, opts):
1737 # Default --rev value depends on --follow but --follow behavior
1737 # Default --rev value depends on --follow but --follow behavior
1738 # depends on revisions resolved from --rev...
1738 # depends on revisions resolved from --rev...
1739 follow = opts.get('follow') or opts.get('follow_first')
1739 follow = opts.get('follow') or opts.get('follow_first')
1740 if opts.get('rev'):
1740 if opts.get('rev'):
1741 revs = scmutil.revrange(repo, opts['rev'])
1741 revs = scmutil.revrange(repo, opts['rev'])
1742 elif follow and repo.dirstate.p1() == nullid:
1742 elif follow and repo.dirstate.p1() == nullid:
1743 revs = smartset.baseset()
1743 revs = smartset.baseset()
1744 elif follow:
1744 elif follow:
1745 revs = repo.revs('reverse(:.)')
1745 revs = repo.revs('reverse(:.)')
1746 else:
1746 else:
1747 revs = smartset.spanset(repo)
1747 revs = smartset.spanset(repo)
1748 revs.reverse()
1748 revs.reverse()
1749 return revs
1749 return revs
1750
1750
1751 class FileWalkError(Exception):
1751 class FileWalkError(Exception):
1752 pass
1752 pass
1753
1753
1754 def walkfilerevs(repo, match, follow, revs, fncache):
1754 def walkfilerevs(repo, match, follow, revs, fncache):
1755 '''Walks the file history for the matched files.
1755 '''Walks the file history for the matched files.
1756
1756
1757 Returns the changeset revs that are involved in the file history.
1757 Returns the changeset revs that are involved in the file history.
1758
1758
1759 Throws FileWalkError if the file history can't be walked using
1759 Throws FileWalkError if the file history can't be walked using
1760 filelogs alone.
1760 filelogs alone.
1761 '''
1761 '''
1762 wanted = set()
1762 wanted = set()
1763 copies = []
1763 copies = []
1764 minrev, maxrev = min(revs), max(revs)
1764 minrev, maxrev = min(revs), max(revs)
1765 def filerevgen(filelog, last):
1765 def filerevgen(filelog, last):
1766 """
1766 """
1767 Only files, no patterns. Check the history of each file.
1767 Only files, no patterns. Check the history of each file.
1768
1768
1769 Examines filelog entries within minrev, maxrev linkrev range
1769 Examines filelog entries within minrev, maxrev linkrev range
1770 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1770 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1771 tuples in backwards order
1771 tuples in backwards order
1772 """
1772 """
1773 cl_count = len(repo)
1773 cl_count = len(repo)
1774 revs = []
1774 revs = []
1775 for j in pycompat.xrange(0, last + 1):
1775 for j in pycompat.xrange(0, last + 1):
1776 linkrev = filelog.linkrev(j)
1776 linkrev = filelog.linkrev(j)
1777 if linkrev < minrev:
1777 if linkrev < minrev:
1778 continue
1778 continue
1779 # only yield rev for which we have the changelog, it can
1779 # only yield rev for which we have the changelog, it can
1780 # happen while doing "hg log" during a pull or commit
1780 # happen while doing "hg log" during a pull or commit
1781 if linkrev >= cl_count:
1781 if linkrev >= cl_count:
1782 break
1782 break
1783
1783
1784 parentlinkrevs = []
1784 parentlinkrevs = []
1785 for p in filelog.parentrevs(j):
1785 for p in filelog.parentrevs(j):
1786 if p != nullrev:
1786 if p != nullrev:
1787 parentlinkrevs.append(filelog.linkrev(p))
1787 parentlinkrevs.append(filelog.linkrev(p))
1788 n = filelog.node(j)
1788 n = filelog.node(j)
1789 revs.append((linkrev, parentlinkrevs,
1789 revs.append((linkrev, parentlinkrevs,
1790 follow and filelog.renamed(n)))
1790 follow and filelog.renamed(n)))
1791
1791
1792 return reversed(revs)
1792 return reversed(revs)
1793 def iterfiles():
1793 def iterfiles():
1794 pctx = repo['.']
1794 pctx = repo['.']
1795 for filename in match.files():
1795 for filename in match.files():
1796 if follow:
1796 if follow:
1797 if filename not in pctx:
1797 if filename not in pctx:
1798 raise error.Abort(_('cannot follow file not in parent '
1798 raise error.Abort(_('cannot follow file not in parent '
1799 'revision: "%s"') % filename)
1799 'revision: "%s"') % filename)
1800 yield filename, pctx[filename].filenode()
1800 yield filename, pctx[filename].filenode()
1801 else:
1801 else:
1802 yield filename, None
1802 yield filename, None
1803 for filename_node in copies:
1803 for filename_node in copies:
1804 yield filename_node
1804 yield filename_node
1805
1805
1806 for file_, node in iterfiles():
1806 for file_, node in iterfiles():
1807 filelog = repo.file(file_)
1807 filelog = repo.file(file_)
1808 if not len(filelog):
1808 if not len(filelog):
1809 if node is None:
1809 if node is None:
1810 # A zero count may be a directory or deleted file, so
1810 # A zero count may be a directory or deleted file, so
1811 # try to find matching entries on the slow path.
1811 # try to find matching entries on the slow path.
1812 if follow:
1812 if follow:
1813 raise error.Abort(
1813 raise error.Abort(
1814 _('cannot follow nonexistent file: "%s"') % file_)
1814 _('cannot follow nonexistent file: "%s"') % file_)
1815 raise FileWalkError("Cannot walk via filelog")
1815 raise FileWalkError("Cannot walk via filelog")
1816 else:
1816 else:
1817 continue
1817 continue
1818
1818
1819 if node is None:
1819 if node is None:
1820 last = len(filelog) - 1
1820 last = len(filelog) - 1
1821 else:
1821 else:
1822 last = filelog.rev(node)
1822 last = filelog.rev(node)
1823
1823
1824 # keep track of all ancestors of the file
1824 # keep track of all ancestors of the file
1825 ancestors = {filelog.linkrev(last)}
1825 ancestors = {filelog.linkrev(last)}
1826
1826
1827 # iterate from latest to oldest revision
1827 # iterate from latest to oldest revision
1828 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1828 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1829 if not follow:
1829 if not follow:
1830 if rev > maxrev:
1830 if rev > maxrev:
1831 continue
1831 continue
1832 else:
1832 else:
1833 # Note that last might not be the first interesting
1833 # Note that last might not be the first interesting
1834 # rev to us:
1834 # rev to us:
1835 # if the file has been changed after maxrev, we'll
1835 # if the file has been changed after maxrev, we'll
1836 # have linkrev(last) > maxrev, and we still need
1836 # have linkrev(last) > maxrev, and we still need
1837 # to explore the file graph
1837 # to explore the file graph
1838 if rev not in ancestors:
1838 if rev not in ancestors:
1839 continue
1839 continue
1840 # XXX insert 1327 fix here
1840 # XXX insert 1327 fix here
1841 if flparentlinkrevs:
1841 if flparentlinkrevs:
1842 ancestors.update(flparentlinkrevs)
1842 ancestors.update(flparentlinkrevs)
1843
1843
1844 fncache.setdefault(rev, []).append(file_)
1844 fncache.setdefault(rev, []).append(file_)
1845 wanted.add(rev)
1845 wanted.add(rev)
1846 if copied:
1846 if copied:
1847 copies.append(copied)
1847 copies.append(copied)
1848
1848
1849 return wanted
1849 return wanted
1850
1850
1851 class _followfilter(object):
1851 class _followfilter(object):
1852 def __init__(self, repo, onlyfirst=False):
1852 def __init__(self, repo, onlyfirst=False):
1853 self.repo = repo
1853 self.repo = repo
1854 self.startrev = nullrev
1854 self.startrev = nullrev
1855 self.roots = set()
1855 self.roots = set()
1856 self.onlyfirst = onlyfirst
1856 self.onlyfirst = onlyfirst
1857
1857
1858 def match(self, rev):
1858 def match(self, rev):
1859 def realparents(rev):
1859 def realparents(rev):
1860 if self.onlyfirst:
1860 if self.onlyfirst:
1861 return self.repo.changelog.parentrevs(rev)[0:1]
1861 return self.repo.changelog.parentrevs(rev)[0:1]
1862 else:
1862 else:
1863 return filter(lambda x: x != nullrev,
1863 return filter(lambda x: x != nullrev,
1864 self.repo.changelog.parentrevs(rev))
1864 self.repo.changelog.parentrevs(rev))
1865
1865
1866 if self.startrev == nullrev:
1866 if self.startrev == nullrev:
1867 self.startrev = rev
1867 self.startrev = rev
1868 return True
1868 return True
1869
1869
1870 if rev > self.startrev:
1870 if rev > self.startrev:
1871 # forward: all descendants
1871 # forward: all descendants
1872 if not self.roots:
1872 if not self.roots:
1873 self.roots.add(self.startrev)
1873 self.roots.add(self.startrev)
1874 for parent in realparents(rev):
1874 for parent in realparents(rev):
1875 if parent in self.roots:
1875 if parent in self.roots:
1876 self.roots.add(rev)
1876 self.roots.add(rev)
1877 return True
1877 return True
1878 else:
1878 else:
1879 # backwards: all parents
1879 # backwards: all parents
1880 if not self.roots:
1880 if not self.roots:
1881 self.roots.update(realparents(self.startrev))
1881 self.roots.update(realparents(self.startrev))
1882 if rev in self.roots:
1882 if rev in self.roots:
1883 self.roots.remove(rev)
1883 self.roots.remove(rev)
1884 self.roots.update(realparents(rev))
1884 self.roots.update(realparents(rev))
1885 return True
1885 return True
1886
1886
1887 return False
1887 return False
1888
1888
1889 def walkchangerevs(repo, match, opts, prepare):
1889 def walkchangerevs(repo, match, opts, prepare):
1890 '''Iterate over files and the revs in which they changed.
1890 '''Iterate over files and the revs in which they changed.
1891
1891
1892 Callers most commonly need to iterate backwards over the history
1892 Callers most commonly need to iterate backwards over the history
1893 in which they are interested. Doing so has awful (quadratic-looking)
1893 in which they are interested. Doing so has awful (quadratic-looking)
1894 performance, so we use iterators in a "windowed" way.
1894 performance, so we use iterators in a "windowed" way.
1895
1895
1896 We walk a window of revisions in the desired order. Within the
1896 We walk a window of revisions in the desired order. Within the
1897 window, we first walk forwards to gather data, then in the desired
1897 window, we first walk forwards to gather data, then in the desired
1898 order (usually backwards) to display it.
1898 order (usually backwards) to display it.
1899
1899
1900 This function returns an iterator yielding contexts. Before
1900 This function returns an iterator yielding contexts. Before
1901 yielding each context, the iterator will first call the prepare
1901 yielding each context, the iterator will first call the prepare
1902 function on each context in the window in forward order.'''
1902 function on each context in the window in forward order.'''
1903
1903
1904 allfiles = opts.get('all_files')
1904 allfiles = opts.get('all_files')
1905 follow = opts.get('follow') or opts.get('follow_first')
1905 follow = opts.get('follow') or opts.get('follow_first')
1906 revs = _walkrevs(repo, opts)
1906 revs = _walkrevs(repo, opts)
1907 if not revs:
1907 if not revs:
1908 return []
1908 return []
1909 wanted = set()
1909 wanted = set()
1910 slowpath = match.anypats() or (not match.always() and opts.get('removed'))
1910 slowpath = match.anypats() or (not match.always() and opts.get('removed'))
1911 fncache = {}
1911 fncache = {}
1912 change = repo.__getitem__
1912 change = repo.__getitem__
1913
1913
1914 # First step is to fill wanted, the set of revisions that we want to yield.
1914 # First step is to fill wanted, the set of revisions that we want to yield.
1915 # When it does not induce extra cost, we also fill fncache for revisions in
1915 # When it does not induce extra cost, we also fill fncache for revisions in
1916 # wanted: a cache of filenames that were changed (ctx.files()) and that
1916 # wanted: a cache of filenames that were changed (ctx.files()) and that
1917 # match the file filtering conditions.
1917 # match the file filtering conditions.
1918
1918
1919 if match.always() or allfiles:
1919 if match.always() or allfiles:
1920 # No files, no patterns. Display all revs.
1920 # No files, no patterns. Display all revs.
1921 wanted = revs
1921 wanted = revs
1922 elif not slowpath:
1922 elif not slowpath:
1923 # We only have to read through the filelog to find wanted revisions
1923 # We only have to read through the filelog to find wanted revisions
1924
1924
1925 try:
1925 try:
1926 wanted = walkfilerevs(repo, match, follow, revs, fncache)
1926 wanted = walkfilerevs(repo, match, follow, revs, fncache)
1927 except FileWalkError:
1927 except FileWalkError:
1928 slowpath = True
1928 slowpath = True
1929
1929
1930 # We decided to fall back to the slowpath because at least one
1930 # We decided to fall back to the slowpath because at least one
1931 # of the paths was not a file. Check to see if at least one of them
1931 # of the paths was not a file. Check to see if at least one of them
1932 # existed in history, otherwise simply return
1932 # existed in history, otherwise simply return
1933 for path in match.files():
1933 for path in match.files():
1934 if path == '.' or path in repo.store:
1934 if path == '.' or path in repo.store:
1935 break
1935 break
1936 else:
1936 else:
1937 return []
1937 return []
1938
1938
1939 if slowpath:
1939 if slowpath:
1940 # We have to read the changelog to match filenames against
1940 # We have to read the changelog to match filenames against
1941 # changed files
1941 # changed files
1942
1942
1943 if follow:
1943 if follow:
1944 raise error.Abort(_('can only follow copies/renames for explicit '
1944 raise error.Abort(_('can only follow copies/renames for explicit '
1945 'filenames'))
1945 'filenames'))
1946
1946
1947 # The slow path checks files modified in every changeset.
1947 # The slow path checks files modified in every changeset.
1948 # This is really slow on large repos, so compute the set lazily.
1948 # This is really slow on large repos, so compute the set lazily.
1949 class lazywantedset(object):
1949 class lazywantedset(object):
1950 def __init__(self):
1950 def __init__(self):
1951 self.set = set()
1951 self.set = set()
1952 self.revs = set(revs)
1952 self.revs = set(revs)
1953
1953
1954 # No need to worry about locality here because it will be accessed
1954 # No need to worry about locality here because it will be accessed
1955 # in the same order as the increasing window below.
1955 # in the same order as the increasing window below.
1956 def __contains__(self, value):
1956 def __contains__(self, value):
1957 if value in self.set:
1957 if value in self.set:
1958 return True
1958 return True
1959 elif not value in self.revs:
1959 elif not value in self.revs:
1960 return False
1960 return False
1961 else:
1961 else:
1962 self.revs.discard(value)
1962 self.revs.discard(value)
1963 ctx = change(value)
1963 ctx = change(value)
1964 if allfiles:
1964 if allfiles:
1965 matches = list(ctx.manifest().walk(match))
1965 matches = list(ctx.manifest().walk(match))
1966 else:
1966 else:
1967 matches = [f for f in ctx.files() if match(f)]
1967 matches = [f for f in ctx.files() if match(f)]
1968 if matches:
1968 if matches:
1969 fncache[value] = matches
1969 fncache[value] = matches
1970 self.set.add(value)
1970 self.set.add(value)
1971 return True
1971 return True
1972 return False
1972 return False
1973
1973
1974 def discard(self, value):
1974 def discard(self, value):
1975 self.revs.discard(value)
1975 self.revs.discard(value)
1976 self.set.discard(value)
1976 self.set.discard(value)
1977
1977
1978 wanted = lazywantedset()
1978 wanted = lazywantedset()
1979
1979
1980 # it might be worthwhile to do this in the iterator if the rev range
1980 # it might be worthwhile to do this in the iterator if the rev range
1981 # is descending and the prune args are all within that range
1981 # is descending and the prune args are all within that range
1982 for rev in opts.get('prune', ()):
1982 for rev in opts.get('prune', ()):
1983 rev = repo[rev].rev()
1983 rev = repo[rev].rev()
1984 ff = _followfilter(repo)
1984 ff = _followfilter(repo)
1985 stop = min(revs[0], revs[-1])
1985 stop = min(revs[0], revs[-1])
1986 for x in pycompat.xrange(rev, stop - 1, -1):
1986 for x in pycompat.xrange(rev, stop - 1, -1):
1987 if ff.match(x):
1987 if ff.match(x):
1988 wanted = wanted - [x]
1988 wanted = wanted - [x]
1989
1989
1990 # Now that wanted is correctly initialized, we can iterate over the
1990 # Now that wanted is correctly initialized, we can iterate over the
1991 # revision range, yielding only revisions in wanted.
1991 # revision range, yielding only revisions in wanted.
1992 def iterate():
1992 def iterate():
1993 if follow and match.always():
1993 if follow and match.always():
1994 ff = _followfilter(repo, onlyfirst=opts.get('follow_first'))
1994 ff = _followfilter(repo, onlyfirst=opts.get('follow_first'))
1995 def want(rev):
1995 def want(rev):
1996 return ff.match(rev) and rev in wanted
1996 return ff.match(rev) and rev in wanted
1997 else:
1997 else:
1998 def want(rev):
1998 def want(rev):
1999 return rev in wanted
1999 return rev in wanted
2000
2000
2001 it = iter(revs)
2001 it = iter(revs)
2002 stopiteration = False
2002 stopiteration = False
2003 for windowsize in increasingwindows():
2003 for windowsize in increasingwindows():
2004 nrevs = []
2004 nrevs = []
2005 for i in pycompat.xrange(windowsize):
2005 for i in pycompat.xrange(windowsize):
2006 rev = next(it, None)
2006 rev = next(it, None)
2007 if rev is None:
2007 if rev is None:
2008 stopiteration = True
2008 stopiteration = True
2009 break
2009 break
2010 elif want(rev):
2010 elif want(rev):
2011 nrevs.append(rev)
2011 nrevs.append(rev)
2012 for rev in sorted(nrevs):
2012 for rev in sorted(nrevs):
2013 fns = fncache.get(rev)
2013 fns = fncache.get(rev)
2014 ctx = change(rev)
2014 ctx = change(rev)
2015 if not fns:
2015 if not fns:
2016 def fns_generator():
2016 def fns_generator():
2017 if allfiles:
2017 if allfiles:
2018 fiter = iter(ctx)
2018 fiter = iter(ctx)
2019 else:
2019 else:
2020 fiter = ctx.files()
2020 fiter = ctx.files()
2021 for f in fiter:
2021 for f in fiter:
2022 if match(f):
2022 if match(f):
2023 yield f
2023 yield f
2024 fns = fns_generator()
2024 fns = fns_generator()
2025 prepare(ctx, fns)
2025 prepare(ctx, fns)
2026 for rev in nrevs:
2026 for rev in nrevs:
2027 yield change(rev)
2027 yield change(rev)
2028
2028
2029 if stopiteration:
2029 if stopiteration:
2030 break
2030 break
2031
2031
2032 return iterate()
2032 return iterate()
2033
2033
2034 def add(ui, repo, match, prefix, uipathfn, explicitonly, **opts):
2034 def add(ui, repo, match, prefix, uipathfn, explicitonly, **opts):
2035 bad = []
2035 bad = []
2036
2036
2037 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2037 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2038 names = []
2038 names = []
2039 wctx = repo[None]
2039 wctx = repo[None]
2040 cca = None
2040 cca = None
2041 abort, warn = scmutil.checkportabilityalert(ui)
2041 abort, warn = scmutil.checkportabilityalert(ui)
2042 if abort or warn:
2042 if abort or warn:
2043 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
2043 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
2044
2044
2045 match = repo.narrowmatch(match, includeexact=True)
2045 match = repo.narrowmatch(match, includeexact=True)
2046 badmatch = matchmod.badmatch(match, badfn)
2046 badmatch = matchmod.badmatch(match, badfn)
2047 dirstate = repo.dirstate
2047 dirstate = repo.dirstate
2048 # We don't want to just call wctx.walk here, since it would return a lot of
2048 # We don't want to just call wctx.walk here, since it would return a lot of
2049 # clean files, which we aren't interested in and takes time.
2049 # clean files, which we aren't interested in and takes time.
2050 for f in sorted(dirstate.walk(badmatch, subrepos=sorted(wctx.substate),
2050 for f in sorted(dirstate.walk(badmatch, subrepos=sorted(wctx.substate),
2051 unknown=True, ignored=False, full=False)):
2051 unknown=True, ignored=False, full=False)):
2052 exact = match.exact(f)
2052 exact = match.exact(f)
2053 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
2053 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
2054 if cca:
2054 if cca:
2055 cca(f)
2055 cca(f)
2056 names.append(f)
2056 names.append(f)
2057 if ui.verbose or not exact:
2057 if ui.verbose or not exact:
2058 ui.status(_('adding %s\n') % uipathfn(f),
2058 ui.status(_('adding %s\n') % uipathfn(f),
2059 label='ui.addremove.added')
2059 label='ui.addremove.added')
2060
2060
2061 for subpath in sorted(wctx.substate):
2061 for subpath in sorted(wctx.substate):
2062 sub = wctx.sub(subpath)
2062 sub = wctx.sub(subpath)
2063 try:
2063 try:
2064 submatch = matchmod.subdirmatcher(subpath, match)
2064 submatch = matchmod.subdirmatcher(subpath, match)
2065 subprefix = repo.wvfs.reljoin(prefix, subpath)
2065 subprefix = repo.wvfs.reljoin(prefix, subpath)
2066 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2066 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2067 if opts.get(r'subrepos'):
2067 if opts.get(r'subrepos'):
2068 bad.extend(sub.add(ui, submatch, subprefix, subuipathfn, False,
2068 bad.extend(sub.add(ui, submatch, subprefix, subuipathfn, False,
2069 **opts))
2069 **opts))
2070 else:
2070 else:
2071 bad.extend(sub.add(ui, submatch, subprefix, subuipathfn, True,
2071 bad.extend(sub.add(ui, submatch, subprefix, subuipathfn, True,
2072 **opts))
2072 **opts))
2073 except error.LookupError:
2073 except error.LookupError:
2074 ui.status(_("skipping missing subrepository: %s\n")
2074 ui.status(_("skipping missing subrepository: %s\n")
2075 % uipathfn(subpath))
2075 % uipathfn(subpath))
2076
2076
2077 if not opts.get(r'dry_run'):
2077 if not opts.get(r'dry_run'):
2078 rejected = wctx.add(names, prefix)
2078 rejected = wctx.add(names, prefix)
2079 bad.extend(f for f in rejected if f in match.files())
2079 bad.extend(f for f in rejected if f in match.files())
2080 return bad
2080 return bad
2081
2081
2082 def addwebdirpath(repo, serverpath, webconf):
2082 def addwebdirpath(repo, serverpath, webconf):
2083 webconf[serverpath] = repo.root
2083 webconf[serverpath] = repo.root
2084 repo.ui.debug('adding %s = %s\n' % (serverpath, repo.root))
2084 repo.ui.debug('adding %s = %s\n' % (serverpath, repo.root))
2085
2085
2086 for r in repo.revs('filelog("path:.hgsub")'):
2086 for r in repo.revs('filelog("path:.hgsub")'):
2087 ctx = repo[r]
2087 ctx = repo[r]
2088 for subpath in ctx.substate:
2088 for subpath in ctx.substate:
2089 ctx.sub(subpath).addwebdirpath(serverpath, webconf)
2089 ctx.sub(subpath).addwebdirpath(serverpath, webconf)
2090
2090
2091 def forget(ui, repo, match, prefix, uipathfn, explicitonly, dryrun,
2091 def forget(ui, repo, match, prefix, uipathfn, explicitonly, dryrun,
2092 interactive):
2092 interactive):
2093 if dryrun and interactive:
2093 if dryrun and interactive:
2094 raise error.Abort(_("cannot specify both --dry-run and --interactive"))
2094 raise error.Abort(_("cannot specify both --dry-run and --interactive"))
2095 bad = []
2095 bad = []
2096 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2096 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2097 wctx = repo[None]
2097 wctx = repo[None]
2098 forgot = []
2098 forgot = []
2099
2099
2100 s = repo.status(match=matchmod.badmatch(match, badfn), clean=True)
2100 s = repo.status(match=matchmod.badmatch(match, badfn), clean=True)
2101 forget = sorted(s.modified + s.added + s.deleted + s.clean)
2101 forget = sorted(s.modified + s.added + s.deleted + s.clean)
2102 if explicitonly:
2102 if explicitonly:
2103 forget = [f for f in forget if match.exact(f)]
2103 forget = [f for f in forget if match.exact(f)]
2104
2104
2105 for subpath in sorted(wctx.substate):
2105 for subpath in sorted(wctx.substate):
2106 sub = wctx.sub(subpath)
2106 sub = wctx.sub(subpath)
2107 submatch = matchmod.subdirmatcher(subpath, match)
2107 submatch = matchmod.subdirmatcher(subpath, match)
2108 subprefix = repo.wvfs.reljoin(prefix, subpath)
2108 subprefix = repo.wvfs.reljoin(prefix, subpath)
2109 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2109 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2110 try:
2110 try:
2111 subbad, subforgot = sub.forget(submatch, subprefix, subuipathfn,
2111 subbad, subforgot = sub.forget(submatch, subprefix, subuipathfn,
2112 dryrun=dryrun,
2112 dryrun=dryrun,
2113 interactive=interactive)
2113 interactive=interactive)
2114 bad.extend([subpath + '/' + f for f in subbad])
2114 bad.extend([subpath + '/' + f for f in subbad])
2115 forgot.extend([subpath + '/' + f for f in subforgot])
2115 forgot.extend([subpath + '/' + f for f in subforgot])
2116 except error.LookupError:
2116 except error.LookupError:
2117 ui.status(_("skipping missing subrepository: %s\n")
2117 ui.status(_("skipping missing subrepository: %s\n")
2118 % uipathfn(subpath))
2118 % uipathfn(subpath))
2119
2119
2120 if not explicitonly:
2120 if not explicitonly:
2121 for f in match.files():
2121 for f in match.files():
2122 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2122 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2123 if f not in forgot:
2123 if f not in forgot:
2124 if repo.wvfs.exists(f):
2124 if repo.wvfs.exists(f):
2125 # Don't complain if the exact case match wasn't given.
2125 # Don't complain if the exact case match wasn't given.
2126 # But don't do this until after checking 'forgot', so
2126 # But don't do this until after checking 'forgot', so
2127 # that subrepo files aren't normalized, and this op is
2127 # that subrepo files aren't normalized, and this op is
2128 # purely from data cached by the status walk above.
2128 # purely from data cached by the status walk above.
2129 if repo.dirstate.normalize(f) in repo.dirstate:
2129 if repo.dirstate.normalize(f) in repo.dirstate:
2130 continue
2130 continue
2131 ui.warn(_('not removing %s: '
2131 ui.warn(_('not removing %s: '
2132 'file is already untracked\n')
2132 'file is already untracked\n')
2133 % uipathfn(f))
2133 % uipathfn(f))
2134 bad.append(f)
2134 bad.append(f)
2135
2135
2136 if interactive:
2136 if interactive:
2137 responses = _('[Ynsa?]'
2137 responses = _('[Ynsa?]'
2138 '$$ &Yes, forget this file'
2138 '$$ &Yes, forget this file'
2139 '$$ &No, skip this file'
2139 '$$ &No, skip this file'
2140 '$$ &Skip remaining files'
2140 '$$ &Skip remaining files'
2141 '$$ Include &all remaining files'
2141 '$$ Include &all remaining files'
2142 '$$ &? (display help)')
2142 '$$ &? (display help)')
2143 for filename in forget[:]:
2143 for filename in forget[:]:
2144 r = ui.promptchoice(_('forget %s %s') %
2144 r = ui.promptchoice(_('forget %s %s') %
2145 (uipathfn(filename), responses))
2145 (uipathfn(filename), responses))
2146 if r == 4: # ?
2146 if r == 4: # ?
2147 while r == 4:
2147 while r == 4:
2148 for c, t in ui.extractchoices(responses)[1]:
2148 for c, t in ui.extractchoices(responses)[1]:
2149 ui.write('%s - %s\n' % (c, encoding.lower(t)))
2149 ui.write('%s - %s\n' % (c, encoding.lower(t)))
2150 r = ui.promptchoice(_('forget %s %s') %
2150 r = ui.promptchoice(_('forget %s %s') %
2151 (uipathfn(filename), responses))
2151 (uipathfn(filename), responses))
2152 if r == 0: # yes
2152 if r == 0: # yes
2153 continue
2153 continue
2154 elif r == 1: # no
2154 elif r == 1: # no
2155 forget.remove(filename)
2155 forget.remove(filename)
2156 elif r == 2: # Skip
2156 elif r == 2: # Skip
2157 fnindex = forget.index(filename)
2157 fnindex = forget.index(filename)
2158 del forget[fnindex:]
2158 del forget[fnindex:]
2159 break
2159 break
2160 elif r == 3: # All
2160 elif r == 3: # All
2161 break
2161 break
2162
2162
2163 for f in forget:
2163 for f in forget:
2164 if ui.verbose or not match.exact(f) or interactive:
2164 if ui.verbose or not match.exact(f) or interactive:
2165 ui.status(_('removing %s\n') % uipathfn(f),
2165 ui.status(_('removing %s\n') % uipathfn(f),
2166 label='ui.addremove.removed')
2166 label='ui.addremove.removed')
2167
2167
2168 if not dryrun:
2168 if not dryrun:
2169 rejected = wctx.forget(forget, prefix)
2169 rejected = wctx.forget(forget, prefix)
2170 bad.extend(f for f in rejected if f in match.files())
2170 bad.extend(f for f in rejected if f in match.files())
2171 forgot.extend(f for f in forget if f not in rejected)
2171 forgot.extend(f for f in forget if f not in rejected)
2172 return bad, forgot
2172 return bad, forgot
2173
2173
2174 def files(ui, ctx, m, fm, fmt, subrepos):
2174 def files(ui, ctx, m, uipathfn, fm, fmt, subrepos):
2175 ret = 1
2175 ret = 1
2176
2176
2177 needsfctx = ui.verbose or {'size', 'flags'} & fm.datahint()
2177 needsfctx = ui.verbose or {'size', 'flags'} & fm.datahint()
2178 uipathfn = scmutil.getuipathfn(ctx.repo(), legacyrelativevalue=True)
2179 for f in ctx.matches(m):
2178 for f in ctx.matches(m):
2180 fm.startitem()
2179 fm.startitem()
2181 fm.context(ctx=ctx)
2180 fm.context(ctx=ctx)
2182 if needsfctx:
2181 if needsfctx:
2183 fc = ctx[f]
2182 fc = ctx[f]
2184 fm.write('size flags', '% 10d % 1s ', fc.size(), fc.flags())
2183 fm.write('size flags', '% 10d % 1s ', fc.size(), fc.flags())
2185 fm.data(path=f)
2184 fm.data(path=f)
2186 fm.plain(fmt % uipathfn(f))
2185 fm.plain(fmt % uipathfn(f))
2187 ret = 0
2186 ret = 0
2188
2187
2189 for subpath in sorted(ctx.substate):
2188 for subpath in sorted(ctx.substate):
2190 submatch = matchmod.subdirmatcher(subpath, m)
2189 submatch = matchmod.subdirmatcher(subpath, m)
2190 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2191 if (subrepos or m.exact(subpath) or any(submatch.files())):
2191 if (subrepos or m.exact(subpath) or any(submatch.files())):
2192 sub = ctx.sub(subpath)
2192 sub = ctx.sub(subpath)
2193 try:
2193 try:
2194 recurse = m.exact(subpath) or subrepos
2194 recurse = m.exact(subpath) or subrepos
2195 if sub.printfiles(ui, submatch, fm, fmt, recurse) == 0:
2195 if sub.printfiles(ui, submatch, subuipathfn, fm, fmt,
2196 recurse) == 0:
2196 ret = 0
2197 ret = 0
2197 except error.LookupError:
2198 except error.LookupError:
2198 ui.status(_("skipping missing subrepository: %s\n")
2199 ui.status(_("skipping missing subrepository: %s\n")
2199 % uipathfn(subpath))
2200 % uipathfn(subpath))
2200
2201
2201 return ret
2202 return ret
2202
2203
2203 def remove(ui, repo, m, prefix, uipathfn, after, force, subrepos, dryrun,
2204 def remove(ui, repo, m, prefix, uipathfn, after, force, subrepos, dryrun,
2204 warnings=None):
2205 warnings=None):
2205 ret = 0
2206 ret = 0
2206 s = repo.status(match=m, clean=True)
2207 s = repo.status(match=m, clean=True)
2207 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2208 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2208
2209
2209 wctx = repo[None]
2210 wctx = repo[None]
2210
2211
2211 if warnings is None:
2212 if warnings is None:
2212 warnings = []
2213 warnings = []
2213 warn = True
2214 warn = True
2214 else:
2215 else:
2215 warn = False
2216 warn = False
2216
2217
2217 subs = sorted(wctx.substate)
2218 subs = sorted(wctx.substate)
2218 progress = ui.makeprogress(_('searching'), total=len(subs),
2219 progress = ui.makeprogress(_('searching'), total=len(subs),
2219 unit=_('subrepos'))
2220 unit=_('subrepos'))
2220 for subpath in subs:
2221 for subpath in subs:
2221 submatch = matchmod.subdirmatcher(subpath, m)
2222 submatch = matchmod.subdirmatcher(subpath, m)
2222 subprefix = repo.wvfs.reljoin(prefix, subpath)
2223 subprefix = repo.wvfs.reljoin(prefix, subpath)
2223 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2224 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2224 if subrepos or m.exact(subpath) or any(submatch.files()):
2225 if subrepos or m.exact(subpath) or any(submatch.files()):
2225 progress.increment()
2226 progress.increment()
2226 sub = wctx.sub(subpath)
2227 sub = wctx.sub(subpath)
2227 try:
2228 try:
2228 if sub.removefiles(submatch, subprefix, subuipathfn, after,
2229 if sub.removefiles(submatch, subprefix, subuipathfn, after,
2229 force, subrepos, dryrun, warnings):
2230 force, subrepos, dryrun, warnings):
2230 ret = 1
2231 ret = 1
2231 except error.LookupError:
2232 except error.LookupError:
2232 warnings.append(_("skipping missing subrepository: %s\n")
2233 warnings.append(_("skipping missing subrepository: %s\n")
2233 % uipathfn(subpath))
2234 % uipathfn(subpath))
2234 progress.complete()
2235 progress.complete()
2235
2236
2236 # warn about failure to delete explicit files/dirs
2237 # warn about failure to delete explicit files/dirs
2237 deleteddirs = util.dirs(deleted)
2238 deleteddirs = util.dirs(deleted)
2238 files = m.files()
2239 files = m.files()
2239 progress = ui.makeprogress(_('deleting'), total=len(files),
2240 progress = ui.makeprogress(_('deleting'), total=len(files),
2240 unit=_('files'))
2241 unit=_('files'))
2241 for f in files:
2242 for f in files:
2242 def insubrepo():
2243 def insubrepo():
2243 for subpath in wctx.substate:
2244 for subpath in wctx.substate:
2244 if f.startswith(subpath + '/'):
2245 if f.startswith(subpath + '/'):
2245 return True
2246 return True
2246 return False
2247 return False
2247
2248
2248 progress.increment()
2249 progress.increment()
2249 isdir = f in deleteddirs or wctx.hasdir(f)
2250 isdir = f in deleteddirs or wctx.hasdir(f)
2250 if (f in repo.dirstate or isdir or f == '.'
2251 if (f in repo.dirstate or isdir or f == '.'
2251 or insubrepo() or f in subs):
2252 or insubrepo() or f in subs):
2252 continue
2253 continue
2253
2254
2254 if repo.wvfs.exists(f):
2255 if repo.wvfs.exists(f):
2255 if repo.wvfs.isdir(f):
2256 if repo.wvfs.isdir(f):
2256 warnings.append(_('not removing %s: no tracked files\n')
2257 warnings.append(_('not removing %s: no tracked files\n')
2257 % uipathfn(f))
2258 % uipathfn(f))
2258 else:
2259 else:
2259 warnings.append(_('not removing %s: file is untracked\n')
2260 warnings.append(_('not removing %s: file is untracked\n')
2260 % uipathfn(f))
2261 % uipathfn(f))
2261 # missing files will generate a warning elsewhere
2262 # missing files will generate a warning elsewhere
2262 ret = 1
2263 ret = 1
2263 progress.complete()
2264 progress.complete()
2264
2265
2265 if force:
2266 if force:
2266 list = modified + deleted + clean + added
2267 list = modified + deleted + clean + added
2267 elif after:
2268 elif after:
2268 list = deleted
2269 list = deleted
2269 remaining = modified + added + clean
2270 remaining = modified + added + clean
2270 progress = ui.makeprogress(_('skipping'), total=len(remaining),
2271 progress = ui.makeprogress(_('skipping'), total=len(remaining),
2271 unit=_('files'))
2272 unit=_('files'))
2272 for f in remaining:
2273 for f in remaining:
2273 progress.increment()
2274 progress.increment()
2274 if ui.verbose or (f in files):
2275 if ui.verbose or (f in files):
2275 warnings.append(_('not removing %s: file still exists\n')
2276 warnings.append(_('not removing %s: file still exists\n')
2276 % uipathfn(f))
2277 % uipathfn(f))
2277 ret = 1
2278 ret = 1
2278 progress.complete()
2279 progress.complete()
2279 else:
2280 else:
2280 list = deleted + clean
2281 list = deleted + clean
2281 progress = ui.makeprogress(_('skipping'),
2282 progress = ui.makeprogress(_('skipping'),
2282 total=(len(modified) + len(added)),
2283 total=(len(modified) + len(added)),
2283 unit=_('files'))
2284 unit=_('files'))
2284 for f in modified:
2285 for f in modified:
2285 progress.increment()
2286 progress.increment()
2286 warnings.append(_('not removing %s: file is modified (use -f'
2287 warnings.append(_('not removing %s: file is modified (use -f'
2287 ' to force removal)\n') % uipathfn(f))
2288 ' to force removal)\n') % uipathfn(f))
2288 ret = 1
2289 ret = 1
2289 for f in added:
2290 for f in added:
2290 progress.increment()
2291 progress.increment()
2291 warnings.append(_("not removing %s: file has been marked for add"
2292 warnings.append(_("not removing %s: file has been marked for add"
2292 " (use 'hg forget' to undo add)\n") % uipathfn(f))
2293 " (use 'hg forget' to undo add)\n") % uipathfn(f))
2293 ret = 1
2294 ret = 1
2294 progress.complete()
2295 progress.complete()
2295
2296
2296 list = sorted(list)
2297 list = sorted(list)
2297 progress = ui.makeprogress(_('deleting'), total=len(list),
2298 progress = ui.makeprogress(_('deleting'), total=len(list),
2298 unit=_('files'))
2299 unit=_('files'))
2299 for f in list:
2300 for f in list:
2300 if ui.verbose or not m.exact(f):
2301 if ui.verbose or not m.exact(f):
2301 progress.increment()
2302 progress.increment()
2302 ui.status(_('removing %s\n') % uipathfn(f),
2303 ui.status(_('removing %s\n') % uipathfn(f),
2303 label='ui.addremove.removed')
2304 label='ui.addremove.removed')
2304 progress.complete()
2305 progress.complete()
2305
2306
2306 if not dryrun:
2307 if not dryrun:
2307 with repo.wlock():
2308 with repo.wlock():
2308 if not after:
2309 if not after:
2309 for f in list:
2310 for f in list:
2310 if f in added:
2311 if f in added:
2311 continue # we never unlink added files on remove
2312 continue # we never unlink added files on remove
2312 rmdir = repo.ui.configbool('experimental',
2313 rmdir = repo.ui.configbool('experimental',
2313 'removeemptydirs')
2314 'removeemptydirs')
2314 repo.wvfs.unlinkpath(f, ignoremissing=True, rmdir=rmdir)
2315 repo.wvfs.unlinkpath(f, ignoremissing=True, rmdir=rmdir)
2315 repo[None].forget(list)
2316 repo[None].forget(list)
2316
2317
2317 if warn:
2318 if warn:
2318 for warning in warnings:
2319 for warning in warnings:
2319 ui.warn(warning)
2320 ui.warn(warning)
2320
2321
2321 return ret
2322 return ret
2322
2323
2323 def _updatecatformatter(fm, ctx, matcher, path, decode):
2324 def _updatecatformatter(fm, ctx, matcher, path, decode):
2324 """Hook for adding data to the formatter used by ``hg cat``.
2325 """Hook for adding data to the formatter used by ``hg cat``.
2325
2326
2326 Extensions (e.g., lfs) can wrap this to inject keywords/data, but must call
2327 Extensions (e.g., lfs) can wrap this to inject keywords/data, but must call
2327 this method first."""
2328 this method first."""
2328 data = ctx[path].data()
2329 data = ctx[path].data()
2329 if decode:
2330 if decode:
2330 data = ctx.repo().wwritedata(path, data)
2331 data = ctx.repo().wwritedata(path, data)
2331 fm.startitem()
2332 fm.startitem()
2332 fm.context(ctx=ctx)
2333 fm.context(ctx=ctx)
2333 fm.write('data', '%s', data)
2334 fm.write('data', '%s', data)
2334 fm.data(path=path)
2335 fm.data(path=path)
2335
2336
2336 def cat(ui, repo, ctx, matcher, basefm, fntemplate, prefix, **opts):
2337 def cat(ui, repo, ctx, matcher, basefm, fntemplate, prefix, **opts):
2337 err = 1
2338 err = 1
2338 opts = pycompat.byteskwargs(opts)
2339 opts = pycompat.byteskwargs(opts)
2339
2340
2340 def write(path):
2341 def write(path):
2341 filename = None
2342 filename = None
2342 if fntemplate:
2343 if fntemplate:
2343 filename = makefilename(ctx, fntemplate,
2344 filename = makefilename(ctx, fntemplate,
2344 pathname=os.path.join(prefix, path))
2345 pathname=os.path.join(prefix, path))
2345 # attempt to create the directory if it does not already exist
2346 # attempt to create the directory if it does not already exist
2346 try:
2347 try:
2347 os.makedirs(os.path.dirname(filename))
2348 os.makedirs(os.path.dirname(filename))
2348 except OSError:
2349 except OSError:
2349 pass
2350 pass
2350 with formatter.maybereopen(basefm, filename) as fm:
2351 with formatter.maybereopen(basefm, filename) as fm:
2351 _updatecatformatter(fm, ctx, matcher, path, opts.get('decode'))
2352 _updatecatformatter(fm, ctx, matcher, path, opts.get('decode'))
2352
2353
2353 # Automation often uses hg cat on single files, so special case it
2354 # Automation often uses hg cat on single files, so special case it
2354 # for performance to avoid the cost of parsing the manifest.
2355 # for performance to avoid the cost of parsing the manifest.
2355 if len(matcher.files()) == 1 and not matcher.anypats():
2356 if len(matcher.files()) == 1 and not matcher.anypats():
2356 file = matcher.files()[0]
2357 file = matcher.files()[0]
2357 mfl = repo.manifestlog
2358 mfl = repo.manifestlog
2358 mfnode = ctx.manifestnode()
2359 mfnode = ctx.manifestnode()
2359 try:
2360 try:
2360 if mfnode and mfl[mfnode].find(file)[0]:
2361 if mfnode and mfl[mfnode].find(file)[0]:
2361 scmutil.prefetchfiles(repo, [ctx.rev()], matcher)
2362 scmutil.prefetchfiles(repo, [ctx.rev()], matcher)
2362 write(file)
2363 write(file)
2363 return 0
2364 return 0
2364 except KeyError:
2365 except KeyError:
2365 pass
2366 pass
2366
2367
2367 scmutil.prefetchfiles(repo, [ctx.rev()], matcher)
2368 scmutil.prefetchfiles(repo, [ctx.rev()], matcher)
2368
2369
2369 for abs in ctx.walk(matcher):
2370 for abs in ctx.walk(matcher):
2370 write(abs)
2371 write(abs)
2371 err = 0
2372 err = 0
2372
2373
2373 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
2374 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
2374 for subpath in sorted(ctx.substate):
2375 for subpath in sorted(ctx.substate):
2375 sub = ctx.sub(subpath)
2376 sub = ctx.sub(subpath)
2376 try:
2377 try:
2377 submatch = matchmod.subdirmatcher(subpath, matcher)
2378 submatch = matchmod.subdirmatcher(subpath, matcher)
2378 subprefix = os.path.join(prefix, subpath)
2379 subprefix = os.path.join(prefix, subpath)
2379 if not sub.cat(submatch, basefm, fntemplate, subprefix,
2380 if not sub.cat(submatch, basefm, fntemplate, subprefix,
2380 **pycompat.strkwargs(opts)):
2381 **pycompat.strkwargs(opts)):
2381 err = 0
2382 err = 0
2382 except error.RepoLookupError:
2383 except error.RepoLookupError:
2383 ui.status(_("skipping missing subrepository: %s\n") %
2384 ui.status(_("skipping missing subrepository: %s\n") %
2384 uipathfn(subpath))
2385 uipathfn(subpath))
2385
2386
2386 return err
2387 return err
2387
2388
2388 def commit(ui, repo, commitfunc, pats, opts):
2389 def commit(ui, repo, commitfunc, pats, opts):
2389 '''commit the specified files or all outstanding changes'''
2390 '''commit the specified files or all outstanding changes'''
2390 date = opts.get('date')
2391 date = opts.get('date')
2391 if date:
2392 if date:
2392 opts['date'] = dateutil.parsedate(date)
2393 opts['date'] = dateutil.parsedate(date)
2393 message = logmessage(ui, opts)
2394 message = logmessage(ui, opts)
2394 matcher = scmutil.match(repo[None], pats, opts)
2395 matcher = scmutil.match(repo[None], pats, opts)
2395
2396
2396 dsguard = None
2397 dsguard = None
2397 # extract addremove carefully -- this function can be called from a command
2398 # extract addremove carefully -- this function can be called from a command
2398 # that doesn't support addremove
2399 # that doesn't support addremove
2399 if opts.get('addremove'):
2400 if opts.get('addremove'):
2400 dsguard = dirstateguard.dirstateguard(repo, 'commit')
2401 dsguard = dirstateguard.dirstateguard(repo, 'commit')
2401 with dsguard or util.nullcontextmanager():
2402 with dsguard or util.nullcontextmanager():
2402 if dsguard:
2403 if dsguard:
2403 relative = scmutil.anypats(pats, opts)
2404 relative = scmutil.anypats(pats, opts)
2404 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
2405 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
2405 if scmutil.addremove(repo, matcher, "", uipathfn, opts) != 0:
2406 if scmutil.addremove(repo, matcher, "", uipathfn, opts) != 0:
2406 raise error.Abort(
2407 raise error.Abort(
2407 _("failed to mark all new/missing files as added/removed"))
2408 _("failed to mark all new/missing files as added/removed"))
2408
2409
2409 return commitfunc(ui, repo, message, matcher, opts)
2410 return commitfunc(ui, repo, message, matcher, opts)
2410
2411
2411 def samefile(f, ctx1, ctx2):
2412 def samefile(f, ctx1, ctx2):
2412 if f in ctx1.manifest():
2413 if f in ctx1.manifest():
2413 a = ctx1.filectx(f)
2414 a = ctx1.filectx(f)
2414 if f in ctx2.manifest():
2415 if f in ctx2.manifest():
2415 b = ctx2.filectx(f)
2416 b = ctx2.filectx(f)
2416 return (not a.cmp(b)
2417 return (not a.cmp(b)
2417 and a.flags() == b.flags())
2418 and a.flags() == b.flags())
2418 else:
2419 else:
2419 return False
2420 return False
2420 else:
2421 else:
2421 return f not in ctx2.manifest()
2422 return f not in ctx2.manifest()
2422
2423
2423 def amend(ui, repo, old, extra, pats, opts):
2424 def amend(ui, repo, old, extra, pats, opts):
2424 # avoid cycle context -> subrepo -> cmdutil
2425 # avoid cycle context -> subrepo -> cmdutil
2425 from . import context
2426 from . import context
2426
2427
2427 # amend will reuse the existing user if not specified, but the obsolete
2428 # amend will reuse the existing user if not specified, but the obsolete
2428 # marker creation requires that the current user's name is specified.
2429 # marker creation requires that the current user's name is specified.
2429 if obsolete.isenabled(repo, obsolete.createmarkersopt):
2430 if obsolete.isenabled(repo, obsolete.createmarkersopt):
2430 ui.username() # raise exception if username not set
2431 ui.username() # raise exception if username not set
2431
2432
2432 ui.note(_('amending changeset %s\n') % old)
2433 ui.note(_('amending changeset %s\n') % old)
2433 base = old.p1()
2434 base = old.p1()
2434
2435
2435 with repo.wlock(), repo.lock(), repo.transaction('amend'):
2436 with repo.wlock(), repo.lock(), repo.transaction('amend'):
2436 # Participating changesets:
2437 # Participating changesets:
2437 #
2438 #
2438 # wctx o - workingctx that contains changes from working copy
2439 # wctx o - workingctx that contains changes from working copy
2439 # | to go into amending commit
2440 # | to go into amending commit
2440 # |
2441 # |
2441 # old o - changeset to amend
2442 # old o - changeset to amend
2442 # |
2443 # |
2443 # base o - first parent of the changeset to amend
2444 # base o - first parent of the changeset to amend
2444 wctx = repo[None]
2445 wctx = repo[None]
2445
2446
2446 # Copy to avoid mutating input
2447 # Copy to avoid mutating input
2447 extra = extra.copy()
2448 extra = extra.copy()
2448 # Update extra dict from amended commit (e.g. to preserve graft
2449 # Update extra dict from amended commit (e.g. to preserve graft
2449 # source)
2450 # source)
2450 extra.update(old.extra())
2451 extra.update(old.extra())
2451
2452
2452 # Also update it from the from the wctx
2453 # Also update it from the from the wctx
2453 extra.update(wctx.extra())
2454 extra.update(wctx.extra())
2454
2455
2455 user = opts.get('user') or old.user()
2456 user = opts.get('user') or old.user()
2456
2457
2457 datemaydiffer = False # date-only change should be ignored?
2458 datemaydiffer = False # date-only change should be ignored?
2458 if opts.get('date') and opts.get('currentdate'):
2459 if opts.get('date') and opts.get('currentdate'):
2459 raise error.Abort(_('--date and --currentdate are mutually '
2460 raise error.Abort(_('--date and --currentdate are mutually '
2460 'exclusive'))
2461 'exclusive'))
2461 if opts.get('date'):
2462 if opts.get('date'):
2462 date = dateutil.parsedate(opts.get('date'))
2463 date = dateutil.parsedate(opts.get('date'))
2463 elif opts.get('currentdate'):
2464 elif opts.get('currentdate'):
2464 date = dateutil.makedate()
2465 date = dateutil.makedate()
2465 elif (ui.configbool('rewrite', 'update-timestamp')
2466 elif (ui.configbool('rewrite', 'update-timestamp')
2466 and opts.get('currentdate') is None):
2467 and opts.get('currentdate') is None):
2467 date = dateutil.makedate()
2468 date = dateutil.makedate()
2468 datemaydiffer = True
2469 datemaydiffer = True
2469 else:
2470 else:
2470 date = old.date()
2471 date = old.date()
2471
2472
2472 if len(old.parents()) > 1:
2473 if len(old.parents()) > 1:
2473 # ctx.files() isn't reliable for merges, so fall back to the
2474 # ctx.files() isn't reliable for merges, so fall back to the
2474 # slower repo.status() method
2475 # slower repo.status() method
2475 files = set([fn for st in base.status(old)[:3]
2476 files = set([fn for st in base.status(old)[:3]
2476 for fn in st])
2477 for fn in st])
2477 else:
2478 else:
2478 files = set(old.files())
2479 files = set(old.files())
2479
2480
2480 # add/remove the files to the working copy if the "addremove" option
2481 # add/remove the files to the working copy if the "addremove" option
2481 # was specified.
2482 # was specified.
2482 matcher = scmutil.match(wctx, pats, opts)
2483 matcher = scmutil.match(wctx, pats, opts)
2483 relative = scmutil.anypats(pats, opts)
2484 relative = scmutil.anypats(pats, opts)
2484 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
2485 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
2485 if (opts.get('addremove')
2486 if (opts.get('addremove')
2486 and scmutil.addremove(repo, matcher, "", uipathfn, opts)):
2487 and scmutil.addremove(repo, matcher, "", uipathfn, opts)):
2487 raise error.Abort(
2488 raise error.Abort(
2488 _("failed to mark all new/missing files as added/removed"))
2489 _("failed to mark all new/missing files as added/removed"))
2489
2490
2490 # Check subrepos. This depends on in-place wctx._status update in
2491 # Check subrepos. This depends on in-place wctx._status update in
2491 # subrepo.precommit(). To minimize the risk of this hack, we do
2492 # subrepo.precommit(). To minimize the risk of this hack, we do
2492 # nothing if .hgsub does not exist.
2493 # nothing if .hgsub does not exist.
2493 if '.hgsub' in wctx or '.hgsub' in old:
2494 if '.hgsub' in wctx or '.hgsub' in old:
2494 subs, commitsubs, newsubstate = subrepoutil.precommit(
2495 subs, commitsubs, newsubstate = subrepoutil.precommit(
2495 ui, wctx, wctx._status, matcher)
2496 ui, wctx, wctx._status, matcher)
2496 # amend should abort if commitsubrepos is enabled
2497 # amend should abort if commitsubrepos is enabled
2497 assert not commitsubs
2498 assert not commitsubs
2498 if subs:
2499 if subs:
2499 subrepoutil.writestate(repo, newsubstate)
2500 subrepoutil.writestate(repo, newsubstate)
2500
2501
2501 ms = mergemod.mergestate.read(repo)
2502 ms = mergemod.mergestate.read(repo)
2502 mergeutil.checkunresolved(ms)
2503 mergeutil.checkunresolved(ms)
2503
2504
2504 filestoamend = set(f for f in wctx.files() if matcher(f))
2505 filestoamend = set(f for f in wctx.files() if matcher(f))
2505
2506
2506 changes = (len(filestoamend) > 0)
2507 changes = (len(filestoamend) > 0)
2507 if changes:
2508 if changes:
2508 # Recompute copies (avoid recording a -> b -> a)
2509 # Recompute copies (avoid recording a -> b -> a)
2509 copied = copies.pathcopies(base, wctx, matcher)
2510 copied = copies.pathcopies(base, wctx, matcher)
2510 if old.p2:
2511 if old.p2:
2511 copied.update(copies.pathcopies(old.p2(), wctx, matcher))
2512 copied.update(copies.pathcopies(old.p2(), wctx, matcher))
2512
2513
2513 # Prune files which were reverted by the updates: if old
2514 # Prune files which were reverted by the updates: if old
2514 # introduced file X and the file was renamed in the working
2515 # introduced file X and the file was renamed in the working
2515 # copy, then those two files are the same and
2516 # copy, then those two files are the same and
2516 # we can discard X from our list of files. Likewise if X
2517 # we can discard X from our list of files. Likewise if X
2517 # was removed, it's no longer relevant. If X is missing (aka
2518 # was removed, it's no longer relevant. If X is missing (aka
2518 # deleted), old X must be preserved.
2519 # deleted), old X must be preserved.
2519 files.update(filestoamend)
2520 files.update(filestoamend)
2520 files = [f for f in files if (not samefile(f, wctx, base)
2521 files = [f for f in files if (not samefile(f, wctx, base)
2521 or f in wctx.deleted())]
2522 or f in wctx.deleted())]
2522
2523
2523 def filectxfn(repo, ctx_, path):
2524 def filectxfn(repo, ctx_, path):
2524 try:
2525 try:
2525 # If the file being considered is not amongst the files
2526 # If the file being considered is not amongst the files
2526 # to be amended, we should return the file context from the
2527 # to be amended, we should return the file context from the
2527 # old changeset. This avoids issues when only some files in
2528 # old changeset. This avoids issues when only some files in
2528 # the working copy are being amended but there are also
2529 # the working copy are being amended but there are also
2529 # changes to other files from the old changeset.
2530 # changes to other files from the old changeset.
2530 if path not in filestoamend:
2531 if path not in filestoamend:
2531 return old.filectx(path)
2532 return old.filectx(path)
2532
2533
2533 # Return None for removed files.
2534 # Return None for removed files.
2534 if path in wctx.removed():
2535 if path in wctx.removed():
2535 return None
2536 return None
2536
2537
2537 fctx = wctx[path]
2538 fctx = wctx[path]
2538 flags = fctx.flags()
2539 flags = fctx.flags()
2539 mctx = context.memfilectx(repo, ctx_,
2540 mctx = context.memfilectx(repo, ctx_,
2540 fctx.path(), fctx.data(),
2541 fctx.path(), fctx.data(),
2541 islink='l' in flags,
2542 islink='l' in flags,
2542 isexec='x' in flags,
2543 isexec='x' in flags,
2543 copied=copied.get(path))
2544 copied=copied.get(path))
2544 return mctx
2545 return mctx
2545 except KeyError:
2546 except KeyError:
2546 return None
2547 return None
2547 else:
2548 else:
2548 ui.note(_('copying changeset %s to %s\n') % (old, base))
2549 ui.note(_('copying changeset %s to %s\n') % (old, base))
2549
2550
2550 # Use version of files as in the old cset
2551 # Use version of files as in the old cset
2551 def filectxfn(repo, ctx_, path):
2552 def filectxfn(repo, ctx_, path):
2552 try:
2553 try:
2553 return old.filectx(path)
2554 return old.filectx(path)
2554 except KeyError:
2555 except KeyError:
2555 return None
2556 return None
2556
2557
2557 # See if we got a message from -m or -l, if not, open the editor with
2558 # See if we got a message from -m or -l, if not, open the editor with
2558 # the message of the changeset to amend.
2559 # the message of the changeset to amend.
2559 message = logmessage(ui, opts)
2560 message = logmessage(ui, opts)
2560
2561
2561 editform = mergeeditform(old, 'commit.amend')
2562 editform = mergeeditform(old, 'commit.amend')
2562 editor = getcommiteditor(editform=editform,
2563 editor = getcommiteditor(editform=editform,
2563 **pycompat.strkwargs(opts))
2564 **pycompat.strkwargs(opts))
2564
2565
2565 if not message:
2566 if not message:
2566 editor = getcommiteditor(edit=True, editform=editform)
2567 editor = getcommiteditor(edit=True, editform=editform)
2567 message = old.description()
2568 message = old.description()
2568
2569
2569 pureextra = extra.copy()
2570 pureextra = extra.copy()
2570 extra['amend_source'] = old.hex()
2571 extra['amend_source'] = old.hex()
2571
2572
2572 new = context.memctx(repo,
2573 new = context.memctx(repo,
2573 parents=[base.node(), old.p2().node()],
2574 parents=[base.node(), old.p2().node()],
2574 text=message,
2575 text=message,
2575 files=files,
2576 files=files,
2576 filectxfn=filectxfn,
2577 filectxfn=filectxfn,
2577 user=user,
2578 user=user,
2578 date=date,
2579 date=date,
2579 extra=extra,
2580 extra=extra,
2580 editor=editor)
2581 editor=editor)
2581
2582
2582 newdesc = changelog.stripdesc(new.description())
2583 newdesc = changelog.stripdesc(new.description())
2583 if ((not changes)
2584 if ((not changes)
2584 and newdesc == old.description()
2585 and newdesc == old.description()
2585 and user == old.user()
2586 and user == old.user()
2586 and (date == old.date() or datemaydiffer)
2587 and (date == old.date() or datemaydiffer)
2587 and pureextra == old.extra()):
2588 and pureextra == old.extra()):
2588 # nothing changed. continuing here would create a new node
2589 # nothing changed. continuing here would create a new node
2589 # anyway because of the amend_source noise.
2590 # anyway because of the amend_source noise.
2590 #
2591 #
2591 # This not what we expect from amend.
2592 # This not what we expect from amend.
2592 return old.node()
2593 return old.node()
2593
2594
2594 commitphase = None
2595 commitphase = None
2595 if opts.get('secret'):
2596 if opts.get('secret'):
2596 commitphase = phases.secret
2597 commitphase = phases.secret
2597 newid = repo.commitctx(new)
2598 newid = repo.commitctx(new)
2598
2599
2599 # Reroute the working copy parent to the new changeset
2600 # Reroute the working copy parent to the new changeset
2600 repo.setparents(newid, nullid)
2601 repo.setparents(newid, nullid)
2601 mapping = {old.node(): (newid,)}
2602 mapping = {old.node(): (newid,)}
2602 obsmetadata = None
2603 obsmetadata = None
2603 if opts.get('note'):
2604 if opts.get('note'):
2604 obsmetadata = {'note': encoding.fromlocal(opts['note'])}
2605 obsmetadata = {'note': encoding.fromlocal(opts['note'])}
2605 backup = ui.configbool('rewrite', 'backup-bundle')
2606 backup = ui.configbool('rewrite', 'backup-bundle')
2606 scmutil.cleanupnodes(repo, mapping, 'amend', metadata=obsmetadata,
2607 scmutil.cleanupnodes(repo, mapping, 'amend', metadata=obsmetadata,
2607 fixphase=True, targetphase=commitphase,
2608 fixphase=True, targetphase=commitphase,
2608 backup=backup)
2609 backup=backup)
2609
2610
2610 # Fixing the dirstate because localrepo.commitctx does not update
2611 # Fixing the dirstate because localrepo.commitctx does not update
2611 # it. This is rather convenient because we did not need to update
2612 # it. This is rather convenient because we did not need to update
2612 # the dirstate for all the files in the new commit which commitctx
2613 # the dirstate for all the files in the new commit which commitctx
2613 # could have done if it updated the dirstate. Now, we can
2614 # could have done if it updated the dirstate. Now, we can
2614 # selectively update the dirstate only for the amended files.
2615 # selectively update the dirstate only for the amended files.
2615 dirstate = repo.dirstate
2616 dirstate = repo.dirstate
2616
2617
2617 # Update the state of the files which were added and
2618 # Update the state of the files which were added and
2618 # and modified in the amend to "normal" in the dirstate.
2619 # and modified in the amend to "normal" in the dirstate.
2619 normalfiles = set(wctx.modified() + wctx.added()) & filestoamend
2620 normalfiles = set(wctx.modified() + wctx.added()) & filestoamend
2620 for f in normalfiles:
2621 for f in normalfiles:
2621 dirstate.normal(f)
2622 dirstate.normal(f)
2622
2623
2623 # Update the state of files which were removed in the amend
2624 # Update the state of files which were removed in the amend
2624 # to "removed" in the dirstate.
2625 # to "removed" in the dirstate.
2625 removedfiles = set(wctx.removed()) & filestoamend
2626 removedfiles = set(wctx.removed()) & filestoamend
2626 for f in removedfiles:
2627 for f in removedfiles:
2627 dirstate.drop(f)
2628 dirstate.drop(f)
2628
2629
2629 return newid
2630 return newid
2630
2631
2631 def commiteditor(repo, ctx, subs, editform=''):
2632 def commiteditor(repo, ctx, subs, editform=''):
2632 if ctx.description():
2633 if ctx.description():
2633 return ctx.description()
2634 return ctx.description()
2634 return commitforceeditor(repo, ctx, subs, editform=editform,
2635 return commitforceeditor(repo, ctx, subs, editform=editform,
2635 unchangedmessagedetection=True)
2636 unchangedmessagedetection=True)
2636
2637
2637 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
2638 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
2638 editform='', unchangedmessagedetection=False):
2639 editform='', unchangedmessagedetection=False):
2639 if not extramsg:
2640 if not extramsg:
2640 extramsg = _("Leave message empty to abort commit.")
2641 extramsg = _("Leave message empty to abort commit.")
2641
2642
2642 forms = [e for e in editform.split('.') if e]
2643 forms = [e for e in editform.split('.') if e]
2643 forms.insert(0, 'changeset')
2644 forms.insert(0, 'changeset')
2644 templatetext = None
2645 templatetext = None
2645 while forms:
2646 while forms:
2646 ref = '.'.join(forms)
2647 ref = '.'.join(forms)
2647 if repo.ui.config('committemplate', ref):
2648 if repo.ui.config('committemplate', ref):
2648 templatetext = committext = buildcommittemplate(
2649 templatetext = committext = buildcommittemplate(
2649 repo, ctx, subs, extramsg, ref)
2650 repo, ctx, subs, extramsg, ref)
2650 break
2651 break
2651 forms.pop()
2652 forms.pop()
2652 else:
2653 else:
2653 committext = buildcommittext(repo, ctx, subs, extramsg)
2654 committext = buildcommittext(repo, ctx, subs, extramsg)
2654
2655
2655 # run editor in the repository root
2656 # run editor in the repository root
2656 olddir = encoding.getcwd()
2657 olddir = encoding.getcwd()
2657 os.chdir(repo.root)
2658 os.chdir(repo.root)
2658
2659
2659 # make in-memory changes visible to external process
2660 # make in-memory changes visible to external process
2660 tr = repo.currenttransaction()
2661 tr = repo.currenttransaction()
2661 repo.dirstate.write(tr)
2662 repo.dirstate.write(tr)
2662 pending = tr and tr.writepending() and repo.root
2663 pending = tr and tr.writepending() and repo.root
2663
2664
2664 editortext = repo.ui.edit(committext, ctx.user(), ctx.extra(),
2665 editortext = repo.ui.edit(committext, ctx.user(), ctx.extra(),
2665 editform=editform, pending=pending,
2666 editform=editform, pending=pending,
2666 repopath=repo.path, action='commit')
2667 repopath=repo.path, action='commit')
2667 text = editortext
2668 text = editortext
2668
2669
2669 # strip away anything below this special string (used for editors that want
2670 # strip away anything below this special string (used for editors that want
2670 # to display the diff)
2671 # to display the diff)
2671 stripbelow = re.search(_linebelow, text, flags=re.MULTILINE)
2672 stripbelow = re.search(_linebelow, text, flags=re.MULTILINE)
2672 if stripbelow:
2673 if stripbelow:
2673 text = text[:stripbelow.start()]
2674 text = text[:stripbelow.start()]
2674
2675
2675 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
2676 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
2676 os.chdir(olddir)
2677 os.chdir(olddir)
2677
2678
2678 if finishdesc:
2679 if finishdesc:
2679 text = finishdesc(text)
2680 text = finishdesc(text)
2680 if not text.strip():
2681 if not text.strip():
2681 raise error.Abort(_("empty commit message"))
2682 raise error.Abort(_("empty commit message"))
2682 if unchangedmessagedetection and editortext == templatetext:
2683 if unchangedmessagedetection and editortext == templatetext:
2683 raise error.Abort(_("commit message unchanged"))
2684 raise error.Abort(_("commit message unchanged"))
2684
2685
2685 return text
2686 return text
2686
2687
2687 def buildcommittemplate(repo, ctx, subs, extramsg, ref):
2688 def buildcommittemplate(repo, ctx, subs, extramsg, ref):
2688 ui = repo.ui
2689 ui = repo.ui
2689 spec = formatter.templatespec(ref, None, None)
2690 spec = formatter.templatespec(ref, None, None)
2690 t = logcmdutil.changesettemplater(ui, repo, spec)
2691 t = logcmdutil.changesettemplater(ui, repo, spec)
2691 t.t.cache.update((k, templater.unquotestring(v))
2692 t.t.cache.update((k, templater.unquotestring(v))
2692 for k, v in repo.ui.configitems('committemplate'))
2693 for k, v in repo.ui.configitems('committemplate'))
2693
2694
2694 if not extramsg:
2695 if not extramsg:
2695 extramsg = '' # ensure that extramsg is string
2696 extramsg = '' # ensure that extramsg is string
2696
2697
2697 ui.pushbuffer()
2698 ui.pushbuffer()
2698 t.show(ctx, extramsg=extramsg)
2699 t.show(ctx, extramsg=extramsg)
2699 return ui.popbuffer()
2700 return ui.popbuffer()
2700
2701
2701 def hgprefix(msg):
2702 def hgprefix(msg):
2702 return "\n".join(["HG: %s" % a for a in msg.split("\n") if a])
2703 return "\n".join(["HG: %s" % a for a in msg.split("\n") if a])
2703
2704
2704 def buildcommittext(repo, ctx, subs, extramsg):
2705 def buildcommittext(repo, ctx, subs, extramsg):
2705 edittext = []
2706 edittext = []
2706 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2707 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2707 if ctx.description():
2708 if ctx.description():
2708 edittext.append(ctx.description())
2709 edittext.append(ctx.description())
2709 edittext.append("")
2710 edittext.append("")
2710 edittext.append("") # Empty line between message and comments.
2711 edittext.append("") # Empty line between message and comments.
2711 edittext.append(hgprefix(_("Enter commit message."
2712 edittext.append(hgprefix(_("Enter commit message."
2712 " Lines beginning with 'HG:' are removed.")))
2713 " Lines beginning with 'HG:' are removed.")))
2713 edittext.append(hgprefix(extramsg))
2714 edittext.append(hgprefix(extramsg))
2714 edittext.append("HG: --")
2715 edittext.append("HG: --")
2715 edittext.append(hgprefix(_("user: %s") % ctx.user()))
2716 edittext.append(hgprefix(_("user: %s") % ctx.user()))
2716 if ctx.p2():
2717 if ctx.p2():
2717 edittext.append(hgprefix(_("branch merge")))
2718 edittext.append(hgprefix(_("branch merge")))
2718 if ctx.branch():
2719 if ctx.branch():
2719 edittext.append(hgprefix(_("branch '%s'") % ctx.branch()))
2720 edittext.append(hgprefix(_("branch '%s'") % ctx.branch()))
2720 if bookmarks.isactivewdirparent(repo):
2721 if bookmarks.isactivewdirparent(repo):
2721 edittext.append(hgprefix(_("bookmark '%s'") % repo._activebookmark))
2722 edittext.append(hgprefix(_("bookmark '%s'") % repo._activebookmark))
2722 edittext.extend([hgprefix(_("subrepo %s") % s) for s in subs])
2723 edittext.extend([hgprefix(_("subrepo %s") % s) for s in subs])
2723 edittext.extend([hgprefix(_("added %s") % f) for f in added])
2724 edittext.extend([hgprefix(_("added %s") % f) for f in added])
2724 edittext.extend([hgprefix(_("changed %s") % f) for f in modified])
2725 edittext.extend([hgprefix(_("changed %s") % f) for f in modified])
2725 edittext.extend([hgprefix(_("removed %s") % f) for f in removed])
2726 edittext.extend([hgprefix(_("removed %s") % f) for f in removed])
2726 if not added and not modified and not removed:
2727 if not added and not modified and not removed:
2727 edittext.append(hgprefix(_("no files changed")))
2728 edittext.append(hgprefix(_("no files changed")))
2728 edittext.append("")
2729 edittext.append("")
2729
2730
2730 return "\n".join(edittext)
2731 return "\n".join(edittext)
2731
2732
2732 def commitstatus(repo, node, branch, bheads=None, opts=None):
2733 def commitstatus(repo, node, branch, bheads=None, opts=None):
2733 if opts is None:
2734 if opts is None:
2734 opts = {}
2735 opts = {}
2735 ctx = repo[node]
2736 ctx = repo[node]
2736 parents = ctx.parents()
2737 parents = ctx.parents()
2737
2738
2738 if (not opts.get('amend') and bheads and node not in bheads and not
2739 if (not opts.get('amend') and bheads and node not in bheads and not
2739 [x for x in parents if x.node() in bheads and x.branch() == branch]):
2740 [x for x in parents if x.node() in bheads and x.branch() == branch]):
2740 repo.ui.status(_('created new head\n'))
2741 repo.ui.status(_('created new head\n'))
2741 # The message is not printed for initial roots. For the other
2742 # The message is not printed for initial roots. For the other
2742 # changesets, it is printed in the following situations:
2743 # changesets, it is printed in the following situations:
2743 #
2744 #
2744 # Par column: for the 2 parents with ...
2745 # Par column: for the 2 parents with ...
2745 # N: null or no parent
2746 # N: null or no parent
2746 # B: parent is on another named branch
2747 # B: parent is on another named branch
2747 # C: parent is a regular non head changeset
2748 # C: parent is a regular non head changeset
2748 # H: parent was a branch head of the current branch
2749 # H: parent was a branch head of the current branch
2749 # Msg column: whether we print "created new head" message
2750 # Msg column: whether we print "created new head" message
2750 # In the following, it is assumed that there already exists some
2751 # In the following, it is assumed that there already exists some
2751 # initial branch heads of the current branch, otherwise nothing is
2752 # initial branch heads of the current branch, otherwise nothing is
2752 # printed anyway.
2753 # printed anyway.
2753 #
2754 #
2754 # Par Msg Comment
2755 # Par Msg Comment
2755 # N N y additional topo root
2756 # N N y additional topo root
2756 #
2757 #
2757 # B N y additional branch root
2758 # B N y additional branch root
2758 # C N y additional topo head
2759 # C N y additional topo head
2759 # H N n usual case
2760 # H N n usual case
2760 #
2761 #
2761 # B B y weird additional branch root
2762 # B B y weird additional branch root
2762 # C B y branch merge
2763 # C B y branch merge
2763 # H B n merge with named branch
2764 # H B n merge with named branch
2764 #
2765 #
2765 # C C y additional head from merge
2766 # C C y additional head from merge
2766 # C H n merge with a head
2767 # C H n merge with a head
2767 #
2768 #
2768 # H H n head merge: head count decreases
2769 # H H n head merge: head count decreases
2769
2770
2770 if not opts.get('close_branch'):
2771 if not opts.get('close_branch'):
2771 for r in parents:
2772 for r in parents:
2772 if r.closesbranch() and r.branch() == branch:
2773 if r.closesbranch() and r.branch() == branch:
2773 repo.ui.status(_('reopening closed branch head %d\n') % r.rev())
2774 repo.ui.status(_('reopening closed branch head %d\n') % r.rev())
2774
2775
2775 if repo.ui.debugflag:
2776 if repo.ui.debugflag:
2776 repo.ui.write(_('committed changeset %d:%s\n') % (ctx.rev(), ctx.hex()))
2777 repo.ui.write(_('committed changeset %d:%s\n') % (ctx.rev(), ctx.hex()))
2777 elif repo.ui.verbose:
2778 elif repo.ui.verbose:
2778 repo.ui.write(_('committed changeset %d:%s\n') % (ctx.rev(), ctx))
2779 repo.ui.write(_('committed changeset %d:%s\n') % (ctx.rev(), ctx))
2779
2780
2780 def postcommitstatus(repo, pats, opts):
2781 def postcommitstatus(repo, pats, opts):
2781 return repo.status(match=scmutil.match(repo[None], pats, opts))
2782 return repo.status(match=scmutil.match(repo[None], pats, opts))
2782
2783
2783 def revert(ui, repo, ctx, parents, *pats, **opts):
2784 def revert(ui, repo, ctx, parents, *pats, **opts):
2784 opts = pycompat.byteskwargs(opts)
2785 opts = pycompat.byteskwargs(opts)
2785 parent, p2 = parents
2786 parent, p2 = parents
2786 node = ctx.node()
2787 node = ctx.node()
2787
2788
2788 mf = ctx.manifest()
2789 mf = ctx.manifest()
2789 if node == p2:
2790 if node == p2:
2790 parent = p2
2791 parent = p2
2791
2792
2792 # need all matching names in dirstate and manifest of target rev,
2793 # need all matching names in dirstate and manifest of target rev,
2793 # so have to walk both. do not print errors if files exist in one
2794 # so have to walk both. do not print errors if files exist in one
2794 # but not other. in both cases, filesets should be evaluated against
2795 # but not other. in both cases, filesets should be evaluated against
2795 # workingctx to get consistent result (issue4497). this means 'set:**'
2796 # workingctx to get consistent result (issue4497). this means 'set:**'
2796 # cannot be used to select missing files from target rev.
2797 # cannot be used to select missing files from target rev.
2797
2798
2798 # `names` is a mapping for all elements in working copy and target revision
2799 # `names` is a mapping for all elements in working copy and target revision
2799 # The mapping is in the form:
2800 # The mapping is in the form:
2800 # <abs path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
2801 # <abs path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
2801 names = {}
2802 names = {}
2802 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
2803 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
2803
2804
2804 with repo.wlock():
2805 with repo.wlock():
2805 ## filling of the `names` mapping
2806 ## filling of the `names` mapping
2806 # walk dirstate to fill `names`
2807 # walk dirstate to fill `names`
2807
2808
2808 interactive = opts.get('interactive', False)
2809 interactive = opts.get('interactive', False)
2809 wctx = repo[None]
2810 wctx = repo[None]
2810 m = scmutil.match(wctx, pats, opts)
2811 m = scmutil.match(wctx, pats, opts)
2811
2812
2812 # we'll need this later
2813 # we'll need this later
2813 targetsubs = sorted(s for s in wctx.substate if m(s))
2814 targetsubs = sorted(s for s in wctx.substate if m(s))
2814
2815
2815 if not m.always():
2816 if not m.always():
2816 matcher = matchmod.badmatch(m, lambda x, y: False)
2817 matcher = matchmod.badmatch(m, lambda x, y: False)
2817 for abs in wctx.walk(matcher):
2818 for abs in wctx.walk(matcher):
2818 names[abs] = m.exact(abs)
2819 names[abs] = m.exact(abs)
2819
2820
2820 # walk target manifest to fill `names`
2821 # walk target manifest to fill `names`
2821
2822
2822 def badfn(path, msg):
2823 def badfn(path, msg):
2823 if path in names:
2824 if path in names:
2824 return
2825 return
2825 if path in ctx.substate:
2826 if path in ctx.substate:
2826 return
2827 return
2827 path_ = path + '/'
2828 path_ = path + '/'
2828 for f in names:
2829 for f in names:
2829 if f.startswith(path_):
2830 if f.startswith(path_):
2830 return
2831 return
2831 ui.warn("%s: %s\n" % (uipathfn(path), msg))
2832 ui.warn("%s: %s\n" % (uipathfn(path), msg))
2832
2833
2833 for abs in ctx.walk(matchmod.badmatch(m, badfn)):
2834 for abs in ctx.walk(matchmod.badmatch(m, badfn)):
2834 if abs not in names:
2835 if abs not in names:
2835 names[abs] = m.exact(abs)
2836 names[abs] = m.exact(abs)
2836
2837
2837 # Find status of all file in `names`.
2838 # Find status of all file in `names`.
2838 m = scmutil.matchfiles(repo, names)
2839 m = scmutil.matchfiles(repo, names)
2839
2840
2840 changes = repo.status(node1=node, match=m,
2841 changes = repo.status(node1=node, match=m,
2841 unknown=True, ignored=True, clean=True)
2842 unknown=True, ignored=True, clean=True)
2842 else:
2843 else:
2843 changes = repo.status(node1=node, match=m)
2844 changes = repo.status(node1=node, match=m)
2844 for kind in changes:
2845 for kind in changes:
2845 for abs in kind:
2846 for abs in kind:
2846 names[abs] = m.exact(abs)
2847 names[abs] = m.exact(abs)
2847
2848
2848 m = scmutil.matchfiles(repo, names)
2849 m = scmutil.matchfiles(repo, names)
2849
2850
2850 modified = set(changes.modified)
2851 modified = set(changes.modified)
2851 added = set(changes.added)
2852 added = set(changes.added)
2852 removed = set(changes.removed)
2853 removed = set(changes.removed)
2853 _deleted = set(changes.deleted)
2854 _deleted = set(changes.deleted)
2854 unknown = set(changes.unknown)
2855 unknown = set(changes.unknown)
2855 unknown.update(changes.ignored)
2856 unknown.update(changes.ignored)
2856 clean = set(changes.clean)
2857 clean = set(changes.clean)
2857 modadded = set()
2858 modadded = set()
2858
2859
2859 # We need to account for the state of the file in the dirstate,
2860 # We need to account for the state of the file in the dirstate,
2860 # even when we revert against something else than parent. This will
2861 # even when we revert against something else than parent. This will
2861 # slightly alter the behavior of revert (doing back up or not, delete
2862 # slightly alter the behavior of revert (doing back up or not, delete
2862 # or just forget etc).
2863 # or just forget etc).
2863 if parent == node:
2864 if parent == node:
2864 dsmodified = modified
2865 dsmodified = modified
2865 dsadded = added
2866 dsadded = added
2866 dsremoved = removed
2867 dsremoved = removed
2867 # store all local modifications, useful later for rename detection
2868 # store all local modifications, useful later for rename detection
2868 localchanges = dsmodified | dsadded
2869 localchanges = dsmodified | dsadded
2869 modified, added, removed = set(), set(), set()
2870 modified, added, removed = set(), set(), set()
2870 else:
2871 else:
2871 changes = repo.status(node1=parent, match=m)
2872 changes = repo.status(node1=parent, match=m)
2872 dsmodified = set(changes.modified)
2873 dsmodified = set(changes.modified)
2873 dsadded = set(changes.added)
2874 dsadded = set(changes.added)
2874 dsremoved = set(changes.removed)
2875 dsremoved = set(changes.removed)
2875 # store all local modifications, useful later for rename detection
2876 # store all local modifications, useful later for rename detection
2876 localchanges = dsmodified | dsadded
2877 localchanges = dsmodified | dsadded
2877
2878
2878 # only take into account for removes between wc and target
2879 # only take into account for removes between wc and target
2879 clean |= dsremoved - removed
2880 clean |= dsremoved - removed
2880 dsremoved &= removed
2881 dsremoved &= removed
2881 # distinct between dirstate remove and other
2882 # distinct between dirstate remove and other
2882 removed -= dsremoved
2883 removed -= dsremoved
2883
2884
2884 modadded = added & dsmodified
2885 modadded = added & dsmodified
2885 added -= modadded
2886 added -= modadded
2886
2887
2887 # tell newly modified apart.
2888 # tell newly modified apart.
2888 dsmodified &= modified
2889 dsmodified &= modified
2889 dsmodified |= modified & dsadded # dirstate added may need backup
2890 dsmodified |= modified & dsadded # dirstate added may need backup
2890 modified -= dsmodified
2891 modified -= dsmodified
2891
2892
2892 # We need to wait for some post-processing to update this set
2893 # We need to wait for some post-processing to update this set
2893 # before making the distinction. The dirstate will be used for
2894 # before making the distinction. The dirstate will be used for
2894 # that purpose.
2895 # that purpose.
2895 dsadded = added
2896 dsadded = added
2896
2897
2897 # in case of merge, files that are actually added can be reported as
2898 # in case of merge, files that are actually added can be reported as
2898 # modified, we need to post process the result
2899 # modified, we need to post process the result
2899 if p2 != nullid:
2900 if p2 != nullid:
2900 mergeadd = set(dsmodified)
2901 mergeadd = set(dsmodified)
2901 for path in dsmodified:
2902 for path in dsmodified:
2902 if path in mf:
2903 if path in mf:
2903 mergeadd.remove(path)
2904 mergeadd.remove(path)
2904 dsadded |= mergeadd
2905 dsadded |= mergeadd
2905 dsmodified -= mergeadd
2906 dsmodified -= mergeadd
2906
2907
2907 # if f is a rename, update `names` to also revert the source
2908 # if f is a rename, update `names` to also revert the source
2908 for f in localchanges:
2909 for f in localchanges:
2909 src = repo.dirstate.copied(f)
2910 src = repo.dirstate.copied(f)
2910 # XXX should we check for rename down to target node?
2911 # XXX should we check for rename down to target node?
2911 if src and src not in names and repo.dirstate[src] == 'r':
2912 if src and src not in names and repo.dirstate[src] == 'r':
2912 dsremoved.add(src)
2913 dsremoved.add(src)
2913 names[src] = True
2914 names[src] = True
2914
2915
2915 # determine the exact nature of the deleted changesets
2916 # determine the exact nature of the deleted changesets
2916 deladded = set(_deleted)
2917 deladded = set(_deleted)
2917 for path in _deleted:
2918 for path in _deleted:
2918 if path in mf:
2919 if path in mf:
2919 deladded.remove(path)
2920 deladded.remove(path)
2920 deleted = _deleted - deladded
2921 deleted = _deleted - deladded
2921
2922
2922 # distinguish between file to forget and the other
2923 # distinguish between file to forget and the other
2923 added = set()
2924 added = set()
2924 for abs in dsadded:
2925 for abs in dsadded:
2925 if repo.dirstate[abs] != 'a':
2926 if repo.dirstate[abs] != 'a':
2926 added.add(abs)
2927 added.add(abs)
2927 dsadded -= added
2928 dsadded -= added
2928
2929
2929 for abs in deladded:
2930 for abs in deladded:
2930 if repo.dirstate[abs] == 'a':
2931 if repo.dirstate[abs] == 'a':
2931 dsadded.add(abs)
2932 dsadded.add(abs)
2932 deladded -= dsadded
2933 deladded -= dsadded
2933
2934
2934 # For files marked as removed, we check if an unknown file is present at
2935 # For files marked as removed, we check if an unknown file is present at
2935 # the same path. If a such file exists it may need to be backed up.
2936 # the same path. If a such file exists it may need to be backed up.
2936 # Making the distinction at this stage helps have simpler backup
2937 # Making the distinction at this stage helps have simpler backup
2937 # logic.
2938 # logic.
2938 removunk = set()
2939 removunk = set()
2939 for abs in removed:
2940 for abs in removed:
2940 target = repo.wjoin(abs)
2941 target = repo.wjoin(abs)
2941 if os.path.lexists(target):
2942 if os.path.lexists(target):
2942 removunk.add(abs)
2943 removunk.add(abs)
2943 removed -= removunk
2944 removed -= removunk
2944
2945
2945 dsremovunk = set()
2946 dsremovunk = set()
2946 for abs in dsremoved:
2947 for abs in dsremoved:
2947 target = repo.wjoin(abs)
2948 target = repo.wjoin(abs)
2948 if os.path.lexists(target):
2949 if os.path.lexists(target):
2949 dsremovunk.add(abs)
2950 dsremovunk.add(abs)
2950 dsremoved -= dsremovunk
2951 dsremoved -= dsremovunk
2951
2952
2952 # action to be actually performed by revert
2953 # action to be actually performed by revert
2953 # (<list of file>, message>) tuple
2954 # (<list of file>, message>) tuple
2954 actions = {'revert': ([], _('reverting %s\n')),
2955 actions = {'revert': ([], _('reverting %s\n')),
2955 'add': ([], _('adding %s\n')),
2956 'add': ([], _('adding %s\n')),
2956 'remove': ([], _('removing %s\n')),
2957 'remove': ([], _('removing %s\n')),
2957 'drop': ([], _('removing %s\n')),
2958 'drop': ([], _('removing %s\n')),
2958 'forget': ([], _('forgetting %s\n')),
2959 'forget': ([], _('forgetting %s\n')),
2959 'undelete': ([], _('undeleting %s\n')),
2960 'undelete': ([], _('undeleting %s\n')),
2960 'noop': (None, _('no changes needed to %s\n')),
2961 'noop': (None, _('no changes needed to %s\n')),
2961 'unknown': (None, _('file not managed: %s\n')),
2962 'unknown': (None, _('file not managed: %s\n')),
2962 }
2963 }
2963
2964
2964 # "constant" that convey the backup strategy.
2965 # "constant" that convey the backup strategy.
2965 # All set to `discard` if `no-backup` is set do avoid checking
2966 # All set to `discard` if `no-backup` is set do avoid checking
2966 # no_backup lower in the code.
2967 # no_backup lower in the code.
2967 # These values are ordered for comparison purposes
2968 # These values are ordered for comparison purposes
2968 backupinteractive = 3 # do backup if interactively modified
2969 backupinteractive = 3 # do backup if interactively modified
2969 backup = 2 # unconditionally do backup
2970 backup = 2 # unconditionally do backup
2970 check = 1 # check if the existing file differs from target
2971 check = 1 # check if the existing file differs from target
2971 discard = 0 # never do backup
2972 discard = 0 # never do backup
2972 if opts.get('no_backup'):
2973 if opts.get('no_backup'):
2973 backupinteractive = backup = check = discard
2974 backupinteractive = backup = check = discard
2974 if interactive:
2975 if interactive:
2975 dsmodifiedbackup = backupinteractive
2976 dsmodifiedbackup = backupinteractive
2976 else:
2977 else:
2977 dsmodifiedbackup = backup
2978 dsmodifiedbackup = backup
2978 tobackup = set()
2979 tobackup = set()
2979
2980
2980 backupanddel = actions['remove']
2981 backupanddel = actions['remove']
2981 if not opts.get('no_backup'):
2982 if not opts.get('no_backup'):
2982 backupanddel = actions['drop']
2983 backupanddel = actions['drop']
2983
2984
2984 disptable = (
2985 disptable = (
2985 # dispatch table:
2986 # dispatch table:
2986 # file state
2987 # file state
2987 # action
2988 # action
2988 # make backup
2989 # make backup
2989
2990
2990 ## Sets that results that will change file on disk
2991 ## Sets that results that will change file on disk
2991 # Modified compared to target, no local change
2992 # Modified compared to target, no local change
2992 (modified, actions['revert'], discard),
2993 (modified, actions['revert'], discard),
2993 # Modified compared to target, but local file is deleted
2994 # Modified compared to target, but local file is deleted
2994 (deleted, actions['revert'], discard),
2995 (deleted, actions['revert'], discard),
2995 # Modified compared to target, local change
2996 # Modified compared to target, local change
2996 (dsmodified, actions['revert'], dsmodifiedbackup),
2997 (dsmodified, actions['revert'], dsmodifiedbackup),
2997 # Added since target
2998 # Added since target
2998 (added, actions['remove'], discard),
2999 (added, actions['remove'], discard),
2999 # Added in working directory
3000 # Added in working directory
3000 (dsadded, actions['forget'], discard),
3001 (dsadded, actions['forget'], discard),
3001 # Added since target, have local modification
3002 # Added since target, have local modification
3002 (modadded, backupanddel, backup),
3003 (modadded, backupanddel, backup),
3003 # Added since target but file is missing in working directory
3004 # Added since target but file is missing in working directory
3004 (deladded, actions['drop'], discard),
3005 (deladded, actions['drop'], discard),
3005 # Removed since target, before working copy parent
3006 # Removed since target, before working copy parent
3006 (removed, actions['add'], discard),
3007 (removed, actions['add'], discard),
3007 # Same as `removed` but an unknown file exists at the same path
3008 # Same as `removed` but an unknown file exists at the same path
3008 (removunk, actions['add'], check),
3009 (removunk, actions['add'], check),
3009 # Removed since targe, marked as such in working copy parent
3010 # Removed since targe, marked as such in working copy parent
3010 (dsremoved, actions['undelete'], discard),
3011 (dsremoved, actions['undelete'], discard),
3011 # Same as `dsremoved` but an unknown file exists at the same path
3012 # Same as `dsremoved` but an unknown file exists at the same path
3012 (dsremovunk, actions['undelete'], check),
3013 (dsremovunk, actions['undelete'], check),
3013 ## the following sets does not result in any file changes
3014 ## the following sets does not result in any file changes
3014 # File with no modification
3015 # File with no modification
3015 (clean, actions['noop'], discard),
3016 (clean, actions['noop'], discard),
3016 # Existing file, not tracked anywhere
3017 # Existing file, not tracked anywhere
3017 (unknown, actions['unknown'], discard),
3018 (unknown, actions['unknown'], discard),
3018 )
3019 )
3019
3020
3020 for abs, exact in sorted(names.items()):
3021 for abs, exact in sorted(names.items()):
3021 # target file to be touch on disk (relative to cwd)
3022 # target file to be touch on disk (relative to cwd)
3022 target = repo.wjoin(abs)
3023 target = repo.wjoin(abs)
3023 # search the entry in the dispatch table.
3024 # search the entry in the dispatch table.
3024 # if the file is in any of these sets, it was touched in the working
3025 # if the file is in any of these sets, it was touched in the working
3025 # directory parent and we are sure it needs to be reverted.
3026 # directory parent and we are sure it needs to be reverted.
3026 for table, (xlist, msg), dobackup in disptable:
3027 for table, (xlist, msg), dobackup in disptable:
3027 if abs not in table:
3028 if abs not in table:
3028 continue
3029 continue
3029 if xlist is not None:
3030 if xlist is not None:
3030 xlist.append(abs)
3031 xlist.append(abs)
3031 if dobackup:
3032 if dobackup:
3032 # If in interactive mode, don't automatically create
3033 # If in interactive mode, don't automatically create
3033 # .orig files (issue4793)
3034 # .orig files (issue4793)
3034 if dobackup == backupinteractive:
3035 if dobackup == backupinteractive:
3035 tobackup.add(abs)
3036 tobackup.add(abs)
3036 elif (backup <= dobackup or wctx[abs].cmp(ctx[abs])):
3037 elif (backup <= dobackup or wctx[abs].cmp(ctx[abs])):
3037 absbakname = scmutil.backuppath(ui, repo, abs)
3038 absbakname = scmutil.backuppath(ui, repo, abs)
3038 bakname = os.path.relpath(absbakname,
3039 bakname = os.path.relpath(absbakname,
3039 start=repo.root)
3040 start=repo.root)
3040 ui.note(_('saving current version of %s as %s\n') %
3041 ui.note(_('saving current version of %s as %s\n') %
3041 (uipathfn(abs), uipathfn(bakname)))
3042 (uipathfn(abs), uipathfn(bakname)))
3042 if not opts.get('dry_run'):
3043 if not opts.get('dry_run'):
3043 if interactive:
3044 if interactive:
3044 util.copyfile(target, absbakname)
3045 util.copyfile(target, absbakname)
3045 else:
3046 else:
3046 util.rename(target, absbakname)
3047 util.rename(target, absbakname)
3047 if opts.get('dry_run'):
3048 if opts.get('dry_run'):
3048 if ui.verbose or not exact:
3049 if ui.verbose or not exact:
3049 ui.status(msg % uipathfn(abs))
3050 ui.status(msg % uipathfn(abs))
3050 elif exact:
3051 elif exact:
3051 ui.warn(msg % uipathfn(abs))
3052 ui.warn(msg % uipathfn(abs))
3052 break
3053 break
3053
3054
3054 if not opts.get('dry_run'):
3055 if not opts.get('dry_run'):
3055 needdata = ('revert', 'add', 'undelete')
3056 needdata = ('revert', 'add', 'undelete')
3056 oplist = [actions[name][0] for name in needdata]
3057 oplist = [actions[name][0] for name in needdata]
3057 prefetch = scmutil.prefetchfiles
3058 prefetch = scmutil.prefetchfiles
3058 matchfiles = scmutil.matchfiles
3059 matchfiles = scmutil.matchfiles
3059 prefetch(repo, [ctx.rev()],
3060 prefetch(repo, [ctx.rev()],
3060 matchfiles(repo,
3061 matchfiles(repo,
3061 [f for sublist in oplist for f in sublist]))
3062 [f for sublist in oplist for f in sublist]))
3062 _performrevert(repo, parents, ctx, names, uipathfn, actions,
3063 _performrevert(repo, parents, ctx, names, uipathfn, actions,
3063 interactive, tobackup)
3064 interactive, tobackup)
3064
3065
3065 if targetsubs:
3066 if targetsubs:
3066 # Revert the subrepos on the revert list
3067 # Revert the subrepos on the revert list
3067 for sub in targetsubs:
3068 for sub in targetsubs:
3068 try:
3069 try:
3069 wctx.sub(sub).revert(ctx.substate[sub], *pats,
3070 wctx.sub(sub).revert(ctx.substate[sub], *pats,
3070 **pycompat.strkwargs(opts))
3071 **pycompat.strkwargs(opts))
3071 except KeyError:
3072 except KeyError:
3072 raise error.Abort("subrepository '%s' does not exist in %s!"
3073 raise error.Abort("subrepository '%s' does not exist in %s!"
3073 % (sub, short(ctx.node())))
3074 % (sub, short(ctx.node())))
3074
3075
3075 def _performrevert(repo, parents, ctx, names, uipathfn, actions,
3076 def _performrevert(repo, parents, ctx, names, uipathfn, actions,
3076 interactive=False, tobackup=None):
3077 interactive=False, tobackup=None):
3077 """function that actually perform all the actions computed for revert
3078 """function that actually perform all the actions computed for revert
3078
3079
3079 This is an independent function to let extension to plug in and react to
3080 This is an independent function to let extension to plug in and react to
3080 the imminent revert.
3081 the imminent revert.
3081
3082
3082 Make sure you have the working directory locked when calling this function.
3083 Make sure you have the working directory locked when calling this function.
3083 """
3084 """
3084 parent, p2 = parents
3085 parent, p2 = parents
3085 node = ctx.node()
3086 node = ctx.node()
3086 excluded_files = []
3087 excluded_files = []
3087
3088
3088 def checkout(f):
3089 def checkout(f):
3089 fc = ctx[f]
3090 fc = ctx[f]
3090 repo.wwrite(f, fc.data(), fc.flags())
3091 repo.wwrite(f, fc.data(), fc.flags())
3091
3092
3092 def doremove(f):
3093 def doremove(f):
3093 try:
3094 try:
3094 rmdir = repo.ui.configbool('experimental', 'removeemptydirs')
3095 rmdir = repo.ui.configbool('experimental', 'removeemptydirs')
3095 repo.wvfs.unlinkpath(f, rmdir=rmdir)
3096 repo.wvfs.unlinkpath(f, rmdir=rmdir)
3096 except OSError:
3097 except OSError:
3097 pass
3098 pass
3098 repo.dirstate.remove(f)
3099 repo.dirstate.remove(f)
3099
3100
3100 def prntstatusmsg(action, f):
3101 def prntstatusmsg(action, f):
3101 exact = names[f]
3102 exact = names[f]
3102 if repo.ui.verbose or not exact:
3103 if repo.ui.verbose or not exact:
3103 repo.ui.status(actions[action][1] % uipathfn(f))
3104 repo.ui.status(actions[action][1] % uipathfn(f))
3104
3105
3105 audit_path = pathutil.pathauditor(repo.root, cached=True)
3106 audit_path = pathutil.pathauditor(repo.root, cached=True)
3106 for f in actions['forget'][0]:
3107 for f in actions['forget'][0]:
3107 if interactive:
3108 if interactive:
3108 choice = repo.ui.promptchoice(
3109 choice = repo.ui.promptchoice(
3109 _("forget added file %s (Yn)?$$ &Yes $$ &No") % uipathfn(f))
3110 _("forget added file %s (Yn)?$$ &Yes $$ &No") % uipathfn(f))
3110 if choice == 0:
3111 if choice == 0:
3111 prntstatusmsg('forget', f)
3112 prntstatusmsg('forget', f)
3112 repo.dirstate.drop(f)
3113 repo.dirstate.drop(f)
3113 else:
3114 else:
3114 excluded_files.append(f)
3115 excluded_files.append(f)
3115 else:
3116 else:
3116 prntstatusmsg('forget', f)
3117 prntstatusmsg('forget', f)
3117 repo.dirstate.drop(f)
3118 repo.dirstate.drop(f)
3118 for f in actions['remove'][0]:
3119 for f in actions['remove'][0]:
3119 audit_path(f)
3120 audit_path(f)
3120 if interactive:
3121 if interactive:
3121 choice = repo.ui.promptchoice(
3122 choice = repo.ui.promptchoice(
3122 _("remove added file %s (Yn)?$$ &Yes $$ &No") % uipathfn(f))
3123 _("remove added file %s (Yn)?$$ &Yes $$ &No") % uipathfn(f))
3123 if choice == 0:
3124 if choice == 0:
3124 prntstatusmsg('remove', f)
3125 prntstatusmsg('remove', f)
3125 doremove(f)
3126 doremove(f)
3126 else:
3127 else:
3127 excluded_files.append(f)
3128 excluded_files.append(f)
3128 else:
3129 else:
3129 prntstatusmsg('remove', f)
3130 prntstatusmsg('remove', f)
3130 doremove(f)
3131 doremove(f)
3131 for f in actions['drop'][0]:
3132 for f in actions['drop'][0]:
3132 audit_path(f)
3133 audit_path(f)
3133 prntstatusmsg('drop', f)
3134 prntstatusmsg('drop', f)
3134 repo.dirstate.remove(f)
3135 repo.dirstate.remove(f)
3135
3136
3136 normal = None
3137 normal = None
3137 if node == parent:
3138 if node == parent:
3138 # We're reverting to our parent. If possible, we'd like status
3139 # We're reverting to our parent. If possible, we'd like status
3139 # to report the file as clean. We have to use normallookup for
3140 # to report the file as clean. We have to use normallookup for
3140 # merges to avoid losing information about merged/dirty files.
3141 # merges to avoid losing information about merged/dirty files.
3141 if p2 != nullid:
3142 if p2 != nullid:
3142 normal = repo.dirstate.normallookup
3143 normal = repo.dirstate.normallookup
3143 else:
3144 else:
3144 normal = repo.dirstate.normal
3145 normal = repo.dirstate.normal
3145
3146
3146 newlyaddedandmodifiedfiles = set()
3147 newlyaddedandmodifiedfiles = set()
3147 if interactive:
3148 if interactive:
3148 # Prompt the user for changes to revert
3149 # Prompt the user for changes to revert
3149 torevert = [f for f in actions['revert'][0] if f not in excluded_files]
3150 torevert = [f for f in actions['revert'][0] if f not in excluded_files]
3150 m = scmutil.matchfiles(repo, torevert)
3151 m = scmutil.matchfiles(repo, torevert)
3151 diffopts = patch.difffeatureopts(repo.ui, whitespace=True,
3152 diffopts = patch.difffeatureopts(repo.ui, whitespace=True,
3152 section='commands',
3153 section='commands',
3153 configprefix='revert.interactive.')
3154 configprefix='revert.interactive.')
3154 diffopts.nodates = True
3155 diffopts.nodates = True
3155 diffopts.git = True
3156 diffopts.git = True
3156 operation = 'discard'
3157 operation = 'discard'
3157 reversehunks = True
3158 reversehunks = True
3158 if node != parent:
3159 if node != parent:
3159 operation = 'apply'
3160 operation = 'apply'
3160 reversehunks = False
3161 reversehunks = False
3161 if reversehunks:
3162 if reversehunks:
3162 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
3163 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
3163 else:
3164 else:
3164 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
3165 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
3165 originalchunks = patch.parsepatch(diff)
3166 originalchunks = patch.parsepatch(diff)
3166
3167
3167 try:
3168 try:
3168
3169
3169 chunks, opts = recordfilter(repo.ui, originalchunks,
3170 chunks, opts = recordfilter(repo.ui, originalchunks,
3170 operation=operation)
3171 operation=operation)
3171 if reversehunks:
3172 if reversehunks:
3172 chunks = patch.reversehunks(chunks)
3173 chunks = patch.reversehunks(chunks)
3173
3174
3174 except error.PatchError as err:
3175 except error.PatchError as err:
3175 raise error.Abort(_('error parsing patch: %s') % err)
3176 raise error.Abort(_('error parsing patch: %s') % err)
3176
3177
3177 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
3178 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
3178 if tobackup is None:
3179 if tobackup is None:
3179 tobackup = set()
3180 tobackup = set()
3180 # Apply changes
3181 # Apply changes
3181 fp = stringio()
3182 fp = stringio()
3182 # chunks are serialized per file, but files aren't sorted
3183 # chunks are serialized per file, but files aren't sorted
3183 for f in sorted(set(c.header.filename() for c in chunks if ishunk(c))):
3184 for f in sorted(set(c.header.filename() for c in chunks if ishunk(c))):
3184 prntstatusmsg('revert', f)
3185 prntstatusmsg('revert', f)
3185 for c in chunks:
3186 for c in chunks:
3186 if ishunk(c):
3187 if ishunk(c):
3187 abs = c.header.filename()
3188 abs = c.header.filename()
3188 # Create a backup file only if this hunk should be backed up
3189 # Create a backup file only if this hunk should be backed up
3189 if c.header.filename() in tobackup:
3190 if c.header.filename() in tobackup:
3190 target = repo.wjoin(abs)
3191 target = repo.wjoin(abs)
3191 bakname = scmutil.backuppath(repo.ui, repo, abs)
3192 bakname = scmutil.backuppath(repo.ui, repo, abs)
3192 util.copyfile(target, bakname)
3193 util.copyfile(target, bakname)
3193 tobackup.remove(abs)
3194 tobackup.remove(abs)
3194 c.write(fp)
3195 c.write(fp)
3195 dopatch = fp.tell()
3196 dopatch = fp.tell()
3196 fp.seek(0)
3197 fp.seek(0)
3197 if dopatch:
3198 if dopatch:
3198 try:
3199 try:
3199 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3200 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3200 except error.PatchError as err:
3201 except error.PatchError as err:
3201 raise error.Abort(pycompat.bytestr(err))
3202 raise error.Abort(pycompat.bytestr(err))
3202 del fp
3203 del fp
3203 else:
3204 else:
3204 for f in actions['revert'][0]:
3205 for f in actions['revert'][0]:
3205 prntstatusmsg('revert', f)
3206 prntstatusmsg('revert', f)
3206 checkout(f)
3207 checkout(f)
3207 if normal:
3208 if normal:
3208 normal(f)
3209 normal(f)
3209
3210
3210 for f in actions['add'][0]:
3211 for f in actions['add'][0]:
3211 # Don't checkout modified files, they are already created by the diff
3212 # Don't checkout modified files, they are already created by the diff
3212 if f not in newlyaddedandmodifiedfiles:
3213 if f not in newlyaddedandmodifiedfiles:
3213 prntstatusmsg('add', f)
3214 prntstatusmsg('add', f)
3214 checkout(f)
3215 checkout(f)
3215 repo.dirstate.add(f)
3216 repo.dirstate.add(f)
3216
3217
3217 normal = repo.dirstate.normallookup
3218 normal = repo.dirstate.normallookup
3218 if node == parent and p2 == nullid:
3219 if node == parent and p2 == nullid:
3219 normal = repo.dirstate.normal
3220 normal = repo.dirstate.normal
3220 for f in actions['undelete'][0]:
3221 for f in actions['undelete'][0]:
3221 if interactive:
3222 if interactive:
3222 choice = repo.ui.promptchoice(
3223 choice = repo.ui.promptchoice(
3223 _("add back removed file %s (Yn)?$$ &Yes $$ &No") % f)
3224 _("add back removed file %s (Yn)?$$ &Yes $$ &No") % f)
3224 if choice == 0:
3225 if choice == 0:
3225 prntstatusmsg('undelete', f)
3226 prntstatusmsg('undelete', f)
3226 checkout(f)
3227 checkout(f)
3227 normal(f)
3228 normal(f)
3228 else:
3229 else:
3229 excluded_files.append(f)
3230 excluded_files.append(f)
3230 else:
3231 else:
3231 prntstatusmsg('undelete', f)
3232 prntstatusmsg('undelete', f)
3232 checkout(f)
3233 checkout(f)
3233 normal(f)
3234 normal(f)
3234
3235
3235 copied = copies.pathcopies(repo[parent], ctx)
3236 copied = copies.pathcopies(repo[parent], ctx)
3236
3237
3237 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
3238 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
3238 if f in copied:
3239 if f in copied:
3239 repo.dirstate.copy(copied[f], f)
3240 repo.dirstate.copy(copied[f], f)
3240
3241
3241 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3242 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3242 # commands.outgoing. "missing" is "missing" of the result of
3243 # commands.outgoing. "missing" is "missing" of the result of
3243 # "findcommonoutgoing()"
3244 # "findcommonoutgoing()"
3244 outgoinghooks = util.hooks()
3245 outgoinghooks = util.hooks()
3245
3246
3246 # a list of (ui, repo) functions called by commands.summary
3247 # a list of (ui, repo) functions called by commands.summary
3247 summaryhooks = util.hooks()
3248 summaryhooks = util.hooks()
3248
3249
3249 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3250 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3250 #
3251 #
3251 # functions should return tuple of booleans below, if 'changes' is None:
3252 # functions should return tuple of booleans below, if 'changes' is None:
3252 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3253 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3253 #
3254 #
3254 # otherwise, 'changes' is a tuple of tuples below:
3255 # otherwise, 'changes' is a tuple of tuples below:
3255 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3256 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3256 # - (desturl, destbranch, destpeer, outgoing)
3257 # - (desturl, destbranch, destpeer, outgoing)
3257 summaryremotehooks = util.hooks()
3258 summaryremotehooks = util.hooks()
3258
3259
3259 # A list of state files kept by multistep operations like graft.
3260 # A list of state files kept by multistep operations like graft.
3260 # Since graft cannot be aborted, it is considered 'clearable' by update.
3261 # Since graft cannot be aborted, it is considered 'clearable' by update.
3261 # note: bisect is intentionally excluded
3262 # note: bisect is intentionally excluded
3262 # (state file, clearable, allowcommit, error, hint)
3263 # (state file, clearable, allowcommit, error, hint)
3263 unfinishedstates = [
3264 unfinishedstates = [
3264 ('graftstate', True, False, _('graft in progress'),
3265 ('graftstate', True, False, _('graft in progress'),
3265 _("use 'hg graft --continue' or 'hg graft --stop' to stop")),
3266 _("use 'hg graft --continue' or 'hg graft --stop' to stop")),
3266 ('updatestate', True, False, _('last update was interrupted'),
3267 ('updatestate', True, False, _('last update was interrupted'),
3267 _("use 'hg update' to get a consistent checkout"))
3268 _("use 'hg update' to get a consistent checkout"))
3268 ]
3269 ]
3269
3270
3270 def checkunfinished(repo, commit=False):
3271 def checkunfinished(repo, commit=False):
3271 '''Look for an unfinished multistep operation, like graft, and abort
3272 '''Look for an unfinished multistep operation, like graft, and abort
3272 if found. It's probably good to check this right before
3273 if found. It's probably good to check this right before
3273 bailifchanged().
3274 bailifchanged().
3274 '''
3275 '''
3275 # Check for non-clearable states first, so things like rebase will take
3276 # Check for non-clearable states first, so things like rebase will take
3276 # precedence over update.
3277 # precedence over update.
3277 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3278 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3278 if clearable or (commit and allowcommit):
3279 if clearable or (commit and allowcommit):
3279 continue
3280 continue
3280 if repo.vfs.exists(f):
3281 if repo.vfs.exists(f):
3281 raise error.Abort(msg, hint=hint)
3282 raise error.Abort(msg, hint=hint)
3282
3283
3283 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3284 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3284 if not clearable or (commit and allowcommit):
3285 if not clearable or (commit and allowcommit):
3285 continue
3286 continue
3286 if repo.vfs.exists(f):
3287 if repo.vfs.exists(f):
3287 raise error.Abort(msg, hint=hint)
3288 raise error.Abort(msg, hint=hint)
3288
3289
3289 def clearunfinished(repo):
3290 def clearunfinished(repo):
3290 '''Check for unfinished operations (as above), and clear the ones
3291 '''Check for unfinished operations (as above), and clear the ones
3291 that are clearable.
3292 that are clearable.
3292 '''
3293 '''
3293 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3294 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3294 if not clearable and repo.vfs.exists(f):
3295 if not clearable and repo.vfs.exists(f):
3295 raise error.Abort(msg, hint=hint)
3296 raise error.Abort(msg, hint=hint)
3296 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3297 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3297 if clearable and repo.vfs.exists(f):
3298 if clearable and repo.vfs.exists(f):
3298 util.unlink(repo.vfs.join(f))
3299 util.unlink(repo.vfs.join(f))
3299
3300
3300 afterresolvedstates = [
3301 afterresolvedstates = [
3301 ('graftstate',
3302 ('graftstate',
3302 _('hg graft --continue')),
3303 _('hg graft --continue')),
3303 ]
3304 ]
3304
3305
3305 def howtocontinue(repo):
3306 def howtocontinue(repo):
3306 '''Check for an unfinished operation and return the command to finish
3307 '''Check for an unfinished operation and return the command to finish
3307 it.
3308 it.
3308
3309
3309 afterresolvedstates tuples define a .hg/{file} and the corresponding
3310 afterresolvedstates tuples define a .hg/{file} and the corresponding
3310 command needed to finish it.
3311 command needed to finish it.
3311
3312
3312 Returns a (msg, warning) tuple. 'msg' is a string and 'warning' is
3313 Returns a (msg, warning) tuple. 'msg' is a string and 'warning' is
3313 a boolean.
3314 a boolean.
3314 '''
3315 '''
3315 contmsg = _("continue: %s")
3316 contmsg = _("continue: %s")
3316 for f, msg in afterresolvedstates:
3317 for f, msg in afterresolvedstates:
3317 if repo.vfs.exists(f):
3318 if repo.vfs.exists(f):
3318 return contmsg % msg, True
3319 return contmsg % msg, True
3319 if repo[None].dirty(missing=True, merge=False, branch=False):
3320 if repo[None].dirty(missing=True, merge=False, branch=False):
3320 return contmsg % _("hg commit"), False
3321 return contmsg % _("hg commit"), False
3321 return None, None
3322 return None, None
3322
3323
3323 def checkafterresolved(repo):
3324 def checkafterresolved(repo):
3324 '''Inform the user about the next action after completing hg resolve
3325 '''Inform the user about the next action after completing hg resolve
3325
3326
3326 If there's a matching afterresolvedstates, howtocontinue will yield
3327 If there's a matching afterresolvedstates, howtocontinue will yield
3327 repo.ui.warn as the reporter.
3328 repo.ui.warn as the reporter.
3328
3329
3329 Otherwise, it will yield repo.ui.note.
3330 Otherwise, it will yield repo.ui.note.
3330 '''
3331 '''
3331 msg, warning = howtocontinue(repo)
3332 msg, warning = howtocontinue(repo)
3332 if msg is not None:
3333 if msg is not None:
3333 if warning:
3334 if warning:
3334 repo.ui.warn("%s\n" % msg)
3335 repo.ui.warn("%s\n" % msg)
3335 else:
3336 else:
3336 repo.ui.note("%s\n" % msg)
3337 repo.ui.note("%s\n" % msg)
3337
3338
3338 def wrongtooltocontinue(repo, task):
3339 def wrongtooltocontinue(repo, task):
3339 '''Raise an abort suggesting how to properly continue if there is an
3340 '''Raise an abort suggesting how to properly continue if there is an
3340 active task.
3341 active task.
3341
3342
3342 Uses howtocontinue() to find the active task.
3343 Uses howtocontinue() to find the active task.
3343
3344
3344 If there's no task (repo.ui.note for 'hg commit'), it does not offer
3345 If there's no task (repo.ui.note for 'hg commit'), it does not offer
3345 a hint.
3346 a hint.
3346 '''
3347 '''
3347 after = howtocontinue(repo)
3348 after = howtocontinue(repo)
3348 hint = None
3349 hint = None
3349 if after[1]:
3350 if after[1]:
3350 hint = after[0]
3351 hint = after[0]
3351 raise error.Abort(_('no %s in progress') % task, hint=hint)
3352 raise error.Abort(_('no %s in progress') % task, hint=hint)
@@ -1,6238 +1,6240
1 # commands.py - command processing for mercurial
1 # commands.py - command processing for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import difflib
10 import difflib
11 import errno
11 import errno
12 import os
12 import os
13 import re
13 import re
14 import sys
14 import sys
15
15
16 from .i18n import _
16 from .i18n import _
17 from .node import (
17 from .node import (
18 hex,
18 hex,
19 nullid,
19 nullid,
20 nullrev,
20 nullrev,
21 short,
21 short,
22 wdirhex,
22 wdirhex,
23 wdirrev,
23 wdirrev,
24 )
24 )
25 from . import (
25 from . import (
26 archival,
26 archival,
27 bookmarks,
27 bookmarks,
28 bundle2,
28 bundle2,
29 changegroup,
29 changegroup,
30 cmdutil,
30 cmdutil,
31 copies,
31 copies,
32 debugcommands as debugcommandsmod,
32 debugcommands as debugcommandsmod,
33 destutil,
33 destutil,
34 dirstateguard,
34 dirstateguard,
35 discovery,
35 discovery,
36 encoding,
36 encoding,
37 error,
37 error,
38 exchange,
38 exchange,
39 extensions,
39 extensions,
40 filemerge,
40 filemerge,
41 formatter,
41 formatter,
42 graphmod,
42 graphmod,
43 hbisect,
43 hbisect,
44 help,
44 help,
45 hg,
45 hg,
46 logcmdutil,
46 logcmdutil,
47 merge as mergemod,
47 merge as mergemod,
48 narrowspec,
48 narrowspec,
49 obsolete,
49 obsolete,
50 obsutil,
50 obsutil,
51 patch,
51 patch,
52 phases,
52 phases,
53 pycompat,
53 pycompat,
54 rcutil,
54 rcutil,
55 registrar,
55 registrar,
56 repair,
56 repair,
57 revsetlang,
57 revsetlang,
58 rewriteutil,
58 rewriteutil,
59 scmutil,
59 scmutil,
60 server,
60 server,
61 state as statemod,
61 state as statemod,
62 streamclone,
62 streamclone,
63 tags as tagsmod,
63 tags as tagsmod,
64 templatekw,
64 templatekw,
65 ui as uimod,
65 ui as uimod,
66 util,
66 util,
67 wireprotoserver,
67 wireprotoserver,
68 )
68 )
69 from .utils import (
69 from .utils import (
70 dateutil,
70 dateutil,
71 stringutil,
71 stringutil,
72 )
72 )
73
73
74 table = {}
74 table = {}
75 table.update(debugcommandsmod.command._table)
75 table.update(debugcommandsmod.command._table)
76
76
77 command = registrar.command(table)
77 command = registrar.command(table)
78 INTENT_READONLY = registrar.INTENT_READONLY
78 INTENT_READONLY = registrar.INTENT_READONLY
79
79
80 # common command options
80 # common command options
81
81
82 globalopts = [
82 globalopts = [
83 ('R', 'repository', '',
83 ('R', 'repository', '',
84 _('repository root directory or name of overlay bundle file'),
84 _('repository root directory or name of overlay bundle file'),
85 _('REPO')),
85 _('REPO')),
86 ('', 'cwd', '',
86 ('', 'cwd', '',
87 _('change working directory'), _('DIR')),
87 _('change working directory'), _('DIR')),
88 ('y', 'noninteractive', None,
88 ('y', 'noninteractive', None,
89 _('do not prompt, automatically pick the first choice for all prompts')),
89 _('do not prompt, automatically pick the first choice for all prompts')),
90 ('q', 'quiet', None, _('suppress output')),
90 ('q', 'quiet', None, _('suppress output')),
91 ('v', 'verbose', None, _('enable additional output')),
91 ('v', 'verbose', None, _('enable additional output')),
92 ('', 'color', '',
92 ('', 'color', '',
93 # i18n: 'always', 'auto', 'never', and 'debug' are keywords
93 # i18n: 'always', 'auto', 'never', and 'debug' are keywords
94 # and should not be translated
94 # and should not be translated
95 _("when to colorize (boolean, always, auto, never, or debug)"),
95 _("when to colorize (boolean, always, auto, never, or debug)"),
96 _('TYPE')),
96 _('TYPE')),
97 ('', 'config', [],
97 ('', 'config', [],
98 _('set/override config option (use \'section.name=value\')'),
98 _('set/override config option (use \'section.name=value\')'),
99 _('CONFIG')),
99 _('CONFIG')),
100 ('', 'debug', None, _('enable debugging output')),
100 ('', 'debug', None, _('enable debugging output')),
101 ('', 'debugger', None, _('start debugger')),
101 ('', 'debugger', None, _('start debugger')),
102 ('', 'encoding', encoding.encoding, _('set the charset encoding'),
102 ('', 'encoding', encoding.encoding, _('set the charset encoding'),
103 _('ENCODE')),
103 _('ENCODE')),
104 ('', 'encodingmode', encoding.encodingmode,
104 ('', 'encodingmode', encoding.encodingmode,
105 _('set the charset encoding mode'), _('MODE')),
105 _('set the charset encoding mode'), _('MODE')),
106 ('', 'traceback', None, _('always print a traceback on exception')),
106 ('', 'traceback', None, _('always print a traceback on exception')),
107 ('', 'time', None, _('time how long the command takes')),
107 ('', 'time', None, _('time how long the command takes')),
108 ('', 'profile', None, _('print command execution profile')),
108 ('', 'profile', None, _('print command execution profile')),
109 ('', 'version', None, _('output version information and exit')),
109 ('', 'version', None, _('output version information and exit')),
110 ('h', 'help', None, _('display help and exit')),
110 ('h', 'help', None, _('display help and exit')),
111 ('', 'hidden', False, _('consider hidden changesets')),
111 ('', 'hidden', False, _('consider hidden changesets')),
112 ('', 'pager', 'auto',
112 ('', 'pager', 'auto',
113 _("when to paginate (boolean, always, auto, or never)"), _('TYPE')),
113 _("when to paginate (boolean, always, auto, or never)"), _('TYPE')),
114 ]
114 ]
115
115
116 dryrunopts = cmdutil.dryrunopts
116 dryrunopts = cmdutil.dryrunopts
117 remoteopts = cmdutil.remoteopts
117 remoteopts = cmdutil.remoteopts
118 walkopts = cmdutil.walkopts
118 walkopts = cmdutil.walkopts
119 commitopts = cmdutil.commitopts
119 commitopts = cmdutil.commitopts
120 commitopts2 = cmdutil.commitopts2
120 commitopts2 = cmdutil.commitopts2
121 formatteropts = cmdutil.formatteropts
121 formatteropts = cmdutil.formatteropts
122 templateopts = cmdutil.templateopts
122 templateopts = cmdutil.templateopts
123 logopts = cmdutil.logopts
123 logopts = cmdutil.logopts
124 diffopts = cmdutil.diffopts
124 diffopts = cmdutil.diffopts
125 diffwsopts = cmdutil.diffwsopts
125 diffwsopts = cmdutil.diffwsopts
126 diffopts2 = cmdutil.diffopts2
126 diffopts2 = cmdutil.diffopts2
127 mergetoolopts = cmdutil.mergetoolopts
127 mergetoolopts = cmdutil.mergetoolopts
128 similarityopts = cmdutil.similarityopts
128 similarityopts = cmdutil.similarityopts
129 subrepoopts = cmdutil.subrepoopts
129 subrepoopts = cmdutil.subrepoopts
130 debugrevlogopts = cmdutil.debugrevlogopts
130 debugrevlogopts = cmdutil.debugrevlogopts
131
131
132 # Commands start here, listed alphabetically
132 # Commands start here, listed alphabetically
133
133
134 @command('add',
134 @command('add',
135 walkopts + subrepoopts + dryrunopts,
135 walkopts + subrepoopts + dryrunopts,
136 _('[OPTION]... [FILE]...'),
136 _('[OPTION]... [FILE]...'),
137 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
137 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
138 helpbasic=True, inferrepo=True)
138 helpbasic=True, inferrepo=True)
139 def add(ui, repo, *pats, **opts):
139 def add(ui, repo, *pats, **opts):
140 """add the specified files on the next commit
140 """add the specified files on the next commit
141
141
142 Schedule files to be version controlled and added to the
142 Schedule files to be version controlled and added to the
143 repository.
143 repository.
144
144
145 The files will be added to the repository at the next commit. To
145 The files will be added to the repository at the next commit. To
146 undo an add before that, see :hg:`forget`.
146 undo an add before that, see :hg:`forget`.
147
147
148 If no names are given, add all files to the repository (except
148 If no names are given, add all files to the repository (except
149 files matching ``.hgignore``).
149 files matching ``.hgignore``).
150
150
151 .. container:: verbose
151 .. container:: verbose
152
152
153 Examples:
153 Examples:
154
154
155 - New (unknown) files are added
155 - New (unknown) files are added
156 automatically by :hg:`add`::
156 automatically by :hg:`add`::
157
157
158 $ ls
158 $ ls
159 foo.c
159 foo.c
160 $ hg status
160 $ hg status
161 ? foo.c
161 ? foo.c
162 $ hg add
162 $ hg add
163 adding foo.c
163 adding foo.c
164 $ hg status
164 $ hg status
165 A foo.c
165 A foo.c
166
166
167 - Specific files to be added can be specified::
167 - Specific files to be added can be specified::
168
168
169 $ ls
169 $ ls
170 bar.c foo.c
170 bar.c foo.c
171 $ hg status
171 $ hg status
172 ? bar.c
172 ? bar.c
173 ? foo.c
173 ? foo.c
174 $ hg add bar.c
174 $ hg add bar.c
175 $ hg status
175 $ hg status
176 A bar.c
176 A bar.c
177 ? foo.c
177 ? foo.c
178
178
179 Returns 0 if all files are successfully added.
179 Returns 0 if all files are successfully added.
180 """
180 """
181
181
182 m = scmutil.match(repo[None], pats, pycompat.byteskwargs(opts))
182 m = scmutil.match(repo[None], pats, pycompat.byteskwargs(opts))
183 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
183 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
184 rejected = cmdutil.add(ui, repo, m, "", uipathfn, False, **opts)
184 rejected = cmdutil.add(ui, repo, m, "", uipathfn, False, **opts)
185 return rejected and 1 or 0
185 return rejected and 1 or 0
186
186
187 @command('addremove',
187 @command('addremove',
188 similarityopts + subrepoopts + walkopts + dryrunopts,
188 similarityopts + subrepoopts + walkopts + dryrunopts,
189 _('[OPTION]... [FILE]...'),
189 _('[OPTION]... [FILE]...'),
190 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
190 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
191 inferrepo=True)
191 inferrepo=True)
192 def addremove(ui, repo, *pats, **opts):
192 def addremove(ui, repo, *pats, **opts):
193 """add all new files, delete all missing files
193 """add all new files, delete all missing files
194
194
195 Add all new files and remove all missing files from the
195 Add all new files and remove all missing files from the
196 repository.
196 repository.
197
197
198 Unless names are given, new files are ignored if they match any of
198 Unless names are given, new files are ignored if they match any of
199 the patterns in ``.hgignore``. As with add, these changes take
199 the patterns in ``.hgignore``. As with add, these changes take
200 effect at the next commit.
200 effect at the next commit.
201
201
202 Use the -s/--similarity option to detect renamed files. This
202 Use the -s/--similarity option to detect renamed files. This
203 option takes a percentage between 0 (disabled) and 100 (files must
203 option takes a percentage between 0 (disabled) and 100 (files must
204 be identical) as its parameter. With a parameter greater than 0,
204 be identical) as its parameter. With a parameter greater than 0,
205 this compares every removed file with every added file and records
205 this compares every removed file with every added file and records
206 those similar enough as renames. Detecting renamed files this way
206 those similar enough as renames. Detecting renamed files this way
207 can be expensive. After using this option, :hg:`status -C` can be
207 can be expensive. After using this option, :hg:`status -C` can be
208 used to check which files were identified as moved or renamed. If
208 used to check which files were identified as moved or renamed. If
209 not specified, -s/--similarity defaults to 100 and only renames of
209 not specified, -s/--similarity defaults to 100 and only renames of
210 identical files are detected.
210 identical files are detected.
211
211
212 .. container:: verbose
212 .. container:: verbose
213
213
214 Examples:
214 Examples:
215
215
216 - A number of files (bar.c and foo.c) are new,
216 - A number of files (bar.c and foo.c) are new,
217 while foobar.c has been removed (without using :hg:`remove`)
217 while foobar.c has been removed (without using :hg:`remove`)
218 from the repository::
218 from the repository::
219
219
220 $ ls
220 $ ls
221 bar.c foo.c
221 bar.c foo.c
222 $ hg status
222 $ hg status
223 ! foobar.c
223 ! foobar.c
224 ? bar.c
224 ? bar.c
225 ? foo.c
225 ? foo.c
226 $ hg addremove
226 $ hg addremove
227 adding bar.c
227 adding bar.c
228 adding foo.c
228 adding foo.c
229 removing foobar.c
229 removing foobar.c
230 $ hg status
230 $ hg status
231 A bar.c
231 A bar.c
232 A foo.c
232 A foo.c
233 R foobar.c
233 R foobar.c
234
234
235 - A file foobar.c was moved to foo.c without using :hg:`rename`.
235 - A file foobar.c was moved to foo.c without using :hg:`rename`.
236 Afterwards, it was edited slightly::
236 Afterwards, it was edited slightly::
237
237
238 $ ls
238 $ ls
239 foo.c
239 foo.c
240 $ hg status
240 $ hg status
241 ! foobar.c
241 ! foobar.c
242 ? foo.c
242 ? foo.c
243 $ hg addremove --similarity 90
243 $ hg addremove --similarity 90
244 removing foobar.c
244 removing foobar.c
245 adding foo.c
245 adding foo.c
246 recording removal of foobar.c as rename to foo.c (94% similar)
246 recording removal of foobar.c as rename to foo.c (94% similar)
247 $ hg status -C
247 $ hg status -C
248 A foo.c
248 A foo.c
249 foobar.c
249 foobar.c
250 R foobar.c
250 R foobar.c
251
251
252 Returns 0 if all files are successfully added.
252 Returns 0 if all files are successfully added.
253 """
253 """
254 opts = pycompat.byteskwargs(opts)
254 opts = pycompat.byteskwargs(opts)
255 if not opts.get('similarity'):
255 if not opts.get('similarity'):
256 opts['similarity'] = '100'
256 opts['similarity'] = '100'
257 matcher = scmutil.match(repo[None], pats, opts)
257 matcher = scmutil.match(repo[None], pats, opts)
258 relative = scmutil.anypats(pats, opts)
258 relative = scmutil.anypats(pats, opts)
259 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
259 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
260 return scmutil.addremove(repo, matcher, "", uipathfn, opts)
260 return scmutil.addremove(repo, matcher, "", uipathfn, opts)
261
261
262 @command('annotate|blame',
262 @command('annotate|blame',
263 [('r', 'rev', '', _('annotate the specified revision'), _('REV')),
263 [('r', 'rev', '', _('annotate the specified revision'), _('REV')),
264 ('', 'follow', None,
264 ('', 'follow', None,
265 _('follow copies/renames and list the filename (DEPRECATED)')),
265 _('follow copies/renames and list the filename (DEPRECATED)')),
266 ('', 'no-follow', None, _("don't follow copies and renames")),
266 ('', 'no-follow', None, _("don't follow copies and renames")),
267 ('a', 'text', None, _('treat all files as text')),
267 ('a', 'text', None, _('treat all files as text')),
268 ('u', 'user', None, _('list the author (long with -v)')),
268 ('u', 'user', None, _('list the author (long with -v)')),
269 ('f', 'file', None, _('list the filename')),
269 ('f', 'file', None, _('list the filename')),
270 ('d', 'date', None, _('list the date (short with -q)')),
270 ('d', 'date', None, _('list the date (short with -q)')),
271 ('n', 'number', None, _('list the revision number (default)')),
271 ('n', 'number', None, _('list the revision number (default)')),
272 ('c', 'changeset', None, _('list the changeset')),
272 ('c', 'changeset', None, _('list the changeset')),
273 ('l', 'line-number', None, _('show line number at the first appearance')),
273 ('l', 'line-number', None, _('show line number at the first appearance')),
274 ('', 'skip', [], _('revision to not display (EXPERIMENTAL)'), _('REV')),
274 ('', 'skip', [], _('revision to not display (EXPERIMENTAL)'), _('REV')),
275 ] + diffwsopts + walkopts + formatteropts,
275 ] + diffwsopts + walkopts + formatteropts,
276 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'),
276 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'),
277 helpcategory=command.CATEGORY_FILE_CONTENTS,
277 helpcategory=command.CATEGORY_FILE_CONTENTS,
278 helpbasic=True, inferrepo=True)
278 helpbasic=True, inferrepo=True)
279 def annotate(ui, repo, *pats, **opts):
279 def annotate(ui, repo, *pats, **opts):
280 """show changeset information by line for each file
280 """show changeset information by line for each file
281
281
282 List changes in files, showing the revision id responsible for
282 List changes in files, showing the revision id responsible for
283 each line.
283 each line.
284
284
285 This command is useful for discovering when a change was made and
285 This command is useful for discovering when a change was made and
286 by whom.
286 by whom.
287
287
288 If you include --file, --user, or --date, the revision number is
288 If you include --file, --user, or --date, the revision number is
289 suppressed unless you also include --number.
289 suppressed unless you also include --number.
290
290
291 Without the -a/--text option, annotate will avoid processing files
291 Without the -a/--text option, annotate will avoid processing files
292 it detects as binary. With -a, annotate will annotate the file
292 it detects as binary. With -a, annotate will annotate the file
293 anyway, although the results will probably be neither useful
293 anyway, although the results will probably be neither useful
294 nor desirable.
294 nor desirable.
295
295
296 .. container:: verbose
296 .. container:: verbose
297
297
298 Template:
298 Template:
299
299
300 The following keywords are supported in addition to the common template
300 The following keywords are supported in addition to the common template
301 keywords and functions. See also :hg:`help templates`.
301 keywords and functions. See also :hg:`help templates`.
302
302
303 :lines: List of lines with annotation data.
303 :lines: List of lines with annotation data.
304 :path: String. Repository-absolute path of the specified file.
304 :path: String. Repository-absolute path of the specified file.
305
305
306 And each entry of ``{lines}`` provides the following sub-keywords in
306 And each entry of ``{lines}`` provides the following sub-keywords in
307 addition to ``{date}``, ``{node}``, ``{rev}``, ``{user}``, etc.
307 addition to ``{date}``, ``{node}``, ``{rev}``, ``{user}``, etc.
308
308
309 :line: String. Line content.
309 :line: String. Line content.
310 :lineno: Integer. Line number at that revision.
310 :lineno: Integer. Line number at that revision.
311 :path: String. Repository-absolute path of the file at that revision.
311 :path: String. Repository-absolute path of the file at that revision.
312
312
313 See :hg:`help templates.operators` for the list expansion syntax.
313 See :hg:`help templates.operators` for the list expansion syntax.
314
314
315 Returns 0 on success.
315 Returns 0 on success.
316 """
316 """
317 opts = pycompat.byteskwargs(opts)
317 opts = pycompat.byteskwargs(opts)
318 if not pats:
318 if not pats:
319 raise error.Abort(_('at least one filename or pattern is required'))
319 raise error.Abort(_('at least one filename or pattern is required'))
320
320
321 if opts.get('follow'):
321 if opts.get('follow'):
322 # --follow is deprecated and now just an alias for -f/--file
322 # --follow is deprecated and now just an alias for -f/--file
323 # to mimic the behavior of Mercurial before version 1.5
323 # to mimic the behavior of Mercurial before version 1.5
324 opts['file'] = True
324 opts['file'] = True
325
325
326 if (not opts.get('user') and not opts.get('changeset')
326 if (not opts.get('user') and not opts.get('changeset')
327 and not opts.get('date') and not opts.get('file')):
327 and not opts.get('date') and not opts.get('file')):
328 opts['number'] = True
328 opts['number'] = True
329
329
330 linenumber = opts.get('line_number') is not None
330 linenumber = opts.get('line_number') is not None
331 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
331 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
332 raise error.Abort(_('at least one of -n/-c is required for -l'))
332 raise error.Abort(_('at least one of -n/-c is required for -l'))
333
333
334 rev = opts.get('rev')
334 rev = opts.get('rev')
335 if rev:
335 if rev:
336 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
336 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
337 ctx = scmutil.revsingle(repo, rev)
337 ctx = scmutil.revsingle(repo, rev)
338
338
339 ui.pager('annotate')
339 ui.pager('annotate')
340 rootfm = ui.formatter('annotate', opts)
340 rootfm = ui.formatter('annotate', opts)
341 if ui.debugflag:
341 if ui.debugflag:
342 shorthex = pycompat.identity
342 shorthex = pycompat.identity
343 else:
343 else:
344 def shorthex(h):
344 def shorthex(h):
345 return h[:12]
345 return h[:12]
346 if ui.quiet:
346 if ui.quiet:
347 datefunc = dateutil.shortdate
347 datefunc = dateutil.shortdate
348 else:
348 else:
349 datefunc = dateutil.datestr
349 datefunc = dateutil.datestr
350 if ctx.rev() is None:
350 if ctx.rev() is None:
351 if opts.get('changeset'):
351 if opts.get('changeset'):
352 # omit "+" suffix which is appended to node hex
352 # omit "+" suffix which is appended to node hex
353 def formatrev(rev):
353 def formatrev(rev):
354 if rev == wdirrev:
354 if rev == wdirrev:
355 return '%d' % ctx.p1().rev()
355 return '%d' % ctx.p1().rev()
356 else:
356 else:
357 return '%d' % rev
357 return '%d' % rev
358 else:
358 else:
359 def formatrev(rev):
359 def formatrev(rev):
360 if rev == wdirrev:
360 if rev == wdirrev:
361 return '%d+' % ctx.p1().rev()
361 return '%d+' % ctx.p1().rev()
362 else:
362 else:
363 return '%d ' % rev
363 return '%d ' % rev
364 def formathex(h):
364 def formathex(h):
365 if h == wdirhex:
365 if h == wdirhex:
366 return '%s+' % shorthex(hex(ctx.p1().node()))
366 return '%s+' % shorthex(hex(ctx.p1().node()))
367 else:
367 else:
368 return '%s ' % shorthex(h)
368 return '%s ' % shorthex(h)
369 else:
369 else:
370 formatrev = b'%d'.__mod__
370 formatrev = b'%d'.__mod__
371 formathex = shorthex
371 formathex = shorthex
372
372
373 opmap = [
373 opmap = [
374 ('user', ' ', lambda x: x.fctx.user(), ui.shortuser),
374 ('user', ' ', lambda x: x.fctx.user(), ui.shortuser),
375 ('rev', ' ', lambda x: scmutil.intrev(x.fctx), formatrev),
375 ('rev', ' ', lambda x: scmutil.intrev(x.fctx), formatrev),
376 ('node', ' ', lambda x: hex(scmutil.binnode(x.fctx)), formathex),
376 ('node', ' ', lambda x: hex(scmutil.binnode(x.fctx)), formathex),
377 ('date', ' ', lambda x: x.fctx.date(), util.cachefunc(datefunc)),
377 ('date', ' ', lambda x: x.fctx.date(), util.cachefunc(datefunc)),
378 ('path', ' ', lambda x: x.fctx.path(), pycompat.bytestr),
378 ('path', ' ', lambda x: x.fctx.path(), pycompat.bytestr),
379 ('lineno', ':', lambda x: x.lineno, pycompat.bytestr),
379 ('lineno', ':', lambda x: x.lineno, pycompat.bytestr),
380 ]
380 ]
381 opnamemap = {
381 opnamemap = {
382 'rev': 'number',
382 'rev': 'number',
383 'node': 'changeset',
383 'node': 'changeset',
384 'path': 'file',
384 'path': 'file',
385 'lineno': 'line_number',
385 'lineno': 'line_number',
386 }
386 }
387
387
388 if rootfm.isplain():
388 if rootfm.isplain():
389 def makefunc(get, fmt):
389 def makefunc(get, fmt):
390 return lambda x: fmt(get(x))
390 return lambda x: fmt(get(x))
391 else:
391 else:
392 def makefunc(get, fmt):
392 def makefunc(get, fmt):
393 return get
393 return get
394 datahint = rootfm.datahint()
394 datahint = rootfm.datahint()
395 funcmap = [(makefunc(get, fmt), sep) for fn, sep, get, fmt in opmap
395 funcmap = [(makefunc(get, fmt), sep) for fn, sep, get, fmt in opmap
396 if opts.get(opnamemap.get(fn, fn)) or fn in datahint]
396 if opts.get(opnamemap.get(fn, fn)) or fn in datahint]
397 funcmap[0] = (funcmap[0][0], '') # no separator in front of first column
397 funcmap[0] = (funcmap[0][0], '') # no separator in front of first column
398 fields = ' '.join(fn for fn, sep, get, fmt in opmap
398 fields = ' '.join(fn for fn, sep, get, fmt in opmap
399 if opts.get(opnamemap.get(fn, fn)) or fn in datahint)
399 if opts.get(opnamemap.get(fn, fn)) or fn in datahint)
400
400
401 def bad(x, y):
401 def bad(x, y):
402 raise error.Abort("%s: %s" % (x, y))
402 raise error.Abort("%s: %s" % (x, y))
403
403
404 m = scmutil.match(ctx, pats, opts, badfn=bad)
404 m = scmutil.match(ctx, pats, opts, badfn=bad)
405
405
406 follow = not opts.get('no_follow')
406 follow = not opts.get('no_follow')
407 diffopts = patch.difffeatureopts(ui, opts, section='annotate',
407 diffopts = patch.difffeatureopts(ui, opts, section='annotate',
408 whitespace=True)
408 whitespace=True)
409 skiprevs = opts.get('skip')
409 skiprevs = opts.get('skip')
410 if skiprevs:
410 if skiprevs:
411 skiprevs = scmutil.revrange(repo, skiprevs)
411 skiprevs = scmutil.revrange(repo, skiprevs)
412
412
413 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
413 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
414 for abs in ctx.walk(m):
414 for abs in ctx.walk(m):
415 fctx = ctx[abs]
415 fctx = ctx[abs]
416 rootfm.startitem()
416 rootfm.startitem()
417 rootfm.data(path=abs)
417 rootfm.data(path=abs)
418 if not opts.get('text') and fctx.isbinary():
418 if not opts.get('text') and fctx.isbinary():
419 rootfm.plain(_("%s: binary file\n") % uipathfn(abs))
419 rootfm.plain(_("%s: binary file\n") % uipathfn(abs))
420 continue
420 continue
421
421
422 fm = rootfm.nested('lines', tmpl='{rev}: {line}')
422 fm = rootfm.nested('lines', tmpl='{rev}: {line}')
423 lines = fctx.annotate(follow=follow, skiprevs=skiprevs,
423 lines = fctx.annotate(follow=follow, skiprevs=skiprevs,
424 diffopts=diffopts)
424 diffopts=diffopts)
425 if not lines:
425 if not lines:
426 fm.end()
426 fm.end()
427 continue
427 continue
428 formats = []
428 formats = []
429 pieces = []
429 pieces = []
430
430
431 for f, sep in funcmap:
431 for f, sep in funcmap:
432 l = [f(n) for n in lines]
432 l = [f(n) for n in lines]
433 if fm.isplain():
433 if fm.isplain():
434 sizes = [encoding.colwidth(x) for x in l]
434 sizes = [encoding.colwidth(x) for x in l]
435 ml = max(sizes)
435 ml = max(sizes)
436 formats.append([sep + ' ' * (ml - w) + '%s' for w in sizes])
436 formats.append([sep + ' ' * (ml - w) + '%s' for w in sizes])
437 else:
437 else:
438 formats.append(['%s' for x in l])
438 formats.append(['%s' for x in l])
439 pieces.append(l)
439 pieces.append(l)
440
440
441 for f, p, n in zip(zip(*formats), zip(*pieces), lines):
441 for f, p, n in zip(zip(*formats), zip(*pieces), lines):
442 fm.startitem()
442 fm.startitem()
443 fm.context(fctx=n.fctx)
443 fm.context(fctx=n.fctx)
444 fm.write(fields, "".join(f), *p)
444 fm.write(fields, "".join(f), *p)
445 if n.skip:
445 if n.skip:
446 fmt = "* %s"
446 fmt = "* %s"
447 else:
447 else:
448 fmt = ": %s"
448 fmt = ": %s"
449 fm.write('line', fmt, n.text)
449 fm.write('line', fmt, n.text)
450
450
451 if not lines[-1].text.endswith('\n'):
451 if not lines[-1].text.endswith('\n'):
452 fm.plain('\n')
452 fm.plain('\n')
453 fm.end()
453 fm.end()
454
454
455 rootfm.end()
455 rootfm.end()
456
456
457 @command('archive',
457 @command('archive',
458 [('', 'no-decode', None, _('do not pass files through decoders')),
458 [('', 'no-decode', None, _('do not pass files through decoders')),
459 ('p', 'prefix', '', _('directory prefix for files in archive'),
459 ('p', 'prefix', '', _('directory prefix for files in archive'),
460 _('PREFIX')),
460 _('PREFIX')),
461 ('r', 'rev', '', _('revision to distribute'), _('REV')),
461 ('r', 'rev', '', _('revision to distribute'), _('REV')),
462 ('t', 'type', '', _('type of distribution to create'), _('TYPE')),
462 ('t', 'type', '', _('type of distribution to create'), _('TYPE')),
463 ] + subrepoopts + walkopts,
463 ] + subrepoopts + walkopts,
464 _('[OPTION]... DEST'),
464 _('[OPTION]... DEST'),
465 helpcategory=command.CATEGORY_IMPORT_EXPORT)
465 helpcategory=command.CATEGORY_IMPORT_EXPORT)
466 def archive(ui, repo, dest, **opts):
466 def archive(ui, repo, dest, **opts):
467 '''create an unversioned archive of a repository revision
467 '''create an unversioned archive of a repository revision
468
468
469 By default, the revision used is the parent of the working
469 By default, the revision used is the parent of the working
470 directory; use -r/--rev to specify a different revision.
470 directory; use -r/--rev to specify a different revision.
471
471
472 The archive type is automatically detected based on file
472 The archive type is automatically detected based on file
473 extension (to override, use -t/--type).
473 extension (to override, use -t/--type).
474
474
475 .. container:: verbose
475 .. container:: verbose
476
476
477 Examples:
477 Examples:
478
478
479 - create a zip file containing the 1.0 release::
479 - create a zip file containing the 1.0 release::
480
480
481 hg archive -r 1.0 project-1.0.zip
481 hg archive -r 1.0 project-1.0.zip
482
482
483 - create a tarball excluding .hg files::
483 - create a tarball excluding .hg files::
484
484
485 hg archive project.tar.gz -X ".hg*"
485 hg archive project.tar.gz -X ".hg*"
486
486
487 Valid types are:
487 Valid types are:
488
488
489 :``files``: a directory full of files (default)
489 :``files``: a directory full of files (default)
490 :``tar``: tar archive, uncompressed
490 :``tar``: tar archive, uncompressed
491 :``tbz2``: tar archive, compressed using bzip2
491 :``tbz2``: tar archive, compressed using bzip2
492 :``tgz``: tar archive, compressed using gzip
492 :``tgz``: tar archive, compressed using gzip
493 :``uzip``: zip archive, uncompressed
493 :``uzip``: zip archive, uncompressed
494 :``zip``: zip archive, compressed using deflate
494 :``zip``: zip archive, compressed using deflate
495
495
496 The exact name of the destination archive or directory is given
496 The exact name of the destination archive or directory is given
497 using a format string; see :hg:`help export` for details.
497 using a format string; see :hg:`help export` for details.
498
498
499 Each member added to an archive file has a directory prefix
499 Each member added to an archive file has a directory prefix
500 prepended. Use -p/--prefix to specify a format string for the
500 prepended. Use -p/--prefix to specify a format string for the
501 prefix. The default is the basename of the archive, with suffixes
501 prefix. The default is the basename of the archive, with suffixes
502 removed.
502 removed.
503
503
504 Returns 0 on success.
504 Returns 0 on success.
505 '''
505 '''
506
506
507 opts = pycompat.byteskwargs(opts)
507 opts = pycompat.byteskwargs(opts)
508 rev = opts.get('rev')
508 rev = opts.get('rev')
509 if rev:
509 if rev:
510 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
510 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
511 ctx = scmutil.revsingle(repo, rev)
511 ctx = scmutil.revsingle(repo, rev)
512 if not ctx:
512 if not ctx:
513 raise error.Abort(_('no working directory: please specify a revision'))
513 raise error.Abort(_('no working directory: please specify a revision'))
514 node = ctx.node()
514 node = ctx.node()
515 dest = cmdutil.makefilename(ctx, dest)
515 dest = cmdutil.makefilename(ctx, dest)
516 if os.path.realpath(dest) == repo.root:
516 if os.path.realpath(dest) == repo.root:
517 raise error.Abort(_('repository root cannot be destination'))
517 raise error.Abort(_('repository root cannot be destination'))
518
518
519 kind = opts.get('type') or archival.guesskind(dest) or 'files'
519 kind = opts.get('type') or archival.guesskind(dest) or 'files'
520 prefix = opts.get('prefix')
520 prefix = opts.get('prefix')
521
521
522 if dest == '-':
522 if dest == '-':
523 if kind == 'files':
523 if kind == 'files':
524 raise error.Abort(_('cannot archive plain files to stdout'))
524 raise error.Abort(_('cannot archive plain files to stdout'))
525 dest = cmdutil.makefileobj(ctx, dest)
525 dest = cmdutil.makefileobj(ctx, dest)
526 if not prefix:
526 if not prefix:
527 prefix = os.path.basename(repo.root) + '-%h'
527 prefix = os.path.basename(repo.root) + '-%h'
528
528
529 prefix = cmdutil.makefilename(ctx, prefix)
529 prefix = cmdutil.makefilename(ctx, prefix)
530 match = scmutil.match(ctx, [], opts)
530 match = scmutil.match(ctx, [], opts)
531 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
531 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
532 match, prefix, subrepos=opts.get('subrepos'))
532 match, prefix, subrepos=opts.get('subrepos'))
533
533
534 @command('backout',
534 @command('backout',
535 [('', 'merge', None, _('merge with old dirstate parent after backout')),
535 [('', 'merge', None, _('merge with old dirstate parent after backout')),
536 ('', 'commit', None,
536 ('', 'commit', None,
537 _('commit if no conflicts were encountered (DEPRECATED)')),
537 _('commit if no conflicts were encountered (DEPRECATED)')),
538 ('', 'no-commit', None, _('do not commit')),
538 ('', 'no-commit', None, _('do not commit')),
539 ('', 'parent', '',
539 ('', 'parent', '',
540 _('parent to choose when backing out merge (DEPRECATED)'), _('REV')),
540 _('parent to choose when backing out merge (DEPRECATED)'), _('REV')),
541 ('r', 'rev', '', _('revision to backout'), _('REV')),
541 ('r', 'rev', '', _('revision to backout'), _('REV')),
542 ('e', 'edit', False, _('invoke editor on commit messages')),
542 ('e', 'edit', False, _('invoke editor on commit messages')),
543 ] + mergetoolopts + walkopts + commitopts + commitopts2,
543 ] + mergetoolopts + walkopts + commitopts + commitopts2,
544 _('[OPTION]... [-r] REV'),
544 _('[OPTION]... [-r] REV'),
545 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT)
545 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT)
546 def backout(ui, repo, node=None, rev=None, **opts):
546 def backout(ui, repo, node=None, rev=None, **opts):
547 '''reverse effect of earlier changeset
547 '''reverse effect of earlier changeset
548
548
549 Prepare a new changeset with the effect of REV undone in the
549 Prepare a new changeset with the effect of REV undone in the
550 current working directory. If no conflicts were encountered,
550 current working directory. If no conflicts were encountered,
551 it will be committed immediately.
551 it will be committed immediately.
552
552
553 If REV is the parent of the working directory, then this new changeset
553 If REV is the parent of the working directory, then this new changeset
554 is committed automatically (unless --no-commit is specified).
554 is committed automatically (unless --no-commit is specified).
555
555
556 .. note::
556 .. note::
557
557
558 :hg:`backout` cannot be used to fix either an unwanted or
558 :hg:`backout` cannot be used to fix either an unwanted or
559 incorrect merge.
559 incorrect merge.
560
560
561 .. container:: verbose
561 .. container:: verbose
562
562
563 Examples:
563 Examples:
564
564
565 - Reverse the effect of the parent of the working directory.
565 - Reverse the effect of the parent of the working directory.
566 This backout will be committed immediately::
566 This backout will be committed immediately::
567
567
568 hg backout -r .
568 hg backout -r .
569
569
570 - Reverse the effect of previous bad revision 23::
570 - Reverse the effect of previous bad revision 23::
571
571
572 hg backout -r 23
572 hg backout -r 23
573
573
574 - Reverse the effect of previous bad revision 23 and
574 - Reverse the effect of previous bad revision 23 and
575 leave changes uncommitted::
575 leave changes uncommitted::
576
576
577 hg backout -r 23 --no-commit
577 hg backout -r 23 --no-commit
578 hg commit -m "Backout revision 23"
578 hg commit -m "Backout revision 23"
579
579
580 By default, the pending changeset will have one parent,
580 By default, the pending changeset will have one parent,
581 maintaining a linear history. With --merge, the pending
581 maintaining a linear history. With --merge, the pending
582 changeset will instead have two parents: the old parent of the
582 changeset will instead have two parents: the old parent of the
583 working directory and a new child of REV that simply undoes REV.
583 working directory and a new child of REV that simply undoes REV.
584
584
585 Before version 1.7, the behavior without --merge was equivalent
585 Before version 1.7, the behavior without --merge was equivalent
586 to specifying --merge followed by :hg:`update --clean .` to
586 to specifying --merge followed by :hg:`update --clean .` to
587 cancel the merge and leave the child of REV as a head to be
587 cancel the merge and leave the child of REV as a head to be
588 merged separately.
588 merged separately.
589
589
590 See :hg:`help dates` for a list of formats valid for -d/--date.
590 See :hg:`help dates` for a list of formats valid for -d/--date.
591
591
592 See :hg:`help revert` for a way to restore files to the state
592 See :hg:`help revert` for a way to restore files to the state
593 of another revision.
593 of another revision.
594
594
595 Returns 0 on success, 1 if nothing to backout or there are unresolved
595 Returns 0 on success, 1 if nothing to backout or there are unresolved
596 files.
596 files.
597 '''
597 '''
598 with repo.wlock(), repo.lock():
598 with repo.wlock(), repo.lock():
599 return _dobackout(ui, repo, node, rev, **opts)
599 return _dobackout(ui, repo, node, rev, **opts)
600
600
601 def _dobackout(ui, repo, node=None, rev=None, **opts):
601 def _dobackout(ui, repo, node=None, rev=None, **opts):
602 opts = pycompat.byteskwargs(opts)
602 opts = pycompat.byteskwargs(opts)
603 if opts.get('commit') and opts.get('no_commit'):
603 if opts.get('commit') and opts.get('no_commit'):
604 raise error.Abort(_("cannot use --commit with --no-commit"))
604 raise error.Abort(_("cannot use --commit with --no-commit"))
605 if opts.get('merge') and opts.get('no_commit'):
605 if opts.get('merge') and opts.get('no_commit'):
606 raise error.Abort(_("cannot use --merge with --no-commit"))
606 raise error.Abort(_("cannot use --merge with --no-commit"))
607
607
608 if rev and node:
608 if rev and node:
609 raise error.Abort(_("please specify just one revision"))
609 raise error.Abort(_("please specify just one revision"))
610
610
611 if not rev:
611 if not rev:
612 rev = node
612 rev = node
613
613
614 if not rev:
614 if not rev:
615 raise error.Abort(_("please specify a revision to backout"))
615 raise error.Abort(_("please specify a revision to backout"))
616
616
617 date = opts.get('date')
617 date = opts.get('date')
618 if date:
618 if date:
619 opts['date'] = dateutil.parsedate(date)
619 opts['date'] = dateutil.parsedate(date)
620
620
621 cmdutil.checkunfinished(repo)
621 cmdutil.checkunfinished(repo)
622 cmdutil.bailifchanged(repo)
622 cmdutil.bailifchanged(repo)
623 node = scmutil.revsingle(repo, rev).node()
623 node = scmutil.revsingle(repo, rev).node()
624
624
625 op1, op2 = repo.dirstate.parents()
625 op1, op2 = repo.dirstate.parents()
626 if not repo.changelog.isancestor(node, op1):
626 if not repo.changelog.isancestor(node, op1):
627 raise error.Abort(_('cannot backout change that is not an ancestor'))
627 raise error.Abort(_('cannot backout change that is not an ancestor'))
628
628
629 p1, p2 = repo.changelog.parents(node)
629 p1, p2 = repo.changelog.parents(node)
630 if p1 == nullid:
630 if p1 == nullid:
631 raise error.Abort(_('cannot backout a change with no parents'))
631 raise error.Abort(_('cannot backout a change with no parents'))
632 if p2 != nullid:
632 if p2 != nullid:
633 if not opts.get('parent'):
633 if not opts.get('parent'):
634 raise error.Abort(_('cannot backout a merge changeset'))
634 raise error.Abort(_('cannot backout a merge changeset'))
635 p = repo.lookup(opts['parent'])
635 p = repo.lookup(opts['parent'])
636 if p not in (p1, p2):
636 if p not in (p1, p2):
637 raise error.Abort(_('%s is not a parent of %s') %
637 raise error.Abort(_('%s is not a parent of %s') %
638 (short(p), short(node)))
638 (short(p), short(node)))
639 parent = p
639 parent = p
640 else:
640 else:
641 if opts.get('parent'):
641 if opts.get('parent'):
642 raise error.Abort(_('cannot use --parent on non-merge changeset'))
642 raise error.Abort(_('cannot use --parent on non-merge changeset'))
643 parent = p1
643 parent = p1
644
644
645 # the backout should appear on the same branch
645 # the backout should appear on the same branch
646 branch = repo.dirstate.branch()
646 branch = repo.dirstate.branch()
647 bheads = repo.branchheads(branch)
647 bheads = repo.branchheads(branch)
648 rctx = scmutil.revsingle(repo, hex(parent))
648 rctx = scmutil.revsingle(repo, hex(parent))
649 if not opts.get('merge') and op1 != node:
649 if not opts.get('merge') and op1 != node:
650 with dirstateguard.dirstateguard(repo, 'backout'):
650 with dirstateguard.dirstateguard(repo, 'backout'):
651 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
651 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
652 with ui.configoverride(overrides, 'backout'):
652 with ui.configoverride(overrides, 'backout'):
653 stats = mergemod.update(repo, parent, branchmerge=True,
653 stats = mergemod.update(repo, parent, branchmerge=True,
654 force=True, ancestor=node,
654 force=True, ancestor=node,
655 mergeancestor=False)
655 mergeancestor=False)
656 repo.setparents(op1, op2)
656 repo.setparents(op1, op2)
657 hg._showstats(repo, stats)
657 hg._showstats(repo, stats)
658 if stats.unresolvedcount:
658 if stats.unresolvedcount:
659 repo.ui.status(_("use 'hg resolve' to retry unresolved "
659 repo.ui.status(_("use 'hg resolve' to retry unresolved "
660 "file merges\n"))
660 "file merges\n"))
661 return 1
661 return 1
662 else:
662 else:
663 hg.clean(repo, node, show_stats=False)
663 hg.clean(repo, node, show_stats=False)
664 repo.dirstate.setbranch(branch)
664 repo.dirstate.setbranch(branch)
665 cmdutil.revert(ui, repo, rctx, repo.dirstate.parents())
665 cmdutil.revert(ui, repo, rctx, repo.dirstate.parents())
666
666
667 if opts.get('no_commit'):
667 if opts.get('no_commit'):
668 msg = _("changeset %s backed out, "
668 msg = _("changeset %s backed out, "
669 "don't forget to commit.\n")
669 "don't forget to commit.\n")
670 ui.status(msg % short(node))
670 ui.status(msg % short(node))
671 return 0
671 return 0
672
672
673 def commitfunc(ui, repo, message, match, opts):
673 def commitfunc(ui, repo, message, match, opts):
674 editform = 'backout'
674 editform = 'backout'
675 e = cmdutil.getcommiteditor(editform=editform,
675 e = cmdutil.getcommiteditor(editform=editform,
676 **pycompat.strkwargs(opts))
676 **pycompat.strkwargs(opts))
677 if not message:
677 if not message:
678 # we don't translate commit messages
678 # we don't translate commit messages
679 message = "Backed out changeset %s" % short(node)
679 message = "Backed out changeset %s" % short(node)
680 e = cmdutil.getcommiteditor(edit=True, editform=editform)
680 e = cmdutil.getcommiteditor(edit=True, editform=editform)
681 return repo.commit(message, opts.get('user'), opts.get('date'),
681 return repo.commit(message, opts.get('user'), opts.get('date'),
682 match, editor=e)
682 match, editor=e)
683 newnode = cmdutil.commit(ui, repo, commitfunc, [], opts)
683 newnode = cmdutil.commit(ui, repo, commitfunc, [], opts)
684 if not newnode:
684 if not newnode:
685 ui.status(_("nothing changed\n"))
685 ui.status(_("nothing changed\n"))
686 return 1
686 return 1
687 cmdutil.commitstatus(repo, newnode, branch, bheads)
687 cmdutil.commitstatus(repo, newnode, branch, bheads)
688
688
689 def nice(node):
689 def nice(node):
690 return '%d:%s' % (repo.changelog.rev(node), short(node))
690 return '%d:%s' % (repo.changelog.rev(node), short(node))
691 ui.status(_('changeset %s backs out changeset %s\n') %
691 ui.status(_('changeset %s backs out changeset %s\n') %
692 (nice(repo.changelog.tip()), nice(node)))
692 (nice(repo.changelog.tip()), nice(node)))
693 if opts.get('merge') and op1 != node:
693 if opts.get('merge') and op1 != node:
694 hg.clean(repo, op1, show_stats=False)
694 hg.clean(repo, op1, show_stats=False)
695 ui.status(_('merging with changeset %s\n')
695 ui.status(_('merging with changeset %s\n')
696 % nice(repo.changelog.tip()))
696 % nice(repo.changelog.tip()))
697 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
697 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
698 with ui.configoverride(overrides, 'backout'):
698 with ui.configoverride(overrides, 'backout'):
699 return hg.merge(repo, hex(repo.changelog.tip()))
699 return hg.merge(repo, hex(repo.changelog.tip()))
700 return 0
700 return 0
701
701
702 @command('bisect',
702 @command('bisect',
703 [('r', 'reset', False, _('reset bisect state')),
703 [('r', 'reset', False, _('reset bisect state')),
704 ('g', 'good', False, _('mark changeset good')),
704 ('g', 'good', False, _('mark changeset good')),
705 ('b', 'bad', False, _('mark changeset bad')),
705 ('b', 'bad', False, _('mark changeset bad')),
706 ('s', 'skip', False, _('skip testing changeset')),
706 ('s', 'skip', False, _('skip testing changeset')),
707 ('e', 'extend', False, _('extend the bisect range')),
707 ('e', 'extend', False, _('extend the bisect range')),
708 ('c', 'command', '', _('use command to check changeset state'), _('CMD')),
708 ('c', 'command', '', _('use command to check changeset state'), _('CMD')),
709 ('U', 'noupdate', False, _('do not update to target'))],
709 ('U', 'noupdate', False, _('do not update to target'))],
710 _("[-gbsr] [-U] [-c CMD] [REV]"),
710 _("[-gbsr] [-U] [-c CMD] [REV]"),
711 helpcategory=command.CATEGORY_CHANGE_NAVIGATION)
711 helpcategory=command.CATEGORY_CHANGE_NAVIGATION)
712 def bisect(ui, repo, rev=None, extra=None, command=None,
712 def bisect(ui, repo, rev=None, extra=None, command=None,
713 reset=None, good=None, bad=None, skip=None, extend=None,
713 reset=None, good=None, bad=None, skip=None, extend=None,
714 noupdate=None):
714 noupdate=None):
715 """subdivision search of changesets
715 """subdivision search of changesets
716
716
717 This command helps to find changesets which introduce problems. To
717 This command helps to find changesets which introduce problems. To
718 use, mark the earliest changeset you know exhibits the problem as
718 use, mark the earliest changeset you know exhibits the problem as
719 bad, then mark the latest changeset which is free from the problem
719 bad, then mark the latest changeset which is free from the problem
720 as good. Bisect will update your working directory to a revision
720 as good. Bisect will update your working directory to a revision
721 for testing (unless the -U/--noupdate option is specified). Once
721 for testing (unless the -U/--noupdate option is specified). Once
722 you have performed tests, mark the working directory as good or
722 you have performed tests, mark the working directory as good or
723 bad, and bisect will either update to another candidate changeset
723 bad, and bisect will either update to another candidate changeset
724 or announce that it has found the bad revision.
724 or announce that it has found the bad revision.
725
725
726 As a shortcut, you can also use the revision argument to mark a
726 As a shortcut, you can also use the revision argument to mark a
727 revision as good or bad without checking it out first.
727 revision as good or bad without checking it out first.
728
728
729 If you supply a command, it will be used for automatic bisection.
729 If you supply a command, it will be used for automatic bisection.
730 The environment variable HG_NODE will contain the ID of the
730 The environment variable HG_NODE will contain the ID of the
731 changeset being tested. The exit status of the command will be
731 changeset being tested. The exit status of the command will be
732 used to mark revisions as good or bad: status 0 means good, 125
732 used to mark revisions as good or bad: status 0 means good, 125
733 means to skip the revision, 127 (command not found) will abort the
733 means to skip the revision, 127 (command not found) will abort the
734 bisection, and any other non-zero exit status means the revision
734 bisection, and any other non-zero exit status means the revision
735 is bad.
735 is bad.
736
736
737 .. container:: verbose
737 .. container:: verbose
738
738
739 Some examples:
739 Some examples:
740
740
741 - start a bisection with known bad revision 34, and good revision 12::
741 - start a bisection with known bad revision 34, and good revision 12::
742
742
743 hg bisect --bad 34
743 hg bisect --bad 34
744 hg bisect --good 12
744 hg bisect --good 12
745
745
746 - advance the current bisection by marking current revision as good or
746 - advance the current bisection by marking current revision as good or
747 bad::
747 bad::
748
748
749 hg bisect --good
749 hg bisect --good
750 hg bisect --bad
750 hg bisect --bad
751
751
752 - mark the current revision, or a known revision, to be skipped (e.g. if
752 - mark the current revision, or a known revision, to be skipped (e.g. if
753 that revision is not usable because of another issue)::
753 that revision is not usable because of another issue)::
754
754
755 hg bisect --skip
755 hg bisect --skip
756 hg bisect --skip 23
756 hg bisect --skip 23
757
757
758 - skip all revisions that do not touch directories ``foo`` or ``bar``::
758 - skip all revisions that do not touch directories ``foo`` or ``bar``::
759
759
760 hg bisect --skip "!( file('path:foo') & file('path:bar') )"
760 hg bisect --skip "!( file('path:foo') & file('path:bar') )"
761
761
762 - forget the current bisection::
762 - forget the current bisection::
763
763
764 hg bisect --reset
764 hg bisect --reset
765
765
766 - use 'make && make tests' to automatically find the first broken
766 - use 'make && make tests' to automatically find the first broken
767 revision::
767 revision::
768
768
769 hg bisect --reset
769 hg bisect --reset
770 hg bisect --bad 34
770 hg bisect --bad 34
771 hg bisect --good 12
771 hg bisect --good 12
772 hg bisect --command "make && make tests"
772 hg bisect --command "make && make tests"
773
773
774 - see all changesets whose states are already known in the current
774 - see all changesets whose states are already known in the current
775 bisection::
775 bisection::
776
776
777 hg log -r "bisect(pruned)"
777 hg log -r "bisect(pruned)"
778
778
779 - see the changeset currently being bisected (especially useful
779 - see the changeset currently being bisected (especially useful
780 if running with -U/--noupdate)::
780 if running with -U/--noupdate)::
781
781
782 hg log -r "bisect(current)"
782 hg log -r "bisect(current)"
783
783
784 - see all changesets that took part in the current bisection::
784 - see all changesets that took part in the current bisection::
785
785
786 hg log -r "bisect(range)"
786 hg log -r "bisect(range)"
787
787
788 - you can even get a nice graph::
788 - you can even get a nice graph::
789
789
790 hg log --graph -r "bisect(range)"
790 hg log --graph -r "bisect(range)"
791
791
792 See :hg:`help revisions.bisect` for more about the `bisect()` predicate.
792 See :hg:`help revisions.bisect` for more about the `bisect()` predicate.
793
793
794 Returns 0 on success.
794 Returns 0 on success.
795 """
795 """
796 # backward compatibility
796 # backward compatibility
797 if rev in "good bad reset init".split():
797 if rev in "good bad reset init".split():
798 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
798 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
799 cmd, rev, extra = rev, extra, None
799 cmd, rev, extra = rev, extra, None
800 if cmd == "good":
800 if cmd == "good":
801 good = True
801 good = True
802 elif cmd == "bad":
802 elif cmd == "bad":
803 bad = True
803 bad = True
804 else:
804 else:
805 reset = True
805 reset = True
806 elif extra:
806 elif extra:
807 raise error.Abort(_('incompatible arguments'))
807 raise error.Abort(_('incompatible arguments'))
808
808
809 incompatibles = {
809 incompatibles = {
810 '--bad': bad,
810 '--bad': bad,
811 '--command': bool(command),
811 '--command': bool(command),
812 '--extend': extend,
812 '--extend': extend,
813 '--good': good,
813 '--good': good,
814 '--reset': reset,
814 '--reset': reset,
815 '--skip': skip,
815 '--skip': skip,
816 }
816 }
817
817
818 enabled = [x for x in incompatibles if incompatibles[x]]
818 enabled = [x for x in incompatibles if incompatibles[x]]
819
819
820 if len(enabled) > 1:
820 if len(enabled) > 1:
821 raise error.Abort(_('%s and %s are incompatible') %
821 raise error.Abort(_('%s and %s are incompatible') %
822 tuple(sorted(enabled)[0:2]))
822 tuple(sorted(enabled)[0:2]))
823
823
824 if reset:
824 if reset:
825 hbisect.resetstate(repo)
825 hbisect.resetstate(repo)
826 return
826 return
827
827
828 state = hbisect.load_state(repo)
828 state = hbisect.load_state(repo)
829
829
830 # update state
830 # update state
831 if good or bad or skip:
831 if good or bad or skip:
832 if rev:
832 if rev:
833 nodes = [repo[i].node() for i in scmutil.revrange(repo, [rev])]
833 nodes = [repo[i].node() for i in scmutil.revrange(repo, [rev])]
834 else:
834 else:
835 nodes = [repo.lookup('.')]
835 nodes = [repo.lookup('.')]
836 if good:
836 if good:
837 state['good'] += nodes
837 state['good'] += nodes
838 elif bad:
838 elif bad:
839 state['bad'] += nodes
839 state['bad'] += nodes
840 elif skip:
840 elif skip:
841 state['skip'] += nodes
841 state['skip'] += nodes
842 hbisect.save_state(repo, state)
842 hbisect.save_state(repo, state)
843 if not (state['good'] and state['bad']):
843 if not (state['good'] and state['bad']):
844 return
844 return
845
845
846 def mayupdate(repo, node, show_stats=True):
846 def mayupdate(repo, node, show_stats=True):
847 """common used update sequence"""
847 """common used update sequence"""
848 if noupdate:
848 if noupdate:
849 return
849 return
850 cmdutil.checkunfinished(repo)
850 cmdutil.checkunfinished(repo)
851 cmdutil.bailifchanged(repo)
851 cmdutil.bailifchanged(repo)
852 return hg.clean(repo, node, show_stats=show_stats)
852 return hg.clean(repo, node, show_stats=show_stats)
853
853
854 displayer = logcmdutil.changesetdisplayer(ui, repo, {})
854 displayer = logcmdutil.changesetdisplayer(ui, repo, {})
855
855
856 if command:
856 if command:
857 changesets = 1
857 changesets = 1
858 if noupdate:
858 if noupdate:
859 try:
859 try:
860 node = state['current'][0]
860 node = state['current'][0]
861 except LookupError:
861 except LookupError:
862 raise error.Abort(_('current bisect revision is unknown - '
862 raise error.Abort(_('current bisect revision is unknown - '
863 'start a new bisect to fix'))
863 'start a new bisect to fix'))
864 else:
864 else:
865 node, p2 = repo.dirstate.parents()
865 node, p2 = repo.dirstate.parents()
866 if p2 != nullid:
866 if p2 != nullid:
867 raise error.Abort(_('current bisect revision is a merge'))
867 raise error.Abort(_('current bisect revision is a merge'))
868 if rev:
868 if rev:
869 node = repo[scmutil.revsingle(repo, rev, node)].node()
869 node = repo[scmutil.revsingle(repo, rev, node)].node()
870 try:
870 try:
871 while changesets:
871 while changesets:
872 # update state
872 # update state
873 state['current'] = [node]
873 state['current'] = [node]
874 hbisect.save_state(repo, state)
874 hbisect.save_state(repo, state)
875 status = ui.system(command, environ={'HG_NODE': hex(node)},
875 status = ui.system(command, environ={'HG_NODE': hex(node)},
876 blockedtag='bisect_check')
876 blockedtag='bisect_check')
877 if status == 125:
877 if status == 125:
878 transition = "skip"
878 transition = "skip"
879 elif status == 0:
879 elif status == 0:
880 transition = "good"
880 transition = "good"
881 # status < 0 means process was killed
881 # status < 0 means process was killed
882 elif status == 127:
882 elif status == 127:
883 raise error.Abort(_("failed to execute %s") % command)
883 raise error.Abort(_("failed to execute %s") % command)
884 elif status < 0:
884 elif status < 0:
885 raise error.Abort(_("%s killed") % command)
885 raise error.Abort(_("%s killed") % command)
886 else:
886 else:
887 transition = "bad"
887 transition = "bad"
888 state[transition].append(node)
888 state[transition].append(node)
889 ctx = repo[node]
889 ctx = repo[node]
890 ui.status(_('changeset %d:%s: %s\n') % (ctx.rev(), ctx,
890 ui.status(_('changeset %d:%s: %s\n') % (ctx.rev(), ctx,
891 transition))
891 transition))
892 hbisect.checkstate(state)
892 hbisect.checkstate(state)
893 # bisect
893 # bisect
894 nodes, changesets, bgood = hbisect.bisect(repo, state)
894 nodes, changesets, bgood = hbisect.bisect(repo, state)
895 # update to next check
895 # update to next check
896 node = nodes[0]
896 node = nodes[0]
897 mayupdate(repo, node, show_stats=False)
897 mayupdate(repo, node, show_stats=False)
898 finally:
898 finally:
899 state['current'] = [node]
899 state['current'] = [node]
900 hbisect.save_state(repo, state)
900 hbisect.save_state(repo, state)
901 hbisect.printresult(ui, repo, state, displayer, nodes, bgood)
901 hbisect.printresult(ui, repo, state, displayer, nodes, bgood)
902 return
902 return
903
903
904 hbisect.checkstate(state)
904 hbisect.checkstate(state)
905
905
906 # actually bisect
906 # actually bisect
907 nodes, changesets, good = hbisect.bisect(repo, state)
907 nodes, changesets, good = hbisect.bisect(repo, state)
908 if extend:
908 if extend:
909 if not changesets:
909 if not changesets:
910 extendnode = hbisect.extendrange(repo, state, nodes, good)
910 extendnode = hbisect.extendrange(repo, state, nodes, good)
911 if extendnode is not None:
911 if extendnode is not None:
912 ui.write(_("Extending search to changeset %d:%s\n")
912 ui.write(_("Extending search to changeset %d:%s\n")
913 % (extendnode.rev(), extendnode))
913 % (extendnode.rev(), extendnode))
914 state['current'] = [extendnode.node()]
914 state['current'] = [extendnode.node()]
915 hbisect.save_state(repo, state)
915 hbisect.save_state(repo, state)
916 return mayupdate(repo, extendnode.node())
916 return mayupdate(repo, extendnode.node())
917 raise error.Abort(_("nothing to extend"))
917 raise error.Abort(_("nothing to extend"))
918
918
919 if changesets == 0:
919 if changesets == 0:
920 hbisect.printresult(ui, repo, state, displayer, nodes, good)
920 hbisect.printresult(ui, repo, state, displayer, nodes, good)
921 else:
921 else:
922 assert len(nodes) == 1 # only a single node can be tested next
922 assert len(nodes) == 1 # only a single node can be tested next
923 node = nodes[0]
923 node = nodes[0]
924 # compute the approximate number of remaining tests
924 # compute the approximate number of remaining tests
925 tests, size = 0, 2
925 tests, size = 0, 2
926 while size <= changesets:
926 while size <= changesets:
927 tests, size = tests + 1, size * 2
927 tests, size = tests + 1, size * 2
928 rev = repo.changelog.rev(node)
928 rev = repo.changelog.rev(node)
929 ui.write(_("Testing changeset %d:%s "
929 ui.write(_("Testing changeset %d:%s "
930 "(%d changesets remaining, ~%d tests)\n")
930 "(%d changesets remaining, ~%d tests)\n")
931 % (rev, short(node), changesets, tests))
931 % (rev, short(node), changesets, tests))
932 state['current'] = [node]
932 state['current'] = [node]
933 hbisect.save_state(repo, state)
933 hbisect.save_state(repo, state)
934 return mayupdate(repo, node)
934 return mayupdate(repo, node)
935
935
936 @command('bookmarks|bookmark',
936 @command('bookmarks|bookmark',
937 [('f', 'force', False, _('force')),
937 [('f', 'force', False, _('force')),
938 ('r', 'rev', '', _('revision for bookmark action'), _('REV')),
938 ('r', 'rev', '', _('revision for bookmark action'), _('REV')),
939 ('d', 'delete', False, _('delete a given bookmark')),
939 ('d', 'delete', False, _('delete a given bookmark')),
940 ('m', 'rename', '', _('rename a given bookmark'), _('OLD')),
940 ('m', 'rename', '', _('rename a given bookmark'), _('OLD')),
941 ('i', 'inactive', False, _('mark a bookmark inactive')),
941 ('i', 'inactive', False, _('mark a bookmark inactive')),
942 ('l', 'list', False, _('list existing bookmarks')),
942 ('l', 'list', False, _('list existing bookmarks')),
943 ] + formatteropts,
943 ] + formatteropts,
944 _('hg bookmarks [OPTIONS]... [NAME]...'),
944 _('hg bookmarks [OPTIONS]... [NAME]...'),
945 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION)
945 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION)
946 def bookmark(ui, repo, *names, **opts):
946 def bookmark(ui, repo, *names, **opts):
947 '''create a new bookmark or list existing bookmarks
947 '''create a new bookmark or list existing bookmarks
948
948
949 Bookmarks are labels on changesets to help track lines of development.
949 Bookmarks are labels on changesets to help track lines of development.
950 Bookmarks are unversioned and can be moved, renamed and deleted.
950 Bookmarks are unversioned and can be moved, renamed and deleted.
951 Deleting or moving a bookmark has no effect on the associated changesets.
951 Deleting or moving a bookmark has no effect on the associated changesets.
952
952
953 Creating or updating to a bookmark causes it to be marked as 'active'.
953 Creating or updating to a bookmark causes it to be marked as 'active'.
954 The active bookmark is indicated with a '*'.
954 The active bookmark is indicated with a '*'.
955 When a commit is made, the active bookmark will advance to the new commit.
955 When a commit is made, the active bookmark will advance to the new commit.
956 A plain :hg:`update` will also advance an active bookmark, if possible.
956 A plain :hg:`update` will also advance an active bookmark, if possible.
957 Updating away from a bookmark will cause it to be deactivated.
957 Updating away from a bookmark will cause it to be deactivated.
958
958
959 Bookmarks can be pushed and pulled between repositories (see
959 Bookmarks can be pushed and pulled between repositories (see
960 :hg:`help push` and :hg:`help pull`). If a shared bookmark has
960 :hg:`help push` and :hg:`help pull`). If a shared bookmark has
961 diverged, a new 'divergent bookmark' of the form 'name@path' will
961 diverged, a new 'divergent bookmark' of the form 'name@path' will
962 be created. Using :hg:`merge` will resolve the divergence.
962 be created. Using :hg:`merge` will resolve the divergence.
963
963
964 Specifying bookmark as '.' to -m/-d/-l options is equivalent to specifying
964 Specifying bookmark as '.' to -m/-d/-l options is equivalent to specifying
965 the active bookmark's name.
965 the active bookmark's name.
966
966
967 A bookmark named '@' has the special property that :hg:`clone` will
967 A bookmark named '@' has the special property that :hg:`clone` will
968 check it out by default if it exists.
968 check it out by default if it exists.
969
969
970 .. container:: verbose
970 .. container:: verbose
971
971
972 Template:
972 Template:
973
973
974 The following keywords are supported in addition to the common template
974 The following keywords are supported in addition to the common template
975 keywords and functions such as ``{bookmark}``. See also
975 keywords and functions such as ``{bookmark}``. See also
976 :hg:`help templates`.
976 :hg:`help templates`.
977
977
978 :active: Boolean. True if the bookmark is active.
978 :active: Boolean. True if the bookmark is active.
979
979
980 Examples:
980 Examples:
981
981
982 - create an active bookmark for a new line of development::
982 - create an active bookmark for a new line of development::
983
983
984 hg book new-feature
984 hg book new-feature
985
985
986 - create an inactive bookmark as a place marker::
986 - create an inactive bookmark as a place marker::
987
987
988 hg book -i reviewed
988 hg book -i reviewed
989
989
990 - create an inactive bookmark on another changeset::
990 - create an inactive bookmark on another changeset::
991
991
992 hg book -r .^ tested
992 hg book -r .^ tested
993
993
994 - rename bookmark turkey to dinner::
994 - rename bookmark turkey to dinner::
995
995
996 hg book -m turkey dinner
996 hg book -m turkey dinner
997
997
998 - move the '@' bookmark from another branch::
998 - move the '@' bookmark from another branch::
999
999
1000 hg book -f @
1000 hg book -f @
1001
1001
1002 - print only the active bookmark name::
1002 - print only the active bookmark name::
1003
1003
1004 hg book -ql .
1004 hg book -ql .
1005 '''
1005 '''
1006 opts = pycompat.byteskwargs(opts)
1006 opts = pycompat.byteskwargs(opts)
1007 force = opts.get('force')
1007 force = opts.get('force')
1008 rev = opts.get('rev')
1008 rev = opts.get('rev')
1009 inactive = opts.get('inactive') # meaning add/rename to inactive bookmark
1009 inactive = opts.get('inactive') # meaning add/rename to inactive bookmark
1010
1010
1011 selactions = [k for k in ['delete', 'rename', 'list'] if opts.get(k)]
1011 selactions = [k for k in ['delete', 'rename', 'list'] if opts.get(k)]
1012 if len(selactions) > 1:
1012 if len(selactions) > 1:
1013 raise error.Abort(_('--%s and --%s are incompatible')
1013 raise error.Abort(_('--%s and --%s are incompatible')
1014 % tuple(selactions[:2]))
1014 % tuple(selactions[:2]))
1015 if selactions:
1015 if selactions:
1016 action = selactions[0]
1016 action = selactions[0]
1017 elif names or rev:
1017 elif names or rev:
1018 action = 'add'
1018 action = 'add'
1019 elif inactive:
1019 elif inactive:
1020 action = 'inactive' # meaning deactivate
1020 action = 'inactive' # meaning deactivate
1021 else:
1021 else:
1022 action = 'list'
1022 action = 'list'
1023
1023
1024 if rev and action in {'delete', 'rename', 'list'}:
1024 if rev and action in {'delete', 'rename', 'list'}:
1025 raise error.Abort(_("--rev is incompatible with --%s") % action)
1025 raise error.Abort(_("--rev is incompatible with --%s") % action)
1026 if inactive and action in {'delete', 'list'}:
1026 if inactive and action in {'delete', 'list'}:
1027 raise error.Abort(_("--inactive is incompatible with --%s") % action)
1027 raise error.Abort(_("--inactive is incompatible with --%s") % action)
1028 if not names and action in {'add', 'delete'}:
1028 if not names and action in {'add', 'delete'}:
1029 raise error.Abort(_("bookmark name required"))
1029 raise error.Abort(_("bookmark name required"))
1030
1030
1031 if action in {'add', 'delete', 'rename', 'inactive'}:
1031 if action in {'add', 'delete', 'rename', 'inactive'}:
1032 with repo.wlock(), repo.lock(), repo.transaction('bookmark') as tr:
1032 with repo.wlock(), repo.lock(), repo.transaction('bookmark') as tr:
1033 if action == 'delete':
1033 if action == 'delete':
1034 names = pycompat.maplist(repo._bookmarks.expandname, names)
1034 names = pycompat.maplist(repo._bookmarks.expandname, names)
1035 bookmarks.delete(repo, tr, names)
1035 bookmarks.delete(repo, tr, names)
1036 elif action == 'rename':
1036 elif action == 'rename':
1037 if not names:
1037 if not names:
1038 raise error.Abort(_("new bookmark name required"))
1038 raise error.Abort(_("new bookmark name required"))
1039 elif len(names) > 1:
1039 elif len(names) > 1:
1040 raise error.Abort(_("only one new bookmark name allowed"))
1040 raise error.Abort(_("only one new bookmark name allowed"))
1041 oldname = repo._bookmarks.expandname(opts['rename'])
1041 oldname = repo._bookmarks.expandname(opts['rename'])
1042 bookmarks.rename(repo, tr, oldname, names[0], force, inactive)
1042 bookmarks.rename(repo, tr, oldname, names[0], force, inactive)
1043 elif action == 'add':
1043 elif action == 'add':
1044 bookmarks.addbookmarks(repo, tr, names, rev, force, inactive)
1044 bookmarks.addbookmarks(repo, tr, names, rev, force, inactive)
1045 elif action == 'inactive':
1045 elif action == 'inactive':
1046 if len(repo._bookmarks) == 0:
1046 if len(repo._bookmarks) == 0:
1047 ui.status(_("no bookmarks set\n"))
1047 ui.status(_("no bookmarks set\n"))
1048 elif not repo._activebookmark:
1048 elif not repo._activebookmark:
1049 ui.status(_("no active bookmark\n"))
1049 ui.status(_("no active bookmark\n"))
1050 else:
1050 else:
1051 bookmarks.deactivate(repo)
1051 bookmarks.deactivate(repo)
1052 elif action == 'list':
1052 elif action == 'list':
1053 names = pycompat.maplist(repo._bookmarks.expandname, names)
1053 names = pycompat.maplist(repo._bookmarks.expandname, names)
1054 with ui.formatter('bookmarks', opts) as fm:
1054 with ui.formatter('bookmarks', opts) as fm:
1055 bookmarks.printbookmarks(ui, repo, fm, names)
1055 bookmarks.printbookmarks(ui, repo, fm, names)
1056 else:
1056 else:
1057 raise error.ProgrammingError('invalid action: %s' % action)
1057 raise error.ProgrammingError('invalid action: %s' % action)
1058
1058
1059 @command('branch',
1059 @command('branch',
1060 [('f', 'force', None,
1060 [('f', 'force', None,
1061 _('set branch name even if it shadows an existing branch')),
1061 _('set branch name even if it shadows an existing branch')),
1062 ('C', 'clean', None, _('reset branch name to parent branch name')),
1062 ('C', 'clean', None, _('reset branch name to parent branch name')),
1063 ('r', 'rev', [], _('change branches of the given revs (EXPERIMENTAL)')),
1063 ('r', 'rev', [], _('change branches of the given revs (EXPERIMENTAL)')),
1064 ],
1064 ],
1065 _('[-fC] [NAME]'),
1065 _('[-fC] [NAME]'),
1066 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION)
1066 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION)
1067 def branch(ui, repo, label=None, **opts):
1067 def branch(ui, repo, label=None, **opts):
1068 """set or show the current branch name
1068 """set or show the current branch name
1069
1069
1070 .. note::
1070 .. note::
1071
1071
1072 Branch names are permanent and global. Use :hg:`bookmark` to create a
1072 Branch names are permanent and global. Use :hg:`bookmark` to create a
1073 light-weight bookmark instead. See :hg:`help glossary` for more
1073 light-weight bookmark instead. See :hg:`help glossary` for more
1074 information about named branches and bookmarks.
1074 information about named branches and bookmarks.
1075
1075
1076 With no argument, show the current branch name. With one argument,
1076 With no argument, show the current branch name. With one argument,
1077 set the working directory branch name (the branch will not exist
1077 set the working directory branch name (the branch will not exist
1078 in the repository until the next commit). Standard practice
1078 in the repository until the next commit). Standard practice
1079 recommends that primary development take place on the 'default'
1079 recommends that primary development take place on the 'default'
1080 branch.
1080 branch.
1081
1081
1082 Unless -f/--force is specified, branch will not let you set a
1082 Unless -f/--force is specified, branch will not let you set a
1083 branch name that already exists.
1083 branch name that already exists.
1084
1084
1085 Use -C/--clean to reset the working directory branch to that of
1085 Use -C/--clean to reset the working directory branch to that of
1086 the parent of the working directory, negating a previous branch
1086 the parent of the working directory, negating a previous branch
1087 change.
1087 change.
1088
1088
1089 Use the command :hg:`update` to switch to an existing branch. Use
1089 Use the command :hg:`update` to switch to an existing branch. Use
1090 :hg:`commit --close-branch` to mark this branch head as closed.
1090 :hg:`commit --close-branch` to mark this branch head as closed.
1091 When all heads of a branch are closed, the branch will be
1091 When all heads of a branch are closed, the branch will be
1092 considered closed.
1092 considered closed.
1093
1093
1094 Returns 0 on success.
1094 Returns 0 on success.
1095 """
1095 """
1096 opts = pycompat.byteskwargs(opts)
1096 opts = pycompat.byteskwargs(opts)
1097 revs = opts.get('rev')
1097 revs = opts.get('rev')
1098 if label:
1098 if label:
1099 label = label.strip()
1099 label = label.strip()
1100
1100
1101 if not opts.get('clean') and not label:
1101 if not opts.get('clean') and not label:
1102 if revs:
1102 if revs:
1103 raise error.Abort(_("no branch name specified for the revisions"))
1103 raise error.Abort(_("no branch name specified for the revisions"))
1104 ui.write("%s\n" % repo.dirstate.branch())
1104 ui.write("%s\n" % repo.dirstate.branch())
1105 return
1105 return
1106
1106
1107 with repo.wlock():
1107 with repo.wlock():
1108 if opts.get('clean'):
1108 if opts.get('clean'):
1109 label = repo['.'].branch()
1109 label = repo['.'].branch()
1110 repo.dirstate.setbranch(label)
1110 repo.dirstate.setbranch(label)
1111 ui.status(_('reset working directory to branch %s\n') % label)
1111 ui.status(_('reset working directory to branch %s\n') % label)
1112 elif label:
1112 elif label:
1113
1113
1114 scmutil.checknewlabel(repo, label, 'branch')
1114 scmutil.checknewlabel(repo, label, 'branch')
1115 if revs:
1115 if revs:
1116 return cmdutil.changebranch(ui, repo, revs, label)
1116 return cmdutil.changebranch(ui, repo, revs, label)
1117
1117
1118 if not opts.get('force') and label in repo.branchmap():
1118 if not opts.get('force') and label in repo.branchmap():
1119 if label not in [p.branch() for p in repo[None].parents()]:
1119 if label not in [p.branch() for p in repo[None].parents()]:
1120 raise error.Abort(_('a branch of the same name already'
1120 raise error.Abort(_('a branch of the same name already'
1121 ' exists'),
1121 ' exists'),
1122 # i18n: "it" refers to an existing branch
1122 # i18n: "it" refers to an existing branch
1123 hint=_("use 'hg update' to switch to it"))
1123 hint=_("use 'hg update' to switch to it"))
1124
1124
1125 repo.dirstate.setbranch(label)
1125 repo.dirstate.setbranch(label)
1126 ui.status(_('marked working directory as branch %s\n') % label)
1126 ui.status(_('marked working directory as branch %s\n') % label)
1127
1127
1128 # find any open named branches aside from default
1128 # find any open named branches aside from default
1129 others = [n for n, h, t, c in repo.branchmap().iterbranches()
1129 others = [n for n, h, t, c in repo.branchmap().iterbranches()
1130 if n != "default" and not c]
1130 if n != "default" and not c]
1131 if not others:
1131 if not others:
1132 ui.status(_('(branches are permanent and global, '
1132 ui.status(_('(branches are permanent and global, '
1133 'did you want a bookmark?)\n'))
1133 'did you want a bookmark?)\n'))
1134
1134
1135 @command('branches',
1135 @command('branches',
1136 [('a', 'active', False,
1136 [('a', 'active', False,
1137 _('show only branches that have unmerged heads (DEPRECATED)')),
1137 _('show only branches that have unmerged heads (DEPRECATED)')),
1138 ('c', 'closed', False, _('show normal and closed branches')),
1138 ('c', 'closed', False, _('show normal and closed branches')),
1139 ('r', 'rev', [], _('show branch name(s) of the given rev'))
1139 ('r', 'rev', [], _('show branch name(s) of the given rev'))
1140 ] + formatteropts,
1140 ] + formatteropts,
1141 _('[-c]'),
1141 _('[-c]'),
1142 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1142 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1143 intents={INTENT_READONLY})
1143 intents={INTENT_READONLY})
1144 def branches(ui, repo, active=False, closed=False, **opts):
1144 def branches(ui, repo, active=False, closed=False, **opts):
1145 """list repository named branches
1145 """list repository named branches
1146
1146
1147 List the repository's named branches, indicating which ones are
1147 List the repository's named branches, indicating which ones are
1148 inactive. If -c/--closed is specified, also list branches which have
1148 inactive. If -c/--closed is specified, also list branches which have
1149 been marked closed (see :hg:`commit --close-branch`).
1149 been marked closed (see :hg:`commit --close-branch`).
1150
1150
1151 Use the command :hg:`update` to switch to an existing branch.
1151 Use the command :hg:`update` to switch to an existing branch.
1152
1152
1153 .. container:: verbose
1153 .. container:: verbose
1154
1154
1155 Template:
1155 Template:
1156
1156
1157 The following keywords are supported in addition to the common template
1157 The following keywords are supported in addition to the common template
1158 keywords and functions such as ``{branch}``. See also
1158 keywords and functions such as ``{branch}``. See also
1159 :hg:`help templates`.
1159 :hg:`help templates`.
1160
1160
1161 :active: Boolean. True if the branch is active.
1161 :active: Boolean. True if the branch is active.
1162 :closed: Boolean. True if the branch is closed.
1162 :closed: Boolean. True if the branch is closed.
1163 :current: Boolean. True if it is the current branch.
1163 :current: Boolean. True if it is the current branch.
1164
1164
1165 Returns 0.
1165 Returns 0.
1166 """
1166 """
1167
1167
1168 opts = pycompat.byteskwargs(opts)
1168 opts = pycompat.byteskwargs(opts)
1169 revs = opts.get('rev')
1169 revs = opts.get('rev')
1170 selectedbranches = None
1170 selectedbranches = None
1171 if revs:
1171 if revs:
1172 revs = scmutil.revrange(repo, revs)
1172 revs = scmutil.revrange(repo, revs)
1173 getbi = repo.revbranchcache().branchinfo
1173 getbi = repo.revbranchcache().branchinfo
1174 selectedbranches = {getbi(r)[0] for r in revs}
1174 selectedbranches = {getbi(r)[0] for r in revs}
1175
1175
1176 ui.pager('branches')
1176 ui.pager('branches')
1177 fm = ui.formatter('branches', opts)
1177 fm = ui.formatter('branches', opts)
1178 hexfunc = fm.hexfunc
1178 hexfunc = fm.hexfunc
1179
1179
1180 allheads = set(repo.heads())
1180 allheads = set(repo.heads())
1181 branches = []
1181 branches = []
1182 for tag, heads, tip, isclosed in repo.branchmap().iterbranches():
1182 for tag, heads, tip, isclosed in repo.branchmap().iterbranches():
1183 if selectedbranches is not None and tag not in selectedbranches:
1183 if selectedbranches is not None and tag not in selectedbranches:
1184 continue
1184 continue
1185 isactive = False
1185 isactive = False
1186 if not isclosed:
1186 if not isclosed:
1187 openheads = set(repo.branchmap().iteropen(heads))
1187 openheads = set(repo.branchmap().iteropen(heads))
1188 isactive = bool(openheads & allheads)
1188 isactive = bool(openheads & allheads)
1189 branches.append((tag, repo[tip], isactive, not isclosed))
1189 branches.append((tag, repo[tip], isactive, not isclosed))
1190 branches.sort(key=lambda i: (i[2], i[1].rev(), i[0], i[3]),
1190 branches.sort(key=lambda i: (i[2], i[1].rev(), i[0], i[3]),
1191 reverse=True)
1191 reverse=True)
1192
1192
1193 for tag, ctx, isactive, isopen in branches:
1193 for tag, ctx, isactive, isopen in branches:
1194 if active and not isactive:
1194 if active and not isactive:
1195 continue
1195 continue
1196 if isactive:
1196 if isactive:
1197 label = 'branches.active'
1197 label = 'branches.active'
1198 notice = ''
1198 notice = ''
1199 elif not isopen:
1199 elif not isopen:
1200 if not closed:
1200 if not closed:
1201 continue
1201 continue
1202 label = 'branches.closed'
1202 label = 'branches.closed'
1203 notice = _(' (closed)')
1203 notice = _(' (closed)')
1204 else:
1204 else:
1205 label = 'branches.inactive'
1205 label = 'branches.inactive'
1206 notice = _(' (inactive)')
1206 notice = _(' (inactive)')
1207 current = (tag == repo.dirstate.branch())
1207 current = (tag == repo.dirstate.branch())
1208 if current:
1208 if current:
1209 label = 'branches.current'
1209 label = 'branches.current'
1210
1210
1211 fm.startitem()
1211 fm.startitem()
1212 fm.write('branch', '%s', tag, label=label)
1212 fm.write('branch', '%s', tag, label=label)
1213 rev = ctx.rev()
1213 rev = ctx.rev()
1214 padsize = max(31 - len("%d" % rev) - encoding.colwidth(tag), 0)
1214 padsize = max(31 - len("%d" % rev) - encoding.colwidth(tag), 0)
1215 fmt = ' ' * padsize + ' %d:%s'
1215 fmt = ' ' * padsize + ' %d:%s'
1216 fm.condwrite(not ui.quiet, 'rev node', fmt, rev, hexfunc(ctx.node()),
1216 fm.condwrite(not ui.quiet, 'rev node', fmt, rev, hexfunc(ctx.node()),
1217 label='log.changeset changeset.%s' % ctx.phasestr())
1217 label='log.changeset changeset.%s' % ctx.phasestr())
1218 fm.context(ctx=ctx)
1218 fm.context(ctx=ctx)
1219 fm.data(active=isactive, closed=not isopen, current=current)
1219 fm.data(active=isactive, closed=not isopen, current=current)
1220 if not ui.quiet:
1220 if not ui.quiet:
1221 fm.plain(notice)
1221 fm.plain(notice)
1222 fm.plain('\n')
1222 fm.plain('\n')
1223 fm.end()
1223 fm.end()
1224
1224
1225 @command('bundle',
1225 @command('bundle',
1226 [('f', 'force', None, _('run even when the destination is unrelated')),
1226 [('f', 'force', None, _('run even when the destination is unrelated')),
1227 ('r', 'rev', [], _('a changeset intended to be added to the destination'),
1227 ('r', 'rev', [], _('a changeset intended to be added to the destination'),
1228 _('REV')),
1228 _('REV')),
1229 ('b', 'branch', [], _('a specific branch you would like to bundle'),
1229 ('b', 'branch', [], _('a specific branch you would like to bundle'),
1230 _('BRANCH')),
1230 _('BRANCH')),
1231 ('', 'base', [],
1231 ('', 'base', [],
1232 _('a base changeset assumed to be available at the destination'),
1232 _('a base changeset assumed to be available at the destination'),
1233 _('REV')),
1233 _('REV')),
1234 ('a', 'all', None, _('bundle all changesets in the repository')),
1234 ('a', 'all', None, _('bundle all changesets in the repository')),
1235 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE')),
1235 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE')),
1236 ] + remoteopts,
1236 ] + remoteopts,
1237 _('[-f] [-t BUNDLESPEC] [-a] [-r REV]... [--base REV]... FILE [DEST]'),
1237 _('[-f] [-t BUNDLESPEC] [-a] [-r REV]... [--base REV]... FILE [DEST]'),
1238 helpcategory=command.CATEGORY_IMPORT_EXPORT)
1238 helpcategory=command.CATEGORY_IMPORT_EXPORT)
1239 def bundle(ui, repo, fname, dest=None, **opts):
1239 def bundle(ui, repo, fname, dest=None, **opts):
1240 """create a bundle file
1240 """create a bundle file
1241
1241
1242 Generate a bundle file containing data to be transferred to another
1242 Generate a bundle file containing data to be transferred to another
1243 repository.
1243 repository.
1244
1244
1245 To create a bundle containing all changesets, use -a/--all
1245 To create a bundle containing all changesets, use -a/--all
1246 (or --base null). Otherwise, hg assumes the destination will have
1246 (or --base null). Otherwise, hg assumes the destination will have
1247 all the nodes you specify with --base parameters. Otherwise, hg
1247 all the nodes you specify with --base parameters. Otherwise, hg
1248 will assume the repository has all the nodes in destination, or
1248 will assume the repository has all the nodes in destination, or
1249 default-push/default if no destination is specified, where destination
1249 default-push/default if no destination is specified, where destination
1250 is the repository you provide through DEST option.
1250 is the repository you provide through DEST option.
1251
1251
1252 You can change bundle format with the -t/--type option. See
1252 You can change bundle format with the -t/--type option. See
1253 :hg:`help bundlespec` for documentation on this format. By default,
1253 :hg:`help bundlespec` for documentation on this format. By default,
1254 the most appropriate format is used and compression defaults to
1254 the most appropriate format is used and compression defaults to
1255 bzip2.
1255 bzip2.
1256
1256
1257 The bundle file can then be transferred using conventional means
1257 The bundle file can then be transferred using conventional means
1258 and applied to another repository with the unbundle or pull
1258 and applied to another repository with the unbundle or pull
1259 command. This is useful when direct push and pull are not
1259 command. This is useful when direct push and pull are not
1260 available or when exporting an entire repository is undesirable.
1260 available or when exporting an entire repository is undesirable.
1261
1261
1262 Applying bundles preserves all changeset contents including
1262 Applying bundles preserves all changeset contents including
1263 permissions, copy/rename information, and revision history.
1263 permissions, copy/rename information, and revision history.
1264
1264
1265 Returns 0 on success, 1 if no changes found.
1265 Returns 0 on success, 1 if no changes found.
1266 """
1266 """
1267 opts = pycompat.byteskwargs(opts)
1267 opts = pycompat.byteskwargs(opts)
1268 revs = None
1268 revs = None
1269 if 'rev' in opts:
1269 if 'rev' in opts:
1270 revstrings = opts['rev']
1270 revstrings = opts['rev']
1271 revs = scmutil.revrange(repo, revstrings)
1271 revs = scmutil.revrange(repo, revstrings)
1272 if revstrings and not revs:
1272 if revstrings and not revs:
1273 raise error.Abort(_('no commits to bundle'))
1273 raise error.Abort(_('no commits to bundle'))
1274
1274
1275 bundletype = opts.get('type', 'bzip2').lower()
1275 bundletype = opts.get('type', 'bzip2').lower()
1276 try:
1276 try:
1277 bundlespec = exchange.parsebundlespec(repo, bundletype, strict=False)
1277 bundlespec = exchange.parsebundlespec(repo, bundletype, strict=False)
1278 except error.UnsupportedBundleSpecification as e:
1278 except error.UnsupportedBundleSpecification as e:
1279 raise error.Abort(pycompat.bytestr(e),
1279 raise error.Abort(pycompat.bytestr(e),
1280 hint=_("see 'hg help bundlespec' for supported "
1280 hint=_("see 'hg help bundlespec' for supported "
1281 "values for --type"))
1281 "values for --type"))
1282 cgversion = bundlespec.contentopts["cg.version"]
1282 cgversion = bundlespec.contentopts["cg.version"]
1283
1283
1284 # Packed bundles are a pseudo bundle format for now.
1284 # Packed bundles are a pseudo bundle format for now.
1285 if cgversion == 's1':
1285 if cgversion == 's1':
1286 raise error.Abort(_('packed bundles cannot be produced by "hg bundle"'),
1286 raise error.Abort(_('packed bundles cannot be produced by "hg bundle"'),
1287 hint=_("use 'hg debugcreatestreamclonebundle'"))
1287 hint=_("use 'hg debugcreatestreamclonebundle'"))
1288
1288
1289 if opts.get('all'):
1289 if opts.get('all'):
1290 if dest:
1290 if dest:
1291 raise error.Abort(_("--all is incompatible with specifying "
1291 raise error.Abort(_("--all is incompatible with specifying "
1292 "a destination"))
1292 "a destination"))
1293 if opts.get('base'):
1293 if opts.get('base'):
1294 ui.warn(_("ignoring --base because --all was specified\n"))
1294 ui.warn(_("ignoring --base because --all was specified\n"))
1295 base = [nullrev]
1295 base = [nullrev]
1296 else:
1296 else:
1297 base = scmutil.revrange(repo, opts.get('base'))
1297 base = scmutil.revrange(repo, opts.get('base'))
1298 if cgversion not in changegroup.supportedoutgoingversions(repo):
1298 if cgversion not in changegroup.supportedoutgoingversions(repo):
1299 raise error.Abort(_("repository does not support bundle version %s") %
1299 raise error.Abort(_("repository does not support bundle version %s") %
1300 cgversion)
1300 cgversion)
1301
1301
1302 if base:
1302 if base:
1303 if dest:
1303 if dest:
1304 raise error.Abort(_("--base is incompatible with specifying "
1304 raise error.Abort(_("--base is incompatible with specifying "
1305 "a destination"))
1305 "a destination"))
1306 common = [repo[rev].node() for rev in base]
1306 common = [repo[rev].node() for rev in base]
1307 heads = [repo[r].node() for r in revs] if revs else None
1307 heads = [repo[r].node() for r in revs] if revs else None
1308 outgoing = discovery.outgoing(repo, common, heads)
1308 outgoing = discovery.outgoing(repo, common, heads)
1309 else:
1309 else:
1310 dest = ui.expandpath(dest or 'default-push', dest or 'default')
1310 dest = ui.expandpath(dest or 'default-push', dest or 'default')
1311 dest, branches = hg.parseurl(dest, opts.get('branch'))
1311 dest, branches = hg.parseurl(dest, opts.get('branch'))
1312 other = hg.peer(repo, opts, dest)
1312 other = hg.peer(repo, opts, dest)
1313 revs = [repo[r].hex() for r in revs]
1313 revs = [repo[r].hex() for r in revs]
1314 revs, checkout = hg.addbranchrevs(repo, repo, branches, revs)
1314 revs, checkout = hg.addbranchrevs(repo, repo, branches, revs)
1315 heads = revs and pycompat.maplist(repo.lookup, revs) or revs
1315 heads = revs and pycompat.maplist(repo.lookup, revs) or revs
1316 outgoing = discovery.findcommonoutgoing(repo, other,
1316 outgoing = discovery.findcommonoutgoing(repo, other,
1317 onlyheads=heads,
1317 onlyheads=heads,
1318 force=opts.get('force'),
1318 force=opts.get('force'),
1319 portable=True)
1319 portable=True)
1320
1320
1321 if not outgoing.missing:
1321 if not outgoing.missing:
1322 scmutil.nochangesfound(ui, repo, not base and outgoing.excluded)
1322 scmutil.nochangesfound(ui, repo, not base and outgoing.excluded)
1323 return 1
1323 return 1
1324
1324
1325 if cgversion == '01': #bundle1
1325 if cgversion == '01': #bundle1
1326 bversion = 'HG10' + bundlespec.wirecompression
1326 bversion = 'HG10' + bundlespec.wirecompression
1327 bcompression = None
1327 bcompression = None
1328 elif cgversion in ('02', '03'):
1328 elif cgversion in ('02', '03'):
1329 bversion = 'HG20'
1329 bversion = 'HG20'
1330 bcompression = bundlespec.wirecompression
1330 bcompression = bundlespec.wirecompression
1331 else:
1331 else:
1332 raise error.ProgrammingError(
1332 raise error.ProgrammingError(
1333 'bundle: unexpected changegroup version %s' % cgversion)
1333 'bundle: unexpected changegroup version %s' % cgversion)
1334
1334
1335 # TODO compression options should be derived from bundlespec parsing.
1335 # TODO compression options should be derived from bundlespec parsing.
1336 # This is a temporary hack to allow adjusting bundle compression
1336 # This is a temporary hack to allow adjusting bundle compression
1337 # level without a) formalizing the bundlespec changes to declare it
1337 # level without a) formalizing the bundlespec changes to declare it
1338 # b) introducing a command flag.
1338 # b) introducing a command flag.
1339 compopts = {}
1339 compopts = {}
1340 complevel = ui.configint('experimental',
1340 complevel = ui.configint('experimental',
1341 'bundlecomplevel.' + bundlespec.compression)
1341 'bundlecomplevel.' + bundlespec.compression)
1342 if complevel is None:
1342 if complevel is None:
1343 complevel = ui.configint('experimental', 'bundlecomplevel')
1343 complevel = ui.configint('experimental', 'bundlecomplevel')
1344 if complevel is not None:
1344 if complevel is not None:
1345 compopts['level'] = complevel
1345 compopts['level'] = complevel
1346
1346
1347 # Allow overriding the bundling of obsmarker in phases through
1347 # Allow overriding the bundling of obsmarker in phases through
1348 # configuration while we don't have a bundle version that include them
1348 # configuration while we don't have a bundle version that include them
1349 if repo.ui.configbool('experimental', 'evolution.bundle-obsmarker'):
1349 if repo.ui.configbool('experimental', 'evolution.bundle-obsmarker'):
1350 bundlespec.contentopts['obsolescence'] = True
1350 bundlespec.contentopts['obsolescence'] = True
1351 if repo.ui.configbool('experimental', 'bundle-phases'):
1351 if repo.ui.configbool('experimental', 'bundle-phases'):
1352 bundlespec.contentopts['phases'] = True
1352 bundlespec.contentopts['phases'] = True
1353
1353
1354 bundle2.writenewbundle(ui, repo, 'bundle', fname, bversion, outgoing,
1354 bundle2.writenewbundle(ui, repo, 'bundle', fname, bversion, outgoing,
1355 bundlespec.contentopts, compression=bcompression,
1355 bundlespec.contentopts, compression=bcompression,
1356 compopts=compopts)
1356 compopts=compopts)
1357
1357
1358 @command('cat',
1358 @command('cat',
1359 [('o', 'output', '',
1359 [('o', 'output', '',
1360 _('print output to file with formatted name'), _('FORMAT')),
1360 _('print output to file with formatted name'), _('FORMAT')),
1361 ('r', 'rev', '', _('print the given revision'), _('REV')),
1361 ('r', 'rev', '', _('print the given revision'), _('REV')),
1362 ('', 'decode', None, _('apply any matching decode filter')),
1362 ('', 'decode', None, _('apply any matching decode filter')),
1363 ] + walkopts + formatteropts,
1363 ] + walkopts + formatteropts,
1364 _('[OPTION]... FILE...'),
1364 _('[OPTION]... FILE...'),
1365 helpcategory=command.CATEGORY_FILE_CONTENTS,
1365 helpcategory=command.CATEGORY_FILE_CONTENTS,
1366 inferrepo=True,
1366 inferrepo=True,
1367 intents={INTENT_READONLY})
1367 intents={INTENT_READONLY})
1368 def cat(ui, repo, file1, *pats, **opts):
1368 def cat(ui, repo, file1, *pats, **opts):
1369 """output the current or given revision of files
1369 """output the current or given revision of files
1370
1370
1371 Print the specified files as they were at the given revision. If
1371 Print the specified files as they were at the given revision. If
1372 no revision is given, the parent of the working directory is used.
1372 no revision is given, the parent of the working directory is used.
1373
1373
1374 Output may be to a file, in which case the name of the file is
1374 Output may be to a file, in which case the name of the file is
1375 given using a template string. See :hg:`help templates`. In addition
1375 given using a template string. See :hg:`help templates`. In addition
1376 to the common template keywords, the following formatting rules are
1376 to the common template keywords, the following formatting rules are
1377 supported:
1377 supported:
1378
1378
1379 :``%%``: literal "%" character
1379 :``%%``: literal "%" character
1380 :``%s``: basename of file being printed
1380 :``%s``: basename of file being printed
1381 :``%d``: dirname of file being printed, or '.' if in repository root
1381 :``%d``: dirname of file being printed, or '.' if in repository root
1382 :``%p``: root-relative path name of file being printed
1382 :``%p``: root-relative path name of file being printed
1383 :``%H``: changeset hash (40 hexadecimal digits)
1383 :``%H``: changeset hash (40 hexadecimal digits)
1384 :``%R``: changeset revision number
1384 :``%R``: changeset revision number
1385 :``%h``: short-form changeset hash (12 hexadecimal digits)
1385 :``%h``: short-form changeset hash (12 hexadecimal digits)
1386 :``%r``: zero-padded changeset revision number
1386 :``%r``: zero-padded changeset revision number
1387 :``%b``: basename of the exporting repository
1387 :``%b``: basename of the exporting repository
1388 :``\\``: literal "\\" character
1388 :``\\``: literal "\\" character
1389
1389
1390 .. container:: verbose
1390 .. container:: verbose
1391
1391
1392 Template:
1392 Template:
1393
1393
1394 The following keywords are supported in addition to the common template
1394 The following keywords are supported in addition to the common template
1395 keywords and functions. See also :hg:`help templates`.
1395 keywords and functions. See also :hg:`help templates`.
1396
1396
1397 :data: String. File content.
1397 :data: String. File content.
1398 :path: String. Repository-absolute path of the file.
1398 :path: String. Repository-absolute path of the file.
1399
1399
1400 Returns 0 on success.
1400 Returns 0 on success.
1401 """
1401 """
1402 opts = pycompat.byteskwargs(opts)
1402 opts = pycompat.byteskwargs(opts)
1403 rev = opts.get('rev')
1403 rev = opts.get('rev')
1404 if rev:
1404 if rev:
1405 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
1405 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
1406 ctx = scmutil.revsingle(repo, rev)
1406 ctx = scmutil.revsingle(repo, rev)
1407 m = scmutil.match(ctx, (file1,) + pats, opts)
1407 m = scmutil.match(ctx, (file1,) + pats, opts)
1408 fntemplate = opts.pop('output', '')
1408 fntemplate = opts.pop('output', '')
1409 if cmdutil.isstdiofilename(fntemplate):
1409 if cmdutil.isstdiofilename(fntemplate):
1410 fntemplate = ''
1410 fntemplate = ''
1411
1411
1412 if fntemplate:
1412 if fntemplate:
1413 fm = formatter.nullformatter(ui, 'cat', opts)
1413 fm = formatter.nullformatter(ui, 'cat', opts)
1414 else:
1414 else:
1415 ui.pager('cat')
1415 ui.pager('cat')
1416 fm = ui.formatter('cat', opts)
1416 fm = ui.formatter('cat', opts)
1417 with fm:
1417 with fm:
1418 return cmdutil.cat(ui, repo, ctx, m, fm, fntemplate, '',
1418 return cmdutil.cat(ui, repo, ctx, m, fm, fntemplate, '',
1419 **pycompat.strkwargs(opts))
1419 **pycompat.strkwargs(opts))
1420
1420
1421 @command('clone',
1421 @command('clone',
1422 [('U', 'noupdate', None, _('the clone will include an empty working '
1422 [('U', 'noupdate', None, _('the clone will include an empty working '
1423 'directory (only a repository)')),
1423 'directory (only a repository)')),
1424 ('u', 'updaterev', '', _('revision, tag, or branch to check out'),
1424 ('u', 'updaterev', '', _('revision, tag, or branch to check out'),
1425 _('REV')),
1425 _('REV')),
1426 ('r', 'rev', [], _('do not clone everything, but include this changeset'
1426 ('r', 'rev', [], _('do not clone everything, but include this changeset'
1427 ' and its ancestors'), _('REV')),
1427 ' and its ancestors'), _('REV')),
1428 ('b', 'branch', [], _('do not clone everything, but include this branch\'s'
1428 ('b', 'branch', [], _('do not clone everything, but include this branch\'s'
1429 ' changesets and their ancestors'), _('BRANCH')),
1429 ' changesets and their ancestors'), _('BRANCH')),
1430 ('', 'pull', None, _('use pull protocol to copy metadata')),
1430 ('', 'pull', None, _('use pull protocol to copy metadata')),
1431 ('', 'uncompressed', None,
1431 ('', 'uncompressed', None,
1432 _('an alias to --stream (DEPRECATED)')),
1432 _('an alias to --stream (DEPRECATED)')),
1433 ('', 'stream', None,
1433 ('', 'stream', None,
1434 _('clone with minimal data processing')),
1434 _('clone with minimal data processing')),
1435 ] + remoteopts,
1435 ] + remoteopts,
1436 _('[OPTION]... SOURCE [DEST]'),
1436 _('[OPTION]... SOURCE [DEST]'),
1437 helpcategory=command.CATEGORY_REPO_CREATION,
1437 helpcategory=command.CATEGORY_REPO_CREATION,
1438 helpbasic=True, norepo=True)
1438 helpbasic=True, norepo=True)
1439 def clone(ui, source, dest=None, **opts):
1439 def clone(ui, source, dest=None, **opts):
1440 """make a copy of an existing repository
1440 """make a copy of an existing repository
1441
1441
1442 Create a copy of an existing repository in a new directory.
1442 Create a copy of an existing repository in a new directory.
1443
1443
1444 If no destination directory name is specified, it defaults to the
1444 If no destination directory name is specified, it defaults to the
1445 basename of the source.
1445 basename of the source.
1446
1446
1447 The location of the source is added to the new repository's
1447 The location of the source is added to the new repository's
1448 ``.hg/hgrc`` file, as the default to be used for future pulls.
1448 ``.hg/hgrc`` file, as the default to be used for future pulls.
1449
1449
1450 Only local paths and ``ssh://`` URLs are supported as
1450 Only local paths and ``ssh://`` URLs are supported as
1451 destinations. For ``ssh://`` destinations, no working directory or
1451 destinations. For ``ssh://`` destinations, no working directory or
1452 ``.hg/hgrc`` will be created on the remote side.
1452 ``.hg/hgrc`` will be created on the remote side.
1453
1453
1454 If the source repository has a bookmark called '@' set, that
1454 If the source repository has a bookmark called '@' set, that
1455 revision will be checked out in the new repository by default.
1455 revision will be checked out in the new repository by default.
1456
1456
1457 To check out a particular version, use -u/--update, or
1457 To check out a particular version, use -u/--update, or
1458 -U/--noupdate to create a clone with no working directory.
1458 -U/--noupdate to create a clone with no working directory.
1459
1459
1460 To pull only a subset of changesets, specify one or more revisions
1460 To pull only a subset of changesets, specify one or more revisions
1461 identifiers with -r/--rev or branches with -b/--branch. The
1461 identifiers with -r/--rev or branches with -b/--branch. The
1462 resulting clone will contain only the specified changesets and
1462 resulting clone will contain only the specified changesets and
1463 their ancestors. These options (or 'clone src#rev dest') imply
1463 their ancestors. These options (or 'clone src#rev dest') imply
1464 --pull, even for local source repositories.
1464 --pull, even for local source repositories.
1465
1465
1466 In normal clone mode, the remote normalizes repository data into a common
1466 In normal clone mode, the remote normalizes repository data into a common
1467 exchange format and the receiving end translates this data into its local
1467 exchange format and the receiving end translates this data into its local
1468 storage format. --stream activates a different clone mode that essentially
1468 storage format. --stream activates a different clone mode that essentially
1469 copies repository files from the remote with minimal data processing. This
1469 copies repository files from the remote with minimal data processing. This
1470 significantly reduces the CPU cost of a clone both remotely and locally.
1470 significantly reduces the CPU cost of a clone both remotely and locally.
1471 However, it often increases the transferred data size by 30-40%. This can
1471 However, it often increases the transferred data size by 30-40%. This can
1472 result in substantially faster clones where I/O throughput is plentiful,
1472 result in substantially faster clones where I/O throughput is plentiful,
1473 especially for larger repositories. A side-effect of --stream clones is
1473 especially for larger repositories. A side-effect of --stream clones is
1474 that storage settings and requirements on the remote are applied locally:
1474 that storage settings and requirements on the remote are applied locally:
1475 a modern client may inherit legacy or inefficient storage used by the
1475 a modern client may inherit legacy or inefficient storage used by the
1476 remote or a legacy Mercurial client may not be able to clone from a
1476 remote or a legacy Mercurial client may not be able to clone from a
1477 modern Mercurial remote.
1477 modern Mercurial remote.
1478
1478
1479 .. note::
1479 .. note::
1480
1480
1481 Specifying a tag will include the tagged changeset but not the
1481 Specifying a tag will include the tagged changeset but not the
1482 changeset containing the tag.
1482 changeset containing the tag.
1483
1483
1484 .. container:: verbose
1484 .. container:: verbose
1485
1485
1486 For efficiency, hardlinks are used for cloning whenever the
1486 For efficiency, hardlinks are used for cloning whenever the
1487 source and destination are on the same filesystem (note this
1487 source and destination are on the same filesystem (note this
1488 applies only to the repository data, not to the working
1488 applies only to the repository data, not to the working
1489 directory). Some filesystems, such as AFS, implement hardlinking
1489 directory). Some filesystems, such as AFS, implement hardlinking
1490 incorrectly, but do not report errors. In these cases, use the
1490 incorrectly, but do not report errors. In these cases, use the
1491 --pull option to avoid hardlinking.
1491 --pull option to avoid hardlinking.
1492
1492
1493 Mercurial will update the working directory to the first applicable
1493 Mercurial will update the working directory to the first applicable
1494 revision from this list:
1494 revision from this list:
1495
1495
1496 a) null if -U or the source repository has no changesets
1496 a) null if -U or the source repository has no changesets
1497 b) if -u . and the source repository is local, the first parent of
1497 b) if -u . and the source repository is local, the first parent of
1498 the source repository's working directory
1498 the source repository's working directory
1499 c) the changeset specified with -u (if a branch name, this means the
1499 c) the changeset specified with -u (if a branch name, this means the
1500 latest head of that branch)
1500 latest head of that branch)
1501 d) the changeset specified with -r
1501 d) the changeset specified with -r
1502 e) the tipmost head specified with -b
1502 e) the tipmost head specified with -b
1503 f) the tipmost head specified with the url#branch source syntax
1503 f) the tipmost head specified with the url#branch source syntax
1504 g) the revision marked with the '@' bookmark, if present
1504 g) the revision marked with the '@' bookmark, if present
1505 h) the tipmost head of the default branch
1505 h) the tipmost head of the default branch
1506 i) tip
1506 i) tip
1507
1507
1508 When cloning from servers that support it, Mercurial may fetch
1508 When cloning from servers that support it, Mercurial may fetch
1509 pre-generated data from a server-advertised URL or inline from the
1509 pre-generated data from a server-advertised URL or inline from the
1510 same stream. When this is done, hooks operating on incoming changesets
1510 same stream. When this is done, hooks operating on incoming changesets
1511 and changegroups may fire more than once, once for each pre-generated
1511 and changegroups may fire more than once, once for each pre-generated
1512 bundle and as well as for any additional remaining data. In addition,
1512 bundle and as well as for any additional remaining data. In addition,
1513 if an error occurs, the repository may be rolled back to a partial
1513 if an error occurs, the repository may be rolled back to a partial
1514 clone. This behavior may change in future releases.
1514 clone. This behavior may change in future releases.
1515 See :hg:`help -e clonebundles` for more.
1515 See :hg:`help -e clonebundles` for more.
1516
1516
1517 Examples:
1517 Examples:
1518
1518
1519 - clone a remote repository to a new directory named hg/::
1519 - clone a remote repository to a new directory named hg/::
1520
1520
1521 hg clone https://www.mercurial-scm.org/repo/hg/
1521 hg clone https://www.mercurial-scm.org/repo/hg/
1522
1522
1523 - create a lightweight local clone::
1523 - create a lightweight local clone::
1524
1524
1525 hg clone project/ project-feature/
1525 hg clone project/ project-feature/
1526
1526
1527 - clone from an absolute path on an ssh server (note double-slash)::
1527 - clone from an absolute path on an ssh server (note double-slash)::
1528
1528
1529 hg clone ssh://user@server//home/projects/alpha/
1529 hg clone ssh://user@server//home/projects/alpha/
1530
1530
1531 - do a streaming clone while checking out a specified version::
1531 - do a streaming clone while checking out a specified version::
1532
1532
1533 hg clone --stream http://server/repo -u 1.5
1533 hg clone --stream http://server/repo -u 1.5
1534
1534
1535 - create a repository without changesets after a particular revision::
1535 - create a repository without changesets after a particular revision::
1536
1536
1537 hg clone -r 04e544 experimental/ good/
1537 hg clone -r 04e544 experimental/ good/
1538
1538
1539 - clone (and track) a particular named branch::
1539 - clone (and track) a particular named branch::
1540
1540
1541 hg clone https://www.mercurial-scm.org/repo/hg/#stable
1541 hg clone https://www.mercurial-scm.org/repo/hg/#stable
1542
1542
1543 See :hg:`help urls` for details on specifying URLs.
1543 See :hg:`help urls` for details on specifying URLs.
1544
1544
1545 Returns 0 on success.
1545 Returns 0 on success.
1546 """
1546 """
1547 opts = pycompat.byteskwargs(opts)
1547 opts = pycompat.byteskwargs(opts)
1548 if opts.get('noupdate') and opts.get('updaterev'):
1548 if opts.get('noupdate') and opts.get('updaterev'):
1549 raise error.Abort(_("cannot specify both --noupdate and --updaterev"))
1549 raise error.Abort(_("cannot specify both --noupdate and --updaterev"))
1550
1550
1551 # --include/--exclude can come from narrow or sparse.
1551 # --include/--exclude can come from narrow or sparse.
1552 includepats, excludepats = None, None
1552 includepats, excludepats = None, None
1553
1553
1554 # hg.clone() differentiates between None and an empty set. So make sure
1554 # hg.clone() differentiates between None and an empty set. So make sure
1555 # patterns are sets if narrow is requested without patterns.
1555 # patterns are sets if narrow is requested without patterns.
1556 if opts.get('narrow'):
1556 if opts.get('narrow'):
1557 includepats = set()
1557 includepats = set()
1558 excludepats = set()
1558 excludepats = set()
1559
1559
1560 if opts.get('include'):
1560 if opts.get('include'):
1561 includepats = narrowspec.parsepatterns(opts.get('include'))
1561 includepats = narrowspec.parsepatterns(opts.get('include'))
1562 if opts.get('exclude'):
1562 if opts.get('exclude'):
1563 excludepats = narrowspec.parsepatterns(opts.get('exclude'))
1563 excludepats = narrowspec.parsepatterns(opts.get('exclude'))
1564
1564
1565 r = hg.clone(ui, opts, source, dest,
1565 r = hg.clone(ui, opts, source, dest,
1566 pull=opts.get('pull'),
1566 pull=opts.get('pull'),
1567 stream=opts.get('stream') or opts.get('uncompressed'),
1567 stream=opts.get('stream') or opts.get('uncompressed'),
1568 revs=opts.get('rev'),
1568 revs=opts.get('rev'),
1569 update=opts.get('updaterev') or not opts.get('noupdate'),
1569 update=opts.get('updaterev') or not opts.get('noupdate'),
1570 branch=opts.get('branch'),
1570 branch=opts.get('branch'),
1571 shareopts=opts.get('shareopts'),
1571 shareopts=opts.get('shareopts'),
1572 storeincludepats=includepats,
1572 storeincludepats=includepats,
1573 storeexcludepats=excludepats,
1573 storeexcludepats=excludepats,
1574 depth=opts.get('depth') or None)
1574 depth=opts.get('depth') or None)
1575
1575
1576 return r is None
1576 return r is None
1577
1577
1578 @command('commit|ci',
1578 @command('commit|ci',
1579 [('A', 'addremove', None,
1579 [('A', 'addremove', None,
1580 _('mark new/missing files as added/removed before committing')),
1580 _('mark new/missing files as added/removed before committing')),
1581 ('', 'close-branch', None,
1581 ('', 'close-branch', None,
1582 _('mark a branch head as closed')),
1582 _('mark a branch head as closed')),
1583 ('', 'amend', None, _('amend the parent of the working directory')),
1583 ('', 'amend', None, _('amend the parent of the working directory')),
1584 ('s', 'secret', None, _('use the secret phase for committing')),
1584 ('s', 'secret', None, _('use the secret phase for committing')),
1585 ('e', 'edit', None, _('invoke editor on commit messages')),
1585 ('e', 'edit', None, _('invoke editor on commit messages')),
1586 ('i', 'interactive', None, _('use interactive mode')),
1586 ('i', 'interactive', None, _('use interactive mode')),
1587 ] + walkopts + commitopts + commitopts2 + subrepoopts,
1587 ] + walkopts + commitopts + commitopts2 + subrepoopts,
1588 _('[OPTION]... [FILE]...'),
1588 _('[OPTION]... [FILE]...'),
1589 helpcategory=command.CATEGORY_COMMITTING, helpbasic=True,
1589 helpcategory=command.CATEGORY_COMMITTING, helpbasic=True,
1590 inferrepo=True)
1590 inferrepo=True)
1591 def commit(ui, repo, *pats, **opts):
1591 def commit(ui, repo, *pats, **opts):
1592 """commit the specified files or all outstanding changes
1592 """commit the specified files or all outstanding changes
1593
1593
1594 Commit changes to the given files into the repository. Unlike a
1594 Commit changes to the given files into the repository. Unlike a
1595 centralized SCM, this operation is a local operation. See
1595 centralized SCM, this operation is a local operation. See
1596 :hg:`push` for a way to actively distribute your changes.
1596 :hg:`push` for a way to actively distribute your changes.
1597
1597
1598 If a list of files is omitted, all changes reported by :hg:`status`
1598 If a list of files is omitted, all changes reported by :hg:`status`
1599 will be committed.
1599 will be committed.
1600
1600
1601 If you are committing the result of a merge, do not provide any
1601 If you are committing the result of a merge, do not provide any
1602 filenames or -I/-X filters.
1602 filenames or -I/-X filters.
1603
1603
1604 If no commit message is specified, Mercurial starts your
1604 If no commit message is specified, Mercurial starts your
1605 configured editor where you can enter a message. In case your
1605 configured editor where you can enter a message. In case your
1606 commit fails, you will find a backup of your message in
1606 commit fails, you will find a backup of your message in
1607 ``.hg/last-message.txt``.
1607 ``.hg/last-message.txt``.
1608
1608
1609 The --close-branch flag can be used to mark the current branch
1609 The --close-branch flag can be used to mark the current branch
1610 head closed. When all heads of a branch are closed, the branch
1610 head closed. When all heads of a branch are closed, the branch
1611 will be considered closed and no longer listed.
1611 will be considered closed and no longer listed.
1612
1612
1613 The --amend flag can be used to amend the parent of the
1613 The --amend flag can be used to amend the parent of the
1614 working directory with a new commit that contains the changes
1614 working directory with a new commit that contains the changes
1615 in the parent in addition to those currently reported by :hg:`status`,
1615 in the parent in addition to those currently reported by :hg:`status`,
1616 if there are any. The old commit is stored in a backup bundle in
1616 if there are any. The old commit is stored in a backup bundle in
1617 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
1617 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
1618 on how to restore it).
1618 on how to restore it).
1619
1619
1620 Message, user and date are taken from the amended commit unless
1620 Message, user and date are taken from the amended commit unless
1621 specified. When a message isn't specified on the command line,
1621 specified. When a message isn't specified on the command line,
1622 the editor will open with the message of the amended commit.
1622 the editor will open with the message of the amended commit.
1623
1623
1624 It is not possible to amend public changesets (see :hg:`help phases`)
1624 It is not possible to amend public changesets (see :hg:`help phases`)
1625 or changesets that have children.
1625 or changesets that have children.
1626
1626
1627 See :hg:`help dates` for a list of formats valid for -d/--date.
1627 See :hg:`help dates` for a list of formats valid for -d/--date.
1628
1628
1629 Returns 0 on success, 1 if nothing changed.
1629 Returns 0 on success, 1 if nothing changed.
1630
1630
1631 .. container:: verbose
1631 .. container:: verbose
1632
1632
1633 Examples:
1633 Examples:
1634
1634
1635 - commit all files ending in .py::
1635 - commit all files ending in .py::
1636
1636
1637 hg commit --include "set:**.py"
1637 hg commit --include "set:**.py"
1638
1638
1639 - commit all non-binary files::
1639 - commit all non-binary files::
1640
1640
1641 hg commit --exclude "set:binary()"
1641 hg commit --exclude "set:binary()"
1642
1642
1643 - amend the current commit and set the date to now::
1643 - amend the current commit and set the date to now::
1644
1644
1645 hg commit --amend --date now
1645 hg commit --amend --date now
1646 """
1646 """
1647 with repo.wlock(), repo.lock():
1647 with repo.wlock(), repo.lock():
1648 return _docommit(ui, repo, *pats, **opts)
1648 return _docommit(ui, repo, *pats, **opts)
1649
1649
1650 def _docommit(ui, repo, *pats, **opts):
1650 def _docommit(ui, repo, *pats, **opts):
1651 if opts.get(r'interactive'):
1651 if opts.get(r'interactive'):
1652 opts.pop(r'interactive')
1652 opts.pop(r'interactive')
1653 ret = cmdutil.dorecord(ui, repo, commit, None, False,
1653 ret = cmdutil.dorecord(ui, repo, commit, None, False,
1654 cmdutil.recordfilter, *pats,
1654 cmdutil.recordfilter, *pats,
1655 **opts)
1655 **opts)
1656 # ret can be 0 (no changes to record) or the value returned by
1656 # ret can be 0 (no changes to record) or the value returned by
1657 # commit(), 1 if nothing changed or None on success.
1657 # commit(), 1 if nothing changed or None on success.
1658 return 1 if ret == 0 else ret
1658 return 1 if ret == 0 else ret
1659
1659
1660 opts = pycompat.byteskwargs(opts)
1660 opts = pycompat.byteskwargs(opts)
1661 if opts.get('subrepos'):
1661 if opts.get('subrepos'):
1662 if opts.get('amend'):
1662 if opts.get('amend'):
1663 raise error.Abort(_('cannot amend with --subrepos'))
1663 raise error.Abort(_('cannot amend with --subrepos'))
1664 # Let --subrepos on the command line override config setting.
1664 # Let --subrepos on the command line override config setting.
1665 ui.setconfig('ui', 'commitsubrepos', True, 'commit')
1665 ui.setconfig('ui', 'commitsubrepos', True, 'commit')
1666
1666
1667 cmdutil.checkunfinished(repo, commit=True)
1667 cmdutil.checkunfinished(repo, commit=True)
1668
1668
1669 branch = repo[None].branch()
1669 branch = repo[None].branch()
1670 bheads = repo.branchheads(branch)
1670 bheads = repo.branchheads(branch)
1671
1671
1672 extra = {}
1672 extra = {}
1673 if opts.get('close_branch'):
1673 if opts.get('close_branch'):
1674 extra['close'] = '1'
1674 extra['close'] = '1'
1675
1675
1676 if not bheads:
1676 if not bheads:
1677 raise error.Abort(_('can only close branch heads'))
1677 raise error.Abort(_('can only close branch heads'))
1678 elif opts.get('amend'):
1678 elif opts.get('amend'):
1679 if repo['.'].p1().branch() != branch and \
1679 if repo['.'].p1().branch() != branch and \
1680 repo['.'].p2().branch() != branch:
1680 repo['.'].p2().branch() != branch:
1681 raise error.Abort(_('can only close branch heads'))
1681 raise error.Abort(_('can only close branch heads'))
1682
1682
1683 if opts.get('amend'):
1683 if opts.get('amend'):
1684 if ui.configbool('ui', 'commitsubrepos'):
1684 if ui.configbool('ui', 'commitsubrepos'):
1685 raise error.Abort(_('cannot amend with ui.commitsubrepos enabled'))
1685 raise error.Abort(_('cannot amend with ui.commitsubrepos enabled'))
1686
1686
1687 old = repo['.']
1687 old = repo['.']
1688 rewriteutil.precheck(repo, [old.rev()], 'amend')
1688 rewriteutil.precheck(repo, [old.rev()], 'amend')
1689
1689
1690 # Currently histedit gets confused if an amend happens while histedit
1690 # Currently histedit gets confused if an amend happens while histedit
1691 # is in progress. Since we have a checkunfinished command, we are
1691 # is in progress. Since we have a checkunfinished command, we are
1692 # temporarily honoring it.
1692 # temporarily honoring it.
1693 #
1693 #
1694 # Note: eventually this guard will be removed. Please do not expect
1694 # Note: eventually this guard will be removed. Please do not expect
1695 # this behavior to remain.
1695 # this behavior to remain.
1696 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
1696 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
1697 cmdutil.checkunfinished(repo)
1697 cmdutil.checkunfinished(repo)
1698
1698
1699 node = cmdutil.amend(ui, repo, old, extra, pats, opts)
1699 node = cmdutil.amend(ui, repo, old, extra, pats, opts)
1700 if node == old.node():
1700 if node == old.node():
1701 ui.status(_("nothing changed\n"))
1701 ui.status(_("nothing changed\n"))
1702 return 1
1702 return 1
1703 else:
1703 else:
1704 def commitfunc(ui, repo, message, match, opts):
1704 def commitfunc(ui, repo, message, match, opts):
1705 overrides = {}
1705 overrides = {}
1706 if opts.get('secret'):
1706 if opts.get('secret'):
1707 overrides[('phases', 'new-commit')] = 'secret'
1707 overrides[('phases', 'new-commit')] = 'secret'
1708
1708
1709 baseui = repo.baseui
1709 baseui = repo.baseui
1710 with baseui.configoverride(overrides, 'commit'):
1710 with baseui.configoverride(overrides, 'commit'):
1711 with ui.configoverride(overrides, 'commit'):
1711 with ui.configoverride(overrides, 'commit'):
1712 editform = cmdutil.mergeeditform(repo[None],
1712 editform = cmdutil.mergeeditform(repo[None],
1713 'commit.normal')
1713 'commit.normal')
1714 editor = cmdutil.getcommiteditor(
1714 editor = cmdutil.getcommiteditor(
1715 editform=editform, **pycompat.strkwargs(opts))
1715 editform=editform, **pycompat.strkwargs(opts))
1716 return repo.commit(message,
1716 return repo.commit(message,
1717 opts.get('user'),
1717 opts.get('user'),
1718 opts.get('date'),
1718 opts.get('date'),
1719 match,
1719 match,
1720 editor=editor,
1720 editor=editor,
1721 extra=extra)
1721 extra=extra)
1722
1722
1723 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
1723 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
1724
1724
1725 if not node:
1725 if not node:
1726 stat = cmdutil.postcommitstatus(repo, pats, opts)
1726 stat = cmdutil.postcommitstatus(repo, pats, opts)
1727 if stat[3]:
1727 if stat[3]:
1728 ui.status(_("nothing changed (%d missing files, see "
1728 ui.status(_("nothing changed (%d missing files, see "
1729 "'hg status')\n") % len(stat[3]))
1729 "'hg status')\n") % len(stat[3]))
1730 else:
1730 else:
1731 ui.status(_("nothing changed\n"))
1731 ui.status(_("nothing changed\n"))
1732 return 1
1732 return 1
1733
1733
1734 cmdutil.commitstatus(repo, node, branch, bheads, opts)
1734 cmdutil.commitstatus(repo, node, branch, bheads, opts)
1735
1735
1736 @command('config|showconfig|debugconfig',
1736 @command('config|showconfig|debugconfig',
1737 [('u', 'untrusted', None, _('show untrusted configuration options')),
1737 [('u', 'untrusted', None, _('show untrusted configuration options')),
1738 ('e', 'edit', None, _('edit user config')),
1738 ('e', 'edit', None, _('edit user config')),
1739 ('l', 'local', None, _('edit repository config')),
1739 ('l', 'local', None, _('edit repository config')),
1740 ('g', 'global', None, _('edit global config'))] + formatteropts,
1740 ('g', 'global', None, _('edit global config'))] + formatteropts,
1741 _('[-u] [NAME]...'),
1741 _('[-u] [NAME]...'),
1742 helpcategory=command.CATEGORY_HELP,
1742 helpcategory=command.CATEGORY_HELP,
1743 optionalrepo=True,
1743 optionalrepo=True,
1744 intents={INTENT_READONLY})
1744 intents={INTENT_READONLY})
1745 def config(ui, repo, *values, **opts):
1745 def config(ui, repo, *values, **opts):
1746 """show combined config settings from all hgrc files
1746 """show combined config settings from all hgrc files
1747
1747
1748 With no arguments, print names and values of all config items.
1748 With no arguments, print names and values of all config items.
1749
1749
1750 With one argument of the form section.name, print just the value
1750 With one argument of the form section.name, print just the value
1751 of that config item.
1751 of that config item.
1752
1752
1753 With multiple arguments, print names and values of all config
1753 With multiple arguments, print names and values of all config
1754 items with matching section names or section.names.
1754 items with matching section names or section.names.
1755
1755
1756 With --edit, start an editor on the user-level config file. With
1756 With --edit, start an editor on the user-level config file. With
1757 --global, edit the system-wide config file. With --local, edit the
1757 --global, edit the system-wide config file. With --local, edit the
1758 repository-level config file.
1758 repository-level config file.
1759
1759
1760 With --debug, the source (filename and line number) is printed
1760 With --debug, the source (filename and line number) is printed
1761 for each config item.
1761 for each config item.
1762
1762
1763 See :hg:`help config` for more information about config files.
1763 See :hg:`help config` for more information about config files.
1764
1764
1765 .. container:: verbose
1765 .. container:: verbose
1766
1766
1767 Template:
1767 Template:
1768
1768
1769 The following keywords are supported. See also :hg:`help templates`.
1769 The following keywords are supported. See also :hg:`help templates`.
1770
1770
1771 :name: String. Config name.
1771 :name: String. Config name.
1772 :source: String. Filename and line number where the item is defined.
1772 :source: String. Filename and line number where the item is defined.
1773 :value: String. Config value.
1773 :value: String. Config value.
1774
1774
1775 Returns 0 on success, 1 if NAME does not exist.
1775 Returns 0 on success, 1 if NAME does not exist.
1776
1776
1777 """
1777 """
1778
1778
1779 opts = pycompat.byteskwargs(opts)
1779 opts = pycompat.byteskwargs(opts)
1780 if opts.get('edit') or opts.get('local') or opts.get('global'):
1780 if opts.get('edit') or opts.get('local') or opts.get('global'):
1781 if opts.get('local') and opts.get('global'):
1781 if opts.get('local') and opts.get('global'):
1782 raise error.Abort(_("can't use --local and --global together"))
1782 raise error.Abort(_("can't use --local and --global together"))
1783
1783
1784 if opts.get('local'):
1784 if opts.get('local'):
1785 if not repo:
1785 if not repo:
1786 raise error.Abort(_("can't use --local outside a repository"))
1786 raise error.Abort(_("can't use --local outside a repository"))
1787 paths = [repo.vfs.join('hgrc')]
1787 paths = [repo.vfs.join('hgrc')]
1788 elif opts.get('global'):
1788 elif opts.get('global'):
1789 paths = rcutil.systemrcpath()
1789 paths = rcutil.systemrcpath()
1790 else:
1790 else:
1791 paths = rcutil.userrcpath()
1791 paths = rcutil.userrcpath()
1792
1792
1793 for f in paths:
1793 for f in paths:
1794 if os.path.exists(f):
1794 if os.path.exists(f):
1795 break
1795 break
1796 else:
1796 else:
1797 if opts.get('global'):
1797 if opts.get('global'):
1798 samplehgrc = uimod.samplehgrcs['global']
1798 samplehgrc = uimod.samplehgrcs['global']
1799 elif opts.get('local'):
1799 elif opts.get('local'):
1800 samplehgrc = uimod.samplehgrcs['local']
1800 samplehgrc = uimod.samplehgrcs['local']
1801 else:
1801 else:
1802 samplehgrc = uimod.samplehgrcs['user']
1802 samplehgrc = uimod.samplehgrcs['user']
1803
1803
1804 f = paths[0]
1804 f = paths[0]
1805 fp = open(f, "wb")
1805 fp = open(f, "wb")
1806 fp.write(util.tonativeeol(samplehgrc))
1806 fp.write(util.tonativeeol(samplehgrc))
1807 fp.close()
1807 fp.close()
1808
1808
1809 editor = ui.geteditor()
1809 editor = ui.geteditor()
1810 ui.system("%s \"%s\"" % (editor, f),
1810 ui.system("%s \"%s\"" % (editor, f),
1811 onerr=error.Abort, errprefix=_("edit failed"),
1811 onerr=error.Abort, errprefix=_("edit failed"),
1812 blockedtag='config_edit')
1812 blockedtag='config_edit')
1813 return
1813 return
1814 ui.pager('config')
1814 ui.pager('config')
1815 fm = ui.formatter('config', opts)
1815 fm = ui.formatter('config', opts)
1816 for t, f in rcutil.rccomponents():
1816 for t, f in rcutil.rccomponents():
1817 if t == 'path':
1817 if t == 'path':
1818 ui.debug('read config from: %s\n' % f)
1818 ui.debug('read config from: %s\n' % f)
1819 elif t == 'items':
1819 elif t == 'items':
1820 for section, name, value, source in f:
1820 for section, name, value, source in f:
1821 ui.debug('set config by: %s\n' % source)
1821 ui.debug('set config by: %s\n' % source)
1822 else:
1822 else:
1823 raise error.ProgrammingError('unknown rctype: %s' % t)
1823 raise error.ProgrammingError('unknown rctype: %s' % t)
1824 untrusted = bool(opts.get('untrusted'))
1824 untrusted = bool(opts.get('untrusted'))
1825
1825
1826 selsections = selentries = []
1826 selsections = selentries = []
1827 if values:
1827 if values:
1828 selsections = [v for v in values if '.' not in v]
1828 selsections = [v for v in values if '.' not in v]
1829 selentries = [v for v in values if '.' in v]
1829 selentries = [v for v in values if '.' in v]
1830 uniquesel = (len(selentries) == 1 and not selsections)
1830 uniquesel = (len(selentries) == 1 and not selsections)
1831 selsections = set(selsections)
1831 selsections = set(selsections)
1832 selentries = set(selentries)
1832 selentries = set(selentries)
1833
1833
1834 matched = False
1834 matched = False
1835 for section, name, value in ui.walkconfig(untrusted=untrusted):
1835 for section, name, value in ui.walkconfig(untrusted=untrusted):
1836 source = ui.configsource(section, name, untrusted)
1836 source = ui.configsource(section, name, untrusted)
1837 value = pycompat.bytestr(value)
1837 value = pycompat.bytestr(value)
1838 if fm.isplain():
1838 if fm.isplain():
1839 source = source or 'none'
1839 source = source or 'none'
1840 value = value.replace('\n', '\\n')
1840 value = value.replace('\n', '\\n')
1841 entryname = section + '.' + name
1841 entryname = section + '.' + name
1842 if values and not (section in selsections or entryname in selentries):
1842 if values and not (section in selsections or entryname in selentries):
1843 continue
1843 continue
1844 fm.startitem()
1844 fm.startitem()
1845 fm.condwrite(ui.debugflag, 'source', '%s: ', source)
1845 fm.condwrite(ui.debugflag, 'source', '%s: ', source)
1846 if uniquesel:
1846 if uniquesel:
1847 fm.data(name=entryname)
1847 fm.data(name=entryname)
1848 fm.write('value', '%s\n', value)
1848 fm.write('value', '%s\n', value)
1849 else:
1849 else:
1850 fm.write('name value', '%s=%s\n', entryname, value)
1850 fm.write('name value', '%s=%s\n', entryname, value)
1851 matched = True
1851 matched = True
1852 fm.end()
1852 fm.end()
1853 if matched:
1853 if matched:
1854 return 0
1854 return 0
1855 return 1
1855 return 1
1856
1856
1857 @command('copy|cp',
1857 @command('copy|cp',
1858 [('A', 'after', None, _('record a copy that has already occurred')),
1858 [('A', 'after', None, _('record a copy that has already occurred')),
1859 ('f', 'force', None, _('forcibly copy over an existing managed file')),
1859 ('f', 'force', None, _('forcibly copy over an existing managed file')),
1860 ] + walkopts + dryrunopts,
1860 ] + walkopts + dryrunopts,
1861 _('[OPTION]... [SOURCE]... DEST'),
1861 _('[OPTION]... [SOURCE]... DEST'),
1862 helpcategory=command.CATEGORY_FILE_CONTENTS)
1862 helpcategory=command.CATEGORY_FILE_CONTENTS)
1863 def copy(ui, repo, *pats, **opts):
1863 def copy(ui, repo, *pats, **opts):
1864 """mark files as copied for the next commit
1864 """mark files as copied for the next commit
1865
1865
1866 Mark dest as having copies of source files. If dest is a
1866 Mark dest as having copies of source files. If dest is a
1867 directory, copies are put in that directory. If dest is a file,
1867 directory, copies are put in that directory. If dest is a file,
1868 the source must be a single file.
1868 the source must be a single file.
1869
1869
1870 By default, this command copies the contents of files as they
1870 By default, this command copies the contents of files as they
1871 exist in the working directory. If invoked with -A/--after, the
1871 exist in the working directory. If invoked with -A/--after, the
1872 operation is recorded, but no copying is performed.
1872 operation is recorded, but no copying is performed.
1873
1873
1874 This command takes effect with the next commit. To undo a copy
1874 This command takes effect with the next commit. To undo a copy
1875 before that, see :hg:`revert`.
1875 before that, see :hg:`revert`.
1876
1876
1877 Returns 0 on success, 1 if errors are encountered.
1877 Returns 0 on success, 1 if errors are encountered.
1878 """
1878 """
1879 opts = pycompat.byteskwargs(opts)
1879 opts = pycompat.byteskwargs(opts)
1880 with repo.wlock(False):
1880 with repo.wlock(False):
1881 return cmdutil.copy(ui, repo, pats, opts)
1881 return cmdutil.copy(ui, repo, pats, opts)
1882
1882
1883 @command(
1883 @command(
1884 'debugcommands', [], _('[COMMAND]'),
1884 'debugcommands', [], _('[COMMAND]'),
1885 helpcategory=command.CATEGORY_HELP,
1885 helpcategory=command.CATEGORY_HELP,
1886 norepo=True)
1886 norepo=True)
1887 def debugcommands(ui, cmd='', *args):
1887 def debugcommands(ui, cmd='', *args):
1888 """list all available commands and options"""
1888 """list all available commands and options"""
1889 for cmd, vals in sorted(table.iteritems()):
1889 for cmd, vals in sorted(table.iteritems()):
1890 cmd = cmd.split('|')[0]
1890 cmd = cmd.split('|')[0]
1891 opts = ', '.join([i[1] for i in vals[1]])
1891 opts = ', '.join([i[1] for i in vals[1]])
1892 ui.write('%s: %s\n' % (cmd, opts))
1892 ui.write('%s: %s\n' % (cmd, opts))
1893
1893
1894 @command('debugcomplete',
1894 @command('debugcomplete',
1895 [('o', 'options', None, _('show the command options'))],
1895 [('o', 'options', None, _('show the command options'))],
1896 _('[-o] CMD'),
1896 _('[-o] CMD'),
1897 helpcategory=command.CATEGORY_HELP,
1897 helpcategory=command.CATEGORY_HELP,
1898 norepo=True)
1898 norepo=True)
1899 def debugcomplete(ui, cmd='', **opts):
1899 def debugcomplete(ui, cmd='', **opts):
1900 """returns the completion list associated with the given command"""
1900 """returns the completion list associated with the given command"""
1901
1901
1902 if opts.get(r'options'):
1902 if opts.get(r'options'):
1903 options = []
1903 options = []
1904 otables = [globalopts]
1904 otables = [globalopts]
1905 if cmd:
1905 if cmd:
1906 aliases, entry = cmdutil.findcmd(cmd, table, False)
1906 aliases, entry = cmdutil.findcmd(cmd, table, False)
1907 otables.append(entry[1])
1907 otables.append(entry[1])
1908 for t in otables:
1908 for t in otables:
1909 for o in t:
1909 for o in t:
1910 if "(DEPRECATED)" in o[3]:
1910 if "(DEPRECATED)" in o[3]:
1911 continue
1911 continue
1912 if o[0]:
1912 if o[0]:
1913 options.append('-%s' % o[0])
1913 options.append('-%s' % o[0])
1914 options.append('--%s' % o[1])
1914 options.append('--%s' % o[1])
1915 ui.write("%s\n" % "\n".join(options))
1915 ui.write("%s\n" % "\n".join(options))
1916 return
1916 return
1917
1917
1918 cmdlist, unused_allcmds = cmdutil.findpossible(cmd, table)
1918 cmdlist, unused_allcmds = cmdutil.findpossible(cmd, table)
1919 if ui.verbose:
1919 if ui.verbose:
1920 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
1920 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
1921 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
1921 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
1922
1922
1923 @command('diff',
1923 @command('diff',
1924 [('r', 'rev', [], _('revision'), _('REV')),
1924 [('r', 'rev', [], _('revision'), _('REV')),
1925 ('c', 'change', '', _('change made by revision'), _('REV'))
1925 ('c', 'change', '', _('change made by revision'), _('REV'))
1926 ] + diffopts + diffopts2 + walkopts + subrepoopts,
1926 ] + diffopts + diffopts2 + walkopts + subrepoopts,
1927 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'),
1927 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'),
1928 helpcategory=command.CATEGORY_FILE_CONTENTS,
1928 helpcategory=command.CATEGORY_FILE_CONTENTS,
1929 helpbasic=True, inferrepo=True, intents={INTENT_READONLY})
1929 helpbasic=True, inferrepo=True, intents={INTENT_READONLY})
1930 def diff(ui, repo, *pats, **opts):
1930 def diff(ui, repo, *pats, **opts):
1931 """diff repository (or selected files)
1931 """diff repository (or selected files)
1932
1932
1933 Show differences between revisions for the specified files.
1933 Show differences between revisions for the specified files.
1934
1934
1935 Differences between files are shown using the unified diff format.
1935 Differences between files are shown using the unified diff format.
1936
1936
1937 .. note::
1937 .. note::
1938
1938
1939 :hg:`diff` may generate unexpected results for merges, as it will
1939 :hg:`diff` may generate unexpected results for merges, as it will
1940 default to comparing against the working directory's first
1940 default to comparing against the working directory's first
1941 parent changeset if no revisions are specified.
1941 parent changeset if no revisions are specified.
1942
1942
1943 When two revision arguments are given, then changes are shown
1943 When two revision arguments are given, then changes are shown
1944 between those revisions. If only one revision is specified then
1944 between those revisions. If only one revision is specified then
1945 that revision is compared to the working directory, and, when no
1945 that revision is compared to the working directory, and, when no
1946 revisions are specified, the working directory files are compared
1946 revisions are specified, the working directory files are compared
1947 to its first parent.
1947 to its first parent.
1948
1948
1949 Alternatively you can specify -c/--change with a revision to see
1949 Alternatively you can specify -c/--change with a revision to see
1950 the changes in that changeset relative to its first parent.
1950 the changes in that changeset relative to its first parent.
1951
1951
1952 Without the -a/--text option, diff will avoid generating diffs of
1952 Without the -a/--text option, diff will avoid generating diffs of
1953 files it detects as binary. With -a, diff will generate a diff
1953 files it detects as binary. With -a, diff will generate a diff
1954 anyway, probably with undesirable results.
1954 anyway, probably with undesirable results.
1955
1955
1956 Use the -g/--git option to generate diffs in the git extended diff
1956 Use the -g/--git option to generate diffs in the git extended diff
1957 format. For more information, read :hg:`help diffs`.
1957 format. For more information, read :hg:`help diffs`.
1958
1958
1959 .. container:: verbose
1959 .. container:: verbose
1960
1960
1961 Examples:
1961 Examples:
1962
1962
1963 - compare a file in the current working directory to its parent::
1963 - compare a file in the current working directory to its parent::
1964
1964
1965 hg diff foo.c
1965 hg diff foo.c
1966
1966
1967 - compare two historical versions of a directory, with rename info::
1967 - compare two historical versions of a directory, with rename info::
1968
1968
1969 hg diff --git -r 1.0:1.2 lib/
1969 hg diff --git -r 1.0:1.2 lib/
1970
1970
1971 - get change stats relative to the last change on some date::
1971 - get change stats relative to the last change on some date::
1972
1972
1973 hg diff --stat -r "date('may 2')"
1973 hg diff --stat -r "date('may 2')"
1974
1974
1975 - diff all newly-added files that contain a keyword::
1975 - diff all newly-added files that contain a keyword::
1976
1976
1977 hg diff "set:added() and grep(GNU)"
1977 hg diff "set:added() and grep(GNU)"
1978
1978
1979 - compare a revision and its parents::
1979 - compare a revision and its parents::
1980
1980
1981 hg diff -c 9353 # compare against first parent
1981 hg diff -c 9353 # compare against first parent
1982 hg diff -r 9353^:9353 # same using revset syntax
1982 hg diff -r 9353^:9353 # same using revset syntax
1983 hg diff -r 9353^2:9353 # compare against the second parent
1983 hg diff -r 9353^2:9353 # compare against the second parent
1984
1984
1985 Returns 0 on success.
1985 Returns 0 on success.
1986 """
1986 """
1987
1987
1988 opts = pycompat.byteskwargs(opts)
1988 opts = pycompat.byteskwargs(opts)
1989 revs = opts.get('rev')
1989 revs = opts.get('rev')
1990 change = opts.get('change')
1990 change = opts.get('change')
1991 stat = opts.get('stat')
1991 stat = opts.get('stat')
1992 reverse = opts.get('reverse')
1992 reverse = opts.get('reverse')
1993
1993
1994 if revs and change:
1994 if revs and change:
1995 msg = _('cannot specify --rev and --change at the same time')
1995 msg = _('cannot specify --rev and --change at the same time')
1996 raise error.Abort(msg)
1996 raise error.Abort(msg)
1997 elif change:
1997 elif change:
1998 repo = scmutil.unhidehashlikerevs(repo, [change], 'nowarn')
1998 repo = scmutil.unhidehashlikerevs(repo, [change], 'nowarn')
1999 ctx2 = scmutil.revsingle(repo, change, None)
1999 ctx2 = scmutil.revsingle(repo, change, None)
2000 ctx1 = ctx2.p1()
2000 ctx1 = ctx2.p1()
2001 else:
2001 else:
2002 repo = scmutil.unhidehashlikerevs(repo, revs, 'nowarn')
2002 repo = scmutil.unhidehashlikerevs(repo, revs, 'nowarn')
2003 ctx1, ctx2 = scmutil.revpair(repo, revs)
2003 ctx1, ctx2 = scmutil.revpair(repo, revs)
2004 node1, node2 = ctx1.node(), ctx2.node()
2004 node1, node2 = ctx1.node(), ctx2.node()
2005
2005
2006 if reverse:
2006 if reverse:
2007 node1, node2 = node2, node1
2007 node1, node2 = node2, node1
2008
2008
2009 diffopts = patch.diffallopts(ui, opts)
2009 diffopts = patch.diffallopts(ui, opts)
2010 m = scmutil.match(ctx2, pats, opts)
2010 m = scmutil.match(ctx2, pats, opts)
2011 m = repo.narrowmatch(m)
2011 m = repo.narrowmatch(m)
2012 ui.pager('diff')
2012 ui.pager('diff')
2013 logcmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
2013 logcmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
2014 listsubrepos=opts.get('subrepos'),
2014 listsubrepos=opts.get('subrepos'),
2015 root=opts.get('root'))
2015 root=opts.get('root'))
2016
2016
2017 @command('export',
2017 @command('export',
2018 [('B', 'bookmark', '',
2018 [('B', 'bookmark', '',
2019 _('export changes only reachable by given bookmark'), _('BOOKMARK')),
2019 _('export changes only reachable by given bookmark'), _('BOOKMARK')),
2020 ('o', 'output', '',
2020 ('o', 'output', '',
2021 _('print output to file with formatted name'), _('FORMAT')),
2021 _('print output to file with formatted name'), _('FORMAT')),
2022 ('', 'switch-parent', None, _('diff against the second parent')),
2022 ('', 'switch-parent', None, _('diff against the second parent')),
2023 ('r', 'rev', [], _('revisions to export'), _('REV')),
2023 ('r', 'rev', [], _('revisions to export'), _('REV')),
2024 ] + diffopts + formatteropts,
2024 ] + diffopts + formatteropts,
2025 _('[OPTION]... [-o OUTFILESPEC] [-r] [REV]...'),
2025 _('[OPTION]... [-o OUTFILESPEC] [-r] [REV]...'),
2026 helpcategory=command.CATEGORY_IMPORT_EXPORT,
2026 helpcategory=command.CATEGORY_IMPORT_EXPORT,
2027 helpbasic=True, intents={INTENT_READONLY})
2027 helpbasic=True, intents={INTENT_READONLY})
2028 def export(ui, repo, *changesets, **opts):
2028 def export(ui, repo, *changesets, **opts):
2029 """dump the header and diffs for one or more changesets
2029 """dump the header and diffs for one or more changesets
2030
2030
2031 Print the changeset header and diffs for one or more revisions.
2031 Print the changeset header and diffs for one or more revisions.
2032 If no revision is given, the parent of the working directory is used.
2032 If no revision is given, the parent of the working directory is used.
2033
2033
2034 The information shown in the changeset header is: author, date,
2034 The information shown in the changeset header is: author, date,
2035 branch name (if non-default), changeset hash, parent(s) and commit
2035 branch name (if non-default), changeset hash, parent(s) and commit
2036 comment.
2036 comment.
2037
2037
2038 .. note::
2038 .. note::
2039
2039
2040 :hg:`export` may generate unexpected diff output for merge
2040 :hg:`export` may generate unexpected diff output for merge
2041 changesets, as it will compare the merge changeset against its
2041 changesets, as it will compare the merge changeset against its
2042 first parent only.
2042 first parent only.
2043
2043
2044 Output may be to a file, in which case the name of the file is
2044 Output may be to a file, in which case the name of the file is
2045 given using a template string. See :hg:`help templates`. In addition
2045 given using a template string. See :hg:`help templates`. In addition
2046 to the common template keywords, the following formatting rules are
2046 to the common template keywords, the following formatting rules are
2047 supported:
2047 supported:
2048
2048
2049 :``%%``: literal "%" character
2049 :``%%``: literal "%" character
2050 :``%H``: changeset hash (40 hexadecimal digits)
2050 :``%H``: changeset hash (40 hexadecimal digits)
2051 :``%N``: number of patches being generated
2051 :``%N``: number of patches being generated
2052 :``%R``: changeset revision number
2052 :``%R``: changeset revision number
2053 :``%b``: basename of the exporting repository
2053 :``%b``: basename of the exporting repository
2054 :``%h``: short-form changeset hash (12 hexadecimal digits)
2054 :``%h``: short-form changeset hash (12 hexadecimal digits)
2055 :``%m``: first line of the commit message (only alphanumeric characters)
2055 :``%m``: first line of the commit message (only alphanumeric characters)
2056 :``%n``: zero-padded sequence number, starting at 1
2056 :``%n``: zero-padded sequence number, starting at 1
2057 :``%r``: zero-padded changeset revision number
2057 :``%r``: zero-padded changeset revision number
2058 :``\\``: literal "\\" character
2058 :``\\``: literal "\\" character
2059
2059
2060 Without the -a/--text option, export will avoid generating diffs
2060 Without the -a/--text option, export will avoid generating diffs
2061 of files it detects as binary. With -a, export will generate a
2061 of files it detects as binary. With -a, export will generate a
2062 diff anyway, probably with undesirable results.
2062 diff anyway, probably with undesirable results.
2063
2063
2064 With -B/--bookmark changesets reachable by the given bookmark are
2064 With -B/--bookmark changesets reachable by the given bookmark are
2065 selected.
2065 selected.
2066
2066
2067 Use the -g/--git option to generate diffs in the git extended diff
2067 Use the -g/--git option to generate diffs in the git extended diff
2068 format. See :hg:`help diffs` for more information.
2068 format. See :hg:`help diffs` for more information.
2069
2069
2070 With the --switch-parent option, the diff will be against the
2070 With the --switch-parent option, the diff will be against the
2071 second parent. It can be useful to review a merge.
2071 second parent. It can be useful to review a merge.
2072
2072
2073 .. container:: verbose
2073 .. container:: verbose
2074
2074
2075 Template:
2075 Template:
2076
2076
2077 The following keywords are supported in addition to the common template
2077 The following keywords are supported in addition to the common template
2078 keywords and functions. See also :hg:`help templates`.
2078 keywords and functions. See also :hg:`help templates`.
2079
2079
2080 :diff: String. Diff content.
2080 :diff: String. Diff content.
2081 :parents: List of strings. Parent nodes of the changeset.
2081 :parents: List of strings. Parent nodes of the changeset.
2082
2082
2083 Examples:
2083 Examples:
2084
2084
2085 - use export and import to transplant a bugfix to the current
2085 - use export and import to transplant a bugfix to the current
2086 branch::
2086 branch::
2087
2087
2088 hg export -r 9353 | hg import -
2088 hg export -r 9353 | hg import -
2089
2089
2090 - export all the changesets between two revisions to a file with
2090 - export all the changesets between two revisions to a file with
2091 rename information::
2091 rename information::
2092
2092
2093 hg export --git -r 123:150 > changes.txt
2093 hg export --git -r 123:150 > changes.txt
2094
2094
2095 - split outgoing changes into a series of patches with
2095 - split outgoing changes into a series of patches with
2096 descriptive names::
2096 descriptive names::
2097
2097
2098 hg export -r "outgoing()" -o "%n-%m.patch"
2098 hg export -r "outgoing()" -o "%n-%m.patch"
2099
2099
2100 Returns 0 on success.
2100 Returns 0 on success.
2101 """
2101 """
2102 opts = pycompat.byteskwargs(opts)
2102 opts = pycompat.byteskwargs(opts)
2103 bookmark = opts.get('bookmark')
2103 bookmark = opts.get('bookmark')
2104 changesets += tuple(opts.get('rev', []))
2104 changesets += tuple(opts.get('rev', []))
2105
2105
2106 if bookmark and changesets:
2106 if bookmark and changesets:
2107 raise error.Abort(_("-r and -B are mutually exclusive"))
2107 raise error.Abort(_("-r and -B are mutually exclusive"))
2108
2108
2109 if bookmark:
2109 if bookmark:
2110 if bookmark not in repo._bookmarks:
2110 if bookmark not in repo._bookmarks:
2111 raise error.Abort(_("bookmark '%s' not found") % bookmark)
2111 raise error.Abort(_("bookmark '%s' not found") % bookmark)
2112
2112
2113 revs = scmutil.bookmarkrevs(repo, bookmark)
2113 revs = scmutil.bookmarkrevs(repo, bookmark)
2114 else:
2114 else:
2115 if not changesets:
2115 if not changesets:
2116 changesets = ['.']
2116 changesets = ['.']
2117
2117
2118 repo = scmutil.unhidehashlikerevs(repo, changesets, 'nowarn')
2118 repo = scmutil.unhidehashlikerevs(repo, changesets, 'nowarn')
2119 revs = scmutil.revrange(repo, changesets)
2119 revs = scmutil.revrange(repo, changesets)
2120
2120
2121 if not revs:
2121 if not revs:
2122 raise error.Abort(_("export requires at least one changeset"))
2122 raise error.Abort(_("export requires at least one changeset"))
2123 if len(revs) > 1:
2123 if len(revs) > 1:
2124 ui.note(_('exporting patches:\n'))
2124 ui.note(_('exporting patches:\n'))
2125 else:
2125 else:
2126 ui.note(_('exporting patch:\n'))
2126 ui.note(_('exporting patch:\n'))
2127
2127
2128 fntemplate = opts.get('output')
2128 fntemplate = opts.get('output')
2129 if cmdutil.isstdiofilename(fntemplate):
2129 if cmdutil.isstdiofilename(fntemplate):
2130 fntemplate = ''
2130 fntemplate = ''
2131
2131
2132 if fntemplate:
2132 if fntemplate:
2133 fm = formatter.nullformatter(ui, 'export', opts)
2133 fm = formatter.nullformatter(ui, 'export', opts)
2134 else:
2134 else:
2135 ui.pager('export')
2135 ui.pager('export')
2136 fm = ui.formatter('export', opts)
2136 fm = ui.formatter('export', opts)
2137 with fm:
2137 with fm:
2138 cmdutil.export(repo, revs, fm, fntemplate=fntemplate,
2138 cmdutil.export(repo, revs, fm, fntemplate=fntemplate,
2139 switch_parent=opts.get('switch_parent'),
2139 switch_parent=opts.get('switch_parent'),
2140 opts=patch.diffallopts(ui, opts))
2140 opts=patch.diffallopts(ui, opts))
2141
2141
2142 @command('files',
2142 @command('files',
2143 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
2143 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
2144 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
2144 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
2145 ] + walkopts + formatteropts + subrepoopts,
2145 ] + walkopts + formatteropts + subrepoopts,
2146 _('[OPTION]... [FILE]...'),
2146 _('[OPTION]... [FILE]...'),
2147 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2147 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2148 intents={INTENT_READONLY})
2148 intents={INTENT_READONLY})
2149 def files(ui, repo, *pats, **opts):
2149 def files(ui, repo, *pats, **opts):
2150 """list tracked files
2150 """list tracked files
2151
2151
2152 Print files under Mercurial control in the working directory or
2152 Print files under Mercurial control in the working directory or
2153 specified revision for given files (excluding removed files).
2153 specified revision for given files (excluding removed files).
2154 Files can be specified as filenames or filesets.
2154 Files can be specified as filenames or filesets.
2155
2155
2156 If no files are given to match, this command prints the names
2156 If no files are given to match, this command prints the names
2157 of all files under Mercurial control.
2157 of all files under Mercurial control.
2158
2158
2159 .. container:: verbose
2159 .. container:: verbose
2160
2160
2161 Template:
2161 Template:
2162
2162
2163 The following keywords are supported in addition to the common template
2163 The following keywords are supported in addition to the common template
2164 keywords and functions. See also :hg:`help templates`.
2164 keywords and functions. See also :hg:`help templates`.
2165
2165
2166 :flags: String. Character denoting file's symlink and executable bits.
2166 :flags: String. Character denoting file's symlink and executable bits.
2167 :path: String. Repository-absolute path of the file.
2167 :path: String. Repository-absolute path of the file.
2168 :size: Integer. Size of the file in bytes.
2168 :size: Integer. Size of the file in bytes.
2169
2169
2170 Examples:
2170 Examples:
2171
2171
2172 - list all files under the current directory::
2172 - list all files under the current directory::
2173
2173
2174 hg files .
2174 hg files .
2175
2175
2176 - shows sizes and flags for current revision::
2176 - shows sizes and flags for current revision::
2177
2177
2178 hg files -vr .
2178 hg files -vr .
2179
2179
2180 - list all files named README::
2180 - list all files named README::
2181
2181
2182 hg files -I "**/README"
2182 hg files -I "**/README"
2183
2183
2184 - list all binary files::
2184 - list all binary files::
2185
2185
2186 hg files "set:binary()"
2186 hg files "set:binary()"
2187
2187
2188 - find files containing a regular expression::
2188 - find files containing a regular expression::
2189
2189
2190 hg files "set:grep('bob')"
2190 hg files "set:grep('bob')"
2191
2191
2192 - search tracked file contents with xargs and grep::
2192 - search tracked file contents with xargs and grep::
2193
2193
2194 hg files -0 | xargs -0 grep foo
2194 hg files -0 | xargs -0 grep foo
2195
2195
2196 See :hg:`help patterns` and :hg:`help filesets` for more information
2196 See :hg:`help patterns` and :hg:`help filesets` for more information
2197 on specifying file patterns.
2197 on specifying file patterns.
2198
2198
2199 Returns 0 if a match is found, 1 otherwise.
2199 Returns 0 if a match is found, 1 otherwise.
2200
2200
2201 """
2201 """
2202
2202
2203 opts = pycompat.byteskwargs(opts)
2203 opts = pycompat.byteskwargs(opts)
2204 rev = opts.get('rev')
2204 rev = opts.get('rev')
2205 if rev:
2205 if rev:
2206 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
2206 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
2207 ctx = scmutil.revsingle(repo, rev, None)
2207 ctx = scmutil.revsingle(repo, rev, None)
2208
2208
2209 end = '\n'
2209 end = '\n'
2210 if opts.get('print0'):
2210 if opts.get('print0'):
2211 end = '\0'
2211 end = '\0'
2212 fmt = '%s' + end
2212 fmt = '%s' + end
2213
2213
2214 m = scmutil.match(ctx, pats, opts)
2214 m = scmutil.match(ctx, pats, opts)
2215 ui.pager('files')
2215 ui.pager('files')
2216 uipathfn = scmutil.getuipathfn(ctx.repo(), legacyrelativevalue=True)
2216 with ui.formatter('files', opts) as fm:
2217 with ui.formatter('files', opts) as fm:
2217 return cmdutil.files(ui, ctx, m, fm, fmt, opts.get('subrepos'))
2218 return cmdutil.files(ui, ctx, m, uipathfn, fm, fmt,
2219 opts.get('subrepos'))
2218
2220
2219 @command(
2221 @command(
2220 'forget',
2222 'forget',
2221 [('i', 'interactive', None, _('use interactive mode')),
2223 [('i', 'interactive', None, _('use interactive mode')),
2222 ] + walkopts + dryrunopts,
2224 ] + walkopts + dryrunopts,
2223 _('[OPTION]... FILE...'),
2225 _('[OPTION]... FILE...'),
2224 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2226 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2225 helpbasic=True, inferrepo=True)
2227 helpbasic=True, inferrepo=True)
2226 def forget(ui, repo, *pats, **opts):
2228 def forget(ui, repo, *pats, **opts):
2227 """forget the specified files on the next commit
2229 """forget the specified files on the next commit
2228
2230
2229 Mark the specified files so they will no longer be tracked
2231 Mark the specified files so they will no longer be tracked
2230 after the next commit.
2232 after the next commit.
2231
2233
2232 This only removes files from the current branch, not from the
2234 This only removes files from the current branch, not from the
2233 entire project history, and it does not delete them from the
2235 entire project history, and it does not delete them from the
2234 working directory.
2236 working directory.
2235
2237
2236 To delete the file from the working directory, see :hg:`remove`.
2238 To delete the file from the working directory, see :hg:`remove`.
2237
2239
2238 To undo a forget before the next commit, see :hg:`add`.
2240 To undo a forget before the next commit, see :hg:`add`.
2239
2241
2240 .. container:: verbose
2242 .. container:: verbose
2241
2243
2242 Examples:
2244 Examples:
2243
2245
2244 - forget newly-added binary files::
2246 - forget newly-added binary files::
2245
2247
2246 hg forget "set:added() and binary()"
2248 hg forget "set:added() and binary()"
2247
2249
2248 - forget files that would be excluded by .hgignore::
2250 - forget files that would be excluded by .hgignore::
2249
2251
2250 hg forget "set:hgignore()"
2252 hg forget "set:hgignore()"
2251
2253
2252 Returns 0 on success.
2254 Returns 0 on success.
2253 """
2255 """
2254
2256
2255 opts = pycompat.byteskwargs(opts)
2257 opts = pycompat.byteskwargs(opts)
2256 if not pats:
2258 if not pats:
2257 raise error.Abort(_('no files specified'))
2259 raise error.Abort(_('no files specified'))
2258
2260
2259 m = scmutil.match(repo[None], pats, opts)
2261 m = scmutil.match(repo[None], pats, opts)
2260 dryrun, interactive = opts.get('dry_run'), opts.get('interactive')
2262 dryrun, interactive = opts.get('dry_run'), opts.get('interactive')
2261 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
2263 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
2262 rejected = cmdutil.forget(ui, repo, m, prefix="", uipathfn=uipathfn,
2264 rejected = cmdutil.forget(ui, repo, m, prefix="", uipathfn=uipathfn,
2263 explicitonly=False, dryrun=dryrun,
2265 explicitonly=False, dryrun=dryrun,
2264 interactive=interactive)[0]
2266 interactive=interactive)[0]
2265 return rejected and 1 or 0
2267 return rejected and 1 or 0
2266
2268
2267 @command(
2269 @command(
2268 'graft',
2270 'graft',
2269 [('r', 'rev', [], _('revisions to graft'), _('REV')),
2271 [('r', 'rev', [], _('revisions to graft'), _('REV')),
2270 ('', 'base', '',
2272 ('', 'base', '',
2271 _('base revision when doing the graft merge (ADVANCED)'), _('REV')),
2273 _('base revision when doing the graft merge (ADVANCED)'), _('REV')),
2272 ('c', 'continue', False, _('resume interrupted graft')),
2274 ('c', 'continue', False, _('resume interrupted graft')),
2273 ('', 'stop', False, _('stop interrupted graft')),
2275 ('', 'stop', False, _('stop interrupted graft')),
2274 ('', 'abort', False, _('abort interrupted graft')),
2276 ('', 'abort', False, _('abort interrupted graft')),
2275 ('e', 'edit', False, _('invoke editor on commit messages')),
2277 ('e', 'edit', False, _('invoke editor on commit messages')),
2276 ('', 'log', None, _('append graft info to log message')),
2278 ('', 'log', None, _('append graft info to log message')),
2277 ('', 'no-commit', None,
2279 ('', 'no-commit', None,
2278 _("don't commit, just apply the changes in working directory")),
2280 _("don't commit, just apply the changes in working directory")),
2279 ('f', 'force', False, _('force graft')),
2281 ('f', 'force', False, _('force graft')),
2280 ('D', 'currentdate', False,
2282 ('D', 'currentdate', False,
2281 _('record the current date as commit date')),
2283 _('record the current date as commit date')),
2282 ('U', 'currentuser', False,
2284 ('U', 'currentuser', False,
2283 _('record the current user as committer'))]
2285 _('record the current user as committer'))]
2284 + commitopts2 + mergetoolopts + dryrunopts,
2286 + commitopts2 + mergetoolopts + dryrunopts,
2285 _('[OPTION]... [-r REV]... REV...'),
2287 _('[OPTION]... [-r REV]... REV...'),
2286 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT)
2288 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT)
2287 def graft(ui, repo, *revs, **opts):
2289 def graft(ui, repo, *revs, **opts):
2288 '''copy changes from other branches onto the current branch
2290 '''copy changes from other branches onto the current branch
2289
2291
2290 This command uses Mercurial's merge logic to copy individual
2292 This command uses Mercurial's merge logic to copy individual
2291 changes from other branches without merging branches in the
2293 changes from other branches without merging branches in the
2292 history graph. This is sometimes known as 'backporting' or
2294 history graph. This is sometimes known as 'backporting' or
2293 'cherry-picking'. By default, graft will copy user, date, and
2295 'cherry-picking'. By default, graft will copy user, date, and
2294 description from the source changesets.
2296 description from the source changesets.
2295
2297
2296 Changesets that are ancestors of the current revision, that have
2298 Changesets that are ancestors of the current revision, that have
2297 already been grafted, or that are merges will be skipped.
2299 already been grafted, or that are merges will be skipped.
2298
2300
2299 If --log is specified, log messages will have a comment appended
2301 If --log is specified, log messages will have a comment appended
2300 of the form::
2302 of the form::
2301
2303
2302 (grafted from CHANGESETHASH)
2304 (grafted from CHANGESETHASH)
2303
2305
2304 If --force is specified, revisions will be grafted even if they
2306 If --force is specified, revisions will be grafted even if they
2305 are already ancestors of, or have been grafted to, the destination.
2307 are already ancestors of, or have been grafted to, the destination.
2306 This is useful when the revisions have since been backed out.
2308 This is useful when the revisions have since been backed out.
2307
2309
2308 If a graft merge results in conflicts, the graft process is
2310 If a graft merge results in conflicts, the graft process is
2309 interrupted so that the current merge can be manually resolved.
2311 interrupted so that the current merge can be manually resolved.
2310 Once all conflicts are addressed, the graft process can be
2312 Once all conflicts are addressed, the graft process can be
2311 continued with the -c/--continue option.
2313 continued with the -c/--continue option.
2312
2314
2313 The -c/--continue option reapplies all the earlier options.
2315 The -c/--continue option reapplies all the earlier options.
2314
2316
2315 .. container:: verbose
2317 .. container:: verbose
2316
2318
2317 The --base option exposes more of how graft internally uses merge with a
2319 The --base option exposes more of how graft internally uses merge with a
2318 custom base revision. --base can be used to specify another ancestor than
2320 custom base revision. --base can be used to specify another ancestor than
2319 the first and only parent.
2321 the first and only parent.
2320
2322
2321 The command::
2323 The command::
2322
2324
2323 hg graft -r 345 --base 234
2325 hg graft -r 345 --base 234
2324
2326
2325 is thus pretty much the same as::
2327 is thus pretty much the same as::
2326
2328
2327 hg diff -r 234 -r 345 | hg import
2329 hg diff -r 234 -r 345 | hg import
2328
2330
2329 but using merge to resolve conflicts and track moved files.
2331 but using merge to resolve conflicts and track moved files.
2330
2332
2331 The result of a merge can thus be backported as a single commit by
2333 The result of a merge can thus be backported as a single commit by
2332 specifying one of the merge parents as base, and thus effectively
2334 specifying one of the merge parents as base, and thus effectively
2333 grafting the changes from the other side.
2335 grafting the changes from the other side.
2334
2336
2335 It is also possible to collapse multiple changesets and clean up history
2337 It is also possible to collapse multiple changesets and clean up history
2336 by specifying another ancestor as base, much like rebase --collapse
2338 by specifying another ancestor as base, much like rebase --collapse
2337 --keep.
2339 --keep.
2338
2340
2339 The commit message can be tweaked after the fact using commit --amend .
2341 The commit message can be tweaked after the fact using commit --amend .
2340
2342
2341 For using non-ancestors as the base to backout changes, see the backout
2343 For using non-ancestors as the base to backout changes, see the backout
2342 command and the hidden --parent option.
2344 command and the hidden --parent option.
2343
2345
2344 .. container:: verbose
2346 .. container:: verbose
2345
2347
2346 Examples:
2348 Examples:
2347
2349
2348 - copy a single change to the stable branch and edit its description::
2350 - copy a single change to the stable branch and edit its description::
2349
2351
2350 hg update stable
2352 hg update stable
2351 hg graft --edit 9393
2353 hg graft --edit 9393
2352
2354
2353 - graft a range of changesets with one exception, updating dates::
2355 - graft a range of changesets with one exception, updating dates::
2354
2356
2355 hg graft -D "2085::2093 and not 2091"
2357 hg graft -D "2085::2093 and not 2091"
2356
2358
2357 - continue a graft after resolving conflicts::
2359 - continue a graft after resolving conflicts::
2358
2360
2359 hg graft -c
2361 hg graft -c
2360
2362
2361 - show the source of a grafted changeset::
2363 - show the source of a grafted changeset::
2362
2364
2363 hg log --debug -r .
2365 hg log --debug -r .
2364
2366
2365 - show revisions sorted by date::
2367 - show revisions sorted by date::
2366
2368
2367 hg log -r "sort(all(), date)"
2369 hg log -r "sort(all(), date)"
2368
2370
2369 - backport the result of a merge as a single commit::
2371 - backport the result of a merge as a single commit::
2370
2372
2371 hg graft -r 123 --base 123^
2373 hg graft -r 123 --base 123^
2372
2374
2373 - land a feature branch as one changeset::
2375 - land a feature branch as one changeset::
2374
2376
2375 hg up -cr default
2377 hg up -cr default
2376 hg graft -r featureX --base "ancestor('featureX', 'default')"
2378 hg graft -r featureX --base "ancestor('featureX', 'default')"
2377
2379
2378 See :hg:`help revisions` for more about specifying revisions.
2380 See :hg:`help revisions` for more about specifying revisions.
2379
2381
2380 Returns 0 on successful completion.
2382 Returns 0 on successful completion.
2381 '''
2383 '''
2382 with repo.wlock():
2384 with repo.wlock():
2383 return _dograft(ui, repo, *revs, **opts)
2385 return _dograft(ui, repo, *revs, **opts)
2384
2386
2385 def _dograft(ui, repo, *revs, **opts):
2387 def _dograft(ui, repo, *revs, **opts):
2386 opts = pycompat.byteskwargs(opts)
2388 opts = pycompat.byteskwargs(opts)
2387 if revs and opts.get('rev'):
2389 if revs and opts.get('rev'):
2388 ui.warn(_('warning: inconsistent use of --rev might give unexpected '
2390 ui.warn(_('warning: inconsistent use of --rev might give unexpected '
2389 'revision ordering!\n'))
2391 'revision ordering!\n'))
2390
2392
2391 revs = list(revs)
2393 revs = list(revs)
2392 revs.extend(opts.get('rev'))
2394 revs.extend(opts.get('rev'))
2393 basectx = None
2395 basectx = None
2394 if opts.get('base'):
2396 if opts.get('base'):
2395 basectx = scmutil.revsingle(repo, opts['base'], None)
2397 basectx = scmutil.revsingle(repo, opts['base'], None)
2396 # a dict of data to be stored in state file
2398 # a dict of data to be stored in state file
2397 statedata = {}
2399 statedata = {}
2398 # list of new nodes created by ongoing graft
2400 # list of new nodes created by ongoing graft
2399 statedata['newnodes'] = []
2401 statedata['newnodes'] = []
2400
2402
2401 if opts.get('user') and opts.get('currentuser'):
2403 if opts.get('user') and opts.get('currentuser'):
2402 raise error.Abort(_('--user and --currentuser are mutually exclusive'))
2404 raise error.Abort(_('--user and --currentuser are mutually exclusive'))
2403 if opts.get('date') and opts.get('currentdate'):
2405 if opts.get('date') and opts.get('currentdate'):
2404 raise error.Abort(_('--date and --currentdate are mutually exclusive'))
2406 raise error.Abort(_('--date and --currentdate are mutually exclusive'))
2405 if not opts.get('user') and opts.get('currentuser'):
2407 if not opts.get('user') and opts.get('currentuser'):
2406 opts['user'] = ui.username()
2408 opts['user'] = ui.username()
2407 if not opts.get('date') and opts.get('currentdate'):
2409 if not opts.get('date') and opts.get('currentdate'):
2408 opts['date'] = "%d %d" % dateutil.makedate()
2410 opts['date'] = "%d %d" % dateutil.makedate()
2409
2411
2410 editor = cmdutil.getcommiteditor(editform='graft',
2412 editor = cmdutil.getcommiteditor(editform='graft',
2411 **pycompat.strkwargs(opts))
2413 **pycompat.strkwargs(opts))
2412
2414
2413 cont = False
2415 cont = False
2414 if opts.get('no_commit'):
2416 if opts.get('no_commit'):
2415 if opts.get('edit'):
2417 if opts.get('edit'):
2416 raise error.Abort(_("cannot specify --no-commit and "
2418 raise error.Abort(_("cannot specify --no-commit and "
2417 "--edit together"))
2419 "--edit together"))
2418 if opts.get('currentuser'):
2420 if opts.get('currentuser'):
2419 raise error.Abort(_("cannot specify --no-commit and "
2421 raise error.Abort(_("cannot specify --no-commit and "
2420 "--currentuser together"))
2422 "--currentuser together"))
2421 if opts.get('currentdate'):
2423 if opts.get('currentdate'):
2422 raise error.Abort(_("cannot specify --no-commit and "
2424 raise error.Abort(_("cannot specify --no-commit and "
2423 "--currentdate together"))
2425 "--currentdate together"))
2424 if opts.get('log'):
2426 if opts.get('log'):
2425 raise error.Abort(_("cannot specify --no-commit and "
2427 raise error.Abort(_("cannot specify --no-commit and "
2426 "--log together"))
2428 "--log together"))
2427
2429
2428 graftstate = statemod.cmdstate(repo, 'graftstate')
2430 graftstate = statemod.cmdstate(repo, 'graftstate')
2429
2431
2430 if opts.get('stop'):
2432 if opts.get('stop'):
2431 if opts.get('continue'):
2433 if opts.get('continue'):
2432 raise error.Abort(_("cannot use '--continue' and "
2434 raise error.Abort(_("cannot use '--continue' and "
2433 "'--stop' together"))
2435 "'--stop' together"))
2434 if opts.get('abort'):
2436 if opts.get('abort'):
2435 raise error.Abort(_("cannot use '--abort' and '--stop' together"))
2437 raise error.Abort(_("cannot use '--abort' and '--stop' together"))
2436
2438
2437 if any((opts.get('edit'), opts.get('log'), opts.get('user'),
2439 if any((opts.get('edit'), opts.get('log'), opts.get('user'),
2438 opts.get('date'), opts.get('currentdate'),
2440 opts.get('date'), opts.get('currentdate'),
2439 opts.get('currentuser'), opts.get('rev'))):
2441 opts.get('currentuser'), opts.get('rev'))):
2440 raise error.Abort(_("cannot specify any other flag with '--stop'"))
2442 raise error.Abort(_("cannot specify any other flag with '--stop'"))
2441 return _stopgraft(ui, repo, graftstate)
2443 return _stopgraft(ui, repo, graftstate)
2442 elif opts.get('abort'):
2444 elif opts.get('abort'):
2443 if opts.get('continue'):
2445 if opts.get('continue'):
2444 raise error.Abort(_("cannot use '--continue' and "
2446 raise error.Abort(_("cannot use '--continue' and "
2445 "'--abort' together"))
2447 "'--abort' together"))
2446 if any((opts.get('edit'), opts.get('log'), opts.get('user'),
2448 if any((opts.get('edit'), opts.get('log'), opts.get('user'),
2447 opts.get('date'), opts.get('currentdate'),
2449 opts.get('date'), opts.get('currentdate'),
2448 opts.get('currentuser'), opts.get('rev'))):
2450 opts.get('currentuser'), opts.get('rev'))):
2449 raise error.Abort(_("cannot specify any other flag with '--abort'"))
2451 raise error.Abort(_("cannot specify any other flag with '--abort'"))
2450
2452
2451 return _abortgraft(ui, repo, graftstate)
2453 return _abortgraft(ui, repo, graftstate)
2452 elif opts.get('continue'):
2454 elif opts.get('continue'):
2453 cont = True
2455 cont = True
2454 if revs:
2456 if revs:
2455 raise error.Abort(_("can't specify --continue and revisions"))
2457 raise error.Abort(_("can't specify --continue and revisions"))
2456 # read in unfinished revisions
2458 # read in unfinished revisions
2457 if graftstate.exists():
2459 if graftstate.exists():
2458 statedata = _readgraftstate(repo, graftstate)
2460 statedata = _readgraftstate(repo, graftstate)
2459 if statedata.get('date'):
2461 if statedata.get('date'):
2460 opts['date'] = statedata['date']
2462 opts['date'] = statedata['date']
2461 if statedata.get('user'):
2463 if statedata.get('user'):
2462 opts['user'] = statedata['user']
2464 opts['user'] = statedata['user']
2463 if statedata.get('log'):
2465 if statedata.get('log'):
2464 opts['log'] = True
2466 opts['log'] = True
2465 if statedata.get('no_commit'):
2467 if statedata.get('no_commit'):
2466 opts['no_commit'] = statedata.get('no_commit')
2468 opts['no_commit'] = statedata.get('no_commit')
2467 nodes = statedata['nodes']
2469 nodes = statedata['nodes']
2468 revs = [repo[node].rev() for node in nodes]
2470 revs = [repo[node].rev() for node in nodes]
2469 else:
2471 else:
2470 cmdutil.wrongtooltocontinue(repo, _('graft'))
2472 cmdutil.wrongtooltocontinue(repo, _('graft'))
2471 else:
2473 else:
2472 if not revs:
2474 if not revs:
2473 raise error.Abort(_('no revisions specified'))
2475 raise error.Abort(_('no revisions specified'))
2474 cmdutil.checkunfinished(repo)
2476 cmdutil.checkunfinished(repo)
2475 cmdutil.bailifchanged(repo)
2477 cmdutil.bailifchanged(repo)
2476 revs = scmutil.revrange(repo, revs)
2478 revs = scmutil.revrange(repo, revs)
2477
2479
2478 skipped = set()
2480 skipped = set()
2479 if basectx is None:
2481 if basectx is None:
2480 # check for merges
2482 # check for merges
2481 for rev in repo.revs('%ld and merge()', revs):
2483 for rev in repo.revs('%ld and merge()', revs):
2482 ui.warn(_('skipping ungraftable merge revision %d\n') % rev)
2484 ui.warn(_('skipping ungraftable merge revision %d\n') % rev)
2483 skipped.add(rev)
2485 skipped.add(rev)
2484 revs = [r for r in revs if r not in skipped]
2486 revs = [r for r in revs if r not in skipped]
2485 if not revs:
2487 if not revs:
2486 return -1
2488 return -1
2487 if basectx is not None and len(revs) != 1:
2489 if basectx is not None and len(revs) != 1:
2488 raise error.Abort(_('only one revision allowed with --base '))
2490 raise error.Abort(_('only one revision allowed with --base '))
2489
2491
2490 # Don't check in the --continue case, in effect retaining --force across
2492 # Don't check in the --continue case, in effect retaining --force across
2491 # --continues. That's because without --force, any revisions we decided to
2493 # --continues. That's because without --force, any revisions we decided to
2492 # skip would have been filtered out here, so they wouldn't have made their
2494 # skip would have been filtered out here, so they wouldn't have made their
2493 # way to the graftstate. With --force, any revisions we would have otherwise
2495 # way to the graftstate. With --force, any revisions we would have otherwise
2494 # skipped would not have been filtered out, and if they hadn't been applied
2496 # skipped would not have been filtered out, and if they hadn't been applied
2495 # already, they'd have been in the graftstate.
2497 # already, they'd have been in the graftstate.
2496 if not (cont or opts.get('force')) and basectx is None:
2498 if not (cont or opts.get('force')) and basectx is None:
2497 # check for ancestors of dest branch
2499 # check for ancestors of dest branch
2498 crev = repo['.'].rev()
2500 crev = repo['.'].rev()
2499 ancestors = repo.changelog.ancestors([crev], inclusive=True)
2501 ancestors = repo.changelog.ancestors([crev], inclusive=True)
2500 # XXX make this lazy in the future
2502 # XXX make this lazy in the future
2501 # don't mutate while iterating, create a copy
2503 # don't mutate while iterating, create a copy
2502 for rev in list(revs):
2504 for rev in list(revs):
2503 if rev in ancestors:
2505 if rev in ancestors:
2504 ui.warn(_('skipping ancestor revision %d:%s\n') %
2506 ui.warn(_('skipping ancestor revision %d:%s\n') %
2505 (rev, repo[rev]))
2507 (rev, repo[rev]))
2506 # XXX remove on list is slow
2508 # XXX remove on list is slow
2507 revs.remove(rev)
2509 revs.remove(rev)
2508 if not revs:
2510 if not revs:
2509 return -1
2511 return -1
2510
2512
2511 # analyze revs for earlier grafts
2513 # analyze revs for earlier grafts
2512 ids = {}
2514 ids = {}
2513 for ctx in repo.set("%ld", revs):
2515 for ctx in repo.set("%ld", revs):
2514 ids[ctx.hex()] = ctx.rev()
2516 ids[ctx.hex()] = ctx.rev()
2515 n = ctx.extra().get('source')
2517 n = ctx.extra().get('source')
2516 if n:
2518 if n:
2517 ids[n] = ctx.rev()
2519 ids[n] = ctx.rev()
2518
2520
2519 # check ancestors for earlier grafts
2521 # check ancestors for earlier grafts
2520 ui.debug('scanning for duplicate grafts\n')
2522 ui.debug('scanning for duplicate grafts\n')
2521
2523
2522 # The only changesets we can be sure doesn't contain grafts of any
2524 # The only changesets we can be sure doesn't contain grafts of any
2523 # revs, are the ones that are common ancestors of *all* revs:
2525 # revs, are the ones that are common ancestors of *all* revs:
2524 for rev in repo.revs('only(%d,ancestor(%ld))', crev, revs):
2526 for rev in repo.revs('only(%d,ancestor(%ld))', crev, revs):
2525 ctx = repo[rev]
2527 ctx = repo[rev]
2526 n = ctx.extra().get('source')
2528 n = ctx.extra().get('source')
2527 if n in ids:
2529 if n in ids:
2528 try:
2530 try:
2529 r = repo[n].rev()
2531 r = repo[n].rev()
2530 except error.RepoLookupError:
2532 except error.RepoLookupError:
2531 r = None
2533 r = None
2532 if r in revs:
2534 if r in revs:
2533 ui.warn(_('skipping revision %d:%s '
2535 ui.warn(_('skipping revision %d:%s '
2534 '(already grafted to %d:%s)\n')
2536 '(already grafted to %d:%s)\n')
2535 % (r, repo[r], rev, ctx))
2537 % (r, repo[r], rev, ctx))
2536 revs.remove(r)
2538 revs.remove(r)
2537 elif ids[n] in revs:
2539 elif ids[n] in revs:
2538 if r is None:
2540 if r is None:
2539 ui.warn(_('skipping already grafted revision %d:%s '
2541 ui.warn(_('skipping already grafted revision %d:%s '
2540 '(%d:%s also has unknown origin %s)\n')
2542 '(%d:%s also has unknown origin %s)\n')
2541 % (ids[n], repo[ids[n]], rev, ctx, n[:12]))
2543 % (ids[n], repo[ids[n]], rev, ctx, n[:12]))
2542 else:
2544 else:
2543 ui.warn(_('skipping already grafted revision %d:%s '
2545 ui.warn(_('skipping already grafted revision %d:%s '
2544 '(%d:%s also has origin %d:%s)\n')
2546 '(%d:%s also has origin %d:%s)\n')
2545 % (ids[n], repo[ids[n]], rev, ctx, r, n[:12]))
2547 % (ids[n], repo[ids[n]], rev, ctx, r, n[:12]))
2546 revs.remove(ids[n])
2548 revs.remove(ids[n])
2547 elif ctx.hex() in ids:
2549 elif ctx.hex() in ids:
2548 r = ids[ctx.hex()]
2550 r = ids[ctx.hex()]
2549 if r in revs:
2551 if r in revs:
2550 ui.warn(_('skipping already grafted revision %d:%s '
2552 ui.warn(_('skipping already grafted revision %d:%s '
2551 '(was grafted from %d:%s)\n') %
2553 '(was grafted from %d:%s)\n') %
2552 (r, repo[r], rev, ctx))
2554 (r, repo[r], rev, ctx))
2553 revs.remove(r)
2555 revs.remove(r)
2554 if not revs:
2556 if not revs:
2555 return -1
2557 return -1
2556
2558
2557 if opts.get('no_commit'):
2559 if opts.get('no_commit'):
2558 statedata['no_commit'] = True
2560 statedata['no_commit'] = True
2559 for pos, ctx in enumerate(repo.set("%ld", revs)):
2561 for pos, ctx in enumerate(repo.set("%ld", revs)):
2560 desc = '%d:%s "%s"' % (ctx.rev(), ctx,
2562 desc = '%d:%s "%s"' % (ctx.rev(), ctx,
2561 ctx.description().split('\n', 1)[0])
2563 ctx.description().split('\n', 1)[0])
2562 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
2564 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
2563 if names:
2565 if names:
2564 desc += ' (%s)' % ' '.join(names)
2566 desc += ' (%s)' % ' '.join(names)
2565 ui.status(_('grafting %s\n') % desc)
2567 ui.status(_('grafting %s\n') % desc)
2566 if opts.get('dry_run'):
2568 if opts.get('dry_run'):
2567 continue
2569 continue
2568
2570
2569 source = ctx.extra().get('source')
2571 source = ctx.extra().get('source')
2570 extra = {}
2572 extra = {}
2571 if source:
2573 if source:
2572 extra['source'] = source
2574 extra['source'] = source
2573 extra['intermediate-source'] = ctx.hex()
2575 extra['intermediate-source'] = ctx.hex()
2574 else:
2576 else:
2575 extra['source'] = ctx.hex()
2577 extra['source'] = ctx.hex()
2576 user = ctx.user()
2578 user = ctx.user()
2577 if opts.get('user'):
2579 if opts.get('user'):
2578 user = opts['user']
2580 user = opts['user']
2579 statedata['user'] = user
2581 statedata['user'] = user
2580 date = ctx.date()
2582 date = ctx.date()
2581 if opts.get('date'):
2583 if opts.get('date'):
2582 date = opts['date']
2584 date = opts['date']
2583 statedata['date'] = date
2585 statedata['date'] = date
2584 message = ctx.description()
2586 message = ctx.description()
2585 if opts.get('log'):
2587 if opts.get('log'):
2586 message += '\n(grafted from %s)' % ctx.hex()
2588 message += '\n(grafted from %s)' % ctx.hex()
2587 statedata['log'] = True
2589 statedata['log'] = True
2588
2590
2589 # we don't merge the first commit when continuing
2591 # we don't merge the first commit when continuing
2590 if not cont:
2592 if not cont:
2591 # perform the graft merge with p1(rev) as 'ancestor'
2593 # perform the graft merge with p1(rev) as 'ancestor'
2592 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
2594 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
2593 base = ctx.p1() if basectx is None else basectx
2595 base = ctx.p1() if basectx is None else basectx
2594 with ui.configoverride(overrides, 'graft'):
2596 with ui.configoverride(overrides, 'graft'):
2595 stats = mergemod.graft(repo, ctx, base, ['local', 'graft'])
2597 stats = mergemod.graft(repo, ctx, base, ['local', 'graft'])
2596 # report any conflicts
2598 # report any conflicts
2597 if stats.unresolvedcount > 0:
2599 if stats.unresolvedcount > 0:
2598 # write out state for --continue
2600 # write out state for --continue
2599 nodes = [repo[rev].hex() for rev in revs[pos:]]
2601 nodes = [repo[rev].hex() for rev in revs[pos:]]
2600 statedata['nodes'] = nodes
2602 statedata['nodes'] = nodes
2601 stateversion = 1
2603 stateversion = 1
2602 graftstate.save(stateversion, statedata)
2604 graftstate.save(stateversion, statedata)
2603 hint = _("use 'hg resolve' and 'hg graft --continue'")
2605 hint = _("use 'hg resolve' and 'hg graft --continue'")
2604 raise error.Abort(
2606 raise error.Abort(
2605 _("unresolved conflicts, can't continue"),
2607 _("unresolved conflicts, can't continue"),
2606 hint=hint)
2608 hint=hint)
2607 else:
2609 else:
2608 cont = False
2610 cont = False
2609
2611
2610 # commit if --no-commit is false
2612 # commit if --no-commit is false
2611 if not opts.get('no_commit'):
2613 if not opts.get('no_commit'):
2612 node = repo.commit(text=message, user=user, date=date, extra=extra,
2614 node = repo.commit(text=message, user=user, date=date, extra=extra,
2613 editor=editor)
2615 editor=editor)
2614 if node is None:
2616 if node is None:
2615 ui.warn(
2617 ui.warn(
2616 _('note: graft of %d:%s created no changes to commit\n') %
2618 _('note: graft of %d:%s created no changes to commit\n') %
2617 (ctx.rev(), ctx))
2619 (ctx.rev(), ctx))
2618 # checking that newnodes exist because old state files won't have it
2620 # checking that newnodes exist because old state files won't have it
2619 elif statedata.get('newnodes') is not None:
2621 elif statedata.get('newnodes') is not None:
2620 statedata['newnodes'].append(node)
2622 statedata['newnodes'].append(node)
2621
2623
2622 # remove state when we complete successfully
2624 # remove state when we complete successfully
2623 if not opts.get('dry_run'):
2625 if not opts.get('dry_run'):
2624 graftstate.delete()
2626 graftstate.delete()
2625
2627
2626 return 0
2628 return 0
2627
2629
2628 def _abortgraft(ui, repo, graftstate):
2630 def _abortgraft(ui, repo, graftstate):
2629 """abort the interrupted graft and rollbacks to the state before interrupted
2631 """abort the interrupted graft and rollbacks to the state before interrupted
2630 graft"""
2632 graft"""
2631 if not graftstate.exists():
2633 if not graftstate.exists():
2632 raise error.Abort(_("no interrupted graft to abort"))
2634 raise error.Abort(_("no interrupted graft to abort"))
2633 statedata = _readgraftstate(repo, graftstate)
2635 statedata = _readgraftstate(repo, graftstate)
2634 newnodes = statedata.get('newnodes')
2636 newnodes = statedata.get('newnodes')
2635 if newnodes is None:
2637 if newnodes is None:
2636 # and old graft state which does not have all the data required to abort
2638 # and old graft state which does not have all the data required to abort
2637 # the graft
2639 # the graft
2638 raise error.Abort(_("cannot abort using an old graftstate"))
2640 raise error.Abort(_("cannot abort using an old graftstate"))
2639
2641
2640 # changeset from which graft operation was started
2642 # changeset from which graft operation was started
2641 if len(newnodes) > 0:
2643 if len(newnodes) > 0:
2642 startctx = repo[newnodes[0]].p1()
2644 startctx = repo[newnodes[0]].p1()
2643 else:
2645 else:
2644 startctx = repo['.']
2646 startctx = repo['.']
2645 # whether to strip or not
2647 # whether to strip or not
2646 cleanup = False
2648 cleanup = False
2647 if newnodes:
2649 if newnodes:
2648 newnodes = [repo[r].rev() for r in newnodes]
2650 newnodes = [repo[r].rev() for r in newnodes]
2649 cleanup = True
2651 cleanup = True
2650 # checking that none of the newnodes turned public or is public
2652 # checking that none of the newnodes turned public or is public
2651 immutable = [c for c in newnodes if not repo[c].mutable()]
2653 immutable = [c for c in newnodes if not repo[c].mutable()]
2652 if immutable:
2654 if immutable:
2653 repo.ui.warn(_("cannot clean up public changesets %s\n")
2655 repo.ui.warn(_("cannot clean up public changesets %s\n")
2654 % ', '.join(bytes(repo[r]) for r in immutable),
2656 % ', '.join(bytes(repo[r]) for r in immutable),
2655 hint=_("see 'hg help phases' for details"))
2657 hint=_("see 'hg help phases' for details"))
2656 cleanup = False
2658 cleanup = False
2657
2659
2658 # checking that no new nodes are created on top of grafted revs
2660 # checking that no new nodes are created on top of grafted revs
2659 desc = set(repo.changelog.descendants(newnodes))
2661 desc = set(repo.changelog.descendants(newnodes))
2660 if desc - set(newnodes):
2662 if desc - set(newnodes):
2661 repo.ui.warn(_("new changesets detected on destination "
2663 repo.ui.warn(_("new changesets detected on destination "
2662 "branch, can't strip\n"))
2664 "branch, can't strip\n"))
2663 cleanup = False
2665 cleanup = False
2664
2666
2665 if cleanup:
2667 if cleanup:
2666 with repo.wlock(), repo.lock():
2668 with repo.wlock(), repo.lock():
2667 hg.updaterepo(repo, startctx.node(), overwrite=True)
2669 hg.updaterepo(repo, startctx.node(), overwrite=True)
2668 # stripping the new nodes created
2670 # stripping the new nodes created
2669 strippoints = [c.node() for c in repo.set("roots(%ld)",
2671 strippoints = [c.node() for c in repo.set("roots(%ld)",
2670 newnodes)]
2672 newnodes)]
2671 repair.strip(repo.ui, repo, strippoints, backup=False)
2673 repair.strip(repo.ui, repo, strippoints, backup=False)
2672
2674
2673 if not cleanup:
2675 if not cleanup:
2674 # we don't update to the startnode if we can't strip
2676 # we don't update to the startnode if we can't strip
2675 startctx = repo['.']
2677 startctx = repo['.']
2676 hg.updaterepo(repo, startctx.node(), overwrite=True)
2678 hg.updaterepo(repo, startctx.node(), overwrite=True)
2677
2679
2678 ui.status(_("graft aborted\n"))
2680 ui.status(_("graft aborted\n"))
2679 ui.status(_("working directory is now at %s\n") % startctx.hex()[:12])
2681 ui.status(_("working directory is now at %s\n") % startctx.hex()[:12])
2680 graftstate.delete()
2682 graftstate.delete()
2681 return 0
2683 return 0
2682
2684
2683 def _readgraftstate(repo, graftstate):
2685 def _readgraftstate(repo, graftstate):
2684 """read the graft state file and return a dict of the data stored in it"""
2686 """read the graft state file and return a dict of the data stored in it"""
2685 try:
2687 try:
2686 return graftstate.read()
2688 return graftstate.read()
2687 except error.CorruptedState:
2689 except error.CorruptedState:
2688 nodes = repo.vfs.read('graftstate').splitlines()
2690 nodes = repo.vfs.read('graftstate').splitlines()
2689 return {'nodes': nodes}
2691 return {'nodes': nodes}
2690
2692
2691 def _stopgraft(ui, repo, graftstate):
2693 def _stopgraft(ui, repo, graftstate):
2692 """stop the interrupted graft"""
2694 """stop the interrupted graft"""
2693 if not graftstate.exists():
2695 if not graftstate.exists():
2694 raise error.Abort(_("no interrupted graft found"))
2696 raise error.Abort(_("no interrupted graft found"))
2695 pctx = repo['.']
2697 pctx = repo['.']
2696 hg.updaterepo(repo, pctx.node(), overwrite=True)
2698 hg.updaterepo(repo, pctx.node(), overwrite=True)
2697 graftstate.delete()
2699 graftstate.delete()
2698 ui.status(_("stopped the interrupted graft\n"))
2700 ui.status(_("stopped the interrupted graft\n"))
2699 ui.status(_("working directory is now at %s\n") % pctx.hex()[:12])
2701 ui.status(_("working directory is now at %s\n") % pctx.hex()[:12])
2700 return 0
2702 return 0
2701
2703
2702 @command('grep',
2704 @command('grep',
2703 [('0', 'print0', None, _('end fields with NUL')),
2705 [('0', 'print0', None, _('end fields with NUL')),
2704 ('', 'all', None, _('print all revisions that match (DEPRECATED) ')),
2706 ('', 'all', None, _('print all revisions that match (DEPRECATED) ')),
2705 ('', 'diff', None, _('print all revisions when the term was introduced '
2707 ('', 'diff', None, _('print all revisions when the term was introduced '
2706 'or removed')),
2708 'or removed')),
2707 ('a', 'text', None, _('treat all files as text')),
2709 ('a', 'text', None, _('treat all files as text')),
2708 ('f', 'follow', None,
2710 ('f', 'follow', None,
2709 _('follow changeset history,'
2711 _('follow changeset history,'
2710 ' or file history across copies and renames')),
2712 ' or file history across copies and renames')),
2711 ('i', 'ignore-case', None, _('ignore case when matching')),
2713 ('i', 'ignore-case', None, _('ignore case when matching')),
2712 ('l', 'files-with-matches', None,
2714 ('l', 'files-with-matches', None,
2713 _('print only filenames and revisions that match')),
2715 _('print only filenames and revisions that match')),
2714 ('n', 'line-number', None, _('print matching line numbers')),
2716 ('n', 'line-number', None, _('print matching line numbers')),
2715 ('r', 'rev', [],
2717 ('r', 'rev', [],
2716 _('only search files changed within revision range'), _('REV')),
2718 _('only search files changed within revision range'), _('REV')),
2717 ('', 'all-files', None,
2719 ('', 'all-files', None,
2718 _('include all files in the changeset while grepping (EXPERIMENTAL)')),
2720 _('include all files in the changeset while grepping (EXPERIMENTAL)')),
2719 ('u', 'user', None, _('list the author (long with -v)')),
2721 ('u', 'user', None, _('list the author (long with -v)')),
2720 ('d', 'date', None, _('list the date (short with -q)')),
2722 ('d', 'date', None, _('list the date (short with -q)')),
2721 ] + formatteropts + walkopts,
2723 ] + formatteropts + walkopts,
2722 _('[OPTION]... PATTERN [FILE]...'),
2724 _('[OPTION]... PATTERN [FILE]...'),
2723 helpcategory=command.CATEGORY_FILE_CONTENTS,
2725 helpcategory=command.CATEGORY_FILE_CONTENTS,
2724 inferrepo=True,
2726 inferrepo=True,
2725 intents={INTENT_READONLY})
2727 intents={INTENT_READONLY})
2726 def grep(ui, repo, pattern, *pats, **opts):
2728 def grep(ui, repo, pattern, *pats, **opts):
2727 """search revision history for a pattern in specified files
2729 """search revision history for a pattern in specified files
2728
2730
2729 Search revision history for a regular expression in the specified
2731 Search revision history for a regular expression in the specified
2730 files or the entire project.
2732 files or the entire project.
2731
2733
2732 By default, grep prints the most recent revision number for each
2734 By default, grep prints the most recent revision number for each
2733 file in which it finds a match. To get it to print every revision
2735 file in which it finds a match. To get it to print every revision
2734 that contains a change in match status ("-" for a match that becomes
2736 that contains a change in match status ("-" for a match that becomes
2735 a non-match, or "+" for a non-match that becomes a match), use the
2737 a non-match, or "+" for a non-match that becomes a match), use the
2736 --diff flag.
2738 --diff flag.
2737
2739
2738 PATTERN can be any Python (roughly Perl-compatible) regular
2740 PATTERN can be any Python (roughly Perl-compatible) regular
2739 expression.
2741 expression.
2740
2742
2741 If no FILEs are specified (and -f/--follow isn't set), all files in
2743 If no FILEs are specified (and -f/--follow isn't set), all files in
2742 the repository are searched, including those that don't exist in the
2744 the repository are searched, including those that don't exist in the
2743 current branch or have been deleted in a prior changeset.
2745 current branch or have been deleted in a prior changeset.
2744
2746
2745 .. container:: verbose
2747 .. container:: verbose
2746
2748
2747 Template:
2749 Template:
2748
2750
2749 The following keywords are supported in addition to the common template
2751 The following keywords are supported in addition to the common template
2750 keywords and functions. See also :hg:`help templates`.
2752 keywords and functions. See also :hg:`help templates`.
2751
2753
2752 :change: String. Character denoting insertion ``+`` or removal ``-``.
2754 :change: String. Character denoting insertion ``+`` or removal ``-``.
2753 Available if ``--diff`` is specified.
2755 Available if ``--diff`` is specified.
2754 :lineno: Integer. Line number of the match.
2756 :lineno: Integer. Line number of the match.
2755 :path: String. Repository-absolute path of the file.
2757 :path: String. Repository-absolute path of the file.
2756 :texts: List of text chunks.
2758 :texts: List of text chunks.
2757
2759
2758 And each entry of ``{texts}`` provides the following sub-keywords.
2760 And each entry of ``{texts}`` provides the following sub-keywords.
2759
2761
2760 :matched: Boolean. True if the chunk matches the specified pattern.
2762 :matched: Boolean. True if the chunk matches the specified pattern.
2761 :text: String. Chunk content.
2763 :text: String. Chunk content.
2762
2764
2763 See :hg:`help templates.operators` for the list expansion syntax.
2765 See :hg:`help templates.operators` for the list expansion syntax.
2764
2766
2765 Returns 0 if a match is found, 1 otherwise.
2767 Returns 0 if a match is found, 1 otherwise.
2766 """
2768 """
2767 opts = pycompat.byteskwargs(opts)
2769 opts = pycompat.byteskwargs(opts)
2768 diff = opts.get('all') or opts.get('diff')
2770 diff = opts.get('all') or opts.get('diff')
2769 all_files = opts.get('all_files')
2771 all_files = opts.get('all_files')
2770 if diff and opts.get('all_files'):
2772 if diff and opts.get('all_files'):
2771 raise error.Abort(_('--diff and --all-files are mutually exclusive'))
2773 raise error.Abort(_('--diff and --all-files are mutually exclusive'))
2772 # TODO: remove "not opts.get('rev')" if --all-files -rMULTIREV gets working
2774 # TODO: remove "not opts.get('rev')" if --all-files -rMULTIREV gets working
2773 if opts.get('all_files') is None and not opts.get('rev') and not diff:
2775 if opts.get('all_files') is None and not opts.get('rev') and not diff:
2774 # experimental config: commands.grep.all-files
2776 # experimental config: commands.grep.all-files
2775 opts['all_files'] = ui.configbool('commands', 'grep.all-files')
2777 opts['all_files'] = ui.configbool('commands', 'grep.all-files')
2776 plaingrep = opts.get('all_files') and not opts.get('rev')
2778 plaingrep = opts.get('all_files') and not opts.get('rev')
2777 if plaingrep:
2779 if plaingrep:
2778 opts['rev'] = ['wdir()']
2780 opts['rev'] = ['wdir()']
2779
2781
2780 reflags = re.M
2782 reflags = re.M
2781 if opts.get('ignore_case'):
2783 if opts.get('ignore_case'):
2782 reflags |= re.I
2784 reflags |= re.I
2783 try:
2785 try:
2784 regexp = util.re.compile(pattern, reflags)
2786 regexp = util.re.compile(pattern, reflags)
2785 except re.error as inst:
2787 except re.error as inst:
2786 ui.warn(_("grep: invalid match pattern: %s\n") % pycompat.bytestr(inst))
2788 ui.warn(_("grep: invalid match pattern: %s\n") % pycompat.bytestr(inst))
2787 return 1
2789 return 1
2788 sep, eol = ':', '\n'
2790 sep, eol = ':', '\n'
2789 if opts.get('print0'):
2791 if opts.get('print0'):
2790 sep = eol = '\0'
2792 sep = eol = '\0'
2791
2793
2792 getfile = util.lrucachefunc(repo.file)
2794 getfile = util.lrucachefunc(repo.file)
2793
2795
2794 def matchlines(body):
2796 def matchlines(body):
2795 begin = 0
2797 begin = 0
2796 linenum = 0
2798 linenum = 0
2797 while begin < len(body):
2799 while begin < len(body):
2798 match = regexp.search(body, begin)
2800 match = regexp.search(body, begin)
2799 if not match:
2801 if not match:
2800 break
2802 break
2801 mstart, mend = match.span()
2803 mstart, mend = match.span()
2802 linenum += body.count('\n', begin, mstart) + 1
2804 linenum += body.count('\n', begin, mstart) + 1
2803 lstart = body.rfind('\n', begin, mstart) + 1 or begin
2805 lstart = body.rfind('\n', begin, mstart) + 1 or begin
2804 begin = body.find('\n', mend) + 1 or len(body) + 1
2806 begin = body.find('\n', mend) + 1 or len(body) + 1
2805 lend = begin - 1
2807 lend = begin - 1
2806 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
2808 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
2807
2809
2808 class linestate(object):
2810 class linestate(object):
2809 def __init__(self, line, linenum, colstart, colend):
2811 def __init__(self, line, linenum, colstart, colend):
2810 self.line = line
2812 self.line = line
2811 self.linenum = linenum
2813 self.linenum = linenum
2812 self.colstart = colstart
2814 self.colstart = colstart
2813 self.colend = colend
2815 self.colend = colend
2814
2816
2815 def __hash__(self):
2817 def __hash__(self):
2816 return hash((self.linenum, self.line))
2818 return hash((self.linenum, self.line))
2817
2819
2818 def __eq__(self, other):
2820 def __eq__(self, other):
2819 return self.line == other.line
2821 return self.line == other.line
2820
2822
2821 def findpos(self):
2823 def findpos(self):
2822 """Iterate all (start, end) indices of matches"""
2824 """Iterate all (start, end) indices of matches"""
2823 yield self.colstart, self.colend
2825 yield self.colstart, self.colend
2824 p = self.colend
2826 p = self.colend
2825 while p < len(self.line):
2827 while p < len(self.line):
2826 m = regexp.search(self.line, p)
2828 m = regexp.search(self.line, p)
2827 if not m:
2829 if not m:
2828 break
2830 break
2829 yield m.span()
2831 yield m.span()
2830 p = m.end()
2832 p = m.end()
2831
2833
2832 matches = {}
2834 matches = {}
2833 copies = {}
2835 copies = {}
2834 def grepbody(fn, rev, body):
2836 def grepbody(fn, rev, body):
2835 matches[rev].setdefault(fn, [])
2837 matches[rev].setdefault(fn, [])
2836 m = matches[rev][fn]
2838 m = matches[rev][fn]
2837 for lnum, cstart, cend, line in matchlines(body):
2839 for lnum, cstart, cend, line in matchlines(body):
2838 s = linestate(line, lnum, cstart, cend)
2840 s = linestate(line, lnum, cstart, cend)
2839 m.append(s)
2841 m.append(s)
2840
2842
2841 def difflinestates(a, b):
2843 def difflinestates(a, b):
2842 sm = difflib.SequenceMatcher(None, a, b)
2844 sm = difflib.SequenceMatcher(None, a, b)
2843 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
2845 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
2844 if tag == r'insert':
2846 if tag == r'insert':
2845 for i in pycompat.xrange(blo, bhi):
2847 for i in pycompat.xrange(blo, bhi):
2846 yield ('+', b[i])
2848 yield ('+', b[i])
2847 elif tag == r'delete':
2849 elif tag == r'delete':
2848 for i in pycompat.xrange(alo, ahi):
2850 for i in pycompat.xrange(alo, ahi):
2849 yield ('-', a[i])
2851 yield ('-', a[i])
2850 elif tag == r'replace':
2852 elif tag == r'replace':
2851 for i in pycompat.xrange(alo, ahi):
2853 for i in pycompat.xrange(alo, ahi):
2852 yield ('-', a[i])
2854 yield ('-', a[i])
2853 for i in pycompat.xrange(blo, bhi):
2855 for i in pycompat.xrange(blo, bhi):
2854 yield ('+', b[i])
2856 yield ('+', b[i])
2855
2857
2856 uipathfn = scmutil.getuipathfn(repo)
2858 uipathfn = scmutil.getuipathfn(repo)
2857 def display(fm, fn, ctx, pstates, states):
2859 def display(fm, fn, ctx, pstates, states):
2858 rev = scmutil.intrev(ctx)
2860 rev = scmutil.intrev(ctx)
2859 if fm.isplain():
2861 if fm.isplain():
2860 formatuser = ui.shortuser
2862 formatuser = ui.shortuser
2861 else:
2863 else:
2862 formatuser = pycompat.bytestr
2864 formatuser = pycompat.bytestr
2863 if ui.quiet:
2865 if ui.quiet:
2864 datefmt = '%Y-%m-%d'
2866 datefmt = '%Y-%m-%d'
2865 else:
2867 else:
2866 datefmt = '%a %b %d %H:%M:%S %Y %1%2'
2868 datefmt = '%a %b %d %H:%M:%S %Y %1%2'
2867 found = False
2869 found = False
2868 @util.cachefunc
2870 @util.cachefunc
2869 def binary():
2871 def binary():
2870 flog = getfile(fn)
2872 flog = getfile(fn)
2871 try:
2873 try:
2872 return stringutil.binary(flog.read(ctx.filenode(fn)))
2874 return stringutil.binary(flog.read(ctx.filenode(fn)))
2873 except error.WdirUnsupported:
2875 except error.WdirUnsupported:
2874 return ctx[fn].isbinary()
2876 return ctx[fn].isbinary()
2875
2877
2876 fieldnamemap = {'linenumber': 'lineno'}
2878 fieldnamemap = {'linenumber': 'lineno'}
2877 if diff:
2879 if diff:
2878 iter = difflinestates(pstates, states)
2880 iter = difflinestates(pstates, states)
2879 else:
2881 else:
2880 iter = [('', l) for l in states]
2882 iter = [('', l) for l in states]
2881 for change, l in iter:
2883 for change, l in iter:
2882 fm.startitem()
2884 fm.startitem()
2883 fm.context(ctx=ctx)
2885 fm.context(ctx=ctx)
2884 fm.data(node=fm.hexfunc(scmutil.binnode(ctx)), path=fn)
2886 fm.data(node=fm.hexfunc(scmutil.binnode(ctx)), path=fn)
2885 fm.plain(uipathfn(fn), label='grep.filename')
2887 fm.plain(uipathfn(fn), label='grep.filename')
2886
2888
2887 cols = [
2889 cols = [
2888 ('rev', '%d', rev, not plaingrep, ''),
2890 ('rev', '%d', rev, not plaingrep, ''),
2889 ('linenumber', '%d', l.linenum, opts.get('line_number'), ''),
2891 ('linenumber', '%d', l.linenum, opts.get('line_number'), ''),
2890 ]
2892 ]
2891 if diff:
2893 if diff:
2892 cols.append(
2894 cols.append(
2893 ('change', '%s', change, True,
2895 ('change', '%s', change, True,
2894 'grep.inserted ' if change == '+' else 'grep.deleted ')
2896 'grep.inserted ' if change == '+' else 'grep.deleted ')
2895 )
2897 )
2896 cols.extend([
2898 cols.extend([
2897 ('user', '%s', formatuser(ctx.user()), opts.get('user'), ''),
2899 ('user', '%s', formatuser(ctx.user()), opts.get('user'), ''),
2898 ('date', '%s', fm.formatdate(ctx.date(), datefmt),
2900 ('date', '%s', fm.formatdate(ctx.date(), datefmt),
2899 opts.get('date'), ''),
2901 opts.get('date'), ''),
2900 ])
2902 ])
2901 for name, fmt, data, cond, extra_label in cols:
2903 for name, fmt, data, cond, extra_label in cols:
2902 if cond:
2904 if cond:
2903 fm.plain(sep, label='grep.sep')
2905 fm.plain(sep, label='grep.sep')
2904 field = fieldnamemap.get(name, name)
2906 field = fieldnamemap.get(name, name)
2905 label = extra_label + ('grep.%s' % name)
2907 label = extra_label + ('grep.%s' % name)
2906 fm.condwrite(cond, field, fmt, data, label=label)
2908 fm.condwrite(cond, field, fmt, data, label=label)
2907 if not opts.get('files_with_matches'):
2909 if not opts.get('files_with_matches'):
2908 fm.plain(sep, label='grep.sep')
2910 fm.plain(sep, label='grep.sep')
2909 if not opts.get('text') and binary():
2911 if not opts.get('text') and binary():
2910 fm.plain(_(" Binary file matches"))
2912 fm.plain(_(" Binary file matches"))
2911 else:
2913 else:
2912 displaymatches(fm.nested('texts', tmpl='{text}'), l)
2914 displaymatches(fm.nested('texts', tmpl='{text}'), l)
2913 fm.plain(eol)
2915 fm.plain(eol)
2914 found = True
2916 found = True
2915 if opts.get('files_with_matches'):
2917 if opts.get('files_with_matches'):
2916 break
2918 break
2917 return found
2919 return found
2918
2920
2919 def displaymatches(fm, l):
2921 def displaymatches(fm, l):
2920 p = 0
2922 p = 0
2921 for s, e in l.findpos():
2923 for s, e in l.findpos():
2922 if p < s:
2924 if p < s:
2923 fm.startitem()
2925 fm.startitem()
2924 fm.write('text', '%s', l.line[p:s])
2926 fm.write('text', '%s', l.line[p:s])
2925 fm.data(matched=False)
2927 fm.data(matched=False)
2926 fm.startitem()
2928 fm.startitem()
2927 fm.write('text', '%s', l.line[s:e], label='grep.match')
2929 fm.write('text', '%s', l.line[s:e], label='grep.match')
2928 fm.data(matched=True)
2930 fm.data(matched=True)
2929 p = e
2931 p = e
2930 if p < len(l.line):
2932 if p < len(l.line):
2931 fm.startitem()
2933 fm.startitem()
2932 fm.write('text', '%s', l.line[p:])
2934 fm.write('text', '%s', l.line[p:])
2933 fm.data(matched=False)
2935 fm.data(matched=False)
2934 fm.end()
2936 fm.end()
2935
2937
2936 skip = set()
2938 skip = set()
2937 revfiles = {}
2939 revfiles = {}
2938 match = scmutil.match(repo[None], pats, opts)
2940 match = scmutil.match(repo[None], pats, opts)
2939 found = False
2941 found = False
2940 follow = opts.get('follow')
2942 follow = opts.get('follow')
2941
2943
2942 def prep(ctx, fns):
2944 def prep(ctx, fns):
2943 rev = ctx.rev()
2945 rev = ctx.rev()
2944 pctx = ctx.p1()
2946 pctx = ctx.p1()
2945 parent = pctx.rev()
2947 parent = pctx.rev()
2946 matches.setdefault(rev, {})
2948 matches.setdefault(rev, {})
2947 matches.setdefault(parent, {})
2949 matches.setdefault(parent, {})
2948 files = revfiles.setdefault(rev, [])
2950 files = revfiles.setdefault(rev, [])
2949 for fn in fns:
2951 for fn in fns:
2950 flog = getfile(fn)
2952 flog = getfile(fn)
2951 try:
2953 try:
2952 fnode = ctx.filenode(fn)
2954 fnode = ctx.filenode(fn)
2953 except error.LookupError:
2955 except error.LookupError:
2954 continue
2956 continue
2955 copy = None
2957 copy = None
2956 if follow:
2958 if follow:
2957 try:
2959 try:
2958 copied = flog.renamed(fnode)
2960 copied = flog.renamed(fnode)
2959 except error.WdirUnsupported:
2961 except error.WdirUnsupported:
2960 copied = ctx[fn].renamed()
2962 copied = ctx[fn].renamed()
2961 copy = copied and copied[0]
2963 copy = copied and copied[0]
2962 if copy:
2964 if copy:
2963 copies.setdefault(rev, {})[fn] = copy
2965 copies.setdefault(rev, {})[fn] = copy
2964 if fn in skip:
2966 if fn in skip:
2965 skip.add(copy)
2967 skip.add(copy)
2966 if fn in skip:
2968 if fn in skip:
2967 continue
2969 continue
2968 files.append(fn)
2970 files.append(fn)
2969
2971
2970 if fn not in matches[rev]:
2972 if fn not in matches[rev]:
2971 try:
2973 try:
2972 content = flog.read(fnode)
2974 content = flog.read(fnode)
2973 except error.WdirUnsupported:
2975 except error.WdirUnsupported:
2974 content = ctx[fn].data()
2976 content = ctx[fn].data()
2975 grepbody(fn, rev, content)
2977 grepbody(fn, rev, content)
2976
2978
2977 pfn = copy or fn
2979 pfn = copy or fn
2978 if pfn not in matches[parent]:
2980 if pfn not in matches[parent]:
2979 try:
2981 try:
2980 fnode = pctx.filenode(pfn)
2982 fnode = pctx.filenode(pfn)
2981 grepbody(pfn, parent, flog.read(fnode))
2983 grepbody(pfn, parent, flog.read(fnode))
2982 except error.LookupError:
2984 except error.LookupError:
2983 pass
2985 pass
2984
2986
2985 ui.pager('grep')
2987 ui.pager('grep')
2986 fm = ui.formatter('grep', opts)
2988 fm = ui.formatter('grep', opts)
2987 for ctx in cmdutil.walkchangerevs(repo, match, opts, prep):
2989 for ctx in cmdutil.walkchangerevs(repo, match, opts, prep):
2988 rev = ctx.rev()
2990 rev = ctx.rev()
2989 parent = ctx.p1().rev()
2991 parent = ctx.p1().rev()
2990 for fn in sorted(revfiles.get(rev, [])):
2992 for fn in sorted(revfiles.get(rev, [])):
2991 states = matches[rev][fn]
2993 states = matches[rev][fn]
2992 copy = copies.get(rev, {}).get(fn)
2994 copy = copies.get(rev, {}).get(fn)
2993 if fn in skip:
2995 if fn in skip:
2994 if copy:
2996 if copy:
2995 skip.add(copy)
2997 skip.add(copy)
2996 continue
2998 continue
2997 pstates = matches.get(parent, {}).get(copy or fn, [])
2999 pstates = matches.get(parent, {}).get(copy or fn, [])
2998 if pstates or states:
3000 if pstates or states:
2999 r = display(fm, fn, ctx, pstates, states)
3001 r = display(fm, fn, ctx, pstates, states)
3000 found = found or r
3002 found = found or r
3001 if r and not diff and not all_files:
3003 if r and not diff and not all_files:
3002 skip.add(fn)
3004 skip.add(fn)
3003 if copy:
3005 if copy:
3004 skip.add(copy)
3006 skip.add(copy)
3005 del revfiles[rev]
3007 del revfiles[rev]
3006 # We will keep the matches dict for the duration of the window
3008 # We will keep the matches dict for the duration of the window
3007 # clear the matches dict once the window is over
3009 # clear the matches dict once the window is over
3008 if not revfiles:
3010 if not revfiles:
3009 matches.clear()
3011 matches.clear()
3010 fm.end()
3012 fm.end()
3011
3013
3012 return not found
3014 return not found
3013
3015
3014 @command('heads',
3016 @command('heads',
3015 [('r', 'rev', '',
3017 [('r', 'rev', '',
3016 _('show only heads which are descendants of STARTREV'), _('STARTREV')),
3018 _('show only heads which are descendants of STARTREV'), _('STARTREV')),
3017 ('t', 'topo', False, _('show topological heads only')),
3019 ('t', 'topo', False, _('show topological heads only')),
3018 ('a', 'active', False, _('show active branchheads only (DEPRECATED)')),
3020 ('a', 'active', False, _('show active branchheads only (DEPRECATED)')),
3019 ('c', 'closed', False, _('show normal and closed branch heads')),
3021 ('c', 'closed', False, _('show normal and closed branch heads')),
3020 ] + templateopts,
3022 ] + templateopts,
3021 _('[-ct] [-r STARTREV] [REV]...'),
3023 _('[-ct] [-r STARTREV] [REV]...'),
3022 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3024 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3023 intents={INTENT_READONLY})
3025 intents={INTENT_READONLY})
3024 def heads(ui, repo, *branchrevs, **opts):
3026 def heads(ui, repo, *branchrevs, **opts):
3025 """show branch heads
3027 """show branch heads
3026
3028
3027 With no arguments, show all open branch heads in the repository.
3029 With no arguments, show all open branch heads in the repository.
3028 Branch heads are changesets that have no descendants on the
3030 Branch heads are changesets that have no descendants on the
3029 same branch. They are where development generally takes place and
3031 same branch. They are where development generally takes place and
3030 are the usual targets for update and merge operations.
3032 are the usual targets for update and merge operations.
3031
3033
3032 If one or more REVs are given, only open branch heads on the
3034 If one or more REVs are given, only open branch heads on the
3033 branches associated with the specified changesets are shown. This
3035 branches associated with the specified changesets are shown. This
3034 means that you can use :hg:`heads .` to see the heads on the
3036 means that you can use :hg:`heads .` to see the heads on the
3035 currently checked-out branch.
3037 currently checked-out branch.
3036
3038
3037 If -c/--closed is specified, also show branch heads marked closed
3039 If -c/--closed is specified, also show branch heads marked closed
3038 (see :hg:`commit --close-branch`).
3040 (see :hg:`commit --close-branch`).
3039
3041
3040 If STARTREV is specified, only those heads that are descendants of
3042 If STARTREV is specified, only those heads that are descendants of
3041 STARTREV will be displayed.
3043 STARTREV will be displayed.
3042
3044
3043 If -t/--topo is specified, named branch mechanics will be ignored and only
3045 If -t/--topo is specified, named branch mechanics will be ignored and only
3044 topological heads (changesets with no children) will be shown.
3046 topological heads (changesets with no children) will be shown.
3045
3047
3046 Returns 0 if matching heads are found, 1 if not.
3048 Returns 0 if matching heads are found, 1 if not.
3047 """
3049 """
3048
3050
3049 opts = pycompat.byteskwargs(opts)
3051 opts = pycompat.byteskwargs(opts)
3050 start = None
3052 start = None
3051 rev = opts.get('rev')
3053 rev = opts.get('rev')
3052 if rev:
3054 if rev:
3053 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
3055 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
3054 start = scmutil.revsingle(repo, rev, None).node()
3056 start = scmutil.revsingle(repo, rev, None).node()
3055
3057
3056 if opts.get('topo'):
3058 if opts.get('topo'):
3057 heads = [repo[h] for h in repo.heads(start)]
3059 heads = [repo[h] for h in repo.heads(start)]
3058 else:
3060 else:
3059 heads = []
3061 heads = []
3060 for branch in repo.branchmap():
3062 for branch in repo.branchmap():
3061 heads += repo.branchheads(branch, start, opts.get('closed'))
3063 heads += repo.branchheads(branch, start, opts.get('closed'))
3062 heads = [repo[h] for h in heads]
3064 heads = [repo[h] for h in heads]
3063
3065
3064 if branchrevs:
3066 if branchrevs:
3065 branches = set(repo[r].branch()
3067 branches = set(repo[r].branch()
3066 for r in scmutil.revrange(repo, branchrevs))
3068 for r in scmutil.revrange(repo, branchrevs))
3067 heads = [h for h in heads if h.branch() in branches]
3069 heads = [h for h in heads if h.branch() in branches]
3068
3070
3069 if opts.get('active') and branchrevs:
3071 if opts.get('active') and branchrevs:
3070 dagheads = repo.heads(start)
3072 dagheads = repo.heads(start)
3071 heads = [h for h in heads if h.node() in dagheads]
3073 heads = [h for h in heads if h.node() in dagheads]
3072
3074
3073 if branchrevs:
3075 if branchrevs:
3074 haveheads = set(h.branch() for h in heads)
3076 haveheads = set(h.branch() for h in heads)
3075 if branches - haveheads:
3077 if branches - haveheads:
3076 headless = ', '.join(b for b in branches - haveheads)
3078 headless = ', '.join(b for b in branches - haveheads)
3077 msg = _('no open branch heads found on branches %s')
3079 msg = _('no open branch heads found on branches %s')
3078 if opts.get('rev'):
3080 if opts.get('rev'):
3079 msg += _(' (started at %s)') % opts['rev']
3081 msg += _(' (started at %s)') % opts['rev']
3080 ui.warn((msg + '\n') % headless)
3082 ui.warn((msg + '\n') % headless)
3081
3083
3082 if not heads:
3084 if not heads:
3083 return 1
3085 return 1
3084
3086
3085 ui.pager('heads')
3087 ui.pager('heads')
3086 heads = sorted(heads, key=lambda x: -x.rev())
3088 heads = sorted(heads, key=lambda x: -x.rev())
3087 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
3089 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
3088 for ctx in heads:
3090 for ctx in heads:
3089 displayer.show(ctx)
3091 displayer.show(ctx)
3090 displayer.close()
3092 displayer.close()
3091
3093
3092 @command('help',
3094 @command('help',
3093 [('e', 'extension', None, _('show only help for extensions')),
3095 [('e', 'extension', None, _('show only help for extensions')),
3094 ('c', 'command', None, _('show only help for commands')),
3096 ('c', 'command', None, _('show only help for commands')),
3095 ('k', 'keyword', None, _('show topics matching keyword')),
3097 ('k', 'keyword', None, _('show topics matching keyword')),
3096 ('s', 'system', [],
3098 ('s', 'system', [],
3097 _('show help for specific platform(s)'), _('PLATFORM')),
3099 _('show help for specific platform(s)'), _('PLATFORM')),
3098 ],
3100 ],
3099 _('[-eck] [-s PLATFORM] [TOPIC]'),
3101 _('[-eck] [-s PLATFORM] [TOPIC]'),
3100 helpcategory=command.CATEGORY_HELP,
3102 helpcategory=command.CATEGORY_HELP,
3101 norepo=True,
3103 norepo=True,
3102 intents={INTENT_READONLY})
3104 intents={INTENT_READONLY})
3103 def help_(ui, name=None, **opts):
3105 def help_(ui, name=None, **opts):
3104 """show help for a given topic or a help overview
3106 """show help for a given topic or a help overview
3105
3107
3106 With no arguments, print a list of commands with short help messages.
3108 With no arguments, print a list of commands with short help messages.
3107
3109
3108 Given a topic, extension, or command name, print help for that
3110 Given a topic, extension, or command name, print help for that
3109 topic.
3111 topic.
3110
3112
3111 Returns 0 if successful.
3113 Returns 0 if successful.
3112 """
3114 """
3113
3115
3114 keep = opts.get(r'system') or []
3116 keep = opts.get(r'system') or []
3115 if len(keep) == 0:
3117 if len(keep) == 0:
3116 if pycompat.sysplatform.startswith('win'):
3118 if pycompat.sysplatform.startswith('win'):
3117 keep.append('windows')
3119 keep.append('windows')
3118 elif pycompat.sysplatform == 'OpenVMS':
3120 elif pycompat.sysplatform == 'OpenVMS':
3119 keep.append('vms')
3121 keep.append('vms')
3120 elif pycompat.sysplatform == 'plan9':
3122 elif pycompat.sysplatform == 'plan9':
3121 keep.append('plan9')
3123 keep.append('plan9')
3122 else:
3124 else:
3123 keep.append('unix')
3125 keep.append('unix')
3124 keep.append(pycompat.sysplatform.lower())
3126 keep.append(pycompat.sysplatform.lower())
3125 if ui.verbose:
3127 if ui.verbose:
3126 keep.append('verbose')
3128 keep.append('verbose')
3127
3129
3128 commands = sys.modules[__name__]
3130 commands = sys.modules[__name__]
3129 formatted = help.formattedhelp(ui, commands, name, keep=keep, **opts)
3131 formatted = help.formattedhelp(ui, commands, name, keep=keep, **opts)
3130 ui.pager('help')
3132 ui.pager('help')
3131 ui.write(formatted)
3133 ui.write(formatted)
3132
3134
3133
3135
3134 @command('identify|id',
3136 @command('identify|id',
3135 [('r', 'rev', '',
3137 [('r', 'rev', '',
3136 _('identify the specified revision'), _('REV')),
3138 _('identify the specified revision'), _('REV')),
3137 ('n', 'num', None, _('show local revision number')),
3139 ('n', 'num', None, _('show local revision number')),
3138 ('i', 'id', None, _('show global revision id')),
3140 ('i', 'id', None, _('show global revision id')),
3139 ('b', 'branch', None, _('show branch')),
3141 ('b', 'branch', None, _('show branch')),
3140 ('t', 'tags', None, _('show tags')),
3142 ('t', 'tags', None, _('show tags')),
3141 ('B', 'bookmarks', None, _('show bookmarks')),
3143 ('B', 'bookmarks', None, _('show bookmarks')),
3142 ] + remoteopts + formatteropts,
3144 ] + remoteopts + formatteropts,
3143 _('[-nibtB] [-r REV] [SOURCE]'),
3145 _('[-nibtB] [-r REV] [SOURCE]'),
3144 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3146 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3145 optionalrepo=True,
3147 optionalrepo=True,
3146 intents={INTENT_READONLY})
3148 intents={INTENT_READONLY})
3147 def identify(ui, repo, source=None, rev=None,
3149 def identify(ui, repo, source=None, rev=None,
3148 num=None, id=None, branch=None, tags=None, bookmarks=None, **opts):
3150 num=None, id=None, branch=None, tags=None, bookmarks=None, **opts):
3149 """identify the working directory or specified revision
3151 """identify the working directory or specified revision
3150
3152
3151 Print a summary identifying the repository state at REV using one or
3153 Print a summary identifying the repository state at REV using one or
3152 two parent hash identifiers, followed by a "+" if the working
3154 two parent hash identifiers, followed by a "+" if the working
3153 directory has uncommitted changes, the branch name (if not default),
3155 directory has uncommitted changes, the branch name (if not default),
3154 a list of tags, and a list of bookmarks.
3156 a list of tags, and a list of bookmarks.
3155
3157
3156 When REV is not given, print a summary of the current state of the
3158 When REV is not given, print a summary of the current state of the
3157 repository including the working directory. Specify -r. to get information
3159 repository including the working directory. Specify -r. to get information
3158 of the working directory parent without scanning uncommitted changes.
3160 of the working directory parent without scanning uncommitted changes.
3159
3161
3160 Specifying a path to a repository root or Mercurial bundle will
3162 Specifying a path to a repository root or Mercurial bundle will
3161 cause lookup to operate on that repository/bundle.
3163 cause lookup to operate on that repository/bundle.
3162
3164
3163 .. container:: verbose
3165 .. container:: verbose
3164
3166
3165 Template:
3167 Template:
3166
3168
3167 The following keywords are supported in addition to the common template
3169 The following keywords are supported in addition to the common template
3168 keywords and functions. See also :hg:`help templates`.
3170 keywords and functions. See also :hg:`help templates`.
3169
3171
3170 :dirty: String. Character ``+`` denoting if the working directory has
3172 :dirty: String. Character ``+`` denoting if the working directory has
3171 uncommitted changes.
3173 uncommitted changes.
3172 :id: String. One or two nodes, optionally followed by ``+``.
3174 :id: String. One or two nodes, optionally followed by ``+``.
3173 :parents: List of strings. Parent nodes of the changeset.
3175 :parents: List of strings. Parent nodes of the changeset.
3174
3176
3175 Examples:
3177 Examples:
3176
3178
3177 - generate a build identifier for the working directory::
3179 - generate a build identifier for the working directory::
3178
3180
3179 hg id --id > build-id.dat
3181 hg id --id > build-id.dat
3180
3182
3181 - find the revision corresponding to a tag::
3183 - find the revision corresponding to a tag::
3182
3184
3183 hg id -n -r 1.3
3185 hg id -n -r 1.3
3184
3186
3185 - check the most recent revision of a remote repository::
3187 - check the most recent revision of a remote repository::
3186
3188
3187 hg id -r tip https://www.mercurial-scm.org/repo/hg/
3189 hg id -r tip https://www.mercurial-scm.org/repo/hg/
3188
3190
3189 See :hg:`log` for generating more information about specific revisions,
3191 See :hg:`log` for generating more information about specific revisions,
3190 including full hash identifiers.
3192 including full hash identifiers.
3191
3193
3192 Returns 0 if successful.
3194 Returns 0 if successful.
3193 """
3195 """
3194
3196
3195 opts = pycompat.byteskwargs(opts)
3197 opts = pycompat.byteskwargs(opts)
3196 if not repo and not source:
3198 if not repo and not source:
3197 raise error.Abort(_("there is no Mercurial repository here "
3199 raise error.Abort(_("there is no Mercurial repository here "
3198 "(.hg not found)"))
3200 "(.hg not found)"))
3199
3201
3200 default = not (num or id or branch or tags or bookmarks)
3202 default = not (num or id or branch or tags or bookmarks)
3201 output = []
3203 output = []
3202 revs = []
3204 revs = []
3203
3205
3204 if source:
3206 if source:
3205 source, branches = hg.parseurl(ui.expandpath(source))
3207 source, branches = hg.parseurl(ui.expandpath(source))
3206 peer = hg.peer(repo or ui, opts, source) # only pass ui when no repo
3208 peer = hg.peer(repo or ui, opts, source) # only pass ui when no repo
3207 repo = peer.local()
3209 repo = peer.local()
3208 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
3210 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
3209
3211
3210 fm = ui.formatter('identify', opts)
3212 fm = ui.formatter('identify', opts)
3211 fm.startitem()
3213 fm.startitem()
3212
3214
3213 if not repo:
3215 if not repo:
3214 if num or branch or tags:
3216 if num or branch or tags:
3215 raise error.Abort(
3217 raise error.Abort(
3216 _("can't query remote revision number, branch, or tags"))
3218 _("can't query remote revision number, branch, or tags"))
3217 if not rev and revs:
3219 if not rev and revs:
3218 rev = revs[0]
3220 rev = revs[0]
3219 if not rev:
3221 if not rev:
3220 rev = "tip"
3222 rev = "tip"
3221
3223
3222 remoterev = peer.lookup(rev)
3224 remoterev = peer.lookup(rev)
3223 hexrev = fm.hexfunc(remoterev)
3225 hexrev = fm.hexfunc(remoterev)
3224 if default or id:
3226 if default or id:
3225 output = [hexrev]
3227 output = [hexrev]
3226 fm.data(id=hexrev)
3228 fm.data(id=hexrev)
3227
3229
3228 @util.cachefunc
3230 @util.cachefunc
3229 def getbms():
3231 def getbms():
3230 bms = []
3232 bms = []
3231
3233
3232 if 'bookmarks' in peer.listkeys('namespaces'):
3234 if 'bookmarks' in peer.listkeys('namespaces'):
3233 hexremoterev = hex(remoterev)
3235 hexremoterev = hex(remoterev)
3234 bms = [bm for bm, bmr in peer.listkeys('bookmarks').iteritems()
3236 bms = [bm for bm, bmr in peer.listkeys('bookmarks').iteritems()
3235 if bmr == hexremoterev]
3237 if bmr == hexremoterev]
3236
3238
3237 return sorted(bms)
3239 return sorted(bms)
3238
3240
3239 if fm.isplain():
3241 if fm.isplain():
3240 if bookmarks:
3242 if bookmarks:
3241 output.extend(getbms())
3243 output.extend(getbms())
3242 elif default and not ui.quiet:
3244 elif default and not ui.quiet:
3243 # multiple bookmarks for a single parent separated by '/'
3245 # multiple bookmarks for a single parent separated by '/'
3244 bm = '/'.join(getbms())
3246 bm = '/'.join(getbms())
3245 if bm:
3247 if bm:
3246 output.append(bm)
3248 output.append(bm)
3247 else:
3249 else:
3248 fm.data(node=hex(remoterev))
3250 fm.data(node=hex(remoterev))
3249 if bookmarks or 'bookmarks' in fm.datahint():
3251 if bookmarks or 'bookmarks' in fm.datahint():
3250 fm.data(bookmarks=fm.formatlist(getbms(), name='bookmark'))
3252 fm.data(bookmarks=fm.formatlist(getbms(), name='bookmark'))
3251 else:
3253 else:
3252 if rev:
3254 if rev:
3253 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
3255 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
3254 ctx = scmutil.revsingle(repo, rev, None)
3256 ctx = scmutil.revsingle(repo, rev, None)
3255
3257
3256 if ctx.rev() is None:
3258 if ctx.rev() is None:
3257 ctx = repo[None]
3259 ctx = repo[None]
3258 parents = ctx.parents()
3260 parents = ctx.parents()
3259 taglist = []
3261 taglist = []
3260 for p in parents:
3262 for p in parents:
3261 taglist.extend(p.tags())
3263 taglist.extend(p.tags())
3262
3264
3263 dirty = ""
3265 dirty = ""
3264 if ctx.dirty(missing=True, merge=False, branch=False):
3266 if ctx.dirty(missing=True, merge=False, branch=False):
3265 dirty = '+'
3267 dirty = '+'
3266 fm.data(dirty=dirty)
3268 fm.data(dirty=dirty)
3267
3269
3268 hexoutput = [fm.hexfunc(p.node()) for p in parents]
3270 hexoutput = [fm.hexfunc(p.node()) for p in parents]
3269 if default or id:
3271 if default or id:
3270 output = ["%s%s" % ('+'.join(hexoutput), dirty)]
3272 output = ["%s%s" % ('+'.join(hexoutput), dirty)]
3271 fm.data(id="%s%s" % ('+'.join(hexoutput), dirty))
3273 fm.data(id="%s%s" % ('+'.join(hexoutput), dirty))
3272
3274
3273 if num:
3275 if num:
3274 numoutput = ["%d" % p.rev() for p in parents]
3276 numoutput = ["%d" % p.rev() for p in parents]
3275 output.append("%s%s" % ('+'.join(numoutput), dirty))
3277 output.append("%s%s" % ('+'.join(numoutput), dirty))
3276
3278
3277 fm.data(parents=fm.formatlist([fm.hexfunc(p.node())
3279 fm.data(parents=fm.formatlist([fm.hexfunc(p.node())
3278 for p in parents], name='node'))
3280 for p in parents], name='node'))
3279 else:
3281 else:
3280 hexoutput = fm.hexfunc(ctx.node())
3282 hexoutput = fm.hexfunc(ctx.node())
3281 if default or id:
3283 if default or id:
3282 output = [hexoutput]
3284 output = [hexoutput]
3283 fm.data(id=hexoutput)
3285 fm.data(id=hexoutput)
3284
3286
3285 if num:
3287 if num:
3286 output.append(pycompat.bytestr(ctx.rev()))
3288 output.append(pycompat.bytestr(ctx.rev()))
3287 taglist = ctx.tags()
3289 taglist = ctx.tags()
3288
3290
3289 if default and not ui.quiet:
3291 if default and not ui.quiet:
3290 b = ctx.branch()
3292 b = ctx.branch()
3291 if b != 'default':
3293 if b != 'default':
3292 output.append("(%s)" % b)
3294 output.append("(%s)" % b)
3293
3295
3294 # multiple tags for a single parent separated by '/'
3296 # multiple tags for a single parent separated by '/'
3295 t = '/'.join(taglist)
3297 t = '/'.join(taglist)
3296 if t:
3298 if t:
3297 output.append(t)
3299 output.append(t)
3298
3300
3299 # multiple bookmarks for a single parent separated by '/'
3301 # multiple bookmarks for a single parent separated by '/'
3300 bm = '/'.join(ctx.bookmarks())
3302 bm = '/'.join(ctx.bookmarks())
3301 if bm:
3303 if bm:
3302 output.append(bm)
3304 output.append(bm)
3303 else:
3305 else:
3304 if branch:
3306 if branch:
3305 output.append(ctx.branch())
3307 output.append(ctx.branch())
3306
3308
3307 if tags:
3309 if tags:
3308 output.extend(taglist)
3310 output.extend(taglist)
3309
3311
3310 if bookmarks:
3312 if bookmarks:
3311 output.extend(ctx.bookmarks())
3313 output.extend(ctx.bookmarks())
3312
3314
3313 fm.data(node=ctx.hex())
3315 fm.data(node=ctx.hex())
3314 fm.data(branch=ctx.branch())
3316 fm.data(branch=ctx.branch())
3315 fm.data(tags=fm.formatlist(taglist, name='tag', sep=':'))
3317 fm.data(tags=fm.formatlist(taglist, name='tag', sep=':'))
3316 fm.data(bookmarks=fm.formatlist(ctx.bookmarks(), name='bookmark'))
3318 fm.data(bookmarks=fm.formatlist(ctx.bookmarks(), name='bookmark'))
3317 fm.context(ctx=ctx)
3319 fm.context(ctx=ctx)
3318
3320
3319 fm.plain("%s\n" % ' '.join(output))
3321 fm.plain("%s\n" % ' '.join(output))
3320 fm.end()
3322 fm.end()
3321
3323
3322 @command('import|patch',
3324 @command('import|patch',
3323 [('p', 'strip', 1,
3325 [('p', 'strip', 1,
3324 _('directory strip option for patch. This has the same '
3326 _('directory strip option for patch. This has the same '
3325 'meaning as the corresponding patch option'), _('NUM')),
3327 'meaning as the corresponding patch option'), _('NUM')),
3326 ('b', 'base', '', _('base path (DEPRECATED)'), _('PATH')),
3328 ('b', 'base', '', _('base path (DEPRECATED)'), _('PATH')),
3327 ('e', 'edit', False, _('invoke editor on commit messages')),
3329 ('e', 'edit', False, _('invoke editor on commit messages')),
3328 ('f', 'force', None,
3330 ('f', 'force', None,
3329 _('skip check for outstanding uncommitted changes (DEPRECATED)')),
3331 _('skip check for outstanding uncommitted changes (DEPRECATED)')),
3330 ('', 'no-commit', None,
3332 ('', 'no-commit', None,
3331 _("don't commit, just update the working directory")),
3333 _("don't commit, just update the working directory")),
3332 ('', 'bypass', None,
3334 ('', 'bypass', None,
3333 _("apply patch without touching the working directory")),
3335 _("apply patch without touching the working directory")),
3334 ('', 'partial', None,
3336 ('', 'partial', None,
3335 _('commit even if some hunks fail')),
3337 _('commit even if some hunks fail')),
3336 ('', 'exact', None,
3338 ('', 'exact', None,
3337 _('abort if patch would apply lossily')),
3339 _('abort if patch would apply lossily')),
3338 ('', 'prefix', '',
3340 ('', 'prefix', '',
3339 _('apply patch to subdirectory'), _('DIR')),
3341 _('apply patch to subdirectory'), _('DIR')),
3340 ('', 'import-branch', None,
3342 ('', 'import-branch', None,
3341 _('use any branch information in patch (implied by --exact)'))] +
3343 _('use any branch information in patch (implied by --exact)'))] +
3342 commitopts + commitopts2 + similarityopts,
3344 commitopts + commitopts2 + similarityopts,
3343 _('[OPTION]... PATCH...'),
3345 _('[OPTION]... PATCH...'),
3344 helpcategory=command.CATEGORY_IMPORT_EXPORT)
3346 helpcategory=command.CATEGORY_IMPORT_EXPORT)
3345 def import_(ui, repo, patch1=None, *patches, **opts):
3347 def import_(ui, repo, patch1=None, *patches, **opts):
3346 """import an ordered set of patches
3348 """import an ordered set of patches
3347
3349
3348 Import a list of patches and commit them individually (unless
3350 Import a list of patches and commit them individually (unless
3349 --no-commit is specified).
3351 --no-commit is specified).
3350
3352
3351 To read a patch from standard input (stdin), use "-" as the patch
3353 To read a patch from standard input (stdin), use "-" as the patch
3352 name. If a URL is specified, the patch will be downloaded from
3354 name. If a URL is specified, the patch will be downloaded from
3353 there.
3355 there.
3354
3356
3355 Import first applies changes to the working directory (unless
3357 Import first applies changes to the working directory (unless
3356 --bypass is specified), import will abort if there are outstanding
3358 --bypass is specified), import will abort if there are outstanding
3357 changes.
3359 changes.
3358
3360
3359 Use --bypass to apply and commit patches directly to the
3361 Use --bypass to apply and commit patches directly to the
3360 repository, without affecting the working directory. Without
3362 repository, without affecting the working directory. Without
3361 --exact, patches will be applied on top of the working directory
3363 --exact, patches will be applied on top of the working directory
3362 parent revision.
3364 parent revision.
3363
3365
3364 You can import a patch straight from a mail message. Even patches
3366 You can import a patch straight from a mail message. Even patches
3365 as attachments work (to use the body part, it must have type
3367 as attachments work (to use the body part, it must have type
3366 text/plain or text/x-patch). From and Subject headers of email
3368 text/plain or text/x-patch). From and Subject headers of email
3367 message are used as default committer and commit message. All
3369 message are used as default committer and commit message. All
3368 text/plain body parts before first diff are added to the commit
3370 text/plain body parts before first diff are added to the commit
3369 message.
3371 message.
3370
3372
3371 If the imported patch was generated by :hg:`export`, user and
3373 If the imported patch was generated by :hg:`export`, user and
3372 description from patch override values from message headers and
3374 description from patch override values from message headers and
3373 body. Values given on command line with -m/--message and -u/--user
3375 body. Values given on command line with -m/--message and -u/--user
3374 override these.
3376 override these.
3375
3377
3376 If --exact is specified, import will set the working directory to
3378 If --exact is specified, import will set the working directory to
3377 the parent of each patch before applying it, and will abort if the
3379 the parent of each patch before applying it, and will abort if the
3378 resulting changeset has a different ID than the one recorded in
3380 resulting changeset has a different ID than the one recorded in
3379 the patch. This will guard against various ways that portable
3381 the patch. This will guard against various ways that portable
3380 patch formats and mail systems might fail to transfer Mercurial
3382 patch formats and mail systems might fail to transfer Mercurial
3381 data or metadata. See :hg:`bundle` for lossless transmission.
3383 data or metadata. See :hg:`bundle` for lossless transmission.
3382
3384
3383 Use --partial to ensure a changeset will be created from the patch
3385 Use --partial to ensure a changeset will be created from the patch
3384 even if some hunks fail to apply. Hunks that fail to apply will be
3386 even if some hunks fail to apply. Hunks that fail to apply will be
3385 written to a <target-file>.rej file. Conflicts can then be resolved
3387 written to a <target-file>.rej file. Conflicts can then be resolved
3386 by hand before :hg:`commit --amend` is run to update the created
3388 by hand before :hg:`commit --amend` is run to update the created
3387 changeset. This flag exists to let people import patches that
3389 changeset. This flag exists to let people import patches that
3388 partially apply without losing the associated metadata (author,
3390 partially apply without losing the associated metadata (author,
3389 date, description, ...).
3391 date, description, ...).
3390
3392
3391 .. note::
3393 .. note::
3392
3394
3393 When no hunks apply cleanly, :hg:`import --partial` will create
3395 When no hunks apply cleanly, :hg:`import --partial` will create
3394 an empty changeset, importing only the patch metadata.
3396 an empty changeset, importing only the patch metadata.
3395
3397
3396 With -s/--similarity, hg will attempt to discover renames and
3398 With -s/--similarity, hg will attempt to discover renames and
3397 copies in the patch in the same way as :hg:`addremove`.
3399 copies in the patch in the same way as :hg:`addremove`.
3398
3400
3399 It is possible to use external patch programs to perform the patch
3401 It is possible to use external patch programs to perform the patch
3400 by setting the ``ui.patch`` configuration option. For the default
3402 by setting the ``ui.patch`` configuration option. For the default
3401 internal tool, the fuzz can also be configured via ``patch.fuzz``.
3403 internal tool, the fuzz can also be configured via ``patch.fuzz``.
3402 See :hg:`help config` for more information about configuration
3404 See :hg:`help config` for more information about configuration
3403 files and how to use these options.
3405 files and how to use these options.
3404
3406
3405 See :hg:`help dates` for a list of formats valid for -d/--date.
3407 See :hg:`help dates` for a list of formats valid for -d/--date.
3406
3408
3407 .. container:: verbose
3409 .. container:: verbose
3408
3410
3409 Examples:
3411 Examples:
3410
3412
3411 - import a traditional patch from a website and detect renames::
3413 - import a traditional patch from a website and detect renames::
3412
3414
3413 hg import -s 80 http://example.com/bugfix.patch
3415 hg import -s 80 http://example.com/bugfix.patch
3414
3416
3415 - import a changeset from an hgweb server::
3417 - import a changeset from an hgweb server::
3416
3418
3417 hg import https://www.mercurial-scm.org/repo/hg/rev/5ca8c111e9aa
3419 hg import https://www.mercurial-scm.org/repo/hg/rev/5ca8c111e9aa
3418
3420
3419 - import all the patches in an Unix-style mbox::
3421 - import all the patches in an Unix-style mbox::
3420
3422
3421 hg import incoming-patches.mbox
3423 hg import incoming-patches.mbox
3422
3424
3423 - import patches from stdin::
3425 - import patches from stdin::
3424
3426
3425 hg import -
3427 hg import -
3426
3428
3427 - attempt to exactly restore an exported changeset (not always
3429 - attempt to exactly restore an exported changeset (not always
3428 possible)::
3430 possible)::
3429
3431
3430 hg import --exact proposed-fix.patch
3432 hg import --exact proposed-fix.patch
3431
3433
3432 - use an external tool to apply a patch which is too fuzzy for
3434 - use an external tool to apply a patch which is too fuzzy for
3433 the default internal tool.
3435 the default internal tool.
3434
3436
3435 hg import --config ui.patch="patch --merge" fuzzy.patch
3437 hg import --config ui.patch="patch --merge" fuzzy.patch
3436
3438
3437 - change the default fuzzing from 2 to a less strict 7
3439 - change the default fuzzing from 2 to a less strict 7
3438
3440
3439 hg import --config ui.fuzz=7 fuzz.patch
3441 hg import --config ui.fuzz=7 fuzz.patch
3440
3442
3441 Returns 0 on success, 1 on partial success (see --partial).
3443 Returns 0 on success, 1 on partial success (see --partial).
3442 """
3444 """
3443
3445
3444 opts = pycompat.byteskwargs(opts)
3446 opts = pycompat.byteskwargs(opts)
3445 if not patch1:
3447 if not patch1:
3446 raise error.Abort(_('need at least one patch to import'))
3448 raise error.Abort(_('need at least one patch to import'))
3447
3449
3448 patches = (patch1,) + patches
3450 patches = (patch1,) + patches
3449
3451
3450 date = opts.get('date')
3452 date = opts.get('date')
3451 if date:
3453 if date:
3452 opts['date'] = dateutil.parsedate(date)
3454 opts['date'] = dateutil.parsedate(date)
3453
3455
3454 exact = opts.get('exact')
3456 exact = opts.get('exact')
3455 update = not opts.get('bypass')
3457 update = not opts.get('bypass')
3456 if not update and opts.get('no_commit'):
3458 if not update and opts.get('no_commit'):
3457 raise error.Abort(_('cannot use --no-commit with --bypass'))
3459 raise error.Abort(_('cannot use --no-commit with --bypass'))
3458 try:
3460 try:
3459 sim = float(opts.get('similarity') or 0)
3461 sim = float(opts.get('similarity') or 0)
3460 except ValueError:
3462 except ValueError:
3461 raise error.Abort(_('similarity must be a number'))
3463 raise error.Abort(_('similarity must be a number'))
3462 if sim < 0 or sim > 100:
3464 if sim < 0 or sim > 100:
3463 raise error.Abort(_('similarity must be between 0 and 100'))
3465 raise error.Abort(_('similarity must be between 0 and 100'))
3464 if sim and not update:
3466 if sim and not update:
3465 raise error.Abort(_('cannot use --similarity with --bypass'))
3467 raise error.Abort(_('cannot use --similarity with --bypass'))
3466 if exact:
3468 if exact:
3467 if opts.get('edit'):
3469 if opts.get('edit'):
3468 raise error.Abort(_('cannot use --exact with --edit'))
3470 raise error.Abort(_('cannot use --exact with --edit'))
3469 if opts.get('prefix'):
3471 if opts.get('prefix'):
3470 raise error.Abort(_('cannot use --exact with --prefix'))
3472 raise error.Abort(_('cannot use --exact with --prefix'))
3471
3473
3472 base = opts["base"]
3474 base = opts["base"]
3473 msgs = []
3475 msgs = []
3474 ret = 0
3476 ret = 0
3475
3477
3476 with repo.wlock():
3478 with repo.wlock():
3477 if update:
3479 if update:
3478 cmdutil.checkunfinished(repo)
3480 cmdutil.checkunfinished(repo)
3479 if (exact or not opts.get('force')):
3481 if (exact or not opts.get('force')):
3480 cmdutil.bailifchanged(repo)
3482 cmdutil.bailifchanged(repo)
3481
3483
3482 if not opts.get('no_commit'):
3484 if not opts.get('no_commit'):
3483 lock = repo.lock
3485 lock = repo.lock
3484 tr = lambda: repo.transaction('import')
3486 tr = lambda: repo.transaction('import')
3485 dsguard = util.nullcontextmanager
3487 dsguard = util.nullcontextmanager
3486 else:
3488 else:
3487 lock = util.nullcontextmanager
3489 lock = util.nullcontextmanager
3488 tr = util.nullcontextmanager
3490 tr = util.nullcontextmanager
3489 dsguard = lambda: dirstateguard.dirstateguard(repo, 'import')
3491 dsguard = lambda: dirstateguard.dirstateguard(repo, 'import')
3490 with lock(), tr(), dsguard():
3492 with lock(), tr(), dsguard():
3491 parents = repo[None].parents()
3493 parents = repo[None].parents()
3492 for patchurl in patches:
3494 for patchurl in patches:
3493 if patchurl == '-':
3495 if patchurl == '-':
3494 ui.status(_('applying patch from stdin\n'))
3496 ui.status(_('applying patch from stdin\n'))
3495 patchfile = ui.fin
3497 patchfile = ui.fin
3496 patchurl = 'stdin' # for error message
3498 patchurl = 'stdin' # for error message
3497 else:
3499 else:
3498 patchurl = os.path.join(base, patchurl)
3500 patchurl = os.path.join(base, patchurl)
3499 ui.status(_('applying %s\n') % patchurl)
3501 ui.status(_('applying %s\n') % patchurl)
3500 patchfile = hg.openpath(ui, patchurl)
3502 patchfile = hg.openpath(ui, patchurl)
3501
3503
3502 haspatch = False
3504 haspatch = False
3503 for hunk in patch.split(patchfile):
3505 for hunk in patch.split(patchfile):
3504 with patch.extract(ui, hunk) as patchdata:
3506 with patch.extract(ui, hunk) as patchdata:
3505 msg, node, rej = cmdutil.tryimportone(ui, repo,
3507 msg, node, rej = cmdutil.tryimportone(ui, repo,
3506 patchdata,
3508 patchdata,
3507 parents, opts,
3509 parents, opts,
3508 msgs, hg.clean)
3510 msgs, hg.clean)
3509 if msg:
3511 if msg:
3510 haspatch = True
3512 haspatch = True
3511 ui.note(msg + '\n')
3513 ui.note(msg + '\n')
3512 if update or exact:
3514 if update or exact:
3513 parents = repo[None].parents()
3515 parents = repo[None].parents()
3514 else:
3516 else:
3515 parents = [repo[node]]
3517 parents = [repo[node]]
3516 if rej:
3518 if rej:
3517 ui.write_err(_("patch applied partially\n"))
3519 ui.write_err(_("patch applied partially\n"))
3518 ui.write_err(_("(fix the .rej files and run "
3520 ui.write_err(_("(fix the .rej files and run "
3519 "`hg commit --amend`)\n"))
3521 "`hg commit --amend`)\n"))
3520 ret = 1
3522 ret = 1
3521 break
3523 break
3522
3524
3523 if not haspatch:
3525 if not haspatch:
3524 raise error.Abort(_('%s: no diffs found') % patchurl)
3526 raise error.Abort(_('%s: no diffs found') % patchurl)
3525
3527
3526 if msgs:
3528 if msgs:
3527 repo.savecommitmessage('\n* * *\n'.join(msgs))
3529 repo.savecommitmessage('\n* * *\n'.join(msgs))
3528 return ret
3530 return ret
3529
3531
3530 @command('incoming|in',
3532 @command('incoming|in',
3531 [('f', 'force', None,
3533 [('f', 'force', None,
3532 _('run even if remote repository is unrelated')),
3534 _('run even if remote repository is unrelated')),
3533 ('n', 'newest-first', None, _('show newest record first')),
3535 ('n', 'newest-first', None, _('show newest record first')),
3534 ('', 'bundle', '',
3536 ('', 'bundle', '',
3535 _('file to store the bundles into'), _('FILE')),
3537 _('file to store the bundles into'), _('FILE')),
3536 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
3538 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
3537 ('B', 'bookmarks', False, _("compare bookmarks")),
3539 ('B', 'bookmarks', False, _("compare bookmarks")),
3538 ('b', 'branch', [],
3540 ('b', 'branch', [],
3539 _('a specific branch you would like to pull'), _('BRANCH')),
3541 _('a specific branch you would like to pull'), _('BRANCH')),
3540 ] + logopts + remoteopts + subrepoopts,
3542 ] + logopts + remoteopts + subrepoopts,
3541 _('[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'),
3543 _('[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'),
3542 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT)
3544 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT)
3543 def incoming(ui, repo, source="default", **opts):
3545 def incoming(ui, repo, source="default", **opts):
3544 """show new changesets found in source
3546 """show new changesets found in source
3545
3547
3546 Show new changesets found in the specified path/URL or the default
3548 Show new changesets found in the specified path/URL or the default
3547 pull location. These are the changesets that would have been pulled
3549 pull location. These are the changesets that would have been pulled
3548 by :hg:`pull` at the time you issued this command.
3550 by :hg:`pull` at the time you issued this command.
3549
3551
3550 See pull for valid source format details.
3552 See pull for valid source format details.
3551
3553
3552 .. container:: verbose
3554 .. container:: verbose
3553
3555
3554 With -B/--bookmarks, the result of bookmark comparison between
3556 With -B/--bookmarks, the result of bookmark comparison between
3555 local and remote repositories is displayed. With -v/--verbose,
3557 local and remote repositories is displayed. With -v/--verbose,
3556 status is also displayed for each bookmark like below::
3558 status is also displayed for each bookmark like below::
3557
3559
3558 BM1 01234567890a added
3560 BM1 01234567890a added
3559 BM2 1234567890ab advanced
3561 BM2 1234567890ab advanced
3560 BM3 234567890abc diverged
3562 BM3 234567890abc diverged
3561 BM4 34567890abcd changed
3563 BM4 34567890abcd changed
3562
3564
3563 The action taken locally when pulling depends on the
3565 The action taken locally when pulling depends on the
3564 status of each bookmark:
3566 status of each bookmark:
3565
3567
3566 :``added``: pull will create it
3568 :``added``: pull will create it
3567 :``advanced``: pull will update it
3569 :``advanced``: pull will update it
3568 :``diverged``: pull will create a divergent bookmark
3570 :``diverged``: pull will create a divergent bookmark
3569 :``changed``: result depends on remote changesets
3571 :``changed``: result depends on remote changesets
3570
3572
3571 From the point of view of pulling behavior, bookmark
3573 From the point of view of pulling behavior, bookmark
3572 existing only in the remote repository are treated as ``added``,
3574 existing only in the remote repository are treated as ``added``,
3573 even if it is in fact locally deleted.
3575 even if it is in fact locally deleted.
3574
3576
3575 .. container:: verbose
3577 .. container:: verbose
3576
3578
3577 For remote repository, using --bundle avoids downloading the
3579 For remote repository, using --bundle avoids downloading the
3578 changesets twice if the incoming is followed by a pull.
3580 changesets twice if the incoming is followed by a pull.
3579
3581
3580 Examples:
3582 Examples:
3581
3583
3582 - show incoming changes with patches and full description::
3584 - show incoming changes with patches and full description::
3583
3585
3584 hg incoming -vp
3586 hg incoming -vp
3585
3587
3586 - show incoming changes excluding merges, store a bundle::
3588 - show incoming changes excluding merges, store a bundle::
3587
3589
3588 hg in -vpM --bundle incoming.hg
3590 hg in -vpM --bundle incoming.hg
3589 hg pull incoming.hg
3591 hg pull incoming.hg
3590
3592
3591 - briefly list changes inside a bundle::
3593 - briefly list changes inside a bundle::
3592
3594
3593 hg in changes.hg -T "{desc|firstline}\\n"
3595 hg in changes.hg -T "{desc|firstline}\\n"
3594
3596
3595 Returns 0 if there are incoming changes, 1 otherwise.
3597 Returns 0 if there are incoming changes, 1 otherwise.
3596 """
3598 """
3597 opts = pycompat.byteskwargs(opts)
3599 opts = pycompat.byteskwargs(opts)
3598 if opts.get('graph'):
3600 if opts.get('graph'):
3599 logcmdutil.checkunsupportedgraphflags([], opts)
3601 logcmdutil.checkunsupportedgraphflags([], opts)
3600 def display(other, chlist, displayer):
3602 def display(other, chlist, displayer):
3601 revdag = logcmdutil.graphrevs(other, chlist, opts)
3603 revdag = logcmdutil.graphrevs(other, chlist, opts)
3602 logcmdutil.displaygraph(ui, repo, revdag, displayer,
3604 logcmdutil.displaygraph(ui, repo, revdag, displayer,
3603 graphmod.asciiedges)
3605 graphmod.asciiedges)
3604
3606
3605 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
3607 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
3606 return 0
3608 return 0
3607
3609
3608 if opts.get('bundle') and opts.get('subrepos'):
3610 if opts.get('bundle') and opts.get('subrepos'):
3609 raise error.Abort(_('cannot combine --bundle and --subrepos'))
3611 raise error.Abort(_('cannot combine --bundle and --subrepos'))
3610
3612
3611 if opts.get('bookmarks'):
3613 if opts.get('bookmarks'):
3612 source, branches = hg.parseurl(ui.expandpath(source),
3614 source, branches = hg.parseurl(ui.expandpath(source),
3613 opts.get('branch'))
3615 opts.get('branch'))
3614 other = hg.peer(repo, opts, source)
3616 other = hg.peer(repo, opts, source)
3615 if 'bookmarks' not in other.listkeys('namespaces'):
3617 if 'bookmarks' not in other.listkeys('namespaces'):
3616 ui.warn(_("remote doesn't support bookmarks\n"))
3618 ui.warn(_("remote doesn't support bookmarks\n"))
3617 return 0
3619 return 0
3618 ui.pager('incoming')
3620 ui.pager('incoming')
3619 ui.status(_('comparing with %s\n') % util.hidepassword(source))
3621 ui.status(_('comparing with %s\n') % util.hidepassword(source))
3620 return bookmarks.incoming(ui, repo, other)
3622 return bookmarks.incoming(ui, repo, other)
3621
3623
3622 repo._subtoppath = ui.expandpath(source)
3624 repo._subtoppath = ui.expandpath(source)
3623 try:
3625 try:
3624 return hg.incoming(ui, repo, source, opts)
3626 return hg.incoming(ui, repo, source, opts)
3625 finally:
3627 finally:
3626 del repo._subtoppath
3628 del repo._subtoppath
3627
3629
3628
3630
3629 @command('init', remoteopts, _('[-e CMD] [--remotecmd CMD] [DEST]'),
3631 @command('init', remoteopts, _('[-e CMD] [--remotecmd CMD] [DEST]'),
3630 helpcategory=command.CATEGORY_REPO_CREATION,
3632 helpcategory=command.CATEGORY_REPO_CREATION,
3631 helpbasic=True, norepo=True)
3633 helpbasic=True, norepo=True)
3632 def init(ui, dest=".", **opts):
3634 def init(ui, dest=".", **opts):
3633 """create a new repository in the given directory
3635 """create a new repository in the given directory
3634
3636
3635 Initialize a new repository in the given directory. If the given
3637 Initialize a new repository in the given directory. If the given
3636 directory does not exist, it will be created.
3638 directory does not exist, it will be created.
3637
3639
3638 If no directory is given, the current directory is used.
3640 If no directory is given, the current directory is used.
3639
3641
3640 It is possible to specify an ``ssh://`` URL as the destination.
3642 It is possible to specify an ``ssh://`` URL as the destination.
3641 See :hg:`help urls` for more information.
3643 See :hg:`help urls` for more information.
3642
3644
3643 Returns 0 on success.
3645 Returns 0 on success.
3644 """
3646 """
3645 opts = pycompat.byteskwargs(opts)
3647 opts = pycompat.byteskwargs(opts)
3646 hg.peer(ui, opts, ui.expandpath(dest), create=True)
3648 hg.peer(ui, opts, ui.expandpath(dest), create=True)
3647
3649
3648 @command('locate',
3650 @command('locate',
3649 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
3651 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
3650 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
3652 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
3651 ('f', 'fullpath', None, _('print complete paths from the filesystem root')),
3653 ('f', 'fullpath', None, _('print complete paths from the filesystem root')),
3652 ] + walkopts,
3654 ] + walkopts,
3653 _('[OPTION]... [PATTERN]...'),
3655 _('[OPTION]... [PATTERN]...'),
3654 helpcategory=command.CATEGORY_WORKING_DIRECTORY)
3656 helpcategory=command.CATEGORY_WORKING_DIRECTORY)
3655 def locate(ui, repo, *pats, **opts):
3657 def locate(ui, repo, *pats, **opts):
3656 """locate files matching specific patterns (DEPRECATED)
3658 """locate files matching specific patterns (DEPRECATED)
3657
3659
3658 Print files under Mercurial control in the working directory whose
3660 Print files under Mercurial control in the working directory whose
3659 names match the given patterns.
3661 names match the given patterns.
3660
3662
3661 By default, this command searches all directories in the working
3663 By default, this command searches all directories in the working
3662 directory. To search just the current directory and its
3664 directory. To search just the current directory and its
3663 subdirectories, use "--include .".
3665 subdirectories, use "--include .".
3664
3666
3665 If no patterns are given to match, this command prints the names
3667 If no patterns are given to match, this command prints the names
3666 of all files under Mercurial control in the working directory.
3668 of all files under Mercurial control in the working directory.
3667
3669
3668 If you want to feed the output of this command into the "xargs"
3670 If you want to feed the output of this command into the "xargs"
3669 command, use the -0 option to both this command and "xargs". This
3671 command, use the -0 option to both this command and "xargs". This
3670 will avoid the problem of "xargs" treating single filenames that
3672 will avoid the problem of "xargs" treating single filenames that
3671 contain whitespace as multiple filenames.
3673 contain whitespace as multiple filenames.
3672
3674
3673 See :hg:`help files` for a more versatile command.
3675 See :hg:`help files` for a more versatile command.
3674
3676
3675 Returns 0 if a match is found, 1 otherwise.
3677 Returns 0 if a match is found, 1 otherwise.
3676 """
3678 """
3677 opts = pycompat.byteskwargs(opts)
3679 opts = pycompat.byteskwargs(opts)
3678 if opts.get('print0'):
3680 if opts.get('print0'):
3679 end = '\0'
3681 end = '\0'
3680 else:
3682 else:
3681 end = '\n'
3683 end = '\n'
3682 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
3684 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
3683
3685
3684 ret = 1
3686 ret = 1
3685 m = scmutil.match(ctx, pats, opts, default='relglob',
3687 m = scmutil.match(ctx, pats, opts, default='relglob',
3686 badfn=lambda x, y: False)
3688 badfn=lambda x, y: False)
3687
3689
3688 ui.pager('locate')
3690 ui.pager('locate')
3689 if ctx.rev() is None:
3691 if ctx.rev() is None:
3690 # When run on the working copy, "locate" includes removed files, so
3692 # When run on the working copy, "locate" includes removed files, so
3691 # we get the list of files from the dirstate.
3693 # we get the list of files from the dirstate.
3692 filesgen = sorted(repo.dirstate.matches(m))
3694 filesgen = sorted(repo.dirstate.matches(m))
3693 else:
3695 else:
3694 filesgen = ctx.matches(m)
3696 filesgen = ctx.matches(m)
3695 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=bool(pats))
3697 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=bool(pats))
3696 for abs in filesgen:
3698 for abs in filesgen:
3697 if opts.get('fullpath'):
3699 if opts.get('fullpath'):
3698 ui.write(repo.wjoin(abs), end)
3700 ui.write(repo.wjoin(abs), end)
3699 else:
3701 else:
3700 ui.write(uipathfn(abs), end)
3702 ui.write(uipathfn(abs), end)
3701 ret = 0
3703 ret = 0
3702
3704
3703 return ret
3705 return ret
3704
3706
3705 @command('log|history',
3707 @command('log|history',
3706 [('f', 'follow', None,
3708 [('f', 'follow', None,
3707 _('follow changeset history, or file history across copies and renames')),
3709 _('follow changeset history, or file history across copies and renames')),
3708 ('', 'follow-first', None,
3710 ('', 'follow-first', None,
3709 _('only follow the first parent of merge changesets (DEPRECATED)')),
3711 _('only follow the first parent of merge changesets (DEPRECATED)')),
3710 ('d', 'date', '', _('show revisions matching date spec'), _('DATE')),
3712 ('d', 'date', '', _('show revisions matching date spec'), _('DATE')),
3711 ('C', 'copies', None, _('show copied files')),
3713 ('C', 'copies', None, _('show copied files')),
3712 ('k', 'keyword', [],
3714 ('k', 'keyword', [],
3713 _('do case-insensitive search for a given text'), _('TEXT')),
3715 _('do case-insensitive search for a given text'), _('TEXT')),
3714 ('r', 'rev', [], _('show the specified revision or revset'), _('REV')),
3716 ('r', 'rev', [], _('show the specified revision or revset'), _('REV')),
3715 ('L', 'line-range', [],
3717 ('L', 'line-range', [],
3716 _('follow line range of specified file (EXPERIMENTAL)'),
3718 _('follow line range of specified file (EXPERIMENTAL)'),
3717 _('FILE,RANGE')),
3719 _('FILE,RANGE')),
3718 ('', 'removed', None, _('include revisions where files were removed')),
3720 ('', 'removed', None, _('include revisions where files were removed')),
3719 ('m', 'only-merges', None, _('show only merges (DEPRECATED)')),
3721 ('m', 'only-merges', None, _('show only merges (DEPRECATED)')),
3720 ('u', 'user', [], _('revisions committed by user'), _('USER')),
3722 ('u', 'user', [], _('revisions committed by user'), _('USER')),
3721 ('', 'only-branch', [],
3723 ('', 'only-branch', [],
3722 _('show only changesets within the given named branch (DEPRECATED)'),
3724 _('show only changesets within the given named branch (DEPRECATED)'),
3723 _('BRANCH')),
3725 _('BRANCH')),
3724 ('b', 'branch', [],
3726 ('b', 'branch', [],
3725 _('show changesets within the given named branch'), _('BRANCH')),
3727 _('show changesets within the given named branch'), _('BRANCH')),
3726 ('P', 'prune', [],
3728 ('P', 'prune', [],
3727 _('do not display revision or any of its ancestors'), _('REV')),
3729 _('do not display revision or any of its ancestors'), _('REV')),
3728 ] + logopts + walkopts,
3730 ] + logopts + walkopts,
3729 _('[OPTION]... [FILE]'),
3731 _('[OPTION]... [FILE]'),
3730 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3732 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3731 helpbasic=True, inferrepo=True,
3733 helpbasic=True, inferrepo=True,
3732 intents={INTENT_READONLY})
3734 intents={INTENT_READONLY})
3733 def log(ui, repo, *pats, **opts):
3735 def log(ui, repo, *pats, **opts):
3734 """show revision history of entire repository or files
3736 """show revision history of entire repository or files
3735
3737
3736 Print the revision history of the specified files or the entire
3738 Print the revision history of the specified files or the entire
3737 project.
3739 project.
3738
3740
3739 If no revision range is specified, the default is ``tip:0`` unless
3741 If no revision range is specified, the default is ``tip:0`` unless
3740 --follow is set, in which case the working directory parent is
3742 --follow is set, in which case the working directory parent is
3741 used as the starting revision.
3743 used as the starting revision.
3742
3744
3743 File history is shown without following rename or copy history of
3745 File history is shown without following rename or copy history of
3744 files. Use -f/--follow with a filename to follow history across
3746 files. Use -f/--follow with a filename to follow history across
3745 renames and copies. --follow without a filename will only show
3747 renames and copies. --follow without a filename will only show
3746 ancestors of the starting revision.
3748 ancestors of the starting revision.
3747
3749
3748 By default this command prints revision number and changeset id,
3750 By default this command prints revision number and changeset id,
3749 tags, non-trivial parents, user, date and time, and a summary for
3751 tags, non-trivial parents, user, date and time, and a summary for
3750 each commit. When the -v/--verbose switch is used, the list of
3752 each commit. When the -v/--verbose switch is used, the list of
3751 changed files and full commit message are shown.
3753 changed files and full commit message are shown.
3752
3754
3753 With --graph the revisions are shown as an ASCII art DAG with the most
3755 With --graph the revisions are shown as an ASCII art DAG with the most
3754 recent changeset at the top.
3756 recent changeset at the top.
3755 'o' is a changeset, '@' is a working directory parent, '_' closes a branch,
3757 'o' is a changeset, '@' is a working directory parent, '_' closes a branch,
3756 'x' is obsolete, '*' is unstable, and '+' represents a fork where the
3758 'x' is obsolete, '*' is unstable, and '+' represents a fork where the
3757 changeset from the lines below is a parent of the 'o' merge on the same
3759 changeset from the lines below is a parent of the 'o' merge on the same
3758 line.
3760 line.
3759 Paths in the DAG are represented with '|', '/' and so forth. ':' in place
3761 Paths in the DAG are represented with '|', '/' and so forth. ':' in place
3760 of a '|' indicates one or more revisions in a path are omitted.
3762 of a '|' indicates one or more revisions in a path are omitted.
3761
3763
3762 .. container:: verbose
3764 .. container:: verbose
3763
3765
3764 Use -L/--line-range FILE,M:N options to follow the history of lines
3766 Use -L/--line-range FILE,M:N options to follow the history of lines
3765 from M to N in FILE. With -p/--patch only diff hunks affecting
3767 from M to N in FILE. With -p/--patch only diff hunks affecting
3766 specified line range will be shown. This option requires --follow;
3768 specified line range will be shown. This option requires --follow;
3767 it can be specified multiple times. Currently, this option is not
3769 it can be specified multiple times. Currently, this option is not
3768 compatible with --graph. This option is experimental.
3770 compatible with --graph. This option is experimental.
3769
3771
3770 .. note::
3772 .. note::
3771
3773
3772 :hg:`log --patch` may generate unexpected diff output for merge
3774 :hg:`log --patch` may generate unexpected diff output for merge
3773 changesets, as it will only compare the merge changeset against
3775 changesets, as it will only compare the merge changeset against
3774 its first parent. Also, only files different from BOTH parents
3776 its first parent. Also, only files different from BOTH parents
3775 will appear in files:.
3777 will appear in files:.
3776
3778
3777 .. note::
3779 .. note::
3778
3780
3779 For performance reasons, :hg:`log FILE` may omit duplicate changes
3781 For performance reasons, :hg:`log FILE` may omit duplicate changes
3780 made on branches and will not show removals or mode changes. To
3782 made on branches and will not show removals or mode changes. To
3781 see all such changes, use the --removed switch.
3783 see all such changes, use the --removed switch.
3782
3784
3783 .. container:: verbose
3785 .. container:: verbose
3784
3786
3785 .. note::
3787 .. note::
3786
3788
3787 The history resulting from -L/--line-range options depends on diff
3789 The history resulting from -L/--line-range options depends on diff
3788 options; for instance if white-spaces are ignored, respective changes
3790 options; for instance if white-spaces are ignored, respective changes
3789 with only white-spaces in specified line range will not be listed.
3791 with only white-spaces in specified line range will not be listed.
3790
3792
3791 .. container:: verbose
3793 .. container:: verbose
3792
3794
3793 Some examples:
3795 Some examples:
3794
3796
3795 - changesets with full descriptions and file lists::
3797 - changesets with full descriptions and file lists::
3796
3798
3797 hg log -v
3799 hg log -v
3798
3800
3799 - changesets ancestral to the working directory::
3801 - changesets ancestral to the working directory::
3800
3802
3801 hg log -f
3803 hg log -f
3802
3804
3803 - last 10 commits on the current branch::
3805 - last 10 commits on the current branch::
3804
3806
3805 hg log -l 10 -b .
3807 hg log -l 10 -b .
3806
3808
3807 - changesets showing all modifications of a file, including removals::
3809 - changesets showing all modifications of a file, including removals::
3808
3810
3809 hg log --removed file.c
3811 hg log --removed file.c
3810
3812
3811 - all changesets that touch a directory, with diffs, excluding merges::
3813 - all changesets that touch a directory, with diffs, excluding merges::
3812
3814
3813 hg log -Mp lib/
3815 hg log -Mp lib/
3814
3816
3815 - all revision numbers that match a keyword::
3817 - all revision numbers that match a keyword::
3816
3818
3817 hg log -k bug --template "{rev}\\n"
3819 hg log -k bug --template "{rev}\\n"
3818
3820
3819 - the full hash identifier of the working directory parent::
3821 - the full hash identifier of the working directory parent::
3820
3822
3821 hg log -r . --template "{node}\\n"
3823 hg log -r . --template "{node}\\n"
3822
3824
3823 - list available log templates::
3825 - list available log templates::
3824
3826
3825 hg log -T list
3827 hg log -T list
3826
3828
3827 - check if a given changeset is included in a tagged release::
3829 - check if a given changeset is included in a tagged release::
3828
3830
3829 hg log -r "a21ccf and ancestor(1.9)"
3831 hg log -r "a21ccf and ancestor(1.9)"
3830
3832
3831 - find all changesets by some user in a date range::
3833 - find all changesets by some user in a date range::
3832
3834
3833 hg log -k alice -d "may 2008 to jul 2008"
3835 hg log -k alice -d "may 2008 to jul 2008"
3834
3836
3835 - summary of all changesets after the last tag::
3837 - summary of all changesets after the last tag::
3836
3838
3837 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
3839 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
3838
3840
3839 - changesets touching lines 13 to 23 for file.c::
3841 - changesets touching lines 13 to 23 for file.c::
3840
3842
3841 hg log -L file.c,13:23
3843 hg log -L file.c,13:23
3842
3844
3843 - changesets touching lines 13 to 23 for file.c and lines 2 to 6 of
3845 - changesets touching lines 13 to 23 for file.c and lines 2 to 6 of
3844 main.c with patch::
3846 main.c with patch::
3845
3847
3846 hg log -L file.c,13:23 -L main.c,2:6 -p
3848 hg log -L file.c,13:23 -L main.c,2:6 -p
3847
3849
3848 See :hg:`help dates` for a list of formats valid for -d/--date.
3850 See :hg:`help dates` for a list of formats valid for -d/--date.
3849
3851
3850 See :hg:`help revisions` for more about specifying and ordering
3852 See :hg:`help revisions` for more about specifying and ordering
3851 revisions.
3853 revisions.
3852
3854
3853 See :hg:`help templates` for more about pre-packaged styles and
3855 See :hg:`help templates` for more about pre-packaged styles and
3854 specifying custom templates. The default template used by the log
3856 specifying custom templates. The default template used by the log
3855 command can be customized via the ``ui.logtemplate`` configuration
3857 command can be customized via the ``ui.logtemplate`` configuration
3856 setting.
3858 setting.
3857
3859
3858 Returns 0 on success.
3860 Returns 0 on success.
3859
3861
3860 """
3862 """
3861 opts = pycompat.byteskwargs(opts)
3863 opts = pycompat.byteskwargs(opts)
3862 linerange = opts.get('line_range')
3864 linerange = opts.get('line_range')
3863
3865
3864 if linerange and not opts.get('follow'):
3866 if linerange and not opts.get('follow'):
3865 raise error.Abort(_('--line-range requires --follow'))
3867 raise error.Abort(_('--line-range requires --follow'))
3866
3868
3867 if linerange and pats:
3869 if linerange and pats:
3868 # TODO: take pats as patterns with no line-range filter
3870 # TODO: take pats as patterns with no line-range filter
3869 raise error.Abort(
3871 raise error.Abort(
3870 _('FILE arguments are not compatible with --line-range option')
3872 _('FILE arguments are not compatible with --line-range option')
3871 )
3873 )
3872
3874
3873 repo = scmutil.unhidehashlikerevs(repo, opts.get('rev'), 'nowarn')
3875 repo = scmutil.unhidehashlikerevs(repo, opts.get('rev'), 'nowarn')
3874 revs, differ = logcmdutil.getrevs(repo, pats, opts)
3876 revs, differ = logcmdutil.getrevs(repo, pats, opts)
3875 if linerange:
3877 if linerange:
3876 # TODO: should follow file history from logcmdutil._initialrevs(),
3878 # TODO: should follow file history from logcmdutil._initialrevs(),
3877 # then filter the result by logcmdutil._makerevset() and --limit
3879 # then filter the result by logcmdutil._makerevset() and --limit
3878 revs, differ = logcmdutil.getlinerangerevs(repo, revs, opts)
3880 revs, differ = logcmdutil.getlinerangerevs(repo, revs, opts)
3879
3881
3880 getrenamed = None
3882 getrenamed = None
3881 if opts.get('copies'):
3883 if opts.get('copies'):
3882 endrev = None
3884 endrev = None
3883 if revs:
3885 if revs:
3884 endrev = revs.max() + 1
3886 endrev = revs.max() + 1
3885 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
3887 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
3886
3888
3887 ui.pager('log')
3889 ui.pager('log')
3888 displayer = logcmdutil.changesetdisplayer(ui, repo, opts, differ,
3890 displayer = logcmdutil.changesetdisplayer(ui, repo, opts, differ,
3889 buffered=True)
3891 buffered=True)
3890 if opts.get('graph'):
3892 if opts.get('graph'):
3891 displayfn = logcmdutil.displaygraphrevs
3893 displayfn = logcmdutil.displaygraphrevs
3892 else:
3894 else:
3893 displayfn = logcmdutil.displayrevs
3895 displayfn = logcmdutil.displayrevs
3894 displayfn(ui, repo, revs, displayer, getrenamed)
3896 displayfn(ui, repo, revs, displayer, getrenamed)
3895
3897
3896 @command('manifest',
3898 @command('manifest',
3897 [('r', 'rev', '', _('revision to display'), _('REV')),
3899 [('r', 'rev', '', _('revision to display'), _('REV')),
3898 ('', 'all', False, _("list files from all revisions"))]
3900 ('', 'all', False, _("list files from all revisions"))]
3899 + formatteropts,
3901 + formatteropts,
3900 _('[-r REV]'),
3902 _('[-r REV]'),
3901 helpcategory=command.CATEGORY_MAINTENANCE,
3903 helpcategory=command.CATEGORY_MAINTENANCE,
3902 intents={INTENT_READONLY})
3904 intents={INTENT_READONLY})
3903 def manifest(ui, repo, node=None, rev=None, **opts):
3905 def manifest(ui, repo, node=None, rev=None, **opts):
3904 """output the current or given revision of the project manifest
3906 """output the current or given revision of the project manifest
3905
3907
3906 Print a list of version controlled files for the given revision.
3908 Print a list of version controlled files for the given revision.
3907 If no revision is given, the first parent of the working directory
3909 If no revision is given, the first parent of the working directory
3908 is used, or the null revision if no revision is checked out.
3910 is used, or the null revision if no revision is checked out.
3909
3911
3910 With -v, print file permissions, symlink and executable bits.
3912 With -v, print file permissions, symlink and executable bits.
3911 With --debug, print file revision hashes.
3913 With --debug, print file revision hashes.
3912
3914
3913 If option --all is specified, the list of all files from all revisions
3915 If option --all is specified, the list of all files from all revisions
3914 is printed. This includes deleted and renamed files.
3916 is printed. This includes deleted and renamed files.
3915
3917
3916 Returns 0 on success.
3918 Returns 0 on success.
3917 """
3919 """
3918 opts = pycompat.byteskwargs(opts)
3920 opts = pycompat.byteskwargs(opts)
3919 fm = ui.formatter('manifest', opts)
3921 fm = ui.formatter('manifest', opts)
3920
3922
3921 if opts.get('all'):
3923 if opts.get('all'):
3922 if rev or node:
3924 if rev or node:
3923 raise error.Abort(_("can't specify a revision with --all"))
3925 raise error.Abort(_("can't specify a revision with --all"))
3924
3926
3925 res = set()
3927 res = set()
3926 for rev in repo:
3928 for rev in repo:
3927 ctx = repo[rev]
3929 ctx = repo[rev]
3928 res |= set(ctx.files())
3930 res |= set(ctx.files())
3929
3931
3930 ui.pager('manifest')
3932 ui.pager('manifest')
3931 for f in sorted(res):
3933 for f in sorted(res):
3932 fm.startitem()
3934 fm.startitem()
3933 fm.write("path", '%s\n', f)
3935 fm.write("path", '%s\n', f)
3934 fm.end()
3936 fm.end()
3935 return
3937 return
3936
3938
3937 if rev and node:
3939 if rev and node:
3938 raise error.Abort(_("please specify just one revision"))
3940 raise error.Abort(_("please specify just one revision"))
3939
3941
3940 if not node:
3942 if not node:
3941 node = rev
3943 node = rev
3942
3944
3943 char = {'l': '@', 'x': '*', '': '', 't': 'd'}
3945 char = {'l': '@', 'x': '*', '': '', 't': 'd'}
3944 mode = {'l': '644', 'x': '755', '': '644', 't': '755'}
3946 mode = {'l': '644', 'x': '755', '': '644', 't': '755'}
3945 if node:
3947 if node:
3946 repo = scmutil.unhidehashlikerevs(repo, [node], 'nowarn')
3948 repo = scmutil.unhidehashlikerevs(repo, [node], 'nowarn')
3947 ctx = scmutil.revsingle(repo, node)
3949 ctx = scmutil.revsingle(repo, node)
3948 mf = ctx.manifest()
3950 mf = ctx.manifest()
3949 ui.pager('manifest')
3951 ui.pager('manifest')
3950 for f in ctx:
3952 for f in ctx:
3951 fm.startitem()
3953 fm.startitem()
3952 fm.context(ctx=ctx)
3954 fm.context(ctx=ctx)
3953 fl = ctx[f].flags()
3955 fl = ctx[f].flags()
3954 fm.condwrite(ui.debugflag, 'hash', '%s ', hex(mf[f]))
3956 fm.condwrite(ui.debugflag, 'hash', '%s ', hex(mf[f]))
3955 fm.condwrite(ui.verbose, 'mode type', '%s %1s ', mode[fl], char[fl])
3957 fm.condwrite(ui.verbose, 'mode type', '%s %1s ', mode[fl], char[fl])
3956 fm.write('path', '%s\n', f)
3958 fm.write('path', '%s\n', f)
3957 fm.end()
3959 fm.end()
3958
3960
3959 @command('merge',
3961 @command('merge',
3960 [('f', 'force', None,
3962 [('f', 'force', None,
3961 _('force a merge including outstanding changes (DEPRECATED)')),
3963 _('force a merge including outstanding changes (DEPRECATED)')),
3962 ('r', 'rev', '', _('revision to merge'), _('REV')),
3964 ('r', 'rev', '', _('revision to merge'), _('REV')),
3963 ('P', 'preview', None,
3965 ('P', 'preview', None,
3964 _('review revisions to merge (no merge is performed)')),
3966 _('review revisions to merge (no merge is performed)')),
3965 ('', 'abort', None, _('abort the ongoing merge')),
3967 ('', 'abort', None, _('abort the ongoing merge')),
3966 ] + mergetoolopts,
3968 ] + mergetoolopts,
3967 _('[-P] [[-r] REV]'),
3969 _('[-P] [[-r] REV]'),
3968 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT, helpbasic=True)
3970 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT, helpbasic=True)
3969 def merge(ui, repo, node=None, **opts):
3971 def merge(ui, repo, node=None, **opts):
3970 """merge another revision into working directory
3972 """merge another revision into working directory
3971
3973
3972 The current working directory is updated with all changes made in
3974 The current working directory is updated with all changes made in
3973 the requested revision since the last common predecessor revision.
3975 the requested revision since the last common predecessor revision.
3974
3976
3975 Files that changed between either parent are marked as changed for
3977 Files that changed between either parent are marked as changed for
3976 the next commit and a commit must be performed before any further
3978 the next commit and a commit must be performed before any further
3977 updates to the repository are allowed. The next commit will have
3979 updates to the repository are allowed. The next commit will have
3978 two parents.
3980 two parents.
3979
3981
3980 ``--tool`` can be used to specify the merge tool used for file
3982 ``--tool`` can be used to specify the merge tool used for file
3981 merges. It overrides the HGMERGE environment variable and your
3983 merges. It overrides the HGMERGE environment variable and your
3982 configuration files. See :hg:`help merge-tools` for options.
3984 configuration files. See :hg:`help merge-tools` for options.
3983
3985
3984 If no revision is specified, the working directory's parent is a
3986 If no revision is specified, the working directory's parent is a
3985 head revision, and the current branch contains exactly one other
3987 head revision, and the current branch contains exactly one other
3986 head, the other head is merged with by default. Otherwise, an
3988 head, the other head is merged with by default. Otherwise, an
3987 explicit revision with which to merge with must be provided.
3989 explicit revision with which to merge with must be provided.
3988
3990
3989 See :hg:`help resolve` for information on handling file conflicts.
3991 See :hg:`help resolve` for information on handling file conflicts.
3990
3992
3991 To undo an uncommitted merge, use :hg:`merge --abort` which
3993 To undo an uncommitted merge, use :hg:`merge --abort` which
3992 will check out a clean copy of the original merge parent, losing
3994 will check out a clean copy of the original merge parent, losing
3993 all changes.
3995 all changes.
3994
3996
3995 Returns 0 on success, 1 if there are unresolved files.
3997 Returns 0 on success, 1 if there are unresolved files.
3996 """
3998 """
3997
3999
3998 opts = pycompat.byteskwargs(opts)
4000 opts = pycompat.byteskwargs(opts)
3999 abort = opts.get('abort')
4001 abort = opts.get('abort')
4000 if abort and repo.dirstate.p2() == nullid:
4002 if abort and repo.dirstate.p2() == nullid:
4001 cmdutil.wrongtooltocontinue(repo, _('merge'))
4003 cmdutil.wrongtooltocontinue(repo, _('merge'))
4002 if abort:
4004 if abort:
4003 if node:
4005 if node:
4004 raise error.Abort(_("cannot specify a node with --abort"))
4006 raise error.Abort(_("cannot specify a node with --abort"))
4005 if opts.get('rev'):
4007 if opts.get('rev'):
4006 raise error.Abort(_("cannot specify both --rev and --abort"))
4008 raise error.Abort(_("cannot specify both --rev and --abort"))
4007 if opts.get('preview'):
4009 if opts.get('preview'):
4008 raise error.Abort(_("cannot specify --preview with --abort"))
4010 raise error.Abort(_("cannot specify --preview with --abort"))
4009 if opts.get('rev') and node:
4011 if opts.get('rev') and node:
4010 raise error.Abort(_("please specify just one revision"))
4012 raise error.Abort(_("please specify just one revision"))
4011 if not node:
4013 if not node:
4012 node = opts.get('rev')
4014 node = opts.get('rev')
4013
4015
4014 if node:
4016 if node:
4015 node = scmutil.revsingle(repo, node).node()
4017 node = scmutil.revsingle(repo, node).node()
4016
4018
4017 if not node and not abort:
4019 if not node and not abort:
4018 node = repo[destutil.destmerge(repo)].node()
4020 node = repo[destutil.destmerge(repo)].node()
4019
4021
4020 if opts.get('preview'):
4022 if opts.get('preview'):
4021 # find nodes that are ancestors of p2 but not of p1
4023 # find nodes that are ancestors of p2 but not of p1
4022 p1 = repo.lookup('.')
4024 p1 = repo.lookup('.')
4023 p2 = node
4025 p2 = node
4024 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
4026 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
4025
4027
4026 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
4028 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
4027 for node in nodes:
4029 for node in nodes:
4028 displayer.show(repo[node])
4030 displayer.show(repo[node])
4029 displayer.close()
4031 displayer.close()
4030 return 0
4032 return 0
4031
4033
4032 # ui.forcemerge is an internal variable, do not document
4034 # ui.forcemerge is an internal variable, do not document
4033 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
4035 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
4034 with ui.configoverride(overrides, 'merge'):
4036 with ui.configoverride(overrides, 'merge'):
4035 force = opts.get('force')
4037 force = opts.get('force')
4036 labels = ['working copy', 'merge rev']
4038 labels = ['working copy', 'merge rev']
4037 return hg.merge(repo, node, force=force, mergeforce=force,
4039 return hg.merge(repo, node, force=force, mergeforce=force,
4038 labels=labels, abort=abort)
4040 labels=labels, abort=abort)
4039
4041
4040 @command('outgoing|out',
4042 @command('outgoing|out',
4041 [('f', 'force', None, _('run even when the destination is unrelated')),
4043 [('f', 'force', None, _('run even when the destination is unrelated')),
4042 ('r', 'rev', [],
4044 ('r', 'rev', [],
4043 _('a changeset intended to be included in the destination'), _('REV')),
4045 _('a changeset intended to be included in the destination'), _('REV')),
4044 ('n', 'newest-first', None, _('show newest record first')),
4046 ('n', 'newest-first', None, _('show newest record first')),
4045 ('B', 'bookmarks', False, _('compare bookmarks')),
4047 ('B', 'bookmarks', False, _('compare bookmarks')),
4046 ('b', 'branch', [], _('a specific branch you would like to push'),
4048 ('b', 'branch', [], _('a specific branch you would like to push'),
4047 _('BRANCH')),
4049 _('BRANCH')),
4048 ] + logopts + remoteopts + subrepoopts,
4050 ] + logopts + remoteopts + subrepoopts,
4049 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]'),
4051 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]'),
4050 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT)
4052 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT)
4051 def outgoing(ui, repo, dest=None, **opts):
4053 def outgoing(ui, repo, dest=None, **opts):
4052 """show changesets not found in the destination
4054 """show changesets not found in the destination
4053
4055
4054 Show changesets not found in the specified destination repository
4056 Show changesets not found in the specified destination repository
4055 or the default push location. These are the changesets that would
4057 or the default push location. These are the changesets that would
4056 be pushed if a push was requested.
4058 be pushed if a push was requested.
4057
4059
4058 See pull for details of valid destination formats.
4060 See pull for details of valid destination formats.
4059
4061
4060 .. container:: verbose
4062 .. container:: verbose
4061
4063
4062 With -B/--bookmarks, the result of bookmark comparison between
4064 With -B/--bookmarks, the result of bookmark comparison between
4063 local and remote repositories is displayed. With -v/--verbose,
4065 local and remote repositories is displayed. With -v/--verbose,
4064 status is also displayed for each bookmark like below::
4066 status is also displayed for each bookmark like below::
4065
4067
4066 BM1 01234567890a added
4068 BM1 01234567890a added
4067 BM2 deleted
4069 BM2 deleted
4068 BM3 234567890abc advanced
4070 BM3 234567890abc advanced
4069 BM4 34567890abcd diverged
4071 BM4 34567890abcd diverged
4070 BM5 4567890abcde changed
4072 BM5 4567890abcde changed
4071
4073
4072 The action taken when pushing depends on the
4074 The action taken when pushing depends on the
4073 status of each bookmark:
4075 status of each bookmark:
4074
4076
4075 :``added``: push with ``-B`` will create it
4077 :``added``: push with ``-B`` will create it
4076 :``deleted``: push with ``-B`` will delete it
4078 :``deleted``: push with ``-B`` will delete it
4077 :``advanced``: push will update it
4079 :``advanced``: push will update it
4078 :``diverged``: push with ``-B`` will update it
4080 :``diverged``: push with ``-B`` will update it
4079 :``changed``: push with ``-B`` will update it
4081 :``changed``: push with ``-B`` will update it
4080
4082
4081 From the point of view of pushing behavior, bookmarks
4083 From the point of view of pushing behavior, bookmarks
4082 existing only in the remote repository are treated as
4084 existing only in the remote repository are treated as
4083 ``deleted``, even if it is in fact added remotely.
4085 ``deleted``, even if it is in fact added remotely.
4084
4086
4085 Returns 0 if there are outgoing changes, 1 otherwise.
4087 Returns 0 if there are outgoing changes, 1 otherwise.
4086 """
4088 """
4087 # hg._outgoing() needs to re-resolve the path in order to handle #branch
4089 # hg._outgoing() needs to re-resolve the path in order to handle #branch
4088 # style URLs, so don't overwrite dest.
4090 # style URLs, so don't overwrite dest.
4089 path = ui.paths.getpath(dest, default=('default-push', 'default'))
4091 path = ui.paths.getpath(dest, default=('default-push', 'default'))
4090 if not path:
4092 if not path:
4091 raise error.Abort(_('default repository not configured!'),
4093 raise error.Abort(_('default repository not configured!'),
4092 hint=_("see 'hg help config.paths'"))
4094 hint=_("see 'hg help config.paths'"))
4093
4095
4094 opts = pycompat.byteskwargs(opts)
4096 opts = pycompat.byteskwargs(opts)
4095 if opts.get('graph'):
4097 if opts.get('graph'):
4096 logcmdutil.checkunsupportedgraphflags([], opts)
4098 logcmdutil.checkunsupportedgraphflags([], opts)
4097 o, other = hg._outgoing(ui, repo, dest, opts)
4099 o, other = hg._outgoing(ui, repo, dest, opts)
4098 if not o:
4100 if not o:
4099 cmdutil.outgoinghooks(ui, repo, other, opts, o)
4101 cmdutil.outgoinghooks(ui, repo, other, opts, o)
4100 return
4102 return
4101
4103
4102 revdag = logcmdutil.graphrevs(repo, o, opts)
4104 revdag = logcmdutil.graphrevs(repo, o, opts)
4103 ui.pager('outgoing')
4105 ui.pager('outgoing')
4104 displayer = logcmdutil.changesetdisplayer(ui, repo, opts, buffered=True)
4106 displayer = logcmdutil.changesetdisplayer(ui, repo, opts, buffered=True)
4105 logcmdutil.displaygraph(ui, repo, revdag, displayer,
4107 logcmdutil.displaygraph(ui, repo, revdag, displayer,
4106 graphmod.asciiedges)
4108 graphmod.asciiedges)
4107 cmdutil.outgoinghooks(ui, repo, other, opts, o)
4109 cmdutil.outgoinghooks(ui, repo, other, opts, o)
4108 return 0
4110 return 0
4109
4111
4110 if opts.get('bookmarks'):
4112 if opts.get('bookmarks'):
4111 dest = path.pushloc or path.loc
4113 dest = path.pushloc or path.loc
4112 other = hg.peer(repo, opts, dest)
4114 other = hg.peer(repo, opts, dest)
4113 if 'bookmarks' not in other.listkeys('namespaces'):
4115 if 'bookmarks' not in other.listkeys('namespaces'):
4114 ui.warn(_("remote doesn't support bookmarks\n"))
4116 ui.warn(_("remote doesn't support bookmarks\n"))
4115 return 0
4117 return 0
4116 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
4118 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
4117 ui.pager('outgoing')
4119 ui.pager('outgoing')
4118 return bookmarks.outgoing(ui, repo, other)
4120 return bookmarks.outgoing(ui, repo, other)
4119
4121
4120 repo._subtoppath = path.pushloc or path.loc
4122 repo._subtoppath = path.pushloc or path.loc
4121 try:
4123 try:
4122 return hg.outgoing(ui, repo, dest, opts)
4124 return hg.outgoing(ui, repo, dest, opts)
4123 finally:
4125 finally:
4124 del repo._subtoppath
4126 del repo._subtoppath
4125
4127
4126 @command('parents',
4128 @command('parents',
4127 [('r', 'rev', '', _('show parents of the specified revision'), _('REV')),
4129 [('r', 'rev', '', _('show parents of the specified revision'), _('REV')),
4128 ] + templateopts,
4130 ] + templateopts,
4129 _('[-r REV] [FILE]'),
4131 _('[-r REV] [FILE]'),
4130 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
4132 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
4131 inferrepo=True)
4133 inferrepo=True)
4132 def parents(ui, repo, file_=None, **opts):
4134 def parents(ui, repo, file_=None, **opts):
4133 """show the parents of the working directory or revision (DEPRECATED)
4135 """show the parents of the working directory or revision (DEPRECATED)
4134
4136
4135 Print the working directory's parent revisions. If a revision is
4137 Print the working directory's parent revisions. If a revision is
4136 given via -r/--rev, the parent of that revision will be printed.
4138 given via -r/--rev, the parent of that revision will be printed.
4137 If a file argument is given, the revision in which the file was
4139 If a file argument is given, the revision in which the file was
4138 last changed (before the working directory revision or the
4140 last changed (before the working directory revision or the
4139 argument to --rev if given) is printed.
4141 argument to --rev if given) is printed.
4140
4142
4141 This command is equivalent to::
4143 This command is equivalent to::
4142
4144
4143 hg log -r "p1()+p2()" or
4145 hg log -r "p1()+p2()" or
4144 hg log -r "p1(REV)+p2(REV)" or
4146 hg log -r "p1(REV)+p2(REV)" or
4145 hg log -r "max(::p1() and file(FILE))+max(::p2() and file(FILE))" or
4147 hg log -r "max(::p1() and file(FILE))+max(::p2() and file(FILE))" or
4146 hg log -r "max(::p1(REV) and file(FILE))+max(::p2(REV) and file(FILE))"
4148 hg log -r "max(::p1(REV) and file(FILE))+max(::p2(REV) and file(FILE))"
4147
4149
4148 See :hg:`summary` and :hg:`help revsets` for related information.
4150 See :hg:`summary` and :hg:`help revsets` for related information.
4149
4151
4150 Returns 0 on success.
4152 Returns 0 on success.
4151 """
4153 """
4152
4154
4153 opts = pycompat.byteskwargs(opts)
4155 opts = pycompat.byteskwargs(opts)
4154 rev = opts.get('rev')
4156 rev = opts.get('rev')
4155 if rev:
4157 if rev:
4156 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
4158 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
4157 ctx = scmutil.revsingle(repo, rev, None)
4159 ctx = scmutil.revsingle(repo, rev, None)
4158
4160
4159 if file_:
4161 if file_:
4160 m = scmutil.match(ctx, (file_,), opts)
4162 m = scmutil.match(ctx, (file_,), opts)
4161 if m.anypats() or len(m.files()) != 1:
4163 if m.anypats() or len(m.files()) != 1:
4162 raise error.Abort(_('can only specify an explicit filename'))
4164 raise error.Abort(_('can only specify an explicit filename'))
4163 file_ = m.files()[0]
4165 file_ = m.files()[0]
4164 filenodes = []
4166 filenodes = []
4165 for cp in ctx.parents():
4167 for cp in ctx.parents():
4166 if not cp:
4168 if not cp:
4167 continue
4169 continue
4168 try:
4170 try:
4169 filenodes.append(cp.filenode(file_))
4171 filenodes.append(cp.filenode(file_))
4170 except error.LookupError:
4172 except error.LookupError:
4171 pass
4173 pass
4172 if not filenodes:
4174 if not filenodes:
4173 raise error.Abort(_("'%s' not found in manifest!") % file_)
4175 raise error.Abort(_("'%s' not found in manifest!") % file_)
4174 p = []
4176 p = []
4175 for fn in filenodes:
4177 for fn in filenodes:
4176 fctx = repo.filectx(file_, fileid=fn)
4178 fctx = repo.filectx(file_, fileid=fn)
4177 p.append(fctx.node())
4179 p.append(fctx.node())
4178 else:
4180 else:
4179 p = [cp.node() for cp in ctx.parents()]
4181 p = [cp.node() for cp in ctx.parents()]
4180
4182
4181 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
4183 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
4182 for n in p:
4184 for n in p:
4183 if n != nullid:
4185 if n != nullid:
4184 displayer.show(repo[n])
4186 displayer.show(repo[n])
4185 displayer.close()
4187 displayer.close()
4186
4188
4187 @command('paths', formatteropts, _('[NAME]'),
4189 @command('paths', formatteropts, _('[NAME]'),
4188 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4190 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4189 optionalrepo=True, intents={INTENT_READONLY})
4191 optionalrepo=True, intents={INTENT_READONLY})
4190 def paths(ui, repo, search=None, **opts):
4192 def paths(ui, repo, search=None, **opts):
4191 """show aliases for remote repositories
4193 """show aliases for remote repositories
4192
4194
4193 Show definition of symbolic path name NAME. If no name is given,
4195 Show definition of symbolic path name NAME. If no name is given,
4194 show definition of all available names.
4196 show definition of all available names.
4195
4197
4196 Option -q/--quiet suppresses all output when searching for NAME
4198 Option -q/--quiet suppresses all output when searching for NAME
4197 and shows only the path names when listing all definitions.
4199 and shows only the path names when listing all definitions.
4198
4200
4199 Path names are defined in the [paths] section of your
4201 Path names are defined in the [paths] section of your
4200 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
4202 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
4201 repository, ``.hg/hgrc`` is used, too.
4203 repository, ``.hg/hgrc`` is used, too.
4202
4204
4203 The path names ``default`` and ``default-push`` have a special
4205 The path names ``default`` and ``default-push`` have a special
4204 meaning. When performing a push or pull operation, they are used
4206 meaning. When performing a push or pull operation, they are used
4205 as fallbacks if no location is specified on the command-line.
4207 as fallbacks if no location is specified on the command-line.
4206 When ``default-push`` is set, it will be used for push and
4208 When ``default-push`` is set, it will be used for push and
4207 ``default`` will be used for pull; otherwise ``default`` is used
4209 ``default`` will be used for pull; otherwise ``default`` is used
4208 as the fallback for both. When cloning a repository, the clone
4210 as the fallback for both. When cloning a repository, the clone
4209 source is written as ``default`` in ``.hg/hgrc``.
4211 source is written as ``default`` in ``.hg/hgrc``.
4210
4212
4211 .. note::
4213 .. note::
4212
4214
4213 ``default`` and ``default-push`` apply to all inbound (e.g.
4215 ``default`` and ``default-push`` apply to all inbound (e.g.
4214 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email`
4216 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email`
4215 and :hg:`bundle`) operations.
4217 and :hg:`bundle`) operations.
4216
4218
4217 See :hg:`help urls` for more information.
4219 See :hg:`help urls` for more information.
4218
4220
4219 .. container:: verbose
4221 .. container:: verbose
4220
4222
4221 Template:
4223 Template:
4222
4224
4223 The following keywords are supported. See also :hg:`help templates`.
4225 The following keywords are supported. See also :hg:`help templates`.
4224
4226
4225 :name: String. Symbolic name of the path alias.
4227 :name: String. Symbolic name of the path alias.
4226 :pushurl: String. URL for push operations.
4228 :pushurl: String. URL for push operations.
4227 :url: String. URL or directory path for the other operations.
4229 :url: String. URL or directory path for the other operations.
4228
4230
4229 Returns 0 on success.
4231 Returns 0 on success.
4230 """
4232 """
4231
4233
4232 opts = pycompat.byteskwargs(opts)
4234 opts = pycompat.byteskwargs(opts)
4233 ui.pager('paths')
4235 ui.pager('paths')
4234 if search:
4236 if search:
4235 pathitems = [(name, path) for name, path in ui.paths.iteritems()
4237 pathitems = [(name, path) for name, path in ui.paths.iteritems()
4236 if name == search]
4238 if name == search]
4237 else:
4239 else:
4238 pathitems = sorted(ui.paths.iteritems())
4240 pathitems = sorted(ui.paths.iteritems())
4239
4241
4240 fm = ui.formatter('paths', opts)
4242 fm = ui.formatter('paths', opts)
4241 if fm.isplain():
4243 if fm.isplain():
4242 hidepassword = util.hidepassword
4244 hidepassword = util.hidepassword
4243 else:
4245 else:
4244 hidepassword = bytes
4246 hidepassword = bytes
4245 if ui.quiet:
4247 if ui.quiet:
4246 namefmt = '%s\n'
4248 namefmt = '%s\n'
4247 else:
4249 else:
4248 namefmt = '%s = '
4250 namefmt = '%s = '
4249 showsubopts = not search and not ui.quiet
4251 showsubopts = not search and not ui.quiet
4250
4252
4251 for name, path in pathitems:
4253 for name, path in pathitems:
4252 fm.startitem()
4254 fm.startitem()
4253 fm.condwrite(not search, 'name', namefmt, name)
4255 fm.condwrite(not search, 'name', namefmt, name)
4254 fm.condwrite(not ui.quiet, 'url', '%s\n', hidepassword(path.rawloc))
4256 fm.condwrite(not ui.quiet, 'url', '%s\n', hidepassword(path.rawloc))
4255 for subopt, value in sorted(path.suboptions.items()):
4257 for subopt, value in sorted(path.suboptions.items()):
4256 assert subopt not in ('name', 'url')
4258 assert subopt not in ('name', 'url')
4257 if showsubopts:
4259 if showsubopts:
4258 fm.plain('%s:%s = ' % (name, subopt))
4260 fm.plain('%s:%s = ' % (name, subopt))
4259 fm.condwrite(showsubopts, subopt, '%s\n', value)
4261 fm.condwrite(showsubopts, subopt, '%s\n', value)
4260
4262
4261 fm.end()
4263 fm.end()
4262
4264
4263 if search and not pathitems:
4265 if search and not pathitems:
4264 if not ui.quiet:
4266 if not ui.quiet:
4265 ui.warn(_("not found!\n"))
4267 ui.warn(_("not found!\n"))
4266 return 1
4268 return 1
4267 else:
4269 else:
4268 return 0
4270 return 0
4269
4271
4270 @command('phase',
4272 @command('phase',
4271 [('p', 'public', False, _('set changeset phase to public')),
4273 [('p', 'public', False, _('set changeset phase to public')),
4272 ('d', 'draft', False, _('set changeset phase to draft')),
4274 ('d', 'draft', False, _('set changeset phase to draft')),
4273 ('s', 'secret', False, _('set changeset phase to secret')),
4275 ('s', 'secret', False, _('set changeset phase to secret')),
4274 ('f', 'force', False, _('allow to move boundary backward')),
4276 ('f', 'force', False, _('allow to move boundary backward')),
4275 ('r', 'rev', [], _('target revision'), _('REV')),
4277 ('r', 'rev', [], _('target revision'), _('REV')),
4276 ],
4278 ],
4277 _('[-p|-d|-s] [-f] [-r] [REV...]'),
4279 _('[-p|-d|-s] [-f] [-r] [REV...]'),
4278 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION)
4280 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION)
4279 def phase(ui, repo, *revs, **opts):
4281 def phase(ui, repo, *revs, **opts):
4280 """set or show the current phase name
4282 """set or show the current phase name
4281
4283
4282 With no argument, show the phase name of the current revision(s).
4284 With no argument, show the phase name of the current revision(s).
4283
4285
4284 With one of -p/--public, -d/--draft or -s/--secret, change the
4286 With one of -p/--public, -d/--draft or -s/--secret, change the
4285 phase value of the specified revisions.
4287 phase value of the specified revisions.
4286
4288
4287 Unless -f/--force is specified, :hg:`phase` won't move changesets from a
4289 Unless -f/--force is specified, :hg:`phase` won't move changesets from a
4288 lower phase to a higher phase. Phases are ordered as follows::
4290 lower phase to a higher phase. Phases are ordered as follows::
4289
4291
4290 public < draft < secret
4292 public < draft < secret
4291
4293
4292 Returns 0 on success, 1 if some phases could not be changed.
4294 Returns 0 on success, 1 if some phases could not be changed.
4293
4295
4294 (For more information about the phases concept, see :hg:`help phases`.)
4296 (For more information about the phases concept, see :hg:`help phases`.)
4295 """
4297 """
4296 opts = pycompat.byteskwargs(opts)
4298 opts = pycompat.byteskwargs(opts)
4297 # search for a unique phase argument
4299 # search for a unique phase argument
4298 targetphase = None
4300 targetphase = None
4299 for idx, name in enumerate(phases.cmdphasenames):
4301 for idx, name in enumerate(phases.cmdphasenames):
4300 if opts[name]:
4302 if opts[name]:
4301 if targetphase is not None:
4303 if targetphase is not None:
4302 raise error.Abort(_('only one phase can be specified'))
4304 raise error.Abort(_('only one phase can be specified'))
4303 targetphase = idx
4305 targetphase = idx
4304
4306
4305 # look for specified revision
4307 # look for specified revision
4306 revs = list(revs)
4308 revs = list(revs)
4307 revs.extend(opts['rev'])
4309 revs.extend(opts['rev'])
4308 if not revs:
4310 if not revs:
4309 # display both parents as the second parent phase can influence
4311 # display both parents as the second parent phase can influence
4310 # the phase of a merge commit
4312 # the phase of a merge commit
4311 revs = [c.rev() for c in repo[None].parents()]
4313 revs = [c.rev() for c in repo[None].parents()]
4312
4314
4313 revs = scmutil.revrange(repo, revs)
4315 revs = scmutil.revrange(repo, revs)
4314
4316
4315 ret = 0
4317 ret = 0
4316 if targetphase is None:
4318 if targetphase is None:
4317 # display
4319 # display
4318 for r in revs:
4320 for r in revs:
4319 ctx = repo[r]
4321 ctx = repo[r]
4320 ui.write('%i: %s\n' % (ctx.rev(), ctx.phasestr()))
4322 ui.write('%i: %s\n' % (ctx.rev(), ctx.phasestr()))
4321 else:
4323 else:
4322 with repo.lock(), repo.transaction("phase") as tr:
4324 with repo.lock(), repo.transaction("phase") as tr:
4323 # set phase
4325 # set phase
4324 if not revs:
4326 if not revs:
4325 raise error.Abort(_('empty revision set'))
4327 raise error.Abort(_('empty revision set'))
4326 nodes = [repo[r].node() for r in revs]
4328 nodes = [repo[r].node() for r in revs]
4327 # moving revision from public to draft may hide them
4329 # moving revision from public to draft may hide them
4328 # We have to check result on an unfiltered repository
4330 # We have to check result on an unfiltered repository
4329 unfi = repo.unfiltered()
4331 unfi = repo.unfiltered()
4330 getphase = unfi._phasecache.phase
4332 getphase = unfi._phasecache.phase
4331 olddata = [getphase(unfi, r) for r in unfi]
4333 olddata = [getphase(unfi, r) for r in unfi]
4332 phases.advanceboundary(repo, tr, targetphase, nodes)
4334 phases.advanceboundary(repo, tr, targetphase, nodes)
4333 if opts['force']:
4335 if opts['force']:
4334 phases.retractboundary(repo, tr, targetphase, nodes)
4336 phases.retractboundary(repo, tr, targetphase, nodes)
4335 getphase = unfi._phasecache.phase
4337 getphase = unfi._phasecache.phase
4336 newdata = [getphase(unfi, r) for r in unfi]
4338 newdata = [getphase(unfi, r) for r in unfi]
4337 changes = sum(newdata[r] != olddata[r] for r in unfi)
4339 changes = sum(newdata[r] != olddata[r] for r in unfi)
4338 cl = unfi.changelog
4340 cl = unfi.changelog
4339 rejected = [n for n in nodes
4341 rejected = [n for n in nodes
4340 if newdata[cl.rev(n)] < targetphase]
4342 if newdata[cl.rev(n)] < targetphase]
4341 if rejected:
4343 if rejected:
4342 ui.warn(_('cannot move %i changesets to a higher '
4344 ui.warn(_('cannot move %i changesets to a higher '
4343 'phase, use --force\n') % len(rejected))
4345 'phase, use --force\n') % len(rejected))
4344 ret = 1
4346 ret = 1
4345 if changes:
4347 if changes:
4346 msg = _('phase changed for %i changesets\n') % changes
4348 msg = _('phase changed for %i changesets\n') % changes
4347 if ret:
4349 if ret:
4348 ui.status(msg)
4350 ui.status(msg)
4349 else:
4351 else:
4350 ui.note(msg)
4352 ui.note(msg)
4351 else:
4353 else:
4352 ui.warn(_('no phases changed\n'))
4354 ui.warn(_('no phases changed\n'))
4353 return ret
4355 return ret
4354
4356
4355 def postincoming(ui, repo, modheads, optupdate, checkout, brev):
4357 def postincoming(ui, repo, modheads, optupdate, checkout, brev):
4356 """Run after a changegroup has been added via pull/unbundle
4358 """Run after a changegroup has been added via pull/unbundle
4357
4359
4358 This takes arguments below:
4360 This takes arguments below:
4359
4361
4360 :modheads: change of heads by pull/unbundle
4362 :modheads: change of heads by pull/unbundle
4361 :optupdate: updating working directory is needed or not
4363 :optupdate: updating working directory is needed or not
4362 :checkout: update destination revision (or None to default destination)
4364 :checkout: update destination revision (or None to default destination)
4363 :brev: a name, which might be a bookmark to be activated after updating
4365 :brev: a name, which might be a bookmark to be activated after updating
4364 """
4366 """
4365 if modheads == 0:
4367 if modheads == 0:
4366 return
4368 return
4367 if optupdate:
4369 if optupdate:
4368 try:
4370 try:
4369 return hg.updatetotally(ui, repo, checkout, brev)
4371 return hg.updatetotally(ui, repo, checkout, brev)
4370 except error.UpdateAbort as inst:
4372 except error.UpdateAbort as inst:
4371 msg = _("not updating: %s") % stringutil.forcebytestr(inst)
4373 msg = _("not updating: %s") % stringutil.forcebytestr(inst)
4372 hint = inst.hint
4374 hint = inst.hint
4373 raise error.UpdateAbort(msg, hint=hint)
4375 raise error.UpdateAbort(msg, hint=hint)
4374 if modheads is not None and modheads > 1:
4376 if modheads is not None and modheads > 1:
4375 currentbranchheads = len(repo.branchheads())
4377 currentbranchheads = len(repo.branchheads())
4376 if currentbranchheads == modheads:
4378 if currentbranchheads == modheads:
4377 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
4379 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
4378 elif currentbranchheads > 1:
4380 elif currentbranchheads > 1:
4379 ui.status(_("(run 'hg heads .' to see heads, 'hg merge' to "
4381 ui.status(_("(run 'hg heads .' to see heads, 'hg merge' to "
4380 "merge)\n"))
4382 "merge)\n"))
4381 else:
4383 else:
4382 ui.status(_("(run 'hg heads' to see heads)\n"))
4384 ui.status(_("(run 'hg heads' to see heads)\n"))
4383 elif not ui.configbool('commands', 'update.requiredest'):
4385 elif not ui.configbool('commands', 'update.requiredest'):
4384 ui.status(_("(run 'hg update' to get a working copy)\n"))
4386 ui.status(_("(run 'hg update' to get a working copy)\n"))
4385
4387
4386 @command('pull',
4388 @command('pull',
4387 [('u', 'update', None,
4389 [('u', 'update', None,
4388 _('update to new branch head if new descendants were pulled')),
4390 _('update to new branch head if new descendants were pulled')),
4389 ('f', 'force', None, _('run even when remote repository is unrelated')),
4391 ('f', 'force', None, _('run even when remote repository is unrelated')),
4390 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
4392 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
4391 ('B', 'bookmark', [], _("bookmark to pull"), _('BOOKMARK')),
4393 ('B', 'bookmark', [], _("bookmark to pull"), _('BOOKMARK')),
4392 ('b', 'branch', [], _('a specific branch you would like to pull'),
4394 ('b', 'branch', [], _('a specific branch you would like to pull'),
4393 _('BRANCH')),
4395 _('BRANCH')),
4394 ] + remoteopts,
4396 ] + remoteopts,
4395 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'),
4397 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'),
4396 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4398 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4397 helpbasic=True)
4399 helpbasic=True)
4398 def pull(ui, repo, source="default", **opts):
4400 def pull(ui, repo, source="default", **opts):
4399 """pull changes from the specified source
4401 """pull changes from the specified source
4400
4402
4401 Pull changes from a remote repository to a local one.
4403 Pull changes from a remote repository to a local one.
4402
4404
4403 This finds all changes from the repository at the specified path
4405 This finds all changes from the repository at the specified path
4404 or URL and adds them to a local repository (the current one unless
4406 or URL and adds them to a local repository (the current one unless
4405 -R is specified). By default, this does not update the copy of the
4407 -R is specified). By default, this does not update the copy of the
4406 project in the working directory.
4408 project in the working directory.
4407
4409
4408 When cloning from servers that support it, Mercurial may fetch
4410 When cloning from servers that support it, Mercurial may fetch
4409 pre-generated data. When this is done, hooks operating on incoming
4411 pre-generated data. When this is done, hooks operating on incoming
4410 changesets and changegroups may fire more than once, once for each
4412 changesets and changegroups may fire more than once, once for each
4411 pre-generated bundle and as well as for any additional remaining
4413 pre-generated bundle and as well as for any additional remaining
4412 data. See :hg:`help -e clonebundles` for more.
4414 data. See :hg:`help -e clonebundles` for more.
4413
4415
4414 Use :hg:`incoming` if you want to see what would have been added
4416 Use :hg:`incoming` if you want to see what would have been added
4415 by a pull at the time you issued this command. If you then decide
4417 by a pull at the time you issued this command. If you then decide
4416 to add those changes to the repository, you should use :hg:`pull
4418 to add those changes to the repository, you should use :hg:`pull
4417 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
4419 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
4418
4420
4419 If SOURCE is omitted, the 'default' path will be used.
4421 If SOURCE is omitted, the 'default' path will be used.
4420 See :hg:`help urls` for more information.
4422 See :hg:`help urls` for more information.
4421
4423
4422 Specifying bookmark as ``.`` is equivalent to specifying the active
4424 Specifying bookmark as ``.`` is equivalent to specifying the active
4423 bookmark's name.
4425 bookmark's name.
4424
4426
4425 Returns 0 on success, 1 if an update had unresolved files.
4427 Returns 0 on success, 1 if an update had unresolved files.
4426 """
4428 """
4427
4429
4428 opts = pycompat.byteskwargs(opts)
4430 opts = pycompat.byteskwargs(opts)
4429 if ui.configbool('commands', 'update.requiredest') and opts.get('update'):
4431 if ui.configbool('commands', 'update.requiredest') and opts.get('update'):
4430 msg = _('update destination required by configuration')
4432 msg = _('update destination required by configuration')
4431 hint = _('use hg pull followed by hg update DEST')
4433 hint = _('use hg pull followed by hg update DEST')
4432 raise error.Abort(msg, hint=hint)
4434 raise error.Abort(msg, hint=hint)
4433
4435
4434 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
4436 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
4435 ui.status(_('pulling from %s\n') % util.hidepassword(source))
4437 ui.status(_('pulling from %s\n') % util.hidepassword(source))
4436 other = hg.peer(repo, opts, source)
4438 other = hg.peer(repo, opts, source)
4437 try:
4439 try:
4438 revs, checkout = hg.addbranchrevs(repo, other, branches,
4440 revs, checkout = hg.addbranchrevs(repo, other, branches,
4439 opts.get('rev'))
4441 opts.get('rev'))
4440
4442
4441 pullopargs = {}
4443 pullopargs = {}
4442
4444
4443 nodes = None
4445 nodes = None
4444 if opts.get('bookmark') or revs:
4446 if opts.get('bookmark') or revs:
4445 # The list of bookmark used here is the same used to actually update
4447 # The list of bookmark used here is the same used to actually update
4446 # the bookmark names, to avoid the race from issue 4689 and we do
4448 # the bookmark names, to avoid the race from issue 4689 and we do
4447 # all lookup and bookmark queries in one go so they see the same
4449 # all lookup and bookmark queries in one go so they see the same
4448 # version of the server state (issue 4700).
4450 # version of the server state (issue 4700).
4449 nodes = []
4451 nodes = []
4450 fnodes = []
4452 fnodes = []
4451 revs = revs or []
4453 revs = revs or []
4452 if revs and not other.capable('lookup'):
4454 if revs and not other.capable('lookup'):
4453 err = _("other repository doesn't support revision lookup, "
4455 err = _("other repository doesn't support revision lookup, "
4454 "so a rev cannot be specified.")
4456 "so a rev cannot be specified.")
4455 raise error.Abort(err)
4457 raise error.Abort(err)
4456 with other.commandexecutor() as e:
4458 with other.commandexecutor() as e:
4457 fremotebookmarks = e.callcommand('listkeys', {
4459 fremotebookmarks = e.callcommand('listkeys', {
4458 'namespace': 'bookmarks'
4460 'namespace': 'bookmarks'
4459 })
4461 })
4460 for r in revs:
4462 for r in revs:
4461 fnodes.append(e.callcommand('lookup', {'key': r}))
4463 fnodes.append(e.callcommand('lookup', {'key': r}))
4462 remotebookmarks = fremotebookmarks.result()
4464 remotebookmarks = fremotebookmarks.result()
4463 remotebookmarks = bookmarks.unhexlifybookmarks(remotebookmarks)
4465 remotebookmarks = bookmarks.unhexlifybookmarks(remotebookmarks)
4464 pullopargs['remotebookmarks'] = remotebookmarks
4466 pullopargs['remotebookmarks'] = remotebookmarks
4465 for b in opts.get('bookmark', []):
4467 for b in opts.get('bookmark', []):
4466 b = repo._bookmarks.expandname(b)
4468 b = repo._bookmarks.expandname(b)
4467 if b not in remotebookmarks:
4469 if b not in remotebookmarks:
4468 raise error.Abort(_('remote bookmark %s not found!') % b)
4470 raise error.Abort(_('remote bookmark %s not found!') % b)
4469 nodes.append(remotebookmarks[b])
4471 nodes.append(remotebookmarks[b])
4470 for i, rev in enumerate(revs):
4472 for i, rev in enumerate(revs):
4471 node = fnodes[i].result()
4473 node = fnodes[i].result()
4472 nodes.append(node)
4474 nodes.append(node)
4473 if rev == checkout:
4475 if rev == checkout:
4474 checkout = node
4476 checkout = node
4475
4477
4476 wlock = util.nullcontextmanager()
4478 wlock = util.nullcontextmanager()
4477 if opts.get('update'):
4479 if opts.get('update'):
4478 wlock = repo.wlock()
4480 wlock = repo.wlock()
4479 with wlock:
4481 with wlock:
4480 pullopargs.update(opts.get('opargs', {}))
4482 pullopargs.update(opts.get('opargs', {}))
4481 modheads = exchange.pull(repo, other, heads=nodes,
4483 modheads = exchange.pull(repo, other, heads=nodes,
4482 force=opts.get('force'),
4484 force=opts.get('force'),
4483 bookmarks=opts.get('bookmark', ()),
4485 bookmarks=opts.get('bookmark', ()),
4484 opargs=pullopargs).cgresult
4486 opargs=pullopargs).cgresult
4485
4487
4486 # brev is a name, which might be a bookmark to be activated at
4488 # brev is a name, which might be a bookmark to be activated at
4487 # the end of the update. In other words, it is an explicit
4489 # the end of the update. In other words, it is an explicit
4488 # destination of the update
4490 # destination of the update
4489 brev = None
4491 brev = None
4490
4492
4491 if checkout:
4493 if checkout:
4492 checkout = repo.changelog.rev(checkout)
4494 checkout = repo.changelog.rev(checkout)
4493
4495
4494 # order below depends on implementation of
4496 # order below depends on implementation of
4495 # hg.addbranchrevs(). opts['bookmark'] is ignored,
4497 # hg.addbranchrevs(). opts['bookmark'] is ignored,
4496 # because 'checkout' is determined without it.
4498 # because 'checkout' is determined without it.
4497 if opts.get('rev'):
4499 if opts.get('rev'):
4498 brev = opts['rev'][0]
4500 brev = opts['rev'][0]
4499 elif opts.get('branch'):
4501 elif opts.get('branch'):
4500 brev = opts['branch'][0]
4502 brev = opts['branch'][0]
4501 else:
4503 else:
4502 brev = branches[0]
4504 brev = branches[0]
4503 repo._subtoppath = source
4505 repo._subtoppath = source
4504 try:
4506 try:
4505 ret = postincoming(ui, repo, modheads, opts.get('update'),
4507 ret = postincoming(ui, repo, modheads, opts.get('update'),
4506 checkout, brev)
4508 checkout, brev)
4507
4509
4508 finally:
4510 finally:
4509 del repo._subtoppath
4511 del repo._subtoppath
4510
4512
4511 finally:
4513 finally:
4512 other.close()
4514 other.close()
4513 return ret
4515 return ret
4514
4516
4515 @command('push',
4517 @command('push',
4516 [('f', 'force', None, _('force push')),
4518 [('f', 'force', None, _('force push')),
4517 ('r', 'rev', [],
4519 ('r', 'rev', [],
4518 _('a changeset intended to be included in the destination'),
4520 _('a changeset intended to be included in the destination'),
4519 _('REV')),
4521 _('REV')),
4520 ('B', 'bookmark', [], _("bookmark to push"), _('BOOKMARK')),
4522 ('B', 'bookmark', [], _("bookmark to push"), _('BOOKMARK')),
4521 ('b', 'branch', [],
4523 ('b', 'branch', [],
4522 _('a specific branch you would like to push'), _('BRANCH')),
4524 _('a specific branch you would like to push'), _('BRANCH')),
4523 ('', 'new-branch', False, _('allow pushing a new branch')),
4525 ('', 'new-branch', False, _('allow pushing a new branch')),
4524 ('', 'pushvars', [], _('variables that can be sent to server (ADVANCED)')),
4526 ('', 'pushvars', [], _('variables that can be sent to server (ADVANCED)')),
4525 ('', 'publish', False, _('push the changeset as public (EXPERIMENTAL)')),
4527 ('', 'publish', False, _('push the changeset as public (EXPERIMENTAL)')),
4526 ] + remoteopts,
4528 ] + remoteopts,
4527 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'),
4529 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'),
4528 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4530 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4529 helpbasic=True)
4531 helpbasic=True)
4530 def push(ui, repo, dest=None, **opts):
4532 def push(ui, repo, dest=None, **opts):
4531 """push changes to the specified destination
4533 """push changes to the specified destination
4532
4534
4533 Push changesets from the local repository to the specified
4535 Push changesets from the local repository to the specified
4534 destination.
4536 destination.
4535
4537
4536 This operation is symmetrical to pull: it is identical to a pull
4538 This operation is symmetrical to pull: it is identical to a pull
4537 in the destination repository from the current one.
4539 in the destination repository from the current one.
4538
4540
4539 By default, push will not allow creation of new heads at the
4541 By default, push will not allow creation of new heads at the
4540 destination, since multiple heads would make it unclear which head
4542 destination, since multiple heads would make it unclear which head
4541 to use. In this situation, it is recommended to pull and merge
4543 to use. In this situation, it is recommended to pull and merge
4542 before pushing.
4544 before pushing.
4543
4545
4544 Use --new-branch if you want to allow push to create a new named
4546 Use --new-branch if you want to allow push to create a new named
4545 branch that is not present at the destination. This allows you to
4547 branch that is not present at the destination. This allows you to
4546 only create a new branch without forcing other changes.
4548 only create a new branch without forcing other changes.
4547
4549
4548 .. note::
4550 .. note::
4549
4551
4550 Extra care should be taken with the -f/--force option,
4552 Extra care should be taken with the -f/--force option,
4551 which will push all new heads on all branches, an action which will
4553 which will push all new heads on all branches, an action which will
4552 almost always cause confusion for collaborators.
4554 almost always cause confusion for collaborators.
4553
4555
4554 If -r/--rev is used, the specified revision and all its ancestors
4556 If -r/--rev is used, the specified revision and all its ancestors
4555 will be pushed to the remote repository.
4557 will be pushed to the remote repository.
4556
4558
4557 If -B/--bookmark is used, the specified bookmarked revision, its
4559 If -B/--bookmark is used, the specified bookmarked revision, its
4558 ancestors, and the bookmark will be pushed to the remote
4560 ancestors, and the bookmark will be pushed to the remote
4559 repository. Specifying ``.`` is equivalent to specifying the active
4561 repository. Specifying ``.`` is equivalent to specifying the active
4560 bookmark's name.
4562 bookmark's name.
4561
4563
4562 Please see :hg:`help urls` for important details about ``ssh://``
4564 Please see :hg:`help urls` for important details about ``ssh://``
4563 URLs. If DESTINATION is omitted, a default path will be used.
4565 URLs. If DESTINATION is omitted, a default path will be used.
4564
4566
4565 .. container:: verbose
4567 .. container:: verbose
4566
4568
4567 The --pushvars option sends strings to the server that become
4569 The --pushvars option sends strings to the server that become
4568 environment variables prepended with ``HG_USERVAR_``. For example,
4570 environment variables prepended with ``HG_USERVAR_``. For example,
4569 ``--pushvars ENABLE_FEATURE=true``, provides the server side hooks with
4571 ``--pushvars ENABLE_FEATURE=true``, provides the server side hooks with
4570 ``HG_USERVAR_ENABLE_FEATURE=true`` as part of their environment.
4572 ``HG_USERVAR_ENABLE_FEATURE=true`` as part of their environment.
4571
4573
4572 pushvars can provide for user-overridable hooks as well as set debug
4574 pushvars can provide for user-overridable hooks as well as set debug
4573 levels. One example is having a hook that blocks commits containing
4575 levels. One example is having a hook that blocks commits containing
4574 conflict markers, but enables the user to override the hook if the file
4576 conflict markers, but enables the user to override the hook if the file
4575 is using conflict markers for testing purposes or the file format has
4577 is using conflict markers for testing purposes or the file format has
4576 strings that look like conflict markers.
4578 strings that look like conflict markers.
4577
4579
4578 By default, servers will ignore `--pushvars`. To enable it add the
4580 By default, servers will ignore `--pushvars`. To enable it add the
4579 following to your configuration file::
4581 following to your configuration file::
4580
4582
4581 [push]
4583 [push]
4582 pushvars.server = true
4584 pushvars.server = true
4583
4585
4584 Returns 0 if push was successful, 1 if nothing to push.
4586 Returns 0 if push was successful, 1 if nothing to push.
4585 """
4587 """
4586
4588
4587 opts = pycompat.byteskwargs(opts)
4589 opts = pycompat.byteskwargs(opts)
4588 if opts.get('bookmark'):
4590 if opts.get('bookmark'):
4589 ui.setconfig('bookmarks', 'pushing', opts['bookmark'], 'push')
4591 ui.setconfig('bookmarks', 'pushing', opts['bookmark'], 'push')
4590 for b in opts['bookmark']:
4592 for b in opts['bookmark']:
4591 # translate -B options to -r so changesets get pushed
4593 # translate -B options to -r so changesets get pushed
4592 b = repo._bookmarks.expandname(b)
4594 b = repo._bookmarks.expandname(b)
4593 if b in repo._bookmarks:
4595 if b in repo._bookmarks:
4594 opts.setdefault('rev', []).append(b)
4596 opts.setdefault('rev', []).append(b)
4595 else:
4597 else:
4596 # if we try to push a deleted bookmark, translate it to null
4598 # if we try to push a deleted bookmark, translate it to null
4597 # this lets simultaneous -r, -b options continue working
4599 # this lets simultaneous -r, -b options continue working
4598 opts.setdefault('rev', []).append("null")
4600 opts.setdefault('rev', []).append("null")
4599
4601
4600 path = ui.paths.getpath(dest, default=('default-push', 'default'))
4602 path = ui.paths.getpath(dest, default=('default-push', 'default'))
4601 if not path:
4603 if not path:
4602 raise error.Abort(_('default repository not configured!'),
4604 raise error.Abort(_('default repository not configured!'),
4603 hint=_("see 'hg help config.paths'"))
4605 hint=_("see 'hg help config.paths'"))
4604 dest = path.pushloc or path.loc
4606 dest = path.pushloc or path.loc
4605 branches = (path.branch, opts.get('branch') or [])
4607 branches = (path.branch, opts.get('branch') or [])
4606 ui.status(_('pushing to %s\n') % util.hidepassword(dest))
4608 ui.status(_('pushing to %s\n') % util.hidepassword(dest))
4607 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
4609 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
4608 other = hg.peer(repo, opts, dest)
4610 other = hg.peer(repo, opts, dest)
4609
4611
4610 if revs:
4612 if revs:
4611 revs = [repo[r].node() for r in scmutil.revrange(repo, revs)]
4613 revs = [repo[r].node() for r in scmutil.revrange(repo, revs)]
4612 if not revs:
4614 if not revs:
4613 raise error.Abort(_("specified revisions evaluate to an empty set"),
4615 raise error.Abort(_("specified revisions evaluate to an empty set"),
4614 hint=_("use different revision arguments"))
4616 hint=_("use different revision arguments"))
4615 elif path.pushrev:
4617 elif path.pushrev:
4616 # It doesn't make any sense to specify ancestor revisions. So limit
4618 # It doesn't make any sense to specify ancestor revisions. So limit
4617 # to DAG heads to make discovery simpler.
4619 # to DAG heads to make discovery simpler.
4618 expr = revsetlang.formatspec('heads(%r)', path.pushrev)
4620 expr = revsetlang.formatspec('heads(%r)', path.pushrev)
4619 revs = scmutil.revrange(repo, [expr])
4621 revs = scmutil.revrange(repo, [expr])
4620 revs = [repo[rev].node() for rev in revs]
4622 revs = [repo[rev].node() for rev in revs]
4621 if not revs:
4623 if not revs:
4622 raise error.Abort(_('default push revset for path evaluates to an '
4624 raise error.Abort(_('default push revset for path evaluates to an '
4623 'empty set'))
4625 'empty set'))
4624
4626
4625 repo._subtoppath = dest
4627 repo._subtoppath = dest
4626 try:
4628 try:
4627 # push subrepos depth-first for coherent ordering
4629 # push subrepos depth-first for coherent ordering
4628 c = repo['.']
4630 c = repo['.']
4629 subs = c.substate # only repos that are committed
4631 subs = c.substate # only repos that are committed
4630 for s in sorted(subs):
4632 for s in sorted(subs):
4631 result = c.sub(s).push(opts)
4633 result = c.sub(s).push(opts)
4632 if result == 0:
4634 if result == 0:
4633 return not result
4635 return not result
4634 finally:
4636 finally:
4635 del repo._subtoppath
4637 del repo._subtoppath
4636
4638
4637 opargs = dict(opts.get('opargs', {})) # copy opargs since we may mutate it
4639 opargs = dict(opts.get('opargs', {})) # copy opargs since we may mutate it
4638 opargs.setdefault('pushvars', []).extend(opts.get('pushvars', []))
4640 opargs.setdefault('pushvars', []).extend(opts.get('pushvars', []))
4639
4641
4640 pushop = exchange.push(repo, other, opts.get('force'), revs=revs,
4642 pushop = exchange.push(repo, other, opts.get('force'), revs=revs,
4641 newbranch=opts.get('new_branch'),
4643 newbranch=opts.get('new_branch'),
4642 bookmarks=opts.get('bookmark', ()),
4644 bookmarks=opts.get('bookmark', ()),
4643 publish=opts.get('publish'),
4645 publish=opts.get('publish'),
4644 opargs=opargs)
4646 opargs=opargs)
4645
4647
4646 result = not pushop.cgresult
4648 result = not pushop.cgresult
4647
4649
4648 if pushop.bkresult is not None:
4650 if pushop.bkresult is not None:
4649 if pushop.bkresult == 2:
4651 if pushop.bkresult == 2:
4650 result = 2
4652 result = 2
4651 elif not result and pushop.bkresult:
4653 elif not result and pushop.bkresult:
4652 result = 2
4654 result = 2
4653
4655
4654 return result
4656 return result
4655
4657
4656 @command('recover', [], helpcategory=command.CATEGORY_MAINTENANCE)
4658 @command('recover', [], helpcategory=command.CATEGORY_MAINTENANCE)
4657 def recover(ui, repo):
4659 def recover(ui, repo):
4658 """roll back an interrupted transaction
4660 """roll back an interrupted transaction
4659
4661
4660 Recover from an interrupted commit or pull.
4662 Recover from an interrupted commit or pull.
4661
4663
4662 This command tries to fix the repository status after an
4664 This command tries to fix the repository status after an
4663 interrupted operation. It should only be necessary when Mercurial
4665 interrupted operation. It should only be necessary when Mercurial
4664 suggests it.
4666 suggests it.
4665
4667
4666 Returns 0 if successful, 1 if nothing to recover or verify fails.
4668 Returns 0 if successful, 1 if nothing to recover or verify fails.
4667 """
4669 """
4668 if repo.recover():
4670 if repo.recover():
4669 return hg.verify(repo)
4671 return hg.verify(repo)
4670 return 1
4672 return 1
4671
4673
4672 @command('remove|rm',
4674 @command('remove|rm',
4673 [('A', 'after', None, _('record delete for missing files')),
4675 [('A', 'after', None, _('record delete for missing files')),
4674 ('f', 'force', None,
4676 ('f', 'force', None,
4675 _('forget added files, delete modified files')),
4677 _('forget added files, delete modified files')),
4676 ] + subrepoopts + walkopts + dryrunopts,
4678 ] + subrepoopts + walkopts + dryrunopts,
4677 _('[OPTION]... FILE...'),
4679 _('[OPTION]... FILE...'),
4678 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
4680 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
4679 helpbasic=True, inferrepo=True)
4681 helpbasic=True, inferrepo=True)
4680 def remove(ui, repo, *pats, **opts):
4682 def remove(ui, repo, *pats, **opts):
4681 """remove the specified files on the next commit
4683 """remove the specified files on the next commit
4682
4684
4683 Schedule the indicated files for removal from the current branch.
4685 Schedule the indicated files for removal from the current branch.
4684
4686
4685 This command schedules the files to be removed at the next commit.
4687 This command schedules the files to be removed at the next commit.
4686 To undo a remove before that, see :hg:`revert`. To undo added
4688 To undo a remove before that, see :hg:`revert`. To undo added
4687 files, see :hg:`forget`.
4689 files, see :hg:`forget`.
4688
4690
4689 .. container:: verbose
4691 .. container:: verbose
4690
4692
4691 -A/--after can be used to remove only files that have already
4693 -A/--after can be used to remove only files that have already
4692 been deleted, -f/--force can be used to force deletion, and -Af
4694 been deleted, -f/--force can be used to force deletion, and -Af
4693 can be used to remove files from the next revision without
4695 can be used to remove files from the next revision without
4694 deleting them from the working directory.
4696 deleting them from the working directory.
4695
4697
4696 The following table details the behavior of remove for different
4698 The following table details the behavior of remove for different
4697 file states (columns) and option combinations (rows). The file
4699 file states (columns) and option combinations (rows). The file
4698 states are Added [A], Clean [C], Modified [M] and Missing [!]
4700 states are Added [A], Clean [C], Modified [M] and Missing [!]
4699 (as reported by :hg:`status`). The actions are Warn, Remove
4701 (as reported by :hg:`status`). The actions are Warn, Remove
4700 (from branch) and Delete (from disk):
4702 (from branch) and Delete (from disk):
4701
4703
4702 ========= == == == ==
4704 ========= == == == ==
4703 opt/state A C M !
4705 opt/state A C M !
4704 ========= == == == ==
4706 ========= == == == ==
4705 none W RD W R
4707 none W RD W R
4706 -f R RD RD R
4708 -f R RD RD R
4707 -A W W W R
4709 -A W W W R
4708 -Af R R R R
4710 -Af R R R R
4709 ========= == == == ==
4711 ========= == == == ==
4710
4712
4711 .. note::
4713 .. note::
4712
4714
4713 :hg:`remove` never deletes files in Added [A] state from the
4715 :hg:`remove` never deletes files in Added [A] state from the
4714 working directory, not even if ``--force`` is specified.
4716 working directory, not even if ``--force`` is specified.
4715
4717
4716 Returns 0 on success, 1 if any warnings encountered.
4718 Returns 0 on success, 1 if any warnings encountered.
4717 """
4719 """
4718
4720
4719 opts = pycompat.byteskwargs(opts)
4721 opts = pycompat.byteskwargs(opts)
4720 after, force = opts.get('after'), opts.get('force')
4722 after, force = opts.get('after'), opts.get('force')
4721 dryrun = opts.get('dry_run')
4723 dryrun = opts.get('dry_run')
4722 if not pats and not after:
4724 if not pats and not after:
4723 raise error.Abort(_('no files specified'))
4725 raise error.Abort(_('no files specified'))
4724
4726
4725 m = scmutil.match(repo[None], pats, opts)
4727 m = scmutil.match(repo[None], pats, opts)
4726 subrepos = opts.get('subrepos')
4728 subrepos = opts.get('subrepos')
4727 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
4729 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
4728 return cmdutil.remove(ui, repo, m, "", uipathfn, after, force, subrepos,
4730 return cmdutil.remove(ui, repo, m, "", uipathfn, after, force, subrepos,
4729 dryrun=dryrun)
4731 dryrun=dryrun)
4730
4732
4731 @command('rename|move|mv',
4733 @command('rename|move|mv',
4732 [('A', 'after', None, _('record a rename that has already occurred')),
4734 [('A', 'after', None, _('record a rename that has already occurred')),
4733 ('f', 'force', None, _('forcibly copy over an existing managed file')),
4735 ('f', 'force', None, _('forcibly copy over an existing managed file')),
4734 ] + walkopts + dryrunopts,
4736 ] + walkopts + dryrunopts,
4735 _('[OPTION]... SOURCE... DEST'),
4737 _('[OPTION]... SOURCE... DEST'),
4736 helpcategory=command.CATEGORY_WORKING_DIRECTORY)
4738 helpcategory=command.CATEGORY_WORKING_DIRECTORY)
4737 def rename(ui, repo, *pats, **opts):
4739 def rename(ui, repo, *pats, **opts):
4738 """rename files; equivalent of copy + remove
4740 """rename files; equivalent of copy + remove
4739
4741
4740 Mark dest as copies of sources; mark sources for deletion. If dest
4742 Mark dest as copies of sources; mark sources for deletion. If dest
4741 is a directory, copies are put in that directory. If dest is a
4743 is a directory, copies are put in that directory. If dest is a
4742 file, there can only be one source.
4744 file, there can only be one source.
4743
4745
4744 By default, this command copies the contents of files as they
4746 By default, this command copies the contents of files as they
4745 exist in the working directory. If invoked with -A/--after, the
4747 exist in the working directory. If invoked with -A/--after, the
4746 operation is recorded, but no copying is performed.
4748 operation is recorded, but no copying is performed.
4747
4749
4748 This command takes effect at the next commit. To undo a rename
4750 This command takes effect at the next commit. To undo a rename
4749 before that, see :hg:`revert`.
4751 before that, see :hg:`revert`.
4750
4752
4751 Returns 0 on success, 1 if errors are encountered.
4753 Returns 0 on success, 1 if errors are encountered.
4752 """
4754 """
4753 opts = pycompat.byteskwargs(opts)
4755 opts = pycompat.byteskwargs(opts)
4754 with repo.wlock(False):
4756 with repo.wlock(False):
4755 return cmdutil.copy(ui, repo, pats, opts, rename=True)
4757 return cmdutil.copy(ui, repo, pats, opts, rename=True)
4756
4758
4757 @command('resolve',
4759 @command('resolve',
4758 [('a', 'all', None, _('select all unresolved files')),
4760 [('a', 'all', None, _('select all unresolved files')),
4759 ('l', 'list', None, _('list state of files needing merge')),
4761 ('l', 'list', None, _('list state of files needing merge')),
4760 ('m', 'mark', None, _('mark files as resolved')),
4762 ('m', 'mark', None, _('mark files as resolved')),
4761 ('u', 'unmark', None, _('mark files as unresolved')),
4763 ('u', 'unmark', None, _('mark files as unresolved')),
4762 ('n', 'no-status', None, _('hide status prefix')),
4764 ('n', 'no-status', None, _('hide status prefix')),
4763 ('', 're-merge', None, _('re-merge files'))]
4765 ('', 're-merge', None, _('re-merge files'))]
4764 + mergetoolopts + walkopts + formatteropts,
4766 + mergetoolopts + walkopts + formatteropts,
4765 _('[OPTION]... [FILE]...'),
4767 _('[OPTION]... [FILE]...'),
4766 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
4768 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
4767 inferrepo=True)
4769 inferrepo=True)
4768 def resolve(ui, repo, *pats, **opts):
4770 def resolve(ui, repo, *pats, **opts):
4769 """redo merges or set/view the merge status of files
4771 """redo merges or set/view the merge status of files
4770
4772
4771 Merges with unresolved conflicts are often the result of
4773 Merges with unresolved conflicts are often the result of
4772 non-interactive merging using the ``internal:merge`` configuration
4774 non-interactive merging using the ``internal:merge`` configuration
4773 setting, or a command-line merge tool like ``diff3``. The resolve
4775 setting, or a command-line merge tool like ``diff3``. The resolve
4774 command is used to manage the files involved in a merge, after
4776 command is used to manage the files involved in a merge, after
4775 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
4777 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
4776 working directory must have two parents). See :hg:`help
4778 working directory must have two parents). See :hg:`help
4777 merge-tools` for information on configuring merge tools.
4779 merge-tools` for information on configuring merge tools.
4778
4780
4779 The resolve command can be used in the following ways:
4781 The resolve command can be used in the following ways:
4780
4782
4781 - :hg:`resolve [--re-merge] [--tool TOOL] FILE...`: attempt to re-merge
4783 - :hg:`resolve [--re-merge] [--tool TOOL] FILE...`: attempt to re-merge
4782 the specified files, discarding any previous merge attempts. Re-merging
4784 the specified files, discarding any previous merge attempts. Re-merging
4783 is not performed for files already marked as resolved. Use ``--all/-a``
4785 is not performed for files already marked as resolved. Use ``--all/-a``
4784 to select all unresolved files. ``--tool`` can be used to specify
4786 to select all unresolved files. ``--tool`` can be used to specify
4785 the merge tool used for the given files. It overrides the HGMERGE
4787 the merge tool used for the given files. It overrides the HGMERGE
4786 environment variable and your configuration files. Previous file
4788 environment variable and your configuration files. Previous file
4787 contents are saved with a ``.orig`` suffix.
4789 contents are saved with a ``.orig`` suffix.
4788
4790
4789 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
4791 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
4790 (e.g. after having manually fixed-up the files). The default is
4792 (e.g. after having manually fixed-up the files). The default is
4791 to mark all unresolved files.
4793 to mark all unresolved files.
4792
4794
4793 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
4795 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
4794 default is to mark all resolved files.
4796 default is to mark all resolved files.
4795
4797
4796 - :hg:`resolve -l`: list files which had or still have conflicts.
4798 - :hg:`resolve -l`: list files which had or still have conflicts.
4797 In the printed list, ``U`` = unresolved and ``R`` = resolved.
4799 In the printed list, ``U`` = unresolved and ``R`` = resolved.
4798 You can use ``set:unresolved()`` or ``set:resolved()`` to filter
4800 You can use ``set:unresolved()`` or ``set:resolved()`` to filter
4799 the list. See :hg:`help filesets` for details.
4801 the list. See :hg:`help filesets` for details.
4800
4802
4801 .. note::
4803 .. note::
4802
4804
4803 Mercurial will not let you commit files with unresolved merge
4805 Mercurial will not let you commit files with unresolved merge
4804 conflicts. You must use :hg:`resolve -m ...` before you can
4806 conflicts. You must use :hg:`resolve -m ...` before you can
4805 commit after a conflicting merge.
4807 commit after a conflicting merge.
4806
4808
4807 .. container:: verbose
4809 .. container:: verbose
4808
4810
4809 Template:
4811 Template:
4810
4812
4811 The following keywords are supported in addition to the common template
4813 The following keywords are supported in addition to the common template
4812 keywords and functions. See also :hg:`help templates`.
4814 keywords and functions. See also :hg:`help templates`.
4813
4815
4814 :mergestatus: String. Character denoting merge conflicts, ``U`` or ``R``.
4816 :mergestatus: String. Character denoting merge conflicts, ``U`` or ``R``.
4815 :path: String. Repository-absolute path of the file.
4817 :path: String. Repository-absolute path of the file.
4816
4818
4817 Returns 0 on success, 1 if any files fail a resolve attempt.
4819 Returns 0 on success, 1 if any files fail a resolve attempt.
4818 """
4820 """
4819
4821
4820 opts = pycompat.byteskwargs(opts)
4822 opts = pycompat.byteskwargs(opts)
4821 confirm = ui.configbool('commands', 'resolve.confirm')
4823 confirm = ui.configbool('commands', 'resolve.confirm')
4822 flaglist = 'all mark unmark list no_status re_merge'.split()
4824 flaglist = 'all mark unmark list no_status re_merge'.split()
4823 all, mark, unmark, show, nostatus, remerge = \
4825 all, mark, unmark, show, nostatus, remerge = \
4824 [opts.get(o) for o in flaglist]
4826 [opts.get(o) for o in flaglist]
4825
4827
4826 actioncount = len(list(filter(None, [show, mark, unmark, remerge])))
4828 actioncount = len(list(filter(None, [show, mark, unmark, remerge])))
4827 if actioncount > 1:
4829 if actioncount > 1:
4828 raise error.Abort(_("too many actions specified"))
4830 raise error.Abort(_("too many actions specified"))
4829 elif (actioncount == 0
4831 elif (actioncount == 0
4830 and ui.configbool('commands', 'resolve.explicit-re-merge')):
4832 and ui.configbool('commands', 'resolve.explicit-re-merge')):
4831 hint = _('use --mark, --unmark, --list or --re-merge')
4833 hint = _('use --mark, --unmark, --list or --re-merge')
4832 raise error.Abort(_('no action specified'), hint=hint)
4834 raise error.Abort(_('no action specified'), hint=hint)
4833 if pats and all:
4835 if pats and all:
4834 raise error.Abort(_("can't specify --all and patterns"))
4836 raise error.Abort(_("can't specify --all and patterns"))
4835 if not (all or pats or show or mark or unmark):
4837 if not (all or pats or show or mark or unmark):
4836 raise error.Abort(_('no files or directories specified'),
4838 raise error.Abort(_('no files or directories specified'),
4837 hint=('use --all to re-merge all unresolved files'))
4839 hint=('use --all to re-merge all unresolved files'))
4838
4840
4839 if confirm:
4841 if confirm:
4840 if all:
4842 if all:
4841 if ui.promptchoice(_(b're-merge all unresolved files (yn)?'
4843 if ui.promptchoice(_(b're-merge all unresolved files (yn)?'
4842 b'$$ &Yes $$ &No')):
4844 b'$$ &Yes $$ &No')):
4843 raise error.Abort(_('user quit'))
4845 raise error.Abort(_('user quit'))
4844 if mark and not pats:
4846 if mark and not pats:
4845 if ui.promptchoice(_(b'mark all unresolved files as resolved (yn)?'
4847 if ui.promptchoice(_(b'mark all unresolved files as resolved (yn)?'
4846 b'$$ &Yes $$ &No')):
4848 b'$$ &Yes $$ &No')):
4847 raise error.Abort(_('user quit'))
4849 raise error.Abort(_('user quit'))
4848 if unmark and not pats:
4850 if unmark and not pats:
4849 if ui.promptchoice(_(b'mark all resolved files as unresolved (yn)?'
4851 if ui.promptchoice(_(b'mark all resolved files as unresolved (yn)?'
4850 b'$$ &Yes $$ &No')):
4852 b'$$ &Yes $$ &No')):
4851 raise error.Abort(_('user quit'))
4853 raise error.Abort(_('user quit'))
4852
4854
4853 uipathfn = scmutil.getuipathfn(repo)
4855 uipathfn = scmutil.getuipathfn(repo)
4854
4856
4855 if show:
4857 if show:
4856 ui.pager('resolve')
4858 ui.pager('resolve')
4857 fm = ui.formatter('resolve', opts)
4859 fm = ui.formatter('resolve', opts)
4858 ms = mergemod.mergestate.read(repo)
4860 ms = mergemod.mergestate.read(repo)
4859 wctx = repo[None]
4861 wctx = repo[None]
4860 m = scmutil.match(wctx, pats, opts)
4862 m = scmutil.match(wctx, pats, opts)
4861
4863
4862 # Labels and keys based on merge state. Unresolved path conflicts show
4864 # Labels and keys based on merge state. Unresolved path conflicts show
4863 # as 'P'. Resolved path conflicts show as 'R', the same as normal
4865 # as 'P'. Resolved path conflicts show as 'R', the same as normal
4864 # resolved conflicts.
4866 # resolved conflicts.
4865 mergestateinfo = {
4867 mergestateinfo = {
4866 mergemod.MERGE_RECORD_UNRESOLVED: ('resolve.unresolved', 'U'),
4868 mergemod.MERGE_RECORD_UNRESOLVED: ('resolve.unresolved', 'U'),
4867 mergemod.MERGE_RECORD_RESOLVED: ('resolve.resolved', 'R'),
4869 mergemod.MERGE_RECORD_RESOLVED: ('resolve.resolved', 'R'),
4868 mergemod.MERGE_RECORD_UNRESOLVED_PATH: ('resolve.unresolved', 'P'),
4870 mergemod.MERGE_RECORD_UNRESOLVED_PATH: ('resolve.unresolved', 'P'),
4869 mergemod.MERGE_RECORD_RESOLVED_PATH: ('resolve.resolved', 'R'),
4871 mergemod.MERGE_RECORD_RESOLVED_PATH: ('resolve.resolved', 'R'),
4870 mergemod.MERGE_RECORD_DRIVER_RESOLVED: ('resolve.driverresolved',
4872 mergemod.MERGE_RECORD_DRIVER_RESOLVED: ('resolve.driverresolved',
4871 'D'),
4873 'D'),
4872 }
4874 }
4873
4875
4874 for f in ms:
4876 for f in ms:
4875 if not m(f):
4877 if not m(f):
4876 continue
4878 continue
4877
4879
4878 label, key = mergestateinfo[ms[f]]
4880 label, key = mergestateinfo[ms[f]]
4879 fm.startitem()
4881 fm.startitem()
4880 fm.context(ctx=wctx)
4882 fm.context(ctx=wctx)
4881 fm.condwrite(not nostatus, 'mergestatus', '%s ', key, label=label)
4883 fm.condwrite(not nostatus, 'mergestatus', '%s ', key, label=label)
4882 fm.data(path=f)
4884 fm.data(path=f)
4883 fm.plain('%s\n' % uipathfn(f), label=label)
4885 fm.plain('%s\n' % uipathfn(f), label=label)
4884 fm.end()
4886 fm.end()
4885 return 0
4887 return 0
4886
4888
4887 with repo.wlock():
4889 with repo.wlock():
4888 ms = mergemod.mergestate.read(repo)
4890 ms = mergemod.mergestate.read(repo)
4889
4891
4890 if not (ms.active() or repo.dirstate.p2() != nullid):
4892 if not (ms.active() or repo.dirstate.p2() != nullid):
4891 raise error.Abort(
4893 raise error.Abort(
4892 _('resolve command not applicable when not merging'))
4894 _('resolve command not applicable when not merging'))
4893
4895
4894 wctx = repo[None]
4896 wctx = repo[None]
4895
4897
4896 if (ms.mergedriver
4898 if (ms.mergedriver
4897 and ms.mdstate() == mergemod.MERGE_DRIVER_STATE_UNMARKED):
4899 and ms.mdstate() == mergemod.MERGE_DRIVER_STATE_UNMARKED):
4898 proceed = mergemod.driverpreprocess(repo, ms, wctx)
4900 proceed = mergemod.driverpreprocess(repo, ms, wctx)
4899 ms.commit()
4901 ms.commit()
4900 # allow mark and unmark to go through
4902 # allow mark and unmark to go through
4901 if not mark and not unmark and not proceed:
4903 if not mark and not unmark and not proceed:
4902 return 1
4904 return 1
4903
4905
4904 m = scmutil.match(wctx, pats, opts)
4906 m = scmutil.match(wctx, pats, opts)
4905 ret = 0
4907 ret = 0
4906 didwork = False
4908 didwork = False
4907 runconclude = False
4909 runconclude = False
4908
4910
4909 tocomplete = []
4911 tocomplete = []
4910 hasconflictmarkers = []
4912 hasconflictmarkers = []
4911 if mark:
4913 if mark:
4912 markcheck = ui.config('commands', 'resolve.mark-check')
4914 markcheck = ui.config('commands', 'resolve.mark-check')
4913 if markcheck not in ['warn', 'abort']:
4915 if markcheck not in ['warn', 'abort']:
4914 # Treat all invalid / unrecognized values as 'none'.
4916 # Treat all invalid / unrecognized values as 'none'.
4915 markcheck = False
4917 markcheck = False
4916 for f in ms:
4918 for f in ms:
4917 if not m(f):
4919 if not m(f):
4918 continue
4920 continue
4919
4921
4920 didwork = True
4922 didwork = True
4921
4923
4922 # don't let driver-resolved files be marked, and run the conclude
4924 # don't let driver-resolved files be marked, and run the conclude
4923 # step if asked to resolve
4925 # step if asked to resolve
4924 if ms[f] == mergemod.MERGE_RECORD_DRIVER_RESOLVED:
4926 if ms[f] == mergemod.MERGE_RECORD_DRIVER_RESOLVED:
4925 exact = m.exact(f)
4927 exact = m.exact(f)
4926 if mark:
4928 if mark:
4927 if exact:
4929 if exact:
4928 ui.warn(_('not marking %s as it is driver-resolved\n')
4930 ui.warn(_('not marking %s as it is driver-resolved\n')
4929 % uipathfn(f))
4931 % uipathfn(f))
4930 elif unmark:
4932 elif unmark:
4931 if exact:
4933 if exact:
4932 ui.warn(_('not unmarking %s as it is driver-resolved\n')
4934 ui.warn(_('not unmarking %s as it is driver-resolved\n')
4933 % uipathfn(f))
4935 % uipathfn(f))
4934 else:
4936 else:
4935 runconclude = True
4937 runconclude = True
4936 continue
4938 continue
4937
4939
4938 # path conflicts must be resolved manually
4940 # path conflicts must be resolved manually
4939 if ms[f] in (mergemod.MERGE_RECORD_UNRESOLVED_PATH,
4941 if ms[f] in (mergemod.MERGE_RECORD_UNRESOLVED_PATH,
4940 mergemod.MERGE_RECORD_RESOLVED_PATH):
4942 mergemod.MERGE_RECORD_RESOLVED_PATH):
4941 if mark:
4943 if mark:
4942 ms.mark(f, mergemod.MERGE_RECORD_RESOLVED_PATH)
4944 ms.mark(f, mergemod.MERGE_RECORD_RESOLVED_PATH)
4943 elif unmark:
4945 elif unmark:
4944 ms.mark(f, mergemod.MERGE_RECORD_UNRESOLVED_PATH)
4946 ms.mark(f, mergemod.MERGE_RECORD_UNRESOLVED_PATH)
4945 elif ms[f] == mergemod.MERGE_RECORD_UNRESOLVED_PATH:
4947 elif ms[f] == mergemod.MERGE_RECORD_UNRESOLVED_PATH:
4946 ui.warn(_('%s: path conflict must be resolved manually\n')
4948 ui.warn(_('%s: path conflict must be resolved manually\n')
4947 % uipathfn(f))
4949 % uipathfn(f))
4948 continue
4950 continue
4949
4951
4950 if mark:
4952 if mark:
4951 if markcheck:
4953 if markcheck:
4952 fdata = repo.wvfs.tryread(f)
4954 fdata = repo.wvfs.tryread(f)
4953 if filemerge.hasconflictmarkers(fdata) and \
4955 if filemerge.hasconflictmarkers(fdata) and \
4954 ms[f] != mergemod.MERGE_RECORD_RESOLVED:
4956 ms[f] != mergemod.MERGE_RECORD_RESOLVED:
4955 hasconflictmarkers.append(f)
4957 hasconflictmarkers.append(f)
4956 ms.mark(f, mergemod.MERGE_RECORD_RESOLVED)
4958 ms.mark(f, mergemod.MERGE_RECORD_RESOLVED)
4957 elif unmark:
4959 elif unmark:
4958 ms.mark(f, mergemod.MERGE_RECORD_UNRESOLVED)
4960 ms.mark(f, mergemod.MERGE_RECORD_UNRESOLVED)
4959 else:
4961 else:
4960 # backup pre-resolve (merge uses .orig for its own purposes)
4962 # backup pre-resolve (merge uses .orig for its own purposes)
4961 a = repo.wjoin(f)
4963 a = repo.wjoin(f)
4962 try:
4964 try:
4963 util.copyfile(a, a + ".resolve")
4965 util.copyfile(a, a + ".resolve")
4964 except (IOError, OSError) as inst:
4966 except (IOError, OSError) as inst:
4965 if inst.errno != errno.ENOENT:
4967 if inst.errno != errno.ENOENT:
4966 raise
4968 raise
4967
4969
4968 try:
4970 try:
4969 # preresolve file
4971 # preresolve file
4970 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
4972 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
4971 with ui.configoverride(overrides, 'resolve'):
4973 with ui.configoverride(overrides, 'resolve'):
4972 complete, r = ms.preresolve(f, wctx)
4974 complete, r = ms.preresolve(f, wctx)
4973 if not complete:
4975 if not complete:
4974 tocomplete.append(f)
4976 tocomplete.append(f)
4975 elif r:
4977 elif r:
4976 ret = 1
4978 ret = 1
4977 finally:
4979 finally:
4978 ms.commit()
4980 ms.commit()
4979
4981
4980 # replace filemerge's .orig file with our resolve file, but only
4982 # replace filemerge's .orig file with our resolve file, but only
4981 # for merges that are complete
4983 # for merges that are complete
4982 if complete:
4984 if complete:
4983 try:
4985 try:
4984 util.rename(a + ".resolve",
4986 util.rename(a + ".resolve",
4985 scmutil.backuppath(ui, repo, f))
4987 scmutil.backuppath(ui, repo, f))
4986 except OSError as inst:
4988 except OSError as inst:
4987 if inst.errno != errno.ENOENT:
4989 if inst.errno != errno.ENOENT:
4988 raise
4990 raise
4989
4991
4990 if hasconflictmarkers:
4992 if hasconflictmarkers:
4991 ui.warn(_('warning: the following files still have conflict '
4993 ui.warn(_('warning: the following files still have conflict '
4992 'markers:\n') + ''.join(' ' + uipathfn(f) + '\n'
4994 'markers:\n') + ''.join(' ' + uipathfn(f) + '\n'
4993 for f in hasconflictmarkers))
4995 for f in hasconflictmarkers))
4994 if markcheck == 'abort' and not all and not pats:
4996 if markcheck == 'abort' and not all and not pats:
4995 raise error.Abort(_('conflict markers detected'),
4997 raise error.Abort(_('conflict markers detected'),
4996 hint=_('use --all to mark anyway'))
4998 hint=_('use --all to mark anyway'))
4997
4999
4998 for f in tocomplete:
5000 for f in tocomplete:
4999 try:
5001 try:
5000 # resolve file
5002 # resolve file
5001 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
5003 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
5002 with ui.configoverride(overrides, 'resolve'):
5004 with ui.configoverride(overrides, 'resolve'):
5003 r = ms.resolve(f, wctx)
5005 r = ms.resolve(f, wctx)
5004 if r:
5006 if r:
5005 ret = 1
5007 ret = 1
5006 finally:
5008 finally:
5007 ms.commit()
5009 ms.commit()
5008
5010
5009 # replace filemerge's .orig file with our resolve file
5011 # replace filemerge's .orig file with our resolve file
5010 a = repo.wjoin(f)
5012 a = repo.wjoin(f)
5011 try:
5013 try:
5012 util.rename(a + ".resolve", scmutil.backuppath(ui, repo, f))
5014 util.rename(a + ".resolve", scmutil.backuppath(ui, repo, f))
5013 except OSError as inst:
5015 except OSError as inst:
5014 if inst.errno != errno.ENOENT:
5016 if inst.errno != errno.ENOENT:
5015 raise
5017 raise
5016
5018
5017 ms.commit()
5019 ms.commit()
5018 ms.recordactions()
5020 ms.recordactions()
5019
5021
5020 if not didwork and pats:
5022 if not didwork and pats:
5021 hint = None
5023 hint = None
5022 if not any([p for p in pats if p.find(':') >= 0]):
5024 if not any([p for p in pats if p.find(':') >= 0]):
5023 pats = ['path:%s' % p for p in pats]
5025 pats = ['path:%s' % p for p in pats]
5024 m = scmutil.match(wctx, pats, opts)
5026 m = scmutil.match(wctx, pats, opts)
5025 for f in ms:
5027 for f in ms:
5026 if not m(f):
5028 if not m(f):
5027 continue
5029 continue
5028 def flag(o):
5030 def flag(o):
5029 if o == 're_merge':
5031 if o == 're_merge':
5030 return '--re-merge '
5032 return '--re-merge '
5031 return '-%s ' % o[0:1]
5033 return '-%s ' % o[0:1]
5032 flags = ''.join([flag(o) for o in flaglist if opts.get(o)])
5034 flags = ''.join([flag(o) for o in flaglist if opts.get(o)])
5033 hint = _("(try: hg resolve %s%s)\n") % (
5035 hint = _("(try: hg resolve %s%s)\n") % (
5034 flags,
5036 flags,
5035 ' '.join(pats))
5037 ' '.join(pats))
5036 break
5038 break
5037 ui.warn(_("arguments do not match paths that need resolving\n"))
5039 ui.warn(_("arguments do not match paths that need resolving\n"))
5038 if hint:
5040 if hint:
5039 ui.warn(hint)
5041 ui.warn(hint)
5040 elif ms.mergedriver and ms.mdstate() != 's':
5042 elif ms.mergedriver and ms.mdstate() != 's':
5041 # run conclude step when either a driver-resolved file is requested
5043 # run conclude step when either a driver-resolved file is requested
5042 # or there are no driver-resolved files
5044 # or there are no driver-resolved files
5043 # we can't use 'ret' to determine whether any files are unresolved
5045 # we can't use 'ret' to determine whether any files are unresolved
5044 # because we might not have tried to resolve some
5046 # because we might not have tried to resolve some
5045 if ((runconclude or not list(ms.driverresolved()))
5047 if ((runconclude or not list(ms.driverresolved()))
5046 and not list(ms.unresolved())):
5048 and not list(ms.unresolved())):
5047 proceed = mergemod.driverconclude(repo, ms, wctx)
5049 proceed = mergemod.driverconclude(repo, ms, wctx)
5048 ms.commit()
5050 ms.commit()
5049 if not proceed:
5051 if not proceed:
5050 return 1
5052 return 1
5051
5053
5052 # Nudge users into finishing an unfinished operation
5054 # Nudge users into finishing an unfinished operation
5053 unresolvedf = list(ms.unresolved())
5055 unresolvedf = list(ms.unresolved())
5054 driverresolvedf = list(ms.driverresolved())
5056 driverresolvedf = list(ms.driverresolved())
5055 if not unresolvedf and not driverresolvedf:
5057 if not unresolvedf and not driverresolvedf:
5056 ui.status(_('(no more unresolved files)\n'))
5058 ui.status(_('(no more unresolved files)\n'))
5057 cmdutil.checkafterresolved(repo)
5059 cmdutil.checkafterresolved(repo)
5058 elif not unresolvedf:
5060 elif not unresolvedf:
5059 ui.status(_('(no more unresolved files -- '
5061 ui.status(_('(no more unresolved files -- '
5060 'run "hg resolve --all" to conclude)\n'))
5062 'run "hg resolve --all" to conclude)\n'))
5061
5063
5062 return ret
5064 return ret
5063
5065
5064 @command('revert',
5066 @command('revert',
5065 [('a', 'all', None, _('revert all changes when no arguments given')),
5067 [('a', 'all', None, _('revert all changes when no arguments given')),
5066 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
5068 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
5067 ('r', 'rev', '', _('revert to the specified revision'), _('REV')),
5069 ('r', 'rev', '', _('revert to the specified revision'), _('REV')),
5068 ('C', 'no-backup', None, _('do not save backup copies of files')),
5070 ('C', 'no-backup', None, _('do not save backup copies of files')),
5069 ('i', 'interactive', None, _('interactively select the changes')),
5071 ('i', 'interactive', None, _('interactively select the changes')),
5070 ] + walkopts + dryrunopts,
5072 ] + walkopts + dryrunopts,
5071 _('[OPTION]... [-r REV] [NAME]...'),
5073 _('[OPTION]... [-r REV] [NAME]...'),
5072 helpcategory=command.CATEGORY_WORKING_DIRECTORY)
5074 helpcategory=command.CATEGORY_WORKING_DIRECTORY)
5073 def revert(ui, repo, *pats, **opts):
5075 def revert(ui, repo, *pats, **opts):
5074 """restore files to their checkout state
5076 """restore files to their checkout state
5075
5077
5076 .. note::
5078 .. note::
5077
5079
5078 To check out earlier revisions, you should use :hg:`update REV`.
5080 To check out earlier revisions, you should use :hg:`update REV`.
5079 To cancel an uncommitted merge (and lose your changes),
5081 To cancel an uncommitted merge (and lose your changes),
5080 use :hg:`merge --abort`.
5082 use :hg:`merge --abort`.
5081
5083
5082 With no revision specified, revert the specified files or directories
5084 With no revision specified, revert the specified files or directories
5083 to the contents they had in the parent of the working directory.
5085 to the contents they had in the parent of the working directory.
5084 This restores the contents of files to an unmodified
5086 This restores the contents of files to an unmodified
5085 state and unschedules adds, removes, copies, and renames. If the
5087 state and unschedules adds, removes, copies, and renames. If the
5086 working directory has two parents, you must explicitly specify a
5088 working directory has two parents, you must explicitly specify a
5087 revision.
5089 revision.
5088
5090
5089 Using the -r/--rev or -d/--date options, revert the given files or
5091 Using the -r/--rev or -d/--date options, revert the given files or
5090 directories to their states as of a specific revision. Because
5092 directories to their states as of a specific revision. Because
5091 revert does not change the working directory parents, this will
5093 revert does not change the working directory parents, this will
5092 cause these files to appear modified. This can be helpful to "back
5094 cause these files to appear modified. This can be helpful to "back
5093 out" some or all of an earlier change. See :hg:`backout` for a
5095 out" some or all of an earlier change. See :hg:`backout` for a
5094 related method.
5096 related method.
5095
5097
5096 Modified files are saved with a .orig suffix before reverting.
5098 Modified files are saved with a .orig suffix before reverting.
5097 To disable these backups, use --no-backup. It is possible to store
5099 To disable these backups, use --no-backup. It is possible to store
5098 the backup files in a custom directory relative to the root of the
5100 the backup files in a custom directory relative to the root of the
5099 repository by setting the ``ui.origbackuppath`` configuration
5101 repository by setting the ``ui.origbackuppath`` configuration
5100 option.
5102 option.
5101
5103
5102 See :hg:`help dates` for a list of formats valid for -d/--date.
5104 See :hg:`help dates` for a list of formats valid for -d/--date.
5103
5105
5104 See :hg:`help backout` for a way to reverse the effect of an
5106 See :hg:`help backout` for a way to reverse the effect of an
5105 earlier changeset.
5107 earlier changeset.
5106
5108
5107 Returns 0 on success.
5109 Returns 0 on success.
5108 """
5110 """
5109
5111
5110 opts = pycompat.byteskwargs(opts)
5112 opts = pycompat.byteskwargs(opts)
5111 if opts.get("date"):
5113 if opts.get("date"):
5112 if opts.get("rev"):
5114 if opts.get("rev"):
5113 raise error.Abort(_("you can't specify a revision and a date"))
5115 raise error.Abort(_("you can't specify a revision and a date"))
5114 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
5116 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
5115
5117
5116 parent, p2 = repo.dirstate.parents()
5118 parent, p2 = repo.dirstate.parents()
5117 if not opts.get('rev') and p2 != nullid:
5119 if not opts.get('rev') and p2 != nullid:
5118 # revert after merge is a trap for new users (issue2915)
5120 # revert after merge is a trap for new users (issue2915)
5119 raise error.Abort(_('uncommitted merge with no revision specified'),
5121 raise error.Abort(_('uncommitted merge with no revision specified'),
5120 hint=_("use 'hg update' or see 'hg help revert'"))
5122 hint=_("use 'hg update' or see 'hg help revert'"))
5121
5123
5122 rev = opts.get('rev')
5124 rev = opts.get('rev')
5123 if rev:
5125 if rev:
5124 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
5126 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
5125 ctx = scmutil.revsingle(repo, rev)
5127 ctx = scmutil.revsingle(repo, rev)
5126
5128
5127 if (not (pats or opts.get('include') or opts.get('exclude') or
5129 if (not (pats or opts.get('include') or opts.get('exclude') or
5128 opts.get('all') or opts.get('interactive'))):
5130 opts.get('all') or opts.get('interactive'))):
5129 msg = _("no files or directories specified")
5131 msg = _("no files or directories specified")
5130 if p2 != nullid:
5132 if p2 != nullid:
5131 hint = _("uncommitted merge, use --all to discard all changes,"
5133 hint = _("uncommitted merge, use --all to discard all changes,"
5132 " or 'hg update -C .' to abort the merge")
5134 " or 'hg update -C .' to abort the merge")
5133 raise error.Abort(msg, hint=hint)
5135 raise error.Abort(msg, hint=hint)
5134 dirty = any(repo.status())
5136 dirty = any(repo.status())
5135 node = ctx.node()
5137 node = ctx.node()
5136 if node != parent:
5138 if node != parent:
5137 if dirty:
5139 if dirty:
5138 hint = _("uncommitted changes, use --all to discard all"
5140 hint = _("uncommitted changes, use --all to discard all"
5139 " changes, or 'hg update %d' to update") % ctx.rev()
5141 " changes, or 'hg update %d' to update") % ctx.rev()
5140 else:
5142 else:
5141 hint = _("use --all to revert all files,"
5143 hint = _("use --all to revert all files,"
5142 " or 'hg update %d' to update") % ctx.rev()
5144 " or 'hg update %d' to update") % ctx.rev()
5143 elif dirty:
5145 elif dirty:
5144 hint = _("uncommitted changes, use --all to discard all changes")
5146 hint = _("uncommitted changes, use --all to discard all changes")
5145 else:
5147 else:
5146 hint = _("use --all to revert all files")
5148 hint = _("use --all to revert all files")
5147 raise error.Abort(msg, hint=hint)
5149 raise error.Abort(msg, hint=hint)
5148
5150
5149 return cmdutil.revert(ui, repo, ctx, (parent, p2), *pats,
5151 return cmdutil.revert(ui, repo, ctx, (parent, p2), *pats,
5150 **pycompat.strkwargs(opts))
5152 **pycompat.strkwargs(opts))
5151
5153
5152 @command(
5154 @command(
5153 'rollback',
5155 'rollback',
5154 dryrunopts + [('f', 'force', False, _('ignore safety measures'))],
5156 dryrunopts + [('f', 'force', False, _('ignore safety measures'))],
5155 helpcategory=command.CATEGORY_MAINTENANCE)
5157 helpcategory=command.CATEGORY_MAINTENANCE)
5156 def rollback(ui, repo, **opts):
5158 def rollback(ui, repo, **opts):
5157 """roll back the last transaction (DANGEROUS) (DEPRECATED)
5159 """roll back the last transaction (DANGEROUS) (DEPRECATED)
5158
5160
5159 Please use :hg:`commit --amend` instead of rollback to correct
5161 Please use :hg:`commit --amend` instead of rollback to correct
5160 mistakes in the last commit.
5162 mistakes in the last commit.
5161
5163
5162 This command should be used with care. There is only one level of
5164 This command should be used with care. There is only one level of
5163 rollback, and there is no way to undo a rollback. It will also
5165 rollback, and there is no way to undo a rollback. It will also
5164 restore the dirstate at the time of the last transaction, losing
5166 restore the dirstate at the time of the last transaction, losing
5165 any dirstate changes since that time. This command does not alter
5167 any dirstate changes since that time. This command does not alter
5166 the working directory.
5168 the working directory.
5167
5169
5168 Transactions are used to encapsulate the effects of all commands
5170 Transactions are used to encapsulate the effects of all commands
5169 that create new changesets or propagate existing changesets into a
5171 that create new changesets or propagate existing changesets into a
5170 repository.
5172 repository.
5171
5173
5172 .. container:: verbose
5174 .. container:: verbose
5173
5175
5174 For example, the following commands are transactional, and their
5176 For example, the following commands are transactional, and their
5175 effects can be rolled back:
5177 effects can be rolled back:
5176
5178
5177 - commit
5179 - commit
5178 - import
5180 - import
5179 - pull
5181 - pull
5180 - push (with this repository as the destination)
5182 - push (with this repository as the destination)
5181 - unbundle
5183 - unbundle
5182
5184
5183 To avoid permanent data loss, rollback will refuse to rollback a
5185 To avoid permanent data loss, rollback will refuse to rollback a
5184 commit transaction if it isn't checked out. Use --force to
5186 commit transaction if it isn't checked out. Use --force to
5185 override this protection.
5187 override this protection.
5186
5188
5187 The rollback command can be entirely disabled by setting the
5189 The rollback command can be entirely disabled by setting the
5188 ``ui.rollback`` configuration setting to false. If you're here
5190 ``ui.rollback`` configuration setting to false. If you're here
5189 because you want to use rollback and it's disabled, you can
5191 because you want to use rollback and it's disabled, you can
5190 re-enable the command by setting ``ui.rollback`` to true.
5192 re-enable the command by setting ``ui.rollback`` to true.
5191
5193
5192 This command is not intended for use on public repositories. Once
5194 This command is not intended for use on public repositories. Once
5193 changes are visible for pull by other users, rolling a transaction
5195 changes are visible for pull by other users, rolling a transaction
5194 back locally is ineffective (someone else may already have pulled
5196 back locally is ineffective (someone else may already have pulled
5195 the changes). Furthermore, a race is possible with readers of the
5197 the changes). Furthermore, a race is possible with readers of the
5196 repository; for example an in-progress pull from the repository
5198 repository; for example an in-progress pull from the repository
5197 may fail if a rollback is performed.
5199 may fail if a rollback is performed.
5198
5200
5199 Returns 0 on success, 1 if no rollback data is available.
5201 Returns 0 on success, 1 if no rollback data is available.
5200 """
5202 """
5201 if not ui.configbool('ui', 'rollback'):
5203 if not ui.configbool('ui', 'rollback'):
5202 raise error.Abort(_('rollback is disabled because it is unsafe'),
5204 raise error.Abort(_('rollback is disabled because it is unsafe'),
5203 hint=('see `hg help -v rollback` for information'))
5205 hint=('see `hg help -v rollback` for information'))
5204 return repo.rollback(dryrun=opts.get(r'dry_run'),
5206 return repo.rollback(dryrun=opts.get(r'dry_run'),
5205 force=opts.get(r'force'))
5207 force=opts.get(r'force'))
5206
5208
5207 @command(
5209 @command(
5208 'root', [], intents={INTENT_READONLY},
5210 'root', [], intents={INTENT_READONLY},
5209 helpcategory=command.CATEGORY_WORKING_DIRECTORY)
5211 helpcategory=command.CATEGORY_WORKING_DIRECTORY)
5210 def root(ui, repo):
5212 def root(ui, repo):
5211 """print the root (top) of the current working directory
5213 """print the root (top) of the current working directory
5212
5214
5213 Print the root directory of the current repository.
5215 Print the root directory of the current repository.
5214
5216
5215 Returns 0 on success.
5217 Returns 0 on success.
5216 """
5218 """
5217 ui.write(repo.root + "\n")
5219 ui.write(repo.root + "\n")
5218
5220
5219 @command('serve',
5221 @command('serve',
5220 [('A', 'accesslog', '', _('name of access log file to write to'),
5222 [('A', 'accesslog', '', _('name of access log file to write to'),
5221 _('FILE')),
5223 _('FILE')),
5222 ('d', 'daemon', None, _('run server in background')),
5224 ('d', 'daemon', None, _('run server in background')),
5223 ('', 'daemon-postexec', [], _('used internally by daemon mode')),
5225 ('', 'daemon-postexec', [], _('used internally by daemon mode')),
5224 ('E', 'errorlog', '', _('name of error log file to write to'), _('FILE')),
5226 ('E', 'errorlog', '', _('name of error log file to write to'), _('FILE')),
5225 # use string type, then we can check if something was passed
5227 # use string type, then we can check if something was passed
5226 ('p', 'port', '', _('port to listen on (default: 8000)'), _('PORT')),
5228 ('p', 'port', '', _('port to listen on (default: 8000)'), _('PORT')),
5227 ('a', 'address', '', _('address to listen on (default: all interfaces)'),
5229 ('a', 'address', '', _('address to listen on (default: all interfaces)'),
5228 _('ADDR')),
5230 _('ADDR')),
5229 ('', 'prefix', '', _('prefix path to serve from (default: server root)'),
5231 ('', 'prefix', '', _('prefix path to serve from (default: server root)'),
5230 _('PREFIX')),
5232 _('PREFIX')),
5231 ('n', 'name', '',
5233 ('n', 'name', '',
5232 _('name to show in web pages (default: working directory)'), _('NAME')),
5234 _('name to show in web pages (default: working directory)'), _('NAME')),
5233 ('', 'web-conf', '',
5235 ('', 'web-conf', '',
5234 _("name of the hgweb config file (see 'hg help hgweb')"), _('FILE')),
5236 _("name of the hgweb config file (see 'hg help hgweb')"), _('FILE')),
5235 ('', 'webdir-conf', '', _('name of the hgweb config file (DEPRECATED)'),
5237 ('', 'webdir-conf', '', _('name of the hgweb config file (DEPRECATED)'),
5236 _('FILE')),
5238 _('FILE')),
5237 ('', 'pid-file', '', _('name of file to write process ID to'), _('FILE')),
5239 ('', 'pid-file', '', _('name of file to write process ID to'), _('FILE')),
5238 ('', 'stdio', None, _('for remote clients (ADVANCED)')),
5240 ('', 'stdio', None, _('for remote clients (ADVANCED)')),
5239 ('', 'cmdserver', '', _('for remote clients (ADVANCED)'), _('MODE')),
5241 ('', 'cmdserver', '', _('for remote clients (ADVANCED)'), _('MODE')),
5240 ('t', 'templates', '', _('web templates to use'), _('TEMPLATE')),
5242 ('t', 'templates', '', _('web templates to use'), _('TEMPLATE')),
5241 ('', 'style', '', _('template style to use'), _('STYLE')),
5243 ('', 'style', '', _('template style to use'), _('STYLE')),
5242 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
5244 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
5243 ('', 'certificate', '', _('SSL certificate file'), _('FILE')),
5245 ('', 'certificate', '', _('SSL certificate file'), _('FILE')),
5244 ('', 'print-url', None, _('start and print only the URL'))]
5246 ('', 'print-url', None, _('start and print only the URL'))]
5245 + subrepoopts,
5247 + subrepoopts,
5246 _('[OPTION]...'),
5248 _('[OPTION]...'),
5247 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5249 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5248 helpbasic=True, optionalrepo=True)
5250 helpbasic=True, optionalrepo=True)
5249 def serve(ui, repo, **opts):
5251 def serve(ui, repo, **opts):
5250 """start stand-alone webserver
5252 """start stand-alone webserver
5251
5253
5252 Start a local HTTP repository browser and pull server. You can use
5254 Start a local HTTP repository browser and pull server. You can use
5253 this for ad-hoc sharing and browsing of repositories. It is
5255 this for ad-hoc sharing and browsing of repositories. It is
5254 recommended to use a real web server to serve a repository for
5256 recommended to use a real web server to serve a repository for
5255 longer periods of time.
5257 longer periods of time.
5256
5258
5257 Please note that the server does not implement access control.
5259 Please note that the server does not implement access control.
5258 This means that, by default, anybody can read from the server and
5260 This means that, by default, anybody can read from the server and
5259 nobody can write to it by default. Set the ``web.allow-push``
5261 nobody can write to it by default. Set the ``web.allow-push``
5260 option to ``*`` to allow everybody to push to the server. You
5262 option to ``*`` to allow everybody to push to the server. You
5261 should use a real web server if you need to authenticate users.
5263 should use a real web server if you need to authenticate users.
5262
5264
5263 By default, the server logs accesses to stdout and errors to
5265 By default, the server logs accesses to stdout and errors to
5264 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
5266 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
5265 files.
5267 files.
5266
5268
5267 To have the server choose a free port number to listen on, specify
5269 To have the server choose a free port number to listen on, specify
5268 a port number of 0; in this case, the server will print the port
5270 a port number of 0; in this case, the server will print the port
5269 number it uses.
5271 number it uses.
5270
5272
5271 Returns 0 on success.
5273 Returns 0 on success.
5272 """
5274 """
5273
5275
5274 opts = pycompat.byteskwargs(opts)
5276 opts = pycompat.byteskwargs(opts)
5275 if opts["stdio"] and opts["cmdserver"]:
5277 if opts["stdio"] and opts["cmdserver"]:
5276 raise error.Abort(_("cannot use --stdio with --cmdserver"))
5278 raise error.Abort(_("cannot use --stdio with --cmdserver"))
5277 if opts["print_url"] and ui.verbose:
5279 if opts["print_url"] and ui.verbose:
5278 raise error.Abort(_("cannot use --print-url with --verbose"))
5280 raise error.Abort(_("cannot use --print-url with --verbose"))
5279
5281
5280 if opts["stdio"]:
5282 if opts["stdio"]:
5281 if repo is None:
5283 if repo is None:
5282 raise error.RepoError(_("there is no Mercurial repository here"
5284 raise error.RepoError(_("there is no Mercurial repository here"
5283 " (.hg not found)"))
5285 " (.hg not found)"))
5284 s = wireprotoserver.sshserver(ui, repo)
5286 s = wireprotoserver.sshserver(ui, repo)
5285 s.serve_forever()
5287 s.serve_forever()
5286
5288
5287 service = server.createservice(ui, repo, opts)
5289 service = server.createservice(ui, repo, opts)
5288 return server.runservice(opts, initfn=service.init, runfn=service.run)
5290 return server.runservice(opts, initfn=service.init, runfn=service.run)
5289
5291
5290 _NOTTERSE = 'nothing'
5292 _NOTTERSE = 'nothing'
5291
5293
5292 @command('status|st',
5294 @command('status|st',
5293 [('A', 'all', None, _('show status of all files')),
5295 [('A', 'all', None, _('show status of all files')),
5294 ('m', 'modified', None, _('show only modified files')),
5296 ('m', 'modified', None, _('show only modified files')),
5295 ('a', 'added', None, _('show only added files')),
5297 ('a', 'added', None, _('show only added files')),
5296 ('r', 'removed', None, _('show only removed files')),
5298 ('r', 'removed', None, _('show only removed files')),
5297 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
5299 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
5298 ('c', 'clean', None, _('show only files without changes')),
5300 ('c', 'clean', None, _('show only files without changes')),
5299 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
5301 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
5300 ('i', 'ignored', None, _('show only ignored files')),
5302 ('i', 'ignored', None, _('show only ignored files')),
5301 ('n', 'no-status', None, _('hide status prefix')),
5303 ('n', 'no-status', None, _('hide status prefix')),
5302 ('t', 'terse', _NOTTERSE, _('show the terse output (EXPERIMENTAL)')),
5304 ('t', 'terse', _NOTTERSE, _('show the terse output (EXPERIMENTAL)')),
5303 ('C', 'copies', None, _('show source of copied files')),
5305 ('C', 'copies', None, _('show source of copied files')),
5304 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
5306 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
5305 ('', 'rev', [], _('show difference from revision'), _('REV')),
5307 ('', 'rev', [], _('show difference from revision'), _('REV')),
5306 ('', 'change', '', _('list the changed files of a revision'), _('REV')),
5308 ('', 'change', '', _('list the changed files of a revision'), _('REV')),
5307 ] + walkopts + subrepoopts + formatteropts,
5309 ] + walkopts + subrepoopts + formatteropts,
5308 _('[OPTION]... [FILE]...'),
5310 _('[OPTION]... [FILE]...'),
5309 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5311 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5310 helpbasic=True, inferrepo=True,
5312 helpbasic=True, inferrepo=True,
5311 intents={INTENT_READONLY})
5313 intents={INTENT_READONLY})
5312 def status(ui, repo, *pats, **opts):
5314 def status(ui, repo, *pats, **opts):
5313 """show changed files in the working directory
5315 """show changed files in the working directory
5314
5316
5315 Show status of files in the repository. If names are given, only
5317 Show status of files in the repository. If names are given, only
5316 files that match are shown. Files that are clean or ignored or
5318 files that match are shown. Files that are clean or ignored or
5317 the source of a copy/move operation, are not listed unless
5319 the source of a copy/move operation, are not listed unless
5318 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
5320 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
5319 Unless options described with "show only ..." are given, the
5321 Unless options described with "show only ..." are given, the
5320 options -mardu are used.
5322 options -mardu are used.
5321
5323
5322 Option -q/--quiet hides untracked (unknown and ignored) files
5324 Option -q/--quiet hides untracked (unknown and ignored) files
5323 unless explicitly requested with -u/--unknown or -i/--ignored.
5325 unless explicitly requested with -u/--unknown or -i/--ignored.
5324
5326
5325 .. note::
5327 .. note::
5326
5328
5327 :hg:`status` may appear to disagree with diff if permissions have
5329 :hg:`status` may appear to disagree with diff if permissions have
5328 changed or a merge has occurred. The standard diff format does
5330 changed or a merge has occurred. The standard diff format does
5329 not report permission changes and diff only reports changes
5331 not report permission changes and diff only reports changes
5330 relative to one merge parent.
5332 relative to one merge parent.
5331
5333
5332 If one revision is given, it is used as the base revision.
5334 If one revision is given, it is used as the base revision.
5333 If two revisions are given, the differences between them are
5335 If two revisions are given, the differences between them are
5334 shown. The --change option can also be used as a shortcut to list
5336 shown. The --change option can also be used as a shortcut to list
5335 the changed files of a revision from its first parent.
5337 the changed files of a revision from its first parent.
5336
5338
5337 The codes used to show the status of files are::
5339 The codes used to show the status of files are::
5338
5340
5339 M = modified
5341 M = modified
5340 A = added
5342 A = added
5341 R = removed
5343 R = removed
5342 C = clean
5344 C = clean
5343 ! = missing (deleted by non-hg command, but still tracked)
5345 ! = missing (deleted by non-hg command, but still tracked)
5344 ? = not tracked
5346 ? = not tracked
5345 I = ignored
5347 I = ignored
5346 = origin of the previous file (with --copies)
5348 = origin of the previous file (with --copies)
5347
5349
5348 .. container:: verbose
5350 .. container:: verbose
5349
5351
5350 The -t/--terse option abbreviates the output by showing only the directory
5352 The -t/--terse option abbreviates the output by showing only the directory
5351 name if all the files in it share the same status. The option takes an
5353 name if all the files in it share the same status. The option takes an
5352 argument indicating the statuses to abbreviate: 'm' for 'modified', 'a'
5354 argument indicating the statuses to abbreviate: 'm' for 'modified', 'a'
5353 for 'added', 'r' for 'removed', 'd' for 'deleted', 'u' for 'unknown', 'i'
5355 for 'added', 'r' for 'removed', 'd' for 'deleted', 'u' for 'unknown', 'i'
5354 for 'ignored' and 'c' for clean.
5356 for 'ignored' and 'c' for clean.
5355
5357
5356 It abbreviates only those statuses which are passed. Note that clean and
5358 It abbreviates only those statuses which are passed. Note that clean and
5357 ignored files are not displayed with '--terse ic' unless the -c/--clean
5359 ignored files are not displayed with '--terse ic' unless the -c/--clean
5358 and -i/--ignored options are also used.
5360 and -i/--ignored options are also used.
5359
5361
5360 The -v/--verbose option shows information when the repository is in an
5362 The -v/--verbose option shows information when the repository is in an
5361 unfinished merge, shelve, rebase state etc. You can have this behavior
5363 unfinished merge, shelve, rebase state etc. You can have this behavior
5362 turned on by default by enabling the ``commands.status.verbose`` option.
5364 turned on by default by enabling the ``commands.status.verbose`` option.
5363
5365
5364 You can skip displaying some of these states by setting
5366 You can skip displaying some of these states by setting
5365 ``commands.status.skipstates`` to one or more of: 'bisect', 'graft',
5367 ``commands.status.skipstates`` to one or more of: 'bisect', 'graft',
5366 'histedit', 'merge', 'rebase', or 'unshelve'.
5368 'histedit', 'merge', 'rebase', or 'unshelve'.
5367
5369
5368 Template:
5370 Template:
5369
5371
5370 The following keywords are supported in addition to the common template
5372 The following keywords are supported in addition to the common template
5371 keywords and functions. See also :hg:`help templates`.
5373 keywords and functions. See also :hg:`help templates`.
5372
5374
5373 :path: String. Repository-absolute path of the file.
5375 :path: String. Repository-absolute path of the file.
5374 :source: String. Repository-absolute path of the file originated from.
5376 :source: String. Repository-absolute path of the file originated from.
5375 Available if ``--copies`` is specified.
5377 Available if ``--copies`` is specified.
5376 :status: String. Character denoting file's status.
5378 :status: String. Character denoting file's status.
5377
5379
5378 Examples:
5380 Examples:
5379
5381
5380 - show changes in the working directory relative to a
5382 - show changes in the working directory relative to a
5381 changeset::
5383 changeset::
5382
5384
5383 hg status --rev 9353
5385 hg status --rev 9353
5384
5386
5385 - show changes in the working directory relative to the
5387 - show changes in the working directory relative to the
5386 current directory (see :hg:`help patterns` for more information)::
5388 current directory (see :hg:`help patterns` for more information)::
5387
5389
5388 hg status re:
5390 hg status re:
5389
5391
5390 - show all changes including copies in an existing changeset::
5392 - show all changes including copies in an existing changeset::
5391
5393
5392 hg status --copies --change 9353
5394 hg status --copies --change 9353
5393
5395
5394 - get a NUL separated list of added files, suitable for xargs::
5396 - get a NUL separated list of added files, suitable for xargs::
5395
5397
5396 hg status -an0
5398 hg status -an0
5397
5399
5398 - show more information about the repository status, abbreviating
5400 - show more information about the repository status, abbreviating
5399 added, removed, modified, deleted, and untracked paths::
5401 added, removed, modified, deleted, and untracked paths::
5400
5402
5401 hg status -v -t mardu
5403 hg status -v -t mardu
5402
5404
5403 Returns 0 on success.
5405 Returns 0 on success.
5404
5406
5405 """
5407 """
5406
5408
5407 opts = pycompat.byteskwargs(opts)
5409 opts = pycompat.byteskwargs(opts)
5408 revs = opts.get('rev')
5410 revs = opts.get('rev')
5409 change = opts.get('change')
5411 change = opts.get('change')
5410 terse = opts.get('terse')
5412 terse = opts.get('terse')
5411 if terse is _NOTTERSE:
5413 if terse is _NOTTERSE:
5412 if revs:
5414 if revs:
5413 terse = ''
5415 terse = ''
5414 else:
5416 else:
5415 terse = ui.config('commands', 'status.terse')
5417 terse = ui.config('commands', 'status.terse')
5416
5418
5417 if revs and change:
5419 if revs and change:
5418 msg = _('cannot specify --rev and --change at the same time')
5420 msg = _('cannot specify --rev and --change at the same time')
5419 raise error.Abort(msg)
5421 raise error.Abort(msg)
5420 elif revs and terse:
5422 elif revs and terse:
5421 msg = _('cannot use --terse with --rev')
5423 msg = _('cannot use --terse with --rev')
5422 raise error.Abort(msg)
5424 raise error.Abort(msg)
5423 elif change:
5425 elif change:
5424 repo = scmutil.unhidehashlikerevs(repo, [change], 'nowarn')
5426 repo = scmutil.unhidehashlikerevs(repo, [change], 'nowarn')
5425 ctx2 = scmutil.revsingle(repo, change, None)
5427 ctx2 = scmutil.revsingle(repo, change, None)
5426 ctx1 = ctx2.p1()
5428 ctx1 = ctx2.p1()
5427 else:
5429 else:
5428 repo = scmutil.unhidehashlikerevs(repo, revs, 'nowarn')
5430 repo = scmutil.unhidehashlikerevs(repo, revs, 'nowarn')
5429 ctx1, ctx2 = scmutil.revpair(repo, revs)
5431 ctx1, ctx2 = scmutil.revpair(repo, revs)
5430
5432
5431 forcerelativevalue = None
5433 forcerelativevalue = None
5432 if ui.hasconfig('commands', 'status.relative'):
5434 if ui.hasconfig('commands', 'status.relative'):
5433 forcerelativevalue = ui.configbool('commands', 'status.relative')
5435 forcerelativevalue = ui.configbool('commands', 'status.relative')
5434 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=bool(pats),
5436 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=bool(pats),
5435 forcerelativevalue=forcerelativevalue)
5437 forcerelativevalue=forcerelativevalue)
5436
5438
5437 if opts.get('print0'):
5439 if opts.get('print0'):
5438 end = '\0'
5440 end = '\0'
5439 else:
5441 else:
5440 end = '\n'
5442 end = '\n'
5441 copy = {}
5443 copy = {}
5442 states = 'modified added removed deleted unknown ignored clean'.split()
5444 states = 'modified added removed deleted unknown ignored clean'.split()
5443 show = [k for k in states if opts.get(k)]
5445 show = [k for k in states if opts.get(k)]
5444 if opts.get('all'):
5446 if opts.get('all'):
5445 show += ui.quiet and (states[:4] + ['clean']) or states
5447 show += ui.quiet and (states[:4] + ['clean']) or states
5446
5448
5447 if not show:
5449 if not show:
5448 if ui.quiet:
5450 if ui.quiet:
5449 show = states[:4]
5451 show = states[:4]
5450 else:
5452 else:
5451 show = states[:5]
5453 show = states[:5]
5452
5454
5453 m = scmutil.match(ctx2, pats, opts)
5455 m = scmutil.match(ctx2, pats, opts)
5454 if terse:
5456 if terse:
5455 # we need to compute clean and unknown to terse
5457 # we need to compute clean and unknown to terse
5456 stat = repo.status(ctx1.node(), ctx2.node(), m,
5458 stat = repo.status(ctx1.node(), ctx2.node(), m,
5457 'ignored' in show or 'i' in terse,
5459 'ignored' in show or 'i' in terse,
5458 clean=True, unknown=True,
5460 clean=True, unknown=True,
5459 listsubrepos=opts.get('subrepos'))
5461 listsubrepos=opts.get('subrepos'))
5460
5462
5461 stat = cmdutil.tersedir(stat, terse)
5463 stat = cmdutil.tersedir(stat, terse)
5462 else:
5464 else:
5463 stat = repo.status(ctx1.node(), ctx2.node(), m,
5465 stat = repo.status(ctx1.node(), ctx2.node(), m,
5464 'ignored' in show, 'clean' in show,
5466 'ignored' in show, 'clean' in show,
5465 'unknown' in show, opts.get('subrepos'))
5467 'unknown' in show, opts.get('subrepos'))
5466
5468
5467 changestates = zip(states, pycompat.iterbytestr('MAR!?IC'), stat)
5469 changestates = zip(states, pycompat.iterbytestr('MAR!?IC'), stat)
5468
5470
5469 if (opts.get('all') or opts.get('copies')
5471 if (opts.get('all') or opts.get('copies')
5470 or ui.configbool('ui', 'statuscopies')) and not opts.get('no_status'):
5472 or ui.configbool('ui', 'statuscopies')) and not opts.get('no_status'):
5471 copy = copies.pathcopies(ctx1, ctx2, m)
5473 copy = copies.pathcopies(ctx1, ctx2, m)
5472
5474
5473 ui.pager('status')
5475 ui.pager('status')
5474 fm = ui.formatter('status', opts)
5476 fm = ui.formatter('status', opts)
5475 fmt = '%s' + end
5477 fmt = '%s' + end
5476 showchar = not opts.get('no_status')
5478 showchar = not opts.get('no_status')
5477
5479
5478 for state, char, files in changestates:
5480 for state, char, files in changestates:
5479 if state in show:
5481 if state in show:
5480 label = 'status.' + state
5482 label = 'status.' + state
5481 for f in files:
5483 for f in files:
5482 fm.startitem()
5484 fm.startitem()
5483 fm.context(ctx=ctx2)
5485 fm.context(ctx=ctx2)
5484 fm.data(path=f)
5486 fm.data(path=f)
5485 fm.condwrite(showchar, 'status', '%s ', char, label=label)
5487 fm.condwrite(showchar, 'status', '%s ', char, label=label)
5486 fm.plain(fmt % uipathfn(f), label=label)
5488 fm.plain(fmt % uipathfn(f), label=label)
5487 if f in copy:
5489 if f in copy:
5488 fm.data(source=copy[f])
5490 fm.data(source=copy[f])
5489 fm.plain((' %s' + end) % uipathfn(copy[f]),
5491 fm.plain((' %s' + end) % uipathfn(copy[f]),
5490 label='status.copied')
5492 label='status.copied')
5491
5493
5492 if ((ui.verbose or ui.configbool('commands', 'status.verbose'))
5494 if ((ui.verbose or ui.configbool('commands', 'status.verbose'))
5493 and not ui.plain()):
5495 and not ui.plain()):
5494 cmdutil.morestatus(repo, fm)
5496 cmdutil.morestatus(repo, fm)
5495 fm.end()
5497 fm.end()
5496
5498
5497 @command('summary|sum',
5499 @command('summary|sum',
5498 [('', 'remote', None, _('check for push and pull'))],
5500 [('', 'remote', None, _('check for push and pull'))],
5499 '[--remote]',
5501 '[--remote]',
5500 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5502 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5501 helpbasic=True,
5503 helpbasic=True,
5502 intents={INTENT_READONLY})
5504 intents={INTENT_READONLY})
5503 def summary(ui, repo, **opts):
5505 def summary(ui, repo, **opts):
5504 """summarize working directory state
5506 """summarize working directory state
5505
5507
5506 This generates a brief summary of the working directory state,
5508 This generates a brief summary of the working directory state,
5507 including parents, branch, commit status, phase and available updates.
5509 including parents, branch, commit status, phase and available updates.
5508
5510
5509 With the --remote option, this will check the default paths for
5511 With the --remote option, this will check the default paths for
5510 incoming and outgoing changes. This can be time-consuming.
5512 incoming and outgoing changes. This can be time-consuming.
5511
5513
5512 Returns 0 on success.
5514 Returns 0 on success.
5513 """
5515 """
5514
5516
5515 opts = pycompat.byteskwargs(opts)
5517 opts = pycompat.byteskwargs(opts)
5516 ui.pager('summary')
5518 ui.pager('summary')
5517 ctx = repo[None]
5519 ctx = repo[None]
5518 parents = ctx.parents()
5520 parents = ctx.parents()
5519 pnode = parents[0].node()
5521 pnode = parents[0].node()
5520 marks = []
5522 marks = []
5521
5523
5522 try:
5524 try:
5523 ms = mergemod.mergestate.read(repo)
5525 ms = mergemod.mergestate.read(repo)
5524 except error.UnsupportedMergeRecords as e:
5526 except error.UnsupportedMergeRecords as e:
5525 s = ' '.join(e.recordtypes)
5527 s = ' '.join(e.recordtypes)
5526 ui.warn(
5528 ui.warn(
5527 _('warning: merge state has unsupported record types: %s\n') % s)
5529 _('warning: merge state has unsupported record types: %s\n') % s)
5528 unresolved = []
5530 unresolved = []
5529 else:
5531 else:
5530 unresolved = list(ms.unresolved())
5532 unresolved = list(ms.unresolved())
5531
5533
5532 for p in parents:
5534 for p in parents:
5533 # label with log.changeset (instead of log.parent) since this
5535 # label with log.changeset (instead of log.parent) since this
5534 # shows a working directory parent *changeset*:
5536 # shows a working directory parent *changeset*:
5535 # i18n: column positioning for "hg summary"
5537 # i18n: column positioning for "hg summary"
5536 ui.write(_('parent: %d:%s ') % (p.rev(), p),
5538 ui.write(_('parent: %d:%s ') % (p.rev(), p),
5537 label=logcmdutil.changesetlabels(p))
5539 label=logcmdutil.changesetlabels(p))
5538 ui.write(' '.join(p.tags()), label='log.tag')
5540 ui.write(' '.join(p.tags()), label='log.tag')
5539 if p.bookmarks():
5541 if p.bookmarks():
5540 marks.extend(p.bookmarks())
5542 marks.extend(p.bookmarks())
5541 if p.rev() == -1:
5543 if p.rev() == -1:
5542 if not len(repo):
5544 if not len(repo):
5543 ui.write(_(' (empty repository)'))
5545 ui.write(_(' (empty repository)'))
5544 else:
5546 else:
5545 ui.write(_(' (no revision checked out)'))
5547 ui.write(_(' (no revision checked out)'))
5546 if p.obsolete():
5548 if p.obsolete():
5547 ui.write(_(' (obsolete)'))
5549 ui.write(_(' (obsolete)'))
5548 if p.isunstable():
5550 if p.isunstable():
5549 instabilities = (ui.label(instability, 'trouble.%s' % instability)
5551 instabilities = (ui.label(instability, 'trouble.%s' % instability)
5550 for instability in p.instabilities())
5552 for instability in p.instabilities())
5551 ui.write(' ('
5553 ui.write(' ('
5552 + ', '.join(instabilities)
5554 + ', '.join(instabilities)
5553 + ')')
5555 + ')')
5554 ui.write('\n')
5556 ui.write('\n')
5555 if p.description():
5557 if p.description():
5556 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
5558 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
5557 label='log.summary')
5559 label='log.summary')
5558
5560
5559 branch = ctx.branch()
5561 branch = ctx.branch()
5560 bheads = repo.branchheads(branch)
5562 bheads = repo.branchheads(branch)
5561 # i18n: column positioning for "hg summary"
5563 # i18n: column positioning for "hg summary"
5562 m = _('branch: %s\n') % branch
5564 m = _('branch: %s\n') % branch
5563 if branch != 'default':
5565 if branch != 'default':
5564 ui.write(m, label='log.branch')
5566 ui.write(m, label='log.branch')
5565 else:
5567 else:
5566 ui.status(m, label='log.branch')
5568 ui.status(m, label='log.branch')
5567
5569
5568 if marks:
5570 if marks:
5569 active = repo._activebookmark
5571 active = repo._activebookmark
5570 # i18n: column positioning for "hg summary"
5572 # i18n: column positioning for "hg summary"
5571 ui.write(_('bookmarks:'), label='log.bookmark')
5573 ui.write(_('bookmarks:'), label='log.bookmark')
5572 if active is not None:
5574 if active is not None:
5573 if active in marks:
5575 if active in marks:
5574 ui.write(' *' + active, label=bookmarks.activebookmarklabel)
5576 ui.write(' *' + active, label=bookmarks.activebookmarklabel)
5575 marks.remove(active)
5577 marks.remove(active)
5576 else:
5578 else:
5577 ui.write(' [%s]' % active, label=bookmarks.activebookmarklabel)
5579 ui.write(' [%s]' % active, label=bookmarks.activebookmarklabel)
5578 for m in marks:
5580 for m in marks:
5579 ui.write(' ' + m, label='log.bookmark')
5581 ui.write(' ' + m, label='log.bookmark')
5580 ui.write('\n', label='log.bookmark')
5582 ui.write('\n', label='log.bookmark')
5581
5583
5582 status = repo.status(unknown=True)
5584 status = repo.status(unknown=True)
5583
5585
5584 c = repo.dirstate.copies()
5586 c = repo.dirstate.copies()
5585 copied, renamed = [], []
5587 copied, renamed = [], []
5586 for d, s in c.iteritems():
5588 for d, s in c.iteritems():
5587 if s in status.removed:
5589 if s in status.removed:
5588 status.removed.remove(s)
5590 status.removed.remove(s)
5589 renamed.append(d)
5591 renamed.append(d)
5590 else:
5592 else:
5591 copied.append(d)
5593 copied.append(d)
5592 if d in status.added:
5594 if d in status.added:
5593 status.added.remove(d)
5595 status.added.remove(d)
5594
5596
5595 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
5597 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
5596
5598
5597 labels = [(ui.label(_('%d modified'), 'status.modified'), status.modified),
5599 labels = [(ui.label(_('%d modified'), 'status.modified'), status.modified),
5598 (ui.label(_('%d added'), 'status.added'), status.added),
5600 (ui.label(_('%d added'), 'status.added'), status.added),
5599 (ui.label(_('%d removed'), 'status.removed'), status.removed),
5601 (ui.label(_('%d removed'), 'status.removed'), status.removed),
5600 (ui.label(_('%d renamed'), 'status.copied'), renamed),
5602 (ui.label(_('%d renamed'), 'status.copied'), renamed),
5601 (ui.label(_('%d copied'), 'status.copied'), copied),
5603 (ui.label(_('%d copied'), 'status.copied'), copied),
5602 (ui.label(_('%d deleted'), 'status.deleted'), status.deleted),
5604 (ui.label(_('%d deleted'), 'status.deleted'), status.deleted),
5603 (ui.label(_('%d unknown'), 'status.unknown'), status.unknown),
5605 (ui.label(_('%d unknown'), 'status.unknown'), status.unknown),
5604 (ui.label(_('%d unresolved'), 'resolve.unresolved'), unresolved),
5606 (ui.label(_('%d unresolved'), 'resolve.unresolved'), unresolved),
5605 (ui.label(_('%d subrepos'), 'status.modified'), subs)]
5607 (ui.label(_('%d subrepos'), 'status.modified'), subs)]
5606 t = []
5608 t = []
5607 for l, s in labels:
5609 for l, s in labels:
5608 if s:
5610 if s:
5609 t.append(l % len(s))
5611 t.append(l % len(s))
5610
5612
5611 t = ', '.join(t)
5613 t = ', '.join(t)
5612 cleanworkdir = False
5614 cleanworkdir = False
5613
5615
5614 if repo.vfs.exists('graftstate'):
5616 if repo.vfs.exists('graftstate'):
5615 t += _(' (graft in progress)')
5617 t += _(' (graft in progress)')
5616 if repo.vfs.exists('updatestate'):
5618 if repo.vfs.exists('updatestate'):
5617 t += _(' (interrupted update)')
5619 t += _(' (interrupted update)')
5618 elif len(parents) > 1:
5620 elif len(parents) > 1:
5619 t += _(' (merge)')
5621 t += _(' (merge)')
5620 elif branch != parents[0].branch():
5622 elif branch != parents[0].branch():
5621 t += _(' (new branch)')
5623 t += _(' (new branch)')
5622 elif (parents[0].closesbranch() and
5624 elif (parents[0].closesbranch() and
5623 pnode in repo.branchheads(branch, closed=True)):
5625 pnode in repo.branchheads(branch, closed=True)):
5624 t += _(' (head closed)')
5626 t += _(' (head closed)')
5625 elif not (status.modified or status.added or status.removed or renamed or
5627 elif not (status.modified or status.added or status.removed or renamed or
5626 copied or subs):
5628 copied or subs):
5627 t += _(' (clean)')
5629 t += _(' (clean)')
5628 cleanworkdir = True
5630 cleanworkdir = True
5629 elif pnode not in bheads:
5631 elif pnode not in bheads:
5630 t += _(' (new branch head)')
5632 t += _(' (new branch head)')
5631
5633
5632 if parents:
5634 if parents:
5633 pendingphase = max(p.phase() for p in parents)
5635 pendingphase = max(p.phase() for p in parents)
5634 else:
5636 else:
5635 pendingphase = phases.public
5637 pendingphase = phases.public
5636
5638
5637 if pendingphase > phases.newcommitphase(ui):
5639 if pendingphase > phases.newcommitphase(ui):
5638 t += ' (%s)' % phases.phasenames[pendingphase]
5640 t += ' (%s)' % phases.phasenames[pendingphase]
5639
5641
5640 if cleanworkdir:
5642 if cleanworkdir:
5641 # i18n: column positioning for "hg summary"
5643 # i18n: column positioning for "hg summary"
5642 ui.status(_('commit: %s\n') % t.strip())
5644 ui.status(_('commit: %s\n') % t.strip())
5643 else:
5645 else:
5644 # i18n: column positioning for "hg summary"
5646 # i18n: column positioning for "hg summary"
5645 ui.write(_('commit: %s\n') % t.strip())
5647 ui.write(_('commit: %s\n') % t.strip())
5646
5648
5647 # all ancestors of branch heads - all ancestors of parent = new csets
5649 # all ancestors of branch heads - all ancestors of parent = new csets
5648 new = len(repo.changelog.findmissing([pctx.node() for pctx in parents],
5650 new = len(repo.changelog.findmissing([pctx.node() for pctx in parents],
5649 bheads))
5651 bheads))
5650
5652
5651 if new == 0:
5653 if new == 0:
5652 # i18n: column positioning for "hg summary"
5654 # i18n: column positioning for "hg summary"
5653 ui.status(_('update: (current)\n'))
5655 ui.status(_('update: (current)\n'))
5654 elif pnode not in bheads:
5656 elif pnode not in bheads:
5655 # i18n: column positioning for "hg summary"
5657 # i18n: column positioning for "hg summary"
5656 ui.write(_('update: %d new changesets (update)\n') % new)
5658 ui.write(_('update: %d new changesets (update)\n') % new)
5657 else:
5659 else:
5658 # i18n: column positioning for "hg summary"
5660 # i18n: column positioning for "hg summary"
5659 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
5661 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
5660 (new, len(bheads)))
5662 (new, len(bheads)))
5661
5663
5662 t = []
5664 t = []
5663 draft = len(repo.revs('draft()'))
5665 draft = len(repo.revs('draft()'))
5664 if draft:
5666 if draft:
5665 t.append(_('%d draft') % draft)
5667 t.append(_('%d draft') % draft)
5666 secret = len(repo.revs('secret()'))
5668 secret = len(repo.revs('secret()'))
5667 if secret:
5669 if secret:
5668 t.append(_('%d secret') % secret)
5670 t.append(_('%d secret') % secret)
5669
5671
5670 if draft or secret:
5672 if draft or secret:
5671 ui.status(_('phases: %s\n') % ', '.join(t))
5673 ui.status(_('phases: %s\n') % ', '.join(t))
5672
5674
5673 if obsolete.isenabled(repo, obsolete.createmarkersopt):
5675 if obsolete.isenabled(repo, obsolete.createmarkersopt):
5674 for trouble in ("orphan", "contentdivergent", "phasedivergent"):
5676 for trouble in ("orphan", "contentdivergent", "phasedivergent"):
5675 numtrouble = len(repo.revs(trouble + "()"))
5677 numtrouble = len(repo.revs(trouble + "()"))
5676 # We write all the possibilities to ease translation
5678 # We write all the possibilities to ease translation
5677 troublemsg = {
5679 troublemsg = {
5678 "orphan": _("orphan: %d changesets"),
5680 "orphan": _("orphan: %d changesets"),
5679 "contentdivergent": _("content-divergent: %d changesets"),
5681 "contentdivergent": _("content-divergent: %d changesets"),
5680 "phasedivergent": _("phase-divergent: %d changesets"),
5682 "phasedivergent": _("phase-divergent: %d changesets"),
5681 }
5683 }
5682 if numtrouble > 0:
5684 if numtrouble > 0:
5683 ui.status(troublemsg[trouble] % numtrouble + "\n")
5685 ui.status(troublemsg[trouble] % numtrouble + "\n")
5684
5686
5685 cmdutil.summaryhooks(ui, repo)
5687 cmdutil.summaryhooks(ui, repo)
5686
5688
5687 if opts.get('remote'):
5689 if opts.get('remote'):
5688 needsincoming, needsoutgoing = True, True
5690 needsincoming, needsoutgoing = True, True
5689 else:
5691 else:
5690 needsincoming, needsoutgoing = False, False
5692 needsincoming, needsoutgoing = False, False
5691 for i, o in cmdutil.summaryremotehooks(ui, repo, opts, None):
5693 for i, o in cmdutil.summaryremotehooks(ui, repo, opts, None):
5692 if i:
5694 if i:
5693 needsincoming = True
5695 needsincoming = True
5694 if o:
5696 if o:
5695 needsoutgoing = True
5697 needsoutgoing = True
5696 if not needsincoming and not needsoutgoing:
5698 if not needsincoming and not needsoutgoing:
5697 return
5699 return
5698
5700
5699 def getincoming():
5701 def getincoming():
5700 source, branches = hg.parseurl(ui.expandpath('default'))
5702 source, branches = hg.parseurl(ui.expandpath('default'))
5701 sbranch = branches[0]
5703 sbranch = branches[0]
5702 try:
5704 try:
5703 other = hg.peer(repo, {}, source)
5705 other = hg.peer(repo, {}, source)
5704 except error.RepoError:
5706 except error.RepoError:
5705 if opts.get('remote'):
5707 if opts.get('remote'):
5706 raise
5708 raise
5707 return source, sbranch, None, None, None
5709 return source, sbranch, None, None, None
5708 revs, checkout = hg.addbranchrevs(repo, other, branches, None)
5710 revs, checkout = hg.addbranchrevs(repo, other, branches, None)
5709 if revs:
5711 if revs:
5710 revs = [other.lookup(rev) for rev in revs]
5712 revs = [other.lookup(rev) for rev in revs]
5711 ui.debug('comparing with %s\n' % util.hidepassword(source))
5713 ui.debug('comparing with %s\n' % util.hidepassword(source))
5712 repo.ui.pushbuffer()
5714 repo.ui.pushbuffer()
5713 commoninc = discovery.findcommonincoming(repo, other, heads=revs)
5715 commoninc = discovery.findcommonincoming(repo, other, heads=revs)
5714 repo.ui.popbuffer()
5716 repo.ui.popbuffer()
5715 return source, sbranch, other, commoninc, commoninc[1]
5717 return source, sbranch, other, commoninc, commoninc[1]
5716
5718
5717 if needsincoming:
5719 if needsincoming:
5718 source, sbranch, sother, commoninc, incoming = getincoming()
5720 source, sbranch, sother, commoninc, incoming = getincoming()
5719 else:
5721 else:
5720 source = sbranch = sother = commoninc = incoming = None
5722 source = sbranch = sother = commoninc = incoming = None
5721
5723
5722 def getoutgoing():
5724 def getoutgoing():
5723 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
5725 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
5724 dbranch = branches[0]
5726 dbranch = branches[0]
5725 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
5727 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
5726 if source != dest:
5728 if source != dest:
5727 try:
5729 try:
5728 dother = hg.peer(repo, {}, dest)
5730 dother = hg.peer(repo, {}, dest)
5729 except error.RepoError:
5731 except error.RepoError:
5730 if opts.get('remote'):
5732 if opts.get('remote'):
5731 raise
5733 raise
5732 return dest, dbranch, None, None
5734 return dest, dbranch, None, None
5733 ui.debug('comparing with %s\n' % util.hidepassword(dest))
5735 ui.debug('comparing with %s\n' % util.hidepassword(dest))
5734 elif sother is None:
5736 elif sother is None:
5735 # there is no explicit destination peer, but source one is invalid
5737 # there is no explicit destination peer, but source one is invalid
5736 return dest, dbranch, None, None
5738 return dest, dbranch, None, None
5737 else:
5739 else:
5738 dother = sother
5740 dother = sother
5739 if (source != dest or (sbranch is not None and sbranch != dbranch)):
5741 if (source != dest or (sbranch is not None and sbranch != dbranch)):
5740 common = None
5742 common = None
5741 else:
5743 else:
5742 common = commoninc
5744 common = commoninc
5743 if revs:
5745 if revs:
5744 revs = [repo.lookup(rev) for rev in revs]
5746 revs = [repo.lookup(rev) for rev in revs]
5745 repo.ui.pushbuffer()
5747 repo.ui.pushbuffer()
5746 outgoing = discovery.findcommonoutgoing(repo, dother, onlyheads=revs,
5748 outgoing = discovery.findcommonoutgoing(repo, dother, onlyheads=revs,
5747 commoninc=common)
5749 commoninc=common)
5748 repo.ui.popbuffer()
5750 repo.ui.popbuffer()
5749 return dest, dbranch, dother, outgoing
5751 return dest, dbranch, dother, outgoing
5750
5752
5751 if needsoutgoing:
5753 if needsoutgoing:
5752 dest, dbranch, dother, outgoing = getoutgoing()
5754 dest, dbranch, dother, outgoing = getoutgoing()
5753 else:
5755 else:
5754 dest = dbranch = dother = outgoing = None
5756 dest = dbranch = dother = outgoing = None
5755
5757
5756 if opts.get('remote'):
5758 if opts.get('remote'):
5757 t = []
5759 t = []
5758 if incoming:
5760 if incoming:
5759 t.append(_('1 or more incoming'))
5761 t.append(_('1 or more incoming'))
5760 o = outgoing.missing
5762 o = outgoing.missing
5761 if o:
5763 if o:
5762 t.append(_('%d outgoing') % len(o))
5764 t.append(_('%d outgoing') % len(o))
5763 other = dother or sother
5765 other = dother or sother
5764 if 'bookmarks' in other.listkeys('namespaces'):
5766 if 'bookmarks' in other.listkeys('namespaces'):
5765 counts = bookmarks.summary(repo, other)
5767 counts = bookmarks.summary(repo, other)
5766 if counts[0] > 0:
5768 if counts[0] > 0:
5767 t.append(_('%d incoming bookmarks') % counts[0])
5769 t.append(_('%d incoming bookmarks') % counts[0])
5768 if counts[1] > 0:
5770 if counts[1] > 0:
5769 t.append(_('%d outgoing bookmarks') % counts[1])
5771 t.append(_('%d outgoing bookmarks') % counts[1])
5770
5772
5771 if t:
5773 if t:
5772 # i18n: column positioning for "hg summary"
5774 # i18n: column positioning for "hg summary"
5773 ui.write(_('remote: %s\n') % (', '.join(t)))
5775 ui.write(_('remote: %s\n') % (', '.join(t)))
5774 else:
5776 else:
5775 # i18n: column positioning for "hg summary"
5777 # i18n: column positioning for "hg summary"
5776 ui.status(_('remote: (synced)\n'))
5778 ui.status(_('remote: (synced)\n'))
5777
5779
5778 cmdutil.summaryremotehooks(ui, repo, opts,
5780 cmdutil.summaryremotehooks(ui, repo, opts,
5779 ((source, sbranch, sother, commoninc),
5781 ((source, sbranch, sother, commoninc),
5780 (dest, dbranch, dother, outgoing)))
5782 (dest, dbranch, dother, outgoing)))
5781
5783
5782 @command('tag',
5784 @command('tag',
5783 [('f', 'force', None, _('force tag')),
5785 [('f', 'force', None, _('force tag')),
5784 ('l', 'local', None, _('make the tag local')),
5786 ('l', 'local', None, _('make the tag local')),
5785 ('r', 'rev', '', _('revision to tag'), _('REV')),
5787 ('r', 'rev', '', _('revision to tag'), _('REV')),
5786 ('', 'remove', None, _('remove a tag')),
5788 ('', 'remove', None, _('remove a tag')),
5787 # -l/--local is already there, commitopts cannot be used
5789 # -l/--local is already there, commitopts cannot be used
5788 ('e', 'edit', None, _('invoke editor on commit messages')),
5790 ('e', 'edit', None, _('invoke editor on commit messages')),
5789 ('m', 'message', '', _('use text as commit message'), _('TEXT')),
5791 ('m', 'message', '', _('use text as commit message'), _('TEXT')),
5790 ] + commitopts2,
5792 ] + commitopts2,
5791 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'),
5793 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'),
5792 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION)
5794 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION)
5793 def tag(ui, repo, name1, *names, **opts):
5795 def tag(ui, repo, name1, *names, **opts):
5794 """add one or more tags for the current or given revision
5796 """add one or more tags for the current or given revision
5795
5797
5796 Name a particular revision using <name>.
5798 Name a particular revision using <name>.
5797
5799
5798 Tags are used to name particular revisions of the repository and are
5800 Tags are used to name particular revisions of the repository and are
5799 very useful to compare different revisions, to go back to significant
5801 very useful to compare different revisions, to go back to significant
5800 earlier versions or to mark branch points as releases, etc. Changing
5802 earlier versions or to mark branch points as releases, etc. Changing
5801 an existing tag is normally disallowed; use -f/--force to override.
5803 an existing tag is normally disallowed; use -f/--force to override.
5802
5804
5803 If no revision is given, the parent of the working directory is
5805 If no revision is given, the parent of the working directory is
5804 used.
5806 used.
5805
5807
5806 To facilitate version control, distribution, and merging of tags,
5808 To facilitate version control, distribution, and merging of tags,
5807 they are stored as a file named ".hgtags" which is managed similarly
5809 they are stored as a file named ".hgtags" which is managed similarly
5808 to other project files and can be hand-edited if necessary. This
5810 to other project files and can be hand-edited if necessary. This
5809 also means that tagging creates a new commit. The file
5811 also means that tagging creates a new commit. The file
5810 ".hg/localtags" is used for local tags (not shared among
5812 ".hg/localtags" is used for local tags (not shared among
5811 repositories).
5813 repositories).
5812
5814
5813 Tag commits are usually made at the head of a branch. If the parent
5815 Tag commits are usually made at the head of a branch. If the parent
5814 of the working directory is not a branch head, :hg:`tag` aborts; use
5816 of the working directory is not a branch head, :hg:`tag` aborts; use
5815 -f/--force to force the tag commit to be based on a non-head
5817 -f/--force to force the tag commit to be based on a non-head
5816 changeset.
5818 changeset.
5817
5819
5818 See :hg:`help dates` for a list of formats valid for -d/--date.
5820 See :hg:`help dates` for a list of formats valid for -d/--date.
5819
5821
5820 Since tag names have priority over branch names during revision
5822 Since tag names have priority over branch names during revision
5821 lookup, using an existing branch name as a tag name is discouraged.
5823 lookup, using an existing branch name as a tag name is discouraged.
5822
5824
5823 Returns 0 on success.
5825 Returns 0 on success.
5824 """
5826 """
5825 opts = pycompat.byteskwargs(opts)
5827 opts = pycompat.byteskwargs(opts)
5826 with repo.wlock(), repo.lock():
5828 with repo.wlock(), repo.lock():
5827 rev_ = "."
5829 rev_ = "."
5828 names = [t.strip() for t in (name1,) + names]
5830 names = [t.strip() for t in (name1,) + names]
5829 if len(names) != len(set(names)):
5831 if len(names) != len(set(names)):
5830 raise error.Abort(_('tag names must be unique'))
5832 raise error.Abort(_('tag names must be unique'))
5831 for n in names:
5833 for n in names:
5832 scmutil.checknewlabel(repo, n, 'tag')
5834 scmutil.checknewlabel(repo, n, 'tag')
5833 if not n:
5835 if not n:
5834 raise error.Abort(_('tag names cannot consist entirely of '
5836 raise error.Abort(_('tag names cannot consist entirely of '
5835 'whitespace'))
5837 'whitespace'))
5836 if opts.get('rev') and opts.get('remove'):
5838 if opts.get('rev') and opts.get('remove'):
5837 raise error.Abort(_("--rev and --remove are incompatible"))
5839 raise error.Abort(_("--rev and --remove are incompatible"))
5838 if opts.get('rev'):
5840 if opts.get('rev'):
5839 rev_ = opts['rev']
5841 rev_ = opts['rev']
5840 message = opts.get('message')
5842 message = opts.get('message')
5841 if opts.get('remove'):
5843 if opts.get('remove'):
5842 if opts.get('local'):
5844 if opts.get('local'):
5843 expectedtype = 'local'
5845 expectedtype = 'local'
5844 else:
5846 else:
5845 expectedtype = 'global'
5847 expectedtype = 'global'
5846
5848
5847 for n in names:
5849 for n in names:
5848 if repo.tagtype(n) == 'global':
5850 if repo.tagtype(n) == 'global':
5849 alltags = tagsmod.findglobaltags(ui, repo)
5851 alltags = tagsmod.findglobaltags(ui, repo)
5850 if alltags[n][0] == nullid:
5852 if alltags[n][0] == nullid:
5851 raise error.Abort(_("tag '%s' is already removed") % n)
5853 raise error.Abort(_("tag '%s' is already removed") % n)
5852 if not repo.tagtype(n):
5854 if not repo.tagtype(n):
5853 raise error.Abort(_("tag '%s' does not exist") % n)
5855 raise error.Abort(_("tag '%s' does not exist") % n)
5854 if repo.tagtype(n) != expectedtype:
5856 if repo.tagtype(n) != expectedtype:
5855 if expectedtype == 'global':
5857 if expectedtype == 'global':
5856 raise error.Abort(_("tag '%s' is not a global tag") % n)
5858 raise error.Abort(_("tag '%s' is not a global tag") % n)
5857 else:
5859 else:
5858 raise error.Abort(_("tag '%s' is not a local tag") % n)
5860 raise error.Abort(_("tag '%s' is not a local tag") % n)
5859 rev_ = 'null'
5861 rev_ = 'null'
5860 if not message:
5862 if not message:
5861 # we don't translate commit messages
5863 # we don't translate commit messages
5862 message = 'Removed tag %s' % ', '.join(names)
5864 message = 'Removed tag %s' % ', '.join(names)
5863 elif not opts.get('force'):
5865 elif not opts.get('force'):
5864 for n in names:
5866 for n in names:
5865 if n in repo.tags():
5867 if n in repo.tags():
5866 raise error.Abort(_("tag '%s' already exists "
5868 raise error.Abort(_("tag '%s' already exists "
5867 "(use -f to force)") % n)
5869 "(use -f to force)") % n)
5868 if not opts.get('local'):
5870 if not opts.get('local'):
5869 p1, p2 = repo.dirstate.parents()
5871 p1, p2 = repo.dirstate.parents()
5870 if p2 != nullid:
5872 if p2 != nullid:
5871 raise error.Abort(_('uncommitted merge'))
5873 raise error.Abort(_('uncommitted merge'))
5872 bheads = repo.branchheads()
5874 bheads = repo.branchheads()
5873 if not opts.get('force') and bheads and p1 not in bheads:
5875 if not opts.get('force') and bheads and p1 not in bheads:
5874 raise error.Abort(_('working directory is not at a branch head '
5876 raise error.Abort(_('working directory is not at a branch head '
5875 '(use -f to force)'))
5877 '(use -f to force)'))
5876 node = scmutil.revsingle(repo, rev_).node()
5878 node = scmutil.revsingle(repo, rev_).node()
5877
5879
5878 if not message:
5880 if not message:
5879 # we don't translate commit messages
5881 # we don't translate commit messages
5880 message = ('Added tag %s for changeset %s' %
5882 message = ('Added tag %s for changeset %s' %
5881 (', '.join(names), short(node)))
5883 (', '.join(names), short(node)))
5882
5884
5883 date = opts.get('date')
5885 date = opts.get('date')
5884 if date:
5886 if date:
5885 date = dateutil.parsedate(date)
5887 date = dateutil.parsedate(date)
5886
5888
5887 if opts.get('remove'):
5889 if opts.get('remove'):
5888 editform = 'tag.remove'
5890 editform = 'tag.remove'
5889 else:
5891 else:
5890 editform = 'tag.add'
5892 editform = 'tag.add'
5891 editor = cmdutil.getcommiteditor(editform=editform,
5893 editor = cmdutil.getcommiteditor(editform=editform,
5892 **pycompat.strkwargs(opts))
5894 **pycompat.strkwargs(opts))
5893
5895
5894 # don't allow tagging the null rev
5896 # don't allow tagging the null rev
5895 if (not opts.get('remove') and
5897 if (not opts.get('remove') and
5896 scmutil.revsingle(repo, rev_).rev() == nullrev):
5898 scmutil.revsingle(repo, rev_).rev() == nullrev):
5897 raise error.Abort(_("cannot tag null revision"))
5899 raise error.Abort(_("cannot tag null revision"))
5898
5900
5899 tagsmod.tag(repo, names, node, message, opts.get('local'),
5901 tagsmod.tag(repo, names, node, message, opts.get('local'),
5900 opts.get('user'), date, editor=editor)
5902 opts.get('user'), date, editor=editor)
5901
5903
5902 @command(
5904 @command(
5903 'tags', formatteropts, '',
5905 'tags', formatteropts, '',
5904 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
5906 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
5905 intents={INTENT_READONLY})
5907 intents={INTENT_READONLY})
5906 def tags(ui, repo, **opts):
5908 def tags(ui, repo, **opts):
5907 """list repository tags
5909 """list repository tags
5908
5910
5909 This lists both regular and local tags. When the -v/--verbose
5911 This lists both regular and local tags. When the -v/--verbose
5910 switch is used, a third column "local" is printed for local tags.
5912 switch is used, a third column "local" is printed for local tags.
5911 When the -q/--quiet switch is used, only the tag name is printed.
5913 When the -q/--quiet switch is used, only the tag name is printed.
5912
5914
5913 .. container:: verbose
5915 .. container:: verbose
5914
5916
5915 Template:
5917 Template:
5916
5918
5917 The following keywords are supported in addition to the common template
5919 The following keywords are supported in addition to the common template
5918 keywords and functions such as ``{tag}``. See also
5920 keywords and functions such as ``{tag}``. See also
5919 :hg:`help templates`.
5921 :hg:`help templates`.
5920
5922
5921 :type: String. ``local`` for local tags.
5923 :type: String. ``local`` for local tags.
5922
5924
5923 Returns 0 on success.
5925 Returns 0 on success.
5924 """
5926 """
5925
5927
5926 opts = pycompat.byteskwargs(opts)
5928 opts = pycompat.byteskwargs(opts)
5927 ui.pager('tags')
5929 ui.pager('tags')
5928 fm = ui.formatter('tags', opts)
5930 fm = ui.formatter('tags', opts)
5929 hexfunc = fm.hexfunc
5931 hexfunc = fm.hexfunc
5930
5932
5931 for t, n in reversed(repo.tagslist()):
5933 for t, n in reversed(repo.tagslist()):
5932 hn = hexfunc(n)
5934 hn = hexfunc(n)
5933 label = 'tags.normal'
5935 label = 'tags.normal'
5934 tagtype = ''
5936 tagtype = ''
5935 if repo.tagtype(t) == 'local':
5937 if repo.tagtype(t) == 'local':
5936 label = 'tags.local'
5938 label = 'tags.local'
5937 tagtype = 'local'
5939 tagtype = 'local'
5938
5940
5939 fm.startitem()
5941 fm.startitem()
5940 fm.context(repo=repo)
5942 fm.context(repo=repo)
5941 fm.write('tag', '%s', t, label=label)
5943 fm.write('tag', '%s', t, label=label)
5942 fmt = " " * (30 - encoding.colwidth(t)) + ' %5d:%s'
5944 fmt = " " * (30 - encoding.colwidth(t)) + ' %5d:%s'
5943 fm.condwrite(not ui.quiet, 'rev node', fmt,
5945 fm.condwrite(not ui.quiet, 'rev node', fmt,
5944 repo.changelog.rev(n), hn, label=label)
5946 repo.changelog.rev(n), hn, label=label)
5945 fm.condwrite(ui.verbose and tagtype, 'type', ' %s',
5947 fm.condwrite(ui.verbose and tagtype, 'type', ' %s',
5946 tagtype, label=label)
5948 tagtype, label=label)
5947 fm.plain('\n')
5949 fm.plain('\n')
5948 fm.end()
5950 fm.end()
5949
5951
5950 @command('tip',
5952 @command('tip',
5951 [('p', 'patch', None, _('show patch')),
5953 [('p', 'patch', None, _('show patch')),
5952 ('g', 'git', None, _('use git extended diff format')),
5954 ('g', 'git', None, _('use git extended diff format')),
5953 ] + templateopts,
5955 ] + templateopts,
5954 _('[-p] [-g]'),
5956 _('[-p] [-g]'),
5955 helpcategory=command.CATEGORY_CHANGE_NAVIGATION)
5957 helpcategory=command.CATEGORY_CHANGE_NAVIGATION)
5956 def tip(ui, repo, **opts):
5958 def tip(ui, repo, **opts):
5957 """show the tip revision (DEPRECATED)
5959 """show the tip revision (DEPRECATED)
5958
5960
5959 The tip revision (usually just called the tip) is the changeset
5961 The tip revision (usually just called the tip) is the changeset
5960 most recently added to the repository (and therefore the most
5962 most recently added to the repository (and therefore the most
5961 recently changed head).
5963 recently changed head).
5962
5964
5963 If you have just made a commit, that commit will be the tip. If
5965 If you have just made a commit, that commit will be the tip. If
5964 you have just pulled changes from another repository, the tip of
5966 you have just pulled changes from another repository, the tip of
5965 that repository becomes the current tip. The "tip" tag is special
5967 that repository becomes the current tip. The "tip" tag is special
5966 and cannot be renamed or assigned to a different changeset.
5968 and cannot be renamed or assigned to a different changeset.
5967
5969
5968 This command is deprecated, please use :hg:`heads` instead.
5970 This command is deprecated, please use :hg:`heads` instead.
5969
5971
5970 Returns 0 on success.
5972 Returns 0 on success.
5971 """
5973 """
5972 opts = pycompat.byteskwargs(opts)
5974 opts = pycompat.byteskwargs(opts)
5973 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
5975 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
5974 displayer.show(repo['tip'])
5976 displayer.show(repo['tip'])
5975 displayer.close()
5977 displayer.close()
5976
5978
5977 @command('unbundle',
5979 @command('unbundle',
5978 [('u', 'update', None,
5980 [('u', 'update', None,
5979 _('update to new branch head if changesets were unbundled'))],
5981 _('update to new branch head if changesets were unbundled'))],
5980 _('[-u] FILE...'),
5982 _('[-u] FILE...'),
5981 helpcategory=command.CATEGORY_IMPORT_EXPORT)
5983 helpcategory=command.CATEGORY_IMPORT_EXPORT)
5982 def unbundle(ui, repo, fname1, *fnames, **opts):
5984 def unbundle(ui, repo, fname1, *fnames, **opts):
5983 """apply one or more bundle files
5985 """apply one or more bundle files
5984
5986
5985 Apply one or more bundle files generated by :hg:`bundle`.
5987 Apply one or more bundle files generated by :hg:`bundle`.
5986
5988
5987 Returns 0 on success, 1 if an update has unresolved files.
5989 Returns 0 on success, 1 if an update has unresolved files.
5988 """
5990 """
5989 fnames = (fname1,) + fnames
5991 fnames = (fname1,) + fnames
5990
5992
5991 with repo.lock():
5993 with repo.lock():
5992 for fname in fnames:
5994 for fname in fnames:
5993 f = hg.openpath(ui, fname)
5995 f = hg.openpath(ui, fname)
5994 gen = exchange.readbundle(ui, f, fname)
5996 gen = exchange.readbundle(ui, f, fname)
5995 if isinstance(gen, streamclone.streamcloneapplier):
5997 if isinstance(gen, streamclone.streamcloneapplier):
5996 raise error.Abort(
5998 raise error.Abort(
5997 _('packed bundles cannot be applied with '
5999 _('packed bundles cannot be applied with '
5998 '"hg unbundle"'),
6000 '"hg unbundle"'),
5999 hint=_('use "hg debugapplystreamclonebundle"'))
6001 hint=_('use "hg debugapplystreamclonebundle"'))
6000 url = 'bundle:' + fname
6002 url = 'bundle:' + fname
6001 try:
6003 try:
6002 txnname = 'unbundle'
6004 txnname = 'unbundle'
6003 if not isinstance(gen, bundle2.unbundle20):
6005 if not isinstance(gen, bundle2.unbundle20):
6004 txnname = 'unbundle\n%s' % util.hidepassword(url)
6006 txnname = 'unbundle\n%s' % util.hidepassword(url)
6005 with repo.transaction(txnname) as tr:
6007 with repo.transaction(txnname) as tr:
6006 op = bundle2.applybundle(repo, gen, tr, source='unbundle',
6008 op = bundle2.applybundle(repo, gen, tr, source='unbundle',
6007 url=url)
6009 url=url)
6008 except error.BundleUnknownFeatureError as exc:
6010 except error.BundleUnknownFeatureError as exc:
6009 raise error.Abort(
6011 raise error.Abort(
6010 _('%s: unknown bundle feature, %s') % (fname, exc),
6012 _('%s: unknown bundle feature, %s') % (fname, exc),
6011 hint=_("see https://mercurial-scm.org/"
6013 hint=_("see https://mercurial-scm.org/"
6012 "wiki/BundleFeature for more "
6014 "wiki/BundleFeature for more "
6013 "information"))
6015 "information"))
6014 modheads = bundle2.combinechangegroupresults(op)
6016 modheads = bundle2.combinechangegroupresults(op)
6015
6017
6016 return postincoming(ui, repo, modheads, opts.get(r'update'), None, None)
6018 return postincoming(ui, repo, modheads, opts.get(r'update'), None, None)
6017
6019
6018 @command('update|up|checkout|co',
6020 @command('update|up|checkout|co',
6019 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
6021 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
6020 ('c', 'check', None, _('require clean working directory')),
6022 ('c', 'check', None, _('require clean working directory')),
6021 ('m', 'merge', None, _('merge uncommitted changes')),
6023 ('m', 'merge', None, _('merge uncommitted changes')),
6022 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
6024 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
6023 ('r', 'rev', '', _('revision'), _('REV'))
6025 ('r', 'rev', '', _('revision'), _('REV'))
6024 ] + mergetoolopts,
6026 ] + mergetoolopts,
6025 _('[-C|-c|-m] [-d DATE] [[-r] REV]'),
6027 _('[-C|-c|-m] [-d DATE] [[-r] REV]'),
6026 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6028 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6027 helpbasic=True)
6029 helpbasic=True)
6028 def update(ui, repo, node=None, **opts):
6030 def update(ui, repo, node=None, **opts):
6029 """update working directory (or switch revisions)
6031 """update working directory (or switch revisions)
6030
6032
6031 Update the repository's working directory to the specified
6033 Update the repository's working directory to the specified
6032 changeset. If no changeset is specified, update to the tip of the
6034 changeset. If no changeset is specified, update to the tip of the
6033 current named branch and move the active bookmark (see :hg:`help
6035 current named branch and move the active bookmark (see :hg:`help
6034 bookmarks`).
6036 bookmarks`).
6035
6037
6036 Update sets the working directory's parent revision to the specified
6038 Update sets the working directory's parent revision to the specified
6037 changeset (see :hg:`help parents`).
6039 changeset (see :hg:`help parents`).
6038
6040
6039 If the changeset is not a descendant or ancestor of the working
6041 If the changeset is not a descendant or ancestor of the working
6040 directory's parent and there are uncommitted changes, the update is
6042 directory's parent and there are uncommitted changes, the update is
6041 aborted. With the -c/--check option, the working directory is checked
6043 aborted. With the -c/--check option, the working directory is checked
6042 for uncommitted changes; if none are found, the working directory is
6044 for uncommitted changes; if none are found, the working directory is
6043 updated to the specified changeset.
6045 updated to the specified changeset.
6044
6046
6045 .. container:: verbose
6047 .. container:: verbose
6046
6048
6047 The -C/--clean, -c/--check, and -m/--merge options control what
6049 The -C/--clean, -c/--check, and -m/--merge options control what
6048 happens if the working directory contains uncommitted changes.
6050 happens if the working directory contains uncommitted changes.
6049 At most of one of them can be specified.
6051 At most of one of them can be specified.
6050
6052
6051 1. If no option is specified, and if
6053 1. If no option is specified, and if
6052 the requested changeset is an ancestor or descendant of
6054 the requested changeset is an ancestor or descendant of
6053 the working directory's parent, the uncommitted changes
6055 the working directory's parent, the uncommitted changes
6054 are merged into the requested changeset and the merged
6056 are merged into the requested changeset and the merged
6055 result is left uncommitted. If the requested changeset is
6057 result is left uncommitted. If the requested changeset is
6056 not an ancestor or descendant (that is, it is on another
6058 not an ancestor or descendant (that is, it is on another
6057 branch), the update is aborted and the uncommitted changes
6059 branch), the update is aborted and the uncommitted changes
6058 are preserved.
6060 are preserved.
6059
6061
6060 2. With the -m/--merge option, the update is allowed even if the
6062 2. With the -m/--merge option, the update is allowed even if the
6061 requested changeset is not an ancestor or descendant of
6063 requested changeset is not an ancestor or descendant of
6062 the working directory's parent.
6064 the working directory's parent.
6063
6065
6064 3. With the -c/--check option, the update is aborted and the
6066 3. With the -c/--check option, the update is aborted and the
6065 uncommitted changes are preserved.
6067 uncommitted changes are preserved.
6066
6068
6067 4. With the -C/--clean option, uncommitted changes are discarded and
6069 4. With the -C/--clean option, uncommitted changes are discarded and
6068 the working directory is updated to the requested changeset.
6070 the working directory is updated to the requested changeset.
6069
6071
6070 To cancel an uncommitted merge (and lose your changes), use
6072 To cancel an uncommitted merge (and lose your changes), use
6071 :hg:`merge --abort`.
6073 :hg:`merge --abort`.
6072
6074
6073 Use null as the changeset to remove the working directory (like
6075 Use null as the changeset to remove the working directory (like
6074 :hg:`clone -U`).
6076 :hg:`clone -U`).
6075
6077
6076 If you want to revert just one file to an older revision, use
6078 If you want to revert just one file to an older revision, use
6077 :hg:`revert [-r REV] NAME`.
6079 :hg:`revert [-r REV] NAME`.
6078
6080
6079 See :hg:`help dates` for a list of formats valid for -d/--date.
6081 See :hg:`help dates` for a list of formats valid for -d/--date.
6080
6082
6081 Returns 0 on success, 1 if there are unresolved files.
6083 Returns 0 on success, 1 if there are unresolved files.
6082 """
6084 """
6083 rev = opts.get(r'rev')
6085 rev = opts.get(r'rev')
6084 date = opts.get(r'date')
6086 date = opts.get(r'date')
6085 clean = opts.get(r'clean')
6087 clean = opts.get(r'clean')
6086 check = opts.get(r'check')
6088 check = opts.get(r'check')
6087 merge = opts.get(r'merge')
6089 merge = opts.get(r'merge')
6088 if rev and node:
6090 if rev and node:
6089 raise error.Abort(_("please specify just one revision"))
6091 raise error.Abort(_("please specify just one revision"))
6090
6092
6091 if ui.configbool('commands', 'update.requiredest'):
6093 if ui.configbool('commands', 'update.requiredest'):
6092 if not node and not rev and not date:
6094 if not node and not rev and not date:
6093 raise error.Abort(_('you must specify a destination'),
6095 raise error.Abort(_('you must specify a destination'),
6094 hint=_('for example: hg update ".::"'))
6096 hint=_('for example: hg update ".::"'))
6095
6097
6096 if rev is None or rev == '':
6098 if rev is None or rev == '':
6097 rev = node
6099 rev = node
6098
6100
6099 if date and rev is not None:
6101 if date and rev is not None:
6100 raise error.Abort(_("you can't specify a revision and a date"))
6102 raise error.Abort(_("you can't specify a revision and a date"))
6101
6103
6102 if len([x for x in (clean, check, merge) if x]) > 1:
6104 if len([x for x in (clean, check, merge) if x]) > 1:
6103 raise error.Abort(_("can only specify one of -C/--clean, -c/--check, "
6105 raise error.Abort(_("can only specify one of -C/--clean, -c/--check, "
6104 "or -m/--merge"))
6106 "or -m/--merge"))
6105
6107
6106 updatecheck = None
6108 updatecheck = None
6107 if check:
6109 if check:
6108 updatecheck = 'abort'
6110 updatecheck = 'abort'
6109 elif merge:
6111 elif merge:
6110 updatecheck = 'none'
6112 updatecheck = 'none'
6111
6113
6112 with repo.wlock():
6114 with repo.wlock():
6113 cmdutil.clearunfinished(repo)
6115 cmdutil.clearunfinished(repo)
6114
6116
6115 if date:
6117 if date:
6116 rev = cmdutil.finddate(ui, repo, date)
6118 rev = cmdutil.finddate(ui, repo, date)
6117
6119
6118 # if we defined a bookmark, we have to remember the original name
6120 # if we defined a bookmark, we have to remember the original name
6119 brev = rev
6121 brev = rev
6120 if rev:
6122 if rev:
6121 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
6123 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
6122 ctx = scmutil.revsingle(repo, rev, default=None)
6124 ctx = scmutil.revsingle(repo, rev, default=None)
6123 rev = ctx.rev()
6125 rev = ctx.rev()
6124 hidden = ctx.hidden()
6126 hidden = ctx.hidden()
6125 overrides = {('ui', 'forcemerge'): opts.get(r'tool', '')}
6127 overrides = {('ui', 'forcemerge'): opts.get(r'tool', '')}
6126 with ui.configoverride(overrides, 'update'):
6128 with ui.configoverride(overrides, 'update'):
6127 ret = hg.updatetotally(ui, repo, rev, brev, clean=clean,
6129 ret = hg.updatetotally(ui, repo, rev, brev, clean=clean,
6128 updatecheck=updatecheck)
6130 updatecheck=updatecheck)
6129 if hidden:
6131 if hidden:
6130 ctxstr = ctx.hex()[:12]
6132 ctxstr = ctx.hex()[:12]
6131 ui.warn(_("updated to hidden changeset %s\n") % ctxstr)
6133 ui.warn(_("updated to hidden changeset %s\n") % ctxstr)
6132
6134
6133 if ctx.obsolete():
6135 if ctx.obsolete():
6134 obsfatemsg = obsutil._getfilteredreason(repo, ctxstr, ctx)
6136 obsfatemsg = obsutil._getfilteredreason(repo, ctxstr, ctx)
6135 ui.warn("(%s)\n" % obsfatemsg)
6137 ui.warn("(%s)\n" % obsfatemsg)
6136 return ret
6138 return ret
6137
6139
6138 @command('verify', [], helpcategory=command.CATEGORY_MAINTENANCE)
6140 @command('verify', [], helpcategory=command.CATEGORY_MAINTENANCE)
6139 def verify(ui, repo):
6141 def verify(ui, repo):
6140 """verify the integrity of the repository
6142 """verify the integrity of the repository
6141
6143
6142 Verify the integrity of the current repository.
6144 Verify the integrity of the current repository.
6143
6145
6144 This will perform an extensive check of the repository's
6146 This will perform an extensive check of the repository's
6145 integrity, validating the hashes and checksums of each entry in
6147 integrity, validating the hashes and checksums of each entry in
6146 the changelog, manifest, and tracked files, as well as the
6148 the changelog, manifest, and tracked files, as well as the
6147 integrity of their crosslinks and indices.
6149 integrity of their crosslinks and indices.
6148
6150
6149 Please see https://mercurial-scm.org/wiki/RepositoryCorruption
6151 Please see https://mercurial-scm.org/wiki/RepositoryCorruption
6150 for more information about recovery from corruption of the
6152 for more information about recovery from corruption of the
6151 repository.
6153 repository.
6152
6154
6153 Returns 0 on success, 1 if errors are encountered.
6155 Returns 0 on success, 1 if errors are encountered.
6154 """
6156 """
6155 return hg.verify(repo)
6157 return hg.verify(repo)
6156
6158
6157 @command(
6159 @command(
6158 'version', [] + formatteropts, helpcategory=command.CATEGORY_HELP,
6160 'version', [] + formatteropts, helpcategory=command.CATEGORY_HELP,
6159 norepo=True, intents={INTENT_READONLY})
6161 norepo=True, intents={INTENT_READONLY})
6160 def version_(ui, **opts):
6162 def version_(ui, **opts):
6161 """output version and copyright information
6163 """output version and copyright information
6162
6164
6163 .. container:: verbose
6165 .. container:: verbose
6164
6166
6165 Template:
6167 Template:
6166
6168
6167 The following keywords are supported. See also :hg:`help templates`.
6169 The following keywords are supported. See also :hg:`help templates`.
6168
6170
6169 :extensions: List of extensions.
6171 :extensions: List of extensions.
6170 :ver: String. Version number.
6172 :ver: String. Version number.
6171
6173
6172 And each entry of ``{extensions}`` provides the following sub-keywords
6174 And each entry of ``{extensions}`` provides the following sub-keywords
6173 in addition to ``{ver}``.
6175 in addition to ``{ver}``.
6174
6176
6175 :bundled: Boolean. True if included in the release.
6177 :bundled: Boolean. True if included in the release.
6176 :name: String. Extension name.
6178 :name: String. Extension name.
6177 """
6179 """
6178 opts = pycompat.byteskwargs(opts)
6180 opts = pycompat.byteskwargs(opts)
6179 if ui.verbose:
6181 if ui.verbose:
6180 ui.pager('version')
6182 ui.pager('version')
6181 fm = ui.formatter("version", opts)
6183 fm = ui.formatter("version", opts)
6182 fm.startitem()
6184 fm.startitem()
6183 fm.write("ver", _("Mercurial Distributed SCM (version %s)\n"),
6185 fm.write("ver", _("Mercurial Distributed SCM (version %s)\n"),
6184 util.version())
6186 util.version())
6185 license = _(
6187 license = _(
6186 "(see https://mercurial-scm.org for more information)\n"
6188 "(see https://mercurial-scm.org for more information)\n"
6187 "\nCopyright (C) 2005-2019 Matt Mackall and others\n"
6189 "\nCopyright (C) 2005-2019 Matt Mackall and others\n"
6188 "This is free software; see the source for copying conditions. "
6190 "This is free software; see the source for copying conditions. "
6189 "There is NO\nwarranty; "
6191 "There is NO\nwarranty; "
6190 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
6192 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
6191 )
6193 )
6192 if not ui.quiet:
6194 if not ui.quiet:
6193 fm.plain(license)
6195 fm.plain(license)
6194
6196
6195 if ui.verbose:
6197 if ui.verbose:
6196 fm.plain(_("\nEnabled extensions:\n\n"))
6198 fm.plain(_("\nEnabled extensions:\n\n"))
6197 # format names and versions into columns
6199 # format names and versions into columns
6198 names = []
6200 names = []
6199 vers = []
6201 vers = []
6200 isinternals = []
6202 isinternals = []
6201 for name, module in extensions.extensions():
6203 for name, module in extensions.extensions():
6202 names.append(name)
6204 names.append(name)
6203 vers.append(extensions.moduleversion(module) or None)
6205 vers.append(extensions.moduleversion(module) or None)
6204 isinternals.append(extensions.ismoduleinternal(module))
6206 isinternals.append(extensions.ismoduleinternal(module))
6205 fn = fm.nested("extensions", tmpl='{name}\n')
6207 fn = fm.nested("extensions", tmpl='{name}\n')
6206 if names:
6208 if names:
6207 namefmt = " %%-%ds " % max(len(n) for n in names)
6209 namefmt = " %%-%ds " % max(len(n) for n in names)
6208 places = [_("external"), _("internal")]
6210 places = [_("external"), _("internal")]
6209 for n, v, p in zip(names, vers, isinternals):
6211 for n, v, p in zip(names, vers, isinternals):
6210 fn.startitem()
6212 fn.startitem()
6211 fn.condwrite(ui.verbose, "name", namefmt, n)
6213 fn.condwrite(ui.verbose, "name", namefmt, n)
6212 if ui.verbose:
6214 if ui.verbose:
6213 fn.plain("%s " % places[p])
6215 fn.plain("%s " % places[p])
6214 fn.data(bundled=p)
6216 fn.data(bundled=p)
6215 fn.condwrite(ui.verbose and v, "ver", "%s", v)
6217 fn.condwrite(ui.verbose and v, "ver", "%s", v)
6216 if ui.verbose:
6218 if ui.verbose:
6217 fn.plain("\n")
6219 fn.plain("\n")
6218 fn.end()
6220 fn.end()
6219 fm.end()
6221 fm.end()
6220
6222
6221 def loadcmdtable(ui, name, cmdtable):
6223 def loadcmdtable(ui, name, cmdtable):
6222 """Load command functions from specified cmdtable
6224 """Load command functions from specified cmdtable
6223 """
6225 """
6224 cmdtable = cmdtable.copy()
6226 cmdtable = cmdtable.copy()
6225 for cmd in list(cmdtable):
6227 for cmd in list(cmdtable):
6226 if not cmd.startswith('^'):
6228 if not cmd.startswith('^'):
6227 continue
6229 continue
6228 ui.deprecwarn("old-style command registration '%s' in extension '%s'"
6230 ui.deprecwarn("old-style command registration '%s' in extension '%s'"
6229 % (cmd, name), '4.8')
6231 % (cmd, name), '4.8')
6230 entry = cmdtable.pop(cmd)
6232 entry = cmdtable.pop(cmd)
6231 entry[0].helpbasic = True
6233 entry[0].helpbasic = True
6232 cmdtable[cmd[1:]] = entry
6234 cmdtable[cmd[1:]] = entry
6233
6235
6234 overrides = [cmd for cmd in cmdtable if cmd in table]
6236 overrides = [cmd for cmd in cmdtable if cmd in table]
6235 if overrides:
6237 if overrides:
6236 ui.warn(_("extension '%s' overrides commands: %s\n")
6238 ui.warn(_("extension '%s' overrides commands: %s\n")
6237 % (name, " ".join(overrides)))
6239 % (name, " ".join(overrides)))
6238 table.update(cmdtable)
6240 table.update(cmdtable)
@@ -1,1838 +1,1838
1 # subrepo.py - sub-repository classes and factory
1 # subrepo.py - sub-repository classes and factory
2 #
2 #
3 # Copyright 2009-2010 Matt Mackall <mpm@selenic.com>
3 # Copyright 2009-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 copy
10 import copy
11 import errno
11 import errno
12 import hashlib
12 import hashlib
13 import os
13 import os
14 import re
14 import re
15 import stat
15 import stat
16 import subprocess
16 import subprocess
17 import sys
17 import sys
18 import tarfile
18 import tarfile
19 import xml.dom.minidom
19 import xml.dom.minidom
20
20
21 from .i18n import _
21 from .i18n import _
22 from . import (
22 from . import (
23 cmdutil,
23 cmdutil,
24 encoding,
24 encoding,
25 error,
25 error,
26 exchange,
26 exchange,
27 logcmdutil,
27 logcmdutil,
28 match as matchmod,
28 match as matchmod,
29 node,
29 node,
30 pathutil,
30 pathutil,
31 phases,
31 phases,
32 pycompat,
32 pycompat,
33 scmutil,
33 scmutil,
34 subrepoutil,
34 subrepoutil,
35 util,
35 util,
36 vfs as vfsmod,
36 vfs as vfsmod,
37 )
37 )
38 from .utils import (
38 from .utils import (
39 dateutil,
39 dateutil,
40 procutil,
40 procutil,
41 stringutil,
41 stringutil,
42 )
42 )
43
43
44 hg = None
44 hg = None
45 reporelpath = subrepoutil.reporelpath
45 reporelpath = subrepoutil.reporelpath
46 subrelpath = subrepoutil.subrelpath
46 subrelpath = subrepoutil.subrelpath
47 _abssource = subrepoutil._abssource
47 _abssource = subrepoutil._abssource
48 propertycache = util.propertycache
48 propertycache = util.propertycache
49
49
50 def _expandedabspath(path):
50 def _expandedabspath(path):
51 '''
51 '''
52 get a path or url and if it is a path expand it and return an absolute path
52 get a path or url and if it is a path expand it and return an absolute path
53 '''
53 '''
54 expandedpath = util.urllocalpath(util.expandpath(path))
54 expandedpath = util.urllocalpath(util.expandpath(path))
55 u = util.url(expandedpath)
55 u = util.url(expandedpath)
56 if not u.scheme:
56 if not u.scheme:
57 path = util.normpath(os.path.abspath(u.path))
57 path = util.normpath(os.path.abspath(u.path))
58 return path
58 return path
59
59
60 def _getstorehashcachename(remotepath):
60 def _getstorehashcachename(remotepath):
61 '''get a unique filename for the store hash cache of a remote repository'''
61 '''get a unique filename for the store hash cache of a remote repository'''
62 return node.hex(hashlib.sha1(_expandedabspath(remotepath)).digest())[0:12]
62 return node.hex(hashlib.sha1(_expandedabspath(remotepath)).digest())[0:12]
63
63
64 class SubrepoAbort(error.Abort):
64 class SubrepoAbort(error.Abort):
65 """Exception class used to avoid handling a subrepo error more than once"""
65 """Exception class used to avoid handling a subrepo error more than once"""
66 def __init__(self, *args, **kw):
66 def __init__(self, *args, **kw):
67 self.subrepo = kw.pop(r'subrepo', None)
67 self.subrepo = kw.pop(r'subrepo', None)
68 self.cause = kw.pop(r'cause', None)
68 self.cause = kw.pop(r'cause', None)
69 error.Abort.__init__(self, *args, **kw)
69 error.Abort.__init__(self, *args, **kw)
70
70
71 def annotatesubrepoerror(func):
71 def annotatesubrepoerror(func):
72 def decoratedmethod(self, *args, **kargs):
72 def decoratedmethod(self, *args, **kargs):
73 try:
73 try:
74 res = func(self, *args, **kargs)
74 res = func(self, *args, **kargs)
75 except SubrepoAbort as ex:
75 except SubrepoAbort as ex:
76 # This exception has already been handled
76 # This exception has already been handled
77 raise ex
77 raise ex
78 except error.Abort as ex:
78 except error.Abort as ex:
79 subrepo = subrelpath(self)
79 subrepo = subrelpath(self)
80 errormsg = (stringutil.forcebytestr(ex) + ' '
80 errormsg = (stringutil.forcebytestr(ex) + ' '
81 + _('(in subrepository "%s")') % subrepo)
81 + _('(in subrepository "%s")') % subrepo)
82 # avoid handling this exception by raising a SubrepoAbort exception
82 # avoid handling this exception by raising a SubrepoAbort exception
83 raise SubrepoAbort(errormsg, hint=ex.hint, subrepo=subrepo,
83 raise SubrepoAbort(errormsg, hint=ex.hint, subrepo=subrepo,
84 cause=sys.exc_info())
84 cause=sys.exc_info())
85 return res
85 return res
86 return decoratedmethod
86 return decoratedmethod
87
87
88 def _updateprompt(ui, sub, dirty, local, remote):
88 def _updateprompt(ui, sub, dirty, local, remote):
89 if dirty:
89 if dirty:
90 msg = (_(' subrepository sources for %s differ\n'
90 msg = (_(' subrepository sources for %s differ\n'
91 'use (l)ocal source (%s) or (r)emote source (%s)?'
91 'use (l)ocal source (%s) or (r)emote source (%s)?'
92 '$$ &Local $$ &Remote')
92 '$$ &Local $$ &Remote')
93 % (subrelpath(sub), local, remote))
93 % (subrelpath(sub), local, remote))
94 else:
94 else:
95 msg = (_(' subrepository sources for %s differ (in checked out '
95 msg = (_(' subrepository sources for %s differ (in checked out '
96 'version)\n'
96 'version)\n'
97 'use (l)ocal source (%s) or (r)emote source (%s)?'
97 'use (l)ocal source (%s) or (r)emote source (%s)?'
98 '$$ &Local $$ &Remote')
98 '$$ &Local $$ &Remote')
99 % (subrelpath(sub), local, remote))
99 % (subrelpath(sub), local, remote))
100 return ui.promptchoice(msg, 0)
100 return ui.promptchoice(msg, 0)
101
101
102 def _sanitize(ui, vfs, ignore):
102 def _sanitize(ui, vfs, ignore):
103 for dirname, dirs, names in vfs.walk():
103 for dirname, dirs, names in vfs.walk():
104 for i, d in enumerate(dirs):
104 for i, d in enumerate(dirs):
105 if d.lower() == ignore:
105 if d.lower() == ignore:
106 del dirs[i]
106 del dirs[i]
107 break
107 break
108 if vfs.basename(dirname).lower() != '.hg':
108 if vfs.basename(dirname).lower() != '.hg':
109 continue
109 continue
110 for f in names:
110 for f in names:
111 if f.lower() == 'hgrc':
111 if f.lower() == 'hgrc':
112 ui.warn(_("warning: removing potentially hostile 'hgrc' "
112 ui.warn(_("warning: removing potentially hostile 'hgrc' "
113 "in '%s'\n") % vfs.join(dirname))
113 "in '%s'\n") % vfs.join(dirname))
114 vfs.unlink(vfs.reljoin(dirname, f))
114 vfs.unlink(vfs.reljoin(dirname, f))
115
115
116 def _auditsubrepopath(repo, path):
116 def _auditsubrepopath(repo, path):
117 # sanity check for potentially unsafe paths such as '~' and '$FOO'
117 # sanity check for potentially unsafe paths such as '~' and '$FOO'
118 if path.startswith('~') or '$' in path or util.expandpath(path) != path:
118 if path.startswith('~') or '$' in path or util.expandpath(path) != path:
119 raise error.Abort(_('subrepo path contains illegal component: %s')
119 raise error.Abort(_('subrepo path contains illegal component: %s')
120 % path)
120 % path)
121 # auditor doesn't check if the path itself is a symlink
121 # auditor doesn't check if the path itself is a symlink
122 pathutil.pathauditor(repo.root)(path)
122 pathutil.pathauditor(repo.root)(path)
123 if repo.wvfs.islink(path):
123 if repo.wvfs.islink(path):
124 raise error.Abort(_("subrepo '%s' traverses symbolic link") % path)
124 raise error.Abort(_("subrepo '%s' traverses symbolic link") % path)
125
125
126 SUBREPO_ALLOWED_DEFAULTS = {
126 SUBREPO_ALLOWED_DEFAULTS = {
127 'hg': True,
127 'hg': True,
128 'git': False,
128 'git': False,
129 'svn': False,
129 'svn': False,
130 }
130 }
131
131
132 def _checktype(ui, kind):
132 def _checktype(ui, kind):
133 # subrepos.allowed is a master kill switch. If disabled, subrepos are
133 # subrepos.allowed is a master kill switch. If disabled, subrepos are
134 # disabled period.
134 # disabled period.
135 if not ui.configbool('subrepos', 'allowed', True):
135 if not ui.configbool('subrepos', 'allowed', True):
136 raise error.Abort(_('subrepos not enabled'),
136 raise error.Abort(_('subrepos not enabled'),
137 hint=_("see 'hg help config.subrepos' for details"))
137 hint=_("see 'hg help config.subrepos' for details"))
138
138
139 default = SUBREPO_ALLOWED_DEFAULTS.get(kind, False)
139 default = SUBREPO_ALLOWED_DEFAULTS.get(kind, False)
140 if not ui.configbool('subrepos', '%s:allowed' % kind, default):
140 if not ui.configbool('subrepos', '%s:allowed' % kind, default):
141 raise error.Abort(_('%s subrepos not allowed') % kind,
141 raise error.Abort(_('%s subrepos not allowed') % kind,
142 hint=_("see 'hg help config.subrepos' for details"))
142 hint=_("see 'hg help config.subrepos' for details"))
143
143
144 if kind not in types:
144 if kind not in types:
145 raise error.Abort(_('unknown subrepo type %s') % kind)
145 raise error.Abort(_('unknown subrepo type %s') % kind)
146
146
147 def subrepo(ctx, path, allowwdir=False, allowcreate=True):
147 def subrepo(ctx, path, allowwdir=False, allowcreate=True):
148 """return instance of the right subrepo class for subrepo in path"""
148 """return instance of the right subrepo class for subrepo in path"""
149 # subrepo inherently violates our import layering rules
149 # subrepo inherently violates our import layering rules
150 # because it wants to make repo objects from deep inside the stack
150 # because it wants to make repo objects from deep inside the stack
151 # so we manually delay the circular imports to not break
151 # so we manually delay the circular imports to not break
152 # scripts that don't use our demand-loading
152 # scripts that don't use our demand-loading
153 global hg
153 global hg
154 from . import hg as h
154 from . import hg as h
155 hg = h
155 hg = h
156
156
157 repo = ctx.repo()
157 repo = ctx.repo()
158 _auditsubrepopath(repo, path)
158 _auditsubrepopath(repo, path)
159 state = ctx.substate[path]
159 state = ctx.substate[path]
160 _checktype(repo.ui, state[2])
160 _checktype(repo.ui, state[2])
161 if allowwdir:
161 if allowwdir:
162 state = (state[0], ctx.subrev(path), state[2])
162 state = (state[0], ctx.subrev(path), state[2])
163 return types[state[2]](ctx, path, state[:2], allowcreate)
163 return types[state[2]](ctx, path, state[:2], allowcreate)
164
164
165 def nullsubrepo(ctx, path, pctx):
165 def nullsubrepo(ctx, path, pctx):
166 """return an empty subrepo in pctx for the extant subrepo in ctx"""
166 """return an empty subrepo in pctx for the extant subrepo in ctx"""
167 # subrepo inherently violates our import layering rules
167 # subrepo inherently violates our import layering rules
168 # because it wants to make repo objects from deep inside the stack
168 # because it wants to make repo objects from deep inside the stack
169 # so we manually delay the circular imports to not break
169 # so we manually delay the circular imports to not break
170 # scripts that don't use our demand-loading
170 # scripts that don't use our demand-loading
171 global hg
171 global hg
172 from . import hg as h
172 from . import hg as h
173 hg = h
173 hg = h
174
174
175 repo = ctx.repo()
175 repo = ctx.repo()
176 _auditsubrepopath(repo, path)
176 _auditsubrepopath(repo, path)
177 state = ctx.substate[path]
177 state = ctx.substate[path]
178 _checktype(repo.ui, state[2])
178 _checktype(repo.ui, state[2])
179 subrev = ''
179 subrev = ''
180 if state[2] == 'hg':
180 if state[2] == 'hg':
181 subrev = "0" * 40
181 subrev = "0" * 40
182 return types[state[2]](pctx, path, (state[0], subrev), True)
182 return types[state[2]](pctx, path, (state[0], subrev), True)
183
183
184 # subrepo classes need to implement the following abstract class:
184 # subrepo classes need to implement the following abstract class:
185
185
186 class abstractsubrepo(object):
186 class abstractsubrepo(object):
187
187
188 def __init__(self, ctx, path):
188 def __init__(self, ctx, path):
189 """Initialize abstractsubrepo part
189 """Initialize abstractsubrepo part
190
190
191 ``ctx`` is the context referring this subrepository in the
191 ``ctx`` is the context referring this subrepository in the
192 parent repository.
192 parent repository.
193
193
194 ``path`` is the path to this subrepository as seen from
194 ``path`` is the path to this subrepository as seen from
195 innermost repository.
195 innermost repository.
196 """
196 """
197 self.ui = ctx.repo().ui
197 self.ui = ctx.repo().ui
198 self._ctx = ctx
198 self._ctx = ctx
199 self._path = path
199 self._path = path
200
200
201 def addwebdirpath(self, serverpath, webconf):
201 def addwebdirpath(self, serverpath, webconf):
202 """Add the hgwebdir entries for this subrepo, and any of its subrepos.
202 """Add the hgwebdir entries for this subrepo, and any of its subrepos.
203
203
204 ``serverpath`` is the path component of the URL for this repo.
204 ``serverpath`` is the path component of the URL for this repo.
205
205
206 ``webconf`` is the dictionary of hgwebdir entries.
206 ``webconf`` is the dictionary of hgwebdir entries.
207 """
207 """
208 pass
208 pass
209
209
210 def storeclean(self, path):
210 def storeclean(self, path):
211 """
211 """
212 returns true if the repository has not changed since it was last
212 returns true if the repository has not changed since it was last
213 cloned from or pushed to a given repository.
213 cloned from or pushed to a given repository.
214 """
214 """
215 return False
215 return False
216
216
217 def dirty(self, ignoreupdate=False, missing=False):
217 def dirty(self, ignoreupdate=False, missing=False):
218 """returns true if the dirstate of the subrepo is dirty or does not
218 """returns true if the dirstate of the subrepo is dirty or does not
219 match current stored state. If ignoreupdate is true, only check
219 match current stored state. If ignoreupdate is true, only check
220 whether the subrepo has uncommitted changes in its dirstate. If missing
220 whether the subrepo has uncommitted changes in its dirstate. If missing
221 is true, check for deleted files.
221 is true, check for deleted files.
222 """
222 """
223 raise NotImplementedError
223 raise NotImplementedError
224
224
225 def dirtyreason(self, ignoreupdate=False, missing=False):
225 def dirtyreason(self, ignoreupdate=False, missing=False):
226 """return reason string if it is ``dirty()``
226 """return reason string if it is ``dirty()``
227
227
228 Returned string should have enough information for the message
228 Returned string should have enough information for the message
229 of exception.
229 of exception.
230
230
231 This returns None, otherwise.
231 This returns None, otherwise.
232 """
232 """
233 if self.dirty(ignoreupdate=ignoreupdate, missing=missing):
233 if self.dirty(ignoreupdate=ignoreupdate, missing=missing):
234 return _('uncommitted changes in subrepository "%s"'
234 return _('uncommitted changes in subrepository "%s"'
235 ) % subrelpath(self)
235 ) % subrelpath(self)
236
236
237 def bailifchanged(self, ignoreupdate=False, hint=None):
237 def bailifchanged(self, ignoreupdate=False, hint=None):
238 """raise Abort if subrepository is ``dirty()``
238 """raise Abort if subrepository is ``dirty()``
239 """
239 """
240 dirtyreason = self.dirtyreason(ignoreupdate=ignoreupdate,
240 dirtyreason = self.dirtyreason(ignoreupdate=ignoreupdate,
241 missing=True)
241 missing=True)
242 if dirtyreason:
242 if dirtyreason:
243 raise error.Abort(dirtyreason, hint=hint)
243 raise error.Abort(dirtyreason, hint=hint)
244
244
245 def basestate(self):
245 def basestate(self):
246 """current working directory base state, disregarding .hgsubstate
246 """current working directory base state, disregarding .hgsubstate
247 state and working directory modifications"""
247 state and working directory modifications"""
248 raise NotImplementedError
248 raise NotImplementedError
249
249
250 def checknested(self, path):
250 def checknested(self, path):
251 """check if path is a subrepository within this repository"""
251 """check if path is a subrepository within this repository"""
252 return False
252 return False
253
253
254 def commit(self, text, user, date):
254 def commit(self, text, user, date):
255 """commit the current changes to the subrepo with the given
255 """commit the current changes to the subrepo with the given
256 log message. Use given user and date if possible. Return the
256 log message. Use given user and date if possible. Return the
257 new state of the subrepo.
257 new state of the subrepo.
258 """
258 """
259 raise NotImplementedError
259 raise NotImplementedError
260
260
261 def phase(self, state):
261 def phase(self, state):
262 """returns phase of specified state in the subrepository.
262 """returns phase of specified state in the subrepository.
263 """
263 """
264 return phases.public
264 return phases.public
265
265
266 def remove(self):
266 def remove(self):
267 """remove the subrepo
267 """remove the subrepo
268
268
269 (should verify the dirstate is not dirty first)
269 (should verify the dirstate is not dirty first)
270 """
270 """
271 raise NotImplementedError
271 raise NotImplementedError
272
272
273 def get(self, state, overwrite=False):
273 def get(self, state, overwrite=False):
274 """run whatever commands are needed to put the subrepo into
274 """run whatever commands are needed to put the subrepo into
275 this state
275 this state
276 """
276 """
277 raise NotImplementedError
277 raise NotImplementedError
278
278
279 def merge(self, state):
279 def merge(self, state):
280 """merge currently-saved state with the new state."""
280 """merge currently-saved state with the new state."""
281 raise NotImplementedError
281 raise NotImplementedError
282
282
283 def push(self, opts):
283 def push(self, opts):
284 """perform whatever action is analogous to 'hg push'
284 """perform whatever action is analogous to 'hg push'
285
285
286 This may be a no-op on some systems.
286 This may be a no-op on some systems.
287 """
287 """
288 raise NotImplementedError
288 raise NotImplementedError
289
289
290 def add(self, ui, match, prefix, uipathfn, explicitonly, **opts):
290 def add(self, ui, match, prefix, uipathfn, explicitonly, **opts):
291 return []
291 return []
292
292
293 def addremove(self, matcher, prefix, uipathfn, opts):
293 def addremove(self, matcher, prefix, uipathfn, opts):
294 self.ui.warn("%s: %s" % (prefix, _("addremove is not supported")))
294 self.ui.warn("%s: %s" % (prefix, _("addremove is not supported")))
295 return 1
295 return 1
296
296
297 def cat(self, match, fm, fntemplate, prefix, **opts):
297 def cat(self, match, fm, fntemplate, prefix, **opts):
298 return 1
298 return 1
299
299
300 def status(self, rev2, **opts):
300 def status(self, rev2, **opts):
301 return scmutil.status([], [], [], [], [], [], [])
301 return scmutil.status([], [], [], [], [], [], [])
302
302
303 def diff(self, ui, diffopts, node2, match, prefix, **opts):
303 def diff(self, ui, diffopts, node2, match, prefix, **opts):
304 pass
304 pass
305
305
306 def outgoing(self, ui, dest, opts):
306 def outgoing(self, ui, dest, opts):
307 return 1
307 return 1
308
308
309 def incoming(self, ui, source, opts):
309 def incoming(self, ui, source, opts):
310 return 1
310 return 1
311
311
312 def files(self):
312 def files(self):
313 """return filename iterator"""
313 """return filename iterator"""
314 raise NotImplementedError
314 raise NotImplementedError
315
315
316 def filedata(self, name, decode):
316 def filedata(self, name, decode):
317 """return file data, optionally passed through repo decoders"""
317 """return file data, optionally passed through repo decoders"""
318 raise NotImplementedError
318 raise NotImplementedError
319
319
320 def fileflags(self, name):
320 def fileflags(self, name):
321 """return file flags"""
321 """return file flags"""
322 return ''
322 return ''
323
323
324 def matchfileset(self, expr, badfn=None):
324 def matchfileset(self, expr, badfn=None):
325 """Resolve the fileset expression for this repo"""
325 """Resolve the fileset expression for this repo"""
326 return matchmod.never(badfn=badfn)
326 return matchmod.never(badfn=badfn)
327
327
328 def printfiles(self, ui, m, fm, fmt, subrepos):
328 def printfiles(self, ui, m, uipathfn, fm, fmt, subrepos):
329 """handle the files command for this subrepo"""
329 """handle the files command for this subrepo"""
330 return 1
330 return 1
331
331
332 def archive(self, archiver, prefix, match=None, decode=True):
332 def archive(self, archiver, prefix, match=None, decode=True):
333 if match is not None:
333 if match is not None:
334 files = [f for f in self.files() if match(f)]
334 files = [f for f in self.files() if match(f)]
335 else:
335 else:
336 files = self.files()
336 files = self.files()
337 total = len(files)
337 total = len(files)
338 relpath = subrelpath(self)
338 relpath = subrelpath(self)
339 progress = self.ui.makeprogress(_('archiving (%s)') % relpath,
339 progress = self.ui.makeprogress(_('archiving (%s)') % relpath,
340 unit=_('files'), total=total)
340 unit=_('files'), total=total)
341 progress.update(0)
341 progress.update(0)
342 for name in files:
342 for name in files:
343 flags = self.fileflags(name)
343 flags = self.fileflags(name)
344 mode = 'x' in flags and 0o755 or 0o644
344 mode = 'x' in flags and 0o755 or 0o644
345 symlink = 'l' in flags
345 symlink = 'l' in flags
346 archiver.addfile(prefix + name, mode, symlink,
346 archiver.addfile(prefix + name, mode, symlink,
347 self.filedata(name, decode))
347 self.filedata(name, decode))
348 progress.increment()
348 progress.increment()
349 progress.complete()
349 progress.complete()
350 return total
350 return total
351
351
352 def walk(self, match):
352 def walk(self, match):
353 '''
353 '''
354 walk recursively through the directory tree, finding all files
354 walk recursively through the directory tree, finding all files
355 matched by the match function
355 matched by the match function
356 '''
356 '''
357
357
358 def forget(self, match, prefix, uipathfn, dryrun, interactive):
358 def forget(self, match, prefix, uipathfn, dryrun, interactive):
359 return ([], [])
359 return ([], [])
360
360
361 def removefiles(self, matcher, prefix, uipathfn, after, force, subrepos,
361 def removefiles(self, matcher, prefix, uipathfn, after, force, subrepos,
362 dryrun, warnings):
362 dryrun, warnings):
363 """remove the matched files from the subrepository and the filesystem,
363 """remove the matched files from the subrepository and the filesystem,
364 possibly by force and/or after the file has been removed from the
364 possibly by force and/or after the file has been removed from the
365 filesystem. Return 0 on success, 1 on any warning.
365 filesystem. Return 0 on success, 1 on any warning.
366 """
366 """
367 warnings.append(_("warning: removefiles not implemented (%s)")
367 warnings.append(_("warning: removefiles not implemented (%s)")
368 % self._path)
368 % self._path)
369 return 1
369 return 1
370
370
371 def revert(self, substate, *pats, **opts):
371 def revert(self, substate, *pats, **opts):
372 self.ui.warn(_('%s: reverting %s subrepos is unsupported\n') \
372 self.ui.warn(_('%s: reverting %s subrepos is unsupported\n') \
373 % (substate[0], substate[2]))
373 % (substate[0], substate[2]))
374 return []
374 return []
375
375
376 def shortid(self, revid):
376 def shortid(self, revid):
377 return revid
377 return revid
378
378
379 def unshare(self):
379 def unshare(self):
380 '''
380 '''
381 convert this repository from shared to normal storage.
381 convert this repository from shared to normal storage.
382 '''
382 '''
383
383
384 def verify(self):
384 def verify(self):
385 '''verify the integrity of the repository. Return 0 on success or
385 '''verify the integrity of the repository. Return 0 on success or
386 warning, 1 on any error.
386 warning, 1 on any error.
387 '''
387 '''
388 return 0
388 return 0
389
389
390 @propertycache
390 @propertycache
391 def wvfs(self):
391 def wvfs(self):
392 """return vfs to access the working directory of this subrepository
392 """return vfs to access the working directory of this subrepository
393 """
393 """
394 return vfsmod.vfs(self._ctx.repo().wvfs.join(self._path))
394 return vfsmod.vfs(self._ctx.repo().wvfs.join(self._path))
395
395
396 @propertycache
396 @propertycache
397 def _relpath(self):
397 def _relpath(self):
398 """return path to this subrepository as seen from outermost repository
398 """return path to this subrepository as seen from outermost repository
399 """
399 """
400 return self.wvfs.reljoin(reporelpath(self._ctx.repo()), self._path)
400 return self.wvfs.reljoin(reporelpath(self._ctx.repo()), self._path)
401
401
402 class hgsubrepo(abstractsubrepo):
402 class hgsubrepo(abstractsubrepo):
403 def __init__(self, ctx, path, state, allowcreate):
403 def __init__(self, ctx, path, state, allowcreate):
404 super(hgsubrepo, self).__init__(ctx, path)
404 super(hgsubrepo, self).__init__(ctx, path)
405 self._state = state
405 self._state = state
406 r = ctx.repo()
406 r = ctx.repo()
407 root = r.wjoin(util.localpath(path))
407 root = r.wjoin(util.localpath(path))
408 create = allowcreate and not r.wvfs.exists('%s/.hg' % path)
408 create = allowcreate and not r.wvfs.exists('%s/.hg' % path)
409 # repository constructor does expand variables in path, which is
409 # repository constructor does expand variables in path, which is
410 # unsafe since subrepo path might come from untrusted source.
410 # unsafe since subrepo path might come from untrusted source.
411 if os.path.realpath(util.expandpath(root)) != root:
411 if os.path.realpath(util.expandpath(root)) != root:
412 raise error.Abort(_('subrepo path contains illegal component: %s')
412 raise error.Abort(_('subrepo path contains illegal component: %s')
413 % path)
413 % path)
414 self._repo = hg.repository(r.baseui, root, create=create)
414 self._repo = hg.repository(r.baseui, root, create=create)
415 if self._repo.root != root:
415 if self._repo.root != root:
416 raise error.ProgrammingError('failed to reject unsafe subrepo '
416 raise error.ProgrammingError('failed to reject unsafe subrepo '
417 'path: %s (expanded to %s)'
417 'path: %s (expanded to %s)'
418 % (root, self._repo.root))
418 % (root, self._repo.root))
419
419
420 # Propagate the parent's --hidden option
420 # Propagate the parent's --hidden option
421 if r is r.unfiltered():
421 if r is r.unfiltered():
422 self._repo = self._repo.unfiltered()
422 self._repo = self._repo.unfiltered()
423
423
424 self.ui = self._repo.ui
424 self.ui = self._repo.ui
425 for s, k in [('ui', 'commitsubrepos')]:
425 for s, k in [('ui', 'commitsubrepos')]:
426 v = r.ui.config(s, k)
426 v = r.ui.config(s, k)
427 if v:
427 if v:
428 self.ui.setconfig(s, k, v, 'subrepo')
428 self.ui.setconfig(s, k, v, 'subrepo')
429 # internal config: ui._usedassubrepo
429 # internal config: ui._usedassubrepo
430 self.ui.setconfig('ui', '_usedassubrepo', 'True', 'subrepo')
430 self.ui.setconfig('ui', '_usedassubrepo', 'True', 'subrepo')
431 self._initrepo(r, state[0], create)
431 self._initrepo(r, state[0], create)
432
432
433 @annotatesubrepoerror
433 @annotatesubrepoerror
434 def addwebdirpath(self, serverpath, webconf):
434 def addwebdirpath(self, serverpath, webconf):
435 cmdutil.addwebdirpath(self._repo, subrelpath(self), webconf)
435 cmdutil.addwebdirpath(self._repo, subrelpath(self), webconf)
436
436
437 def storeclean(self, path):
437 def storeclean(self, path):
438 with self._repo.lock():
438 with self._repo.lock():
439 return self._storeclean(path)
439 return self._storeclean(path)
440
440
441 def _storeclean(self, path):
441 def _storeclean(self, path):
442 clean = True
442 clean = True
443 itercache = self._calcstorehash(path)
443 itercache = self._calcstorehash(path)
444 for filehash in self._readstorehashcache(path):
444 for filehash in self._readstorehashcache(path):
445 if filehash != next(itercache, None):
445 if filehash != next(itercache, None):
446 clean = False
446 clean = False
447 break
447 break
448 if clean:
448 if clean:
449 # if not empty:
449 # if not empty:
450 # the cached and current pull states have a different size
450 # the cached and current pull states have a different size
451 clean = next(itercache, None) is None
451 clean = next(itercache, None) is None
452 return clean
452 return clean
453
453
454 def _calcstorehash(self, remotepath):
454 def _calcstorehash(self, remotepath):
455 '''calculate a unique "store hash"
455 '''calculate a unique "store hash"
456
456
457 This method is used to to detect when there are changes that may
457 This method is used to to detect when there are changes that may
458 require a push to a given remote path.'''
458 require a push to a given remote path.'''
459 # sort the files that will be hashed in increasing (likely) file size
459 # sort the files that will be hashed in increasing (likely) file size
460 filelist = ('bookmarks', 'store/phaseroots', 'store/00changelog.i')
460 filelist = ('bookmarks', 'store/phaseroots', 'store/00changelog.i')
461 yield '# %s\n' % _expandedabspath(remotepath)
461 yield '# %s\n' % _expandedabspath(remotepath)
462 vfs = self._repo.vfs
462 vfs = self._repo.vfs
463 for relname in filelist:
463 for relname in filelist:
464 filehash = node.hex(hashlib.sha1(vfs.tryread(relname)).digest())
464 filehash = node.hex(hashlib.sha1(vfs.tryread(relname)).digest())
465 yield '%s = %s\n' % (relname, filehash)
465 yield '%s = %s\n' % (relname, filehash)
466
466
467 @propertycache
467 @propertycache
468 def _cachestorehashvfs(self):
468 def _cachestorehashvfs(self):
469 return vfsmod.vfs(self._repo.vfs.join('cache/storehash'))
469 return vfsmod.vfs(self._repo.vfs.join('cache/storehash'))
470
470
471 def _readstorehashcache(self, remotepath):
471 def _readstorehashcache(self, remotepath):
472 '''read the store hash cache for a given remote repository'''
472 '''read the store hash cache for a given remote repository'''
473 cachefile = _getstorehashcachename(remotepath)
473 cachefile = _getstorehashcachename(remotepath)
474 return self._cachestorehashvfs.tryreadlines(cachefile, 'r')
474 return self._cachestorehashvfs.tryreadlines(cachefile, 'r')
475
475
476 def _cachestorehash(self, remotepath):
476 def _cachestorehash(self, remotepath):
477 '''cache the current store hash
477 '''cache the current store hash
478
478
479 Each remote repo requires its own store hash cache, because a subrepo
479 Each remote repo requires its own store hash cache, because a subrepo
480 store may be "clean" versus a given remote repo, but not versus another
480 store may be "clean" versus a given remote repo, but not versus another
481 '''
481 '''
482 cachefile = _getstorehashcachename(remotepath)
482 cachefile = _getstorehashcachename(remotepath)
483 with self._repo.lock():
483 with self._repo.lock():
484 storehash = list(self._calcstorehash(remotepath))
484 storehash = list(self._calcstorehash(remotepath))
485 vfs = self._cachestorehashvfs
485 vfs = self._cachestorehashvfs
486 vfs.writelines(cachefile, storehash, mode='wb', notindexed=True)
486 vfs.writelines(cachefile, storehash, mode='wb', notindexed=True)
487
487
488 def _getctx(self):
488 def _getctx(self):
489 '''fetch the context for this subrepo revision, possibly a workingctx
489 '''fetch the context for this subrepo revision, possibly a workingctx
490 '''
490 '''
491 if self._ctx.rev() is None:
491 if self._ctx.rev() is None:
492 return self._repo[None] # workingctx if parent is workingctx
492 return self._repo[None] # workingctx if parent is workingctx
493 else:
493 else:
494 rev = self._state[1]
494 rev = self._state[1]
495 return self._repo[rev]
495 return self._repo[rev]
496
496
497 @annotatesubrepoerror
497 @annotatesubrepoerror
498 def _initrepo(self, parentrepo, source, create):
498 def _initrepo(self, parentrepo, source, create):
499 self._repo._subparent = parentrepo
499 self._repo._subparent = parentrepo
500 self._repo._subsource = source
500 self._repo._subsource = source
501
501
502 if create:
502 if create:
503 lines = ['[paths]\n']
503 lines = ['[paths]\n']
504
504
505 def addpathconfig(key, value):
505 def addpathconfig(key, value):
506 if value:
506 if value:
507 lines.append('%s = %s\n' % (key, value))
507 lines.append('%s = %s\n' % (key, value))
508 self.ui.setconfig('paths', key, value, 'subrepo')
508 self.ui.setconfig('paths', key, value, 'subrepo')
509
509
510 defpath = _abssource(self._repo, abort=False)
510 defpath = _abssource(self._repo, abort=False)
511 defpushpath = _abssource(self._repo, True, abort=False)
511 defpushpath = _abssource(self._repo, True, abort=False)
512 addpathconfig('default', defpath)
512 addpathconfig('default', defpath)
513 if defpath != defpushpath:
513 if defpath != defpushpath:
514 addpathconfig('default-push', defpushpath)
514 addpathconfig('default-push', defpushpath)
515
515
516 self._repo.vfs.write('hgrc', util.tonativeeol(''.join(lines)))
516 self._repo.vfs.write('hgrc', util.tonativeeol(''.join(lines)))
517
517
518 @annotatesubrepoerror
518 @annotatesubrepoerror
519 def add(self, ui, match, prefix, uipathfn, explicitonly, **opts):
519 def add(self, ui, match, prefix, uipathfn, explicitonly, **opts):
520 return cmdutil.add(ui, self._repo, match, prefix, uipathfn,
520 return cmdutil.add(ui, self._repo, match, prefix, uipathfn,
521 explicitonly, **opts)
521 explicitonly, **opts)
522
522
523 @annotatesubrepoerror
523 @annotatesubrepoerror
524 def addremove(self, m, prefix, uipathfn, opts):
524 def addremove(self, m, prefix, uipathfn, opts):
525 # In the same way as sub directories are processed, once in a subrepo,
525 # In the same way as sub directories are processed, once in a subrepo,
526 # always entry any of its subrepos. Don't corrupt the options that will
526 # always entry any of its subrepos. Don't corrupt the options that will
527 # be used to process sibling subrepos however.
527 # be used to process sibling subrepos however.
528 opts = copy.copy(opts)
528 opts = copy.copy(opts)
529 opts['subrepos'] = True
529 opts['subrepos'] = True
530 return scmutil.addremove(self._repo, m, prefix, uipathfn, opts)
530 return scmutil.addremove(self._repo, m, prefix, uipathfn, opts)
531
531
532 @annotatesubrepoerror
532 @annotatesubrepoerror
533 def cat(self, match, fm, fntemplate, prefix, **opts):
533 def cat(self, match, fm, fntemplate, prefix, **opts):
534 rev = self._state[1]
534 rev = self._state[1]
535 ctx = self._repo[rev]
535 ctx = self._repo[rev]
536 return cmdutil.cat(self.ui, self._repo, ctx, match, fm, fntemplate,
536 return cmdutil.cat(self.ui, self._repo, ctx, match, fm, fntemplate,
537 prefix, **opts)
537 prefix, **opts)
538
538
539 @annotatesubrepoerror
539 @annotatesubrepoerror
540 def status(self, rev2, **opts):
540 def status(self, rev2, **opts):
541 try:
541 try:
542 rev1 = self._state[1]
542 rev1 = self._state[1]
543 ctx1 = self._repo[rev1]
543 ctx1 = self._repo[rev1]
544 ctx2 = self._repo[rev2]
544 ctx2 = self._repo[rev2]
545 return self._repo.status(ctx1, ctx2, **opts)
545 return self._repo.status(ctx1, ctx2, **opts)
546 except error.RepoLookupError as inst:
546 except error.RepoLookupError as inst:
547 self.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
547 self.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
548 % (inst, subrelpath(self)))
548 % (inst, subrelpath(self)))
549 return scmutil.status([], [], [], [], [], [], [])
549 return scmutil.status([], [], [], [], [], [], [])
550
550
551 @annotatesubrepoerror
551 @annotatesubrepoerror
552 def diff(self, ui, diffopts, node2, match, prefix, **opts):
552 def diff(self, ui, diffopts, node2, match, prefix, **opts):
553 try:
553 try:
554 node1 = node.bin(self._state[1])
554 node1 = node.bin(self._state[1])
555 # We currently expect node2 to come from substate and be
555 # We currently expect node2 to come from substate and be
556 # in hex format
556 # in hex format
557 if node2 is not None:
557 if node2 is not None:
558 node2 = node.bin(node2)
558 node2 = node.bin(node2)
559 logcmdutil.diffordiffstat(ui, self._repo, diffopts, node1, node2,
559 logcmdutil.diffordiffstat(ui, self._repo, diffopts, node1, node2,
560 match, prefix=prefix, listsubrepos=True,
560 match, prefix=prefix, listsubrepos=True,
561 **opts)
561 **opts)
562 except error.RepoLookupError as inst:
562 except error.RepoLookupError as inst:
563 self.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
563 self.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
564 % (inst, subrelpath(self)))
564 % (inst, subrelpath(self)))
565
565
566 @annotatesubrepoerror
566 @annotatesubrepoerror
567 def archive(self, archiver, prefix, match=None, decode=True):
567 def archive(self, archiver, prefix, match=None, decode=True):
568 self._get(self._state + ('hg',))
568 self._get(self._state + ('hg',))
569 files = self.files()
569 files = self.files()
570 if match:
570 if match:
571 files = [f for f in files if match(f)]
571 files = [f for f in files if match(f)]
572 rev = self._state[1]
572 rev = self._state[1]
573 ctx = self._repo[rev]
573 ctx = self._repo[rev]
574 scmutil.prefetchfiles(self._repo, [ctx.rev()],
574 scmutil.prefetchfiles(self._repo, [ctx.rev()],
575 scmutil.matchfiles(self._repo, files))
575 scmutil.matchfiles(self._repo, files))
576 total = abstractsubrepo.archive(self, archiver, prefix, match)
576 total = abstractsubrepo.archive(self, archiver, prefix, match)
577 for subpath in ctx.substate:
577 for subpath in ctx.substate:
578 s = subrepo(ctx, subpath, True)
578 s = subrepo(ctx, subpath, True)
579 submatch = matchmod.subdirmatcher(subpath, match)
579 submatch = matchmod.subdirmatcher(subpath, match)
580 subprefix = prefix + subpath + '/'
580 subprefix = prefix + subpath + '/'
581 total += s.archive(archiver, subprefix, submatch,
581 total += s.archive(archiver, subprefix, submatch,
582 decode)
582 decode)
583 return total
583 return total
584
584
585 @annotatesubrepoerror
585 @annotatesubrepoerror
586 def dirty(self, ignoreupdate=False, missing=False):
586 def dirty(self, ignoreupdate=False, missing=False):
587 r = self._state[1]
587 r = self._state[1]
588 if r == '' and not ignoreupdate: # no state recorded
588 if r == '' and not ignoreupdate: # no state recorded
589 return True
589 return True
590 w = self._repo[None]
590 w = self._repo[None]
591 if r != w.p1().hex() and not ignoreupdate:
591 if r != w.p1().hex() and not ignoreupdate:
592 # different version checked out
592 # different version checked out
593 return True
593 return True
594 return w.dirty(missing=missing) # working directory changed
594 return w.dirty(missing=missing) # working directory changed
595
595
596 def basestate(self):
596 def basestate(self):
597 return self._repo['.'].hex()
597 return self._repo['.'].hex()
598
598
599 def checknested(self, path):
599 def checknested(self, path):
600 return self._repo._checknested(self._repo.wjoin(path))
600 return self._repo._checknested(self._repo.wjoin(path))
601
601
602 @annotatesubrepoerror
602 @annotatesubrepoerror
603 def commit(self, text, user, date):
603 def commit(self, text, user, date):
604 # don't bother committing in the subrepo if it's only been
604 # don't bother committing in the subrepo if it's only been
605 # updated
605 # updated
606 if not self.dirty(True):
606 if not self.dirty(True):
607 return self._repo['.'].hex()
607 return self._repo['.'].hex()
608 self.ui.debug("committing subrepo %s\n" % subrelpath(self))
608 self.ui.debug("committing subrepo %s\n" % subrelpath(self))
609 n = self._repo.commit(text, user, date)
609 n = self._repo.commit(text, user, date)
610 if not n:
610 if not n:
611 return self._repo['.'].hex() # different version checked out
611 return self._repo['.'].hex() # different version checked out
612 return node.hex(n)
612 return node.hex(n)
613
613
614 @annotatesubrepoerror
614 @annotatesubrepoerror
615 def phase(self, state):
615 def phase(self, state):
616 return self._repo[state or '.'].phase()
616 return self._repo[state or '.'].phase()
617
617
618 @annotatesubrepoerror
618 @annotatesubrepoerror
619 def remove(self):
619 def remove(self):
620 # we can't fully delete the repository as it may contain
620 # we can't fully delete the repository as it may contain
621 # local-only history
621 # local-only history
622 self.ui.note(_('removing subrepo %s\n') % subrelpath(self))
622 self.ui.note(_('removing subrepo %s\n') % subrelpath(self))
623 hg.clean(self._repo, node.nullid, False)
623 hg.clean(self._repo, node.nullid, False)
624
624
625 def _get(self, state):
625 def _get(self, state):
626 source, revision, kind = state
626 source, revision, kind = state
627 parentrepo = self._repo._subparent
627 parentrepo = self._repo._subparent
628
628
629 if revision in self._repo.unfiltered():
629 if revision in self._repo.unfiltered():
630 # Allow shared subrepos tracked at null to setup the sharedpath
630 # Allow shared subrepos tracked at null to setup the sharedpath
631 if len(self._repo) != 0 or not parentrepo.shared():
631 if len(self._repo) != 0 or not parentrepo.shared():
632 return True
632 return True
633 self._repo._subsource = source
633 self._repo._subsource = source
634 srcurl = _abssource(self._repo)
634 srcurl = _abssource(self._repo)
635
635
636 # Defer creating the peer until after the status message is logged, in
636 # Defer creating the peer until after the status message is logged, in
637 # case there are network problems.
637 # case there are network problems.
638 getpeer = lambda: hg.peer(self._repo, {}, srcurl)
638 getpeer = lambda: hg.peer(self._repo, {}, srcurl)
639
639
640 if len(self._repo) == 0:
640 if len(self._repo) == 0:
641 # use self._repo.vfs instead of self.wvfs to remove .hg only
641 # use self._repo.vfs instead of self.wvfs to remove .hg only
642 self._repo.vfs.rmtree()
642 self._repo.vfs.rmtree()
643
643
644 # A remote subrepo could be shared if there is a local copy
644 # A remote subrepo could be shared if there is a local copy
645 # relative to the parent's share source. But clone pooling doesn't
645 # relative to the parent's share source. But clone pooling doesn't
646 # assemble the repos in a tree, so that can't be consistently done.
646 # assemble the repos in a tree, so that can't be consistently done.
647 # A simpler option is for the user to configure clone pooling, and
647 # A simpler option is for the user to configure clone pooling, and
648 # work with that.
648 # work with that.
649 if parentrepo.shared() and hg.islocal(srcurl):
649 if parentrepo.shared() and hg.islocal(srcurl):
650 self.ui.status(_('sharing subrepo %s from %s\n')
650 self.ui.status(_('sharing subrepo %s from %s\n')
651 % (subrelpath(self), srcurl))
651 % (subrelpath(self), srcurl))
652 shared = hg.share(self._repo._subparent.baseui,
652 shared = hg.share(self._repo._subparent.baseui,
653 getpeer(), self._repo.root,
653 getpeer(), self._repo.root,
654 update=False, bookmarks=False)
654 update=False, bookmarks=False)
655 self._repo = shared.local()
655 self._repo = shared.local()
656 else:
656 else:
657 # TODO: find a common place for this and this code in the
657 # TODO: find a common place for this and this code in the
658 # share.py wrap of the clone command.
658 # share.py wrap of the clone command.
659 if parentrepo.shared():
659 if parentrepo.shared():
660 pool = self.ui.config('share', 'pool')
660 pool = self.ui.config('share', 'pool')
661 if pool:
661 if pool:
662 pool = util.expandpath(pool)
662 pool = util.expandpath(pool)
663
663
664 shareopts = {
664 shareopts = {
665 'pool': pool,
665 'pool': pool,
666 'mode': self.ui.config('share', 'poolnaming'),
666 'mode': self.ui.config('share', 'poolnaming'),
667 }
667 }
668 else:
668 else:
669 shareopts = {}
669 shareopts = {}
670
670
671 self.ui.status(_('cloning subrepo %s from %s\n')
671 self.ui.status(_('cloning subrepo %s from %s\n')
672 % (subrelpath(self), util.hidepassword(srcurl)))
672 % (subrelpath(self), util.hidepassword(srcurl)))
673 other, cloned = hg.clone(self._repo._subparent.baseui, {},
673 other, cloned = hg.clone(self._repo._subparent.baseui, {},
674 getpeer(), self._repo.root,
674 getpeer(), self._repo.root,
675 update=False, shareopts=shareopts)
675 update=False, shareopts=shareopts)
676 self._repo = cloned.local()
676 self._repo = cloned.local()
677 self._initrepo(parentrepo, source, create=True)
677 self._initrepo(parentrepo, source, create=True)
678 self._cachestorehash(srcurl)
678 self._cachestorehash(srcurl)
679 else:
679 else:
680 self.ui.status(_('pulling subrepo %s from %s\n')
680 self.ui.status(_('pulling subrepo %s from %s\n')
681 % (subrelpath(self), util.hidepassword(srcurl)))
681 % (subrelpath(self), util.hidepassword(srcurl)))
682 cleansub = self.storeclean(srcurl)
682 cleansub = self.storeclean(srcurl)
683 exchange.pull(self._repo, getpeer())
683 exchange.pull(self._repo, getpeer())
684 if cleansub:
684 if cleansub:
685 # keep the repo clean after pull
685 # keep the repo clean after pull
686 self._cachestorehash(srcurl)
686 self._cachestorehash(srcurl)
687 return False
687 return False
688
688
689 @annotatesubrepoerror
689 @annotatesubrepoerror
690 def get(self, state, overwrite=False):
690 def get(self, state, overwrite=False):
691 inrepo = self._get(state)
691 inrepo = self._get(state)
692 source, revision, kind = state
692 source, revision, kind = state
693 repo = self._repo
693 repo = self._repo
694 repo.ui.debug("getting subrepo %s\n" % self._path)
694 repo.ui.debug("getting subrepo %s\n" % self._path)
695 if inrepo:
695 if inrepo:
696 urepo = repo.unfiltered()
696 urepo = repo.unfiltered()
697 ctx = urepo[revision]
697 ctx = urepo[revision]
698 if ctx.hidden():
698 if ctx.hidden():
699 urepo.ui.warn(
699 urepo.ui.warn(
700 _('revision %s in subrepository "%s" is hidden\n') \
700 _('revision %s in subrepository "%s" is hidden\n') \
701 % (revision[0:12], self._path))
701 % (revision[0:12], self._path))
702 repo = urepo
702 repo = urepo
703 hg.updaterepo(repo, revision, overwrite)
703 hg.updaterepo(repo, revision, overwrite)
704
704
705 @annotatesubrepoerror
705 @annotatesubrepoerror
706 def merge(self, state):
706 def merge(self, state):
707 self._get(state)
707 self._get(state)
708 cur = self._repo['.']
708 cur = self._repo['.']
709 dst = self._repo[state[1]]
709 dst = self._repo[state[1]]
710 anc = dst.ancestor(cur)
710 anc = dst.ancestor(cur)
711
711
712 def mergefunc():
712 def mergefunc():
713 if anc == cur and dst.branch() == cur.branch():
713 if anc == cur and dst.branch() == cur.branch():
714 self.ui.debug('updating subrepository "%s"\n'
714 self.ui.debug('updating subrepository "%s"\n'
715 % subrelpath(self))
715 % subrelpath(self))
716 hg.update(self._repo, state[1])
716 hg.update(self._repo, state[1])
717 elif anc == dst:
717 elif anc == dst:
718 self.ui.debug('skipping subrepository "%s"\n'
718 self.ui.debug('skipping subrepository "%s"\n'
719 % subrelpath(self))
719 % subrelpath(self))
720 else:
720 else:
721 self.ui.debug('merging subrepository "%s"\n' % subrelpath(self))
721 self.ui.debug('merging subrepository "%s"\n' % subrelpath(self))
722 hg.merge(self._repo, state[1], remind=False)
722 hg.merge(self._repo, state[1], remind=False)
723
723
724 wctx = self._repo[None]
724 wctx = self._repo[None]
725 if self.dirty():
725 if self.dirty():
726 if anc != dst:
726 if anc != dst:
727 if _updateprompt(self.ui, self, wctx.dirty(), cur, dst):
727 if _updateprompt(self.ui, self, wctx.dirty(), cur, dst):
728 mergefunc()
728 mergefunc()
729 else:
729 else:
730 mergefunc()
730 mergefunc()
731 else:
731 else:
732 mergefunc()
732 mergefunc()
733
733
734 @annotatesubrepoerror
734 @annotatesubrepoerror
735 def push(self, opts):
735 def push(self, opts):
736 force = opts.get('force')
736 force = opts.get('force')
737 newbranch = opts.get('new_branch')
737 newbranch = opts.get('new_branch')
738 ssh = opts.get('ssh')
738 ssh = opts.get('ssh')
739
739
740 # push subrepos depth-first for coherent ordering
740 # push subrepos depth-first for coherent ordering
741 c = self._repo['.']
741 c = self._repo['.']
742 subs = c.substate # only repos that are committed
742 subs = c.substate # only repos that are committed
743 for s in sorted(subs):
743 for s in sorted(subs):
744 if c.sub(s).push(opts) == 0:
744 if c.sub(s).push(opts) == 0:
745 return False
745 return False
746
746
747 dsturl = _abssource(self._repo, True)
747 dsturl = _abssource(self._repo, True)
748 if not force:
748 if not force:
749 if self.storeclean(dsturl):
749 if self.storeclean(dsturl):
750 self.ui.status(
750 self.ui.status(
751 _('no changes made to subrepo %s since last push to %s\n')
751 _('no changes made to subrepo %s since last push to %s\n')
752 % (subrelpath(self), util.hidepassword(dsturl)))
752 % (subrelpath(self), util.hidepassword(dsturl)))
753 return None
753 return None
754 self.ui.status(_('pushing subrepo %s to %s\n') %
754 self.ui.status(_('pushing subrepo %s to %s\n') %
755 (subrelpath(self), util.hidepassword(dsturl)))
755 (subrelpath(self), util.hidepassword(dsturl)))
756 other = hg.peer(self._repo, {'ssh': ssh}, dsturl)
756 other = hg.peer(self._repo, {'ssh': ssh}, dsturl)
757 res = exchange.push(self._repo, other, force, newbranch=newbranch)
757 res = exchange.push(self._repo, other, force, newbranch=newbranch)
758
758
759 # the repo is now clean
759 # the repo is now clean
760 self._cachestorehash(dsturl)
760 self._cachestorehash(dsturl)
761 return res.cgresult
761 return res.cgresult
762
762
763 @annotatesubrepoerror
763 @annotatesubrepoerror
764 def outgoing(self, ui, dest, opts):
764 def outgoing(self, ui, dest, opts):
765 if 'rev' in opts or 'branch' in opts:
765 if 'rev' in opts or 'branch' in opts:
766 opts = copy.copy(opts)
766 opts = copy.copy(opts)
767 opts.pop('rev', None)
767 opts.pop('rev', None)
768 opts.pop('branch', None)
768 opts.pop('branch', None)
769 return hg.outgoing(ui, self._repo, _abssource(self._repo, True), opts)
769 return hg.outgoing(ui, self._repo, _abssource(self._repo, True), opts)
770
770
771 @annotatesubrepoerror
771 @annotatesubrepoerror
772 def incoming(self, ui, source, opts):
772 def incoming(self, ui, source, opts):
773 if 'rev' in opts or 'branch' in opts:
773 if 'rev' in opts or 'branch' in opts:
774 opts = copy.copy(opts)
774 opts = copy.copy(opts)
775 opts.pop('rev', None)
775 opts.pop('rev', None)
776 opts.pop('branch', None)
776 opts.pop('branch', None)
777 return hg.incoming(ui, self._repo, _abssource(self._repo, False), opts)
777 return hg.incoming(ui, self._repo, _abssource(self._repo, False), opts)
778
778
779 @annotatesubrepoerror
779 @annotatesubrepoerror
780 def files(self):
780 def files(self):
781 rev = self._state[1]
781 rev = self._state[1]
782 ctx = self._repo[rev]
782 ctx = self._repo[rev]
783 return ctx.manifest().keys()
783 return ctx.manifest().keys()
784
784
785 def filedata(self, name, decode):
785 def filedata(self, name, decode):
786 rev = self._state[1]
786 rev = self._state[1]
787 data = self._repo[rev][name].data()
787 data = self._repo[rev][name].data()
788 if decode:
788 if decode:
789 data = self._repo.wwritedata(name, data)
789 data = self._repo.wwritedata(name, data)
790 return data
790 return data
791
791
792 def fileflags(self, name):
792 def fileflags(self, name):
793 rev = self._state[1]
793 rev = self._state[1]
794 ctx = self._repo[rev]
794 ctx = self._repo[rev]
795 return ctx.flags(name)
795 return ctx.flags(name)
796
796
797 @annotatesubrepoerror
797 @annotatesubrepoerror
798 def printfiles(self, ui, m, fm, fmt, subrepos):
798 def printfiles(self, ui, m, uipathfn, fm, fmt, subrepos):
799 # If the parent context is a workingctx, use the workingctx here for
799 # If the parent context is a workingctx, use the workingctx here for
800 # consistency.
800 # consistency.
801 if self._ctx.rev() is None:
801 if self._ctx.rev() is None:
802 ctx = self._repo[None]
802 ctx = self._repo[None]
803 else:
803 else:
804 rev = self._state[1]
804 rev = self._state[1]
805 ctx = self._repo[rev]
805 ctx = self._repo[rev]
806 return cmdutil.files(ui, ctx, m, fm, fmt, subrepos)
806 return cmdutil.files(ui, ctx, m, uipathfn, fm, fmt, subrepos)
807
807
808 @annotatesubrepoerror
808 @annotatesubrepoerror
809 def matchfileset(self, expr, badfn=None):
809 def matchfileset(self, expr, badfn=None):
810 if self._ctx.rev() is None:
810 if self._ctx.rev() is None:
811 ctx = self._repo[None]
811 ctx = self._repo[None]
812 else:
812 else:
813 rev = self._state[1]
813 rev = self._state[1]
814 ctx = self._repo[rev]
814 ctx = self._repo[rev]
815
815
816 matchers = [ctx.matchfileset(expr, badfn=badfn)]
816 matchers = [ctx.matchfileset(expr, badfn=badfn)]
817
817
818 for subpath in ctx.substate:
818 for subpath in ctx.substate:
819 sub = ctx.sub(subpath)
819 sub = ctx.sub(subpath)
820
820
821 try:
821 try:
822 sm = sub.matchfileset(expr, badfn=badfn)
822 sm = sub.matchfileset(expr, badfn=badfn)
823 pm = matchmod.prefixdirmatcher(subpath, sm, badfn=badfn)
823 pm = matchmod.prefixdirmatcher(subpath, sm, badfn=badfn)
824 matchers.append(pm)
824 matchers.append(pm)
825 except error.LookupError:
825 except error.LookupError:
826 self.ui.status(_("skipping missing subrepository: %s\n")
826 self.ui.status(_("skipping missing subrepository: %s\n")
827 % self.wvfs.reljoin(reporelpath(self), subpath))
827 % self.wvfs.reljoin(reporelpath(self), subpath))
828 if len(matchers) == 1:
828 if len(matchers) == 1:
829 return matchers[0]
829 return matchers[0]
830 return matchmod.unionmatcher(matchers)
830 return matchmod.unionmatcher(matchers)
831
831
832 def walk(self, match):
832 def walk(self, match):
833 ctx = self._repo[None]
833 ctx = self._repo[None]
834 return ctx.walk(match)
834 return ctx.walk(match)
835
835
836 @annotatesubrepoerror
836 @annotatesubrepoerror
837 def forget(self, match, prefix, uipathfn, dryrun, interactive):
837 def forget(self, match, prefix, uipathfn, dryrun, interactive):
838 return cmdutil.forget(self.ui, self._repo, match, prefix, uipathfn,
838 return cmdutil.forget(self.ui, self._repo, match, prefix, uipathfn,
839 True, dryrun=dryrun, interactive=interactive)
839 True, dryrun=dryrun, interactive=interactive)
840
840
841 @annotatesubrepoerror
841 @annotatesubrepoerror
842 def removefiles(self, matcher, prefix, uipathfn, after, force, subrepos,
842 def removefiles(self, matcher, prefix, uipathfn, after, force, subrepos,
843 dryrun, warnings):
843 dryrun, warnings):
844 return cmdutil.remove(self.ui, self._repo, matcher, prefix, uipathfn,
844 return cmdutil.remove(self.ui, self._repo, matcher, prefix, uipathfn,
845 after, force, subrepos, dryrun)
845 after, force, subrepos, dryrun)
846
846
847 @annotatesubrepoerror
847 @annotatesubrepoerror
848 def revert(self, substate, *pats, **opts):
848 def revert(self, substate, *pats, **opts):
849 # reverting a subrepo is a 2 step process:
849 # reverting a subrepo is a 2 step process:
850 # 1. if the no_backup is not set, revert all modified
850 # 1. if the no_backup is not set, revert all modified
851 # files inside the subrepo
851 # files inside the subrepo
852 # 2. update the subrepo to the revision specified in
852 # 2. update the subrepo to the revision specified in
853 # the corresponding substate dictionary
853 # the corresponding substate dictionary
854 self.ui.status(_('reverting subrepo %s\n') % substate[0])
854 self.ui.status(_('reverting subrepo %s\n') % substate[0])
855 if not opts.get(r'no_backup'):
855 if not opts.get(r'no_backup'):
856 # Revert all files on the subrepo, creating backups
856 # Revert all files on the subrepo, creating backups
857 # Note that this will not recursively revert subrepos
857 # Note that this will not recursively revert subrepos
858 # We could do it if there was a set:subrepos() predicate
858 # We could do it if there was a set:subrepos() predicate
859 opts = opts.copy()
859 opts = opts.copy()
860 opts[r'date'] = None
860 opts[r'date'] = None
861 opts[r'rev'] = substate[1]
861 opts[r'rev'] = substate[1]
862
862
863 self.filerevert(*pats, **opts)
863 self.filerevert(*pats, **opts)
864
864
865 # Update the repo to the revision specified in the given substate
865 # Update the repo to the revision specified in the given substate
866 if not opts.get(r'dry_run'):
866 if not opts.get(r'dry_run'):
867 self.get(substate, overwrite=True)
867 self.get(substate, overwrite=True)
868
868
869 def filerevert(self, *pats, **opts):
869 def filerevert(self, *pats, **opts):
870 ctx = self._repo[opts[r'rev']]
870 ctx = self._repo[opts[r'rev']]
871 parents = self._repo.dirstate.parents()
871 parents = self._repo.dirstate.parents()
872 if opts.get(r'all'):
872 if opts.get(r'all'):
873 pats = ['set:modified()']
873 pats = ['set:modified()']
874 else:
874 else:
875 pats = []
875 pats = []
876 cmdutil.revert(self.ui, self._repo, ctx, parents, *pats, **opts)
876 cmdutil.revert(self.ui, self._repo, ctx, parents, *pats, **opts)
877
877
878 def shortid(self, revid):
878 def shortid(self, revid):
879 return revid[:12]
879 return revid[:12]
880
880
881 @annotatesubrepoerror
881 @annotatesubrepoerror
882 def unshare(self):
882 def unshare(self):
883 # subrepo inherently violates our import layering rules
883 # subrepo inherently violates our import layering rules
884 # because it wants to make repo objects from deep inside the stack
884 # because it wants to make repo objects from deep inside the stack
885 # so we manually delay the circular imports to not break
885 # so we manually delay the circular imports to not break
886 # scripts that don't use our demand-loading
886 # scripts that don't use our demand-loading
887 global hg
887 global hg
888 from . import hg as h
888 from . import hg as h
889 hg = h
889 hg = h
890
890
891 # Nothing prevents a user from sharing in a repo, and then making that a
891 # Nothing prevents a user from sharing in a repo, and then making that a
892 # subrepo. Alternately, the previous unshare attempt may have failed
892 # subrepo. Alternately, the previous unshare attempt may have failed
893 # part way through. So recurse whether or not this layer is shared.
893 # part way through. So recurse whether or not this layer is shared.
894 if self._repo.shared():
894 if self._repo.shared():
895 self.ui.status(_("unsharing subrepo '%s'\n") % self._relpath)
895 self.ui.status(_("unsharing subrepo '%s'\n") % self._relpath)
896
896
897 hg.unshare(self.ui, self._repo)
897 hg.unshare(self.ui, self._repo)
898
898
899 def verify(self):
899 def verify(self):
900 try:
900 try:
901 rev = self._state[1]
901 rev = self._state[1]
902 ctx = self._repo.unfiltered()[rev]
902 ctx = self._repo.unfiltered()[rev]
903 if ctx.hidden():
903 if ctx.hidden():
904 # Since hidden revisions aren't pushed/pulled, it seems worth an
904 # Since hidden revisions aren't pushed/pulled, it seems worth an
905 # explicit warning.
905 # explicit warning.
906 ui = self._repo.ui
906 ui = self._repo.ui
907 ui.warn(_("subrepo '%s' is hidden in revision %s\n") %
907 ui.warn(_("subrepo '%s' is hidden in revision %s\n") %
908 (self._relpath, node.short(self._ctx.node())))
908 (self._relpath, node.short(self._ctx.node())))
909 return 0
909 return 0
910 except error.RepoLookupError:
910 except error.RepoLookupError:
911 # A missing subrepo revision may be a case of needing to pull it, so
911 # A missing subrepo revision may be a case of needing to pull it, so
912 # don't treat this as an error.
912 # don't treat this as an error.
913 self._repo.ui.warn(_("subrepo '%s' not found in revision %s\n") %
913 self._repo.ui.warn(_("subrepo '%s' not found in revision %s\n") %
914 (self._relpath, node.short(self._ctx.node())))
914 (self._relpath, node.short(self._ctx.node())))
915 return 0
915 return 0
916
916
917 @propertycache
917 @propertycache
918 def wvfs(self):
918 def wvfs(self):
919 """return own wvfs for efficiency and consistency
919 """return own wvfs for efficiency and consistency
920 """
920 """
921 return self._repo.wvfs
921 return self._repo.wvfs
922
922
923 @propertycache
923 @propertycache
924 def _relpath(self):
924 def _relpath(self):
925 """return path to this subrepository as seen from outermost repository
925 """return path to this subrepository as seen from outermost repository
926 """
926 """
927 # Keep consistent dir separators by avoiding vfs.join(self._path)
927 # Keep consistent dir separators by avoiding vfs.join(self._path)
928 return reporelpath(self._repo)
928 return reporelpath(self._repo)
929
929
930 class svnsubrepo(abstractsubrepo):
930 class svnsubrepo(abstractsubrepo):
931 def __init__(self, ctx, path, state, allowcreate):
931 def __init__(self, ctx, path, state, allowcreate):
932 super(svnsubrepo, self).__init__(ctx, path)
932 super(svnsubrepo, self).__init__(ctx, path)
933 self._state = state
933 self._state = state
934 self._exe = procutil.findexe('svn')
934 self._exe = procutil.findexe('svn')
935 if not self._exe:
935 if not self._exe:
936 raise error.Abort(_("'svn' executable not found for subrepo '%s'")
936 raise error.Abort(_("'svn' executable not found for subrepo '%s'")
937 % self._path)
937 % self._path)
938
938
939 def _svncommand(self, commands, filename='', failok=False):
939 def _svncommand(self, commands, filename='', failok=False):
940 cmd = [self._exe]
940 cmd = [self._exe]
941 extrakw = {}
941 extrakw = {}
942 if not self.ui.interactive():
942 if not self.ui.interactive():
943 # Making stdin be a pipe should prevent svn from behaving
943 # Making stdin be a pipe should prevent svn from behaving
944 # interactively even if we can't pass --non-interactive.
944 # interactively even if we can't pass --non-interactive.
945 extrakw[r'stdin'] = subprocess.PIPE
945 extrakw[r'stdin'] = subprocess.PIPE
946 # Starting in svn 1.5 --non-interactive is a global flag
946 # Starting in svn 1.5 --non-interactive is a global flag
947 # instead of being per-command, but we need to support 1.4 so
947 # instead of being per-command, but we need to support 1.4 so
948 # we have to be intelligent about what commands take
948 # we have to be intelligent about what commands take
949 # --non-interactive.
949 # --non-interactive.
950 if commands[0] in ('update', 'checkout', 'commit'):
950 if commands[0] in ('update', 'checkout', 'commit'):
951 cmd.append('--non-interactive')
951 cmd.append('--non-interactive')
952 cmd.extend(commands)
952 cmd.extend(commands)
953 if filename is not None:
953 if filename is not None:
954 path = self.wvfs.reljoin(self._ctx.repo().origroot,
954 path = self.wvfs.reljoin(self._ctx.repo().origroot,
955 self._path, filename)
955 self._path, filename)
956 cmd.append(path)
956 cmd.append(path)
957 env = dict(encoding.environ)
957 env = dict(encoding.environ)
958 # Avoid localized output, preserve current locale for everything else.
958 # Avoid localized output, preserve current locale for everything else.
959 lc_all = env.get('LC_ALL')
959 lc_all = env.get('LC_ALL')
960 if lc_all:
960 if lc_all:
961 env['LANG'] = lc_all
961 env['LANG'] = lc_all
962 del env['LC_ALL']
962 del env['LC_ALL']
963 env['LC_MESSAGES'] = 'C'
963 env['LC_MESSAGES'] = 'C'
964 p = subprocess.Popen(pycompat.rapply(procutil.tonativestr, cmd),
964 p = subprocess.Popen(pycompat.rapply(procutil.tonativestr, cmd),
965 bufsize=-1, close_fds=procutil.closefds,
965 bufsize=-1, close_fds=procutil.closefds,
966 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
966 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
967 env=procutil.tonativeenv(env), **extrakw)
967 env=procutil.tonativeenv(env), **extrakw)
968 stdout, stderr = map(util.fromnativeeol, p.communicate())
968 stdout, stderr = map(util.fromnativeeol, p.communicate())
969 stderr = stderr.strip()
969 stderr = stderr.strip()
970 if not failok:
970 if not failok:
971 if p.returncode:
971 if p.returncode:
972 raise error.Abort(stderr or 'exited with code %d'
972 raise error.Abort(stderr or 'exited with code %d'
973 % p.returncode)
973 % p.returncode)
974 if stderr:
974 if stderr:
975 self.ui.warn(stderr + '\n')
975 self.ui.warn(stderr + '\n')
976 return stdout, stderr
976 return stdout, stderr
977
977
978 @propertycache
978 @propertycache
979 def _svnversion(self):
979 def _svnversion(self):
980 output, err = self._svncommand(['--version', '--quiet'], filename=None)
980 output, err = self._svncommand(['--version', '--quiet'], filename=None)
981 m = re.search(br'^(\d+)\.(\d+)', output)
981 m = re.search(br'^(\d+)\.(\d+)', output)
982 if not m:
982 if not m:
983 raise error.Abort(_('cannot retrieve svn tool version'))
983 raise error.Abort(_('cannot retrieve svn tool version'))
984 return (int(m.group(1)), int(m.group(2)))
984 return (int(m.group(1)), int(m.group(2)))
985
985
986 def _svnmissing(self):
986 def _svnmissing(self):
987 return not self.wvfs.exists('.svn')
987 return not self.wvfs.exists('.svn')
988
988
989 def _wcrevs(self):
989 def _wcrevs(self):
990 # Get the working directory revision as well as the last
990 # Get the working directory revision as well as the last
991 # commit revision so we can compare the subrepo state with
991 # commit revision so we can compare the subrepo state with
992 # both. We used to store the working directory one.
992 # both. We used to store the working directory one.
993 output, err = self._svncommand(['info', '--xml'])
993 output, err = self._svncommand(['info', '--xml'])
994 doc = xml.dom.minidom.parseString(output)
994 doc = xml.dom.minidom.parseString(output)
995 entries = doc.getElementsByTagName(r'entry')
995 entries = doc.getElementsByTagName(r'entry')
996 lastrev, rev = '0', '0'
996 lastrev, rev = '0', '0'
997 if entries:
997 if entries:
998 rev = pycompat.bytestr(entries[0].getAttribute(r'revision')) or '0'
998 rev = pycompat.bytestr(entries[0].getAttribute(r'revision')) or '0'
999 commits = entries[0].getElementsByTagName(r'commit')
999 commits = entries[0].getElementsByTagName(r'commit')
1000 if commits:
1000 if commits:
1001 lastrev = pycompat.bytestr(
1001 lastrev = pycompat.bytestr(
1002 commits[0].getAttribute(r'revision')) or '0'
1002 commits[0].getAttribute(r'revision')) or '0'
1003 return (lastrev, rev)
1003 return (lastrev, rev)
1004
1004
1005 def _wcrev(self):
1005 def _wcrev(self):
1006 return self._wcrevs()[0]
1006 return self._wcrevs()[0]
1007
1007
1008 def _wcchanged(self):
1008 def _wcchanged(self):
1009 """Return (changes, extchanges, missing) where changes is True
1009 """Return (changes, extchanges, missing) where changes is True
1010 if the working directory was changed, extchanges is
1010 if the working directory was changed, extchanges is
1011 True if any of these changes concern an external entry and missing
1011 True if any of these changes concern an external entry and missing
1012 is True if any change is a missing entry.
1012 is True if any change is a missing entry.
1013 """
1013 """
1014 output, err = self._svncommand(['status', '--xml'])
1014 output, err = self._svncommand(['status', '--xml'])
1015 externals, changes, missing = [], [], []
1015 externals, changes, missing = [], [], []
1016 doc = xml.dom.minidom.parseString(output)
1016 doc = xml.dom.minidom.parseString(output)
1017 for e in doc.getElementsByTagName(r'entry'):
1017 for e in doc.getElementsByTagName(r'entry'):
1018 s = e.getElementsByTagName(r'wc-status')
1018 s = e.getElementsByTagName(r'wc-status')
1019 if not s:
1019 if not s:
1020 continue
1020 continue
1021 item = s[0].getAttribute(r'item')
1021 item = s[0].getAttribute(r'item')
1022 props = s[0].getAttribute(r'props')
1022 props = s[0].getAttribute(r'props')
1023 path = e.getAttribute(r'path').encode('utf8')
1023 path = e.getAttribute(r'path').encode('utf8')
1024 if item == r'external':
1024 if item == r'external':
1025 externals.append(path)
1025 externals.append(path)
1026 elif item == r'missing':
1026 elif item == r'missing':
1027 missing.append(path)
1027 missing.append(path)
1028 if (item not in (r'', r'normal', r'unversioned', r'external')
1028 if (item not in (r'', r'normal', r'unversioned', r'external')
1029 or props not in (r'', r'none', r'normal')):
1029 or props not in (r'', r'none', r'normal')):
1030 changes.append(path)
1030 changes.append(path)
1031 for path in changes:
1031 for path in changes:
1032 for ext in externals:
1032 for ext in externals:
1033 if path == ext or path.startswith(ext + pycompat.ossep):
1033 if path == ext or path.startswith(ext + pycompat.ossep):
1034 return True, True, bool(missing)
1034 return True, True, bool(missing)
1035 return bool(changes), False, bool(missing)
1035 return bool(changes), False, bool(missing)
1036
1036
1037 @annotatesubrepoerror
1037 @annotatesubrepoerror
1038 def dirty(self, ignoreupdate=False, missing=False):
1038 def dirty(self, ignoreupdate=False, missing=False):
1039 if self._svnmissing():
1039 if self._svnmissing():
1040 return self._state[1] != ''
1040 return self._state[1] != ''
1041 wcchanged = self._wcchanged()
1041 wcchanged = self._wcchanged()
1042 changed = wcchanged[0] or (missing and wcchanged[2])
1042 changed = wcchanged[0] or (missing and wcchanged[2])
1043 if not changed:
1043 if not changed:
1044 if self._state[1] in self._wcrevs() or ignoreupdate:
1044 if self._state[1] in self._wcrevs() or ignoreupdate:
1045 return False
1045 return False
1046 return True
1046 return True
1047
1047
1048 def basestate(self):
1048 def basestate(self):
1049 lastrev, rev = self._wcrevs()
1049 lastrev, rev = self._wcrevs()
1050 if lastrev != rev:
1050 if lastrev != rev:
1051 # Last committed rev is not the same than rev. We would
1051 # Last committed rev is not the same than rev. We would
1052 # like to take lastrev but we do not know if the subrepo
1052 # like to take lastrev but we do not know if the subrepo
1053 # URL exists at lastrev. Test it and fallback to rev it
1053 # URL exists at lastrev. Test it and fallback to rev it
1054 # is not there.
1054 # is not there.
1055 try:
1055 try:
1056 self._svncommand(['list', '%s@%s' % (self._state[0], lastrev)])
1056 self._svncommand(['list', '%s@%s' % (self._state[0], lastrev)])
1057 return lastrev
1057 return lastrev
1058 except error.Abort:
1058 except error.Abort:
1059 pass
1059 pass
1060 return rev
1060 return rev
1061
1061
1062 @annotatesubrepoerror
1062 @annotatesubrepoerror
1063 def commit(self, text, user, date):
1063 def commit(self, text, user, date):
1064 # user and date are out of our hands since svn is centralized
1064 # user and date are out of our hands since svn is centralized
1065 changed, extchanged, missing = self._wcchanged()
1065 changed, extchanged, missing = self._wcchanged()
1066 if not changed:
1066 if not changed:
1067 return self.basestate()
1067 return self.basestate()
1068 if extchanged:
1068 if extchanged:
1069 # Do not try to commit externals
1069 # Do not try to commit externals
1070 raise error.Abort(_('cannot commit svn externals'))
1070 raise error.Abort(_('cannot commit svn externals'))
1071 if missing:
1071 if missing:
1072 # svn can commit with missing entries but aborting like hg
1072 # svn can commit with missing entries but aborting like hg
1073 # seems a better approach.
1073 # seems a better approach.
1074 raise error.Abort(_('cannot commit missing svn entries'))
1074 raise error.Abort(_('cannot commit missing svn entries'))
1075 commitinfo, err = self._svncommand(['commit', '-m', text])
1075 commitinfo, err = self._svncommand(['commit', '-m', text])
1076 self.ui.status(commitinfo)
1076 self.ui.status(commitinfo)
1077 newrev = re.search('Committed revision ([0-9]+).', commitinfo)
1077 newrev = re.search('Committed revision ([0-9]+).', commitinfo)
1078 if not newrev:
1078 if not newrev:
1079 if not commitinfo.strip():
1079 if not commitinfo.strip():
1080 # Sometimes, our definition of "changed" differs from
1080 # Sometimes, our definition of "changed" differs from
1081 # svn one. For instance, svn ignores missing files
1081 # svn one. For instance, svn ignores missing files
1082 # when committing. If there are only missing files, no
1082 # when committing. If there are only missing files, no
1083 # commit is made, no output and no error code.
1083 # commit is made, no output and no error code.
1084 raise error.Abort(_('failed to commit svn changes'))
1084 raise error.Abort(_('failed to commit svn changes'))
1085 raise error.Abort(commitinfo.splitlines()[-1])
1085 raise error.Abort(commitinfo.splitlines()[-1])
1086 newrev = newrev.groups()[0]
1086 newrev = newrev.groups()[0]
1087 self.ui.status(self._svncommand(['update', '-r', newrev])[0])
1087 self.ui.status(self._svncommand(['update', '-r', newrev])[0])
1088 return newrev
1088 return newrev
1089
1089
1090 @annotatesubrepoerror
1090 @annotatesubrepoerror
1091 def remove(self):
1091 def remove(self):
1092 if self.dirty():
1092 if self.dirty():
1093 self.ui.warn(_('not removing repo %s because '
1093 self.ui.warn(_('not removing repo %s because '
1094 'it has changes.\n') % self._path)
1094 'it has changes.\n') % self._path)
1095 return
1095 return
1096 self.ui.note(_('removing subrepo %s\n') % self._path)
1096 self.ui.note(_('removing subrepo %s\n') % self._path)
1097
1097
1098 self.wvfs.rmtree(forcibly=True)
1098 self.wvfs.rmtree(forcibly=True)
1099 try:
1099 try:
1100 pwvfs = self._ctx.repo().wvfs
1100 pwvfs = self._ctx.repo().wvfs
1101 pwvfs.removedirs(pwvfs.dirname(self._path))
1101 pwvfs.removedirs(pwvfs.dirname(self._path))
1102 except OSError:
1102 except OSError:
1103 pass
1103 pass
1104
1104
1105 @annotatesubrepoerror
1105 @annotatesubrepoerror
1106 def get(self, state, overwrite=False):
1106 def get(self, state, overwrite=False):
1107 if overwrite:
1107 if overwrite:
1108 self._svncommand(['revert', '--recursive'])
1108 self._svncommand(['revert', '--recursive'])
1109 args = ['checkout']
1109 args = ['checkout']
1110 if self._svnversion >= (1, 5):
1110 if self._svnversion >= (1, 5):
1111 args.append('--force')
1111 args.append('--force')
1112 # The revision must be specified at the end of the URL to properly
1112 # The revision must be specified at the end of the URL to properly
1113 # update to a directory which has since been deleted and recreated.
1113 # update to a directory which has since been deleted and recreated.
1114 args.append('%s@%s' % (state[0], state[1]))
1114 args.append('%s@%s' % (state[0], state[1]))
1115
1115
1116 # SEC: check that the ssh url is safe
1116 # SEC: check that the ssh url is safe
1117 util.checksafessh(state[0])
1117 util.checksafessh(state[0])
1118
1118
1119 status, err = self._svncommand(args, failok=True)
1119 status, err = self._svncommand(args, failok=True)
1120 _sanitize(self.ui, self.wvfs, '.svn')
1120 _sanitize(self.ui, self.wvfs, '.svn')
1121 if not re.search('Checked out revision [0-9]+.', status):
1121 if not re.search('Checked out revision [0-9]+.', status):
1122 if ('is already a working copy for a different URL' in err
1122 if ('is already a working copy for a different URL' in err
1123 and (self._wcchanged()[:2] == (False, False))):
1123 and (self._wcchanged()[:2] == (False, False))):
1124 # obstructed but clean working copy, so just blow it away.
1124 # obstructed but clean working copy, so just blow it away.
1125 self.remove()
1125 self.remove()
1126 self.get(state, overwrite=False)
1126 self.get(state, overwrite=False)
1127 return
1127 return
1128 raise error.Abort((status or err).splitlines()[-1])
1128 raise error.Abort((status or err).splitlines()[-1])
1129 self.ui.status(status)
1129 self.ui.status(status)
1130
1130
1131 @annotatesubrepoerror
1131 @annotatesubrepoerror
1132 def merge(self, state):
1132 def merge(self, state):
1133 old = self._state[1]
1133 old = self._state[1]
1134 new = state[1]
1134 new = state[1]
1135 wcrev = self._wcrev()
1135 wcrev = self._wcrev()
1136 if new != wcrev:
1136 if new != wcrev:
1137 dirty = old == wcrev or self._wcchanged()[0]
1137 dirty = old == wcrev or self._wcchanged()[0]
1138 if _updateprompt(self.ui, self, dirty, wcrev, new):
1138 if _updateprompt(self.ui, self, dirty, wcrev, new):
1139 self.get(state, False)
1139 self.get(state, False)
1140
1140
1141 def push(self, opts):
1141 def push(self, opts):
1142 # push is a no-op for SVN
1142 # push is a no-op for SVN
1143 return True
1143 return True
1144
1144
1145 @annotatesubrepoerror
1145 @annotatesubrepoerror
1146 def files(self):
1146 def files(self):
1147 output = self._svncommand(['list', '--recursive', '--xml'])[0]
1147 output = self._svncommand(['list', '--recursive', '--xml'])[0]
1148 doc = xml.dom.minidom.parseString(output)
1148 doc = xml.dom.minidom.parseString(output)
1149 paths = []
1149 paths = []
1150 for e in doc.getElementsByTagName(r'entry'):
1150 for e in doc.getElementsByTagName(r'entry'):
1151 kind = pycompat.bytestr(e.getAttribute(r'kind'))
1151 kind = pycompat.bytestr(e.getAttribute(r'kind'))
1152 if kind != 'file':
1152 if kind != 'file':
1153 continue
1153 continue
1154 name = r''.join(c.data for c
1154 name = r''.join(c.data for c
1155 in e.getElementsByTagName(r'name')[0].childNodes
1155 in e.getElementsByTagName(r'name')[0].childNodes
1156 if c.nodeType == c.TEXT_NODE)
1156 if c.nodeType == c.TEXT_NODE)
1157 paths.append(name.encode('utf8'))
1157 paths.append(name.encode('utf8'))
1158 return paths
1158 return paths
1159
1159
1160 def filedata(self, name, decode):
1160 def filedata(self, name, decode):
1161 return self._svncommand(['cat'], name)[0]
1161 return self._svncommand(['cat'], name)[0]
1162
1162
1163
1163
1164 class gitsubrepo(abstractsubrepo):
1164 class gitsubrepo(abstractsubrepo):
1165 def __init__(self, ctx, path, state, allowcreate):
1165 def __init__(self, ctx, path, state, allowcreate):
1166 super(gitsubrepo, self).__init__(ctx, path)
1166 super(gitsubrepo, self).__init__(ctx, path)
1167 self._state = state
1167 self._state = state
1168 self._abspath = ctx.repo().wjoin(path)
1168 self._abspath = ctx.repo().wjoin(path)
1169 self._subparent = ctx.repo()
1169 self._subparent = ctx.repo()
1170 self._ensuregit()
1170 self._ensuregit()
1171
1171
1172 def _ensuregit(self):
1172 def _ensuregit(self):
1173 try:
1173 try:
1174 self._gitexecutable = 'git'
1174 self._gitexecutable = 'git'
1175 out, err = self._gitnodir(['--version'])
1175 out, err = self._gitnodir(['--version'])
1176 except OSError as e:
1176 except OSError as e:
1177 genericerror = _("error executing git for subrepo '%s': %s")
1177 genericerror = _("error executing git for subrepo '%s': %s")
1178 notfoundhint = _("check git is installed and in your PATH")
1178 notfoundhint = _("check git is installed and in your PATH")
1179 if e.errno != errno.ENOENT:
1179 if e.errno != errno.ENOENT:
1180 raise error.Abort(genericerror % (
1180 raise error.Abort(genericerror % (
1181 self._path, encoding.strtolocal(e.strerror)))
1181 self._path, encoding.strtolocal(e.strerror)))
1182 elif pycompat.iswindows:
1182 elif pycompat.iswindows:
1183 try:
1183 try:
1184 self._gitexecutable = 'git.cmd'
1184 self._gitexecutable = 'git.cmd'
1185 out, err = self._gitnodir(['--version'])
1185 out, err = self._gitnodir(['--version'])
1186 except OSError as e2:
1186 except OSError as e2:
1187 if e2.errno == errno.ENOENT:
1187 if e2.errno == errno.ENOENT:
1188 raise error.Abort(_("couldn't find 'git' or 'git.cmd'"
1188 raise error.Abort(_("couldn't find 'git' or 'git.cmd'"
1189 " for subrepo '%s'") % self._path,
1189 " for subrepo '%s'") % self._path,
1190 hint=notfoundhint)
1190 hint=notfoundhint)
1191 else:
1191 else:
1192 raise error.Abort(genericerror % (self._path,
1192 raise error.Abort(genericerror % (self._path,
1193 encoding.strtolocal(e2.strerror)))
1193 encoding.strtolocal(e2.strerror)))
1194 else:
1194 else:
1195 raise error.Abort(_("couldn't find git for subrepo '%s'")
1195 raise error.Abort(_("couldn't find git for subrepo '%s'")
1196 % self._path, hint=notfoundhint)
1196 % self._path, hint=notfoundhint)
1197 versionstatus = self._checkversion(out)
1197 versionstatus = self._checkversion(out)
1198 if versionstatus == 'unknown':
1198 if versionstatus == 'unknown':
1199 self.ui.warn(_('cannot retrieve git version\n'))
1199 self.ui.warn(_('cannot retrieve git version\n'))
1200 elif versionstatus == 'abort':
1200 elif versionstatus == 'abort':
1201 raise error.Abort(_('git subrepo requires at least 1.6.0 or later'))
1201 raise error.Abort(_('git subrepo requires at least 1.6.0 or later'))
1202 elif versionstatus == 'warning':
1202 elif versionstatus == 'warning':
1203 self.ui.warn(_('git subrepo requires at least 1.6.0 or later\n'))
1203 self.ui.warn(_('git subrepo requires at least 1.6.0 or later\n'))
1204
1204
1205 @staticmethod
1205 @staticmethod
1206 def _gitversion(out):
1206 def _gitversion(out):
1207 m = re.search(br'^git version (\d+)\.(\d+)\.(\d+)', out)
1207 m = re.search(br'^git version (\d+)\.(\d+)\.(\d+)', out)
1208 if m:
1208 if m:
1209 return (int(m.group(1)), int(m.group(2)), int(m.group(3)))
1209 return (int(m.group(1)), int(m.group(2)), int(m.group(3)))
1210
1210
1211 m = re.search(br'^git version (\d+)\.(\d+)', out)
1211 m = re.search(br'^git version (\d+)\.(\d+)', out)
1212 if m:
1212 if m:
1213 return (int(m.group(1)), int(m.group(2)), 0)
1213 return (int(m.group(1)), int(m.group(2)), 0)
1214
1214
1215 return -1
1215 return -1
1216
1216
1217 @staticmethod
1217 @staticmethod
1218 def _checkversion(out):
1218 def _checkversion(out):
1219 '''ensure git version is new enough
1219 '''ensure git version is new enough
1220
1220
1221 >>> _checkversion = gitsubrepo._checkversion
1221 >>> _checkversion = gitsubrepo._checkversion
1222 >>> _checkversion(b'git version 1.6.0')
1222 >>> _checkversion(b'git version 1.6.0')
1223 'ok'
1223 'ok'
1224 >>> _checkversion(b'git version 1.8.5')
1224 >>> _checkversion(b'git version 1.8.5')
1225 'ok'
1225 'ok'
1226 >>> _checkversion(b'git version 1.4.0')
1226 >>> _checkversion(b'git version 1.4.0')
1227 'abort'
1227 'abort'
1228 >>> _checkversion(b'git version 1.5.0')
1228 >>> _checkversion(b'git version 1.5.0')
1229 'warning'
1229 'warning'
1230 >>> _checkversion(b'git version 1.9-rc0')
1230 >>> _checkversion(b'git version 1.9-rc0')
1231 'ok'
1231 'ok'
1232 >>> _checkversion(b'git version 1.9.0.265.g81cdec2')
1232 >>> _checkversion(b'git version 1.9.0.265.g81cdec2')
1233 'ok'
1233 'ok'
1234 >>> _checkversion(b'git version 1.9.0.GIT')
1234 >>> _checkversion(b'git version 1.9.0.GIT')
1235 'ok'
1235 'ok'
1236 >>> _checkversion(b'git version 12345')
1236 >>> _checkversion(b'git version 12345')
1237 'unknown'
1237 'unknown'
1238 >>> _checkversion(b'no')
1238 >>> _checkversion(b'no')
1239 'unknown'
1239 'unknown'
1240 '''
1240 '''
1241 version = gitsubrepo._gitversion(out)
1241 version = gitsubrepo._gitversion(out)
1242 # git 1.4.0 can't work at all, but 1.5.X can in at least some cases,
1242 # git 1.4.0 can't work at all, but 1.5.X can in at least some cases,
1243 # despite the docstring comment. For now, error on 1.4.0, warn on
1243 # despite the docstring comment. For now, error on 1.4.0, warn on
1244 # 1.5.0 but attempt to continue.
1244 # 1.5.0 but attempt to continue.
1245 if version == -1:
1245 if version == -1:
1246 return 'unknown'
1246 return 'unknown'
1247 if version < (1, 5, 0):
1247 if version < (1, 5, 0):
1248 return 'abort'
1248 return 'abort'
1249 elif version < (1, 6, 0):
1249 elif version < (1, 6, 0):
1250 return 'warning'
1250 return 'warning'
1251 return 'ok'
1251 return 'ok'
1252
1252
1253 def _gitcommand(self, commands, env=None, stream=False):
1253 def _gitcommand(self, commands, env=None, stream=False):
1254 return self._gitdir(commands, env=env, stream=stream)[0]
1254 return self._gitdir(commands, env=env, stream=stream)[0]
1255
1255
1256 def _gitdir(self, commands, env=None, stream=False):
1256 def _gitdir(self, commands, env=None, stream=False):
1257 return self._gitnodir(commands, env=env, stream=stream,
1257 return self._gitnodir(commands, env=env, stream=stream,
1258 cwd=self._abspath)
1258 cwd=self._abspath)
1259
1259
1260 def _gitnodir(self, commands, env=None, stream=False, cwd=None):
1260 def _gitnodir(self, commands, env=None, stream=False, cwd=None):
1261 """Calls the git command
1261 """Calls the git command
1262
1262
1263 The methods tries to call the git command. versions prior to 1.6.0
1263 The methods tries to call the git command. versions prior to 1.6.0
1264 are not supported and very probably fail.
1264 are not supported and very probably fail.
1265 """
1265 """
1266 self.ui.debug('%s: git %s\n' % (self._relpath, ' '.join(commands)))
1266 self.ui.debug('%s: git %s\n' % (self._relpath, ' '.join(commands)))
1267 if env is None:
1267 if env is None:
1268 env = encoding.environ.copy()
1268 env = encoding.environ.copy()
1269 # disable localization for Git output (issue5176)
1269 # disable localization for Git output (issue5176)
1270 env['LC_ALL'] = 'C'
1270 env['LC_ALL'] = 'C'
1271 # fix for Git CVE-2015-7545
1271 # fix for Git CVE-2015-7545
1272 if 'GIT_ALLOW_PROTOCOL' not in env:
1272 if 'GIT_ALLOW_PROTOCOL' not in env:
1273 env['GIT_ALLOW_PROTOCOL'] = 'file:git:http:https:ssh'
1273 env['GIT_ALLOW_PROTOCOL'] = 'file:git:http:https:ssh'
1274 # unless ui.quiet is set, print git's stderr,
1274 # unless ui.quiet is set, print git's stderr,
1275 # which is mostly progress and useful info
1275 # which is mostly progress and useful info
1276 errpipe = None
1276 errpipe = None
1277 if self.ui.quiet:
1277 if self.ui.quiet:
1278 errpipe = open(os.devnull, 'w')
1278 errpipe = open(os.devnull, 'w')
1279 if self.ui._colormode and len(commands) and commands[0] == "diff":
1279 if self.ui._colormode and len(commands) and commands[0] == "diff":
1280 # insert the argument in the front,
1280 # insert the argument in the front,
1281 # the end of git diff arguments is used for paths
1281 # the end of git diff arguments is used for paths
1282 commands.insert(1, '--color')
1282 commands.insert(1, '--color')
1283 p = subprocess.Popen(pycompat.rapply(procutil.tonativestr,
1283 p = subprocess.Popen(pycompat.rapply(procutil.tonativestr,
1284 [self._gitexecutable] + commands),
1284 [self._gitexecutable] + commands),
1285 bufsize=-1,
1285 bufsize=-1,
1286 cwd=pycompat.rapply(procutil.tonativestr, cwd),
1286 cwd=pycompat.rapply(procutil.tonativestr, cwd),
1287 env=procutil.tonativeenv(env),
1287 env=procutil.tonativeenv(env),
1288 close_fds=procutil.closefds,
1288 close_fds=procutil.closefds,
1289 stdout=subprocess.PIPE, stderr=errpipe)
1289 stdout=subprocess.PIPE, stderr=errpipe)
1290 if stream:
1290 if stream:
1291 return p.stdout, None
1291 return p.stdout, None
1292
1292
1293 retdata = p.stdout.read().strip()
1293 retdata = p.stdout.read().strip()
1294 # wait for the child to exit to avoid race condition.
1294 # wait for the child to exit to avoid race condition.
1295 p.wait()
1295 p.wait()
1296
1296
1297 if p.returncode != 0 and p.returncode != 1:
1297 if p.returncode != 0 and p.returncode != 1:
1298 # there are certain error codes that are ok
1298 # there are certain error codes that are ok
1299 command = commands[0]
1299 command = commands[0]
1300 if command in ('cat-file', 'symbolic-ref'):
1300 if command in ('cat-file', 'symbolic-ref'):
1301 return retdata, p.returncode
1301 return retdata, p.returncode
1302 # for all others, abort
1302 # for all others, abort
1303 raise error.Abort(_('git %s error %d in %s') %
1303 raise error.Abort(_('git %s error %d in %s') %
1304 (command, p.returncode, self._relpath))
1304 (command, p.returncode, self._relpath))
1305
1305
1306 return retdata, p.returncode
1306 return retdata, p.returncode
1307
1307
1308 def _gitmissing(self):
1308 def _gitmissing(self):
1309 return not self.wvfs.exists('.git')
1309 return not self.wvfs.exists('.git')
1310
1310
1311 def _gitstate(self):
1311 def _gitstate(self):
1312 return self._gitcommand(['rev-parse', 'HEAD'])
1312 return self._gitcommand(['rev-parse', 'HEAD'])
1313
1313
1314 def _gitcurrentbranch(self):
1314 def _gitcurrentbranch(self):
1315 current, err = self._gitdir(['symbolic-ref', 'HEAD', '--quiet'])
1315 current, err = self._gitdir(['symbolic-ref', 'HEAD', '--quiet'])
1316 if err:
1316 if err:
1317 current = None
1317 current = None
1318 return current
1318 return current
1319
1319
1320 def _gitremote(self, remote):
1320 def _gitremote(self, remote):
1321 out = self._gitcommand(['remote', 'show', '-n', remote])
1321 out = self._gitcommand(['remote', 'show', '-n', remote])
1322 line = out.split('\n')[1]
1322 line = out.split('\n')[1]
1323 i = line.index('URL: ') + len('URL: ')
1323 i = line.index('URL: ') + len('URL: ')
1324 return line[i:]
1324 return line[i:]
1325
1325
1326 def _githavelocally(self, revision):
1326 def _githavelocally(self, revision):
1327 out, code = self._gitdir(['cat-file', '-e', revision])
1327 out, code = self._gitdir(['cat-file', '-e', revision])
1328 return code == 0
1328 return code == 0
1329
1329
1330 def _gitisancestor(self, r1, r2):
1330 def _gitisancestor(self, r1, r2):
1331 base = self._gitcommand(['merge-base', r1, r2])
1331 base = self._gitcommand(['merge-base', r1, r2])
1332 return base == r1
1332 return base == r1
1333
1333
1334 def _gitisbare(self):
1334 def _gitisbare(self):
1335 return self._gitcommand(['config', '--bool', 'core.bare']) == 'true'
1335 return self._gitcommand(['config', '--bool', 'core.bare']) == 'true'
1336
1336
1337 def _gitupdatestat(self):
1337 def _gitupdatestat(self):
1338 """This must be run before git diff-index.
1338 """This must be run before git diff-index.
1339 diff-index only looks at changes to file stat;
1339 diff-index only looks at changes to file stat;
1340 this command looks at file contents and updates the stat."""
1340 this command looks at file contents and updates the stat."""
1341 self._gitcommand(['update-index', '-q', '--refresh'])
1341 self._gitcommand(['update-index', '-q', '--refresh'])
1342
1342
1343 def _gitbranchmap(self):
1343 def _gitbranchmap(self):
1344 '''returns 2 things:
1344 '''returns 2 things:
1345 a map from git branch to revision
1345 a map from git branch to revision
1346 a map from revision to branches'''
1346 a map from revision to branches'''
1347 branch2rev = {}
1347 branch2rev = {}
1348 rev2branch = {}
1348 rev2branch = {}
1349
1349
1350 out = self._gitcommand(['for-each-ref', '--format',
1350 out = self._gitcommand(['for-each-ref', '--format',
1351 '%(objectname) %(refname)'])
1351 '%(objectname) %(refname)'])
1352 for line in out.split('\n'):
1352 for line in out.split('\n'):
1353 revision, ref = line.split(' ')
1353 revision, ref = line.split(' ')
1354 if (not ref.startswith('refs/heads/') and
1354 if (not ref.startswith('refs/heads/') and
1355 not ref.startswith('refs/remotes/')):
1355 not ref.startswith('refs/remotes/')):
1356 continue
1356 continue
1357 if ref.startswith('refs/remotes/') and ref.endswith('/HEAD'):
1357 if ref.startswith('refs/remotes/') and ref.endswith('/HEAD'):
1358 continue # ignore remote/HEAD redirects
1358 continue # ignore remote/HEAD redirects
1359 branch2rev[ref] = revision
1359 branch2rev[ref] = revision
1360 rev2branch.setdefault(revision, []).append(ref)
1360 rev2branch.setdefault(revision, []).append(ref)
1361 return branch2rev, rev2branch
1361 return branch2rev, rev2branch
1362
1362
1363 def _gittracking(self, branches):
1363 def _gittracking(self, branches):
1364 'return map of remote branch to local tracking branch'
1364 'return map of remote branch to local tracking branch'
1365 # assumes no more than one local tracking branch for each remote
1365 # assumes no more than one local tracking branch for each remote
1366 tracking = {}
1366 tracking = {}
1367 for b in branches:
1367 for b in branches:
1368 if b.startswith('refs/remotes/'):
1368 if b.startswith('refs/remotes/'):
1369 continue
1369 continue
1370 bname = b.split('/', 2)[2]
1370 bname = b.split('/', 2)[2]
1371 remote = self._gitcommand(['config', 'branch.%s.remote' % bname])
1371 remote = self._gitcommand(['config', 'branch.%s.remote' % bname])
1372 if remote:
1372 if remote:
1373 ref = self._gitcommand(['config', 'branch.%s.merge' % bname])
1373 ref = self._gitcommand(['config', 'branch.%s.merge' % bname])
1374 tracking['refs/remotes/%s/%s' %
1374 tracking['refs/remotes/%s/%s' %
1375 (remote, ref.split('/', 2)[2])] = b
1375 (remote, ref.split('/', 2)[2])] = b
1376 return tracking
1376 return tracking
1377
1377
1378 def _abssource(self, source):
1378 def _abssource(self, source):
1379 if '://' not in source:
1379 if '://' not in source:
1380 # recognize the scp syntax as an absolute source
1380 # recognize the scp syntax as an absolute source
1381 colon = source.find(':')
1381 colon = source.find(':')
1382 if colon != -1 and '/' not in source[:colon]:
1382 if colon != -1 and '/' not in source[:colon]:
1383 return source
1383 return source
1384 self._subsource = source
1384 self._subsource = source
1385 return _abssource(self)
1385 return _abssource(self)
1386
1386
1387 def _fetch(self, source, revision):
1387 def _fetch(self, source, revision):
1388 if self._gitmissing():
1388 if self._gitmissing():
1389 # SEC: check for safe ssh url
1389 # SEC: check for safe ssh url
1390 util.checksafessh(source)
1390 util.checksafessh(source)
1391
1391
1392 source = self._abssource(source)
1392 source = self._abssource(source)
1393 self.ui.status(_('cloning subrepo %s from %s\n') %
1393 self.ui.status(_('cloning subrepo %s from %s\n') %
1394 (self._relpath, source))
1394 (self._relpath, source))
1395 self._gitnodir(['clone', source, self._abspath])
1395 self._gitnodir(['clone', source, self._abspath])
1396 if self._githavelocally(revision):
1396 if self._githavelocally(revision):
1397 return
1397 return
1398 self.ui.status(_('pulling subrepo %s from %s\n') %
1398 self.ui.status(_('pulling subrepo %s from %s\n') %
1399 (self._relpath, self._gitremote('origin')))
1399 (self._relpath, self._gitremote('origin')))
1400 # try only origin: the originally cloned repo
1400 # try only origin: the originally cloned repo
1401 self._gitcommand(['fetch'])
1401 self._gitcommand(['fetch'])
1402 if not self._githavelocally(revision):
1402 if not self._githavelocally(revision):
1403 raise error.Abort(_('revision %s does not exist in subrepository '
1403 raise error.Abort(_('revision %s does not exist in subrepository '
1404 '"%s"\n') % (revision, self._relpath))
1404 '"%s"\n') % (revision, self._relpath))
1405
1405
1406 @annotatesubrepoerror
1406 @annotatesubrepoerror
1407 def dirty(self, ignoreupdate=False, missing=False):
1407 def dirty(self, ignoreupdate=False, missing=False):
1408 if self._gitmissing():
1408 if self._gitmissing():
1409 return self._state[1] != ''
1409 return self._state[1] != ''
1410 if self._gitisbare():
1410 if self._gitisbare():
1411 return True
1411 return True
1412 if not ignoreupdate and self._state[1] != self._gitstate():
1412 if not ignoreupdate and self._state[1] != self._gitstate():
1413 # different version checked out
1413 # different version checked out
1414 return True
1414 return True
1415 # check for staged changes or modified files; ignore untracked files
1415 # check for staged changes or modified files; ignore untracked files
1416 self._gitupdatestat()
1416 self._gitupdatestat()
1417 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
1417 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
1418 return code == 1
1418 return code == 1
1419
1419
1420 def basestate(self):
1420 def basestate(self):
1421 return self._gitstate()
1421 return self._gitstate()
1422
1422
1423 @annotatesubrepoerror
1423 @annotatesubrepoerror
1424 def get(self, state, overwrite=False):
1424 def get(self, state, overwrite=False):
1425 source, revision, kind = state
1425 source, revision, kind = state
1426 if not revision:
1426 if not revision:
1427 self.remove()
1427 self.remove()
1428 return
1428 return
1429 self._fetch(source, revision)
1429 self._fetch(source, revision)
1430 # if the repo was set to be bare, unbare it
1430 # if the repo was set to be bare, unbare it
1431 if self._gitisbare():
1431 if self._gitisbare():
1432 self._gitcommand(['config', 'core.bare', 'false'])
1432 self._gitcommand(['config', 'core.bare', 'false'])
1433 if self._gitstate() == revision:
1433 if self._gitstate() == revision:
1434 self._gitcommand(['reset', '--hard', 'HEAD'])
1434 self._gitcommand(['reset', '--hard', 'HEAD'])
1435 return
1435 return
1436 elif self._gitstate() == revision:
1436 elif self._gitstate() == revision:
1437 if overwrite:
1437 if overwrite:
1438 # first reset the index to unmark new files for commit, because
1438 # first reset the index to unmark new files for commit, because
1439 # reset --hard will otherwise throw away files added for commit,
1439 # reset --hard will otherwise throw away files added for commit,
1440 # not just unmark them.
1440 # not just unmark them.
1441 self._gitcommand(['reset', 'HEAD'])
1441 self._gitcommand(['reset', 'HEAD'])
1442 self._gitcommand(['reset', '--hard', 'HEAD'])
1442 self._gitcommand(['reset', '--hard', 'HEAD'])
1443 return
1443 return
1444 branch2rev, rev2branch = self._gitbranchmap()
1444 branch2rev, rev2branch = self._gitbranchmap()
1445
1445
1446 def checkout(args):
1446 def checkout(args):
1447 cmd = ['checkout']
1447 cmd = ['checkout']
1448 if overwrite:
1448 if overwrite:
1449 # first reset the index to unmark new files for commit, because
1449 # first reset the index to unmark new files for commit, because
1450 # the -f option will otherwise throw away files added for
1450 # the -f option will otherwise throw away files added for
1451 # commit, not just unmark them.
1451 # commit, not just unmark them.
1452 self._gitcommand(['reset', 'HEAD'])
1452 self._gitcommand(['reset', 'HEAD'])
1453 cmd.append('-f')
1453 cmd.append('-f')
1454 self._gitcommand(cmd + args)
1454 self._gitcommand(cmd + args)
1455 _sanitize(self.ui, self.wvfs, '.git')
1455 _sanitize(self.ui, self.wvfs, '.git')
1456
1456
1457 def rawcheckout():
1457 def rawcheckout():
1458 # no branch to checkout, check it out with no branch
1458 # no branch to checkout, check it out with no branch
1459 self.ui.warn(_('checking out detached HEAD in '
1459 self.ui.warn(_('checking out detached HEAD in '
1460 'subrepository "%s"\n') % self._relpath)
1460 'subrepository "%s"\n') % self._relpath)
1461 self.ui.warn(_('check out a git branch if you intend '
1461 self.ui.warn(_('check out a git branch if you intend '
1462 'to make changes\n'))
1462 'to make changes\n'))
1463 checkout(['-q', revision])
1463 checkout(['-q', revision])
1464
1464
1465 if revision not in rev2branch:
1465 if revision not in rev2branch:
1466 rawcheckout()
1466 rawcheckout()
1467 return
1467 return
1468 branches = rev2branch[revision]
1468 branches = rev2branch[revision]
1469 firstlocalbranch = None
1469 firstlocalbranch = None
1470 for b in branches:
1470 for b in branches:
1471 if b == 'refs/heads/master':
1471 if b == 'refs/heads/master':
1472 # master trumps all other branches
1472 # master trumps all other branches
1473 checkout(['refs/heads/master'])
1473 checkout(['refs/heads/master'])
1474 return
1474 return
1475 if not firstlocalbranch and not b.startswith('refs/remotes/'):
1475 if not firstlocalbranch and not b.startswith('refs/remotes/'):
1476 firstlocalbranch = b
1476 firstlocalbranch = b
1477 if firstlocalbranch:
1477 if firstlocalbranch:
1478 checkout([firstlocalbranch])
1478 checkout([firstlocalbranch])
1479 return
1479 return
1480
1480
1481 tracking = self._gittracking(branch2rev.keys())
1481 tracking = self._gittracking(branch2rev.keys())
1482 # choose a remote branch already tracked if possible
1482 # choose a remote branch already tracked if possible
1483 remote = branches[0]
1483 remote = branches[0]
1484 if remote not in tracking:
1484 if remote not in tracking:
1485 for b in branches:
1485 for b in branches:
1486 if b in tracking:
1486 if b in tracking:
1487 remote = b
1487 remote = b
1488 break
1488 break
1489
1489
1490 if remote not in tracking:
1490 if remote not in tracking:
1491 # create a new local tracking branch
1491 # create a new local tracking branch
1492 local = remote.split('/', 3)[3]
1492 local = remote.split('/', 3)[3]
1493 checkout(['-b', local, remote])
1493 checkout(['-b', local, remote])
1494 elif self._gitisancestor(branch2rev[tracking[remote]], remote):
1494 elif self._gitisancestor(branch2rev[tracking[remote]], remote):
1495 # When updating to a tracked remote branch,
1495 # When updating to a tracked remote branch,
1496 # if the local tracking branch is downstream of it,
1496 # if the local tracking branch is downstream of it,
1497 # a normal `git pull` would have performed a "fast-forward merge"
1497 # a normal `git pull` would have performed a "fast-forward merge"
1498 # which is equivalent to updating the local branch to the remote.
1498 # which is equivalent to updating the local branch to the remote.
1499 # Since we are only looking at branching at update, we need to
1499 # Since we are only looking at branching at update, we need to
1500 # detect this situation and perform this action lazily.
1500 # detect this situation and perform this action lazily.
1501 if tracking[remote] != self._gitcurrentbranch():
1501 if tracking[remote] != self._gitcurrentbranch():
1502 checkout([tracking[remote]])
1502 checkout([tracking[remote]])
1503 self._gitcommand(['merge', '--ff', remote])
1503 self._gitcommand(['merge', '--ff', remote])
1504 _sanitize(self.ui, self.wvfs, '.git')
1504 _sanitize(self.ui, self.wvfs, '.git')
1505 else:
1505 else:
1506 # a real merge would be required, just checkout the revision
1506 # a real merge would be required, just checkout the revision
1507 rawcheckout()
1507 rawcheckout()
1508
1508
1509 @annotatesubrepoerror
1509 @annotatesubrepoerror
1510 def commit(self, text, user, date):
1510 def commit(self, text, user, date):
1511 if self._gitmissing():
1511 if self._gitmissing():
1512 raise error.Abort(_("subrepo %s is missing") % self._relpath)
1512 raise error.Abort(_("subrepo %s is missing") % self._relpath)
1513 cmd = ['commit', '-a', '-m', text]
1513 cmd = ['commit', '-a', '-m', text]
1514 env = encoding.environ.copy()
1514 env = encoding.environ.copy()
1515 if user:
1515 if user:
1516 cmd += ['--author', user]
1516 cmd += ['--author', user]
1517 if date:
1517 if date:
1518 # git's date parser silently ignores when seconds < 1e9
1518 # git's date parser silently ignores when seconds < 1e9
1519 # convert to ISO8601
1519 # convert to ISO8601
1520 env['GIT_AUTHOR_DATE'] = dateutil.datestr(date,
1520 env['GIT_AUTHOR_DATE'] = dateutil.datestr(date,
1521 '%Y-%m-%dT%H:%M:%S %1%2')
1521 '%Y-%m-%dT%H:%M:%S %1%2')
1522 self._gitcommand(cmd, env=env)
1522 self._gitcommand(cmd, env=env)
1523 # make sure commit works otherwise HEAD might not exist under certain
1523 # make sure commit works otherwise HEAD might not exist under certain
1524 # circumstances
1524 # circumstances
1525 return self._gitstate()
1525 return self._gitstate()
1526
1526
1527 @annotatesubrepoerror
1527 @annotatesubrepoerror
1528 def merge(self, state):
1528 def merge(self, state):
1529 source, revision, kind = state
1529 source, revision, kind = state
1530 self._fetch(source, revision)
1530 self._fetch(source, revision)
1531 base = self._gitcommand(['merge-base', revision, self._state[1]])
1531 base = self._gitcommand(['merge-base', revision, self._state[1]])
1532 self._gitupdatestat()
1532 self._gitupdatestat()
1533 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
1533 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
1534
1534
1535 def mergefunc():
1535 def mergefunc():
1536 if base == revision:
1536 if base == revision:
1537 self.get(state) # fast forward merge
1537 self.get(state) # fast forward merge
1538 elif base != self._state[1]:
1538 elif base != self._state[1]:
1539 self._gitcommand(['merge', '--no-commit', revision])
1539 self._gitcommand(['merge', '--no-commit', revision])
1540 _sanitize(self.ui, self.wvfs, '.git')
1540 _sanitize(self.ui, self.wvfs, '.git')
1541
1541
1542 if self.dirty():
1542 if self.dirty():
1543 if self._gitstate() != revision:
1543 if self._gitstate() != revision:
1544 dirty = self._gitstate() == self._state[1] or code != 0
1544 dirty = self._gitstate() == self._state[1] or code != 0
1545 if _updateprompt(self.ui, self, dirty,
1545 if _updateprompt(self.ui, self, dirty,
1546 self._state[1][:7], revision[:7]):
1546 self._state[1][:7], revision[:7]):
1547 mergefunc()
1547 mergefunc()
1548 else:
1548 else:
1549 mergefunc()
1549 mergefunc()
1550
1550
1551 @annotatesubrepoerror
1551 @annotatesubrepoerror
1552 def push(self, opts):
1552 def push(self, opts):
1553 force = opts.get('force')
1553 force = opts.get('force')
1554
1554
1555 if not self._state[1]:
1555 if not self._state[1]:
1556 return True
1556 return True
1557 if self._gitmissing():
1557 if self._gitmissing():
1558 raise error.Abort(_("subrepo %s is missing") % self._relpath)
1558 raise error.Abort(_("subrepo %s is missing") % self._relpath)
1559 # if a branch in origin contains the revision, nothing to do
1559 # if a branch in origin contains the revision, nothing to do
1560 branch2rev, rev2branch = self._gitbranchmap()
1560 branch2rev, rev2branch = self._gitbranchmap()
1561 if self._state[1] in rev2branch:
1561 if self._state[1] in rev2branch:
1562 for b in rev2branch[self._state[1]]:
1562 for b in rev2branch[self._state[1]]:
1563 if b.startswith('refs/remotes/origin/'):
1563 if b.startswith('refs/remotes/origin/'):
1564 return True
1564 return True
1565 for b, revision in branch2rev.iteritems():
1565 for b, revision in branch2rev.iteritems():
1566 if b.startswith('refs/remotes/origin/'):
1566 if b.startswith('refs/remotes/origin/'):
1567 if self._gitisancestor(self._state[1], revision):
1567 if self._gitisancestor(self._state[1], revision):
1568 return True
1568 return True
1569 # otherwise, try to push the currently checked out branch
1569 # otherwise, try to push the currently checked out branch
1570 cmd = ['push']
1570 cmd = ['push']
1571 if force:
1571 if force:
1572 cmd.append('--force')
1572 cmd.append('--force')
1573
1573
1574 current = self._gitcurrentbranch()
1574 current = self._gitcurrentbranch()
1575 if current:
1575 if current:
1576 # determine if the current branch is even useful
1576 # determine if the current branch is even useful
1577 if not self._gitisancestor(self._state[1], current):
1577 if not self._gitisancestor(self._state[1], current):
1578 self.ui.warn(_('unrelated git branch checked out '
1578 self.ui.warn(_('unrelated git branch checked out '
1579 'in subrepository "%s"\n') % self._relpath)
1579 'in subrepository "%s"\n') % self._relpath)
1580 return False
1580 return False
1581 self.ui.status(_('pushing branch %s of subrepository "%s"\n') %
1581 self.ui.status(_('pushing branch %s of subrepository "%s"\n') %
1582 (current.split('/', 2)[2], self._relpath))
1582 (current.split('/', 2)[2], self._relpath))
1583 ret = self._gitdir(cmd + ['origin', current])
1583 ret = self._gitdir(cmd + ['origin', current])
1584 return ret[1] == 0
1584 return ret[1] == 0
1585 else:
1585 else:
1586 self.ui.warn(_('no branch checked out in subrepository "%s"\n'
1586 self.ui.warn(_('no branch checked out in subrepository "%s"\n'
1587 'cannot push revision %s\n') %
1587 'cannot push revision %s\n') %
1588 (self._relpath, self._state[1]))
1588 (self._relpath, self._state[1]))
1589 return False
1589 return False
1590
1590
1591 @annotatesubrepoerror
1591 @annotatesubrepoerror
1592 def add(self, ui, match, prefix, uipathfn, explicitonly, **opts):
1592 def add(self, ui, match, prefix, uipathfn, explicitonly, **opts):
1593 if self._gitmissing():
1593 if self._gitmissing():
1594 return []
1594 return []
1595
1595
1596 s = self.status(None, unknown=True, clean=True)
1596 s = self.status(None, unknown=True, clean=True)
1597
1597
1598 tracked = set()
1598 tracked = set()
1599 # dirstates 'amn' warn, 'r' is added again
1599 # dirstates 'amn' warn, 'r' is added again
1600 for l in (s.modified, s.added, s.deleted, s.clean):
1600 for l in (s.modified, s.added, s.deleted, s.clean):
1601 tracked.update(l)
1601 tracked.update(l)
1602
1602
1603 # Unknown files not of interest will be rejected by the matcher
1603 # Unknown files not of interest will be rejected by the matcher
1604 files = s.unknown
1604 files = s.unknown
1605 files.extend(match.files())
1605 files.extend(match.files())
1606
1606
1607 rejected = []
1607 rejected = []
1608
1608
1609 files = [f for f in sorted(set(files)) if match(f)]
1609 files = [f for f in sorted(set(files)) if match(f)]
1610 for f in files:
1610 for f in files:
1611 exact = match.exact(f)
1611 exact = match.exact(f)
1612 command = ["add"]
1612 command = ["add"]
1613 if exact:
1613 if exact:
1614 command.append("-f") #should be added, even if ignored
1614 command.append("-f") #should be added, even if ignored
1615 if ui.verbose or not exact:
1615 if ui.verbose or not exact:
1616 ui.status(_('adding %s\n') % uipathfn(f))
1616 ui.status(_('adding %s\n') % uipathfn(f))
1617
1617
1618 if f in tracked: # hg prints 'adding' even if already tracked
1618 if f in tracked: # hg prints 'adding' even if already tracked
1619 if exact:
1619 if exact:
1620 rejected.append(f)
1620 rejected.append(f)
1621 continue
1621 continue
1622 if not opts.get(r'dry_run'):
1622 if not opts.get(r'dry_run'):
1623 self._gitcommand(command + [f])
1623 self._gitcommand(command + [f])
1624
1624
1625 for f in rejected:
1625 for f in rejected:
1626 ui.warn(_("%s already tracked!\n") % uipathfn(f))
1626 ui.warn(_("%s already tracked!\n") % uipathfn(f))
1627
1627
1628 return rejected
1628 return rejected
1629
1629
1630 @annotatesubrepoerror
1630 @annotatesubrepoerror
1631 def remove(self):
1631 def remove(self):
1632 if self._gitmissing():
1632 if self._gitmissing():
1633 return
1633 return
1634 if self.dirty():
1634 if self.dirty():
1635 self.ui.warn(_('not removing repo %s because '
1635 self.ui.warn(_('not removing repo %s because '
1636 'it has changes.\n') % self._relpath)
1636 'it has changes.\n') % self._relpath)
1637 return
1637 return
1638 # we can't fully delete the repository as it may contain
1638 # we can't fully delete the repository as it may contain
1639 # local-only history
1639 # local-only history
1640 self.ui.note(_('removing subrepo %s\n') % self._relpath)
1640 self.ui.note(_('removing subrepo %s\n') % self._relpath)
1641 self._gitcommand(['config', 'core.bare', 'true'])
1641 self._gitcommand(['config', 'core.bare', 'true'])
1642 for f, kind in self.wvfs.readdir():
1642 for f, kind in self.wvfs.readdir():
1643 if f == '.git':
1643 if f == '.git':
1644 continue
1644 continue
1645 if kind == stat.S_IFDIR:
1645 if kind == stat.S_IFDIR:
1646 self.wvfs.rmtree(f)
1646 self.wvfs.rmtree(f)
1647 else:
1647 else:
1648 self.wvfs.unlink(f)
1648 self.wvfs.unlink(f)
1649
1649
1650 def archive(self, archiver, prefix, match=None, decode=True):
1650 def archive(self, archiver, prefix, match=None, decode=True):
1651 total = 0
1651 total = 0
1652 source, revision = self._state
1652 source, revision = self._state
1653 if not revision:
1653 if not revision:
1654 return total
1654 return total
1655 self._fetch(source, revision)
1655 self._fetch(source, revision)
1656
1656
1657 # Parse git's native archive command.
1657 # Parse git's native archive command.
1658 # This should be much faster than manually traversing the trees
1658 # This should be much faster than manually traversing the trees
1659 # and objects with many subprocess calls.
1659 # and objects with many subprocess calls.
1660 tarstream = self._gitcommand(['archive', revision], stream=True)
1660 tarstream = self._gitcommand(['archive', revision], stream=True)
1661 tar = tarfile.open(fileobj=tarstream, mode=r'r|')
1661 tar = tarfile.open(fileobj=tarstream, mode=r'r|')
1662 relpath = subrelpath(self)
1662 relpath = subrelpath(self)
1663 progress = self.ui.makeprogress(_('archiving (%s)') % relpath,
1663 progress = self.ui.makeprogress(_('archiving (%s)') % relpath,
1664 unit=_('files'))
1664 unit=_('files'))
1665 progress.update(0)
1665 progress.update(0)
1666 for info in tar:
1666 for info in tar:
1667 if info.isdir():
1667 if info.isdir():
1668 continue
1668 continue
1669 bname = pycompat.fsencode(info.name)
1669 bname = pycompat.fsencode(info.name)
1670 if match and not match(bname):
1670 if match and not match(bname):
1671 continue
1671 continue
1672 if info.issym():
1672 if info.issym():
1673 data = info.linkname
1673 data = info.linkname
1674 else:
1674 else:
1675 data = tar.extractfile(info).read()
1675 data = tar.extractfile(info).read()
1676 archiver.addfile(prefix + bname, info.mode, info.issym(), data)
1676 archiver.addfile(prefix + bname, info.mode, info.issym(), data)
1677 total += 1
1677 total += 1
1678 progress.increment()
1678 progress.increment()
1679 progress.complete()
1679 progress.complete()
1680 return total
1680 return total
1681
1681
1682
1682
1683 @annotatesubrepoerror
1683 @annotatesubrepoerror
1684 def cat(self, match, fm, fntemplate, prefix, **opts):
1684 def cat(self, match, fm, fntemplate, prefix, **opts):
1685 rev = self._state[1]
1685 rev = self._state[1]
1686 if match.anypats():
1686 if match.anypats():
1687 return 1 #No support for include/exclude yet
1687 return 1 #No support for include/exclude yet
1688
1688
1689 if not match.files():
1689 if not match.files():
1690 return 1
1690 return 1
1691
1691
1692 # TODO: add support for non-plain formatter (see cmdutil.cat())
1692 # TODO: add support for non-plain formatter (see cmdutil.cat())
1693 for f in match.files():
1693 for f in match.files():
1694 output = self._gitcommand(["show", "%s:%s" % (rev, f)])
1694 output = self._gitcommand(["show", "%s:%s" % (rev, f)])
1695 fp = cmdutil.makefileobj(self._ctx, fntemplate,
1695 fp = cmdutil.makefileobj(self._ctx, fntemplate,
1696 pathname=self.wvfs.reljoin(prefix, f))
1696 pathname=self.wvfs.reljoin(prefix, f))
1697 fp.write(output)
1697 fp.write(output)
1698 fp.close()
1698 fp.close()
1699 return 0
1699 return 0
1700
1700
1701
1701
1702 @annotatesubrepoerror
1702 @annotatesubrepoerror
1703 def status(self, rev2, **opts):
1703 def status(self, rev2, **opts):
1704 rev1 = self._state[1]
1704 rev1 = self._state[1]
1705 if self._gitmissing() or not rev1:
1705 if self._gitmissing() or not rev1:
1706 # if the repo is missing, return no results
1706 # if the repo is missing, return no results
1707 return scmutil.status([], [], [], [], [], [], [])
1707 return scmutil.status([], [], [], [], [], [], [])
1708 modified, added, removed = [], [], []
1708 modified, added, removed = [], [], []
1709 self._gitupdatestat()
1709 self._gitupdatestat()
1710 if rev2:
1710 if rev2:
1711 command = ['diff-tree', '--no-renames', '-r', rev1, rev2]
1711 command = ['diff-tree', '--no-renames', '-r', rev1, rev2]
1712 else:
1712 else:
1713 command = ['diff-index', '--no-renames', rev1]
1713 command = ['diff-index', '--no-renames', rev1]
1714 out = self._gitcommand(command)
1714 out = self._gitcommand(command)
1715 for line in out.split('\n'):
1715 for line in out.split('\n'):
1716 tab = line.find('\t')
1716 tab = line.find('\t')
1717 if tab == -1:
1717 if tab == -1:
1718 continue
1718 continue
1719 status, f = line[tab - 1:tab], line[tab + 1:]
1719 status, f = line[tab - 1:tab], line[tab + 1:]
1720 if status == 'M':
1720 if status == 'M':
1721 modified.append(f)
1721 modified.append(f)
1722 elif status == 'A':
1722 elif status == 'A':
1723 added.append(f)
1723 added.append(f)
1724 elif status == 'D':
1724 elif status == 'D':
1725 removed.append(f)
1725 removed.append(f)
1726
1726
1727 deleted, unknown, ignored, clean = [], [], [], []
1727 deleted, unknown, ignored, clean = [], [], [], []
1728
1728
1729 command = ['status', '--porcelain', '-z']
1729 command = ['status', '--porcelain', '-z']
1730 if opts.get(r'unknown'):
1730 if opts.get(r'unknown'):
1731 command += ['--untracked-files=all']
1731 command += ['--untracked-files=all']
1732 if opts.get(r'ignored'):
1732 if opts.get(r'ignored'):
1733 command += ['--ignored']
1733 command += ['--ignored']
1734 out = self._gitcommand(command)
1734 out = self._gitcommand(command)
1735
1735
1736 changedfiles = set()
1736 changedfiles = set()
1737 changedfiles.update(modified)
1737 changedfiles.update(modified)
1738 changedfiles.update(added)
1738 changedfiles.update(added)
1739 changedfiles.update(removed)
1739 changedfiles.update(removed)
1740 for line in out.split('\0'):
1740 for line in out.split('\0'):
1741 if not line:
1741 if not line:
1742 continue
1742 continue
1743 st = line[0:2]
1743 st = line[0:2]
1744 #moves and copies show 2 files on one line
1744 #moves and copies show 2 files on one line
1745 if line.find('\0') >= 0:
1745 if line.find('\0') >= 0:
1746 filename1, filename2 = line[3:].split('\0')
1746 filename1, filename2 = line[3:].split('\0')
1747 else:
1747 else:
1748 filename1 = line[3:]
1748 filename1 = line[3:]
1749 filename2 = None
1749 filename2 = None
1750
1750
1751 changedfiles.add(filename1)
1751 changedfiles.add(filename1)
1752 if filename2:
1752 if filename2:
1753 changedfiles.add(filename2)
1753 changedfiles.add(filename2)
1754
1754
1755 if st == '??':
1755 if st == '??':
1756 unknown.append(filename1)
1756 unknown.append(filename1)
1757 elif st == '!!':
1757 elif st == '!!':
1758 ignored.append(filename1)
1758 ignored.append(filename1)
1759
1759
1760 if opts.get(r'clean'):
1760 if opts.get(r'clean'):
1761 out = self._gitcommand(['ls-files'])
1761 out = self._gitcommand(['ls-files'])
1762 for f in out.split('\n'):
1762 for f in out.split('\n'):
1763 if not f in changedfiles:
1763 if not f in changedfiles:
1764 clean.append(f)
1764 clean.append(f)
1765
1765
1766 return scmutil.status(modified, added, removed, deleted,
1766 return scmutil.status(modified, added, removed, deleted,
1767 unknown, ignored, clean)
1767 unknown, ignored, clean)
1768
1768
1769 @annotatesubrepoerror
1769 @annotatesubrepoerror
1770 def diff(self, ui, diffopts, node2, match, prefix, **opts):
1770 def diff(self, ui, diffopts, node2, match, prefix, **opts):
1771 node1 = self._state[1]
1771 node1 = self._state[1]
1772 cmd = ['diff', '--no-renames']
1772 cmd = ['diff', '--no-renames']
1773 if opts[r'stat']:
1773 if opts[r'stat']:
1774 cmd.append('--stat')
1774 cmd.append('--stat')
1775 else:
1775 else:
1776 # for Git, this also implies '-p'
1776 # for Git, this also implies '-p'
1777 cmd.append('-U%d' % diffopts.context)
1777 cmd.append('-U%d' % diffopts.context)
1778
1778
1779 if diffopts.noprefix:
1779 if diffopts.noprefix:
1780 cmd.extend(['--src-prefix=%s/' % prefix,
1780 cmd.extend(['--src-prefix=%s/' % prefix,
1781 '--dst-prefix=%s/' % prefix])
1781 '--dst-prefix=%s/' % prefix])
1782 else:
1782 else:
1783 cmd.extend(['--src-prefix=a/%s/' % prefix,
1783 cmd.extend(['--src-prefix=a/%s/' % prefix,
1784 '--dst-prefix=b/%s/' % prefix])
1784 '--dst-prefix=b/%s/' % prefix])
1785
1785
1786 if diffopts.ignorews:
1786 if diffopts.ignorews:
1787 cmd.append('--ignore-all-space')
1787 cmd.append('--ignore-all-space')
1788 if diffopts.ignorewsamount:
1788 if diffopts.ignorewsamount:
1789 cmd.append('--ignore-space-change')
1789 cmd.append('--ignore-space-change')
1790 if self._gitversion(self._gitcommand(['--version'])) >= (1, 8, 4) \
1790 if self._gitversion(self._gitcommand(['--version'])) >= (1, 8, 4) \
1791 and diffopts.ignoreblanklines:
1791 and diffopts.ignoreblanklines:
1792 cmd.append('--ignore-blank-lines')
1792 cmd.append('--ignore-blank-lines')
1793
1793
1794 cmd.append(node1)
1794 cmd.append(node1)
1795 if node2:
1795 if node2:
1796 cmd.append(node2)
1796 cmd.append(node2)
1797
1797
1798 output = ""
1798 output = ""
1799 if match.always():
1799 if match.always():
1800 output += self._gitcommand(cmd) + '\n'
1800 output += self._gitcommand(cmd) + '\n'
1801 else:
1801 else:
1802 st = self.status(node2)[:3]
1802 st = self.status(node2)[:3]
1803 files = [f for sublist in st for f in sublist]
1803 files = [f for sublist in st for f in sublist]
1804 for f in files:
1804 for f in files:
1805 if match(f):
1805 if match(f):
1806 output += self._gitcommand(cmd + ['--', f]) + '\n'
1806 output += self._gitcommand(cmd + ['--', f]) + '\n'
1807
1807
1808 if output.strip():
1808 if output.strip():
1809 ui.write(output)
1809 ui.write(output)
1810
1810
1811 @annotatesubrepoerror
1811 @annotatesubrepoerror
1812 def revert(self, substate, *pats, **opts):
1812 def revert(self, substate, *pats, **opts):
1813 self.ui.status(_('reverting subrepo %s\n') % substate[0])
1813 self.ui.status(_('reverting subrepo %s\n') % substate[0])
1814 if not opts.get(r'no_backup'):
1814 if not opts.get(r'no_backup'):
1815 status = self.status(None)
1815 status = self.status(None)
1816 names = status.modified
1816 names = status.modified
1817 for name in names:
1817 for name in names:
1818 # backuppath() expects a path relative to the parent repo (the
1818 # backuppath() expects a path relative to the parent repo (the
1819 # repo that ui.origbackuppath is relative to)
1819 # repo that ui.origbackuppath is relative to)
1820 parentname = os.path.join(self._path, name)
1820 parentname = os.path.join(self._path, name)
1821 bakname = scmutil.backuppath(self.ui, self._subparent,
1821 bakname = scmutil.backuppath(self.ui, self._subparent,
1822 parentname)
1822 parentname)
1823 self.ui.note(_('saving current version of %s as %s\n') %
1823 self.ui.note(_('saving current version of %s as %s\n') %
1824 (name, os.path.relpath(bakname)))
1824 (name, os.path.relpath(bakname)))
1825 util.rename(self.wvfs.join(name), bakname)
1825 util.rename(self.wvfs.join(name), bakname)
1826
1826
1827 if not opts.get(r'dry_run'):
1827 if not opts.get(r'dry_run'):
1828 self.get(substate, overwrite=True)
1828 self.get(substate, overwrite=True)
1829 return []
1829 return []
1830
1830
1831 def shortid(self, revid):
1831 def shortid(self, revid):
1832 return revid[:7]
1832 return revid[:7]
1833
1833
1834 types = {
1834 types = {
1835 'hg': hgsubrepo,
1835 'hg': hgsubrepo,
1836 'svn': svnsubrepo,
1836 'svn': svnsubrepo,
1837 'git': gitsubrepo,
1837 'git': gitsubrepo,
1838 }
1838 }
@@ -1,1996 +1,1996
1 Let commit recurse into subrepos by default to match pre-2.0 behavior:
1 Let commit recurse into subrepos by default to match pre-2.0 behavior:
2
2
3 $ echo "[ui]" >> $HGRCPATH
3 $ echo "[ui]" >> $HGRCPATH
4 $ echo "commitsubrepos = Yes" >> $HGRCPATH
4 $ echo "commitsubrepos = Yes" >> $HGRCPATH
5
5
6 $ hg init t
6 $ hg init t
7 $ cd t
7 $ cd t
8
8
9 first revision, no sub
9 first revision, no sub
10
10
11 $ echo a > a
11 $ echo a > a
12 $ hg ci -Am0
12 $ hg ci -Am0
13 adding a
13 adding a
14
14
15 add first sub
15 add first sub
16
16
17 $ echo s = s > .hgsub
17 $ echo s = s > .hgsub
18 $ hg add .hgsub
18 $ hg add .hgsub
19 $ hg init s
19 $ hg init s
20 $ echo a > s/a
20 $ echo a > s/a
21
21
22 Issue2232: committing a subrepo without .hgsub
22 Issue2232: committing a subrepo without .hgsub
23
23
24 $ hg ci -mbad s
24 $ hg ci -mbad s
25 abort: can't commit subrepos without .hgsub
25 abort: can't commit subrepos without .hgsub
26 [255]
26 [255]
27
27
28 $ hg -R s add s/a
28 $ hg -R s add s/a
29 $ hg files -S
29 $ hg files -S
30 .hgsub
30 .hgsub
31 a
31 a
32 s/a
32 s/a
33
33
34 `hg files` respects ui.relative-paths
34 `hg files` respects ui.relative-paths
35 BROKEN: shows subrepo paths relative to the subrepo
35 BROKEN: shows subrepo paths relative to the subrepo
36 $ hg files -S --config ui.relative-paths=no
36 $ hg files -S --config ui.relative-paths=no
37 .hgsub
37 .hgsub
38 a
38 a
39 a
39 s/a
40
40
41 $ hg -R s ci -Ams0
41 $ hg -R s ci -Ams0
42 $ hg sum
42 $ hg sum
43 parent: 0:f7b1eb17ad24 tip
43 parent: 0:f7b1eb17ad24 tip
44 0
44 0
45 branch: default
45 branch: default
46 commit: 1 added, 1 subrepos
46 commit: 1 added, 1 subrepos
47 update: (current)
47 update: (current)
48 phases: 1 draft
48 phases: 1 draft
49 $ hg ci -m1
49 $ hg ci -m1
50
50
51 test handling .hgsubstate "added" explicitly.
51 test handling .hgsubstate "added" explicitly.
52
52
53 $ hg parents --template '{node}\n{files}\n'
53 $ hg parents --template '{node}\n{files}\n'
54 7cf8cfea66e410e8e3336508dfeec07b3192de51
54 7cf8cfea66e410e8e3336508dfeec07b3192de51
55 .hgsub .hgsubstate
55 .hgsub .hgsubstate
56 $ hg rollback -q
56 $ hg rollback -q
57 $ hg add .hgsubstate
57 $ hg add .hgsubstate
58 $ hg ci -m1
58 $ hg ci -m1
59 $ hg parents --template '{node}\n{files}\n'
59 $ hg parents --template '{node}\n{files}\n'
60 7cf8cfea66e410e8e3336508dfeec07b3192de51
60 7cf8cfea66e410e8e3336508dfeec07b3192de51
61 .hgsub .hgsubstate
61 .hgsub .hgsubstate
62
62
63 Subrepopath which overlaps with filepath, does not change warnings in remove()
63 Subrepopath which overlaps with filepath, does not change warnings in remove()
64
64
65 $ mkdir snot
65 $ mkdir snot
66 $ touch snot/file
66 $ touch snot/file
67 $ hg remove -S snot/file
67 $ hg remove -S snot/file
68 not removing snot/file: file is untracked
68 not removing snot/file: file is untracked
69 [1]
69 [1]
70 $ hg cat snot/filenot
70 $ hg cat snot/filenot
71 snot/filenot: no such file in rev 7cf8cfea66e4
71 snot/filenot: no such file in rev 7cf8cfea66e4
72 [1]
72 [1]
73 $ rm -r snot
73 $ rm -r snot
74
74
75 Revert subrepo and test subrepo fileset keyword:
75 Revert subrepo and test subrepo fileset keyword:
76
76
77 $ echo b > s/a
77 $ echo b > s/a
78 $ hg revert --dry-run "set:subrepo('glob:s*')"
78 $ hg revert --dry-run "set:subrepo('glob:s*')"
79 reverting subrepo s
79 reverting subrepo s
80 reverting s/a
80 reverting s/a
81 $ cat s/a
81 $ cat s/a
82 b
82 b
83 $ hg revert "set:subrepo('glob:s*')"
83 $ hg revert "set:subrepo('glob:s*')"
84 reverting subrepo s
84 reverting subrepo s
85 reverting s/a
85 reverting s/a
86 $ cat s/a
86 $ cat s/a
87 a
87 a
88 $ rm s/a.orig
88 $ rm s/a.orig
89
89
90 Revert subrepo with no backup. The "reverting s/a" line is gone since
90 Revert subrepo with no backup. The "reverting s/a" line is gone since
91 we're really running 'hg update' in the subrepo:
91 we're really running 'hg update' in the subrepo:
92
92
93 $ echo b > s/a
93 $ echo b > s/a
94 $ hg revert --no-backup s
94 $ hg revert --no-backup s
95 reverting subrepo s
95 reverting subrepo s
96
96
97 Issue2022: update -C
97 Issue2022: update -C
98
98
99 $ echo b > s/a
99 $ echo b > s/a
100 $ hg sum
100 $ hg sum
101 parent: 1:7cf8cfea66e4 tip
101 parent: 1:7cf8cfea66e4 tip
102 1
102 1
103 branch: default
103 branch: default
104 commit: 1 subrepos
104 commit: 1 subrepos
105 update: (current)
105 update: (current)
106 phases: 2 draft
106 phases: 2 draft
107 $ hg co -C 1
107 $ hg co -C 1
108 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
108 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
109 $ hg sum
109 $ hg sum
110 parent: 1:7cf8cfea66e4 tip
110 parent: 1:7cf8cfea66e4 tip
111 1
111 1
112 branch: default
112 branch: default
113 commit: (clean)
113 commit: (clean)
114 update: (current)
114 update: (current)
115 phases: 2 draft
115 phases: 2 draft
116
116
117 commands that require a clean repo should respect subrepos
117 commands that require a clean repo should respect subrepos
118
118
119 $ echo b >> s/a
119 $ echo b >> s/a
120 $ hg backout tip
120 $ hg backout tip
121 abort: uncommitted changes in subrepository "s"
121 abort: uncommitted changes in subrepository "s"
122 [255]
122 [255]
123 $ hg revert -C -R s s/a
123 $ hg revert -C -R s s/a
124
124
125 add sub sub
125 add sub sub
126
126
127 $ echo ss = ss > s/.hgsub
127 $ echo ss = ss > s/.hgsub
128 $ hg init s/ss
128 $ hg init s/ss
129 $ echo a > s/ss/a
129 $ echo a > s/ss/a
130 $ hg -R s add s/.hgsub
130 $ hg -R s add s/.hgsub
131 $ hg -R s/ss add s/ss/a
131 $ hg -R s/ss add s/ss/a
132 $ hg sum
132 $ hg sum
133 parent: 1:7cf8cfea66e4 tip
133 parent: 1:7cf8cfea66e4 tip
134 1
134 1
135 branch: default
135 branch: default
136 commit: 1 subrepos
136 commit: 1 subrepos
137 update: (current)
137 update: (current)
138 phases: 2 draft
138 phases: 2 draft
139 $ hg ci -m2
139 $ hg ci -m2
140 committing subrepository s
140 committing subrepository s
141 committing subrepository s/ss
141 committing subrepository s/ss
142 $ hg sum
142 $ hg sum
143 parent: 2:df30734270ae tip
143 parent: 2:df30734270ae tip
144 2
144 2
145 branch: default
145 branch: default
146 commit: (clean)
146 commit: (clean)
147 update: (current)
147 update: (current)
148 phases: 3 draft
148 phases: 3 draft
149
149
150 test handling .hgsubstate "modified" explicitly.
150 test handling .hgsubstate "modified" explicitly.
151
151
152 $ hg parents --template '{node}\n{files}\n'
152 $ hg parents --template '{node}\n{files}\n'
153 df30734270ae757feb35e643b7018e818e78a9aa
153 df30734270ae757feb35e643b7018e818e78a9aa
154 .hgsubstate
154 .hgsubstate
155 $ hg rollback -q
155 $ hg rollback -q
156 $ hg status -A .hgsubstate
156 $ hg status -A .hgsubstate
157 M .hgsubstate
157 M .hgsubstate
158 $ hg ci -m2
158 $ hg ci -m2
159 $ hg parents --template '{node}\n{files}\n'
159 $ hg parents --template '{node}\n{files}\n'
160 df30734270ae757feb35e643b7018e818e78a9aa
160 df30734270ae757feb35e643b7018e818e78a9aa
161 .hgsubstate
161 .hgsubstate
162
162
163 bump sub rev (and check it is ignored by ui.commitsubrepos)
163 bump sub rev (and check it is ignored by ui.commitsubrepos)
164
164
165 $ echo b > s/a
165 $ echo b > s/a
166 $ hg -R s ci -ms1
166 $ hg -R s ci -ms1
167 $ hg --config ui.commitsubrepos=no ci -m3
167 $ hg --config ui.commitsubrepos=no ci -m3
168
168
169 leave sub dirty (and check ui.commitsubrepos=no aborts the commit)
169 leave sub dirty (and check ui.commitsubrepos=no aborts the commit)
170
170
171 $ echo c > s/a
171 $ echo c > s/a
172 $ hg --config ui.commitsubrepos=no ci -m4
172 $ hg --config ui.commitsubrepos=no ci -m4
173 abort: uncommitted changes in subrepository "s"
173 abort: uncommitted changes in subrepository "s"
174 (use --subrepos for recursive commit)
174 (use --subrepos for recursive commit)
175 [255]
175 [255]
176 $ hg id
176 $ hg id
177 f6affe3fbfaa+ tip
177 f6affe3fbfaa+ tip
178 $ hg -R s ci -mc
178 $ hg -R s ci -mc
179 $ hg id
179 $ hg id
180 f6affe3fbfaa+ tip
180 f6affe3fbfaa+ tip
181 $ echo d > s/a
181 $ echo d > s/a
182 $ hg ci -m4
182 $ hg ci -m4
183 committing subrepository s
183 committing subrepository s
184 $ hg tip -R s
184 $ hg tip -R s
185 changeset: 4:02dcf1d70411
185 changeset: 4:02dcf1d70411
186 tag: tip
186 tag: tip
187 user: test
187 user: test
188 date: Thu Jan 01 00:00:00 1970 +0000
188 date: Thu Jan 01 00:00:00 1970 +0000
189 summary: 4
189 summary: 4
190
190
191
191
192 check caching
192 check caching
193
193
194 $ hg co 0
194 $ hg co 0
195 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
195 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
196 $ hg debugsub
196 $ hg debugsub
197
197
198 restore
198 restore
199
199
200 $ hg co
200 $ hg co
201 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
201 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
202 $ hg debugsub
202 $ hg debugsub
203 path s
203 path s
204 source s
204 source s
205 revision 02dcf1d704118aee3ee306ccfa1910850d5b05ef
205 revision 02dcf1d704118aee3ee306ccfa1910850d5b05ef
206
206
207 new branch for merge tests
207 new branch for merge tests
208
208
209 $ hg co 1
209 $ hg co 1
210 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
210 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
211 $ echo t = t >> .hgsub
211 $ echo t = t >> .hgsub
212 $ hg init t
212 $ hg init t
213 $ echo t > t/t
213 $ echo t > t/t
214 $ hg -R t add t
214 $ hg -R t add t
215 adding t/t
215 adding t/t
216
216
217 5
217 5
218
218
219 $ hg ci -m5 # add sub
219 $ hg ci -m5 # add sub
220 committing subrepository t
220 committing subrepository t
221 created new head
221 created new head
222 $ echo t2 > t/t
222 $ echo t2 > t/t
223
223
224 6
224 6
225
225
226 $ hg st -R s
226 $ hg st -R s
227 $ hg ci -m6 # change sub
227 $ hg ci -m6 # change sub
228 committing subrepository t
228 committing subrepository t
229 $ hg debugsub
229 $ hg debugsub
230 path s
230 path s
231 source s
231 source s
232 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
232 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
233 path t
233 path t
234 source t
234 source t
235 revision 6747d179aa9a688023c4b0cad32e4c92bb7f34ad
235 revision 6747d179aa9a688023c4b0cad32e4c92bb7f34ad
236 $ echo t3 > t/t
236 $ echo t3 > t/t
237
237
238 7
238 7
239
239
240 $ hg ci -m7 # change sub again for conflict test
240 $ hg ci -m7 # change sub again for conflict test
241 committing subrepository t
241 committing subrepository t
242 $ hg rm .hgsub
242 $ hg rm .hgsub
243
243
244 8
244 8
245
245
246 $ hg ci -m8 # remove sub
246 $ hg ci -m8 # remove sub
247
247
248 test handling .hgsubstate "removed" explicitly.
248 test handling .hgsubstate "removed" explicitly.
249
249
250 $ hg parents --template '{node}\n{files}\n'
250 $ hg parents --template '{node}\n{files}\n'
251 96615c1dad2dc8e3796d7332c77ce69156f7b78e
251 96615c1dad2dc8e3796d7332c77ce69156f7b78e
252 .hgsub .hgsubstate
252 .hgsub .hgsubstate
253 $ hg rollback -q
253 $ hg rollback -q
254 $ hg remove .hgsubstate
254 $ hg remove .hgsubstate
255 $ hg ci -m8
255 $ hg ci -m8
256 $ hg parents --template '{node}\n{files}\n'
256 $ hg parents --template '{node}\n{files}\n'
257 96615c1dad2dc8e3796d7332c77ce69156f7b78e
257 96615c1dad2dc8e3796d7332c77ce69156f7b78e
258 .hgsub .hgsubstate
258 .hgsub .hgsubstate
259
259
260 merge tests
260 merge tests
261
261
262 $ hg co -C 3
262 $ hg co -C 3
263 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
263 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
264 $ hg merge 5 # test adding
264 $ hg merge 5 # test adding
265 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
265 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
266 (branch merge, don't forget to commit)
266 (branch merge, don't forget to commit)
267 $ hg debugsub
267 $ hg debugsub
268 path s
268 path s
269 source s
269 source s
270 revision fc627a69481fcbe5f1135069e8a3881c023e4cf5
270 revision fc627a69481fcbe5f1135069e8a3881c023e4cf5
271 path t
271 path t
272 source t
272 source t
273 revision 60ca1237c19474e7a3978b0dc1ca4e6f36d51382
273 revision 60ca1237c19474e7a3978b0dc1ca4e6f36d51382
274 $ hg ci -m9
274 $ hg ci -m9
275 created new head
275 created new head
276 $ hg merge 6 --debug # test change
276 $ hg merge 6 --debug # test change
277 searching for copies back to rev 2
277 searching for copies back to rev 2
278 resolving manifests
278 resolving manifests
279 branchmerge: True, force: False, partial: False
279 branchmerge: True, force: False, partial: False
280 ancestor: 1f14a2e2d3ec, local: f0d2028bf86d+, remote: 1831e14459c4
280 ancestor: 1f14a2e2d3ec, local: f0d2028bf86d+, remote: 1831e14459c4
281 starting 4 threads for background file closing (?)
281 starting 4 threads for background file closing (?)
282 .hgsubstate: versions differ -> m (premerge)
282 .hgsubstate: versions differ -> m (premerge)
283 subrepo merge f0d2028bf86d+ 1831e14459c4 1f14a2e2d3ec
283 subrepo merge f0d2028bf86d+ 1831e14459c4 1f14a2e2d3ec
284 subrepo t: other changed, get t:6747d179aa9a688023c4b0cad32e4c92bb7f34ad:hg
284 subrepo t: other changed, get t:6747d179aa9a688023c4b0cad32e4c92bb7f34ad:hg
285 getting subrepo t
285 getting subrepo t
286 resolving manifests
286 resolving manifests
287 branchmerge: False, force: False, partial: False
287 branchmerge: False, force: False, partial: False
288 ancestor: 60ca1237c194, local: 60ca1237c194+, remote: 6747d179aa9a
288 ancestor: 60ca1237c194, local: 60ca1237c194+, remote: 6747d179aa9a
289 t: remote is newer -> g
289 t: remote is newer -> g
290 getting t
290 getting t
291 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
291 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
292 (branch merge, don't forget to commit)
292 (branch merge, don't forget to commit)
293 $ hg debugsub
293 $ hg debugsub
294 path s
294 path s
295 source s
295 source s
296 revision fc627a69481fcbe5f1135069e8a3881c023e4cf5
296 revision fc627a69481fcbe5f1135069e8a3881c023e4cf5
297 path t
297 path t
298 source t
298 source t
299 revision 6747d179aa9a688023c4b0cad32e4c92bb7f34ad
299 revision 6747d179aa9a688023c4b0cad32e4c92bb7f34ad
300 $ echo conflict > t/t
300 $ echo conflict > t/t
301 $ hg ci -m10
301 $ hg ci -m10
302 committing subrepository t
302 committing subrepository t
303 $ HGMERGE=internal:merge hg merge --debug 7 # test conflict
303 $ HGMERGE=internal:merge hg merge --debug 7 # test conflict
304 searching for copies back to rev 2
304 searching for copies back to rev 2
305 resolving manifests
305 resolving manifests
306 branchmerge: True, force: False, partial: False
306 branchmerge: True, force: False, partial: False
307 ancestor: 1831e14459c4, local: e45c8b14af55+, remote: f94576341bcf
307 ancestor: 1831e14459c4, local: e45c8b14af55+, remote: f94576341bcf
308 starting 4 threads for background file closing (?)
308 starting 4 threads for background file closing (?)
309 .hgsubstate: versions differ -> m (premerge)
309 .hgsubstate: versions differ -> m (premerge)
310 subrepo merge e45c8b14af55+ f94576341bcf 1831e14459c4
310 subrepo merge e45c8b14af55+ f94576341bcf 1831e14459c4
311 subrepo t: both sides changed
311 subrepo t: both sides changed
312 subrepository t diverged (local revision: 20a0db6fbf6c, remote revision: 7af322bc1198)
312 subrepository t diverged (local revision: 20a0db6fbf6c, remote revision: 7af322bc1198)
313 starting 4 threads for background file closing (?)
313 starting 4 threads for background file closing (?)
314 (M)erge, keep (l)ocal [working copy] or keep (r)emote [merge rev]? m
314 (M)erge, keep (l)ocal [working copy] or keep (r)emote [merge rev]? m
315 merging subrepository "t"
315 merging subrepository "t"
316 searching for copies back to rev 2
316 searching for copies back to rev 2
317 resolving manifests
317 resolving manifests
318 branchmerge: True, force: False, partial: False
318 branchmerge: True, force: False, partial: False
319 ancestor: 6747d179aa9a, local: 20a0db6fbf6c+, remote: 7af322bc1198
319 ancestor: 6747d179aa9a, local: 20a0db6fbf6c+, remote: 7af322bc1198
320 preserving t for resolve of t
320 preserving t for resolve of t
321 starting 4 threads for background file closing (?)
321 starting 4 threads for background file closing (?)
322 t: versions differ -> m (premerge)
322 t: versions differ -> m (premerge)
323 picked tool ':merge' for t (binary False symlink False changedelete False)
323 picked tool ':merge' for t (binary False symlink False changedelete False)
324 merging t
324 merging t
325 my t@20a0db6fbf6c+ other t@7af322bc1198 ancestor t@6747d179aa9a
325 my t@20a0db6fbf6c+ other t@7af322bc1198 ancestor t@6747d179aa9a
326 t: versions differ -> m (merge)
326 t: versions differ -> m (merge)
327 picked tool ':merge' for t (binary False symlink False changedelete False)
327 picked tool ':merge' for t (binary False symlink False changedelete False)
328 my t@20a0db6fbf6c+ other t@7af322bc1198 ancestor t@6747d179aa9a
328 my t@20a0db6fbf6c+ other t@7af322bc1198 ancestor t@6747d179aa9a
329 warning: conflicts while merging t! (edit, then use 'hg resolve --mark')
329 warning: conflicts while merging t! (edit, then use 'hg resolve --mark')
330 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
330 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
331 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
331 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
332 subrepo t: merge with t:7af322bc1198a32402fe903e0b7ebcfc5c9bf8f4:hg
332 subrepo t: merge with t:7af322bc1198a32402fe903e0b7ebcfc5c9bf8f4:hg
333 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
333 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
334 (branch merge, don't forget to commit)
334 (branch merge, don't forget to commit)
335
335
336 should conflict
336 should conflict
337
337
338 $ cat t/t
338 $ cat t/t
339 <<<<<<< local: 20a0db6fbf6c - test: 10
339 <<<<<<< local: 20a0db6fbf6c - test: 10
340 conflict
340 conflict
341 =======
341 =======
342 t3
342 t3
343 >>>>>>> other: 7af322bc1198 - test: 7
343 >>>>>>> other: 7af322bc1198 - test: 7
344
344
345 11: remove subrepo t
345 11: remove subrepo t
346
346
347 $ hg co -C 5
347 $ hg co -C 5
348 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
348 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
349 $ hg revert -r 4 .hgsub # remove t
349 $ hg revert -r 4 .hgsub # remove t
350 $ hg ci -m11
350 $ hg ci -m11
351 created new head
351 created new head
352 $ hg debugsub
352 $ hg debugsub
353 path s
353 path s
354 source s
354 source s
355 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
355 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
356
356
357 local removed, remote changed, keep changed
357 local removed, remote changed, keep changed
358
358
359 $ hg merge 6
359 $ hg merge 6
360 remote [merge rev] changed subrepository t which local [working copy] removed
360 remote [merge rev] changed subrepository t which local [working copy] removed
361 use (c)hanged version or (d)elete? c
361 use (c)hanged version or (d)elete? c
362 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
362 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
363 (branch merge, don't forget to commit)
363 (branch merge, don't forget to commit)
364 BROKEN: should include subrepo t
364 BROKEN: should include subrepo t
365 $ hg debugsub
365 $ hg debugsub
366 path s
366 path s
367 source s
367 source s
368 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
368 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
369 $ cat .hgsubstate
369 $ cat .hgsubstate
370 e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
370 e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
371 6747d179aa9a688023c4b0cad32e4c92bb7f34ad t
371 6747d179aa9a688023c4b0cad32e4c92bb7f34ad t
372 $ hg ci -m 'local removed, remote changed, keep changed'
372 $ hg ci -m 'local removed, remote changed, keep changed'
373 BROKEN: should include subrepo t
373 BROKEN: should include subrepo t
374 $ hg debugsub
374 $ hg debugsub
375 path s
375 path s
376 source s
376 source s
377 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
377 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
378 BROKEN: should include subrepo t
378 BROKEN: should include subrepo t
379 $ cat .hgsubstate
379 $ cat .hgsubstate
380 e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
380 e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
381 $ cat t/t
381 $ cat t/t
382 t2
382 t2
383
383
384 local removed, remote changed, keep removed
384 local removed, remote changed, keep removed
385
385
386 $ hg co -C 11
386 $ hg co -C 11
387 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
387 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
388 $ hg merge --config ui.interactive=true 6 <<EOF
388 $ hg merge --config ui.interactive=true 6 <<EOF
389 > d
389 > d
390 > EOF
390 > EOF
391 remote [merge rev] changed subrepository t which local [working copy] removed
391 remote [merge rev] changed subrepository t which local [working copy] removed
392 use (c)hanged version or (d)elete? d
392 use (c)hanged version or (d)elete? d
393 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
393 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
394 (branch merge, don't forget to commit)
394 (branch merge, don't forget to commit)
395 $ hg debugsub
395 $ hg debugsub
396 path s
396 path s
397 source s
397 source s
398 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
398 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
399 $ cat .hgsubstate
399 $ cat .hgsubstate
400 e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
400 e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
401 $ hg ci -m 'local removed, remote changed, keep removed'
401 $ hg ci -m 'local removed, remote changed, keep removed'
402 created new head
402 created new head
403 $ hg debugsub
403 $ hg debugsub
404 path s
404 path s
405 source s
405 source s
406 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
406 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
407 $ cat .hgsubstate
407 $ cat .hgsubstate
408 e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
408 e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
409
409
410 local changed, remote removed, keep changed
410 local changed, remote removed, keep changed
411
411
412 $ hg co -C 6
412 $ hg co -C 6
413 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
413 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
414 $ hg merge 11
414 $ hg merge 11
415 local [working copy] changed subrepository t which remote [merge rev] removed
415 local [working copy] changed subrepository t which remote [merge rev] removed
416 use (c)hanged version or (d)elete? c
416 use (c)hanged version or (d)elete? c
417 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
417 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
418 (branch merge, don't forget to commit)
418 (branch merge, don't forget to commit)
419 BROKEN: should include subrepo t
419 BROKEN: should include subrepo t
420 $ hg debugsub
420 $ hg debugsub
421 path s
421 path s
422 source s
422 source s
423 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
423 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
424 BROKEN: should include subrepo t
424 BROKEN: should include subrepo t
425 $ cat .hgsubstate
425 $ cat .hgsubstate
426 e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
426 e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
427 $ hg ci -m 'local changed, remote removed, keep changed'
427 $ hg ci -m 'local changed, remote removed, keep changed'
428 created new head
428 created new head
429 BROKEN: should include subrepo t
429 BROKEN: should include subrepo t
430 $ hg debugsub
430 $ hg debugsub
431 path s
431 path s
432 source s
432 source s
433 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
433 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
434 BROKEN: should include subrepo t
434 BROKEN: should include subrepo t
435 $ cat .hgsubstate
435 $ cat .hgsubstate
436 e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
436 e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
437 $ cat t/t
437 $ cat t/t
438 t2
438 t2
439
439
440 local changed, remote removed, keep removed
440 local changed, remote removed, keep removed
441
441
442 $ hg co -C 6
442 $ hg co -C 6
443 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
443 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
444 $ hg merge --config ui.interactive=true 11 <<EOF
444 $ hg merge --config ui.interactive=true 11 <<EOF
445 > d
445 > d
446 > EOF
446 > EOF
447 local [working copy] changed subrepository t which remote [merge rev] removed
447 local [working copy] changed subrepository t which remote [merge rev] removed
448 use (c)hanged version or (d)elete? d
448 use (c)hanged version or (d)elete? d
449 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
449 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
450 (branch merge, don't forget to commit)
450 (branch merge, don't forget to commit)
451 $ hg debugsub
451 $ hg debugsub
452 path s
452 path s
453 source s
453 source s
454 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
454 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
455 $ cat .hgsubstate
455 $ cat .hgsubstate
456 e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
456 e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
457 $ hg ci -m 'local changed, remote removed, keep removed'
457 $ hg ci -m 'local changed, remote removed, keep removed'
458 created new head
458 created new head
459 $ hg debugsub
459 $ hg debugsub
460 path s
460 path s
461 source s
461 source s
462 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
462 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
463 $ cat .hgsubstate
463 $ cat .hgsubstate
464 e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
464 e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
465
465
466 clean up to avoid having to fix up the tests below
466 clean up to avoid having to fix up the tests below
467
467
468 $ hg co -C 10
468 $ hg co -C 10
469 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
469 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
470 $ cat >> $HGRCPATH <<EOF
470 $ cat >> $HGRCPATH <<EOF
471 > [extensions]
471 > [extensions]
472 > strip=
472 > strip=
473 > EOF
473 > EOF
474 $ hg strip -r 11:15
474 $ hg strip -r 11:15
475 saved backup bundle to $TESTTMP/t/.hg/strip-backup/*-backup.hg (glob)
475 saved backup bundle to $TESTTMP/t/.hg/strip-backup/*-backup.hg (glob)
476
476
477 clone
477 clone
478
478
479 $ cd ..
479 $ cd ..
480 $ hg clone t tc
480 $ hg clone t tc
481 updating to branch default
481 updating to branch default
482 cloning subrepo s from $TESTTMP/t/s
482 cloning subrepo s from $TESTTMP/t/s
483 cloning subrepo s/ss from $TESTTMP/t/s/ss
483 cloning subrepo s/ss from $TESTTMP/t/s/ss
484 cloning subrepo t from $TESTTMP/t/t
484 cloning subrepo t from $TESTTMP/t/t
485 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
485 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
486 $ cd tc
486 $ cd tc
487 $ hg debugsub
487 $ hg debugsub
488 path s
488 path s
489 source s
489 source s
490 revision fc627a69481fcbe5f1135069e8a3881c023e4cf5
490 revision fc627a69481fcbe5f1135069e8a3881c023e4cf5
491 path t
491 path t
492 source t
492 source t
493 revision 20a0db6fbf6c3d2836e6519a642ae929bfc67c0e
493 revision 20a0db6fbf6c3d2836e6519a642ae929bfc67c0e
494 $ cd ..
494 $ cd ..
495
495
496 clone with subrepo disabled (update should fail)
496 clone with subrepo disabled (update should fail)
497
497
498 $ hg clone t -U tc2 --config subrepos.allowed=false
498 $ hg clone t -U tc2 --config subrepos.allowed=false
499 $ hg update -R tc2 --config subrepos.allowed=false
499 $ hg update -R tc2 --config subrepos.allowed=false
500 abort: subrepos not enabled
500 abort: subrepos not enabled
501 (see 'hg help config.subrepos' for details)
501 (see 'hg help config.subrepos' for details)
502 [255]
502 [255]
503 $ ls tc2
503 $ ls tc2
504 a
504 a
505
505
506 $ hg clone t tc3 --config subrepos.allowed=false
506 $ hg clone t tc3 --config subrepos.allowed=false
507 updating to branch default
507 updating to branch default
508 abort: subrepos not enabled
508 abort: subrepos not enabled
509 (see 'hg help config.subrepos' for details)
509 (see 'hg help config.subrepos' for details)
510 [255]
510 [255]
511 $ ls tc3
511 $ ls tc3
512 a
512 a
513
513
514 And again with just the hg type disabled
514 And again with just the hg type disabled
515
515
516 $ hg clone t -U tc4 --config subrepos.hg:allowed=false
516 $ hg clone t -U tc4 --config subrepos.hg:allowed=false
517 $ hg update -R tc4 --config subrepos.hg:allowed=false
517 $ hg update -R tc4 --config subrepos.hg:allowed=false
518 abort: hg subrepos not allowed
518 abort: hg subrepos not allowed
519 (see 'hg help config.subrepos' for details)
519 (see 'hg help config.subrepos' for details)
520 [255]
520 [255]
521 $ ls tc4
521 $ ls tc4
522 a
522 a
523
523
524 $ hg clone t tc5 --config subrepos.hg:allowed=false
524 $ hg clone t tc5 --config subrepos.hg:allowed=false
525 updating to branch default
525 updating to branch default
526 abort: hg subrepos not allowed
526 abort: hg subrepos not allowed
527 (see 'hg help config.subrepos' for details)
527 (see 'hg help config.subrepos' for details)
528 [255]
528 [255]
529 $ ls tc5
529 $ ls tc5
530 a
530 a
531
531
532 push
532 push
533
533
534 $ cd tc
534 $ cd tc
535 $ echo bah > t/t
535 $ echo bah > t/t
536 $ hg ci -m11
536 $ hg ci -m11
537 committing subrepository t
537 committing subrepository t
538 $ hg push
538 $ hg push
539 pushing to $TESTTMP/t
539 pushing to $TESTTMP/t
540 no changes made to subrepo s/ss since last push to $TESTTMP/t/s/ss
540 no changes made to subrepo s/ss since last push to $TESTTMP/t/s/ss
541 no changes made to subrepo s since last push to $TESTTMP/t/s
541 no changes made to subrepo s since last push to $TESTTMP/t/s
542 pushing subrepo t to $TESTTMP/t/t
542 pushing subrepo t to $TESTTMP/t/t
543 searching for changes
543 searching for changes
544 adding changesets
544 adding changesets
545 adding manifests
545 adding manifests
546 adding file changes
546 adding file changes
547 added 1 changesets with 1 changes to 1 files
547 added 1 changesets with 1 changes to 1 files
548 searching for changes
548 searching for changes
549 adding changesets
549 adding changesets
550 adding manifests
550 adding manifests
551 adding file changes
551 adding file changes
552 added 1 changesets with 1 changes to 1 files
552 added 1 changesets with 1 changes to 1 files
553
553
554 push -f
554 push -f
555
555
556 $ echo bah > s/a
556 $ echo bah > s/a
557 $ hg ci -m12
557 $ hg ci -m12
558 committing subrepository s
558 committing subrepository s
559 $ hg push
559 $ hg push
560 pushing to $TESTTMP/t
560 pushing to $TESTTMP/t
561 no changes made to subrepo s/ss since last push to $TESTTMP/t/s/ss
561 no changes made to subrepo s/ss since last push to $TESTTMP/t/s/ss
562 pushing subrepo s to $TESTTMP/t/s
562 pushing subrepo s to $TESTTMP/t/s
563 searching for changes
563 searching for changes
564 abort: push creates new remote head 12a213df6fa9! (in subrepository "s")
564 abort: push creates new remote head 12a213df6fa9! (in subrepository "s")
565 (merge or see 'hg help push' for details about pushing new heads)
565 (merge or see 'hg help push' for details about pushing new heads)
566 [255]
566 [255]
567 $ hg push -f
567 $ hg push -f
568 pushing to $TESTTMP/t
568 pushing to $TESTTMP/t
569 pushing subrepo s/ss to $TESTTMP/t/s/ss
569 pushing subrepo s/ss to $TESTTMP/t/s/ss
570 searching for changes
570 searching for changes
571 no changes found
571 no changes found
572 pushing subrepo s to $TESTTMP/t/s
572 pushing subrepo s to $TESTTMP/t/s
573 searching for changes
573 searching for changes
574 adding changesets
574 adding changesets
575 adding manifests
575 adding manifests
576 adding file changes
576 adding file changes
577 added 1 changesets with 1 changes to 1 files (+1 heads)
577 added 1 changesets with 1 changes to 1 files (+1 heads)
578 pushing subrepo t to $TESTTMP/t/t
578 pushing subrepo t to $TESTTMP/t/t
579 searching for changes
579 searching for changes
580 no changes found
580 no changes found
581 searching for changes
581 searching for changes
582 adding changesets
582 adding changesets
583 adding manifests
583 adding manifests
584 adding file changes
584 adding file changes
585 added 1 changesets with 1 changes to 1 files
585 added 1 changesets with 1 changes to 1 files
586
586
587 check that unmodified subrepos are not pushed
587 check that unmodified subrepos are not pushed
588
588
589 $ hg clone . ../tcc
589 $ hg clone . ../tcc
590 updating to branch default
590 updating to branch default
591 cloning subrepo s from $TESTTMP/tc/s
591 cloning subrepo s from $TESTTMP/tc/s
592 cloning subrepo s/ss from $TESTTMP/tc/s/ss
592 cloning subrepo s/ss from $TESTTMP/tc/s/ss
593 cloning subrepo t from $TESTTMP/tc/t
593 cloning subrepo t from $TESTTMP/tc/t
594 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
594 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
595
595
596 the subrepos on the new clone have nothing to push to its source
596 the subrepos on the new clone have nothing to push to its source
597
597
598 $ hg push -R ../tcc .
598 $ hg push -R ../tcc .
599 pushing to .
599 pushing to .
600 no changes made to subrepo s/ss since last push to s/ss
600 no changes made to subrepo s/ss since last push to s/ss
601 no changes made to subrepo s since last push to s
601 no changes made to subrepo s since last push to s
602 no changes made to subrepo t since last push to t
602 no changes made to subrepo t since last push to t
603 searching for changes
603 searching for changes
604 no changes found
604 no changes found
605 [1]
605 [1]
606
606
607 the subrepos on the source do not have a clean store versus the clone target
607 the subrepos on the source do not have a clean store versus the clone target
608 because they were never explicitly pushed to the source
608 because they were never explicitly pushed to the source
609
609
610 $ hg push ../tcc
610 $ hg push ../tcc
611 pushing to ../tcc
611 pushing to ../tcc
612 pushing subrepo s/ss to ../tcc/s/ss
612 pushing subrepo s/ss to ../tcc/s/ss
613 searching for changes
613 searching for changes
614 no changes found
614 no changes found
615 pushing subrepo s to ../tcc/s
615 pushing subrepo s to ../tcc/s
616 searching for changes
616 searching for changes
617 no changes found
617 no changes found
618 pushing subrepo t to ../tcc/t
618 pushing subrepo t to ../tcc/t
619 searching for changes
619 searching for changes
620 no changes found
620 no changes found
621 searching for changes
621 searching for changes
622 no changes found
622 no changes found
623 [1]
623 [1]
624
624
625 after push their stores become clean
625 after push their stores become clean
626
626
627 $ hg push ../tcc
627 $ hg push ../tcc
628 pushing to ../tcc
628 pushing to ../tcc
629 no changes made to subrepo s/ss since last push to ../tcc/s/ss
629 no changes made to subrepo s/ss since last push to ../tcc/s/ss
630 no changes made to subrepo s since last push to ../tcc/s
630 no changes made to subrepo s since last push to ../tcc/s
631 no changes made to subrepo t since last push to ../tcc/t
631 no changes made to subrepo t since last push to ../tcc/t
632 searching for changes
632 searching for changes
633 no changes found
633 no changes found
634 [1]
634 [1]
635
635
636 updating a subrepo to a different revision or changing
636 updating a subrepo to a different revision or changing
637 its working directory does not make its store dirty
637 its working directory does not make its store dirty
638
638
639 $ hg -R s update '.^'
639 $ hg -R s update '.^'
640 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
640 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
641 $ hg push
641 $ hg push
642 pushing to $TESTTMP/t
642 pushing to $TESTTMP/t
643 no changes made to subrepo s/ss since last push to $TESTTMP/t/s/ss
643 no changes made to subrepo s/ss since last push to $TESTTMP/t/s/ss
644 no changes made to subrepo s since last push to $TESTTMP/t/s
644 no changes made to subrepo s since last push to $TESTTMP/t/s
645 no changes made to subrepo t since last push to $TESTTMP/t/t
645 no changes made to subrepo t since last push to $TESTTMP/t/t
646 searching for changes
646 searching for changes
647 no changes found
647 no changes found
648 [1]
648 [1]
649 $ echo foo >> s/a
649 $ echo foo >> s/a
650 $ hg push
650 $ hg push
651 pushing to $TESTTMP/t
651 pushing to $TESTTMP/t
652 no changes made to subrepo s/ss since last push to $TESTTMP/t/s/ss
652 no changes made to subrepo s/ss since last push to $TESTTMP/t/s/ss
653 no changes made to subrepo s since last push to $TESTTMP/t/s
653 no changes made to subrepo s since last push to $TESTTMP/t/s
654 no changes made to subrepo t since last push to $TESTTMP/t/t
654 no changes made to subrepo t since last push to $TESTTMP/t/t
655 searching for changes
655 searching for changes
656 no changes found
656 no changes found
657 [1]
657 [1]
658 $ hg -R s update -C tip
658 $ hg -R s update -C tip
659 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
659 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
660
660
661 committing into a subrepo makes its store (but not its parent's store) dirty
661 committing into a subrepo makes its store (but not its parent's store) dirty
662
662
663 $ echo foo >> s/ss/a
663 $ echo foo >> s/ss/a
664 $ hg -R s/ss commit -m 'test dirty store detection'
664 $ hg -R s/ss commit -m 'test dirty store detection'
665
665
666 $ hg out -S -r `hg log -r tip -T "{node|short}"`
666 $ hg out -S -r `hg log -r tip -T "{node|short}"`
667 comparing with $TESTTMP/t
667 comparing with $TESTTMP/t
668 searching for changes
668 searching for changes
669 no changes found
669 no changes found
670 comparing with $TESTTMP/t/s
670 comparing with $TESTTMP/t/s
671 searching for changes
671 searching for changes
672 no changes found
672 no changes found
673 comparing with $TESTTMP/t/s/ss
673 comparing with $TESTTMP/t/s/ss
674 searching for changes
674 searching for changes
675 changeset: 1:79ea5566a333
675 changeset: 1:79ea5566a333
676 tag: tip
676 tag: tip
677 user: test
677 user: test
678 date: Thu Jan 01 00:00:00 1970 +0000
678 date: Thu Jan 01 00:00:00 1970 +0000
679 summary: test dirty store detection
679 summary: test dirty store detection
680
680
681 comparing with $TESTTMP/t/t
681 comparing with $TESTTMP/t/t
682 searching for changes
682 searching for changes
683 no changes found
683 no changes found
684
684
685 $ hg push
685 $ hg push
686 pushing to $TESTTMP/t
686 pushing to $TESTTMP/t
687 pushing subrepo s/ss to $TESTTMP/t/s/ss
687 pushing subrepo s/ss to $TESTTMP/t/s/ss
688 searching for changes
688 searching for changes
689 adding changesets
689 adding changesets
690 adding manifests
690 adding manifests
691 adding file changes
691 adding file changes
692 added 1 changesets with 1 changes to 1 files
692 added 1 changesets with 1 changes to 1 files
693 no changes made to subrepo s since last push to $TESTTMP/t/s
693 no changes made to subrepo s since last push to $TESTTMP/t/s
694 no changes made to subrepo t since last push to $TESTTMP/t/t
694 no changes made to subrepo t since last push to $TESTTMP/t/t
695 searching for changes
695 searching for changes
696 no changes found
696 no changes found
697 [1]
697 [1]
698
698
699 a subrepo store may be clean versus one repo but not versus another
699 a subrepo store may be clean versus one repo but not versus another
700
700
701 $ hg push
701 $ hg push
702 pushing to $TESTTMP/t
702 pushing to $TESTTMP/t
703 no changes made to subrepo s/ss since last push to $TESTTMP/t/s/ss
703 no changes made to subrepo s/ss since last push to $TESTTMP/t/s/ss
704 no changes made to subrepo s since last push to $TESTTMP/t/s
704 no changes made to subrepo s since last push to $TESTTMP/t/s
705 no changes made to subrepo t since last push to $TESTTMP/t/t
705 no changes made to subrepo t since last push to $TESTTMP/t/t
706 searching for changes
706 searching for changes
707 no changes found
707 no changes found
708 [1]
708 [1]
709 $ hg push ../tcc
709 $ hg push ../tcc
710 pushing to ../tcc
710 pushing to ../tcc
711 pushing subrepo s/ss to ../tcc/s/ss
711 pushing subrepo s/ss to ../tcc/s/ss
712 searching for changes
712 searching for changes
713 adding changesets
713 adding changesets
714 adding manifests
714 adding manifests
715 adding file changes
715 adding file changes
716 added 1 changesets with 1 changes to 1 files
716 added 1 changesets with 1 changes to 1 files
717 no changes made to subrepo s since last push to ../tcc/s
717 no changes made to subrepo s since last push to ../tcc/s
718 no changes made to subrepo t since last push to ../tcc/t
718 no changes made to subrepo t since last push to ../tcc/t
719 searching for changes
719 searching for changes
720 no changes found
720 no changes found
721 [1]
721 [1]
722
722
723 update
723 update
724
724
725 $ cd ../t
725 $ cd ../t
726 $ hg up -C # discard our earlier merge
726 $ hg up -C # discard our earlier merge
727 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
727 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
728 updated to "c373c8102e68: 12"
728 updated to "c373c8102e68: 12"
729 2 other heads for branch "default"
729 2 other heads for branch "default"
730 $ echo blah > t/t
730 $ echo blah > t/t
731 $ hg ci -m13
731 $ hg ci -m13
732 committing subrepository t
732 committing subrepository t
733
733
734 backout calls revert internally with minimal opts, which should not raise
734 backout calls revert internally with minimal opts, which should not raise
735 KeyError
735 KeyError
736
736
737 $ hg backout ".^" --no-commit
737 $ hg backout ".^" --no-commit
738 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
738 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
739 changeset c373c8102e68 backed out, don't forget to commit.
739 changeset c373c8102e68 backed out, don't forget to commit.
740
740
741 $ hg up -C # discard changes
741 $ hg up -C # discard changes
742 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
742 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
743 updated to "925c17564ef8: 13"
743 updated to "925c17564ef8: 13"
744 2 other heads for branch "default"
744 2 other heads for branch "default"
745
745
746 pull
746 pull
747
747
748 $ cd ../tc
748 $ cd ../tc
749 $ hg pull
749 $ hg pull
750 pulling from $TESTTMP/t
750 pulling from $TESTTMP/t
751 searching for changes
751 searching for changes
752 adding changesets
752 adding changesets
753 adding manifests
753 adding manifests
754 adding file changes
754 adding file changes
755 added 1 changesets with 1 changes to 1 files
755 added 1 changesets with 1 changes to 1 files
756 new changesets 925c17564ef8
756 new changesets 925c17564ef8
757 (run 'hg update' to get a working copy)
757 (run 'hg update' to get a working copy)
758
758
759 should pull t
759 should pull t
760
760
761 $ hg incoming -S -r `hg log -r tip -T "{node|short}"`
761 $ hg incoming -S -r `hg log -r tip -T "{node|short}"`
762 comparing with $TESTTMP/t
762 comparing with $TESTTMP/t
763 no changes found
763 no changes found
764 comparing with $TESTTMP/t/s
764 comparing with $TESTTMP/t/s
765 searching for changes
765 searching for changes
766 no changes found
766 no changes found
767 comparing with $TESTTMP/t/s/ss
767 comparing with $TESTTMP/t/s/ss
768 searching for changes
768 searching for changes
769 no changes found
769 no changes found
770 comparing with $TESTTMP/t/t
770 comparing with $TESTTMP/t/t
771 searching for changes
771 searching for changes
772 changeset: 5:52c0adc0515a
772 changeset: 5:52c0adc0515a
773 tag: tip
773 tag: tip
774 user: test
774 user: test
775 date: Thu Jan 01 00:00:00 1970 +0000
775 date: Thu Jan 01 00:00:00 1970 +0000
776 summary: 13
776 summary: 13
777
777
778
778
779 $ hg up
779 $ hg up
780 pulling subrepo t from $TESTTMP/t/t
780 pulling subrepo t from $TESTTMP/t/t
781 searching for changes
781 searching for changes
782 adding changesets
782 adding changesets
783 adding manifests
783 adding manifests
784 adding file changes
784 adding file changes
785 added 1 changesets with 1 changes to 1 files
785 added 1 changesets with 1 changes to 1 files
786 new changesets 52c0adc0515a
786 new changesets 52c0adc0515a
787 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
787 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
788 updated to "925c17564ef8: 13"
788 updated to "925c17564ef8: 13"
789 2 other heads for branch "default"
789 2 other heads for branch "default"
790 $ cat t/t
790 $ cat t/t
791 blah
791 blah
792
792
793 bogus subrepo path aborts
793 bogus subrepo path aborts
794
794
795 $ echo 'bogus=[boguspath' >> .hgsub
795 $ echo 'bogus=[boguspath' >> .hgsub
796 $ hg ci -m 'bogus subrepo path'
796 $ hg ci -m 'bogus subrepo path'
797 abort: missing ] in subrepository source
797 abort: missing ] in subrepository source
798 [255]
798 [255]
799
799
800 Issue1986: merge aborts when trying to merge a subrepo that
800 Issue1986: merge aborts when trying to merge a subrepo that
801 shouldn't need merging
801 shouldn't need merging
802
802
803 # subrepo layout
803 # subrepo layout
804 #
804 #
805 # o 5 br
805 # o 5 br
806 # /|
806 # /|
807 # o | 4 default
807 # o | 4 default
808 # | |
808 # | |
809 # | o 3 br
809 # | o 3 br
810 # |/|
810 # |/|
811 # o | 2 default
811 # o | 2 default
812 # | |
812 # | |
813 # | o 1 br
813 # | o 1 br
814 # |/
814 # |/
815 # o 0 default
815 # o 0 default
816
816
817 $ cd ..
817 $ cd ..
818 $ rm -rf sub
818 $ rm -rf sub
819 $ hg init main
819 $ hg init main
820 $ cd main
820 $ cd main
821 $ hg init s
821 $ hg init s
822 $ cd s
822 $ cd s
823 $ echo a > a
823 $ echo a > a
824 $ hg ci -Am1
824 $ hg ci -Am1
825 adding a
825 adding a
826 $ hg branch br
826 $ hg branch br
827 marked working directory as branch br
827 marked working directory as branch br
828 (branches are permanent and global, did you want a bookmark?)
828 (branches are permanent and global, did you want a bookmark?)
829 $ echo a >> a
829 $ echo a >> a
830 $ hg ci -m1
830 $ hg ci -m1
831 $ hg up default
831 $ hg up default
832 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
832 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
833 $ echo b > b
833 $ echo b > b
834 $ hg ci -Am1
834 $ hg ci -Am1
835 adding b
835 adding b
836 $ hg up br
836 $ hg up br
837 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
837 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
838 $ hg merge tip
838 $ hg merge tip
839 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
839 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
840 (branch merge, don't forget to commit)
840 (branch merge, don't forget to commit)
841 $ hg ci -m1
841 $ hg ci -m1
842 $ hg up 2
842 $ hg up 2
843 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
843 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
844 $ echo c > c
844 $ echo c > c
845 $ hg ci -Am1
845 $ hg ci -Am1
846 adding c
846 adding c
847 $ hg up 3
847 $ hg up 3
848 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
848 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
849 $ hg merge 4
849 $ hg merge 4
850 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
850 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
851 (branch merge, don't forget to commit)
851 (branch merge, don't forget to commit)
852 $ hg ci -m1
852 $ hg ci -m1
853
853
854 # main repo layout:
854 # main repo layout:
855 #
855 #
856 # * <-- try to merge default into br again
856 # * <-- try to merge default into br again
857 # .`|
857 # .`|
858 # . o 5 br --> substate = 5
858 # . o 5 br --> substate = 5
859 # . |
859 # . |
860 # o | 4 default --> substate = 4
860 # o | 4 default --> substate = 4
861 # | |
861 # | |
862 # | o 3 br --> substate = 2
862 # | o 3 br --> substate = 2
863 # |/|
863 # |/|
864 # o | 2 default --> substate = 2
864 # o | 2 default --> substate = 2
865 # | |
865 # | |
866 # | o 1 br --> substate = 3
866 # | o 1 br --> substate = 3
867 # |/
867 # |/
868 # o 0 default --> substate = 2
868 # o 0 default --> substate = 2
869
869
870 $ cd ..
870 $ cd ..
871 $ echo 's = s' > .hgsub
871 $ echo 's = s' > .hgsub
872 $ hg -R s up 2
872 $ hg -R s up 2
873 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
873 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
874 $ hg ci -Am1
874 $ hg ci -Am1
875 adding .hgsub
875 adding .hgsub
876 $ hg branch br
876 $ hg branch br
877 marked working directory as branch br
877 marked working directory as branch br
878 (branches are permanent and global, did you want a bookmark?)
878 (branches are permanent and global, did you want a bookmark?)
879 $ echo b > b
879 $ echo b > b
880 $ hg -R s up 3
880 $ hg -R s up 3
881 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
881 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
882 $ hg ci -Am1
882 $ hg ci -Am1
883 adding b
883 adding b
884 $ hg up default
884 $ hg up default
885 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
885 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
886 $ echo c > c
886 $ echo c > c
887 $ hg ci -Am1
887 $ hg ci -Am1
888 adding c
888 adding c
889 $ hg up 1
889 $ hg up 1
890 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
890 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
891 $ hg merge 2
891 $ hg merge 2
892 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
892 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
893 (branch merge, don't forget to commit)
893 (branch merge, don't forget to commit)
894 $ hg ci -m1
894 $ hg ci -m1
895 $ hg up 2
895 $ hg up 2
896 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
896 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
897 $ hg -R s up 4
897 $ hg -R s up 4
898 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
898 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
899 $ echo d > d
899 $ echo d > d
900 $ hg ci -Am1
900 $ hg ci -Am1
901 adding d
901 adding d
902 $ hg up 3
902 $ hg up 3
903 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
903 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
904 $ hg -R s up 5
904 $ hg -R s up 5
905 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
905 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
906 $ echo e > e
906 $ echo e > e
907 $ hg ci -Am1
907 $ hg ci -Am1
908 adding e
908 adding e
909
909
910 $ hg up 5
910 $ hg up 5
911 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
911 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
912 $ hg merge 4 # try to merge default into br again
912 $ hg merge 4 # try to merge default into br again
913 subrepository s diverged (local revision: f8f13b33206e, remote revision: a3f9062a4f88)
913 subrepository s diverged (local revision: f8f13b33206e, remote revision: a3f9062a4f88)
914 (M)erge, keep (l)ocal [working copy] or keep (r)emote [merge rev]? m
914 (M)erge, keep (l)ocal [working copy] or keep (r)emote [merge rev]? m
915 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
915 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
916 (branch merge, don't forget to commit)
916 (branch merge, don't forget to commit)
917 $ cd ..
917 $ cd ..
918
918
919 test subrepo delete from .hgsubstate
919 test subrepo delete from .hgsubstate
920
920
921 $ hg init testdelete
921 $ hg init testdelete
922 $ mkdir testdelete/nested testdelete/nested2
922 $ mkdir testdelete/nested testdelete/nested2
923 $ hg init testdelete/nested
923 $ hg init testdelete/nested
924 $ hg init testdelete/nested2
924 $ hg init testdelete/nested2
925 $ echo test > testdelete/nested/foo
925 $ echo test > testdelete/nested/foo
926 $ echo test > testdelete/nested2/foo
926 $ echo test > testdelete/nested2/foo
927 $ hg -R testdelete/nested add
927 $ hg -R testdelete/nested add
928 adding testdelete/nested/foo
928 adding testdelete/nested/foo
929 $ hg -R testdelete/nested2 add
929 $ hg -R testdelete/nested2 add
930 adding testdelete/nested2/foo
930 adding testdelete/nested2/foo
931 $ hg -R testdelete/nested ci -m test
931 $ hg -R testdelete/nested ci -m test
932 $ hg -R testdelete/nested2 ci -m test
932 $ hg -R testdelete/nested2 ci -m test
933 $ echo nested = nested > testdelete/.hgsub
933 $ echo nested = nested > testdelete/.hgsub
934 $ echo nested2 = nested2 >> testdelete/.hgsub
934 $ echo nested2 = nested2 >> testdelete/.hgsub
935 $ hg -R testdelete add
935 $ hg -R testdelete add
936 adding testdelete/.hgsub
936 adding testdelete/.hgsub
937 $ hg -R testdelete ci -m "nested 1 & 2 added"
937 $ hg -R testdelete ci -m "nested 1 & 2 added"
938 $ echo nested = nested > testdelete/.hgsub
938 $ echo nested = nested > testdelete/.hgsub
939 $ hg -R testdelete ci -m "nested 2 deleted"
939 $ hg -R testdelete ci -m "nested 2 deleted"
940 $ cat testdelete/.hgsubstate
940 $ cat testdelete/.hgsubstate
941 bdf5c9a3103743d900b12ae0db3ffdcfd7b0d878 nested
941 bdf5c9a3103743d900b12ae0db3ffdcfd7b0d878 nested
942 $ hg -R testdelete remove testdelete/.hgsub
942 $ hg -R testdelete remove testdelete/.hgsub
943 $ hg -R testdelete ci -m ".hgsub deleted"
943 $ hg -R testdelete ci -m ".hgsub deleted"
944 $ cat testdelete/.hgsubstate
944 $ cat testdelete/.hgsubstate
945 bdf5c9a3103743d900b12ae0db3ffdcfd7b0d878 nested
945 bdf5c9a3103743d900b12ae0db3ffdcfd7b0d878 nested
946
946
947 test repository cloning
947 test repository cloning
948
948
949 $ mkdir mercurial mercurial2
949 $ mkdir mercurial mercurial2
950 $ hg init nested_absolute
950 $ hg init nested_absolute
951 $ echo test > nested_absolute/foo
951 $ echo test > nested_absolute/foo
952 $ hg -R nested_absolute add
952 $ hg -R nested_absolute add
953 adding nested_absolute/foo
953 adding nested_absolute/foo
954 $ hg -R nested_absolute ci -mtest
954 $ hg -R nested_absolute ci -mtest
955 $ cd mercurial
955 $ cd mercurial
956 $ hg init nested_relative
956 $ hg init nested_relative
957 $ echo test2 > nested_relative/foo2
957 $ echo test2 > nested_relative/foo2
958 $ hg -R nested_relative add
958 $ hg -R nested_relative add
959 adding nested_relative/foo2
959 adding nested_relative/foo2
960 $ hg -R nested_relative ci -mtest2
960 $ hg -R nested_relative ci -mtest2
961 $ hg init main
961 $ hg init main
962 $ echo "nested_relative = ../nested_relative" > main/.hgsub
962 $ echo "nested_relative = ../nested_relative" > main/.hgsub
963 $ echo "nested_absolute = `pwd`/nested_absolute" >> main/.hgsub
963 $ echo "nested_absolute = `pwd`/nested_absolute" >> main/.hgsub
964 $ hg -R main add
964 $ hg -R main add
965 adding main/.hgsub
965 adding main/.hgsub
966 $ hg -R main ci -m "add subrepos"
966 $ hg -R main ci -m "add subrepos"
967 $ cd ..
967 $ cd ..
968 $ hg clone mercurial/main mercurial2/main
968 $ hg clone mercurial/main mercurial2/main
969 updating to branch default
969 updating to branch default
970 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
970 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
971 $ cat mercurial2/main/nested_absolute/.hg/hgrc \
971 $ cat mercurial2/main/nested_absolute/.hg/hgrc \
972 > mercurial2/main/nested_relative/.hg/hgrc
972 > mercurial2/main/nested_relative/.hg/hgrc
973 [paths]
973 [paths]
974 default = $TESTTMP/mercurial/nested_absolute
974 default = $TESTTMP/mercurial/nested_absolute
975 [paths]
975 [paths]
976 default = $TESTTMP/mercurial/nested_relative
976 default = $TESTTMP/mercurial/nested_relative
977 $ rm -rf mercurial mercurial2
977 $ rm -rf mercurial mercurial2
978
978
979 Issue1977: multirepo push should fail if subrepo push fails
979 Issue1977: multirepo push should fail if subrepo push fails
980
980
981 $ hg init repo
981 $ hg init repo
982 $ hg init repo/s
982 $ hg init repo/s
983 $ echo a > repo/s/a
983 $ echo a > repo/s/a
984 $ hg -R repo/s ci -Am0
984 $ hg -R repo/s ci -Am0
985 adding a
985 adding a
986 $ echo s = s > repo/.hgsub
986 $ echo s = s > repo/.hgsub
987 $ hg -R repo ci -Am1
987 $ hg -R repo ci -Am1
988 adding .hgsub
988 adding .hgsub
989 $ hg clone repo repo2
989 $ hg clone repo repo2
990 updating to branch default
990 updating to branch default
991 cloning subrepo s from $TESTTMP/repo/s
991 cloning subrepo s from $TESTTMP/repo/s
992 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
992 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
993 $ hg -q -R repo2 pull -u
993 $ hg -q -R repo2 pull -u
994 $ echo 1 > repo2/s/a
994 $ echo 1 > repo2/s/a
995 $ hg -R repo2/s ci -m2
995 $ hg -R repo2/s ci -m2
996 $ hg -q -R repo2/s push
996 $ hg -q -R repo2/s push
997 $ hg -R repo2/s up -C 0
997 $ hg -R repo2/s up -C 0
998 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
998 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
999 $ echo 2 > repo2/s/b
999 $ echo 2 > repo2/s/b
1000 $ hg -R repo2/s ci -m3 -A
1000 $ hg -R repo2/s ci -m3 -A
1001 adding b
1001 adding b
1002 created new head
1002 created new head
1003 $ hg -R repo2 ci -m3
1003 $ hg -R repo2 ci -m3
1004 $ hg -q -R repo2 push
1004 $ hg -q -R repo2 push
1005 abort: push creates new remote head cc505f09a8b2! (in subrepository "s")
1005 abort: push creates new remote head cc505f09a8b2! (in subrepository "s")
1006 (merge or see 'hg help push' for details about pushing new heads)
1006 (merge or see 'hg help push' for details about pushing new heads)
1007 [255]
1007 [255]
1008 $ hg -R repo update
1008 $ hg -R repo update
1009 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1009 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1010
1010
1011 test if untracked file is not overwritten
1011 test if untracked file is not overwritten
1012
1012
1013 (this also tests that updated .hgsubstate is treated as "modified",
1013 (this also tests that updated .hgsubstate is treated as "modified",
1014 when 'merge.update()' is aborted before 'merge.recordupdates()', even
1014 when 'merge.update()' is aborted before 'merge.recordupdates()', even
1015 if none of mode, size and timestamp of it isn't changed on the
1015 if none of mode, size and timestamp of it isn't changed on the
1016 filesystem (see also issue4583))
1016 filesystem (see also issue4583))
1017
1017
1018 $ echo issue3276_ok > repo/s/b
1018 $ echo issue3276_ok > repo/s/b
1019 $ hg -R repo2 push -f -q
1019 $ hg -R repo2 push -f -q
1020 $ touch -t 200001010000 repo/.hgsubstate
1020 $ touch -t 200001010000 repo/.hgsubstate
1021
1021
1022 $ cat >> repo/.hg/hgrc <<EOF
1022 $ cat >> repo/.hg/hgrc <<EOF
1023 > [fakedirstatewritetime]
1023 > [fakedirstatewritetime]
1024 > # emulate invoking dirstate.write() via repo.status()
1024 > # emulate invoking dirstate.write() via repo.status()
1025 > # at 2000-01-01 00:00
1025 > # at 2000-01-01 00:00
1026 > fakenow = 200001010000
1026 > fakenow = 200001010000
1027 >
1027 >
1028 > [extensions]
1028 > [extensions]
1029 > fakedirstatewritetime = $TESTDIR/fakedirstatewritetime.py
1029 > fakedirstatewritetime = $TESTDIR/fakedirstatewritetime.py
1030 > EOF
1030 > EOF
1031 $ hg -R repo update
1031 $ hg -R repo update
1032 b: untracked file differs
1032 b: untracked file differs
1033 abort: untracked files in working directory differ from files in requested revision (in subrepository "s")
1033 abort: untracked files in working directory differ from files in requested revision (in subrepository "s")
1034 [255]
1034 [255]
1035 $ cat >> repo/.hg/hgrc <<EOF
1035 $ cat >> repo/.hg/hgrc <<EOF
1036 > [extensions]
1036 > [extensions]
1037 > fakedirstatewritetime = !
1037 > fakedirstatewritetime = !
1038 > EOF
1038 > EOF
1039
1039
1040 $ cat repo/s/b
1040 $ cat repo/s/b
1041 issue3276_ok
1041 issue3276_ok
1042 $ rm repo/s/b
1042 $ rm repo/s/b
1043 $ touch -t 200001010000 repo/.hgsubstate
1043 $ touch -t 200001010000 repo/.hgsubstate
1044 $ hg -R repo revert --all
1044 $ hg -R repo revert --all
1045 reverting repo/.hgsubstate
1045 reverting repo/.hgsubstate
1046 reverting subrepo s
1046 reverting subrepo s
1047 $ hg -R repo update
1047 $ hg -R repo update
1048 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1048 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1049 $ cat repo/s/b
1049 $ cat repo/s/b
1050 2
1050 2
1051 $ rm -rf repo2 repo
1051 $ rm -rf repo2 repo
1052
1052
1053
1053
1054 Issue1852 subrepos with relative paths always push/pull relative to default
1054 Issue1852 subrepos with relative paths always push/pull relative to default
1055
1055
1056 Prepare a repo with subrepo
1056 Prepare a repo with subrepo
1057
1057
1058 $ hg init issue1852a
1058 $ hg init issue1852a
1059 $ cd issue1852a
1059 $ cd issue1852a
1060 $ hg init sub/repo
1060 $ hg init sub/repo
1061 $ echo test > sub/repo/foo
1061 $ echo test > sub/repo/foo
1062 $ hg -R sub/repo add sub/repo/foo
1062 $ hg -R sub/repo add sub/repo/foo
1063 $ echo sub/repo = sub/repo > .hgsub
1063 $ echo sub/repo = sub/repo > .hgsub
1064 $ hg add .hgsub
1064 $ hg add .hgsub
1065 $ hg ci -mtest
1065 $ hg ci -mtest
1066 committing subrepository sub/repo
1066 committing subrepository sub/repo
1067 $ echo test >> sub/repo/foo
1067 $ echo test >> sub/repo/foo
1068 $ hg ci -mtest
1068 $ hg ci -mtest
1069 committing subrepository sub/repo
1069 committing subrepository sub/repo
1070 $ hg cat sub/repo/foo
1070 $ hg cat sub/repo/foo
1071 test
1071 test
1072 test
1072 test
1073 $ hg cat sub/repo/foo -Tjson | sed 's|\\\\|/|g'
1073 $ hg cat sub/repo/foo -Tjson | sed 's|\\\\|/|g'
1074 [
1074 [
1075 {
1075 {
1076 "data": "test\ntest\n",
1076 "data": "test\ntest\n",
1077 "path": "foo"
1077 "path": "foo"
1078 }
1078 }
1079 ]
1079 ]
1080
1080
1081 non-exact match:
1081 non-exact match:
1082
1082
1083 $ hg cat -T '{path|relpath}\n' 'glob:**'
1083 $ hg cat -T '{path|relpath}\n' 'glob:**'
1084 .hgsub
1084 .hgsub
1085 .hgsubstate
1085 .hgsubstate
1086 sub/repo/foo
1086 sub/repo/foo
1087 $ hg cat -T '{path|relpath}\n' 're:^sub'
1087 $ hg cat -T '{path|relpath}\n' 're:^sub'
1088 sub/repo/foo
1088 sub/repo/foo
1089
1089
1090 missing subrepos in working directory:
1090 missing subrepos in working directory:
1091
1091
1092 $ mkdir -p tmp/sub/repo
1092 $ mkdir -p tmp/sub/repo
1093 $ hg cat -r 0 --output tmp/%p_p sub/repo/foo
1093 $ hg cat -r 0 --output tmp/%p_p sub/repo/foo
1094 $ cat tmp/sub/repo/foo_p
1094 $ cat tmp/sub/repo/foo_p
1095 test
1095 test
1096 $ mv sub/repo sub_
1096 $ mv sub/repo sub_
1097 $ hg cat sub/repo/baz
1097 $ hg cat sub/repo/baz
1098 skipping missing subrepository: sub/repo
1098 skipping missing subrepository: sub/repo
1099 [1]
1099 [1]
1100 $ rm -rf sub/repo
1100 $ rm -rf sub/repo
1101 $ mv sub_ sub/repo
1101 $ mv sub_ sub/repo
1102 $ cd ..
1102 $ cd ..
1103
1103
1104 Create repo without default path, pull top repo, and see what happens on update
1104 Create repo without default path, pull top repo, and see what happens on update
1105
1105
1106 $ hg init issue1852b
1106 $ hg init issue1852b
1107 $ hg -R issue1852b pull issue1852a
1107 $ hg -R issue1852b pull issue1852a
1108 pulling from issue1852a
1108 pulling from issue1852a
1109 requesting all changes
1109 requesting all changes
1110 adding changesets
1110 adding changesets
1111 adding manifests
1111 adding manifests
1112 adding file changes
1112 adding file changes
1113 added 2 changesets with 3 changes to 2 files
1113 added 2 changesets with 3 changes to 2 files
1114 new changesets 19487b456929:be5eb94e7215
1114 new changesets 19487b456929:be5eb94e7215
1115 (run 'hg update' to get a working copy)
1115 (run 'hg update' to get a working copy)
1116 $ hg -R issue1852b update
1116 $ hg -R issue1852b update
1117 abort: default path for subrepository not found (in subrepository "sub/repo")
1117 abort: default path for subrepository not found (in subrepository "sub/repo")
1118 [255]
1118 [255]
1119
1119
1120 Ensure a full traceback, not just the SubrepoAbort part
1120 Ensure a full traceback, not just the SubrepoAbort part
1121
1121
1122 $ hg -R issue1852b update --traceback 2>&1 | grep 'raise error\.Abort'
1122 $ hg -R issue1852b update --traceback 2>&1 | grep 'raise error\.Abort'
1123 raise error.Abort(_("default path for subrepository not found"))
1123 raise error.Abort(_("default path for subrepository not found"))
1124
1124
1125 Pull -u now doesn't help
1125 Pull -u now doesn't help
1126
1126
1127 $ hg -R issue1852b pull -u issue1852a
1127 $ hg -R issue1852b pull -u issue1852a
1128 pulling from issue1852a
1128 pulling from issue1852a
1129 searching for changes
1129 searching for changes
1130 no changes found
1130 no changes found
1131
1131
1132 Try the same, but with pull -u
1132 Try the same, but with pull -u
1133
1133
1134 $ hg init issue1852c
1134 $ hg init issue1852c
1135 $ hg -R issue1852c pull -r0 -u issue1852a
1135 $ hg -R issue1852c pull -r0 -u issue1852a
1136 pulling from issue1852a
1136 pulling from issue1852a
1137 adding changesets
1137 adding changesets
1138 adding manifests
1138 adding manifests
1139 adding file changes
1139 adding file changes
1140 added 1 changesets with 2 changes to 2 files
1140 added 1 changesets with 2 changes to 2 files
1141 new changesets 19487b456929
1141 new changesets 19487b456929
1142 cloning subrepo sub/repo from issue1852a/sub/repo
1142 cloning subrepo sub/repo from issue1852a/sub/repo
1143 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
1143 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
1144
1144
1145 Try to push from the other side
1145 Try to push from the other side
1146
1146
1147 $ hg -R issue1852a push `pwd`/issue1852c
1147 $ hg -R issue1852a push `pwd`/issue1852c
1148 pushing to $TESTTMP/issue1852c
1148 pushing to $TESTTMP/issue1852c
1149 pushing subrepo sub/repo to $TESTTMP/issue1852c/sub/repo
1149 pushing subrepo sub/repo to $TESTTMP/issue1852c/sub/repo
1150 searching for changes
1150 searching for changes
1151 no changes found
1151 no changes found
1152 searching for changes
1152 searching for changes
1153 adding changesets
1153 adding changesets
1154 adding manifests
1154 adding manifests
1155 adding file changes
1155 adding file changes
1156 added 1 changesets with 1 changes to 1 files
1156 added 1 changesets with 1 changes to 1 files
1157
1157
1158 Incoming and outgoing should not use the default path:
1158 Incoming and outgoing should not use the default path:
1159
1159
1160 $ hg clone -q issue1852a issue1852d
1160 $ hg clone -q issue1852a issue1852d
1161 $ hg -R issue1852d outgoing --subrepos issue1852c
1161 $ hg -R issue1852d outgoing --subrepos issue1852c
1162 comparing with issue1852c
1162 comparing with issue1852c
1163 searching for changes
1163 searching for changes
1164 no changes found
1164 no changes found
1165 comparing with issue1852c/sub/repo
1165 comparing with issue1852c/sub/repo
1166 searching for changes
1166 searching for changes
1167 no changes found
1167 no changes found
1168 [1]
1168 [1]
1169 $ hg -R issue1852d incoming --subrepos issue1852c
1169 $ hg -R issue1852d incoming --subrepos issue1852c
1170 comparing with issue1852c
1170 comparing with issue1852c
1171 searching for changes
1171 searching for changes
1172 no changes found
1172 no changes found
1173 comparing with issue1852c/sub/repo
1173 comparing with issue1852c/sub/repo
1174 searching for changes
1174 searching for changes
1175 no changes found
1175 no changes found
1176 [1]
1176 [1]
1177
1177
1178 Check that merge of a new subrepo doesn't write the uncommitted state to
1178 Check that merge of a new subrepo doesn't write the uncommitted state to
1179 .hgsubstate (issue4622)
1179 .hgsubstate (issue4622)
1180
1180
1181 $ hg init issue1852a/addedsub
1181 $ hg init issue1852a/addedsub
1182 $ echo zzz > issue1852a/addedsub/zz.txt
1182 $ echo zzz > issue1852a/addedsub/zz.txt
1183 $ hg -R issue1852a/addedsub ci -Aqm "initial ZZ"
1183 $ hg -R issue1852a/addedsub ci -Aqm "initial ZZ"
1184
1184
1185 $ hg clone issue1852a/addedsub issue1852d/addedsub
1185 $ hg clone issue1852a/addedsub issue1852d/addedsub
1186 updating to branch default
1186 updating to branch default
1187 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1187 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1188
1188
1189 $ echo def > issue1852a/sub/repo/foo
1189 $ echo def > issue1852a/sub/repo/foo
1190 $ hg -R issue1852a ci -SAm 'tweaked subrepo'
1190 $ hg -R issue1852a ci -SAm 'tweaked subrepo'
1191 adding tmp/sub/repo/foo_p
1191 adding tmp/sub/repo/foo_p
1192 committing subrepository sub/repo
1192 committing subrepository sub/repo
1193
1193
1194 $ echo 'addedsub = addedsub' >> issue1852d/.hgsub
1194 $ echo 'addedsub = addedsub' >> issue1852d/.hgsub
1195 $ echo xyz > issue1852d/sub/repo/foo
1195 $ echo xyz > issue1852d/sub/repo/foo
1196 $ hg -R issue1852d pull -u
1196 $ hg -R issue1852d pull -u
1197 pulling from $TESTTMP/issue1852a
1197 pulling from $TESTTMP/issue1852a
1198 searching for changes
1198 searching for changes
1199 adding changesets
1199 adding changesets
1200 adding manifests
1200 adding manifests
1201 adding file changes
1201 adding file changes
1202 added 1 changesets with 2 changes to 2 files
1202 added 1 changesets with 2 changes to 2 files
1203 new changesets c82b79fdcc5b
1203 new changesets c82b79fdcc5b
1204 subrepository sub/repo diverged (local revision: f42d5c7504a8, remote revision: 46cd4aac504c)
1204 subrepository sub/repo diverged (local revision: f42d5c7504a8, remote revision: 46cd4aac504c)
1205 (M)erge, keep (l)ocal [working copy] or keep (r)emote [destination]? m
1205 (M)erge, keep (l)ocal [working copy] or keep (r)emote [destination]? m
1206 pulling subrepo sub/repo from $TESTTMP/issue1852a/sub/repo
1206 pulling subrepo sub/repo from $TESTTMP/issue1852a/sub/repo
1207 searching for changes
1207 searching for changes
1208 adding changesets
1208 adding changesets
1209 adding manifests
1209 adding manifests
1210 adding file changes
1210 adding file changes
1211 added 1 changesets with 1 changes to 1 files
1211 added 1 changesets with 1 changes to 1 files
1212 new changesets 46cd4aac504c
1212 new changesets 46cd4aac504c
1213 subrepository sources for sub/repo differ
1213 subrepository sources for sub/repo differ
1214 use (l)ocal source (f42d5c7504a8) or (r)emote source (46cd4aac504c)? l
1214 use (l)ocal source (f42d5c7504a8) or (r)emote source (46cd4aac504c)? l
1215 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1215 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1216 $ cat issue1852d/.hgsubstate
1216 $ cat issue1852d/.hgsubstate
1217 f42d5c7504a811dda50f5cf3e5e16c3330b87172 sub/repo
1217 f42d5c7504a811dda50f5cf3e5e16c3330b87172 sub/repo
1218
1218
1219 Check status of files when none of them belong to the first
1219 Check status of files when none of them belong to the first
1220 subrepository:
1220 subrepository:
1221
1221
1222 $ hg init subrepo-status
1222 $ hg init subrepo-status
1223 $ cd subrepo-status
1223 $ cd subrepo-status
1224 $ hg init subrepo-1
1224 $ hg init subrepo-1
1225 $ hg init subrepo-2
1225 $ hg init subrepo-2
1226 $ cd subrepo-2
1226 $ cd subrepo-2
1227 $ touch file
1227 $ touch file
1228 $ hg add file
1228 $ hg add file
1229 $ cd ..
1229 $ cd ..
1230 $ echo subrepo-1 = subrepo-1 > .hgsub
1230 $ echo subrepo-1 = subrepo-1 > .hgsub
1231 $ echo subrepo-2 = subrepo-2 >> .hgsub
1231 $ echo subrepo-2 = subrepo-2 >> .hgsub
1232 $ hg add .hgsub
1232 $ hg add .hgsub
1233 $ hg ci -m 'Added subrepos'
1233 $ hg ci -m 'Added subrepos'
1234 committing subrepository subrepo-2
1234 committing subrepository subrepo-2
1235 $ hg st subrepo-2/file
1235 $ hg st subrepo-2/file
1236
1236
1237 Check that share works with subrepo
1237 Check that share works with subrepo
1238 $ hg --config extensions.share= share . ../shared
1238 $ hg --config extensions.share= share . ../shared
1239 updating working directory
1239 updating working directory
1240 sharing subrepo subrepo-1 from $TESTTMP/subrepo-status/subrepo-1
1240 sharing subrepo subrepo-1 from $TESTTMP/subrepo-status/subrepo-1
1241 sharing subrepo subrepo-2 from $TESTTMP/subrepo-status/subrepo-2
1241 sharing subrepo subrepo-2 from $TESTTMP/subrepo-status/subrepo-2
1242 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
1242 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
1243 $ find ../shared/* | sort
1243 $ find ../shared/* | sort
1244 ../shared/subrepo-1
1244 ../shared/subrepo-1
1245 ../shared/subrepo-1/.hg
1245 ../shared/subrepo-1/.hg
1246 ../shared/subrepo-1/.hg/cache
1246 ../shared/subrepo-1/.hg/cache
1247 ../shared/subrepo-1/.hg/cache/storehash
1247 ../shared/subrepo-1/.hg/cache/storehash
1248 ../shared/subrepo-1/.hg/cache/storehash/* (glob)
1248 ../shared/subrepo-1/.hg/cache/storehash/* (glob)
1249 ../shared/subrepo-1/.hg/hgrc
1249 ../shared/subrepo-1/.hg/hgrc
1250 ../shared/subrepo-1/.hg/requires
1250 ../shared/subrepo-1/.hg/requires
1251 ../shared/subrepo-1/.hg/sharedpath
1251 ../shared/subrepo-1/.hg/sharedpath
1252 ../shared/subrepo-1/.hg/wcache
1252 ../shared/subrepo-1/.hg/wcache
1253 ../shared/subrepo-2
1253 ../shared/subrepo-2
1254 ../shared/subrepo-2/.hg
1254 ../shared/subrepo-2/.hg
1255 ../shared/subrepo-2/.hg/branch
1255 ../shared/subrepo-2/.hg/branch
1256 ../shared/subrepo-2/.hg/cache
1256 ../shared/subrepo-2/.hg/cache
1257 ../shared/subrepo-2/.hg/cache/storehash
1257 ../shared/subrepo-2/.hg/cache/storehash
1258 ../shared/subrepo-2/.hg/cache/storehash/* (glob)
1258 ../shared/subrepo-2/.hg/cache/storehash/* (glob)
1259 ../shared/subrepo-2/.hg/dirstate
1259 ../shared/subrepo-2/.hg/dirstate
1260 ../shared/subrepo-2/.hg/hgrc
1260 ../shared/subrepo-2/.hg/hgrc
1261 ../shared/subrepo-2/.hg/requires
1261 ../shared/subrepo-2/.hg/requires
1262 ../shared/subrepo-2/.hg/sharedpath
1262 ../shared/subrepo-2/.hg/sharedpath
1263 ../shared/subrepo-2/.hg/wcache
1263 ../shared/subrepo-2/.hg/wcache
1264 ../shared/subrepo-2/.hg/wcache/checkisexec (execbit !)
1264 ../shared/subrepo-2/.hg/wcache/checkisexec (execbit !)
1265 ../shared/subrepo-2/.hg/wcache/checklink (symlink !)
1265 ../shared/subrepo-2/.hg/wcache/checklink (symlink !)
1266 ../shared/subrepo-2/.hg/wcache/checklink-target (symlink !)
1266 ../shared/subrepo-2/.hg/wcache/checklink-target (symlink !)
1267 ../shared/subrepo-2/file
1267 ../shared/subrepo-2/file
1268 $ hg -R ../shared in
1268 $ hg -R ../shared in
1269 abort: repository default not found!
1269 abort: repository default not found!
1270 [255]
1270 [255]
1271 $ hg -R ../shared/subrepo-2 showconfig paths
1271 $ hg -R ../shared/subrepo-2 showconfig paths
1272 paths.default=$TESTTMP/subrepo-status/subrepo-2
1272 paths.default=$TESTTMP/subrepo-status/subrepo-2
1273 $ hg -R ../shared/subrepo-1 sum --remote
1273 $ hg -R ../shared/subrepo-1 sum --remote
1274 parent: -1:000000000000 tip (empty repository)
1274 parent: -1:000000000000 tip (empty repository)
1275 branch: default
1275 branch: default
1276 commit: (clean)
1276 commit: (clean)
1277 update: (current)
1277 update: (current)
1278 remote: (synced)
1278 remote: (synced)
1279
1279
1280 Check hg update --clean
1280 Check hg update --clean
1281 $ cd $TESTTMP/t
1281 $ cd $TESTTMP/t
1282 $ rm -r t/t.orig
1282 $ rm -r t/t.orig
1283 $ hg status -S --all
1283 $ hg status -S --all
1284 C .hgsub
1284 C .hgsub
1285 C .hgsubstate
1285 C .hgsubstate
1286 C a
1286 C a
1287 C s/.hgsub
1287 C s/.hgsub
1288 C s/.hgsubstate
1288 C s/.hgsubstate
1289 C s/a
1289 C s/a
1290 C s/ss/a
1290 C s/ss/a
1291 C t/t
1291 C t/t
1292 $ echo c1 > s/a
1292 $ echo c1 > s/a
1293 $ cd s
1293 $ cd s
1294 $ echo c1 > b
1294 $ echo c1 > b
1295 $ echo c1 > c
1295 $ echo c1 > c
1296 $ hg add b
1296 $ hg add b
1297 $ cd ..
1297 $ cd ..
1298 $ hg status -S
1298 $ hg status -S
1299 M s/a
1299 M s/a
1300 A s/b
1300 A s/b
1301 ? s/c
1301 ? s/c
1302 $ hg update -C
1302 $ hg update -C
1303 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1303 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1304 updated to "925c17564ef8: 13"
1304 updated to "925c17564ef8: 13"
1305 2 other heads for branch "default"
1305 2 other heads for branch "default"
1306 $ hg status -S
1306 $ hg status -S
1307 ? s/b
1307 ? s/b
1308 ? s/c
1308 ? s/c
1309
1309
1310 Sticky subrepositories, no changes
1310 Sticky subrepositories, no changes
1311 $ cd $TESTTMP/t
1311 $ cd $TESTTMP/t
1312 $ hg id
1312 $ hg id
1313 925c17564ef8 tip
1313 925c17564ef8 tip
1314 $ hg -R s id
1314 $ hg -R s id
1315 12a213df6fa9 tip
1315 12a213df6fa9 tip
1316 $ hg -R t id
1316 $ hg -R t id
1317 52c0adc0515a tip
1317 52c0adc0515a tip
1318 $ hg update 11
1318 $ hg update 11
1319 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1319 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1320 $ hg id
1320 $ hg id
1321 365661e5936a
1321 365661e5936a
1322 $ hg -R s id
1322 $ hg -R s id
1323 fc627a69481f
1323 fc627a69481f
1324 $ hg -R t id
1324 $ hg -R t id
1325 e95bcfa18a35
1325 e95bcfa18a35
1326
1326
1327 Sticky subrepositories, file changes
1327 Sticky subrepositories, file changes
1328 $ touch s/f1
1328 $ touch s/f1
1329 $ touch t/f1
1329 $ touch t/f1
1330 $ hg add -S s/f1
1330 $ hg add -S s/f1
1331 $ hg add -S t/f1
1331 $ hg add -S t/f1
1332 $ hg id
1332 $ hg id
1333 365661e5936a+
1333 365661e5936a+
1334 $ hg -R s id
1334 $ hg -R s id
1335 fc627a69481f+
1335 fc627a69481f+
1336 $ hg -R t id
1336 $ hg -R t id
1337 e95bcfa18a35+
1337 e95bcfa18a35+
1338 $ hg update tip
1338 $ hg update tip
1339 subrepository s diverged (local revision: fc627a69481f, remote revision: 12a213df6fa9)
1339 subrepository s diverged (local revision: fc627a69481f, remote revision: 12a213df6fa9)
1340 (M)erge, keep (l)ocal [working copy] or keep (r)emote [destination]? m
1340 (M)erge, keep (l)ocal [working copy] or keep (r)emote [destination]? m
1341 subrepository sources for s differ
1341 subrepository sources for s differ
1342 use (l)ocal source (fc627a69481f) or (r)emote source (12a213df6fa9)? l
1342 use (l)ocal source (fc627a69481f) or (r)emote source (12a213df6fa9)? l
1343 subrepository t diverged (local revision: e95bcfa18a35, remote revision: 52c0adc0515a)
1343 subrepository t diverged (local revision: e95bcfa18a35, remote revision: 52c0adc0515a)
1344 (M)erge, keep (l)ocal [working copy] or keep (r)emote [destination]? m
1344 (M)erge, keep (l)ocal [working copy] or keep (r)emote [destination]? m
1345 subrepository sources for t differ
1345 subrepository sources for t differ
1346 use (l)ocal source (e95bcfa18a35) or (r)emote source (52c0adc0515a)? l
1346 use (l)ocal source (e95bcfa18a35) or (r)emote source (52c0adc0515a)? l
1347 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1347 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1348 $ hg id
1348 $ hg id
1349 925c17564ef8+ tip
1349 925c17564ef8+ tip
1350 $ hg -R s id
1350 $ hg -R s id
1351 fc627a69481f+
1351 fc627a69481f+
1352 $ hg -R t id
1352 $ hg -R t id
1353 e95bcfa18a35+
1353 e95bcfa18a35+
1354 $ hg update --clean tip
1354 $ hg update --clean tip
1355 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1355 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1356
1356
1357 Sticky subrepository, revision updates
1357 Sticky subrepository, revision updates
1358 $ hg id
1358 $ hg id
1359 925c17564ef8 tip
1359 925c17564ef8 tip
1360 $ hg -R s id
1360 $ hg -R s id
1361 12a213df6fa9 tip
1361 12a213df6fa9 tip
1362 $ hg -R t id
1362 $ hg -R t id
1363 52c0adc0515a tip
1363 52c0adc0515a tip
1364 $ cd s
1364 $ cd s
1365 $ hg update -r -2
1365 $ hg update -r -2
1366 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1366 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1367 $ cd ../t
1367 $ cd ../t
1368 $ hg update -r 2
1368 $ hg update -r 2
1369 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1369 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1370 $ cd ..
1370 $ cd ..
1371 $ hg update 10
1371 $ hg update 10
1372 subrepository s diverged (local revision: 12a213df6fa9, remote revision: fc627a69481f)
1372 subrepository s diverged (local revision: 12a213df6fa9, remote revision: fc627a69481f)
1373 (M)erge, keep (l)ocal [working copy] or keep (r)emote [destination]? m
1373 (M)erge, keep (l)ocal [working copy] or keep (r)emote [destination]? m
1374 subrepository t diverged (local revision: 52c0adc0515a, remote revision: 20a0db6fbf6c)
1374 subrepository t diverged (local revision: 52c0adc0515a, remote revision: 20a0db6fbf6c)
1375 (M)erge, keep (l)ocal [working copy] or keep (r)emote [destination]? m
1375 (M)erge, keep (l)ocal [working copy] or keep (r)emote [destination]? m
1376 subrepository sources for t differ (in checked out version)
1376 subrepository sources for t differ (in checked out version)
1377 use (l)ocal source (7af322bc1198) or (r)emote source (20a0db6fbf6c)? l
1377 use (l)ocal source (7af322bc1198) or (r)emote source (20a0db6fbf6c)? l
1378 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1378 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1379 $ hg id
1379 $ hg id
1380 e45c8b14af55+
1380 e45c8b14af55+
1381 $ hg -R s id
1381 $ hg -R s id
1382 02dcf1d70411
1382 02dcf1d70411
1383 $ hg -R t id
1383 $ hg -R t id
1384 7af322bc1198
1384 7af322bc1198
1385
1385
1386 Sticky subrepository, file changes and revision updates
1386 Sticky subrepository, file changes and revision updates
1387 $ touch s/f1
1387 $ touch s/f1
1388 $ touch t/f1
1388 $ touch t/f1
1389 $ hg add -S s/f1
1389 $ hg add -S s/f1
1390 $ hg add -S t/f1
1390 $ hg add -S t/f1
1391 $ hg id
1391 $ hg id
1392 e45c8b14af55+
1392 e45c8b14af55+
1393 $ hg -R s id
1393 $ hg -R s id
1394 02dcf1d70411+
1394 02dcf1d70411+
1395 $ hg -R t id
1395 $ hg -R t id
1396 7af322bc1198+
1396 7af322bc1198+
1397 $ hg update tip
1397 $ hg update tip
1398 subrepository s diverged (local revision: 12a213df6fa9, remote revision: 12a213df6fa9)
1398 subrepository s diverged (local revision: 12a213df6fa9, remote revision: 12a213df6fa9)
1399 (M)erge, keep (l)ocal [working copy] or keep (r)emote [destination]? m
1399 (M)erge, keep (l)ocal [working copy] or keep (r)emote [destination]? m
1400 subrepository sources for s differ
1400 subrepository sources for s differ
1401 use (l)ocal source (02dcf1d70411) or (r)emote source (12a213df6fa9)? l
1401 use (l)ocal source (02dcf1d70411) or (r)emote source (12a213df6fa9)? l
1402 subrepository t diverged (local revision: 52c0adc0515a, remote revision: 52c0adc0515a)
1402 subrepository t diverged (local revision: 52c0adc0515a, remote revision: 52c0adc0515a)
1403 (M)erge, keep (l)ocal [working copy] or keep (r)emote [destination]? m
1403 (M)erge, keep (l)ocal [working copy] or keep (r)emote [destination]? m
1404 subrepository sources for t differ
1404 subrepository sources for t differ
1405 use (l)ocal source (7af322bc1198) or (r)emote source (52c0adc0515a)? l
1405 use (l)ocal source (7af322bc1198) or (r)emote source (52c0adc0515a)? l
1406 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1406 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1407 $ hg id
1407 $ hg id
1408 925c17564ef8+ tip
1408 925c17564ef8+ tip
1409 $ hg -R s id
1409 $ hg -R s id
1410 02dcf1d70411+
1410 02dcf1d70411+
1411 $ hg -R t id
1411 $ hg -R t id
1412 7af322bc1198+
1412 7af322bc1198+
1413
1413
1414 Sticky repository, update --clean
1414 Sticky repository, update --clean
1415 $ hg update --clean tip
1415 $ hg update --clean tip
1416 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1416 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1417 $ hg id
1417 $ hg id
1418 925c17564ef8 tip
1418 925c17564ef8 tip
1419 $ hg -R s id
1419 $ hg -R s id
1420 12a213df6fa9 tip
1420 12a213df6fa9 tip
1421 $ hg -R t id
1421 $ hg -R t id
1422 52c0adc0515a tip
1422 52c0adc0515a tip
1423
1423
1424 Test subrepo already at intended revision:
1424 Test subrepo already at intended revision:
1425 $ cd s
1425 $ cd s
1426 $ hg update fc627a69481f
1426 $ hg update fc627a69481f
1427 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1427 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1428 $ cd ..
1428 $ cd ..
1429 $ hg update 11
1429 $ hg update 11
1430 subrepository s diverged (local revision: 12a213df6fa9, remote revision: fc627a69481f)
1430 subrepository s diverged (local revision: 12a213df6fa9, remote revision: fc627a69481f)
1431 (M)erge, keep (l)ocal [working copy] or keep (r)emote [destination]? m
1431 (M)erge, keep (l)ocal [working copy] or keep (r)emote [destination]? m
1432 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1432 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1433 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1433 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1434 $ hg id -n
1434 $ hg id -n
1435 11+
1435 11+
1436 $ hg -R s id
1436 $ hg -R s id
1437 fc627a69481f
1437 fc627a69481f
1438 $ hg -R t id
1438 $ hg -R t id
1439 e95bcfa18a35
1439 e95bcfa18a35
1440
1440
1441 Test that removing .hgsubstate doesn't break anything:
1441 Test that removing .hgsubstate doesn't break anything:
1442
1442
1443 $ hg rm -f .hgsubstate
1443 $ hg rm -f .hgsubstate
1444 $ hg ci -mrm
1444 $ hg ci -mrm
1445 nothing changed
1445 nothing changed
1446 [1]
1446 [1]
1447 $ hg log -vr tip
1447 $ hg log -vr tip
1448 changeset: 13:925c17564ef8
1448 changeset: 13:925c17564ef8
1449 tag: tip
1449 tag: tip
1450 user: test
1450 user: test
1451 date: Thu Jan 01 00:00:00 1970 +0000
1451 date: Thu Jan 01 00:00:00 1970 +0000
1452 files: .hgsubstate
1452 files: .hgsubstate
1453 description:
1453 description:
1454 13
1454 13
1455
1455
1456
1456
1457
1457
1458 Test that removing .hgsub removes .hgsubstate:
1458 Test that removing .hgsub removes .hgsubstate:
1459
1459
1460 $ hg rm .hgsub
1460 $ hg rm .hgsub
1461 $ hg ci -mrm2
1461 $ hg ci -mrm2
1462 created new head
1462 created new head
1463 $ hg log -vr tip
1463 $ hg log -vr tip
1464 changeset: 14:2400bccd50af
1464 changeset: 14:2400bccd50af
1465 tag: tip
1465 tag: tip
1466 parent: 11:365661e5936a
1466 parent: 11:365661e5936a
1467 user: test
1467 user: test
1468 date: Thu Jan 01 00:00:00 1970 +0000
1468 date: Thu Jan 01 00:00:00 1970 +0000
1469 files: .hgsub .hgsubstate
1469 files: .hgsub .hgsubstate
1470 description:
1470 description:
1471 rm2
1471 rm2
1472
1472
1473
1473
1474 Test issue3153: diff -S with deleted subrepos
1474 Test issue3153: diff -S with deleted subrepos
1475
1475
1476 $ hg diff --nodates -S -c .
1476 $ hg diff --nodates -S -c .
1477 diff -r 365661e5936a -r 2400bccd50af .hgsub
1477 diff -r 365661e5936a -r 2400bccd50af .hgsub
1478 --- a/.hgsub
1478 --- a/.hgsub
1479 +++ /dev/null
1479 +++ /dev/null
1480 @@ -1,2 +0,0 @@
1480 @@ -1,2 +0,0 @@
1481 -s = s
1481 -s = s
1482 -t = t
1482 -t = t
1483 diff -r 365661e5936a -r 2400bccd50af .hgsubstate
1483 diff -r 365661e5936a -r 2400bccd50af .hgsubstate
1484 --- a/.hgsubstate
1484 --- a/.hgsubstate
1485 +++ /dev/null
1485 +++ /dev/null
1486 @@ -1,2 +0,0 @@
1486 @@ -1,2 +0,0 @@
1487 -fc627a69481fcbe5f1135069e8a3881c023e4cf5 s
1487 -fc627a69481fcbe5f1135069e8a3881c023e4cf5 s
1488 -e95bcfa18a358dc4936da981ebf4147b4cad1362 t
1488 -e95bcfa18a358dc4936da981ebf4147b4cad1362 t
1489
1489
1490 Test behavior of add for explicit path in subrepo:
1490 Test behavior of add for explicit path in subrepo:
1491 $ cd ..
1491 $ cd ..
1492 $ hg init explicit
1492 $ hg init explicit
1493 $ cd explicit
1493 $ cd explicit
1494 $ echo s = s > .hgsub
1494 $ echo s = s > .hgsub
1495 $ hg add .hgsub
1495 $ hg add .hgsub
1496 $ hg init s
1496 $ hg init s
1497 $ hg ci -m0
1497 $ hg ci -m0
1498 Adding with an explicit path in a subrepo adds the file
1498 Adding with an explicit path in a subrepo adds the file
1499 $ echo c1 > f1
1499 $ echo c1 > f1
1500 $ echo c2 > s/f2
1500 $ echo c2 > s/f2
1501 $ hg st -S
1501 $ hg st -S
1502 ? f1
1502 ? f1
1503 ? s/f2
1503 ? s/f2
1504 $ hg add s/f2
1504 $ hg add s/f2
1505 $ hg st -S
1505 $ hg st -S
1506 A s/f2
1506 A s/f2
1507 ? f1
1507 ? f1
1508 $ hg ci -R s -m0
1508 $ hg ci -R s -m0
1509 $ hg ci -Am1
1509 $ hg ci -Am1
1510 adding f1
1510 adding f1
1511 Adding with an explicit path in a subrepo with -S has the same behavior
1511 Adding with an explicit path in a subrepo with -S has the same behavior
1512 $ echo c3 > f3
1512 $ echo c3 > f3
1513 $ echo c4 > s/f4
1513 $ echo c4 > s/f4
1514 $ hg st -S
1514 $ hg st -S
1515 ? f3
1515 ? f3
1516 ? s/f4
1516 ? s/f4
1517 $ hg add -S s/f4
1517 $ hg add -S s/f4
1518 $ hg st -S
1518 $ hg st -S
1519 A s/f4
1519 A s/f4
1520 ? f3
1520 ? f3
1521 $ hg ci -R s -m1
1521 $ hg ci -R s -m1
1522 $ hg ci -Ama2
1522 $ hg ci -Ama2
1523 adding f3
1523 adding f3
1524 Adding without a path or pattern silently ignores subrepos
1524 Adding without a path or pattern silently ignores subrepos
1525 $ echo c5 > f5
1525 $ echo c5 > f5
1526 $ echo c6 > s/f6
1526 $ echo c6 > s/f6
1527 $ echo c7 > s/f7
1527 $ echo c7 > s/f7
1528 $ hg st -S
1528 $ hg st -S
1529 ? f5
1529 ? f5
1530 ? s/f6
1530 ? s/f6
1531 ? s/f7
1531 ? s/f7
1532 $ hg add
1532 $ hg add
1533 adding f5
1533 adding f5
1534 $ hg st -S
1534 $ hg st -S
1535 A f5
1535 A f5
1536 ? s/f6
1536 ? s/f6
1537 ? s/f7
1537 ? s/f7
1538 $ hg ci -R s -Am2
1538 $ hg ci -R s -Am2
1539 adding f6
1539 adding f6
1540 adding f7
1540 adding f7
1541 $ hg ci -m3
1541 $ hg ci -m3
1542 Adding without a path or pattern with -S also adds files in subrepos
1542 Adding without a path or pattern with -S also adds files in subrepos
1543 $ echo c8 > f8
1543 $ echo c8 > f8
1544 $ echo c9 > s/f9
1544 $ echo c9 > s/f9
1545 $ echo c10 > s/f10
1545 $ echo c10 > s/f10
1546 $ hg st -S
1546 $ hg st -S
1547 ? f8
1547 ? f8
1548 ? s/f10
1548 ? s/f10
1549 ? s/f9
1549 ? s/f9
1550 $ hg add -S
1550 $ hg add -S
1551 adding f8
1551 adding f8
1552 adding s/f10
1552 adding s/f10
1553 adding s/f9
1553 adding s/f9
1554 $ hg st -S
1554 $ hg st -S
1555 A f8
1555 A f8
1556 A s/f10
1556 A s/f10
1557 A s/f9
1557 A s/f9
1558 $ hg ci -R s -m3
1558 $ hg ci -R s -m3
1559 $ hg ci -m4
1559 $ hg ci -m4
1560 Adding with a pattern silently ignores subrepos
1560 Adding with a pattern silently ignores subrepos
1561 $ echo c11 > fm11
1561 $ echo c11 > fm11
1562 $ echo c12 > fn12
1562 $ echo c12 > fn12
1563 $ echo c13 > s/fm13
1563 $ echo c13 > s/fm13
1564 $ echo c14 > s/fn14
1564 $ echo c14 > s/fn14
1565 $ hg st -S
1565 $ hg st -S
1566 ? fm11
1566 ? fm11
1567 ? fn12
1567 ? fn12
1568 ? s/fm13
1568 ? s/fm13
1569 ? s/fn14
1569 ? s/fn14
1570 $ hg add 'glob:**fm*'
1570 $ hg add 'glob:**fm*'
1571 adding fm11
1571 adding fm11
1572 $ hg st -S
1572 $ hg st -S
1573 A fm11
1573 A fm11
1574 ? fn12
1574 ? fn12
1575 ? s/fm13
1575 ? s/fm13
1576 ? s/fn14
1576 ? s/fn14
1577 $ hg ci -R s -Am4
1577 $ hg ci -R s -Am4
1578 adding fm13
1578 adding fm13
1579 adding fn14
1579 adding fn14
1580 $ hg ci -Am5
1580 $ hg ci -Am5
1581 adding fn12
1581 adding fn12
1582 Adding with a pattern with -S also adds matches in subrepos
1582 Adding with a pattern with -S also adds matches in subrepos
1583 $ echo c15 > fm15
1583 $ echo c15 > fm15
1584 $ echo c16 > fn16
1584 $ echo c16 > fn16
1585 $ echo c17 > s/fm17
1585 $ echo c17 > s/fm17
1586 $ echo c18 > s/fn18
1586 $ echo c18 > s/fn18
1587 $ hg st -S
1587 $ hg st -S
1588 ? fm15
1588 ? fm15
1589 ? fn16
1589 ? fn16
1590 ? s/fm17
1590 ? s/fm17
1591 ? s/fn18
1591 ? s/fn18
1592 $ hg add -S 'glob:**fm*'
1592 $ hg add -S 'glob:**fm*'
1593 adding fm15
1593 adding fm15
1594 adding s/fm17
1594 adding s/fm17
1595 $ hg st -S
1595 $ hg st -S
1596 A fm15
1596 A fm15
1597 A s/fm17
1597 A s/fm17
1598 ? fn16
1598 ? fn16
1599 ? s/fn18
1599 ? s/fn18
1600 $ hg ci -R s -Am5
1600 $ hg ci -R s -Am5
1601 adding fn18
1601 adding fn18
1602 $ hg ci -Am6
1602 $ hg ci -Am6
1603 adding fn16
1603 adding fn16
1604
1604
1605 Test behavior of forget for explicit path in subrepo:
1605 Test behavior of forget for explicit path in subrepo:
1606 Forgetting an explicit path in a subrepo untracks the file
1606 Forgetting an explicit path in a subrepo untracks the file
1607 $ echo c19 > s/f19
1607 $ echo c19 > s/f19
1608 $ hg add s/f19
1608 $ hg add s/f19
1609 $ hg st -S
1609 $ hg st -S
1610 A s/f19
1610 A s/f19
1611 $ hg forget s/f19
1611 $ hg forget s/f19
1612 $ hg st -S
1612 $ hg st -S
1613 ? s/f19
1613 ? s/f19
1614 $ rm s/f19
1614 $ rm s/f19
1615 $ cd ..
1615 $ cd ..
1616
1616
1617 Courtesy phases synchronisation to publishing server does not block the push
1617 Courtesy phases synchronisation to publishing server does not block the push
1618 (issue3781)
1618 (issue3781)
1619
1619
1620 $ cp -R main issue3781
1620 $ cp -R main issue3781
1621 $ cp -R main issue3781-dest
1621 $ cp -R main issue3781-dest
1622 $ cd issue3781-dest/s
1622 $ cd issue3781-dest/s
1623 $ hg phase tip # show we have draft changeset
1623 $ hg phase tip # show we have draft changeset
1624 5: draft
1624 5: draft
1625 $ chmod a-w .hg/store/phaseroots # prevent phase push
1625 $ chmod a-w .hg/store/phaseroots # prevent phase push
1626 $ cd ../../issue3781
1626 $ cd ../../issue3781
1627 $ cat >> .hg/hgrc << EOF
1627 $ cat >> .hg/hgrc << EOF
1628 > [paths]
1628 > [paths]
1629 > default=../issue3781-dest/
1629 > default=../issue3781-dest/
1630 > EOF
1630 > EOF
1631 $ hg push --config devel.legacy.exchange=bundle1
1631 $ hg push --config devel.legacy.exchange=bundle1
1632 pushing to $TESTTMP/issue3781-dest
1632 pushing to $TESTTMP/issue3781-dest
1633 pushing subrepo s to $TESTTMP/issue3781-dest/s
1633 pushing subrepo s to $TESTTMP/issue3781-dest/s
1634 searching for changes
1634 searching for changes
1635 no changes found
1635 no changes found
1636 searching for changes
1636 searching for changes
1637 no changes found
1637 no changes found
1638 [1]
1638 [1]
1639 # clean the push cache
1639 # clean the push cache
1640 $ rm s/.hg/cache/storehash/*
1640 $ rm s/.hg/cache/storehash/*
1641 $ hg push # bundle2+
1641 $ hg push # bundle2+
1642 pushing to $TESTTMP/issue3781-dest
1642 pushing to $TESTTMP/issue3781-dest
1643 pushing subrepo s to $TESTTMP/issue3781-dest/s
1643 pushing subrepo s to $TESTTMP/issue3781-dest/s
1644 searching for changes
1644 searching for changes
1645 no changes found
1645 no changes found
1646 searching for changes
1646 searching for changes
1647 no changes found
1647 no changes found
1648 [1]
1648 [1]
1649 $ cd ..
1649 $ cd ..
1650
1650
1651 Test phase choice for newly created commit with "phases.subrepochecks"
1651 Test phase choice for newly created commit with "phases.subrepochecks"
1652 configuration
1652 configuration
1653
1653
1654 $ cd t
1654 $ cd t
1655 $ hg update -q -r 12
1655 $ hg update -q -r 12
1656
1656
1657 $ cat >> s/ss/.hg/hgrc <<EOF
1657 $ cat >> s/ss/.hg/hgrc <<EOF
1658 > [phases]
1658 > [phases]
1659 > new-commit = secret
1659 > new-commit = secret
1660 > EOF
1660 > EOF
1661 $ cat >> s/.hg/hgrc <<EOF
1661 $ cat >> s/.hg/hgrc <<EOF
1662 > [phases]
1662 > [phases]
1663 > new-commit = draft
1663 > new-commit = draft
1664 > EOF
1664 > EOF
1665 $ echo phasecheck1 >> s/ss/a
1665 $ echo phasecheck1 >> s/ss/a
1666 $ hg -R s commit -S --config phases.checksubrepos=abort -m phasecheck1
1666 $ hg -R s commit -S --config phases.checksubrepos=abort -m phasecheck1
1667 committing subrepository ss
1667 committing subrepository ss
1668 transaction abort!
1668 transaction abort!
1669 rollback completed
1669 rollback completed
1670 abort: can't commit in draft phase conflicting secret from subrepository ss
1670 abort: can't commit in draft phase conflicting secret from subrepository ss
1671 [255]
1671 [255]
1672 $ echo phasecheck2 >> s/ss/a
1672 $ echo phasecheck2 >> s/ss/a
1673 $ hg -R s commit -S --config phases.checksubrepos=ignore -m phasecheck2
1673 $ hg -R s commit -S --config phases.checksubrepos=ignore -m phasecheck2
1674 committing subrepository ss
1674 committing subrepository ss
1675 $ hg -R s/ss phase tip
1675 $ hg -R s/ss phase tip
1676 3: secret
1676 3: secret
1677 $ hg -R s phase tip
1677 $ hg -R s phase tip
1678 6: draft
1678 6: draft
1679 $ echo phasecheck3 >> s/ss/a
1679 $ echo phasecheck3 >> s/ss/a
1680 $ hg -R s commit -S -m phasecheck3
1680 $ hg -R s commit -S -m phasecheck3
1681 committing subrepository ss
1681 committing subrepository ss
1682 warning: changes are committed in secret phase from subrepository ss
1682 warning: changes are committed in secret phase from subrepository ss
1683 $ hg -R s/ss phase tip
1683 $ hg -R s/ss phase tip
1684 4: secret
1684 4: secret
1685 $ hg -R s phase tip
1685 $ hg -R s phase tip
1686 7: secret
1686 7: secret
1687
1687
1688 $ cat >> t/.hg/hgrc <<EOF
1688 $ cat >> t/.hg/hgrc <<EOF
1689 > [phases]
1689 > [phases]
1690 > new-commit = draft
1690 > new-commit = draft
1691 > EOF
1691 > EOF
1692 $ cat >> .hg/hgrc <<EOF
1692 $ cat >> .hg/hgrc <<EOF
1693 > [phases]
1693 > [phases]
1694 > new-commit = public
1694 > new-commit = public
1695 > EOF
1695 > EOF
1696 $ echo phasecheck4 >> s/ss/a
1696 $ echo phasecheck4 >> s/ss/a
1697 $ echo phasecheck4 >> t/t
1697 $ echo phasecheck4 >> t/t
1698 $ hg commit -S -m phasecheck4
1698 $ hg commit -S -m phasecheck4
1699 committing subrepository s
1699 committing subrepository s
1700 committing subrepository s/ss
1700 committing subrepository s/ss
1701 warning: changes are committed in secret phase from subrepository ss
1701 warning: changes are committed in secret phase from subrepository ss
1702 committing subrepository t
1702 committing subrepository t
1703 warning: changes are committed in secret phase from subrepository s
1703 warning: changes are committed in secret phase from subrepository s
1704 created new head
1704 created new head
1705 $ hg -R s/ss phase tip
1705 $ hg -R s/ss phase tip
1706 5: secret
1706 5: secret
1707 $ hg -R s phase tip
1707 $ hg -R s phase tip
1708 8: secret
1708 8: secret
1709 $ hg -R t phase tip
1709 $ hg -R t phase tip
1710 6: draft
1710 6: draft
1711 $ hg phase tip
1711 $ hg phase tip
1712 15: secret
1712 15: secret
1713
1713
1714 $ cd ..
1714 $ cd ..
1715
1715
1716
1716
1717 Test that commit --secret works on both repo and subrepo (issue4182)
1717 Test that commit --secret works on both repo and subrepo (issue4182)
1718
1718
1719 $ cd main
1719 $ cd main
1720 $ echo secret >> b
1720 $ echo secret >> b
1721 $ echo secret >> s/b
1721 $ echo secret >> s/b
1722 $ hg commit --secret --subrepo -m "secret"
1722 $ hg commit --secret --subrepo -m "secret"
1723 committing subrepository s
1723 committing subrepository s
1724 $ hg phase -r .
1724 $ hg phase -r .
1725 6: secret
1725 6: secret
1726 $ cd s
1726 $ cd s
1727 $ hg phase -r .
1727 $ hg phase -r .
1728 6: secret
1728 6: secret
1729 $ cd ../../
1729 $ cd ../../
1730
1730
1731 Test "subrepos" template keyword
1731 Test "subrepos" template keyword
1732
1732
1733 $ cd t
1733 $ cd t
1734 $ hg update -q 15
1734 $ hg update -q 15
1735 $ cat > .hgsub <<EOF
1735 $ cat > .hgsub <<EOF
1736 > s = s
1736 > s = s
1737 > EOF
1737 > EOF
1738 $ hg commit -m "16"
1738 $ hg commit -m "16"
1739 warning: changes are committed in secret phase from subrepository s
1739 warning: changes are committed in secret phase from subrepository s
1740
1740
1741 (addition of ".hgsub" itself)
1741 (addition of ".hgsub" itself)
1742
1742
1743 $ hg diff --nodates -c 1 .hgsubstate
1743 $ hg diff --nodates -c 1 .hgsubstate
1744 diff -r f7b1eb17ad24 -r 7cf8cfea66e4 .hgsubstate
1744 diff -r f7b1eb17ad24 -r 7cf8cfea66e4 .hgsubstate
1745 --- /dev/null
1745 --- /dev/null
1746 +++ b/.hgsubstate
1746 +++ b/.hgsubstate
1747 @@ -0,0 +1,1 @@
1747 @@ -0,0 +1,1 @@
1748 +e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
1748 +e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
1749 $ hg log -r 1 --template "{p1node|short} {p2node|short}\n{subrepos % '{subrepo}\n'}"
1749 $ hg log -r 1 --template "{p1node|short} {p2node|short}\n{subrepos % '{subrepo}\n'}"
1750 f7b1eb17ad24 000000000000
1750 f7b1eb17ad24 000000000000
1751 s
1751 s
1752
1752
1753 (modification of existing entry)
1753 (modification of existing entry)
1754
1754
1755 $ hg diff --nodates -c 2 .hgsubstate
1755 $ hg diff --nodates -c 2 .hgsubstate
1756 diff -r 7cf8cfea66e4 -r df30734270ae .hgsubstate
1756 diff -r 7cf8cfea66e4 -r df30734270ae .hgsubstate
1757 --- a/.hgsubstate
1757 --- a/.hgsubstate
1758 +++ b/.hgsubstate
1758 +++ b/.hgsubstate
1759 @@ -1,1 +1,1 @@
1759 @@ -1,1 +1,1 @@
1760 -e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
1760 -e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
1761 +dc73e2e6d2675eb2e41e33c205f4bdab4ea5111d s
1761 +dc73e2e6d2675eb2e41e33c205f4bdab4ea5111d s
1762 $ hg log -r 2 --template "{p1node|short} {p2node|short}\n{subrepos % '{subrepo}\n'}"
1762 $ hg log -r 2 --template "{p1node|short} {p2node|short}\n{subrepos % '{subrepo}\n'}"
1763 7cf8cfea66e4 000000000000
1763 7cf8cfea66e4 000000000000
1764 s
1764 s
1765
1765
1766 (addition of entry)
1766 (addition of entry)
1767
1767
1768 $ hg diff --nodates -c 5 .hgsubstate
1768 $ hg diff --nodates -c 5 .hgsubstate
1769 diff -r 7cf8cfea66e4 -r 1f14a2e2d3ec .hgsubstate
1769 diff -r 7cf8cfea66e4 -r 1f14a2e2d3ec .hgsubstate
1770 --- a/.hgsubstate
1770 --- a/.hgsubstate
1771 +++ b/.hgsubstate
1771 +++ b/.hgsubstate
1772 @@ -1,1 +1,2 @@
1772 @@ -1,1 +1,2 @@
1773 e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
1773 e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
1774 +60ca1237c19474e7a3978b0dc1ca4e6f36d51382 t
1774 +60ca1237c19474e7a3978b0dc1ca4e6f36d51382 t
1775 $ hg log -r 5 --template "{p1node|short} {p2node|short}\n{subrepos % '{subrepo}\n'}"
1775 $ hg log -r 5 --template "{p1node|short} {p2node|short}\n{subrepos % '{subrepo}\n'}"
1776 7cf8cfea66e4 000000000000
1776 7cf8cfea66e4 000000000000
1777 t
1777 t
1778
1778
1779 (removal of existing entry)
1779 (removal of existing entry)
1780
1780
1781 $ hg diff --nodates -c 16 .hgsubstate
1781 $ hg diff --nodates -c 16 .hgsubstate
1782 diff -r 8bec38d2bd0b -r f2f70bc3d3c9 .hgsubstate
1782 diff -r 8bec38d2bd0b -r f2f70bc3d3c9 .hgsubstate
1783 --- a/.hgsubstate
1783 --- a/.hgsubstate
1784 +++ b/.hgsubstate
1784 +++ b/.hgsubstate
1785 @@ -1,2 +1,1 @@
1785 @@ -1,2 +1,1 @@
1786 0731af8ca9423976d3743119d0865097c07bdc1b s
1786 0731af8ca9423976d3743119d0865097c07bdc1b s
1787 -e202dc79b04c88a636ea8913d9182a1346d9b3dc t
1787 -e202dc79b04c88a636ea8913d9182a1346d9b3dc t
1788 $ hg log -r 16 --template "{p1node|short} {p2node|short}\n{subrepos % '{subrepo}\n'}"
1788 $ hg log -r 16 --template "{p1node|short} {p2node|short}\n{subrepos % '{subrepo}\n'}"
1789 8bec38d2bd0b 000000000000
1789 8bec38d2bd0b 000000000000
1790 t
1790 t
1791
1791
1792 (merging)
1792 (merging)
1793
1793
1794 $ hg diff --nodates -c 9 .hgsubstate
1794 $ hg diff --nodates -c 9 .hgsubstate
1795 diff -r f6affe3fbfaa -r f0d2028bf86d .hgsubstate
1795 diff -r f6affe3fbfaa -r f0d2028bf86d .hgsubstate
1796 --- a/.hgsubstate
1796 --- a/.hgsubstate
1797 +++ b/.hgsubstate
1797 +++ b/.hgsubstate
1798 @@ -1,1 +1,2 @@
1798 @@ -1,1 +1,2 @@
1799 fc627a69481fcbe5f1135069e8a3881c023e4cf5 s
1799 fc627a69481fcbe5f1135069e8a3881c023e4cf5 s
1800 +60ca1237c19474e7a3978b0dc1ca4e6f36d51382 t
1800 +60ca1237c19474e7a3978b0dc1ca4e6f36d51382 t
1801 $ hg log -r 9 --template "{p1node|short} {p2node|short}\n{subrepos % '{subrepo}\n'}"
1801 $ hg log -r 9 --template "{p1node|short} {p2node|short}\n{subrepos % '{subrepo}\n'}"
1802 f6affe3fbfaa 1f14a2e2d3ec
1802 f6affe3fbfaa 1f14a2e2d3ec
1803 t
1803 t
1804
1804
1805 (removal of ".hgsub" itself)
1805 (removal of ".hgsub" itself)
1806
1806
1807 $ hg diff --nodates -c 8 .hgsubstate
1807 $ hg diff --nodates -c 8 .hgsubstate
1808 diff -r f94576341bcf -r 96615c1dad2d .hgsubstate
1808 diff -r f94576341bcf -r 96615c1dad2d .hgsubstate
1809 --- a/.hgsubstate
1809 --- a/.hgsubstate
1810 +++ /dev/null
1810 +++ /dev/null
1811 @@ -1,2 +0,0 @@
1811 @@ -1,2 +0,0 @@
1812 -e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
1812 -e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
1813 -7af322bc1198a32402fe903e0b7ebcfc5c9bf8f4 t
1813 -7af322bc1198a32402fe903e0b7ebcfc5c9bf8f4 t
1814 $ hg log -r 8 --template "{p1node|short} {p2node|short}\n{subrepos % '{subrepo}\n'}"
1814 $ hg log -r 8 --template "{p1node|short} {p2node|short}\n{subrepos % '{subrepo}\n'}"
1815 f94576341bcf 000000000000
1815 f94576341bcf 000000000000
1816
1816
1817 Test that '[paths]' is configured correctly at subrepo creation
1817 Test that '[paths]' is configured correctly at subrepo creation
1818
1818
1819 $ cd $TESTTMP/tc
1819 $ cd $TESTTMP/tc
1820 $ cat > .hgsub <<EOF
1820 $ cat > .hgsub <<EOF
1821 > # to clear bogus subrepo path 'bogus=[boguspath'
1821 > # to clear bogus subrepo path 'bogus=[boguspath'
1822 > s = s
1822 > s = s
1823 > t = t
1823 > t = t
1824 > EOF
1824 > EOF
1825 $ hg update -q --clean null
1825 $ hg update -q --clean null
1826 $ rm -rf s t
1826 $ rm -rf s t
1827 $ cat >> .hg/hgrc <<EOF
1827 $ cat >> .hg/hgrc <<EOF
1828 > [paths]
1828 > [paths]
1829 > default-push = /foo/bar
1829 > default-push = /foo/bar
1830 > EOF
1830 > EOF
1831 $ hg update -q
1831 $ hg update -q
1832 $ cat s/.hg/hgrc
1832 $ cat s/.hg/hgrc
1833 [paths]
1833 [paths]
1834 default = $TESTTMP/t/s
1834 default = $TESTTMP/t/s
1835 default-push = /foo/bar/s
1835 default-push = /foo/bar/s
1836 $ cat s/ss/.hg/hgrc
1836 $ cat s/ss/.hg/hgrc
1837 [paths]
1837 [paths]
1838 default = $TESTTMP/t/s/ss
1838 default = $TESTTMP/t/s/ss
1839 default-push = /foo/bar/s/ss
1839 default-push = /foo/bar/s/ss
1840 $ cat t/.hg/hgrc
1840 $ cat t/.hg/hgrc
1841 [paths]
1841 [paths]
1842 default = $TESTTMP/t/t
1842 default = $TESTTMP/t/t
1843 default-push = /foo/bar/t
1843 default-push = /foo/bar/t
1844
1844
1845 $ cd $TESTTMP/t
1845 $ cd $TESTTMP/t
1846 $ hg up -qC 0
1846 $ hg up -qC 0
1847 $ echo 'bar' > bar.txt
1847 $ echo 'bar' > bar.txt
1848 $ hg ci -Am 'branch before subrepo add'
1848 $ hg ci -Am 'branch before subrepo add'
1849 adding bar.txt
1849 adding bar.txt
1850 created new head
1850 created new head
1851 $ hg merge -r "first(subrepo('s'))"
1851 $ hg merge -r "first(subrepo('s'))"
1852 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
1852 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
1853 (branch merge, don't forget to commit)
1853 (branch merge, don't forget to commit)
1854 $ hg status -S -X '.hgsub*'
1854 $ hg status -S -X '.hgsub*'
1855 A s/a
1855 A s/a
1856 ? s/b
1856 ? s/b
1857 ? s/c
1857 ? s/c
1858 ? s/f1
1858 ? s/f1
1859 $ hg status -S --rev 'p2()'
1859 $ hg status -S --rev 'p2()'
1860 A bar.txt
1860 A bar.txt
1861 ? s/b
1861 ? s/b
1862 ? s/c
1862 ? s/c
1863 ? s/f1
1863 ? s/f1
1864 $ hg diff -S -X '.hgsub*' --nodates
1864 $ hg diff -S -X '.hgsub*' --nodates
1865 diff -r 000000000000 s/a
1865 diff -r 000000000000 s/a
1866 --- /dev/null
1866 --- /dev/null
1867 +++ b/s/a
1867 +++ b/s/a
1868 @@ -0,0 +1,1 @@
1868 @@ -0,0 +1,1 @@
1869 +a
1869 +a
1870 $ hg diff -S --rev 'p2()' --nodates
1870 $ hg diff -S --rev 'p2()' --nodates
1871 diff -r 7cf8cfea66e4 bar.txt
1871 diff -r 7cf8cfea66e4 bar.txt
1872 --- /dev/null
1872 --- /dev/null
1873 +++ b/bar.txt
1873 +++ b/bar.txt
1874 @@ -0,0 +1,1 @@
1874 @@ -0,0 +1,1 @@
1875 +bar
1875 +bar
1876
1876
1877 $ cd ..
1877 $ cd ..
1878
1878
1879 test for ssh exploit 2017-07-25
1879 test for ssh exploit 2017-07-25
1880
1880
1881 $ cat >> $HGRCPATH << EOF
1881 $ cat >> $HGRCPATH << EOF
1882 > [ui]
1882 > [ui]
1883 > ssh = sh -c "read l; read l; read l"
1883 > ssh = sh -c "read l; read l; read l"
1884 > EOF
1884 > EOF
1885
1885
1886 $ hg init malicious-proxycommand
1886 $ hg init malicious-proxycommand
1887 $ cd malicious-proxycommand
1887 $ cd malicious-proxycommand
1888 $ echo 's = [hg]ssh://-oProxyCommand=touch${IFS}owned/path' > .hgsub
1888 $ echo 's = [hg]ssh://-oProxyCommand=touch${IFS}owned/path' > .hgsub
1889 $ hg init s
1889 $ hg init s
1890 $ cd s
1890 $ cd s
1891 $ echo init > init
1891 $ echo init > init
1892 $ hg add
1892 $ hg add
1893 adding init
1893 adding init
1894 $ hg commit -m init
1894 $ hg commit -m init
1895 $ cd ..
1895 $ cd ..
1896 $ hg add .hgsub
1896 $ hg add .hgsub
1897 $ hg ci -m 'add subrepo'
1897 $ hg ci -m 'add subrepo'
1898 $ cd ..
1898 $ cd ..
1899 $ hg clone malicious-proxycommand malicious-proxycommand-clone
1899 $ hg clone malicious-proxycommand malicious-proxycommand-clone
1900 updating to branch default
1900 updating to branch default
1901 cloning subrepo s from ssh://-oProxyCommand%3Dtouch%24%7BIFS%7Downed/path
1901 cloning subrepo s from ssh://-oProxyCommand%3Dtouch%24%7BIFS%7Downed/path
1902 abort: potentially unsafe url: 'ssh://-oProxyCommand=touch${IFS}owned/path' (in subrepository "s")
1902 abort: potentially unsafe url: 'ssh://-oProxyCommand=touch${IFS}owned/path' (in subrepository "s")
1903 [255]
1903 [255]
1904
1904
1905 also check that a percent encoded '-' (%2D) doesn't work
1905 also check that a percent encoded '-' (%2D) doesn't work
1906
1906
1907 $ cd malicious-proxycommand
1907 $ cd malicious-proxycommand
1908 $ echo 's = [hg]ssh://%2DoProxyCommand=touch${IFS}owned/path' > .hgsub
1908 $ echo 's = [hg]ssh://%2DoProxyCommand=touch${IFS}owned/path' > .hgsub
1909 $ hg ci -m 'change url to percent encoded'
1909 $ hg ci -m 'change url to percent encoded'
1910 $ cd ..
1910 $ cd ..
1911 $ rm -r malicious-proxycommand-clone
1911 $ rm -r malicious-proxycommand-clone
1912 $ hg clone malicious-proxycommand malicious-proxycommand-clone
1912 $ hg clone malicious-proxycommand malicious-proxycommand-clone
1913 updating to branch default
1913 updating to branch default
1914 cloning subrepo s from ssh://-oProxyCommand%3Dtouch%24%7BIFS%7Downed/path
1914 cloning subrepo s from ssh://-oProxyCommand%3Dtouch%24%7BIFS%7Downed/path
1915 abort: potentially unsafe url: 'ssh://-oProxyCommand=touch${IFS}owned/path' (in subrepository "s")
1915 abort: potentially unsafe url: 'ssh://-oProxyCommand=touch${IFS}owned/path' (in subrepository "s")
1916 [255]
1916 [255]
1917
1917
1918 also check for a pipe
1918 also check for a pipe
1919
1919
1920 $ cd malicious-proxycommand
1920 $ cd malicious-proxycommand
1921 $ echo 's = [hg]ssh://fakehost|touch${IFS}owned/path' > .hgsub
1921 $ echo 's = [hg]ssh://fakehost|touch${IFS}owned/path' > .hgsub
1922 $ hg ci -m 'change url to pipe'
1922 $ hg ci -m 'change url to pipe'
1923 $ cd ..
1923 $ cd ..
1924 $ rm -r malicious-proxycommand-clone
1924 $ rm -r malicious-proxycommand-clone
1925 $ hg clone malicious-proxycommand malicious-proxycommand-clone
1925 $ hg clone malicious-proxycommand malicious-proxycommand-clone
1926 updating to branch default
1926 updating to branch default
1927 cloning subrepo s from ssh://fakehost%7Ctouch%24%7BIFS%7Downed/path
1927 cloning subrepo s from ssh://fakehost%7Ctouch%24%7BIFS%7Downed/path
1928 abort: no suitable response from remote hg!
1928 abort: no suitable response from remote hg!
1929 [255]
1929 [255]
1930 $ [ ! -f owned ] || echo 'you got owned'
1930 $ [ ! -f owned ] || echo 'you got owned'
1931
1931
1932 also check that a percent encoded '|' (%7C) doesn't work
1932 also check that a percent encoded '|' (%7C) doesn't work
1933
1933
1934 $ cd malicious-proxycommand
1934 $ cd malicious-proxycommand
1935 $ echo 's = [hg]ssh://fakehost%7Ctouch%20owned/path' > .hgsub
1935 $ echo 's = [hg]ssh://fakehost%7Ctouch%20owned/path' > .hgsub
1936 $ hg ci -m 'change url to percent encoded pipe'
1936 $ hg ci -m 'change url to percent encoded pipe'
1937 $ cd ..
1937 $ cd ..
1938 $ rm -r malicious-proxycommand-clone
1938 $ rm -r malicious-proxycommand-clone
1939 $ hg clone malicious-proxycommand malicious-proxycommand-clone
1939 $ hg clone malicious-proxycommand malicious-proxycommand-clone
1940 updating to branch default
1940 updating to branch default
1941 cloning subrepo s from ssh://fakehost%7Ctouch%20owned/path
1941 cloning subrepo s from ssh://fakehost%7Ctouch%20owned/path
1942 abort: no suitable response from remote hg!
1942 abort: no suitable response from remote hg!
1943 [255]
1943 [255]
1944 $ [ ! -f owned ] || echo 'you got owned'
1944 $ [ ! -f owned ] || echo 'you got owned'
1945
1945
1946 and bad usernames:
1946 and bad usernames:
1947 $ cd malicious-proxycommand
1947 $ cd malicious-proxycommand
1948 $ echo 's = [hg]ssh://-oProxyCommand=touch owned@example.com/path' > .hgsub
1948 $ echo 's = [hg]ssh://-oProxyCommand=touch owned@example.com/path' > .hgsub
1949 $ hg ci -m 'owned username'
1949 $ hg ci -m 'owned username'
1950 $ cd ..
1950 $ cd ..
1951 $ rm -r malicious-proxycommand-clone
1951 $ rm -r malicious-proxycommand-clone
1952 $ hg clone malicious-proxycommand malicious-proxycommand-clone
1952 $ hg clone malicious-proxycommand malicious-proxycommand-clone
1953 updating to branch default
1953 updating to branch default
1954 cloning subrepo s from ssh://-oProxyCommand%3Dtouch%20owned@example.com/path
1954 cloning subrepo s from ssh://-oProxyCommand%3Dtouch%20owned@example.com/path
1955 abort: potentially unsafe url: 'ssh://-oProxyCommand=touch owned@example.com/path' (in subrepository "s")
1955 abort: potentially unsafe url: 'ssh://-oProxyCommand=touch owned@example.com/path' (in subrepository "s")
1956 [255]
1956 [255]
1957
1957
1958 Test convert subrepositories including merge (issue5526):
1958 Test convert subrepositories including merge (issue5526):
1959
1959
1960 $ hg init tconv
1960 $ hg init tconv
1961 $ hg convert --config extensions.convert= -q t/s tconv/s
1961 $ hg convert --config extensions.convert= -q t/s tconv/s
1962 $ hg convert --config extensions.convert= -q t/s/ss tconv/s/ss
1962 $ hg convert --config extensions.convert= -q t/s/ss tconv/s/ss
1963 $ hg convert --config extensions.convert= -q t/t tconv/t
1963 $ hg convert --config extensions.convert= -q t/t tconv/t
1964
1964
1965 convert shouldn't fail because of pseudo filenode:
1965 convert shouldn't fail because of pseudo filenode:
1966
1966
1967 $ hg convert --config extensions.convert= t tconv
1967 $ hg convert --config extensions.convert= t tconv
1968 scanning source...
1968 scanning source...
1969 sorting...
1969 sorting...
1970 converting...
1970 converting...
1971 17 0
1971 17 0
1972 16 1
1972 16 1
1973 15 2
1973 15 2
1974 14 3
1974 14 3
1975 13 4
1975 13 4
1976 12 5
1976 12 5
1977 11 6
1977 11 6
1978 10 7
1978 10 7
1979 9 8
1979 9 8
1980 8 9
1980 8 9
1981 7 10
1981 7 10
1982 6 11
1982 6 11
1983 5 12
1983 5 12
1984 4 13
1984 4 13
1985 3 rm2
1985 3 rm2
1986 2 phasecheck4
1986 2 phasecheck4
1987 1 16
1987 1 16
1988 0 branch before subrepo add
1988 0 branch before subrepo add
1989
1989
1990 converted .hgsubstate should point to valid nodes:
1990 converted .hgsubstate should point to valid nodes:
1991
1991
1992 $ hg up -R tconv 9
1992 $ hg up -R tconv 9
1993 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
1993 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
1994 $ cat tconv/.hgsubstate
1994 $ cat tconv/.hgsubstate
1995 fc627a69481fcbe5f1135069e8a3881c023e4cf5 s
1995 fc627a69481fcbe5f1135069e8a3881c023e4cf5 s
1996 60ca1237c19474e7a3978b0dc1ca4e6f36d51382 t
1996 60ca1237c19474e7a3978b0dc1ca4e6f36d51382 t
General Comments 0
You need to be logged in to leave comments. Login now