##// END OF EJS Templates
cat: pass filename template as explicit argument...
Yuya Nishihara -
r32540:f4cd4c49 default
parent child Browse files
Show More
@@ -1,3582 +1,3582 b''
1 # cmdutil.py - help for command processing in mercurial
1 # cmdutil.py - help for command processing in mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import errno
10 import errno
11 import itertools
11 import itertools
12 import os
12 import os
13 import re
13 import re
14 import tempfile
14 import tempfile
15
15
16 from .i18n import _
16 from .i18n import _
17 from .node import (
17 from .node import (
18 bin,
18 bin,
19 hex,
19 hex,
20 nullid,
20 nullid,
21 nullrev,
21 nullrev,
22 short,
22 short,
23 )
23 )
24
24
25 from . import (
25 from . import (
26 bookmarks,
26 bookmarks,
27 changelog,
27 changelog,
28 copies,
28 copies,
29 crecord as crecordmod,
29 crecord as crecordmod,
30 encoding,
30 encoding,
31 error,
31 error,
32 formatter,
32 formatter,
33 graphmod,
33 graphmod,
34 lock as lockmod,
34 lock as lockmod,
35 match as matchmod,
35 match as matchmod,
36 obsolete,
36 obsolete,
37 patch,
37 patch,
38 pathutil,
38 pathutil,
39 phases,
39 phases,
40 pycompat,
40 pycompat,
41 registrar,
41 registrar,
42 repair,
42 repair,
43 revlog,
43 revlog,
44 revset,
44 revset,
45 scmutil,
45 scmutil,
46 smartset,
46 smartset,
47 templatekw,
47 templatekw,
48 templater,
48 templater,
49 util,
49 util,
50 vfs as vfsmod,
50 vfs as vfsmod,
51 )
51 )
52 stringio = util.stringio
52 stringio = util.stringio
53
53
54 # templates of common command options
54 # templates of common command options
55
55
56 dryrunopts = [
56 dryrunopts = [
57 ('n', 'dry-run', None,
57 ('n', 'dry-run', None,
58 _('do not perform actions, just print output')),
58 _('do not perform actions, just print output')),
59 ]
59 ]
60
60
61 remoteopts = [
61 remoteopts = [
62 ('e', 'ssh', '',
62 ('e', 'ssh', '',
63 _('specify ssh command to use'), _('CMD')),
63 _('specify ssh command to use'), _('CMD')),
64 ('', 'remotecmd', '',
64 ('', 'remotecmd', '',
65 _('specify hg command to run on the remote side'), _('CMD')),
65 _('specify hg command to run on the remote side'), _('CMD')),
66 ('', 'insecure', None,
66 ('', 'insecure', None,
67 _('do not verify server certificate (ignoring web.cacerts config)')),
67 _('do not verify server certificate (ignoring web.cacerts config)')),
68 ]
68 ]
69
69
70 walkopts = [
70 walkopts = [
71 ('I', 'include', [],
71 ('I', 'include', [],
72 _('include names matching the given patterns'), _('PATTERN')),
72 _('include names matching the given patterns'), _('PATTERN')),
73 ('X', 'exclude', [],
73 ('X', 'exclude', [],
74 _('exclude names matching the given patterns'), _('PATTERN')),
74 _('exclude names matching the given patterns'), _('PATTERN')),
75 ]
75 ]
76
76
77 commitopts = [
77 commitopts = [
78 ('m', 'message', '',
78 ('m', 'message', '',
79 _('use text as commit message'), _('TEXT')),
79 _('use text as commit message'), _('TEXT')),
80 ('l', 'logfile', '',
80 ('l', 'logfile', '',
81 _('read commit message from file'), _('FILE')),
81 _('read commit message from file'), _('FILE')),
82 ]
82 ]
83
83
84 commitopts2 = [
84 commitopts2 = [
85 ('d', 'date', '',
85 ('d', 'date', '',
86 _('record the specified date as commit date'), _('DATE')),
86 _('record the specified date as commit date'), _('DATE')),
87 ('u', 'user', '',
87 ('u', 'user', '',
88 _('record the specified user as committer'), _('USER')),
88 _('record the specified user as committer'), _('USER')),
89 ]
89 ]
90
90
91 # hidden for now
91 # hidden for now
92 formatteropts = [
92 formatteropts = [
93 ('T', 'template', '',
93 ('T', 'template', '',
94 _('display with template (EXPERIMENTAL)'), _('TEMPLATE')),
94 _('display with template (EXPERIMENTAL)'), _('TEMPLATE')),
95 ]
95 ]
96
96
97 templateopts = [
97 templateopts = [
98 ('', 'style', '',
98 ('', 'style', '',
99 _('display using template map file (DEPRECATED)'), _('STYLE')),
99 _('display using template map file (DEPRECATED)'), _('STYLE')),
100 ('T', 'template', '',
100 ('T', 'template', '',
101 _('display with template'), _('TEMPLATE')),
101 _('display with template'), _('TEMPLATE')),
102 ]
102 ]
103
103
104 logopts = [
104 logopts = [
105 ('p', 'patch', None, _('show patch')),
105 ('p', 'patch', None, _('show patch')),
106 ('g', 'git', None, _('use git extended diff format')),
106 ('g', 'git', None, _('use git extended diff format')),
107 ('l', 'limit', '',
107 ('l', 'limit', '',
108 _('limit number of changes displayed'), _('NUM')),
108 _('limit number of changes displayed'), _('NUM')),
109 ('M', 'no-merges', None, _('do not show merges')),
109 ('M', 'no-merges', None, _('do not show merges')),
110 ('', 'stat', None, _('output diffstat-style summary of changes')),
110 ('', 'stat', None, _('output diffstat-style summary of changes')),
111 ('G', 'graph', None, _("show the revision DAG")),
111 ('G', 'graph', None, _("show the revision DAG")),
112 ] + templateopts
112 ] + templateopts
113
113
114 diffopts = [
114 diffopts = [
115 ('a', 'text', None, _('treat all files as text')),
115 ('a', 'text', None, _('treat all files as text')),
116 ('g', 'git', None, _('use git extended diff format')),
116 ('g', 'git', None, _('use git extended diff format')),
117 ('', 'binary', None, _('generate binary diffs in git mode (default)')),
117 ('', 'binary', None, _('generate binary diffs in git mode (default)')),
118 ('', 'nodates', None, _('omit dates from diff headers'))
118 ('', 'nodates', None, _('omit dates from diff headers'))
119 ]
119 ]
120
120
121 diffwsopts = [
121 diffwsopts = [
122 ('w', 'ignore-all-space', None,
122 ('w', 'ignore-all-space', None,
123 _('ignore white space when comparing lines')),
123 _('ignore white space when comparing lines')),
124 ('b', 'ignore-space-change', None,
124 ('b', 'ignore-space-change', None,
125 _('ignore changes in the amount of white space')),
125 _('ignore changes in the amount of white space')),
126 ('B', 'ignore-blank-lines', None,
126 ('B', 'ignore-blank-lines', None,
127 _('ignore changes whose lines are all blank')),
127 _('ignore changes whose lines are all blank')),
128 ]
128 ]
129
129
130 diffopts2 = [
130 diffopts2 = [
131 ('', 'noprefix', None, _('omit a/ and b/ prefixes from filenames')),
131 ('', 'noprefix', None, _('omit a/ and b/ prefixes from filenames')),
132 ('p', 'show-function', None, _('show which function each change is in')),
132 ('p', 'show-function', None, _('show which function each change is in')),
133 ('', 'reverse', None, _('produce a diff that undoes the changes')),
133 ('', 'reverse', None, _('produce a diff that undoes the changes')),
134 ] + diffwsopts + [
134 ] + diffwsopts + [
135 ('U', 'unified', '',
135 ('U', 'unified', '',
136 _('number of lines of context to show'), _('NUM')),
136 _('number of lines of context to show'), _('NUM')),
137 ('', 'stat', None, _('output diffstat-style summary of changes')),
137 ('', 'stat', None, _('output diffstat-style summary of changes')),
138 ('', 'root', '', _('produce diffs relative to subdirectory'), _('DIR')),
138 ('', 'root', '', _('produce diffs relative to subdirectory'), _('DIR')),
139 ]
139 ]
140
140
141 mergetoolopts = [
141 mergetoolopts = [
142 ('t', 'tool', '', _('specify merge tool')),
142 ('t', 'tool', '', _('specify merge tool')),
143 ]
143 ]
144
144
145 similarityopts = [
145 similarityopts = [
146 ('s', 'similarity', '',
146 ('s', 'similarity', '',
147 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
147 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
148 ]
148 ]
149
149
150 subrepoopts = [
150 subrepoopts = [
151 ('S', 'subrepos', None,
151 ('S', 'subrepos', None,
152 _('recurse into subrepositories'))
152 _('recurse into subrepositories'))
153 ]
153 ]
154
154
155 debugrevlogopts = [
155 debugrevlogopts = [
156 ('c', 'changelog', False, _('open changelog')),
156 ('c', 'changelog', False, _('open changelog')),
157 ('m', 'manifest', False, _('open manifest')),
157 ('m', 'manifest', False, _('open manifest')),
158 ('', 'dir', '', _('open directory manifest')),
158 ('', 'dir', '', _('open directory manifest')),
159 ]
159 ]
160
160
161 # special string such that everything below this line will be ingored in the
161 # special string such that everything below this line will be ingored in the
162 # editor text
162 # editor text
163 _linebelow = "^HG: ------------------------ >8 ------------------------$"
163 _linebelow = "^HG: ------------------------ >8 ------------------------$"
164
164
165 def ishunk(x):
165 def ishunk(x):
166 hunkclasses = (crecordmod.uihunk, patch.recordhunk)
166 hunkclasses = (crecordmod.uihunk, patch.recordhunk)
167 return isinstance(x, hunkclasses)
167 return isinstance(x, hunkclasses)
168
168
169 def newandmodified(chunks, originalchunks):
169 def newandmodified(chunks, originalchunks):
170 newlyaddedandmodifiedfiles = set()
170 newlyaddedandmodifiedfiles = set()
171 for chunk in chunks:
171 for chunk in chunks:
172 if ishunk(chunk) and chunk.header.isnewfile() and chunk not in \
172 if ishunk(chunk) and chunk.header.isnewfile() and chunk not in \
173 originalchunks:
173 originalchunks:
174 newlyaddedandmodifiedfiles.add(chunk.header.filename())
174 newlyaddedandmodifiedfiles.add(chunk.header.filename())
175 return newlyaddedandmodifiedfiles
175 return newlyaddedandmodifiedfiles
176
176
177 def parsealiases(cmd):
177 def parsealiases(cmd):
178 return cmd.lstrip("^").split("|")
178 return cmd.lstrip("^").split("|")
179
179
180 def setupwrapcolorwrite(ui):
180 def setupwrapcolorwrite(ui):
181 # wrap ui.write so diff output can be labeled/colorized
181 # wrap ui.write so diff output can be labeled/colorized
182 def wrapwrite(orig, *args, **kw):
182 def wrapwrite(orig, *args, **kw):
183 label = kw.pop('label', '')
183 label = kw.pop('label', '')
184 for chunk, l in patch.difflabel(lambda: args):
184 for chunk, l in patch.difflabel(lambda: args):
185 orig(chunk, label=label + l)
185 orig(chunk, label=label + l)
186
186
187 oldwrite = ui.write
187 oldwrite = ui.write
188 def wrap(*args, **kwargs):
188 def wrap(*args, **kwargs):
189 return wrapwrite(oldwrite, *args, **kwargs)
189 return wrapwrite(oldwrite, *args, **kwargs)
190 setattr(ui, 'write', wrap)
190 setattr(ui, 'write', wrap)
191 return oldwrite
191 return oldwrite
192
192
193 def filterchunks(ui, originalhunks, usecurses, testfile, operation=None):
193 def filterchunks(ui, originalhunks, usecurses, testfile, operation=None):
194 if usecurses:
194 if usecurses:
195 if testfile:
195 if testfile:
196 recordfn = crecordmod.testdecorator(testfile,
196 recordfn = crecordmod.testdecorator(testfile,
197 crecordmod.testchunkselector)
197 crecordmod.testchunkselector)
198 else:
198 else:
199 recordfn = crecordmod.chunkselector
199 recordfn = crecordmod.chunkselector
200
200
201 return crecordmod.filterpatch(ui, originalhunks, recordfn, operation)
201 return crecordmod.filterpatch(ui, originalhunks, recordfn, operation)
202
202
203 else:
203 else:
204 return patch.filterpatch(ui, originalhunks, operation)
204 return patch.filterpatch(ui, originalhunks, operation)
205
205
206 def recordfilter(ui, originalhunks, operation=None):
206 def recordfilter(ui, originalhunks, operation=None):
207 """ Prompts the user to filter the originalhunks and return a list of
207 """ Prompts the user to filter the originalhunks and return a list of
208 selected hunks.
208 selected hunks.
209 *operation* is used for to build ui messages to indicate the user what
209 *operation* is used for to build ui messages to indicate the user what
210 kind of filtering they are doing: reverting, committing, shelving, etc.
210 kind of filtering they are doing: reverting, committing, shelving, etc.
211 (see patch.filterpatch).
211 (see patch.filterpatch).
212 """
212 """
213 usecurses = crecordmod.checkcurses(ui)
213 usecurses = crecordmod.checkcurses(ui)
214 testfile = ui.config('experimental', 'crecordtest', None)
214 testfile = ui.config('experimental', 'crecordtest', None)
215 oldwrite = setupwrapcolorwrite(ui)
215 oldwrite = setupwrapcolorwrite(ui)
216 try:
216 try:
217 newchunks, newopts = filterchunks(ui, originalhunks, usecurses,
217 newchunks, newopts = filterchunks(ui, originalhunks, usecurses,
218 testfile, operation)
218 testfile, operation)
219 finally:
219 finally:
220 ui.write = oldwrite
220 ui.write = oldwrite
221 return newchunks, newopts
221 return newchunks, newopts
222
222
223 def dorecord(ui, repo, commitfunc, cmdsuggest, backupall,
223 def dorecord(ui, repo, commitfunc, cmdsuggest, backupall,
224 filterfn, *pats, **opts):
224 filterfn, *pats, **opts):
225 from . import merge as mergemod
225 from . import merge as mergemod
226 opts = pycompat.byteskwargs(opts)
226 opts = pycompat.byteskwargs(opts)
227 if not ui.interactive():
227 if not ui.interactive():
228 if cmdsuggest:
228 if cmdsuggest:
229 msg = _('running non-interactively, use %s instead') % cmdsuggest
229 msg = _('running non-interactively, use %s instead') % cmdsuggest
230 else:
230 else:
231 msg = _('running non-interactively')
231 msg = _('running non-interactively')
232 raise error.Abort(msg)
232 raise error.Abort(msg)
233
233
234 # make sure username is set before going interactive
234 # make sure username is set before going interactive
235 if not opts.get('user'):
235 if not opts.get('user'):
236 ui.username() # raise exception, username not provided
236 ui.username() # raise exception, username not provided
237
237
238 def recordfunc(ui, repo, message, match, opts):
238 def recordfunc(ui, repo, message, match, opts):
239 """This is generic record driver.
239 """This is generic record driver.
240
240
241 Its job is to interactively filter local changes, and
241 Its job is to interactively filter local changes, and
242 accordingly prepare working directory into a state in which the
242 accordingly prepare working directory into a state in which the
243 job can be delegated to a non-interactive commit command such as
243 job can be delegated to a non-interactive commit command such as
244 'commit' or 'qrefresh'.
244 'commit' or 'qrefresh'.
245
245
246 After the actual job is done by non-interactive command, the
246 After the actual job is done by non-interactive command, the
247 working directory is restored to its original state.
247 working directory is restored to its original state.
248
248
249 In the end we'll record interesting changes, and everything else
249 In the end we'll record interesting changes, and everything else
250 will be left in place, so the user can continue working.
250 will be left in place, so the user can continue working.
251 """
251 """
252
252
253 checkunfinished(repo, commit=True)
253 checkunfinished(repo, commit=True)
254 wctx = repo[None]
254 wctx = repo[None]
255 merge = len(wctx.parents()) > 1
255 merge = len(wctx.parents()) > 1
256 if merge:
256 if merge:
257 raise error.Abort(_('cannot partially commit a merge '
257 raise error.Abort(_('cannot partially commit a merge '
258 '(use "hg commit" instead)'))
258 '(use "hg commit" instead)'))
259
259
260 def fail(f, msg):
260 def fail(f, msg):
261 raise error.Abort('%s: %s' % (f, msg))
261 raise error.Abort('%s: %s' % (f, msg))
262
262
263 force = opts.get('force')
263 force = opts.get('force')
264 if not force:
264 if not force:
265 vdirs = []
265 vdirs = []
266 match.explicitdir = vdirs.append
266 match.explicitdir = vdirs.append
267 match.bad = fail
267 match.bad = fail
268
268
269 status = repo.status(match=match)
269 status = repo.status(match=match)
270 if not force:
270 if not force:
271 repo.checkcommitpatterns(wctx, vdirs, match, status, fail)
271 repo.checkcommitpatterns(wctx, vdirs, match, status, fail)
272 diffopts = patch.difffeatureopts(ui, opts=opts, whitespace=True)
272 diffopts = patch.difffeatureopts(ui, opts=opts, whitespace=True)
273 diffopts.nodates = True
273 diffopts.nodates = True
274 diffopts.git = True
274 diffopts.git = True
275 diffopts.showfunc = True
275 diffopts.showfunc = True
276 originaldiff = patch.diff(repo, changes=status, opts=diffopts)
276 originaldiff = patch.diff(repo, changes=status, opts=diffopts)
277 originalchunks = patch.parsepatch(originaldiff)
277 originalchunks = patch.parsepatch(originaldiff)
278
278
279 # 1. filter patch, since we are intending to apply subset of it
279 # 1. filter patch, since we are intending to apply subset of it
280 try:
280 try:
281 chunks, newopts = filterfn(ui, originalchunks)
281 chunks, newopts = filterfn(ui, originalchunks)
282 except patch.PatchError as err:
282 except patch.PatchError as err:
283 raise error.Abort(_('error parsing patch: %s') % err)
283 raise error.Abort(_('error parsing patch: %s') % err)
284 opts.update(newopts)
284 opts.update(newopts)
285
285
286 # We need to keep a backup of files that have been newly added and
286 # We need to keep a backup of files that have been newly added and
287 # modified during the recording process because there is a previous
287 # modified during the recording process because there is a previous
288 # version without the edit in the workdir
288 # version without the edit in the workdir
289 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
289 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
290 contenders = set()
290 contenders = set()
291 for h in chunks:
291 for h in chunks:
292 try:
292 try:
293 contenders.update(set(h.files()))
293 contenders.update(set(h.files()))
294 except AttributeError:
294 except AttributeError:
295 pass
295 pass
296
296
297 changed = status.modified + status.added + status.removed
297 changed = status.modified + status.added + status.removed
298 newfiles = [f for f in changed if f in contenders]
298 newfiles = [f for f in changed if f in contenders]
299 if not newfiles:
299 if not newfiles:
300 ui.status(_('no changes to record\n'))
300 ui.status(_('no changes to record\n'))
301 return 0
301 return 0
302
302
303 modified = set(status.modified)
303 modified = set(status.modified)
304
304
305 # 2. backup changed files, so we can restore them in the end
305 # 2. backup changed files, so we can restore them in the end
306
306
307 if backupall:
307 if backupall:
308 tobackup = changed
308 tobackup = changed
309 else:
309 else:
310 tobackup = [f for f in newfiles if f in modified or f in \
310 tobackup = [f for f in newfiles if f in modified or f in \
311 newlyaddedandmodifiedfiles]
311 newlyaddedandmodifiedfiles]
312 backups = {}
312 backups = {}
313 if tobackup:
313 if tobackup:
314 backupdir = repo.vfs.join('record-backups')
314 backupdir = repo.vfs.join('record-backups')
315 try:
315 try:
316 os.mkdir(backupdir)
316 os.mkdir(backupdir)
317 except OSError as err:
317 except OSError as err:
318 if err.errno != errno.EEXIST:
318 if err.errno != errno.EEXIST:
319 raise
319 raise
320 try:
320 try:
321 # backup continues
321 # backup continues
322 for f in tobackup:
322 for f in tobackup:
323 fd, tmpname = tempfile.mkstemp(prefix=f.replace('/', '_')+'.',
323 fd, tmpname = tempfile.mkstemp(prefix=f.replace('/', '_')+'.',
324 dir=backupdir)
324 dir=backupdir)
325 os.close(fd)
325 os.close(fd)
326 ui.debug('backup %r as %r\n' % (f, tmpname))
326 ui.debug('backup %r as %r\n' % (f, tmpname))
327 util.copyfile(repo.wjoin(f), tmpname, copystat=True)
327 util.copyfile(repo.wjoin(f), tmpname, copystat=True)
328 backups[f] = tmpname
328 backups[f] = tmpname
329
329
330 fp = stringio()
330 fp = stringio()
331 for c in chunks:
331 for c in chunks:
332 fname = c.filename()
332 fname = c.filename()
333 if fname in backups:
333 if fname in backups:
334 c.write(fp)
334 c.write(fp)
335 dopatch = fp.tell()
335 dopatch = fp.tell()
336 fp.seek(0)
336 fp.seek(0)
337
337
338 # 2.5 optionally review / modify patch in text editor
338 # 2.5 optionally review / modify patch in text editor
339 if opts.get('review', False):
339 if opts.get('review', False):
340 patchtext = (crecordmod.diffhelptext
340 patchtext = (crecordmod.diffhelptext
341 + crecordmod.patchhelptext
341 + crecordmod.patchhelptext
342 + fp.read())
342 + fp.read())
343 reviewedpatch = ui.edit(patchtext, "",
343 reviewedpatch = ui.edit(patchtext, "",
344 extra={"suffix": ".diff"},
344 extra={"suffix": ".diff"},
345 repopath=repo.path)
345 repopath=repo.path)
346 fp.truncate(0)
346 fp.truncate(0)
347 fp.write(reviewedpatch)
347 fp.write(reviewedpatch)
348 fp.seek(0)
348 fp.seek(0)
349
349
350 [os.unlink(repo.wjoin(c)) for c in newlyaddedandmodifiedfiles]
350 [os.unlink(repo.wjoin(c)) for c in newlyaddedandmodifiedfiles]
351 # 3a. apply filtered patch to clean repo (clean)
351 # 3a. apply filtered patch to clean repo (clean)
352 if backups:
352 if backups:
353 # Equivalent to hg.revert
353 # Equivalent to hg.revert
354 m = scmutil.matchfiles(repo, backups.keys())
354 m = scmutil.matchfiles(repo, backups.keys())
355 mergemod.update(repo, repo.dirstate.p1(),
355 mergemod.update(repo, repo.dirstate.p1(),
356 False, True, matcher=m)
356 False, True, matcher=m)
357
357
358 # 3b. (apply)
358 # 3b. (apply)
359 if dopatch:
359 if dopatch:
360 try:
360 try:
361 ui.debug('applying patch\n')
361 ui.debug('applying patch\n')
362 ui.debug(fp.getvalue())
362 ui.debug(fp.getvalue())
363 patch.internalpatch(ui, repo, fp, 1, eolmode=None)
363 patch.internalpatch(ui, repo, fp, 1, eolmode=None)
364 except patch.PatchError as err:
364 except patch.PatchError as err:
365 raise error.Abort(str(err))
365 raise error.Abort(str(err))
366 del fp
366 del fp
367
367
368 # 4. We prepared working directory according to filtered
368 # 4. We prepared working directory according to filtered
369 # patch. Now is the time to delegate the job to
369 # patch. Now is the time to delegate the job to
370 # commit/qrefresh or the like!
370 # commit/qrefresh or the like!
371
371
372 # Make all of the pathnames absolute.
372 # Make all of the pathnames absolute.
373 newfiles = [repo.wjoin(nf) for nf in newfiles]
373 newfiles = [repo.wjoin(nf) for nf in newfiles]
374 return commitfunc(ui, repo, *newfiles, **opts)
374 return commitfunc(ui, repo, *newfiles, **opts)
375 finally:
375 finally:
376 # 5. finally restore backed-up files
376 # 5. finally restore backed-up files
377 try:
377 try:
378 dirstate = repo.dirstate
378 dirstate = repo.dirstate
379 for realname, tmpname in backups.iteritems():
379 for realname, tmpname in backups.iteritems():
380 ui.debug('restoring %r to %r\n' % (tmpname, realname))
380 ui.debug('restoring %r to %r\n' % (tmpname, realname))
381
381
382 if dirstate[realname] == 'n':
382 if dirstate[realname] == 'n':
383 # without normallookup, restoring timestamp
383 # without normallookup, restoring timestamp
384 # may cause partially committed files
384 # may cause partially committed files
385 # to be treated as unmodified
385 # to be treated as unmodified
386 dirstate.normallookup(realname)
386 dirstate.normallookup(realname)
387
387
388 # copystat=True here and above are a hack to trick any
388 # copystat=True here and above are a hack to trick any
389 # editors that have f open that we haven't modified them.
389 # editors that have f open that we haven't modified them.
390 #
390 #
391 # Also note that this racy as an editor could notice the
391 # Also note that this racy as an editor could notice the
392 # file's mtime before we've finished writing it.
392 # file's mtime before we've finished writing it.
393 util.copyfile(tmpname, repo.wjoin(realname), copystat=True)
393 util.copyfile(tmpname, repo.wjoin(realname), copystat=True)
394 os.unlink(tmpname)
394 os.unlink(tmpname)
395 if tobackup:
395 if tobackup:
396 os.rmdir(backupdir)
396 os.rmdir(backupdir)
397 except OSError:
397 except OSError:
398 pass
398 pass
399
399
400 def recordinwlock(ui, repo, message, match, opts):
400 def recordinwlock(ui, repo, message, match, opts):
401 with repo.wlock():
401 with repo.wlock():
402 return recordfunc(ui, repo, message, match, opts)
402 return recordfunc(ui, repo, message, match, opts)
403
403
404 return commit(ui, repo, recordinwlock, pats, opts)
404 return commit(ui, repo, recordinwlock, pats, opts)
405
405
406 def findpossible(cmd, table, strict=False):
406 def findpossible(cmd, table, strict=False):
407 """
407 """
408 Return cmd -> (aliases, command table entry)
408 Return cmd -> (aliases, command table entry)
409 for each matching command.
409 for each matching command.
410 Return debug commands (or their aliases) only if no normal command matches.
410 Return debug commands (or their aliases) only if no normal command matches.
411 """
411 """
412 choice = {}
412 choice = {}
413 debugchoice = {}
413 debugchoice = {}
414
414
415 if cmd in table:
415 if cmd in table:
416 # short-circuit exact matches, "log" alias beats "^log|history"
416 # short-circuit exact matches, "log" alias beats "^log|history"
417 keys = [cmd]
417 keys = [cmd]
418 else:
418 else:
419 keys = table.keys()
419 keys = table.keys()
420
420
421 allcmds = []
421 allcmds = []
422 for e in keys:
422 for e in keys:
423 aliases = parsealiases(e)
423 aliases = parsealiases(e)
424 allcmds.extend(aliases)
424 allcmds.extend(aliases)
425 found = None
425 found = None
426 if cmd in aliases:
426 if cmd in aliases:
427 found = cmd
427 found = cmd
428 elif not strict:
428 elif not strict:
429 for a in aliases:
429 for a in aliases:
430 if a.startswith(cmd):
430 if a.startswith(cmd):
431 found = a
431 found = a
432 break
432 break
433 if found is not None:
433 if found is not None:
434 if aliases[0].startswith("debug") or found.startswith("debug"):
434 if aliases[0].startswith("debug") or found.startswith("debug"):
435 debugchoice[found] = (aliases, table[e])
435 debugchoice[found] = (aliases, table[e])
436 else:
436 else:
437 choice[found] = (aliases, table[e])
437 choice[found] = (aliases, table[e])
438
438
439 if not choice and debugchoice:
439 if not choice and debugchoice:
440 choice = debugchoice
440 choice = debugchoice
441
441
442 return choice, allcmds
442 return choice, allcmds
443
443
444 def findcmd(cmd, table, strict=True):
444 def findcmd(cmd, table, strict=True):
445 """Return (aliases, command table entry) for command string."""
445 """Return (aliases, command table entry) for command string."""
446 choice, allcmds = findpossible(cmd, table, strict)
446 choice, allcmds = findpossible(cmd, table, strict)
447
447
448 if cmd in choice:
448 if cmd in choice:
449 return choice[cmd]
449 return choice[cmd]
450
450
451 if len(choice) > 1:
451 if len(choice) > 1:
452 clist = sorted(choice)
452 clist = sorted(choice)
453 raise error.AmbiguousCommand(cmd, clist)
453 raise error.AmbiguousCommand(cmd, clist)
454
454
455 if choice:
455 if choice:
456 return choice.values()[0]
456 return choice.values()[0]
457
457
458 raise error.UnknownCommand(cmd, allcmds)
458 raise error.UnknownCommand(cmd, allcmds)
459
459
460 def findrepo(p):
460 def findrepo(p):
461 while not os.path.isdir(os.path.join(p, ".hg")):
461 while not os.path.isdir(os.path.join(p, ".hg")):
462 oldp, p = p, os.path.dirname(p)
462 oldp, p = p, os.path.dirname(p)
463 if p == oldp:
463 if p == oldp:
464 return None
464 return None
465
465
466 return p
466 return p
467
467
468 def bailifchanged(repo, merge=True, hint=None):
468 def bailifchanged(repo, merge=True, hint=None):
469 """ enforce the precondition that working directory must be clean.
469 """ enforce the precondition that working directory must be clean.
470
470
471 'merge' can be set to false if a pending uncommitted merge should be
471 'merge' can be set to false if a pending uncommitted merge should be
472 ignored (such as when 'update --check' runs).
472 ignored (such as when 'update --check' runs).
473
473
474 'hint' is the usual hint given to Abort exception.
474 'hint' is the usual hint given to Abort exception.
475 """
475 """
476
476
477 if merge and repo.dirstate.p2() != nullid:
477 if merge and repo.dirstate.p2() != nullid:
478 raise error.Abort(_('outstanding uncommitted merge'), hint=hint)
478 raise error.Abort(_('outstanding uncommitted merge'), hint=hint)
479 modified, added, removed, deleted = repo.status()[:4]
479 modified, added, removed, deleted = repo.status()[:4]
480 if modified or added or removed or deleted:
480 if modified or added or removed or deleted:
481 raise error.Abort(_('uncommitted changes'), hint=hint)
481 raise error.Abort(_('uncommitted changes'), hint=hint)
482 ctx = repo[None]
482 ctx = repo[None]
483 for s in sorted(ctx.substate):
483 for s in sorted(ctx.substate):
484 ctx.sub(s).bailifchanged(hint=hint)
484 ctx.sub(s).bailifchanged(hint=hint)
485
485
486 def logmessage(ui, opts):
486 def logmessage(ui, opts):
487 """ get the log message according to -m and -l option """
487 """ get the log message according to -m and -l option """
488 message = opts.get('message')
488 message = opts.get('message')
489 logfile = opts.get('logfile')
489 logfile = opts.get('logfile')
490
490
491 if message and logfile:
491 if message and logfile:
492 raise error.Abort(_('options --message and --logfile are mutually '
492 raise error.Abort(_('options --message and --logfile are mutually '
493 'exclusive'))
493 'exclusive'))
494 if not message and logfile:
494 if not message and logfile:
495 try:
495 try:
496 if logfile == '-':
496 if logfile == '-':
497 message = ui.fin.read()
497 message = ui.fin.read()
498 else:
498 else:
499 message = '\n'.join(util.readfile(logfile).splitlines())
499 message = '\n'.join(util.readfile(logfile).splitlines())
500 except IOError as inst:
500 except IOError as inst:
501 raise error.Abort(_("can't read commit message '%s': %s") %
501 raise error.Abort(_("can't read commit message '%s': %s") %
502 (logfile, inst.strerror))
502 (logfile, inst.strerror))
503 return message
503 return message
504
504
505 def mergeeditform(ctxorbool, baseformname):
505 def mergeeditform(ctxorbool, baseformname):
506 """return appropriate editform name (referencing a committemplate)
506 """return appropriate editform name (referencing a committemplate)
507
507
508 'ctxorbool' is either a ctx to be committed, or a bool indicating whether
508 'ctxorbool' is either a ctx to be committed, or a bool indicating whether
509 merging is committed.
509 merging is committed.
510
510
511 This returns baseformname with '.merge' appended if it is a merge,
511 This returns baseformname with '.merge' appended if it is a merge,
512 otherwise '.normal' is appended.
512 otherwise '.normal' is appended.
513 """
513 """
514 if isinstance(ctxorbool, bool):
514 if isinstance(ctxorbool, bool):
515 if ctxorbool:
515 if ctxorbool:
516 return baseformname + ".merge"
516 return baseformname + ".merge"
517 elif 1 < len(ctxorbool.parents()):
517 elif 1 < len(ctxorbool.parents()):
518 return baseformname + ".merge"
518 return baseformname + ".merge"
519
519
520 return baseformname + ".normal"
520 return baseformname + ".normal"
521
521
522 def getcommiteditor(edit=False, finishdesc=None, extramsg=None,
522 def getcommiteditor(edit=False, finishdesc=None, extramsg=None,
523 editform='', **opts):
523 editform='', **opts):
524 """get appropriate commit message editor according to '--edit' option
524 """get appropriate commit message editor according to '--edit' option
525
525
526 'finishdesc' is a function to be called with edited commit message
526 'finishdesc' is a function to be called with edited commit message
527 (= 'description' of the new changeset) just after editing, but
527 (= 'description' of the new changeset) just after editing, but
528 before checking empty-ness. It should return actual text to be
528 before checking empty-ness. It should return actual text to be
529 stored into history. This allows to change description before
529 stored into history. This allows to change description before
530 storing.
530 storing.
531
531
532 'extramsg' is a extra message to be shown in the editor instead of
532 'extramsg' is a extra message to be shown in the editor instead of
533 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
533 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
534 is automatically added.
534 is automatically added.
535
535
536 'editform' is a dot-separated list of names, to distinguish
536 'editform' is a dot-separated list of names, to distinguish
537 the purpose of commit text editing.
537 the purpose of commit text editing.
538
538
539 'getcommiteditor' returns 'commitforceeditor' regardless of
539 'getcommiteditor' returns 'commitforceeditor' regardless of
540 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
540 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
541 they are specific for usage in MQ.
541 they are specific for usage in MQ.
542 """
542 """
543 if edit or finishdesc or extramsg:
543 if edit or finishdesc or extramsg:
544 return lambda r, c, s: commitforceeditor(r, c, s,
544 return lambda r, c, s: commitforceeditor(r, c, s,
545 finishdesc=finishdesc,
545 finishdesc=finishdesc,
546 extramsg=extramsg,
546 extramsg=extramsg,
547 editform=editform)
547 editform=editform)
548 elif editform:
548 elif editform:
549 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
549 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
550 else:
550 else:
551 return commiteditor
551 return commiteditor
552
552
553 def loglimit(opts):
553 def loglimit(opts):
554 """get the log limit according to option -l/--limit"""
554 """get the log limit according to option -l/--limit"""
555 limit = opts.get('limit')
555 limit = opts.get('limit')
556 if limit:
556 if limit:
557 try:
557 try:
558 limit = int(limit)
558 limit = int(limit)
559 except ValueError:
559 except ValueError:
560 raise error.Abort(_('limit must be a positive integer'))
560 raise error.Abort(_('limit must be a positive integer'))
561 if limit <= 0:
561 if limit <= 0:
562 raise error.Abort(_('limit must be positive'))
562 raise error.Abort(_('limit must be positive'))
563 else:
563 else:
564 limit = None
564 limit = None
565 return limit
565 return limit
566
566
567 def makefilename(repo, pat, node, desc=None,
567 def makefilename(repo, pat, node, desc=None,
568 total=None, seqno=None, revwidth=None, pathname=None):
568 total=None, seqno=None, revwidth=None, pathname=None):
569 node_expander = {
569 node_expander = {
570 'H': lambda: hex(node),
570 'H': lambda: hex(node),
571 'R': lambda: str(repo.changelog.rev(node)),
571 'R': lambda: str(repo.changelog.rev(node)),
572 'h': lambda: short(node),
572 'h': lambda: short(node),
573 'm': lambda: re.sub('[^\w]', '_', str(desc))
573 'm': lambda: re.sub('[^\w]', '_', str(desc))
574 }
574 }
575 expander = {
575 expander = {
576 '%': lambda: '%',
576 '%': lambda: '%',
577 'b': lambda: os.path.basename(repo.root),
577 'b': lambda: os.path.basename(repo.root),
578 }
578 }
579
579
580 try:
580 try:
581 if node:
581 if node:
582 expander.update(node_expander)
582 expander.update(node_expander)
583 if node:
583 if node:
584 expander['r'] = (lambda:
584 expander['r'] = (lambda:
585 str(repo.changelog.rev(node)).zfill(revwidth or 0))
585 str(repo.changelog.rev(node)).zfill(revwidth or 0))
586 if total is not None:
586 if total is not None:
587 expander['N'] = lambda: str(total)
587 expander['N'] = lambda: str(total)
588 if seqno is not None:
588 if seqno is not None:
589 expander['n'] = lambda: str(seqno)
589 expander['n'] = lambda: str(seqno)
590 if total is not None and seqno is not None:
590 if total is not None and seqno is not None:
591 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
591 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
592 if pathname is not None:
592 if pathname is not None:
593 expander['s'] = lambda: os.path.basename(pathname)
593 expander['s'] = lambda: os.path.basename(pathname)
594 expander['d'] = lambda: os.path.dirname(pathname) or '.'
594 expander['d'] = lambda: os.path.dirname(pathname) or '.'
595 expander['p'] = lambda: pathname
595 expander['p'] = lambda: pathname
596
596
597 newname = []
597 newname = []
598 patlen = len(pat)
598 patlen = len(pat)
599 i = 0
599 i = 0
600 while i < patlen:
600 while i < patlen:
601 c = pat[i:i + 1]
601 c = pat[i:i + 1]
602 if c == '%':
602 if c == '%':
603 i += 1
603 i += 1
604 c = pat[i:i + 1]
604 c = pat[i:i + 1]
605 c = expander[c]()
605 c = expander[c]()
606 newname.append(c)
606 newname.append(c)
607 i += 1
607 i += 1
608 return ''.join(newname)
608 return ''.join(newname)
609 except KeyError as inst:
609 except KeyError as inst:
610 raise error.Abort(_("invalid format spec '%%%s' in output filename") %
610 raise error.Abort(_("invalid format spec '%%%s' in output filename") %
611 inst.args[0])
611 inst.args[0])
612
612
613 def isstdiofilename(pat):
613 def isstdiofilename(pat):
614 """True if the given pat looks like a filename denoting stdin/stdout"""
614 """True if the given pat looks like a filename denoting stdin/stdout"""
615 return not pat or pat == '-'
615 return not pat or pat == '-'
616
616
617 class _unclosablefile(object):
617 class _unclosablefile(object):
618 def __init__(self, fp):
618 def __init__(self, fp):
619 self._fp = fp
619 self._fp = fp
620
620
621 def close(self):
621 def close(self):
622 pass
622 pass
623
623
624 def __iter__(self):
624 def __iter__(self):
625 return iter(self._fp)
625 return iter(self._fp)
626
626
627 def __getattr__(self, attr):
627 def __getattr__(self, attr):
628 return getattr(self._fp, attr)
628 return getattr(self._fp, attr)
629
629
630 def __enter__(self):
630 def __enter__(self):
631 return self
631 return self
632
632
633 def __exit__(self, exc_type, exc_value, exc_tb):
633 def __exit__(self, exc_type, exc_value, exc_tb):
634 pass
634 pass
635
635
636 def makefileobj(repo, pat, node=None, desc=None, total=None,
636 def makefileobj(repo, pat, node=None, desc=None, total=None,
637 seqno=None, revwidth=None, mode='wb', modemap=None,
637 seqno=None, revwidth=None, mode='wb', modemap=None,
638 pathname=None):
638 pathname=None):
639
639
640 writable = mode not in ('r', 'rb')
640 writable = mode not in ('r', 'rb')
641
641
642 if isstdiofilename(pat):
642 if isstdiofilename(pat):
643 if writable:
643 if writable:
644 fp = repo.ui.fout
644 fp = repo.ui.fout
645 else:
645 else:
646 fp = repo.ui.fin
646 fp = repo.ui.fin
647 return _unclosablefile(fp)
647 return _unclosablefile(fp)
648 fn = makefilename(repo, pat, node, desc, total, seqno, revwidth, pathname)
648 fn = makefilename(repo, pat, node, desc, total, seqno, revwidth, pathname)
649 if modemap is not None:
649 if modemap is not None:
650 mode = modemap.get(fn, mode)
650 mode = modemap.get(fn, mode)
651 if mode == 'wb':
651 if mode == 'wb':
652 modemap[fn] = 'ab'
652 modemap[fn] = 'ab'
653 return open(fn, mode)
653 return open(fn, mode)
654
654
655 def openrevlog(repo, cmd, file_, opts):
655 def openrevlog(repo, cmd, file_, opts):
656 """opens the changelog, manifest, a filelog or a given revlog"""
656 """opens the changelog, manifest, a filelog or a given revlog"""
657 cl = opts['changelog']
657 cl = opts['changelog']
658 mf = opts['manifest']
658 mf = opts['manifest']
659 dir = opts['dir']
659 dir = opts['dir']
660 msg = None
660 msg = None
661 if cl and mf:
661 if cl and mf:
662 msg = _('cannot specify --changelog and --manifest at the same time')
662 msg = _('cannot specify --changelog and --manifest at the same time')
663 elif cl and dir:
663 elif cl and dir:
664 msg = _('cannot specify --changelog and --dir at the same time')
664 msg = _('cannot specify --changelog and --dir at the same time')
665 elif cl or mf or dir:
665 elif cl or mf or dir:
666 if file_:
666 if file_:
667 msg = _('cannot specify filename with --changelog or --manifest')
667 msg = _('cannot specify filename with --changelog or --manifest')
668 elif not repo:
668 elif not repo:
669 msg = _('cannot specify --changelog or --manifest or --dir '
669 msg = _('cannot specify --changelog or --manifest or --dir '
670 'without a repository')
670 'without a repository')
671 if msg:
671 if msg:
672 raise error.Abort(msg)
672 raise error.Abort(msg)
673
673
674 r = None
674 r = None
675 if repo:
675 if repo:
676 if cl:
676 if cl:
677 r = repo.unfiltered().changelog
677 r = repo.unfiltered().changelog
678 elif dir:
678 elif dir:
679 if 'treemanifest' not in repo.requirements:
679 if 'treemanifest' not in repo.requirements:
680 raise error.Abort(_("--dir can only be used on repos with "
680 raise error.Abort(_("--dir can only be used on repos with "
681 "treemanifest enabled"))
681 "treemanifest enabled"))
682 dirlog = repo.manifestlog._revlog.dirlog(dir)
682 dirlog = repo.manifestlog._revlog.dirlog(dir)
683 if len(dirlog):
683 if len(dirlog):
684 r = dirlog
684 r = dirlog
685 elif mf:
685 elif mf:
686 r = repo.manifestlog._revlog
686 r = repo.manifestlog._revlog
687 elif file_:
687 elif file_:
688 filelog = repo.file(file_)
688 filelog = repo.file(file_)
689 if len(filelog):
689 if len(filelog):
690 r = filelog
690 r = filelog
691 if not r:
691 if not r:
692 if not file_:
692 if not file_:
693 raise error.CommandError(cmd, _('invalid arguments'))
693 raise error.CommandError(cmd, _('invalid arguments'))
694 if not os.path.isfile(file_):
694 if not os.path.isfile(file_):
695 raise error.Abort(_("revlog '%s' not found") % file_)
695 raise error.Abort(_("revlog '%s' not found") % file_)
696 r = revlog.revlog(vfsmod.vfs(pycompat.getcwd(), audit=False),
696 r = revlog.revlog(vfsmod.vfs(pycompat.getcwd(), audit=False),
697 file_[:-2] + ".i")
697 file_[:-2] + ".i")
698 return r
698 return r
699
699
700 def copy(ui, repo, pats, opts, rename=False):
700 def copy(ui, repo, pats, opts, rename=False):
701 # called with the repo lock held
701 # called with the repo lock held
702 #
702 #
703 # hgsep => pathname that uses "/" to separate directories
703 # hgsep => pathname that uses "/" to separate directories
704 # ossep => pathname that uses os.sep to separate directories
704 # ossep => pathname that uses os.sep to separate directories
705 cwd = repo.getcwd()
705 cwd = repo.getcwd()
706 targets = {}
706 targets = {}
707 after = opts.get("after")
707 after = opts.get("after")
708 dryrun = opts.get("dry_run")
708 dryrun = opts.get("dry_run")
709 wctx = repo[None]
709 wctx = repo[None]
710
710
711 def walkpat(pat):
711 def walkpat(pat):
712 srcs = []
712 srcs = []
713 if after:
713 if after:
714 badstates = '?'
714 badstates = '?'
715 else:
715 else:
716 badstates = '?r'
716 badstates = '?r'
717 m = scmutil.match(wctx, [pat], opts, globbed=True)
717 m = scmutil.match(wctx, [pat], opts, globbed=True)
718 for abs in wctx.walk(m):
718 for abs in wctx.walk(m):
719 state = repo.dirstate[abs]
719 state = repo.dirstate[abs]
720 rel = m.rel(abs)
720 rel = m.rel(abs)
721 exact = m.exact(abs)
721 exact = m.exact(abs)
722 if state in badstates:
722 if state in badstates:
723 if exact and state == '?':
723 if exact and state == '?':
724 ui.warn(_('%s: not copying - file is not managed\n') % rel)
724 ui.warn(_('%s: not copying - file is not managed\n') % rel)
725 if exact and state == 'r':
725 if exact and state == 'r':
726 ui.warn(_('%s: not copying - file has been marked for'
726 ui.warn(_('%s: not copying - file has been marked for'
727 ' remove\n') % rel)
727 ' remove\n') % rel)
728 continue
728 continue
729 # abs: hgsep
729 # abs: hgsep
730 # rel: ossep
730 # rel: ossep
731 srcs.append((abs, rel, exact))
731 srcs.append((abs, rel, exact))
732 return srcs
732 return srcs
733
733
734 # abssrc: hgsep
734 # abssrc: hgsep
735 # relsrc: ossep
735 # relsrc: ossep
736 # otarget: ossep
736 # otarget: ossep
737 def copyfile(abssrc, relsrc, otarget, exact):
737 def copyfile(abssrc, relsrc, otarget, exact):
738 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
738 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
739 if '/' in abstarget:
739 if '/' in abstarget:
740 # We cannot normalize abstarget itself, this would prevent
740 # We cannot normalize abstarget itself, this would prevent
741 # case only renames, like a => A.
741 # case only renames, like a => A.
742 abspath, absname = abstarget.rsplit('/', 1)
742 abspath, absname = abstarget.rsplit('/', 1)
743 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
743 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
744 reltarget = repo.pathto(abstarget, cwd)
744 reltarget = repo.pathto(abstarget, cwd)
745 target = repo.wjoin(abstarget)
745 target = repo.wjoin(abstarget)
746 src = repo.wjoin(abssrc)
746 src = repo.wjoin(abssrc)
747 state = repo.dirstate[abstarget]
747 state = repo.dirstate[abstarget]
748
748
749 scmutil.checkportable(ui, abstarget)
749 scmutil.checkportable(ui, abstarget)
750
750
751 # check for collisions
751 # check for collisions
752 prevsrc = targets.get(abstarget)
752 prevsrc = targets.get(abstarget)
753 if prevsrc is not None:
753 if prevsrc is not None:
754 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
754 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
755 (reltarget, repo.pathto(abssrc, cwd),
755 (reltarget, repo.pathto(abssrc, cwd),
756 repo.pathto(prevsrc, cwd)))
756 repo.pathto(prevsrc, cwd)))
757 return
757 return
758
758
759 # check for overwrites
759 # check for overwrites
760 exists = os.path.lexists(target)
760 exists = os.path.lexists(target)
761 samefile = False
761 samefile = False
762 if exists and abssrc != abstarget:
762 if exists and abssrc != abstarget:
763 if (repo.dirstate.normalize(abssrc) ==
763 if (repo.dirstate.normalize(abssrc) ==
764 repo.dirstate.normalize(abstarget)):
764 repo.dirstate.normalize(abstarget)):
765 if not rename:
765 if not rename:
766 ui.warn(_("%s: can't copy - same file\n") % reltarget)
766 ui.warn(_("%s: can't copy - same file\n") % reltarget)
767 return
767 return
768 exists = False
768 exists = False
769 samefile = True
769 samefile = True
770
770
771 if not after and exists or after and state in 'mn':
771 if not after and exists or after and state in 'mn':
772 if not opts['force']:
772 if not opts['force']:
773 if state in 'mn':
773 if state in 'mn':
774 msg = _('%s: not overwriting - file already committed\n')
774 msg = _('%s: not overwriting - file already committed\n')
775 if after:
775 if after:
776 flags = '--after --force'
776 flags = '--after --force'
777 else:
777 else:
778 flags = '--force'
778 flags = '--force'
779 if rename:
779 if rename:
780 hint = _('(hg rename %s to replace the file by '
780 hint = _('(hg rename %s to replace the file by '
781 'recording a rename)\n') % flags
781 'recording a rename)\n') % flags
782 else:
782 else:
783 hint = _('(hg copy %s to replace the file by '
783 hint = _('(hg copy %s to replace the file by '
784 'recording a copy)\n') % flags
784 'recording a copy)\n') % flags
785 else:
785 else:
786 msg = _('%s: not overwriting - file exists\n')
786 msg = _('%s: not overwriting - file exists\n')
787 if rename:
787 if rename:
788 hint = _('(hg rename --after to record the rename)\n')
788 hint = _('(hg rename --after to record the rename)\n')
789 else:
789 else:
790 hint = _('(hg copy --after to record the copy)\n')
790 hint = _('(hg copy --after to record the copy)\n')
791 ui.warn(msg % reltarget)
791 ui.warn(msg % reltarget)
792 ui.warn(hint)
792 ui.warn(hint)
793 return
793 return
794
794
795 if after:
795 if after:
796 if not exists:
796 if not exists:
797 if rename:
797 if rename:
798 ui.warn(_('%s: not recording move - %s does not exist\n') %
798 ui.warn(_('%s: not recording move - %s does not exist\n') %
799 (relsrc, reltarget))
799 (relsrc, reltarget))
800 else:
800 else:
801 ui.warn(_('%s: not recording copy - %s does not exist\n') %
801 ui.warn(_('%s: not recording copy - %s does not exist\n') %
802 (relsrc, reltarget))
802 (relsrc, reltarget))
803 return
803 return
804 elif not dryrun:
804 elif not dryrun:
805 try:
805 try:
806 if exists:
806 if exists:
807 os.unlink(target)
807 os.unlink(target)
808 targetdir = os.path.dirname(target) or '.'
808 targetdir = os.path.dirname(target) or '.'
809 if not os.path.isdir(targetdir):
809 if not os.path.isdir(targetdir):
810 os.makedirs(targetdir)
810 os.makedirs(targetdir)
811 if samefile:
811 if samefile:
812 tmp = target + "~hgrename"
812 tmp = target + "~hgrename"
813 os.rename(src, tmp)
813 os.rename(src, tmp)
814 os.rename(tmp, target)
814 os.rename(tmp, target)
815 else:
815 else:
816 util.copyfile(src, target)
816 util.copyfile(src, target)
817 srcexists = True
817 srcexists = True
818 except IOError as inst:
818 except IOError as inst:
819 if inst.errno == errno.ENOENT:
819 if inst.errno == errno.ENOENT:
820 ui.warn(_('%s: deleted in working directory\n') % relsrc)
820 ui.warn(_('%s: deleted in working directory\n') % relsrc)
821 srcexists = False
821 srcexists = False
822 else:
822 else:
823 ui.warn(_('%s: cannot copy - %s\n') %
823 ui.warn(_('%s: cannot copy - %s\n') %
824 (relsrc, inst.strerror))
824 (relsrc, inst.strerror))
825 return True # report a failure
825 return True # report a failure
826
826
827 if ui.verbose or not exact:
827 if ui.verbose or not exact:
828 if rename:
828 if rename:
829 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
829 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
830 else:
830 else:
831 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
831 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
832
832
833 targets[abstarget] = abssrc
833 targets[abstarget] = abssrc
834
834
835 # fix up dirstate
835 # fix up dirstate
836 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
836 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
837 dryrun=dryrun, cwd=cwd)
837 dryrun=dryrun, cwd=cwd)
838 if rename and not dryrun:
838 if rename and not dryrun:
839 if not after and srcexists and not samefile:
839 if not after and srcexists and not samefile:
840 repo.wvfs.unlinkpath(abssrc)
840 repo.wvfs.unlinkpath(abssrc)
841 wctx.forget([abssrc])
841 wctx.forget([abssrc])
842
842
843 # pat: ossep
843 # pat: ossep
844 # dest ossep
844 # dest ossep
845 # srcs: list of (hgsep, hgsep, ossep, bool)
845 # srcs: list of (hgsep, hgsep, ossep, bool)
846 # return: function that takes hgsep and returns ossep
846 # return: function that takes hgsep and returns ossep
847 def targetpathfn(pat, dest, srcs):
847 def targetpathfn(pat, dest, srcs):
848 if os.path.isdir(pat):
848 if os.path.isdir(pat):
849 abspfx = pathutil.canonpath(repo.root, cwd, pat)
849 abspfx = pathutil.canonpath(repo.root, cwd, pat)
850 abspfx = util.localpath(abspfx)
850 abspfx = util.localpath(abspfx)
851 if destdirexists:
851 if destdirexists:
852 striplen = len(os.path.split(abspfx)[0])
852 striplen = len(os.path.split(abspfx)[0])
853 else:
853 else:
854 striplen = len(abspfx)
854 striplen = len(abspfx)
855 if striplen:
855 if striplen:
856 striplen += len(pycompat.ossep)
856 striplen += len(pycompat.ossep)
857 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
857 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
858 elif destdirexists:
858 elif destdirexists:
859 res = lambda p: os.path.join(dest,
859 res = lambda p: os.path.join(dest,
860 os.path.basename(util.localpath(p)))
860 os.path.basename(util.localpath(p)))
861 else:
861 else:
862 res = lambda p: dest
862 res = lambda p: dest
863 return res
863 return res
864
864
865 # pat: ossep
865 # pat: ossep
866 # dest ossep
866 # dest ossep
867 # srcs: list of (hgsep, hgsep, ossep, bool)
867 # srcs: list of (hgsep, hgsep, ossep, bool)
868 # return: function that takes hgsep and returns ossep
868 # return: function that takes hgsep and returns ossep
869 def targetpathafterfn(pat, dest, srcs):
869 def targetpathafterfn(pat, dest, srcs):
870 if matchmod.patkind(pat):
870 if matchmod.patkind(pat):
871 # a mercurial pattern
871 # a mercurial pattern
872 res = lambda p: os.path.join(dest,
872 res = lambda p: os.path.join(dest,
873 os.path.basename(util.localpath(p)))
873 os.path.basename(util.localpath(p)))
874 else:
874 else:
875 abspfx = pathutil.canonpath(repo.root, cwd, pat)
875 abspfx = pathutil.canonpath(repo.root, cwd, pat)
876 if len(abspfx) < len(srcs[0][0]):
876 if len(abspfx) < len(srcs[0][0]):
877 # A directory. Either the target path contains the last
877 # A directory. Either the target path contains the last
878 # component of the source path or it does not.
878 # component of the source path or it does not.
879 def evalpath(striplen):
879 def evalpath(striplen):
880 score = 0
880 score = 0
881 for s in srcs:
881 for s in srcs:
882 t = os.path.join(dest, util.localpath(s[0])[striplen:])
882 t = os.path.join(dest, util.localpath(s[0])[striplen:])
883 if os.path.lexists(t):
883 if os.path.lexists(t):
884 score += 1
884 score += 1
885 return score
885 return score
886
886
887 abspfx = util.localpath(abspfx)
887 abspfx = util.localpath(abspfx)
888 striplen = len(abspfx)
888 striplen = len(abspfx)
889 if striplen:
889 if striplen:
890 striplen += len(pycompat.ossep)
890 striplen += len(pycompat.ossep)
891 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
891 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
892 score = evalpath(striplen)
892 score = evalpath(striplen)
893 striplen1 = len(os.path.split(abspfx)[0])
893 striplen1 = len(os.path.split(abspfx)[0])
894 if striplen1:
894 if striplen1:
895 striplen1 += len(pycompat.ossep)
895 striplen1 += len(pycompat.ossep)
896 if evalpath(striplen1) > score:
896 if evalpath(striplen1) > score:
897 striplen = striplen1
897 striplen = striplen1
898 res = lambda p: os.path.join(dest,
898 res = lambda p: os.path.join(dest,
899 util.localpath(p)[striplen:])
899 util.localpath(p)[striplen:])
900 else:
900 else:
901 # a file
901 # a file
902 if destdirexists:
902 if destdirexists:
903 res = lambda p: os.path.join(dest,
903 res = lambda p: os.path.join(dest,
904 os.path.basename(util.localpath(p)))
904 os.path.basename(util.localpath(p)))
905 else:
905 else:
906 res = lambda p: dest
906 res = lambda p: dest
907 return res
907 return res
908
908
909 pats = scmutil.expandpats(pats)
909 pats = scmutil.expandpats(pats)
910 if not pats:
910 if not pats:
911 raise error.Abort(_('no source or destination specified'))
911 raise error.Abort(_('no source or destination specified'))
912 if len(pats) == 1:
912 if len(pats) == 1:
913 raise error.Abort(_('no destination specified'))
913 raise error.Abort(_('no destination specified'))
914 dest = pats.pop()
914 dest = pats.pop()
915 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
915 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
916 if not destdirexists:
916 if not destdirexists:
917 if len(pats) > 1 or matchmod.patkind(pats[0]):
917 if len(pats) > 1 or matchmod.patkind(pats[0]):
918 raise error.Abort(_('with multiple sources, destination must be an '
918 raise error.Abort(_('with multiple sources, destination must be an '
919 'existing directory'))
919 'existing directory'))
920 if util.endswithsep(dest):
920 if util.endswithsep(dest):
921 raise error.Abort(_('destination %s is not a directory') % dest)
921 raise error.Abort(_('destination %s is not a directory') % dest)
922
922
923 tfn = targetpathfn
923 tfn = targetpathfn
924 if after:
924 if after:
925 tfn = targetpathafterfn
925 tfn = targetpathafterfn
926 copylist = []
926 copylist = []
927 for pat in pats:
927 for pat in pats:
928 srcs = walkpat(pat)
928 srcs = walkpat(pat)
929 if not srcs:
929 if not srcs:
930 continue
930 continue
931 copylist.append((tfn(pat, dest, srcs), srcs))
931 copylist.append((tfn(pat, dest, srcs), srcs))
932 if not copylist:
932 if not copylist:
933 raise error.Abort(_('no files to copy'))
933 raise error.Abort(_('no files to copy'))
934
934
935 errors = 0
935 errors = 0
936 for targetpath, srcs in copylist:
936 for targetpath, srcs in copylist:
937 for abssrc, relsrc, exact in srcs:
937 for abssrc, relsrc, exact in srcs:
938 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
938 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
939 errors += 1
939 errors += 1
940
940
941 if errors:
941 if errors:
942 ui.warn(_('(consider using --after)\n'))
942 ui.warn(_('(consider using --after)\n'))
943
943
944 return errors != 0
944 return errors != 0
945
945
946 ## facility to let extension process additional data into an import patch
946 ## facility to let extension process additional data into an import patch
947 # list of identifier to be executed in order
947 # list of identifier to be executed in order
948 extrapreimport = [] # run before commit
948 extrapreimport = [] # run before commit
949 extrapostimport = [] # run after commit
949 extrapostimport = [] # run after commit
950 # mapping from identifier to actual import function
950 # mapping from identifier to actual import function
951 #
951 #
952 # 'preimport' are run before the commit is made and are provided the following
952 # 'preimport' are run before the commit is made and are provided the following
953 # arguments:
953 # arguments:
954 # - repo: the localrepository instance,
954 # - repo: the localrepository instance,
955 # - patchdata: data extracted from patch header (cf m.patch.patchheadermap),
955 # - patchdata: data extracted from patch header (cf m.patch.patchheadermap),
956 # - extra: the future extra dictionary of the changeset, please mutate it,
956 # - extra: the future extra dictionary of the changeset, please mutate it,
957 # - opts: the import options.
957 # - opts: the import options.
958 # XXX ideally, we would just pass an ctx ready to be computed, that would allow
958 # XXX ideally, we would just pass an ctx ready to be computed, that would allow
959 # mutation of in memory commit and more. Feel free to rework the code to get
959 # mutation of in memory commit and more. Feel free to rework the code to get
960 # there.
960 # there.
961 extrapreimportmap = {}
961 extrapreimportmap = {}
962 # 'postimport' are run after the commit is made and are provided the following
962 # 'postimport' are run after the commit is made and are provided the following
963 # argument:
963 # argument:
964 # - ctx: the changectx created by import.
964 # - ctx: the changectx created by import.
965 extrapostimportmap = {}
965 extrapostimportmap = {}
966
966
967 def tryimportone(ui, repo, hunk, parents, opts, msgs, updatefunc):
967 def tryimportone(ui, repo, hunk, parents, opts, msgs, updatefunc):
968 """Utility function used by commands.import to import a single patch
968 """Utility function used by commands.import to import a single patch
969
969
970 This function is explicitly defined here to help the evolve extension to
970 This function is explicitly defined here to help the evolve extension to
971 wrap this part of the import logic.
971 wrap this part of the import logic.
972
972
973 The API is currently a bit ugly because it a simple code translation from
973 The API is currently a bit ugly because it a simple code translation from
974 the import command. Feel free to make it better.
974 the import command. Feel free to make it better.
975
975
976 :hunk: a patch (as a binary string)
976 :hunk: a patch (as a binary string)
977 :parents: nodes that will be parent of the created commit
977 :parents: nodes that will be parent of the created commit
978 :opts: the full dict of option passed to the import command
978 :opts: the full dict of option passed to the import command
979 :msgs: list to save commit message to.
979 :msgs: list to save commit message to.
980 (used in case we need to save it when failing)
980 (used in case we need to save it when failing)
981 :updatefunc: a function that update a repo to a given node
981 :updatefunc: a function that update a repo to a given node
982 updatefunc(<repo>, <node>)
982 updatefunc(<repo>, <node>)
983 """
983 """
984 # avoid cycle context -> subrepo -> cmdutil
984 # avoid cycle context -> subrepo -> cmdutil
985 from . import context
985 from . import context
986 extractdata = patch.extract(ui, hunk)
986 extractdata = patch.extract(ui, hunk)
987 tmpname = extractdata.get('filename')
987 tmpname = extractdata.get('filename')
988 message = extractdata.get('message')
988 message = extractdata.get('message')
989 user = opts.get('user') or extractdata.get('user')
989 user = opts.get('user') or extractdata.get('user')
990 date = opts.get('date') or extractdata.get('date')
990 date = opts.get('date') or extractdata.get('date')
991 branch = extractdata.get('branch')
991 branch = extractdata.get('branch')
992 nodeid = extractdata.get('nodeid')
992 nodeid = extractdata.get('nodeid')
993 p1 = extractdata.get('p1')
993 p1 = extractdata.get('p1')
994 p2 = extractdata.get('p2')
994 p2 = extractdata.get('p2')
995
995
996 nocommit = opts.get('no_commit')
996 nocommit = opts.get('no_commit')
997 importbranch = opts.get('import_branch')
997 importbranch = opts.get('import_branch')
998 update = not opts.get('bypass')
998 update = not opts.get('bypass')
999 strip = opts["strip"]
999 strip = opts["strip"]
1000 prefix = opts["prefix"]
1000 prefix = opts["prefix"]
1001 sim = float(opts.get('similarity') or 0)
1001 sim = float(opts.get('similarity') or 0)
1002 if not tmpname:
1002 if not tmpname:
1003 return (None, None, False)
1003 return (None, None, False)
1004
1004
1005 rejects = False
1005 rejects = False
1006
1006
1007 try:
1007 try:
1008 cmdline_message = logmessage(ui, opts)
1008 cmdline_message = logmessage(ui, opts)
1009 if cmdline_message:
1009 if cmdline_message:
1010 # pickup the cmdline msg
1010 # pickup the cmdline msg
1011 message = cmdline_message
1011 message = cmdline_message
1012 elif message:
1012 elif message:
1013 # pickup the patch msg
1013 # pickup the patch msg
1014 message = message.strip()
1014 message = message.strip()
1015 else:
1015 else:
1016 # launch the editor
1016 # launch the editor
1017 message = None
1017 message = None
1018 ui.debug('message:\n%s\n' % message)
1018 ui.debug('message:\n%s\n' % message)
1019
1019
1020 if len(parents) == 1:
1020 if len(parents) == 1:
1021 parents.append(repo[nullid])
1021 parents.append(repo[nullid])
1022 if opts.get('exact'):
1022 if opts.get('exact'):
1023 if not nodeid or not p1:
1023 if not nodeid or not p1:
1024 raise error.Abort(_('not a Mercurial patch'))
1024 raise error.Abort(_('not a Mercurial patch'))
1025 p1 = repo[p1]
1025 p1 = repo[p1]
1026 p2 = repo[p2 or nullid]
1026 p2 = repo[p2 or nullid]
1027 elif p2:
1027 elif p2:
1028 try:
1028 try:
1029 p1 = repo[p1]
1029 p1 = repo[p1]
1030 p2 = repo[p2]
1030 p2 = repo[p2]
1031 # Without any options, consider p2 only if the
1031 # Without any options, consider p2 only if the
1032 # patch is being applied on top of the recorded
1032 # patch is being applied on top of the recorded
1033 # first parent.
1033 # first parent.
1034 if p1 != parents[0]:
1034 if p1 != parents[0]:
1035 p1 = parents[0]
1035 p1 = parents[0]
1036 p2 = repo[nullid]
1036 p2 = repo[nullid]
1037 except error.RepoError:
1037 except error.RepoError:
1038 p1, p2 = parents
1038 p1, p2 = parents
1039 if p2.node() == nullid:
1039 if p2.node() == nullid:
1040 ui.warn(_("warning: import the patch as a normal revision\n"
1040 ui.warn(_("warning: import the patch as a normal revision\n"
1041 "(use --exact to import the patch as a merge)\n"))
1041 "(use --exact to import the patch as a merge)\n"))
1042 else:
1042 else:
1043 p1, p2 = parents
1043 p1, p2 = parents
1044
1044
1045 n = None
1045 n = None
1046 if update:
1046 if update:
1047 if p1 != parents[0]:
1047 if p1 != parents[0]:
1048 updatefunc(repo, p1.node())
1048 updatefunc(repo, p1.node())
1049 if p2 != parents[1]:
1049 if p2 != parents[1]:
1050 repo.setparents(p1.node(), p2.node())
1050 repo.setparents(p1.node(), p2.node())
1051
1051
1052 if opts.get('exact') or importbranch:
1052 if opts.get('exact') or importbranch:
1053 repo.dirstate.setbranch(branch or 'default')
1053 repo.dirstate.setbranch(branch or 'default')
1054
1054
1055 partial = opts.get('partial', False)
1055 partial = opts.get('partial', False)
1056 files = set()
1056 files = set()
1057 try:
1057 try:
1058 patch.patch(ui, repo, tmpname, strip=strip, prefix=prefix,
1058 patch.patch(ui, repo, tmpname, strip=strip, prefix=prefix,
1059 files=files, eolmode=None, similarity=sim / 100.0)
1059 files=files, eolmode=None, similarity=sim / 100.0)
1060 except patch.PatchError as e:
1060 except patch.PatchError as e:
1061 if not partial:
1061 if not partial:
1062 raise error.Abort(str(e))
1062 raise error.Abort(str(e))
1063 if partial:
1063 if partial:
1064 rejects = True
1064 rejects = True
1065
1065
1066 files = list(files)
1066 files = list(files)
1067 if nocommit:
1067 if nocommit:
1068 if message:
1068 if message:
1069 msgs.append(message)
1069 msgs.append(message)
1070 else:
1070 else:
1071 if opts.get('exact') or p2:
1071 if opts.get('exact') or p2:
1072 # If you got here, you either use --force and know what
1072 # If you got here, you either use --force and know what
1073 # you are doing or used --exact or a merge patch while
1073 # you are doing or used --exact or a merge patch while
1074 # being updated to its first parent.
1074 # being updated to its first parent.
1075 m = None
1075 m = None
1076 else:
1076 else:
1077 m = scmutil.matchfiles(repo, files or [])
1077 m = scmutil.matchfiles(repo, files or [])
1078 editform = mergeeditform(repo[None], 'import.normal')
1078 editform = mergeeditform(repo[None], 'import.normal')
1079 if opts.get('exact'):
1079 if opts.get('exact'):
1080 editor = None
1080 editor = None
1081 else:
1081 else:
1082 editor = getcommiteditor(editform=editform, **opts)
1082 editor = getcommiteditor(editform=editform, **opts)
1083 extra = {}
1083 extra = {}
1084 for idfunc in extrapreimport:
1084 for idfunc in extrapreimport:
1085 extrapreimportmap[idfunc](repo, extractdata, extra, opts)
1085 extrapreimportmap[idfunc](repo, extractdata, extra, opts)
1086 overrides = {}
1086 overrides = {}
1087 if partial:
1087 if partial:
1088 overrides[('ui', 'allowemptycommit')] = True
1088 overrides[('ui', 'allowemptycommit')] = True
1089 with repo.ui.configoverride(overrides, 'import'):
1089 with repo.ui.configoverride(overrides, 'import'):
1090 n = repo.commit(message, user,
1090 n = repo.commit(message, user,
1091 date, match=m,
1091 date, match=m,
1092 editor=editor, extra=extra)
1092 editor=editor, extra=extra)
1093 for idfunc in extrapostimport:
1093 for idfunc in extrapostimport:
1094 extrapostimportmap[idfunc](repo[n])
1094 extrapostimportmap[idfunc](repo[n])
1095 else:
1095 else:
1096 if opts.get('exact') or importbranch:
1096 if opts.get('exact') or importbranch:
1097 branch = branch or 'default'
1097 branch = branch or 'default'
1098 else:
1098 else:
1099 branch = p1.branch()
1099 branch = p1.branch()
1100 store = patch.filestore()
1100 store = patch.filestore()
1101 try:
1101 try:
1102 files = set()
1102 files = set()
1103 try:
1103 try:
1104 patch.patchrepo(ui, repo, p1, store, tmpname, strip, prefix,
1104 patch.patchrepo(ui, repo, p1, store, tmpname, strip, prefix,
1105 files, eolmode=None)
1105 files, eolmode=None)
1106 except patch.PatchError as e:
1106 except patch.PatchError as e:
1107 raise error.Abort(str(e))
1107 raise error.Abort(str(e))
1108 if opts.get('exact'):
1108 if opts.get('exact'):
1109 editor = None
1109 editor = None
1110 else:
1110 else:
1111 editor = getcommiteditor(editform='import.bypass')
1111 editor = getcommiteditor(editform='import.bypass')
1112 memctx = context.makememctx(repo, (p1.node(), p2.node()),
1112 memctx = context.makememctx(repo, (p1.node(), p2.node()),
1113 message,
1113 message,
1114 user,
1114 user,
1115 date,
1115 date,
1116 branch, files, store,
1116 branch, files, store,
1117 editor=editor)
1117 editor=editor)
1118 n = memctx.commit()
1118 n = memctx.commit()
1119 finally:
1119 finally:
1120 store.close()
1120 store.close()
1121 if opts.get('exact') and nocommit:
1121 if opts.get('exact') and nocommit:
1122 # --exact with --no-commit is still useful in that it does merge
1122 # --exact with --no-commit is still useful in that it does merge
1123 # and branch bits
1123 # and branch bits
1124 ui.warn(_("warning: can't check exact import with --no-commit\n"))
1124 ui.warn(_("warning: can't check exact import with --no-commit\n"))
1125 elif opts.get('exact') and hex(n) != nodeid:
1125 elif opts.get('exact') and hex(n) != nodeid:
1126 raise error.Abort(_('patch is damaged or loses information'))
1126 raise error.Abort(_('patch is damaged or loses information'))
1127 msg = _('applied to working directory')
1127 msg = _('applied to working directory')
1128 if n:
1128 if n:
1129 # i18n: refers to a short changeset id
1129 # i18n: refers to a short changeset id
1130 msg = _('created %s') % short(n)
1130 msg = _('created %s') % short(n)
1131 return (msg, n, rejects)
1131 return (msg, n, rejects)
1132 finally:
1132 finally:
1133 os.unlink(tmpname)
1133 os.unlink(tmpname)
1134
1134
1135 # facility to let extensions include additional data in an exported patch
1135 # facility to let extensions include additional data in an exported patch
1136 # list of identifiers to be executed in order
1136 # list of identifiers to be executed in order
1137 extraexport = []
1137 extraexport = []
1138 # mapping from identifier to actual export function
1138 # mapping from identifier to actual export function
1139 # function as to return a string to be added to the header or None
1139 # function as to return a string to be added to the header or None
1140 # it is given two arguments (sequencenumber, changectx)
1140 # it is given two arguments (sequencenumber, changectx)
1141 extraexportmap = {}
1141 extraexportmap = {}
1142
1142
1143 def _exportsingle(repo, ctx, match, switch_parent, rev, seqno, write, diffopts):
1143 def _exportsingle(repo, ctx, match, switch_parent, rev, seqno, write, diffopts):
1144 node = ctx.node()
1144 node = ctx.node()
1145 parents = [p.node() for p in ctx.parents() if p]
1145 parents = [p.node() for p in ctx.parents() if p]
1146 branch = ctx.branch()
1146 branch = ctx.branch()
1147 if switch_parent:
1147 if switch_parent:
1148 parents.reverse()
1148 parents.reverse()
1149
1149
1150 if parents:
1150 if parents:
1151 prev = parents[0]
1151 prev = parents[0]
1152 else:
1152 else:
1153 prev = nullid
1153 prev = nullid
1154
1154
1155 write("# HG changeset patch\n")
1155 write("# HG changeset patch\n")
1156 write("# User %s\n" % ctx.user())
1156 write("# User %s\n" % ctx.user())
1157 write("# Date %d %d\n" % ctx.date())
1157 write("# Date %d %d\n" % ctx.date())
1158 write("# %s\n" % util.datestr(ctx.date()))
1158 write("# %s\n" % util.datestr(ctx.date()))
1159 if branch and branch != 'default':
1159 if branch and branch != 'default':
1160 write("# Branch %s\n" % branch)
1160 write("# Branch %s\n" % branch)
1161 write("# Node ID %s\n" % hex(node))
1161 write("# Node ID %s\n" % hex(node))
1162 write("# Parent %s\n" % hex(prev))
1162 write("# Parent %s\n" % hex(prev))
1163 if len(parents) > 1:
1163 if len(parents) > 1:
1164 write("# Parent %s\n" % hex(parents[1]))
1164 write("# Parent %s\n" % hex(parents[1]))
1165
1165
1166 for headerid in extraexport:
1166 for headerid in extraexport:
1167 header = extraexportmap[headerid](seqno, ctx)
1167 header = extraexportmap[headerid](seqno, ctx)
1168 if header is not None:
1168 if header is not None:
1169 write('# %s\n' % header)
1169 write('# %s\n' % header)
1170 write(ctx.description().rstrip())
1170 write(ctx.description().rstrip())
1171 write("\n\n")
1171 write("\n\n")
1172
1172
1173 for chunk, label in patch.diffui(repo, prev, node, match, opts=diffopts):
1173 for chunk, label in patch.diffui(repo, prev, node, match, opts=diffopts):
1174 write(chunk, label=label)
1174 write(chunk, label=label)
1175
1175
1176 def export(repo, revs, fntemplate='hg-%h.patch', fp=None, switch_parent=False,
1176 def export(repo, revs, fntemplate='hg-%h.patch', fp=None, switch_parent=False,
1177 opts=None, match=None):
1177 opts=None, match=None):
1178 '''export changesets as hg patches
1178 '''export changesets as hg patches
1179
1179
1180 Args:
1180 Args:
1181 repo: The repository from which we're exporting revisions.
1181 repo: The repository from which we're exporting revisions.
1182 revs: A list of revisions to export as revision numbers.
1182 revs: A list of revisions to export as revision numbers.
1183 fntemplate: An optional string to use for generating patch file names.
1183 fntemplate: An optional string to use for generating patch file names.
1184 fp: An optional file-like object to which patches should be written.
1184 fp: An optional file-like object to which patches should be written.
1185 switch_parent: If True, show diffs against second parent when not nullid.
1185 switch_parent: If True, show diffs against second parent when not nullid.
1186 Default is false, which always shows diff against p1.
1186 Default is false, which always shows diff against p1.
1187 opts: diff options to use for generating the patch.
1187 opts: diff options to use for generating the patch.
1188 match: If specified, only export changes to files matching this matcher.
1188 match: If specified, only export changes to files matching this matcher.
1189
1189
1190 Returns:
1190 Returns:
1191 Nothing.
1191 Nothing.
1192
1192
1193 Side Effect:
1193 Side Effect:
1194 "HG Changeset Patch" data is emitted to one of the following
1194 "HG Changeset Patch" data is emitted to one of the following
1195 destinations:
1195 destinations:
1196 fp is specified: All revs are written to the specified
1196 fp is specified: All revs are written to the specified
1197 file-like object.
1197 file-like object.
1198 fntemplate specified: Each rev is written to a unique file named using
1198 fntemplate specified: Each rev is written to a unique file named using
1199 the given template.
1199 the given template.
1200 Neither fp nor template specified: All revs written to repo.ui.write()
1200 Neither fp nor template specified: All revs written to repo.ui.write()
1201 '''
1201 '''
1202
1202
1203 total = len(revs)
1203 total = len(revs)
1204 revwidth = max(len(str(rev)) for rev in revs)
1204 revwidth = max(len(str(rev)) for rev in revs)
1205 filemode = {}
1205 filemode = {}
1206
1206
1207 write = None
1207 write = None
1208 dest = '<unnamed>'
1208 dest = '<unnamed>'
1209 if fp:
1209 if fp:
1210 dest = getattr(fp, 'name', dest)
1210 dest = getattr(fp, 'name', dest)
1211 def write(s, **kw):
1211 def write(s, **kw):
1212 fp.write(s)
1212 fp.write(s)
1213 elif not fntemplate:
1213 elif not fntemplate:
1214 write = repo.ui.write
1214 write = repo.ui.write
1215
1215
1216 for seqno, rev in enumerate(revs, 1):
1216 for seqno, rev in enumerate(revs, 1):
1217 ctx = repo[rev]
1217 ctx = repo[rev]
1218 fo = None
1218 fo = None
1219 if not fp and fntemplate:
1219 if not fp and fntemplate:
1220 desc_lines = ctx.description().rstrip().split('\n')
1220 desc_lines = ctx.description().rstrip().split('\n')
1221 desc = desc_lines[0] #Commit always has a first line.
1221 desc = desc_lines[0] #Commit always has a first line.
1222 fo = makefileobj(repo, fntemplate, ctx.node(), desc=desc,
1222 fo = makefileobj(repo, fntemplate, ctx.node(), desc=desc,
1223 total=total, seqno=seqno, revwidth=revwidth,
1223 total=total, seqno=seqno, revwidth=revwidth,
1224 mode='wb', modemap=filemode)
1224 mode='wb', modemap=filemode)
1225 dest = fo.name
1225 dest = fo.name
1226 def write(s, **kw):
1226 def write(s, **kw):
1227 fo.write(s)
1227 fo.write(s)
1228 if not dest.startswith('<'):
1228 if not dest.startswith('<'):
1229 repo.ui.note("%s\n" % dest)
1229 repo.ui.note("%s\n" % dest)
1230 _exportsingle(
1230 _exportsingle(
1231 repo, ctx, match, switch_parent, rev, seqno, write, opts)
1231 repo, ctx, match, switch_parent, rev, seqno, write, opts)
1232 if fo is not None:
1232 if fo is not None:
1233 fo.close()
1233 fo.close()
1234
1234
1235 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
1235 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
1236 changes=None, stat=False, fp=None, prefix='',
1236 changes=None, stat=False, fp=None, prefix='',
1237 root='', listsubrepos=False):
1237 root='', listsubrepos=False):
1238 '''show diff or diffstat.'''
1238 '''show diff or diffstat.'''
1239 if fp is None:
1239 if fp is None:
1240 write = ui.write
1240 write = ui.write
1241 else:
1241 else:
1242 def write(s, **kw):
1242 def write(s, **kw):
1243 fp.write(s)
1243 fp.write(s)
1244
1244
1245 if root:
1245 if root:
1246 relroot = pathutil.canonpath(repo.root, repo.getcwd(), root)
1246 relroot = pathutil.canonpath(repo.root, repo.getcwd(), root)
1247 else:
1247 else:
1248 relroot = ''
1248 relroot = ''
1249 if relroot != '':
1249 if relroot != '':
1250 # XXX relative roots currently don't work if the root is within a
1250 # XXX relative roots currently don't work if the root is within a
1251 # subrepo
1251 # subrepo
1252 uirelroot = match.uipath(relroot)
1252 uirelroot = match.uipath(relroot)
1253 relroot += '/'
1253 relroot += '/'
1254 for matchroot in match.files():
1254 for matchroot in match.files():
1255 if not matchroot.startswith(relroot):
1255 if not matchroot.startswith(relroot):
1256 ui.warn(_('warning: %s not inside relative root %s\n') % (
1256 ui.warn(_('warning: %s not inside relative root %s\n') % (
1257 match.uipath(matchroot), uirelroot))
1257 match.uipath(matchroot), uirelroot))
1258
1258
1259 if stat:
1259 if stat:
1260 diffopts = diffopts.copy(context=0)
1260 diffopts = diffopts.copy(context=0)
1261 width = 80
1261 width = 80
1262 if not ui.plain():
1262 if not ui.plain():
1263 width = ui.termwidth()
1263 width = ui.termwidth()
1264 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
1264 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
1265 prefix=prefix, relroot=relroot)
1265 prefix=prefix, relroot=relroot)
1266 for chunk, label in patch.diffstatui(util.iterlines(chunks),
1266 for chunk, label in patch.diffstatui(util.iterlines(chunks),
1267 width=width):
1267 width=width):
1268 write(chunk, label=label)
1268 write(chunk, label=label)
1269 else:
1269 else:
1270 for chunk, label in patch.diffui(repo, node1, node2, match,
1270 for chunk, label in patch.diffui(repo, node1, node2, match,
1271 changes, diffopts, prefix=prefix,
1271 changes, diffopts, prefix=prefix,
1272 relroot=relroot):
1272 relroot=relroot):
1273 write(chunk, label=label)
1273 write(chunk, label=label)
1274
1274
1275 if listsubrepos:
1275 if listsubrepos:
1276 ctx1 = repo[node1]
1276 ctx1 = repo[node1]
1277 ctx2 = repo[node2]
1277 ctx2 = repo[node2]
1278 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
1278 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
1279 tempnode2 = node2
1279 tempnode2 = node2
1280 try:
1280 try:
1281 if node2 is not None:
1281 if node2 is not None:
1282 tempnode2 = ctx2.substate[subpath][1]
1282 tempnode2 = ctx2.substate[subpath][1]
1283 except KeyError:
1283 except KeyError:
1284 # A subrepo that existed in node1 was deleted between node1 and
1284 # A subrepo that existed in node1 was deleted between node1 and
1285 # node2 (inclusive). Thus, ctx2's substate won't contain that
1285 # node2 (inclusive). Thus, ctx2's substate won't contain that
1286 # subpath. The best we can do is to ignore it.
1286 # subpath. The best we can do is to ignore it.
1287 tempnode2 = None
1287 tempnode2 = None
1288 submatch = matchmod.subdirmatcher(subpath, match)
1288 submatch = matchmod.subdirmatcher(subpath, match)
1289 sub.diff(ui, diffopts, tempnode2, submatch, changes=changes,
1289 sub.diff(ui, diffopts, tempnode2, submatch, changes=changes,
1290 stat=stat, fp=fp, prefix=prefix)
1290 stat=stat, fp=fp, prefix=prefix)
1291
1291
1292 def _changesetlabels(ctx):
1292 def _changesetlabels(ctx):
1293 labels = ['log.changeset', 'changeset.%s' % ctx.phasestr()]
1293 labels = ['log.changeset', 'changeset.%s' % ctx.phasestr()]
1294 if ctx.obsolete():
1294 if ctx.obsolete():
1295 labels.append('changeset.obsolete')
1295 labels.append('changeset.obsolete')
1296 if ctx.troubled():
1296 if ctx.troubled():
1297 labels.append('changeset.troubled')
1297 labels.append('changeset.troubled')
1298 for trouble in ctx.troubles():
1298 for trouble in ctx.troubles():
1299 labels.append('trouble.%s' % trouble)
1299 labels.append('trouble.%s' % trouble)
1300 return ' '.join(labels)
1300 return ' '.join(labels)
1301
1301
1302 class changeset_printer(object):
1302 class changeset_printer(object):
1303 '''show changeset information when templating not requested.'''
1303 '''show changeset information when templating not requested.'''
1304
1304
1305 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1305 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1306 self.ui = ui
1306 self.ui = ui
1307 self.repo = repo
1307 self.repo = repo
1308 self.buffered = buffered
1308 self.buffered = buffered
1309 self.matchfn = matchfn
1309 self.matchfn = matchfn
1310 self.diffopts = diffopts
1310 self.diffopts = diffopts
1311 self.header = {}
1311 self.header = {}
1312 self.hunk = {}
1312 self.hunk = {}
1313 self.lastheader = None
1313 self.lastheader = None
1314 self.footer = None
1314 self.footer = None
1315
1315
1316 def flush(self, ctx):
1316 def flush(self, ctx):
1317 rev = ctx.rev()
1317 rev = ctx.rev()
1318 if rev in self.header:
1318 if rev in self.header:
1319 h = self.header[rev]
1319 h = self.header[rev]
1320 if h != self.lastheader:
1320 if h != self.lastheader:
1321 self.lastheader = h
1321 self.lastheader = h
1322 self.ui.write(h)
1322 self.ui.write(h)
1323 del self.header[rev]
1323 del self.header[rev]
1324 if rev in self.hunk:
1324 if rev in self.hunk:
1325 self.ui.write(self.hunk[rev])
1325 self.ui.write(self.hunk[rev])
1326 del self.hunk[rev]
1326 del self.hunk[rev]
1327 return 1
1327 return 1
1328 return 0
1328 return 0
1329
1329
1330 def close(self):
1330 def close(self):
1331 if self.footer:
1331 if self.footer:
1332 self.ui.write(self.footer)
1332 self.ui.write(self.footer)
1333
1333
1334 def show(self, ctx, copies=None, matchfn=None, **props):
1334 def show(self, ctx, copies=None, matchfn=None, **props):
1335 if self.buffered:
1335 if self.buffered:
1336 self.ui.pushbuffer(labeled=True)
1336 self.ui.pushbuffer(labeled=True)
1337 self._show(ctx, copies, matchfn, props)
1337 self._show(ctx, copies, matchfn, props)
1338 self.hunk[ctx.rev()] = self.ui.popbuffer()
1338 self.hunk[ctx.rev()] = self.ui.popbuffer()
1339 else:
1339 else:
1340 self._show(ctx, copies, matchfn, props)
1340 self._show(ctx, copies, matchfn, props)
1341
1341
1342 def _show(self, ctx, copies, matchfn, props):
1342 def _show(self, ctx, copies, matchfn, props):
1343 '''show a single changeset or file revision'''
1343 '''show a single changeset or file revision'''
1344 changenode = ctx.node()
1344 changenode = ctx.node()
1345 rev = ctx.rev()
1345 rev = ctx.rev()
1346 if self.ui.debugflag:
1346 if self.ui.debugflag:
1347 hexfunc = hex
1347 hexfunc = hex
1348 else:
1348 else:
1349 hexfunc = short
1349 hexfunc = short
1350 # as of now, wctx.node() and wctx.rev() return None, but we want to
1350 # as of now, wctx.node() and wctx.rev() return None, but we want to
1351 # show the same values as {node} and {rev} templatekw
1351 # show the same values as {node} and {rev} templatekw
1352 revnode = (scmutil.intrev(rev), hexfunc(bin(ctx.hex())))
1352 revnode = (scmutil.intrev(rev), hexfunc(bin(ctx.hex())))
1353
1353
1354 if self.ui.quiet:
1354 if self.ui.quiet:
1355 self.ui.write("%d:%s\n" % revnode, label='log.node')
1355 self.ui.write("%d:%s\n" % revnode, label='log.node')
1356 return
1356 return
1357
1357
1358 date = util.datestr(ctx.date())
1358 date = util.datestr(ctx.date())
1359
1359
1360 # i18n: column positioning for "hg log"
1360 # i18n: column positioning for "hg log"
1361 self.ui.write(_("changeset: %d:%s\n") % revnode,
1361 self.ui.write(_("changeset: %d:%s\n") % revnode,
1362 label=_changesetlabels(ctx))
1362 label=_changesetlabels(ctx))
1363
1363
1364 # branches are shown first before any other names due to backwards
1364 # branches are shown first before any other names due to backwards
1365 # compatibility
1365 # compatibility
1366 branch = ctx.branch()
1366 branch = ctx.branch()
1367 # don't show the default branch name
1367 # don't show the default branch name
1368 if branch != 'default':
1368 if branch != 'default':
1369 # i18n: column positioning for "hg log"
1369 # i18n: column positioning for "hg log"
1370 self.ui.write(_("branch: %s\n") % branch,
1370 self.ui.write(_("branch: %s\n") % branch,
1371 label='log.branch')
1371 label='log.branch')
1372
1372
1373 for nsname, ns in self.repo.names.iteritems():
1373 for nsname, ns in self.repo.names.iteritems():
1374 # branches has special logic already handled above, so here we just
1374 # branches has special logic already handled above, so here we just
1375 # skip it
1375 # skip it
1376 if nsname == 'branches':
1376 if nsname == 'branches':
1377 continue
1377 continue
1378 # we will use the templatename as the color name since those two
1378 # we will use the templatename as the color name since those two
1379 # should be the same
1379 # should be the same
1380 for name in ns.names(self.repo, changenode):
1380 for name in ns.names(self.repo, changenode):
1381 self.ui.write(ns.logfmt % name,
1381 self.ui.write(ns.logfmt % name,
1382 label='log.%s' % ns.colorname)
1382 label='log.%s' % ns.colorname)
1383 if self.ui.debugflag:
1383 if self.ui.debugflag:
1384 # i18n: column positioning for "hg log"
1384 # i18n: column positioning for "hg log"
1385 self.ui.write(_("phase: %s\n") % ctx.phasestr(),
1385 self.ui.write(_("phase: %s\n") % ctx.phasestr(),
1386 label='log.phase')
1386 label='log.phase')
1387 for pctx in scmutil.meaningfulparents(self.repo, ctx):
1387 for pctx in scmutil.meaningfulparents(self.repo, ctx):
1388 label = 'log.parent changeset.%s' % pctx.phasestr()
1388 label = 'log.parent changeset.%s' % pctx.phasestr()
1389 # i18n: column positioning for "hg log"
1389 # i18n: column positioning for "hg log"
1390 self.ui.write(_("parent: %d:%s\n")
1390 self.ui.write(_("parent: %d:%s\n")
1391 % (pctx.rev(), hexfunc(pctx.node())),
1391 % (pctx.rev(), hexfunc(pctx.node())),
1392 label=label)
1392 label=label)
1393
1393
1394 if self.ui.debugflag and rev is not None:
1394 if self.ui.debugflag and rev is not None:
1395 mnode = ctx.manifestnode()
1395 mnode = ctx.manifestnode()
1396 # i18n: column positioning for "hg log"
1396 # i18n: column positioning for "hg log"
1397 self.ui.write(_("manifest: %d:%s\n") %
1397 self.ui.write(_("manifest: %d:%s\n") %
1398 (self.repo.manifestlog._revlog.rev(mnode),
1398 (self.repo.manifestlog._revlog.rev(mnode),
1399 hex(mnode)),
1399 hex(mnode)),
1400 label='ui.debug log.manifest')
1400 label='ui.debug log.manifest')
1401 # i18n: column positioning for "hg log"
1401 # i18n: column positioning for "hg log"
1402 self.ui.write(_("user: %s\n") % ctx.user(),
1402 self.ui.write(_("user: %s\n") % ctx.user(),
1403 label='log.user')
1403 label='log.user')
1404 # i18n: column positioning for "hg log"
1404 # i18n: column positioning for "hg log"
1405 self.ui.write(_("date: %s\n") % date,
1405 self.ui.write(_("date: %s\n") % date,
1406 label='log.date')
1406 label='log.date')
1407
1407
1408 if ctx.troubled():
1408 if ctx.troubled():
1409 # i18n: column positioning for "hg log"
1409 # i18n: column positioning for "hg log"
1410 self.ui.write(_("trouble: %s\n") % ', '.join(ctx.troubles()),
1410 self.ui.write(_("trouble: %s\n") % ', '.join(ctx.troubles()),
1411 label='log.trouble')
1411 label='log.trouble')
1412
1412
1413 if self.ui.debugflag:
1413 if self.ui.debugflag:
1414 files = ctx.p1().status(ctx)[:3]
1414 files = ctx.p1().status(ctx)[:3]
1415 for key, value in zip([# i18n: column positioning for "hg log"
1415 for key, value in zip([# i18n: column positioning for "hg log"
1416 _("files:"),
1416 _("files:"),
1417 # i18n: column positioning for "hg log"
1417 # i18n: column positioning for "hg log"
1418 _("files+:"),
1418 _("files+:"),
1419 # i18n: column positioning for "hg log"
1419 # i18n: column positioning for "hg log"
1420 _("files-:")], files):
1420 _("files-:")], files):
1421 if value:
1421 if value:
1422 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
1422 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
1423 label='ui.debug log.files')
1423 label='ui.debug log.files')
1424 elif ctx.files() and self.ui.verbose:
1424 elif ctx.files() and self.ui.verbose:
1425 # i18n: column positioning for "hg log"
1425 # i18n: column positioning for "hg log"
1426 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
1426 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
1427 label='ui.note log.files')
1427 label='ui.note log.files')
1428 if copies and self.ui.verbose:
1428 if copies and self.ui.verbose:
1429 copies = ['%s (%s)' % c for c in copies]
1429 copies = ['%s (%s)' % c for c in copies]
1430 # i18n: column positioning for "hg log"
1430 # i18n: column positioning for "hg log"
1431 self.ui.write(_("copies: %s\n") % ' '.join(copies),
1431 self.ui.write(_("copies: %s\n") % ' '.join(copies),
1432 label='ui.note log.copies')
1432 label='ui.note log.copies')
1433
1433
1434 extra = ctx.extra()
1434 extra = ctx.extra()
1435 if extra and self.ui.debugflag:
1435 if extra and self.ui.debugflag:
1436 for key, value in sorted(extra.items()):
1436 for key, value in sorted(extra.items()):
1437 # i18n: column positioning for "hg log"
1437 # i18n: column positioning for "hg log"
1438 self.ui.write(_("extra: %s=%s\n")
1438 self.ui.write(_("extra: %s=%s\n")
1439 % (key, util.escapestr(value)),
1439 % (key, util.escapestr(value)),
1440 label='ui.debug log.extra')
1440 label='ui.debug log.extra')
1441
1441
1442 description = ctx.description().strip()
1442 description = ctx.description().strip()
1443 if description:
1443 if description:
1444 if self.ui.verbose:
1444 if self.ui.verbose:
1445 self.ui.write(_("description:\n"),
1445 self.ui.write(_("description:\n"),
1446 label='ui.note log.description')
1446 label='ui.note log.description')
1447 self.ui.write(description,
1447 self.ui.write(description,
1448 label='ui.note log.description')
1448 label='ui.note log.description')
1449 self.ui.write("\n\n")
1449 self.ui.write("\n\n")
1450 else:
1450 else:
1451 # i18n: column positioning for "hg log"
1451 # i18n: column positioning for "hg log"
1452 self.ui.write(_("summary: %s\n") %
1452 self.ui.write(_("summary: %s\n") %
1453 description.splitlines()[0],
1453 description.splitlines()[0],
1454 label='log.summary')
1454 label='log.summary')
1455 self.ui.write("\n")
1455 self.ui.write("\n")
1456
1456
1457 self.showpatch(ctx, matchfn)
1457 self.showpatch(ctx, matchfn)
1458
1458
1459 def showpatch(self, ctx, matchfn):
1459 def showpatch(self, ctx, matchfn):
1460 if not matchfn:
1460 if not matchfn:
1461 matchfn = self.matchfn
1461 matchfn = self.matchfn
1462 if matchfn:
1462 if matchfn:
1463 stat = self.diffopts.get('stat')
1463 stat = self.diffopts.get('stat')
1464 diff = self.diffopts.get('patch')
1464 diff = self.diffopts.get('patch')
1465 diffopts = patch.diffallopts(self.ui, self.diffopts)
1465 diffopts = patch.diffallopts(self.ui, self.diffopts)
1466 node = ctx.node()
1466 node = ctx.node()
1467 prev = ctx.p1().node()
1467 prev = ctx.p1().node()
1468 if stat:
1468 if stat:
1469 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1469 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1470 match=matchfn, stat=True)
1470 match=matchfn, stat=True)
1471 if diff:
1471 if diff:
1472 if stat:
1472 if stat:
1473 self.ui.write("\n")
1473 self.ui.write("\n")
1474 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1474 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1475 match=matchfn, stat=False)
1475 match=matchfn, stat=False)
1476 self.ui.write("\n")
1476 self.ui.write("\n")
1477
1477
1478 class jsonchangeset(changeset_printer):
1478 class jsonchangeset(changeset_printer):
1479 '''format changeset information.'''
1479 '''format changeset information.'''
1480
1480
1481 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1481 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1482 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1482 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1483 self.cache = {}
1483 self.cache = {}
1484 self._first = True
1484 self._first = True
1485
1485
1486 def close(self):
1486 def close(self):
1487 if not self._first:
1487 if not self._first:
1488 self.ui.write("\n]\n")
1488 self.ui.write("\n]\n")
1489 else:
1489 else:
1490 self.ui.write("[]\n")
1490 self.ui.write("[]\n")
1491
1491
1492 def _show(self, ctx, copies, matchfn, props):
1492 def _show(self, ctx, copies, matchfn, props):
1493 '''show a single changeset or file revision'''
1493 '''show a single changeset or file revision'''
1494 rev = ctx.rev()
1494 rev = ctx.rev()
1495 if rev is None:
1495 if rev is None:
1496 jrev = jnode = 'null'
1496 jrev = jnode = 'null'
1497 else:
1497 else:
1498 jrev = '%d' % rev
1498 jrev = '%d' % rev
1499 jnode = '"%s"' % hex(ctx.node())
1499 jnode = '"%s"' % hex(ctx.node())
1500 j = encoding.jsonescape
1500 j = encoding.jsonescape
1501
1501
1502 if self._first:
1502 if self._first:
1503 self.ui.write("[\n {")
1503 self.ui.write("[\n {")
1504 self._first = False
1504 self._first = False
1505 else:
1505 else:
1506 self.ui.write(",\n {")
1506 self.ui.write(",\n {")
1507
1507
1508 if self.ui.quiet:
1508 if self.ui.quiet:
1509 self.ui.write(('\n "rev": %s') % jrev)
1509 self.ui.write(('\n "rev": %s') % jrev)
1510 self.ui.write((',\n "node": %s') % jnode)
1510 self.ui.write((',\n "node": %s') % jnode)
1511 self.ui.write('\n }')
1511 self.ui.write('\n }')
1512 return
1512 return
1513
1513
1514 self.ui.write(('\n "rev": %s') % jrev)
1514 self.ui.write(('\n "rev": %s') % jrev)
1515 self.ui.write((',\n "node": %s') % jnode)
1515 self.ui.write((',\n "node": %s') % jnode)
1516 self.ui.write((',\n "branch": "%s"') % j(ctx.branch()))
1516 self.ui.write((',\n "branch": "%s"') % j(ctx.branch()))
1517 self.ui.write((',\n "phase": "%s"') % ctx.phasestr())
1517 self.ui.write((',\n "phase": "%s"') % ctx.phasestr())
1518 self.ui.write((',\n "user": "%s"') % j(ctx.user()))
1518 self.ui.write((',\n "user": "%s"') % j(ctx.user()))
1519 self.ui.write((',\n "date": [%d, %d]') % ctx.date())
1519 self.ui.write((',\n "date": [%d, %d]') % ctx.date())
1520 self.ui.write((',\n "desc": "%s"') % j(ctx.description()))
1520 self.ui.write((',\n "desc": "%s"') % j(ctx.description()))
1521
1521
1522 self.ui.write((',\n "bookmarks": [%s]') %
1522 self.ui.write((',\n "bookmarks": [%s]') %
1523 ", ".join('"%s"' % j(b) for b in ctx.bookmarks()))
1523 ", ".join('"%s"' % j(b) for b in ctx.bookmarks()))
1524 self.ui.write((',\n "tags": [%s]') %
1524 self.ui.write((',\n "tags": [%s]') %
1525 ", ".join('"%s"' % j(t) for t in ctx.tags()))
1525 ", ".join('"%s"' % j(t) for t in ctx.tags()))
1526 self.ui.write((',\n "parents": [%s]') %
1526 self.ui.write((',\n "parents": [%s]') %
1527 ", ".join('"%s"' % c.hex() for c in ctx.parents()))
1527 ", ".join('"%s"' % c.hex() for c in ctx.parents()))
1528
1528
1529 if self.ui.debugflag:
1529 if self.ui.debugflag:
1530 if rev is None:
1530 if rev is None:
1531 jmanifestnode = 'null'
1531 jmanifestnode = 'null'
1532 else:
1532 else:
1533 jmanifestnode = '"%s"' % hex(ctx.manifestnode())
1533 jmanifestnode = '"%s"' % hex(ctx.manifestnode())
1534 self.ui.write((',\n "manifest": %s') % jmanifestnode)
1534 self.ui.write((',\n "manifest": %s') % jmanifestnode)
1535
1535
1536 self.ui.write((',\n "extra": {%s}') %
1536 self.ui.write((',\n "extra": {%s}') %
1537 ", ".join('"%s": "%s"' % (j(k), j(v))
1537 ", ".join('"%s": "%s"' % (j(k), j(v))
1538 for k, v in ctx.extra().items()))
1538 for k, v in ctx.extra().items()))
1539
1539
1540 files = ctx.p1().status(ctx)
1540 files = ctx.p1().status(ctx)
1541 self.ui.write((',\n "modified": [%s]') %
1541 self.ui.write((',\n "modified": [%s]') %
1542 ", ".join('"%s"' % j(f) for f in files[0]))
1542 ", ".join('"%s"' % j(f) for f in files[0]))
1543 self.ui.write((',\n "added": [%s]') %
1543 self.ui.write((',\n "added": [%s]') %
1544 ", ".join('"%s"' % j(f) for f in files[1]))
1544 ", ".join('"%s"' % j(f) for f in files[1]))
1545 self.ui.write((',\n "removed": [%s]') %
1545 self.ui.write((',\n "removed": [%s]') %
1546 ", ".join('"%s"' % j(f) for f in files[2]))
1546 ", ".join('"%s"' % j(f) for f in files[2]))
1547
1547
1548 elif self.ui.verbose:
1548 elif self.ui.verbose:
1549 self.ui.write((',\n "files": [%s]') %
1549 self.ui.write((',\n "files": [%s]') %
1550 ", ".join('"%s"' % j(f) for f in ctx.files()))
1550 ", ".join('"%s"' % j(f) for f in ctx.files()))
1551
1551
1552 if copies:
1552 if copies:
1553 self.ui.write((',\n "copies": {%s}') %
1553 self.ui.write((',\n "copies": {%s}') %
1554 ", ".join('"%s": "%s"' % (j(k), j(v))
1554 ", ".join('"%s": "%s"' % (j(k), j(v))
1555 for k, v in copies))
1555 for k, v in copies))
1556
1556
1557 matchfn = self.matchfn
1557 matchfn = self.matchfn
1558 if matchfn:
1558 if matchfn:
1559 stat = self.diffopts.get('stat')
1559 stat = self.diffopts.get('stat')
1560 diff = self.diffopts.get('patch')
1560 diff = self.diffopts.get('patch')
1561 diffopts = patch.difffeatureopts(self.ui, self.diffopts, git=True)
1561 diffopts = patch.difffeatureopts(self.ui, self.diffopts, git=True)
1562 node, prev = ctx.node(), ctx.p1().node()
1562 node, prev = ctx.node(), ctx.p1().node()
1563 if stat:
1563 if stat:
1564 self.ui.pushbuffer()
1564 self.ui.pushbuffer()
1565 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1565 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1566 match=matchfn, stat=True)
1566 match=matchfn, stat=True)
1567 self.ui.write((',\n "diffstat": "%s"')
1567 self.ui.write((',\n "diffstat": "%s"')
1568 % j(self.ui.popbuffer()))
1568 % j(self.ui.popbuffer()))
1569 if diff:
1569 if diff:
1570 self.ui.pushbuffer()
1570 self.ui.pushbuffer()
1571 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1571 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1572 match=matchfn, stat=False)
1572 match=matchfn, stat=False)
1573 self.ui.write((',\n "diff": "%s"') % j(self.ui.popbuffer()))
1573 self.ui.write((',\n "diff": "%s"') % j(self.ui.popbuffer()))
1574
1574
1575 self.ui.write("\n }")
1575 self.ui.write("\n }")
1576
1576
1577 class changeset_templater(changeset_printer):
1577 class changeset_templater(changeset_printer):
1578 '''format changeset information.'''
1578 '''format changeset information.'''
1579
1579
1580 def __init__(self, ui, repo, matchfn, diffopts, tmpl, mapfile, buffered):
1580 def __init__(self, ui, repo, matchfn, diffopts, tmpl, mapfile, buffered):
1581 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1581 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1582 assert not (tmpl and mapfile)
1582 assert not (tmpl and mapfile)
1583 defaulttempl = templatekw.defaulttempl
1583 defaulttempl = templatekw.defaulttempl
1584 if mapfile:
1584 if mapfile:
1585 self.t = templater.templater.frommapfile(mapfile,
1585 self.t = templater.templater.frommapfile(mapfile,
1586 cache=defaulttempl)
1586 cache=defaulttempl)
1587 else:
1587 else:
1588 self.t = formatter.maketemplater(ui, 'changeset', tmpl,
1588 self.t = formatter.maketemplater(ui, 'changeset', tmpl,
1589 cache=defaulttempl)
1589 cache=defaulttempl)
1590
1590
1591 self._counter = itertools.count()
1591 self._counter = itertools.count()
1592 self.cache = {}
1592 self.cache = {}
1593
1593
1594 # find correct templates for current mode
1594 # find correct templates for current mode
1595 tmplmodes = [
1595 tmplmodes = [
1596 (True, None),
1596 (True, None),
1597 (self.ui.verbose, 'verbose'),
1597 (self.ui.verbose, 'verbose'),
1598 (self.ui.quiet, 'quiet'),
1598 (self.ui.quiet, 'quiet'),
1599 (self.ui.debugflag, 'debug'),
1599 (self.ui.debugflag, 'debug'),
1600 ]
1600 ]
1601
1601
1602 self._parts = {'header': '', 'footer': '', 'changeset': 'changeset',
1602 self._parts = {'header': '', 'footer': '', 'changeset': 'changeset',
1603 'docheader': '', 'docfooter': ''}
1603 'docheader': '', 'docfooter': ''}
1604 for mode, postfix in tmplmodes:
1604 for mode, postfix in tmplmodes:
1605 for t in self._parts:
1605 for t in self._parts:
1606 cur = t
1606 cur = t
1607 if postfix:
1607 if postfix:
1608 cur += "_" + postfix
1608 cur += "_" + postfix
1609 if mode and cur in self.t:
1609 if mode and cur in self.t:
1610 self._parts[t] = cur
1610 self._parts[t] = cur
1611
1611
1612 if self._parts['docheader']:
1612 if self._parts['docheader']:
1613 self.ui.write(templater.stringify(self.t(self._parts['docheader'])))
1613 self.ui.write(templater.stringify(self.t(self._parts['docheader'])))
1614
1614
1615 def close(self):
1615 def close(self):
1616 if self._parts['docfooter']:
1616 if self._parts['docfooter']:
1617 if not self.footer:
1617 if not self.footer:
1618 self.footer = ""
1618 self.footer = ""
1619 self.footer += templater.stringify(self.t(self._parts['docfooter']))
1619 self.footer += templater.stringify(self.t(self._parts['docfooter']))
1620 return super(changeset_templater, self).close()
1620 return super(changeset_templater, self).close()
1621
1621
1622 def _show(self, ctx, copies, matchfn, props):
1622 def _show(self, ctx, copies, matchfn, props):
1623 '''show a single changeset or file revision'''
1623 '''show a single changeset or file revision'''
1624 props = props.copy()
1624 props = props.copy()
1625 props.update(templatekw.keywords)
1625 props.update(templatekw.keywords)
1626 props['templ'] = self.t
1626 props['templ'] = self.t
1627 props['ctx'] = ctx
1627 props['ctx'] = ctx
1628 props['repo'] = self.repo
1628 props['repo'] = self.repo
1629 props['ui'] = self.repo.ui
1629 props['ui'] = self.repo.ui
1630 props['index'] = next(self._counter)
1630 props['index'] = next(self._counter)
1631 props['revcache'] = {'copies': copies}
1631 props['revcache'] = {'copies': copies}
1632 props['cache'] = self.cache
1632 props['cache'] = self.cache
1633 props = pycompat.strkwargs(props)
1633 props = pycompat.strkwargs(props)
1634
1634
1635 # write header
1635 # write header
1636 if self._parts['header']:
1636 if self._parts['header']:
1637 h = templater.stringify(self.t(self._parts['header'], **props))
1637 h = templater.stringify(self.t(self._parts['header'], **props))
1638 if self.buffered:
1638 if self.buffered:
1639 self.header[ctx.rev()] = h
1639 self.header[ctx.rev()] = h
1640 else:
1640 else:
1641 if self.lastheader != h:
1641 if self.lastheader != h:
1642 self.lastheader = h
1642 self.lastheader = h
1643 self.ui.write(h)
1643 self.ui.write(h)
1644
1644
1645 # write changeset metadata, then patch if requested
1645 # write changeset metadata, then patch if requested
1646 key = self._parts['changeset']
1646 key = self._parts['changeset']
1647 self.ui.write(templater.stringify(self.t(key, **props)))
1647 self.ui.write(templater.stringify(self.t(key, **props)))
1648 self.showpatch(ctx, matchfn)
1648 self.showpatch(ctx, matchfn)
1649
1649
1650 if self._parts['footer']:
1650 if self._parts['footer']:
1651 if not self.footer:
1651 if not self.footer:
1652 self.footer = templater.stringify(
1652 self.footer = templater.stringify(
1653 self.t(self._parts['footer'], **props))
1653 self.t(self._parts['footer'], **props))
1654
1654
1655 def gettemplate(ui, tmpl, style):
1655 def gettemplate(ui, tmpl, style):
1656 """
1656 """
1657 Find the template matching the given template spec or style.
1657 Find the template matching the given template spec or style.
1658 """
1658 """
1659
1659
1660 # ui settings
1660 # ui settings
1661 if not tmpl and not style: # template are stronger than style
1661 if not tmpl and not style: # template are stronger than style
1662 tmpl = ui.config('ui', 'logtemplate')
1662 tmpl = ui.config('ui', 'logtemplate')
1663 if tmpl:
1663 if tmpl:
1664 return templater.unquotestring(tmpl), None
1664 return templater.unquotestring(tmpl), None
1665 else:
1665 else:
1666 style = util.expandpath(ui.config('ui', 'style', ''))
1666 style = util.expandpath(ui.config('ui', 'style', ''))
1667
1667
1668 if not tmpl and style:
1668 if not tmpl and style:
1669 mapfile = style
1669 mapfile = style
1670 if not os.path.split(mapfile)[0]:
1670 if not os.path.split(mapfile)[0]:
1671 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1671 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1672 or templater.templatepath(mapfile))
1672 or templater.templatepath(mapfile))
1673 if mapname:
1673 if mapname:
1674 mapfile = mapname
1674 mapfile = mapname
1675 return None, mapfile
1675 return None, mapfile
1676
1676
1677 if not tmpl:
1677 if not tmpl:
1678 return None, None
1678 return None, None
1679
1679
1680 return formatter.lookuptemplate(ui, 'changeset', tmpl)
1680 return formatter.lookuptemplate(ui, 'changeset', tmpl)
1681
1681
1682 def show_changeset(ui, repo, opts, buffered=False):
1682 def show_changeset(ui, repo, opts, buffered=False):
1683 """show one changeset using template or regular display.
1683 """show one changeset using template or regular display.
1684
1684
1685 Display format will be the first non-empty hit of:
1685 Display format will be the first non-empty hit of:
1686 1. option 'template'
1686 1. option 'template'
1687 2. option 'style'
1687 2. option 'style'
1688 3. [ui] setting 'logtemplate'
1688 3. [ui] setting 'logtemplate'
1689 4. [ui] setting 'style'
1689 4. [ui] setting 'style'
1690 If all of these values are either the unset or the empty string,
1690 If all of these values are either the unset or the empty string,
1691 regular display via changeset_printer() is done.
1691 regular display via changeset_printer() is done.
1692 """
1692 """
1693 # options
1693 # options
1694 matchfn = None
1694 matchfn = None
1695 if opts.get('patch') or opts.get('stat'):
1695 if opts.get('patch') or opts.get('stat'):
1696 matchfn = scmutil.matchall(repo)
1696 matchfn = scmutil.matchall(repo)
1697
1697
1698 if opts.get('template') == 'json':
1698 if opts.get('template') == 'json':
1699 return jsonchangeset(ui, repo, matchfn, opts, buffered)
1699 return jsonchangeset(ui, repo, matchfn, opts, buffered)
1700
1700
1701 tmpl, mapfile = gettemplate(ui, opts.get('template'), opts.get('style'))
1701 tmpl, mapfile = gettemplate(ui, opts.get('template'), opts.get('style'))
1702
1702
1703 if not tmpl and not mapfile:
1703 if not tmpl and not mapfile:
1704 return changeset_printer(ui, repo, matchfn, opts, buffered)
1704 return changeset_printer(ui, repo, matchfn, opts, buffered)
1705
1705
1706 return changeset_templater(ui, repo, matchfn, opts, tmpl, mapfile, buffered)
1706 return changeset_templater(ui, repo, matchfn, opts, tmpl, mapfile, buffered)
1707
1707
1708 def showmarker(fm, marker, index=None):
1708 def showmarker(fm, marker, index=None):
1709 """utility function to display obsolescence marker in a readable way
1709 """utility function to display obsolescence marker in a readable way
1710
1710
1711 To be used by debug function."""
1711 To be used by debug function."""
1712 if index is not None:
1712 if index is not None:
1713 fm.write('index', '%i ', index)
1713 fm.write('index', '%i ', index)
1714 fm.write('precnode', '%s ', hex(marker.precnode()))
1714 fm.write('precnode', '%s ', hex(marker.precnode()))
1715 succs = marker.succnodes()
1715 succs = marker.succnodes()
1716 fm.condwrite(succs, 'succnodes', '%s ',
1716 fm.condwrite(succs, 'succnodes', '%s ',
1717 fm.formatlist(map(hex, succs), name='node'))
1717 fm.formatlist(map(hex, succs), name='node'))
1718 fm.write('flag', '%X ', marker.flags())
1718 fm.write('flag', '%X ', marker.flags())
1719 parents = marker.parentnodes()
1719 parents = marker.parentnodes()
1720 if parents is not None:
1720 if parents is not None:
1721 fm.write('parentnodes', '{%s} ',
1721 fm.write('parentnodes', '{%s} ',
1722 fm.formatlist(map(hex, parents), name='node', sep=', '))
1722 fm.formatlist(map(hex, parents), name='node', sep=', '))
1723 fm.write('date', '(%s) ', fm.formatdate(marker.date()))
1723 fm.write('date', '(%s) ', fm.formatdate(marker.date()))
1724 meta = marker.metadata().copy()
1724 meta = marker.metadata().copy()
1725 meta.pop('date', None)
1725 meta.pop('date', None)
1726 fm.write('metadata', '{%s}', fm.formatdict(meta, fmt='%r: %r', sep=', '))
1726 fm.write('metadata', '{%s}', fm.formatdict(meta, fmt='%r: %r', sep=', '))
1727 fm.plain('\n')
1727 fm.plain('\n')
1728
1728
1729 def finddate(ui, repo, date):
1729 def finddate(ui, repo, date):
1730 """Find the tipmost changeset that matches the given date spec"""
1730 """Find the tipmost changeset that matches the given date spec"""
1731
1731
1732 df = util.matchdate(date)
1732 df = util.matchdate(date)
1733 m = scmutil.matchall(repo)
1733 m = scmutil.matchall(repo)
1734 results = {}
1734 results = {}
1735
1735
1736 def prep(ctx, fns):
1736 def prep(ctx, fns):
1737 d = ctx.date()
1737 d = ctx.date()
1738 if df(d[0]):
1738 if df(d[0]):
1739 results[ctx.rev()] = d
1739 results[ctx.rev()] = d
1740
1740
1741 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1741 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1742 rev = ctx.rev()
1742 rev = ctx.rev()
1743 if rev in results:
1743 if rev in results:
1744 ui.status(_("found revision %s from %s\n") %
1744 ui.status(_("found revision %s from %s\n") %
1745 (rev, util.datestr(results[rev])))
1745 (rev, util.datestr(results[rev])))
1746 return '%d' % rev
1746 return '%d' % rev
1747
1747
1748 raise error.Abort(_("revision matching date not found"))
1748 raise error.Abort(_("revision matching date not found"))
1749
1749
1750 def increasingwindows(windowsize=8, sizelimit=512):
1750 def increasingwindows(windowsize=8, sizelimit=512):
1751 while True:
1751 while True:
1752 yield windowsize
1752 yield windowsize
1753 if windowsize < sizelimit:
1753 if windowsize < sizelimit:
1754 windowsize *= 2
1754 windowsize *= 2
1755
1755
1756 class FileWalkError(Exception):
1756 class FileWalkError(Exception):
1757 pass
1757 pass
1758
1758
1759 def walkfilerevs(repo, match, follow, revs, fncache):
1759 def walkfilerevs(repo, match, follow, revs, fncache):
1760 '''Walks the file history for the matched files.
1760 '''Walks the file history for the matched files.
1761
1761
1762 Returns the changeset revs that are involved in the file history.
1762 Returns the changeset revs that are involved in the file history.
1763
1763
1764 Throws FileWalkError if the file history can't be walked using
1764 Throws FileWalkError if the file history can't be walked using
1765 filelogs alone.
1765 filelogs alone.
1766 '''
1766 '''
1767 wanted = set()
1767 wanted = set()
1768 copies = []
1768 copies = []
1769 minrev, maxrev = min(revs), max(revs)
1769 minrev, maxrev = min(revs), max(revs)
1770 def filerevgen(filelog, last):
1770 def filerevgen(filelog, last):
1771 """
1771 """
1772 Only files, no patterns. Check the history of each file.
1772 Only files, no patterns. Check the history of each file.
1773
1773
1774 Examines filelog entries within minrev, maxrev linkrev range
1774 Examines filelog entries within minrev, maxrev linkrev range
1775 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1775 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1776 tuples in backwards order
1776 tuples in backwards order
1777 """
1777 """
1778 cl_count = len(repo)
1778 cl_count = len(repo)
1779 revs = []
1779 revs = []
1780 for j in xrange(0, last + 1):
1780 for j in xrange(0, last + 1):
1781 linkrev = filelog.linkrev(j)
1781 linkrev = filelog.linkrev(j)
1782 if linkrev < minrev:
1782 if linkrev < minrev:
1783 continue
1783 continue
1784 # only yield rev for which we have the changelog, it can
1784 # only yield rev for which we have the changelog, it can
1785 # happen while doing "hg log" during a pull or commit
1785 # happen while doing "hg log" during a pull or commit
1786 if linkrev >= cl_count:
1786 if linkrev >= cl_count:
1787 break
1787 break
1788
1788
1789 parentlinkrevs = []
1789 parentlinkrevs = []
1790 for p in filelog.parentrevs(j):
1790 for p in filelog.parentrevs(j):
1791 if p != nullrev:
1791 if p != nullrev:
1792 parentlinkrevs.append(filelog.linkrev(p))
1792 parentlinkrevs.append(filelog.linkrev(p))
1793 n = filelog.node(j)
1793 n = filelog.node(j)
1794 revs.append((linkrev, parentlinkrevs,
1794 revs.append((linkrev, parentlinkrevs,
1795 follow and filelog.renamed(n)))
1795 follow and filelog.renamed(n)))
1796
1796
1797 return reversed(revs)
1797 return reversed(revs)
1798 def iterfiles():
1798 def iterfiles():
1799 pctx = repo['.']
1799 pctx = repo['.']
1800 for filename in match.files():
1800 for filename in match.files():
1801 if follow:
1801 if follow:
1802 if filename not in pctx:
1802 if filename not in pctx:
1803 raise error.Abort(_('cannot follow file not in parent '
1803 raise error.Abort(_('cannot follow file not in parent '
1804 'revision: "%s"') % filename)
1804 'revision: "%s"') % filename)
1805 yield filename, pctx[filename].filenode()
1805 yield filename, pctx[filename].filenode()
1806 else:
1806 else:
1807 yield filename, None
1807 yield filename, None
1808 for filename_node in copies:
1808 for filename_node in copies:
1809 yield filename_node
1809 yield filename_node
1810
1810
1811 for file_, node in iterfiles():
1811 for file_, node in iterfiles():
1812 filelog = repo.file(file_)
1812 filelog = repo.file(file_)
1813 if not len(filelog):
1813 if not len(filelog):
1814 if node is None:
1814 if node is None:
1815 # A zero count may be a directory or deleted file, so
1815 # A zero count may be a directory or deleted file, so
1816 # try to find matching entries on the slow path.
1816 # try to find matching entries on the slow path.
1817 if follow:
1817 if follow:
1818 raise error.Abort(
1818 raise error.Abort(
1819 _('cannot follow nonexistent file: "%s"') % file_)
1819 _('cannot follow nonexistent file: "%s"') % file_)
1820 raise FileWalkError("Cannot walk via filelog")
1820 raise FileWalkError("Cannot walk via filelog")
1821 else:
1821 else:
1822 continue
1822 continue
1823
1823
1824 if node is None:
1824 if node is None:
1825 last = len(filelog) - 1
1825 last = len(filelog) - 1
1826 else:
1826 else:
1827 last = filelog.rev(node)
1827 last = filelog.rev(node)
1828
1828
1829 # keep track of all ancestors of the file
1829 # keep track of all ancestors of the file
1830 ancestors = {filelog.linkrev(last)}
1830 ancestors = {filelog.linkrev(last)}
1831
1831
1832 # iterate from latest to oldest revision
1832 # iterate from latest to oldest revision
1833 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1833 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1834 if not follow:
1834 if not follow:
1835 if rev > maxrev:
1835 if rev > maxrev:
1836 continue
1836 continue
1837 else:
1837 else:
1838 # Note that last might not be the first interesting
1838 # Note that last might not be the first interesting
1839 # rev to us:
1839 # rev to us:
1840 # if the file has been changed after maxrev, we'll
1840 # if the file has been changed after maxrev, we'll
1841 # have linkrev(last) > maxrev, and we still need
1841 # have linkrev(last) > maxrev, and we still need
1842 # to explore the file graph
1842 # to explore the file graph
1843 if rev not in ancestors:
1843 if rev not in ancestors:
1844 continue
1844 continue
1845 # XXX insert 1327 fix here
1845 # XXX insert 1327 fix here
1846 if flparentlinkrevs:
1846 if flparentlinkrevs:
1847 ancestors.update(flparentlinkrevs)
1847 ancestors.update(flparentlinkrevs)
1848
1848
1849 fncache.setdefault(rev, []).append(file_)
1849 fncache.setdefault(rev, []).append(file_)
1850 wanted.add(rev)
1850 wanted.add(rev)
1851 if copied:
1851 if copied:
1852 copies.append(copied)
1852 copies.append(copied)
1853
1853
1854 return wanted
1854 return wanted
1855
1855
1856 class _followfilter(object):
1856 class _followfilter(object):
1857 def __init__(self, repo, onlyfirst=False):
1857 def __init__(self, repo, onlyfirst=False):
1858 self.repo = repo
1858 self.repo = repo
1859 self.startrev = nullrev
1859 self.startrev = nullrev
1860 self.roots = set()
1860 self.roots = set()
1861 self.onlyfirst = onlyfirst
1861 self.onlyfirst = onlyfirst
1862
1862
1863 def match(self, rev):
1863 def match(self, rev):
1864 def realparents(rev):
1864 def realparents(rev):
1865 if self.onlyfirst:
1865 if self.onlyfirst:
1866 return self.repo.changelog.parentrevs(rev)[0:1]
1866 return self.repo.changelog.parentrevs(rev)[0:1]
1867 else:
1867 else:
1868 return filter(lambda x: x != nullrev,
1868 return filter(lambda x: x != nullrev,
1869 self.repo.changelog.parentrevs(rev))
1869 self.repo.changelog.parentrevs(rev))
1870
1870
1871 if self.startrev == nullrev:
1871 if self.startrev == nullrev:
1872 self.startrev = rev
1872 self.startrev = rev
1873 return True
1873 return True
1874
1874
1875 if rev > self.startrev:
1875 if rev > self.startrev:
1876 # forward: all descendants
1876 # forward: all descendants
1877 if not self.roots:
1877 if not self.roots:
1878 self.roots.add(self.startrev)
1878 self.roots.add(self.startrev)
1879 for parent in realparents(rev):
1879 for parent in realparents(rev):
1880 if parent in self.roots:
1880 if parent in self.roots:
1881 self.roots.add(rev)
1881 self.roots.add(rev)
1882 return True
1882 return True
1883 else:
1883 else:
1884 # backwards: all parents
1884 # backwards: all parents
1885 if not self.roots:
1885 if not self.roots:
1886 self.roots.update(realparents(self.startrev))
1886 self.roots.update(realparents(self.startrev))
1887 if rev in self.roots:
1887 if rev in self.roots:
1888 self.roots.remove(rev)
1888 self.roots.remove(rev)
1889 self.roots.update(realparents(rev))
1889 self.roots.update(realparents(rev))
1890 return True
1890 return True
1891
1891
1892 return False
1892 return False
1893
1893
1894 def walkchangerevs(repo, match, opts, prepare):
1894 def walkchangerevs(repo, match, opts, prepare):
1895 '''Iterate over files and the revs in which they changed.
1895 '''Iterate over files and the revs in which they changed.
1896
1896
1897 Callers most commonly need to iterate backwards over the history
1897 Callers most commonly need to iterate backwards over the history
1898 in which they are interested. Doing so has awful (quadratic-looking)
1898 in which they are interested. Doing so has awful (quadratic-looking)
1899 performance, so we use iterators in a "windowed" way.
1899 performance, so we use iterators in a "windowed" way.
1900
1900
1901 We walk a window of revisions in the desired order. Within the
1901 We walk a window of revisions in the desired order. Within the
1902 window, we first walk forwards to gather data, then in the desired
1902 window, we first walk forwards to gather data, then in the desired
1903 order (usually backwards) to display it.
1903 order (usually backwards) to display it.
1904
1904
1905 This function returns an iterator yielding contexts. Before
1905 This function returns an iterator yielding contexts. Before
1906 yielding each context, the iterator will first call the prepare
1906 yielding each context, the iterator will first call the prepare
1907 function on each context in the window in forward order.'''
1907 function on each context in the window in forward order.'''
1908
1908
1909 follow = opts.get('follow') or opts.get('follow_first')
1909 follow = opts.get('follow') or opts.get('follow_first')
1910 revs = _logrevs(repo, opts)
1910 revs = _logrevs(repo, opts)
1911 if not revs:
1911 if not revs:
1912 return []
1912 return []
1913 wanted = set()
1913 wanted = set()
1914 slowpath = match.anypats() or ((match.isexact() or match.prefix()) and
1914 slowpath = match.anypats() or ((match.isexact() or match.prefix()) and
1915 opts.get('removed'))
1915 opts.get('removed'))
1916 fncache = {}
1916 fncache = {}
1917 change = repo.changectx
1917 change = repo.changectx
1918
1918
1919 # First step is to fill wanted, the set of revisions that we want to yield.
1919 # First step is to fill wanted, the set of revisions that we want to yield.
1920 # When it does not induce extra cost, we also fill fncache for revisions in
1920 # When it does not induce extra cost, we also fill fncache for revisions in
1921 # wanted: a cache of filenames that were changed (ctx.files()) and that
1921 # wanted: a cache of filenames that were changed (ctx.files()) and that
1922 # match the file filtering conditions.
1922 # match the file filtering conditions.
1923
1923
1924 if match.always():
1924 if match.always():
1925 # No files, no patterns. Display all revs.
1925 # No files, no patterns. Display all revs.
1926 wanted = revs
1926 wanted = revs
1927 elif not slowpath:
1927 elif not slowpath:
1928 # We only have to read through the filelog to find wanted revisions
1928 # We only have to read through the filelog to find wanted revisions
1929
1929
1930 try:
1930 try:
1931 wanted = walkfilerevs(repo, match, follow, revs, fncache)
1931 wanted = walkfilerevs(repo, match, follow, revs, fncache)
1932 except FileWalkError:
1932 except FileWalkError:
1933 slowpath = True
1933 slowpath = True
1934
1934
1935 # We decided to fall back to the slowpath because at least one
1935 # We decided to fall back to the slowpath because at least one
1936 # of the paths was not a file. Check to see if at least one of them
1936 # of the paths was not a file. Check to see if at least one of them
1937 # existed in history, otherwise simply return
1937 # existed in history, otherwise simply return
1938 for path in match.files():
1938 for path in match.files():
1939 if path == '.' or path in repo.store:
1939 if path == '.' or path in repo.store:
1940 break
1940 break
1941 else:
1941 else:
1942 return []
1942 return []
1943
1943
1944 if slowpath:
1944 if slowpath:
1945 # We have to read the changelog to match filenames against
1945 # We have to read the changelog to match filenames against
1946 # changed files
1946 # changed files
1947
1947
1948 if follow:
1948 if follow:
1949 raise error.Abort(_('can only follow copies/renames for explicit '
1949 raise error.Abort(_('can only follow copies/renames for explicit '
1950 'filenames'))
1950 'filenames'))
1951
1951
1952 # The slow path checks files modified in every changeset.
1952 # The slow path checks files modified in every changeset.
1953 # This is really slow on large repos, so compute the set lazily.
1953 # This is really slow on large repos, so compute the set lazily.
1954 class lazywantedset(object):
1954 class lazywantedset(object):
1955 def __init__(self):
1955 def __init__(self):
1956 self.set = set()
1956 self.set = set()
1957 self.revs = set(revs)
1957 self.revs = set(revs)
1958
1958
1959 # No need to worry about locality here because it will be accessed
1959 # No need to worry about locality here because it will be accessed
1960 # in the same order as the increasing window below.
1960 # in the same order as the increasing window below.
1961 def __contains__(self, value):
1961 def __contains__(self, value):
1962 if value in self.set:
1962 if value in self.set:
1963 return True
1963 return True
1964 elif not value in self.revs:
1964 elif not value in self.revs:
1965 return False
1965 return False
1966 else:
1966 else:
1967 self.revs.discard(value)
1967 self.revs.discard(value)
1968 ctx = change(value)
1968 ctx = change(value)
1969 matches = filter(match, ctx.files())
1969 matches = filter(match, ctx.files())
1970 if matches:
1970 if matches:
1971 fncache[value] = matches
1971 fncache[value] = matches
1972 self.set.add(value)
1972 self.set.add(value)
1973 return True
1973 return True
1974 return False
1974 return False
1975
1975
1976 def discard(self, value):
1976 def discard(self, value):
1977 self.revs.discard(value)
1977 self.revs.discard(value)
1978 self.set.discard(value)
1978 self.set.discard(value)
1979
1979
1980 wanted = lazywantedset()
1980 wanted = lazywantedset()
1981
1981
1982 # it might be worthwhile to do this in the iterator if the rev range
1982 # it might be worthwhile to do this in the iterator if the rev range
1983 # is descending and the prune args are all within that range
1983 # is descending and the prune args are all within that range
1984 for rev in opts.get('prune', ()):
1984 for rev in opts.get('prune', ()):
1985 rev = repo[rev].rev()
1985 rev = repo[rev].rev()
1986 ff = _followfilter(repo)
1986 ff = _followfilter(repo)
1987 stop = min(revs[0], revs[-1])
1987 stop = min(revs[0], revs[-1])
1988 for x in xrange(rev, stop - 1, -1):
1988 for x in xrange(rev, stop - 1, -1):
1989 if ff.match(x):
1989 if ff.match(x):
1990 wanted = wanted - [x]
1990 wanted = wanted - [x]
1991
1991
1992 # Now that wanted is correctly initialized, we can iterate over the
1992 # Now that wanted is correctly initialized, we can iterate over the
1993 # revision range, yielding only revisions in wanted.
1993 # revision range, yielding only revisions in wanted.
1994 def iterate():
1994 def iterate():
1995 if follow and match.always():
1995 if follow and match.always():
1996 ff = _followfilter(repo, onlyfirst=opts.get('follow_first'))
1996 ff = _followfilter(repo, onlyfirst=opts.get('follow_first'))
1997 def want(rev):
1997 def want(rev):
1998 return ff.match(rev) and rev in wanted
1998 return ff.match(rev) and rev in wanted
1999 else:
1999 else:
2000 def want(rev):
2000 def want(rev):
2001 return rev in wanted
2001 return rev in wanted
2002
2002
2003 it = iter(revs)
2003 it = iter(revs)
2004 stopiteration = False
2004 stopiteration = False
2005 for windowsize in increasingwindows():
2005 for windowsize in increasingwindows():
2006 nrevs = []
2006 nrevs = []
2007 for i in xrange(windowsize):
2007 for i in xrange(windowsize):
2008 rev = next(it, None)
2008 rev = next(it, None)
2009 if rev is None:
2009 if rev is None:
2010 stopiteration = True
2010 stopiteration = True
2011 break
2011 break
2012 elif want(rev):
2012 elif want(rev):
2013 nrevs.append(rev)
2013 nrevs.append(rev)
2014 for rev in sorted(nrevs):
2014 for rev in sorted(nrevs):
2015 fns = fncache.get(rev)
2015 fns = fncache.get(rev)
2016 ctx = change(rev)
2016 ctx = change(rev)
2017 if not fns:
2017 if not fns:
2018 def fns_generator():
2018 def fns_generator():
2019 for f in ctx.files():
2019 for f in ctx.files():
2020 if match(f):
2020 if match(f):
2021 yield f
2021 yield f
2022 fns = fns_generator()
2022 fns = fns_generator()
2023 prepare(ctx, fns)
2023 prepare(ctx, fns)
2024 for rev in nrevs:
2024 for rev in nrevs:
2025 yield change(rev)
2025 yield change(rev)
2026
2026
2027 if stopiteration:
2027 if stopiteration:
2028 break
2028 break
2029
2029
2030 return iterate()
2030 return iterate()
2031
2031
2032 def _makefollowlogfilematcher(repo, files, followfirst):
2032 def _makefollowlogfilematcher(repo, files, followfirst):
2033 # When displaying a revision with --patch --follow FILE, we have
2033 # When displaying a revision with --patch --follow FILE, we have
2034 # to know which file of the revision must be diffed. With
2034 # to know which file of the revision must be diffed. With
2035 # --follow, we want the names of the ancestors of FILE in the
2035 # --follow, we want the names of the ancestors of FILE in the
2036 # revision, stored in "fcache". "fcache" is populated by
2036 # revision, stored in "fcache". "fcache" is populated by
2037 # reproducing the graph traversal already done by --follow revset
2037 # reproducing the graph traversal already done by --follow revset
2038 # and relating revs to file names (which is not "correct" but
2038 # and relating revs to file names (which is not "correct" but
2039 # good enough).
2039 # good enough).
2040 fcache = {}
2040 fcache = {}
2041 fcacheready = [False]
2041 fcacheready = [False]
2042 pctx = repo['.']
2042 pctx = repo['.']
2043
2043
2044 def populate():
2044 def populate():
2045 for fn in files:
2045 for fn in files:
2046 fctx = pctx[fn]
2046 fctx = pctx[fn]
2047 fcache.setdefault(fctx.introrev(), set()).add(fctx.path())
2047 fcache.setdefault(fctx.introrev(), set()).add(fctx.path())
2048 for c in fctx.ancestors(followfirst=followfirst):
2048 for c in fctx.ancestors(followfirst=followfirst):
2049 fcache.setdefault(c.rev(), set()).add(c.path())
2049 fcache.setdefault(c.rev(), set()).add(c.path())
2050
2050
2051 def filematcher(rev):
2051 def filematcher(rev):
2052 if not fcacheready[0]:
2052 if not fcacheready[0]:
2053 # Lazy initialization
2053 # Lazy initialization
2054 fcacheready[0] = True
2054 fcacheready[0] = True
2055 populate()
2055 populate()
2056 return scmutil.matchfiles(repo, fcache.get(rev, []))
2056 return scmutil.matchfiles(repo, fcache.get(rev, []))
2057
2057
2058 return filematcher
2058 return filematcher
2059
2059
2060 def _makenofollowlogfilematcher(repo, pats, opts):
2060 def _makenofollowlogfilematcher(repo, pats, opts):
2061 '''hook for extensions to override the filematcher for non-follow cases'''
2061 '''hook for extensions to override the filematcher for non-follow cases'''
2062 return None
2062 return None
2063
2063
2064 def _makelogrevset(repo, pats, opts, revs):
2064 def _makelogrevset(repo, pats, opts, revs):
2065 """Return (expr, filematcher) where expr is a revset string built
2065 """Return (expr, filematcher) where expr is a revset string built
2066 from log options and file patterns or None. If --stat or --patch
2066 from log options and file patterns or None. If --stat or --patch
2067 are not passed filematcher is None. Otherwise it is a callable
2067 are not passed filematcher is None. Otherwise it is a callable
2068 taking a revision number and returning a match objects filtering
2068 taking a revision number and returning a match objects filtering
2069 the files to be detailed when displaying the revision.
2069 the files to be detailed when displaying the revision.
2070 """
2070 """
2071 opt2revset = {
2071 opt2revset = {
2072 'no_merges': ('not merge()', None),
2072 'no_merges': ('not merge()', None),
2073 'only_merges': ('merge()', None),
2073 'only_merges': ('merge()', None),
2074 '_ancestors': ('ancestors(%(val)s)', None),
2074 '_ancestors': ('ancestors(%(val)s)', None),
2075 '_fancestors': ('_firstancestors(%(val)s)', None),
2075 '_fancestors': ('_firstancestors(%(val)s)', None),
2076 '_descendants': ('descendants(%(val)s)', None),
2076 '_descendants': ('descendants(%(val)s)', None),
2077 '_fdescendants': ('_firstdescendants(%(val)s)', None),
2077 '_fdescendants': ('_firstdescendants(%(val)s)', None),
2078 '_matchfiles': ('_matchfiles(%(val)s)', None),
2078 '_matchfiles': ('_matchfiles(%(val)s)', None),
2079 'date': ('date(%(val)r)', None),
2079 'date': ('date(%(val)r)', None),
2080 'branch': ('branch(%(val)r)', ' or '),
2080 'branch': ('branch(%(val)r)', ' or '),
2081 '_patslog': ('filelog(%(val)r)', ' or '),
2081 '_patslog': ('filelog(%(val)r)', ' or '),
2082 '_patsfollow': ('follow(%(val)r)', ' or '),
2082 '_patsfollow': ('follow(%(val)r)', ' or '),
2083 '_patsfollowfirst': ('_followfirst(%(val)r)', ' or '),
2083 '_patsfollowfirst': ('_followfirst(%(val)r)', ' or '),
2084 'keyword': ('keyword(%(val)r)', ' or '),
2084 'keyword': ('keyword(%(val)r)', ' or '),
2085 'prune': ('not (%(val)r or ancestors(%(val)r))', ' and '),
2085 'prune': ('not (%(val)r or ancestors(%(val)r))', ' and '),
2086 'user': ('user(%(val)r)', ' or '),
2086 'user': ('user(%(val)r)', ' or '),
2087 }
2087 }
2088
2088
2089 opts = dict(opts)
2089 opts = dict(opts)
2090 # follow or not follow?
2090 # follow or not follow?
2091 follow = opts.get('follow') or opts.get('follow_first')
2091 follow = opts.get('follow') or opts.get('follow_first')
2092 if opts.get('follow_first'):
2092 if opts.get('follow_first'):
2093 followfirst = 1
2093 followfirst = 1
2094 else:
2094 else:
2095 followfirst = 0
2095 followfirst = 0
2096 # --follow with FILE behavior depends on revs...
2096 # --follow with FILE behavior depends on revs...
2097 it = iter(revs)
2097 it = iter(revs)
2098 startrev = next(it)
2098 startrev = next(it)
2099 followdescendants = startrev < next(it, startrev)
2099 followdescendants = startrev < next(it, startrev)
2100
2100
2101 # branch and only_branch are really aliases and must be handled at
2101 # branch and only_branch are really aliases and must be handled at
2102 # the same time
2102 # the same time
2103 opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
2103 opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
2104 opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
2104 opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
2105 # pats/include/exclude are passed to match.match() directly in
2105 # pats/include/exclude are passed to match.match() directly in
2106 # _matchfiles() revset but walkchangerevs() builds its matcher with
2106 # _matchfiles() revset but walkchangerevs() builds its matcher with
2107 # scmutil.match(). The difference is input pats are globbed on
2107 # scmutil.match(). The difference is input pats are globbed on
2108 # platforms without shell expansion (windows).
2108 # platforms without shell expansion (windows).
2109 wctx = repo[None]
2109 wctx = repo[None]
2110 match, pats = scmutil.matchandpats(wctx, pats, opts)
2110 match, pats = scmutil.matchandpats(wctx, pats, opts)
2111 slowpath = match.anypats() or ((match.isexact() or match.prefix()) and
2111 slowpath = match.anypats() or ((match.isexact() or match.prefix()) and
2112 opts.get('removed'))
2112 opts.get('removed'))
2113 if not slowpath:
2113 if not slowpath:
2114 for f in match.files():
2114 for f in match.files():
2115 if follow and f not in wctx:
2115 if follow and f not in wctx:
2116 # If the file exists, it may be a directory, so let it
2116 # If the file exists, it may be a directory, so let it
2117 # take the slow path.
2117 # take the slow path.
2118 if os.path.exists(repo.wjoin(f)):
2118 if os.path.exists(repo.wjoin(f)):
2119 slowpath = True
2119 slowpath = True
2120 continue
2120 continue
2121 else:
2121 else:
2122 raise error.Abort(_('cannot follow file not in parent '
2122 raise error.Abort(_('cannot follow file not in parent '
2123 'revision: "%s"') % f)
2123 'revision: "%s"') % f)
2124 filelog = repo.file(f)
2124 filelog = repo.file(f)
2125 if not filelog:
2125 if not filelog:
2126 # A zero count may be a directory or deleted file, so
2126 # A zero count may be a directory or deleted file, so
2127 # try to find matching entries on the slow path.
2127 # try to find matching entries on the slow path.
2128 if follow:
2128 if follow:
2129 raise error.Abort(
2129 raise error.Abort(
2130 _('cannot follow nonexistent file: "%s"') % f)
2130 _('cannot follow nonexistent file: "%s"') % f)
2131 slowpath = True
2131 slowpath = True
2132
2132
2133 # We decided to fall back to the slowpath because at least one
2133 # We decided to fall back to the slowpath because at least one
2134 # of the paths was not a file. Check to see if at least one of them
2134 # of the paths was not a file. Check to see if at least one of them
2135 # existed in history - in that case, we'll continue down the
2135 # existed in history - in that case, we'll continue down the
2136 # slowpath; otherwise, we can turn off the slowpath
2136 # slowpath; otherwise, we can turn off the slowpath
2137 if slowpath:
2137 if slowpath:
2138 for path in match.files():
2138 for path in match.files():
2139 if path == '.' or path in repo.store:
2139 if path == '.' or path in repo.store:
2140 break
2140 break
2141 else:
2141 else:
2142 slowpath = False
2142 slowpath = False
2143
2143
2144 fpats = ('_patsfollow', '_patsfollowfirst')
2144 fpats = ('_patsfollow', '_patsfollowfirst')
2145 fnopats = (('_ancestors', '_fancestors'),
2145 fnopats = (('_ancestors', '_fancestors'),
2146 ('_descendants', '_fdescendants'))
2146 ('_descendants', '_fdescendants'))
2147 if slowpath:
2147 if slowpath:
2148 # See walkchangerevs() slow path.
2148 # See walkchangerevs() slow path.
2149 #
2149 #
2150 # pats/include/exclude cannot be represented as separate
2150 # pats/include/exclude cannot be represented as separate
2151 # revset expressions as their filtering logic applies at file
2151 # revset expressions as their filtering logic applies at file
2152 # level. For instance "-I a -X a" matches a revision touching
2152 # level. For instance "-I a -X a" matches a revision touching
2153 # "a" and "b" while "file(a) and not file(b)" does
2153 # "a" and "b" while "file(a) and not file(b)" does
2154 # not. Besides, filesets are evaluated against the working
2154 # not. Besides, filesets are evaluated against the working
2155 # directory.
2155 # directory.
2156 matchargs = ['r:', 'd:relpath']
2156 matchargs = ['r:', 'd:relpath']
2157 for p in pats:
2157 for p in pats:
2158 matchargs.append('p:' + p)
2158 matchargs.append('p:' + p)
2159 for p in opts.get('include', []):
2159 for p in opts.get('include', []):
2160 matchargs.append('i:' + p)
2160 matchargs.append('i:' + p)
2161 for p in opts.get('exclude', []):
2161 for p in opts.get('exclude', []):
2162 matchargs.append('x:' + p)
2162 matchargs.append('x:' + p)
2163 matchargs = ','.join(('%r' % p) for p in matchargs)
2163 matchargs = ','.join(('%r' % p) for p in matchargs)
2164 opts['_matchfiles'] = matchargs
2164 opts['_matchfiles'] = matchargs
2165 if follow:
2165 if follow:
2166 opts[fnopats[0][followfirst]] = '.'
2166 opts[fnopats[0][followfirst]] = '.'
2167 else:
2167 else:
2168 if follow:
2168 if follow:
2169 if pats:
2169 if pats:
2170 # follow() revset interprets its file argument as a
2170 # follow() revset interprets its file argument as a
2171 # manifest entry, so use match.files(), not pats.
2171 # manifest entry, so use match.files(), not pats.
2172 opts[fpats[followfirst]] = list(match.files())
2172 opts[fpats[followfirst]] = list(match.files())
2173 else:
2173 else:
2174 op = fnopats[followdescendants][followfirst]
2174 op = fnopats[followdescendants][followfirst]
2175 opts[op] = 'rev(%d)' % startrev
2175 opts[op] = 'rev(%d)' % startrev
2176 else:
2176 else:
2177 opts['_patslog'] = list(pats)
2177 opts['_patslog'] = list(pats)
2178
2178
2179 filematcher = None
2179 filematcher = None
2180 if opts.get('patch') or opts.get('stat'):
2180 if opts.get('patch') or opts.get('stat'):
2181 # When following files, track renames via a special matcher.
2181 # When following files, track renames via a special matcher.
2182 # If we're forced to take the slowpath it means we're following
2182 # If we're forced to take the slowpath it means we're following
2183 # at least one pattern/directory, so don't bother with rename tracking.
2183 # at least one pattern/directory, so don't bother with rename tracking.
2184 if follow and not match.always() and not slowpath:
2184 if follow and not match.always() and not slowpath:
2185 # _makefollowlogfilematcher expects its files argument to be
2185 # _makefollowlogfilematcher expects its files argument to be
2186 # relative to the repo root, so use match.files(), not pats.
2186 # relative to the repo root, so use match.files(), not pats.
2187 filematcher = _makefollowlogfilematcher(repo, match.files(),
2187 filematcher = _makefollowlogfilematcher(repo, match.files(),
2188 followfirst)
2188 followfirst)
2189 else:
2189 else:
2190 filematcher = _makenofollowlogfilematcher(repo, pats, opts)
2190 filematcher = _makenofollowlogfilematcher(repo, pats, opts)
2191 if filematcher is None:
2191 if filematcher is None:
2192 filematcher = lambda rev: match
2192 filematcher = lambda rev: match
2193
2193
2194 expr = []
2194 expr = []
2195 for op, val in sorted(opts.iteritems()):
2195 for op, val in sorted(opts.iteritems()):
2196 if not val:
2196 if not val:
2197 continue
2197 continue
2198 if op not in opt2revset:
2198 if op not in opt2revset:
2199 continue
2199 continue
2200 revop, andor = opt2revset[op]
2200 revop, andor = opt2revset[op]
2201 if '%(val)' not in revop:
2201 if '%(val)' not in revop:
2202 expr.append(revop)
2202 expr.append(revop)
2203 else:
2203 else:
2204 if not isinstance(val, list):
2204 if not isinstance(val, list):
2205 e = revop % {'val': val}
2205 e = revop % {'val': val}
2206 else:
2206 else:
2207 e = '(' + andor.join((revop % {'val': v}) for v in val) + ')'
2207 e = '(' + andor.join((revop % {'val': v}) for v in val) + ')'
2208 expr.append(e)
2208 expr.append(e)
2209
2209
2210 if expr:
2210 if expr:
2211 expr = '(' + ' and '.join(expr) + ')'
2211 expr = '(' + ' and '.join(expr) + ')'
2212 else:
2212 else:
2213 expr = None
2213 expr = None
2214 return expr, filematcher
2214 return expr, filematcher
2215
2215
2216 def _logrevs(repo, opts):
2216 def _logrevs(repo, opts):
2217 # Default --rev value depends on --follow but --follow behavior
2217 # Default --rev value depends on --follow but --follow behavior
2218 # depends on revisions resolved from --rev...
2218 # depends on revisions resolved from --rev...
2219 follow = opts.get('follow') or opts.get('follow_first')
2219 follow = opts.get('follow') or opts.get('follow_first')
2220 if opts.get('rev'):
2220 if opts.get('rev'):
2221 revs = scmutil.revrange(repo, opts['rev'])
2221 revs = scmutil.revrange(repo, opts['rev'])
2222 elif follow and repo.dirstate.p1() == nullid:
2222 elif follow and repo.dirstate.p1() == nullid:
2223 revs = smartset.baseset()
2223 revs = smartset.baseset()
2224 elif follow:
2224 elif follow:
2225 revs = repo.revs('reverse(:.)')
2225 revs = repo.revs('reverse(:.)')
2226 else:
2226 else:
2227 revs = smartset.spanset(repo)
2227 revs = smartset.spanset(repo)
2228 revs.reverse()
2228 revs.reverse()
2229 return revs
2229 return revs
2230
2230
2231 def getgraphlogrevs(repo, pats, opts):
2231 def getgraphlogrevs(repo, pats, opts):
2232 """Return (revs, expr, filematcher) where revs is an iterable of
2232 """Return (revs, expr, filematcher) where revs is an iterable of
2233 revision numbers, expr is a revset string built from log options
2233 revision numbers, expr is a revset string built from log options
2234 and file patterns or None, and used to filter 'revs'. If --stat or
2234 and file patterns or None, and used to filter 'revs'. If --stat or
2235 --patch are not passed filematcher is None. Otherwise it is a
2235 --patch are not passed filematcher is None. Otherwise it is a
2236 callable taking a revision number and returning a match objects
2236 callable taking a revision number and returning a match objects
2237 filtering the files to be detailed when displaying the revision.
2237 filtering the files to be detailed when displaying the revision.
2238 """
2238 """
2239 limit = loglimit(opts)
2239 limit = loglimit(opts)
2240 revs = _logrevs(repo, opts)
2240 revs = _logrevs(repo, opts)
2241 if not revs:
2241 if not revs:
2242 return smartset.baseset(), None, None
2242 return smartset.baseset(), None, None
2243 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2243 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2244 if opts.get('rev'):
2244 if opts.get('rev'):
2245 # User-specified revs might be unsorted, but don't sort before
2245 # User-specified revs might be unsorted, but don't sort before
2246 # _makelogrevset because it might depend on the order of revs
2246 # _makelogrevset because it might depend on the order of revs
2247 if not (revs.isdescending() or revs.istopo()):
2247 if not (revs.isdescending() or revs.istopo()):
2248 revs.sort(reverse=True)
2248 revs.sort(reverse=True)
2249 if expr:
2249 if expr:
2250 matcher = revset.match(repo.ui, expr, order=revset.followorder)
2250 matcher = revset.match(repo.ui, expr, order=revset.followorder)
2251 revs = matcher(repo, revs)
2251 revs = matcher(repo, revs)
2252 if limit is not None:
2252 if limit is not None:
2253 limitedrevs = []
2253 limitedrevs = []
2254 for idx, rev in enumerate(revs):
2254 for idx, rev in enumerate(revs):
2255 if idx >= limit:
2255 if idx >= limit:
2256 break
2256 break
2257 limitedrevs.append(rev)
2257 limitedrevs.append(rev)
2258 revs = smartset.baseset(limitedrevs)
2258 revs = smartset.baseset(limitedrevs)
2259
2259
2260 return revs, expr, filematcher
2260 return revs, expr, filematcher
2261
2261
2262 def getlogrevs(repo, pats, opts):
2262 def getlogrevs(repo, pats, opts):
2263 """Return (revs, expr, filematcher) where revs is an iterable of
2263 """Return (revs, expr, filematcher) where revs is an iterable of
2264 revision numbers, expr is a revset string built from log options
2264 revision numbers, expr is a revset string built from log options
2265 and file patterns or None, and used to filter 'revs'. If --stat or
2265 and file patterns or None, and used to filter 'revs'. If --stat or
2266 --patch are not passed filematcher is None. Otherwise it is a
2266 --patch are not passed filematcher is None. Otherwise it is a
2267 callable taking a revision number and returning a match objects
2267 callable taking a revision number and returning a match objects
2268 filtering the files to be detailed when displaying the revision.
2268 filtering the files to be detailed when displaying the revision.
2269 """
2269 """
2270 limit = loglimit(opts)
2270 limit = loglimit(opts)
2271 revs = _logrevs(repo, opts)
2271 revs = _logrevs(repo, opts)
2272 if not revs:
2272 if not revs:
2273 return smartset.baseset([]), None, None
2273 return smartset.baseset([]), None, None
2274 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2274 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2275 if expr:
2275 if expr:
2276 matcher = revset.match(repo.ui, expr, order=revset.followorder)
2276 matcher = revset.match(repo.ui, expr, order=revset.followorder)
2277 revs = matcher(repo, revs)
2277 revs = matcher(repo, revs)
2278 if limit is not None:
2278 if limit is not None:
2279 limitedrevs = []
2279 limitedrevs = []
2280 for idx, r in enumerate(revs):
2280 for idx, r in enumerate(revs):
2281 if limit <= idx:
2281 if limit <= idx:
2282 break
2282 break
2283 limitedrevs.append(r)
2283 limitedrevs.append(r)
2284 revs = smartset.baseset(limitedrevs)
2284 revs = smartset.baseset(limitedrevs)
2285
2285
2286 return revs, expr, filematcher
2286 return revs, expr, filematcher
2287
2287
2288 def _graphnodeformatter(ui, displayer):
2288 def _graphnodeformatter(ui, displayer):
2289 spec = ui.config('ui', 'graphnodetemplate')
2289 spec = ui.config('ui', 'graphnodetemplate')
2290 if not spec:
2290 if not spec:
2291 return templatekw.showgraphnode # fast path for "{graphnode}"
2291 return templatekw.showgraphnode # fast path for "{graphnode}"
2292
2292
2293 spec = templater.unquotestring(spec)
2293 spec = templater.unquotestring(spec)
2294 templ = formatter.gettemplater(ui, 'graphnode', spec)
2294 templ = formatter.gettemplater(ui, 'graphnode', spec)
2295 cache = {}
2295 cache = {}
2296 if isinstance(displayer, changeset_templater):
2296 if isinstance(displayer, changeset_templater):
2297 cache = displayer.cache # reuse cache of slow templates
2297 cache = displayer.cache # reuse cache of slow templates
2298 props = templatekw.keywords.copy()
2298 props = templatekw.keywords.copy()
2299 props['templ'] = templ
2299 props['templ'] = templ
2300 props['cache'] = cache
2300 props['cache'] = cache
2301 def formatnode(repo, ctx):
2301 def formatnode(repo, ctx):
2302 props['ctx'] = ctx
2302 props['ctx'] = ctx
2303 props['repo'] = repo
2303 props['repo'] = repo
2304 props['ui'] = repo.ui
2304 props['ui'] = repo.ui
2305 props['revcache'] = {}
2305 props['revcache'] = {}
2306 return templater.stringify(templ('graphnode', **props))
2306 return templater.stringify(templ('graphnode', **props))
2307 return formatnode
2307 return formatnode
2308
2308
2309 def displaygraph(ui, repo, dag, displayer, edgefn, getrenamed=None,
2309 def displaygraph(ui, repo, dag, displayer, edgefn, getrenamed=None,
2310 filematcher=None):
2310 filematcher=None):
2311 formatnode = _graphnodeformatter(ui, displayer)
2311 formatnode = _graphnodeformatter(ui, displayer)
2312 state = graphmod.asciistate()
2312 state = graphmod.asciistate()
2313 styles = state['styles']
2313 styles = state['styles']
2314
2314
2315 # only set graph styling if HGPLAIN is not set.
2315 # only set graph styling if HGPLAIN is not set.
2316 if ui.plain('graph'):
2316 if ui.plain('graph'):
2317 # set all edge styles to |, the default pre-3.8 behaviour
2317 # set all edge styles to |, the default pre-3.8 behaviour
2318 styles.update(dict.fromkeys(styles, '|'))
2318 styles.update(dict.fromkeys(styles, '|'))
2319 else:
2319 else:
2320 edgetypes = {
2320 edgetypes = {
2321 'parent': graphmod.PARENT,
2321 'parent': graphmod.PARENT,
2322 'grandparent': graphmod.GRANDPARENT,
2322 'grandparent': graphmod.GRANDPARENT,
2323 'missing': graphmod.MISSINGPARENT
2323 'missing': graphmod.MISSINGPARENT
2324 }
2324 }
2325 for name, key in edgetypes.items():
2325 for name, key in edgetypes.items():
2326 # experimental config: experimental.graphstyle.*
2326 # experimental config: experimental.graphstyle.*
2327 styles[key] = ui.config('experimental', 'graphstyle.%s' % name,
2327 styles[key] = ui.config('experimental', 'graphstyle.%s' % name,
2328 styles[key])
2328 styles[key])
2329 if not styles[key]:
2329 if not styles[key]:
2330 styles[key] = None
2330 styles[key] = None
2331
2331
2332 # experimental config: experimental.graphshorten
2332 # experimental config: experimental.graphshorten
2333 state['graphshorten'] = ui.configbool('experimental', 'graphshorten')
2333 state['graphshorten'] = ui.configbool('experimental', 'graphshorten')
2334
2334
2335 for rev, type, ctx, parents in dag:
2335 for rev, type, ctx, parents in dag:
2336 char = formatnode(repo, ctx)
2336 char = formatnode(repo, ctx)
2337 copies = None
2337 copies = None
2338 if getrenamed and ctx.rev():
2338 if getrenamed and ctx.rev():
2339 copies = []
2339 copies = []
2340 for fn in ctx.files():
2340 for fn in ctx.files():
2341 rename = getrenamed(fn, ctx.rev())
2341 rename = getrenamed(fn, ctx.rev())
2342 if rename:
2342 if rename:
2343 copies.append((fn, rename[0]))
2343 copies.append((fn, rename[0]))
2344 revmatchfn = None
2344 revmatchfn = None
2345 if filematcher is not None:
2345 if filematcher is not None:
2346 revmatchfn = filematcher(ctx.rev())
2346 revmatchfn = filematcher(ctx.rev())
2347 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
2347 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
2348 lines = displayer.hunk.pop(rev).split('\n')
2348 lines = displayer.hunk.pop(rev).split('\n')
2349 if not lines[-1]:
2349 if not lines[-1]:
2350 del lines[-1]
2350 del lines[-1]
2351 displayer.flush(ctx)
2351 displayer.flush(ctx)
2352 edges = edgefn(type, char, lines, state, rev, parents)
2352 edges = edgefn(type, char, lines, state, rev, parents)
2353 for type, char, lines, coldata in edges:
2353 for type, char, lines, coldata in edges:
2354 graphmod.ascii(ui, state, type, char, lines, coldata)
2354 graphmod.ascii(ui, state, type, char, lines, coldata)
2355 displayer.close()
2355 displayer.close()
2356
2356
2357 def graphlog(ui, repo, pats, opts):
2357 def graphlog(ui, repo, pats, opts):
2358 # Parameters are identical to log command ones
2358 # Parameters are identical to log command ones
2359 revs, expr, filematcher = getgraphlogrevs(repo, pats, opts)
2359 revs, expr, filematcher = getgraphlogrevs(repo, pats, opts)
2360 revdag = graphmod.dagwalker(repo, revs)
2360 revdag = graphmod.dagwalker(repo, revs)
2361
2361
2362 getrenamed = None
2362 getrenamed = None
2363 if opts.get('copies'):
2363 if opts.get('copies'):
2364 endrev = None
2364 endrev = None
2365 if opts.get('rev'):
2365 if opts.get('rev'):
2366 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
2366 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
2367 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
2367 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
2368
2368
2369 ui.pager('log')
2369 ui.pager('log')
2370 displayer = show_changeset(ui, repo, opts, buffered=True)
2370 displayer = show_changeset(ui, repo, opts, buffered=True)
2371 displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges, getrenamed,
2371 displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges, getrenamed,
2372 filematcher)
2372 filematcher)
2373
2373
2374 def checkunsupportedgraphflags(pats, opts):
2374 def checkunsupportedgraphflags(pats, opts):
2375 for op in ["newest_first"]:
2375 for op in ["newest_first"]:
2376 if op in opts and opts[op]:
2376 if op in opts and opts[op]:
2377 raise error.Abort(_("-G/--graph option is incompatible with --%s")
2377 raise error.Abort(_("-G/--graph option is incompatible with --%s")
2378 % op.replace("_", "-"))
2378 % op.replace("_", "-"))
2379
2379
2380 def graphrevs(repo, nodes, opts):
2380 def graphrevs(repo, nodes, opts):
2381 limit = loglimit(opts)
2381 limit = loglimit(opts)
2382 nodes.reverse()
2382 nodes.reverse()
2383 if limit is not None:
2383 if limit is not None:
2384 nodes = nodes[:limit]
2384 nodes = nodes[:limit]
2385 return graphmod.nodes(repo, nodes)
2385 return graphmod.nodes(repo, nodes)
2386
2386
2387 def add(ui, repo, match, prefix, explicitonly, **opts):
2387 def add(ui, repo, match, prefix, explicitonly, **opts):
2388 join = lambda f: os.path.join(prefix, f)
2388 join = lambda f: os.path.join(prefix, f)
2389 bad = []
2389 bad = []
2390
2390
2391 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2391 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2392 names = []
2392 names = []
2393 wctx = repo[None]
2393 wctx = repo[None]
2394 cca = None
2394 cca = None
2395 abort, warn = scmutil.checkportabilityalert(ui)
2395 abort, warn = scmutil.checkportabilityalert(ui)
2396 if abort or warn:
2396 if abort or warn:
2397 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
2397 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
2398
2398
2399 badmatch = matchmod.badmatch(match, badfn)
2399 badmatch = matchmod.badmatch(match, badfn)
2400 dirstate = repo.dirstate
2400 dirstate = repo.dirstate
2401 # We don't want to just call wctx.walk here, since it would return a lot of
2401 # We don't want to just call wctx.walk here, since it would return a lot of
2402 # clean files, which we aren't interested in and takes time.
2402 # clean files, which we aren't interested in and takes time.
2403 for f in sorted(dirstate.walk(badmatch, sorted(wctx.substate),
2403 for f in sorted(dirstate.walk(badmatch, sorted(wctx.substate),
2404 True, False, full=False)):
2404 True, False, full=False)):
2405 exact = match.exact(f)
2405 exact = match.exact(f)
2406 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
2406 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
2407 if cca:
2407 if cca:
2408 cca(f)
2408 cca(f)
2409 names.append(f)
2409 names.append(f)
2410 if ui.verbose or not exact:
2410 if ui.verbose or not exact:
2411 ui.status(_('adding %s\n') % match.rel(f))
2411 ui.status(_('adding %s\n') % match.rel(f))
2412
2412
2413 for subpath in sorted(wctx.substate):
2413 for subpath in sorted(wctx.substate):
2414 sub = wctx.sub(subpath)
2414 sub = wctx.sub(subpath)
2415 try:
2415 try:
2416 submatch = matchmod.subdirmatcher(subpath, match)
2416 submatch = matchmod.subdirmatcher(subpath, match)
2417 if opts.get(r'subrepos'):
2417 if opts.get(r'subrepos'):
2418 bad.extend(sub.add(ui, submatch, prefix, False, **opts))
2418 bad.extend(sub.add(ui, submatch, prefix, False, **opts))
2419 else:
2419 else:
2420 bad.extend(sub.add(ui, submatch, prefix, True, **opts))
2420 bad.extend(sub.add(ui, submatch, prefix, True, **opts))
2421 except error.LookupError:
2421 except error.LookupError:
2422 ui.status(_("skipping missing subrepository: %s\n")
2422 ui.status(_("skipping missing subrepository: %s\n")
2423 % join(subpath))
2423 % join(subpath))
2424
2424
2425 if not opts.get(r'dry_run'):
2425 if not opts.get(r'dry_run'):
2426 rejected = wctx.add(names, prefix)
2426 rejected = wctx.add(names, prefix)
2427 bad.extend(f for f in rejected if f in match.files())
2427 bad.extend(f for f in rejected if f in match.files())
2428 return bad
2428 return bad
2429
2429
2430 def addwebdirpath(repo, serverpath, webconf):
2430 def addwebdirpath(repo, serverpath, webconf):
2431 webconf[serverpath] = repo.root
2431 webconf[serverpath] = repo.root
2432 repo.ui.debug('adding %s = %s\n' % (serverpath, repo.root))
2432 repo.ui.debug('adding %s = %s\n' % (serverpath, repo.root))
2433
2433
2434 for r in repo.revs('filelog("path:.hgsub")'):
2434 for r in repo.revs('filelog("path:.hgsub")'):
2435 ctx = repo[r]
2435 ctx = repo[r]
2436 for subpath in ctx.substate:
2436 for subpath in ctx.substate:
2437 ctx.sub(subpath).addwebdirpath(serverpath, webconf)
2437 ctx.sub(subpath).addwebdirpath(serverpath, webconf)
2438
2438
2439 def forget(ui, repo, match, prefix, explicitonly):
2439 def forget(ui, repo, match, prefix, explicitonly):
2440 join = lambda f: os.path.join(prefix, f)
2440 join = lambda f: os.path.join(prefix, f)
2441 bad = []
2441 bad = []
2442 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2442 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2443 wctx = repo[None]
2443 wctx = repo[None]
2444 forgot = []
2444 forgot = []
2445
2445
2446 s = repo.status(match=matchmod.badmatch(match, badfn), clean=True)
2446 s = repo.status(match=matchmod.badmatch(match, badfn), clean=True)
2447 forget = sorted(s.modified + s.added + s.deleted + s.clean)
2447 forget = sorted(s.modified + s.added + s.deleted + s.clean)
2448 if explicitonly:
2448 if explicitonly:
2449 forget = [f for f in forget if match.exact(f)]
2449 forget = [f for f in forget if match.exact(f)]
2450
2450
2451 for subpath in sorted(wctx.substate):
2451 for subpath in sorted(wctx.substate):
2452 sub = wctx.sub(subpath)
2452 sub = wctx.sub(subpath)
2453 try:
2453 try:
2454 submatch = matchmod.subdirmatcher(subpath, match)
2454 submatch = matchmod.subdirmatcher(subpath, match)
2455 subbad, subforgot = sub.forget(submatch, prefix)
2455 subbad, subforgot = sub.forget(submatch, prefix)
2456 bad.extend([subpath + '/' + f for f in subbad])
2456 bad.extend([subpath + '/' + f for f in subbad])
2457 forgot.extend([subpath + '/' + f for f in subforgot])
2457 forgot.extend([subpath + '/' + f for f in subforgot])
2458 except error.LookupError:
2458 except error.LookupError:
2459 ui.status(_("skipping missing subrepository: %s\n")
2459 ui.status(_("skipping missing subrepository: %s\n")
2460 % join(subpath))
2460 % join(subpath))
2461
2461
2462 if not explicitonly:
2462 if not explicitonly:
2463 for f in match.files():
2463 for f in match.files():
2464 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2464 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2465 if f not in forgot:
2465 if f not in forgot:
2466 if repo.wvfs.exists(f):
2466 if repo.wvfs.exists(f):
2467 # Don't complain if the exact case match wasn't given.
2467 # Don't complain if the exact case match wasn't given.
2468 # But don't do this until after checking 'forgot', so
2468 # But don't do this until after checking 'forgot', so
2469 # that subrepo files aren't normalized, and this op is
2469 # that subrepo files aren't normalized, and this op is
2470 # purely from data cached by the status walk above.
2470 # purely from data cached by the status walk above.
2471 if repo.dirstate.normalize(f) in repo.dirstate:
2471 if repo.dirstate.normalize(f) in repo.dirstate:
2472 continue
2472 continue
2473 ui.warn(_('not removing %s: '
2473 ui.warn(_('not removing %s: '
2474 'file is already untracked\n')
2474 'file is already untracked\n')
2475 % match.rel(f))
2475 % match.rel(f))
2476 bad.append(f)
2476 bad.append(f)
2477
2477
2478 for f in forget:
2478 for f in forget:
2479 if ui.verbose or not match.exact(f):
2479 if ui.verbose or not match.exact(f):
2480 ui.status(_('removing %s\n') % match.rel(f))
2480 ui.status(_('removing %s\n') % match.rel(f))
2481
2481
2482 rejected = wctx.forget(forget, prefix)
2482 rejected = wctx.forget(forget, prefix)
2483 bad.extend(f for f in rejected if f in match.files())
2483 bad.extend(f for f in rejected if f in match.files())
2484 forgot.extend(f for f in forget if f not in rejected)
2484 forgot.extend(f for f in forget if f not in rejected)
2485 return bad, forgot
2485 return bad, forgot
2486
2486
2487 def files(ui, ctx, m, fm, fmt, subrepos):
2487 def files(ui, ctx, m, fm, fmt, subrepos):
2488 rev = ctx.rev()
2488 rev = ctx.rev()
2489 ret = 1
2489 ret = 1
2490 ds = ctx.repo().dirstate
2490 ds = ctx.repo().dirstate
2491
2491
2492 for f in ctx.matches(m):
2492 for f in ctx.matches(m):
2493 if rev is None and ds[f] == 'r':
2493 if rev is None and ds[f] == 'r':
2494 continue
2494 continue
2495 fm.startitem()
2495 fm.startitem()
2496 if ui.verbose:
2496 if ui.verbose:
2497 fc = ctx[f]
2497 fc = ctx[f]
2498 fm.write('size flags', '% 10d % 1s ', fc.size(), fc.flags())
2498 fm.write('size flags', '% 10d % 1s ', fc.size(), fc.flags())
2499 fm.data(abspath=f)
2499 fm.data(abspath=f)
2500 fm.write('path', fmt, m.rel(f))
2500 fm.write('path', fmt, m.rel(f))
2501 ret = 0
2501 ret = 0
2502
2502
2503 for subpath in sorted(ctx.substate):
2503 for subpath in sorted(ctx.substate):
2504 submatch = matchmod.subdirmatcher(subpath, m)
2504 submatch = matchmod.subdirmatcher(subpath, m)
2505 if (subrepos or m.exact(subpath) or any(submatch.files())):
2505 if (subrepos or m.exact(subpath) or any(submatch.files())):
2506 sub = ctx.sub(subpath)
2506 sub = ctx.sub(subpath)
2507 try:
2507 try:
2508 recurse = m.exact(subpath) or subrepos
2508 recurse = m.exact(subpath) or subrepos
2509 if sub.printfiles(ui, submatch, fm, fmt, recurse) == 0:
2509 if sub.printfiles(ui, submatch, fm, fmt, recurse) == 0:
2510 ret = 0
2510 ret = 0
2511 except error.LookupError:
2511 except error.LookupError:
2512 ui.status(_("skipping missing subrepository: %s\n")
2512 ui.status(_("skipping missing subrepository: %s\n")
2513 % m.abs(subpath))
2513 % m.abs(subpath))
2514
2514
2515 return ret
2515 return ret
2516
2516
2517 def remove(ui, repo, m, prefix, after, force, subrepos, warnings=None):
2517 def remove(ui, repo, m, prefix, after, force, subrepos, warnings=None):
2518 join = lambda f: os.path.join(prefix, f)
2518 join = lambda f: os.path.join(prefix, f)
2519 ret = 0
2519 ret = 0
2520 s = repo.status(match=m, clean=True)
2520 s = repo.status(match=m, clean=True)
2521 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2521 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2522
2522
2523 wctx = repo[None]
2523 wctx = repo[None]
2524
2524
2525 if warnings is None:
2525 if warnings is None:
2526 warnings = []
2526 warnings = []
2527 warn = True
2527 warn = True
2528 else:
2528 else:
2529 warn = False
2529 warn = False
2530
2530
2531 subs = sorted(wctx.substate)
2531 subs = sorted(wctx.substate)
2532 total = len(subs)
2532 total = len(subs)
2533 count = 0
2533 count = 0
2534 for subpath in subs:
2534 for subpath in subs:
2535 count += 1
2535 count += 1
2536 submatch = matchmod.subdirmatcher(subpath, m)
2536 submatch = matchmod.subdirmatcher(subpath, m)
2537 if subrepos or m.exact(subpath) or any(submatch.files()):
2537 if subrepos or m.exact(subpath) or any(submatch.files()):
2538 ui.progress(_('searching'), count, total=total, unit=_('subrepos'))
2538 ui.progress(_('searching'), count, total=total, unit=_('subrepos'))
2539 sub = wctx.sub(subpath)
2539 sub = wctx.sub(subpath)
2540 try:
2540 try:
2541 if sub.removefiles(submatch, prefix, after, force, subrepos,
2541 if sub.removefiles(submatch, prefix, after, force, subrepos,
2542 warnings):
2542 warnings):
2543 ret = 1
2543 ret = 1
2544 except error.LookupError:
2544 except error.LookupError:
2545 warnings.append(_("skipping missing subrepository: %s\n")
2545 warnings.append(_("skipping missing subrepository: %s\n")
2546 % join(subpath))
2546 % join(subpath))
2547 ui.progress(_('searching'), None)
2547 ui.progress(_('searching'), None)
2548
2548
2549 # warn about failure to delete explicit files/dirs
2549 # warn about failure to delete explicit files/dirs
2550 deleteddirs = util.dirs(deleted)
2550 deleteddirs = util.dirs(deleted)
2551 files = m.files()
2551 files = m.files()
2552 total = len(files)
2552 total = len(files)
2553 count = 0
2553 count = 0
2554 for f in files:
2554 for f in files:
2555 def insubrepo():
2555 def insubrepo():
2556 for subpath in wctx.substate:
2556 for subpath in wctx.substate:
2557 if f.startswith(subpath + '/'):
2557 if f.startswith(subpath + '/'):
2558 return True
2558 return True
2559 return False
2559 return False
2560
2560
2561 count += 1
2561 count += 1
2562 ui.progress(_('deleting'), count, total=total, unit=_('files'))
2562 ui.progress(_('deleting'), count, total=total, unit=_('files'))
2563 isdir = f in deleteddirs or wctx.hasdir(f)
2563 isdir = f in deleteddirs or wctx.hasdir(f)
2564 if (f in repo.dirstate or isdir or f == '.'
2564 if (f in repo.dirstate or isdir or f == '.'
2565 or insubrepo() or f in subs):
2565 or insubrepo() or f in subs):
2566 continue
2566 continue
2567
2567
2568 if repo.wvfs.exists(f):
2568 if repo.wvfs.exists(f):
2569 if repo.wvfs.isdir(f):
2569 if repo.wvfs.isdir(f):
2570 warnings.append(_('not removing %s: no tracked files\n')
2570 warnings.append(_('not removing %s: no tracked files\n')
2571 % m.rel(f))
2571 % m.rel(f))
2572 else:
2572 else:
2573 warnings.append(_('not removing %s: file is untracked\n')
2573 warnings.append(_('not removing %s: file is untracked\n')
2574 % m.rel(f))
2574 % m.rel(f))
2575 # missing files will generate a warning elsewhere
2575 # missing files will generate a warning elsewhere
2576 ret = 1
2576 ret = 1
2577 ui.progress(_('deleting'), None)
2577 ui.progress(_('deleting'), None)
2578
2578
2579 if force:
2579 if force:
2580 list = modified + deleted + clean + added
2580 list = modified + deleted + clean + added
2581 elif after:
2581 elif after:
2582 list = deleted
2582 list = deleted
2583 remaining = modified + added + clean
2583 remaining = modified + added + clean
2584 total = len(remaining)
2584 total = len(remaining)
2585 count = 0
2585 count = 0
2586 for f in remaining:
2586 for f in remaining:
2587 count += 1
2587 count += 1
2588 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2588 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2589 warnings.append(_('not removing %s: file still exists\n')
2589 warnings.append(_('not removing %s: file still exists\n')
2590 % m.rel(f))
2590 % m.rel(f))
2591 ret = 1
2591 ret = 1
2592 ui.progress(_('skipping'), None)
2592 ui.progress(_('skipping'), None)
2593 else:
2593 else:
2594 list = deleted + clean
2594 list = deleted + clean
2595 total = len(modified) + len(added)
2595 total = len(modified) + len(added)
2596 count = 0
2596 count = 0
2597 for f in modified:
2597 for f in modified:
2598 count += 1
2598 count += 1
2599 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2599 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2600 warnings.append(_('not removing %s: file is modified (use -f'
2600 warnings.append(_('not removing %s: file is modified (use -f'
2601 ' to force removal)\n') % m.rel(f))
2601 ' to force removal)\n') % m.rel(f))
2602 ret = 1
2602 ret = 1
2603 for f in added:
2603 for f in added:
2604 count += 1
2604 count += 1
2605 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2605 ui.progress(_('skipping'), count, total=total, unit=_('files'))
2606 warnings.append(_("not removing %s: file has been marked for add"
2606 warnings.append(_("not removing %s: file has been marked for add"
2607 " (use 'hg forget' to undo add)\n") % m.rel(f))
2607 " (use 'hg forget' to undo add)\n") % m.rel(f))
2608 ret = 1
2608 ret = 1
2609 ui.progress(_('skipping'), None)
2609 ui.progress(_('skipping'), None)
2610
2610
2611 list = sorted(list)
2611 list = sorted(list)
2612 total = len(list)
2612 total = len(list)
2613 count = 0
2613 count = 0
2614 for f in list:
2614 for f in list:
2615 count += 1
2615 count += 1
2616 if ui.verbose or not m.exact(f):
2616 if ui.verbose or not m.exact(f):
2617 ui.progress(_('deleting'), count, total=total, unit=_('files'))
2617 ui.progress(_('deleting'), count, total=total, unit=_('files'))
2618 ui.status(_('removing %s\n') % m.rel(f))
2618 ui.status(_('removing %s\n') % m.rel(f))
2619 ui.progress(_('deleting'), None)
2619 ui.progress(_('deleting'), None)
2620
2620
2621 with repo.wlock():
2621 with repo.wlock():
2622 if not after:
2622 if not after:
2623 for f in list:
2623 for f in list:
2624 if f in added:
2624 if f in added:
2625 continue # we never unlink added files on remove
2625 continue # we never unlink added files on remove
2626 repo.wvfs.unlinkpath(f, ignoremissing=True)
2626 repo.wvfs.unlinkpath(f, ignoremissing=True)
2627 repo[None].forget(list)
2627 repo[None].forget(list)
2628
2628
2629 if warn:
2629 if warn:
2630 for warning in warnings:
2630 for warning in warnings:
2631 ui.warn(warning)
2631 ui.warn(warning)
2632
2632
2633 return ret
2633 return ret
2634
2634
2635 def cat(ui, repo, ctx, matcher, prefix, **opts):
2635 def cat(ui, repo, ctx, matcher, fntemplate, prefix, **opts):
2636 err = 1
2636 err = 1
2637
2637
2638 def write(path):
2638 def write(path):
2639 fp = makefileobj(repo, opts.get('output'), ctx.node(),
2639 fp = makefileobj(repo, fntemplate, ctx.node(),
2640 pathname=os.path.join(prefix, path))
2640 pathname=os.path.join(prefix, path))
2641 data = ctx[path].data()
2641 data = ctx[path].data()
2642 if opts.get('decode'):
2642 if opts.get('decode'):
2643 data = repo.wwritedata(path, data)
2643 data = repo.wwritedata(path, data)
2644 fp.write(data)
2644 fp.write(data)
2645 fp.close()
2645 fp.close()
2646
2646
2647 # Automation often uses hg cat on single files, so special case it
2647 # Automation often uses hg cat on single files, so special case it
2648 # for performance to avoid the cost of parsing the manifest.
2648 # for performance to avoid the cost of parsing the manifest.
2649 if len(matcher.files()) == 1 and not matcher.anypats():
2649 if len(matcher.files()) == 1 and not matcher.anypats():
2650 file = matcher.files()[0]
2650 file = matcher.files()[0]
2651 mfl = repo.manifestlog
2651 mfl = repo.manifestlog
2652 mfnode = ctx.manifestnode()
2652 mfnode = ctx.manifestnode()
2653 try:
2653 try:
2654 if mfnode and mfl[mfnode].find(file)[0]:
2654 if mfnode and mfl[mfnode].find(file)[0]:
2655 write(file)
2655 write(file)
2656 return 0
2656 return 0
2657 except KeyError:
2657 except KeyError:
2658 pass
2658 pass
2659
2659
2660 for abs in ctx.walk(matcher):
2660 for abs in ctx.walk(matcher):
2661 write(abs)
2661 write(abs)
2662 err = 0
2662 err = 0
2663
2663
2664 for subpath in sorted(ctx.substate):
2664 for subpath in sorted(ctx.substate):
2665 sub = ctx.sub(subpath)
2665 sub = ctx.sub(subpath)
2666 try:
2666 try:
2667 submatch = matchmod.subdirmatcher(subpath, matcher)
2667 submatch = matchmod.subdirmatcher(subpath, matcher)
2668
2668
2669 if not sub.cat(submatch, os.path.join(prefix, sub._path),
2669 if not sub.cat(submatch, fntemplate,
2670 **opts):
2670 os.path.join(prefix, sub._path), **opts):
2671 err = 0
2671 err = 0
2672 except error.RepoLookupError:
2672 except error.RepoLookupError:
2673 ui.status(_("skipping missing subrepository: %s\n")
2673 ui.status(_("skipping missing subrepository: %s\n")
2674 % os.path.join(prefix, subpath))
2674 % os.path.join(prefix, subpath))
2675
2675
2676 return err
2676 return err
2677
2677
2678 def commit(ui, repo, commitfunc, pats, opts):
2678 def commit(ui, repo, commitfunc, pats, opts):
2679 '''commit the specified files or all outstanding changes'''
2679 '''commit the specified files or all outstanding changes'''
2680 date = opts.get('date')
2680 date = opts.get('date')
2681 if date:
2681 if date:
2682 opts['date'] = util.parsedate(date)
2682 opts['date'] = util.parsedate(date)
2683 message = logmessage(ui, opts)
2683 message = logmessage(ui, opts)
2684 matcher = scmutil.match(repo[None], pats, opts)
2684 matcher = scmutil.match(repo[None], pats, opts)
2685
2685
2686 # extract addremove carefully -- this function can be called from a command
2686 # extract addremove carefully -- this function can be called from a command
2687 # that doesn't support addremove
2687 # that doesn't support addremove
2688 if opts.get('addremove'):
2688 if opts.get('addremove'):
2689 if scmutil.addremove(repo, matcher, "", opts) != 0:
2689 if scmutil.addremove(repo, matcher, "", opts) != 0:
2690 raise error.Abort(
2690 raise error.Abort(
2691 _("failed to mark all new/missing files as added/removed"))
2691 _("failed to mark all new/missing files as added/removed"))
2692
2692
2693 return commitfunc(ui, repo, message, matcher, opts)
2693 return commitfunc(ui, repo, message, matcher, opts)
2694
2694
2695 def samefile(f, ctx1, ctx2):
2695 def samefile(f, ctx1, ctx2):
2696 if f in ctx1.manifest():
2696 if f in ctx1.manifest():
2697 a = ctx1.filectx(f)
2697 a = ctx1.filectx(f)
2698 if f in ctx2.manifest():
2698 if f in ctx2.manifest():
2699 b = ctx2.filectx(f)
2699 b = ctx2.filectx(f)
2700 return (not a.cmp(b)
2700 return (not a.cmp(b)
2701 and a.flags() == b.flags())
2701 and a.flags() == b.flags())
2702 else:
2702 else:
2703 return False
2703 return False
2704 else:
2704 else:
2705 return f not in ctx2.manifest()
2705 return f not in ctx2.manifest()
2706
2706
2707 def amend(ui, repo, commitfunc, old, extra, pats, opts):
2707 def amend(ui, repo, commitfunc, old, extra, pats, opts):
2708 # avoid cycle context -> subrepo -> cmdutil
2708 # avoid cycle context -> subrepo -> cmdutil
2709 from . import context
2709 from . import context
2710
2710
2711 # amend will reuse the existing user if not specified, but the obsolete
2711 # amend will reuse the existing user if not specified, but the obsolete
2712 # marker creation requires that the current user's name is specified.
2712 # marker creation requires that the current user's name is specified.
2713 if obsolete.isenabled(repo, obsolete.createmarkersopt):
2713 if obsolete.isenabled(repo, obsolete.createmarkersopt):
2714 ui.username() # raise exception if username not set
2714 ui.username() # raise exception if username not set
2715
2715
2716 ui.note(_('amending changeset %s\n') % old)
2716 ui.note(_('amending changeset %s\n') % old)
2717 base = old.p1()
2717 base = old.p1()
2718 createmarkers = obsolete.isenabled(repo, obsolete.createmarkersopt)
2718 createmarkers = obsolete.isenabled(repo, obsolete.createmarkersopt)
2719
2719
2720 wlock = lock = newid = None
2720 wlock = lock = newid = None
2721 try:
2721 try:
2722 wlock = repo.wlock()
2722 wlock = repo.wlock()
2723 lock = repo.lock()
2723 lock = repo.lock()
2724 with repo.transaction('amend') as tr:
2724 with repo.transaction('amend') as tr:
2725 # See if we got a message from -m or -l, if not, open the editor
2725 # See if we got a message from -m or -l, if not, open the editor
2726 # with the message of the changeset to amend
2726 # with the message of the changeset to amend
2727 message = logmessage(ui, opts)
2727 message = logmessage(ui, opts)
2728 # ensure logfile does not conflict with later enforcement of the
2728 # ensure logfile does not conflict with later enforcement of the
2729 # message. potential logfile content has been processed by
2729 # message. potential logfile content has been processed by
2730 # `logmessage` anyway.
2730 # `logmessage` anyway.
2731 opts.pop('logfile')
2731 opts.pop('logfile')
2732 # First, do a regular commit to record all changes in the working
2732 # First, do a regular commit to record all changes in the working
2733 # directory (if there are any)
2733 # directory (if there are any)
2734 ui.callhooks = False
2734 ui.callhooks = False
2735 activebookmark = repo._bookmarks.active
2735 activebookmark = repo._bookmarks.active
2736 try:
2736 try:
2737 repo._bookmarks.active = None
2737 repo._bookmarks.active = None
2738 opts['message'] = 'temporary amend commit for %s' % old
2738 opts['message'] = 'temporary amend commit for %s' % old
2739 node = commit(ui, repo, commitfunc, pats, opts)
2739 node = commit(ui, repo, commitfunc, pats, opts)
2740 finally:
2740 finally:
2741 repo._bookmarks.active = activebookmark
2741 repo._bookmarks.active = activebookmark
2742 repo._bookmarks.recordchange(tr)
2742 repo._bookmarks.recordchange(tr)
2743 ui.callhooks = True
2743 ui.callhooks = True
2744 ctx = repo[node]
2744 ctx = repo[node]
2745
2745
2746 # Participating changesets:
2746 # Participating changesets:
2747 #
2747 #
2748 # node/ctx o - new (intermediate) commit that contains changes
2748 # node/ctx o - new (intermediate) commit that contains changes
2749 # | from working dir to go into amending commit
2749 # | from working dir to go into amending commit
2750 # | (or a workingctx if there were no changes)
2750 # | (or a workingctx if there were no changes)
2751 # |
2751 # |
2752 # old o - changeset to amend
2752 # old o - changeset to amend
2753 # |
2753 # |
2754 # base o - parent of amending changeset
2754 # base o - parent of amending changeset
2755
2755
2756 # Update extra dict from amended commit (e.g. to preserve graft
2756 # Update extra dict from amended commit (e.g. to preserve graft
2757 # source)
2757 # source)
2758 extra.update(old.extra())
2758 extra.update(old.extra())
2759
2759
2760 # Also update it from the intermediate commit or from the wctx
2760 # Also update it from the intermediate commit or from the wctx
2761 extra.update(ctx.extra())
2761 extra.update(ctx.extra())
2762
2762
2763 if len(old.parents()) > 1:
2763 if len(old.parents()) > 1:
2764 # ctx.files() isn't reliable for merges, so fall back to the
2764 # ctx.files() isn't reliable for merges, so fall back to the
2765 # slower repo.status() method
2765 # slower repo.status() method
2766 files = set([fn for st in repo.status(base, old)[:3]
2766 files = set([fn for st in repo.status(base, old)[:3]
2767 for fn in st])
2767 for fn in st])
2768 else:
2768 else:
2769 files = set(old.files())
2769 files = set(old.files())
2770
2770
2771 # Second, we use either the commit we just did, or if there were no
2771 # Second, we use either the commit we just did, or if there were no
2772 # changes the parent of the working directory as the version of the
2772 # changes the parent of the working directory as the version of the
2773 # files in the final amend commit
2773 # files in the final amend commit
2774 if node:
2774 if node:
2775 ui.note(_('copying changeset %s to %s\n') % (ctx, base))
2775 ui.note(_('copying changeset %s to %s\n') % (ctx, base))
2776
2776
2777 user = ctx.user()
2777 user = ctx.user()
2778 date = ctx.date()
2778 date = ctx.date()
2779 # Recompute copies (avoid recording a -> b -> a)
2779 # Recompute copies (avoid recording a -> b -> a)
2780 copied = copies.pathcopies(base, ctx)
2780 copied = copies.pathcopies(base, ctx)
2781 if old.p2:
2781 if old.p2:
2782 copied.update(copies.pathcopies(old.p2(), ctx))
2782 copied.update(copies.pathcopies(old.p2(), ctx))
2783
2783
2784 # Prune files which were reverted by the updates: if old
2784 # Prune files which were reverted by the updates: if old
2785 # introduced file X and our intermediate commit, node,
2785 # introduced file X and our intermediate commit, node,
2786 # renamed that file, then those two files are the same and
2786 # renamed that file, then those two files are the same and
2787 # we can discard X from our list of files. Likewise if X
2787 # we can discard X from our list of files. Likewise if X
2788 # was deleted, it's no longer relevant
2788 # was deleted, it's no longer relevant
2789 files.update(ctx.files())
2789 files.update(ctx.files())
2790 files = [f for f in files if not samefile(f, ctx, base)]
2790 files = [f for f in files if not samefile(f, ctx, base)]
2791
2791
2792 def filectxfn(repo, ctx_, path):
2792 def filectxfn(repo, ctx_, path):
2793 try:
2793 try:
2794 fctx = ctx[path]
2794 fctx = ctx[path]
2795 flags = fctx.flags()
2795 flags = fctx.flags()
2796 mctx = context.memfilectx(repo,
2796 mctx = context.memfilectx(repo,
2797 fctx.path(), fctx.data(),
2797 fctx.path(), fctx.data(),
2798 islink='l' in flags,
2798 islink='l' in flags,
2799 isexec='x' in flags,
2799 isexec='x' in flags,
2800 copied=copied.get(path))
2800 copied=copied.get(path))
2801 return mctx
2801 return mctx
2802 except KeyError:
2802 except KeyError:
2803 return None
2803 return None
2804 else:
2804 else:
2805 ui.note(_('copying changeset %s to %s\n') % (old, base))
2805 ui.note(_('copying changeset %s to %s\n') % (old, base))
2806
2806
2807 # Use version of files as in the old cset
2807 # Use version of files as in the old cset
2808 def filectxfn(repo, ctx_, path):
2808 def filectxfn(repo, ctx_, path):
2809 try:
2809 try:
2810 return old.filectx(path)
2810 return old.filectx(path)
2811 except KeyError:
2811 except KeyError:
2812 return None
2812 return None
2813
2813
2814 user = opts.get('user') or old.user()
2814 user = opts.get('user') or old.user()
2815 date = opts.get('date') or old.date()
2815 date = opts.get('date') or old.date()
2816 editform = mergeeditform(old, 'commit.amend')
2816 editform = mergeeditform(old, 'commit.amend')
2817 editor = getcommiteditor(editform=editform, **opts)
2817 editor = getcommiteditor(editform=editform, **opts)
2818 if not message:
2818 if not message:
2819 editor = getcommiteditor(edit=True, editform=editform)
2819 editor = getcommiteditor(edit=True, editform=editform)
2820 message = old.description()
2820 message = old.description()
2821
2821
2822 pureextra = extra.copy()
2822 pureextra = extra.copy()
2823 extra['amend_source'] = old.hex()
2823 extra['amend_source'] = old.hex()
2824
2824
2825 new = context.memctx(repo,
2825 new = context.memctx(repo,
2826 parents=[base.node(), old.p2().node()],
2826 parents=[base.node(), old.p2().node()],
2827 text=message,
2827 text=message,
2828 files=files,
2828 files=files,
2829 filectxfn=filectxfn,
2829 filectxfn=filectxfn,
2830 user=user,
2830 user=user,
2831 date=date,
2831 date=date,
2832 extra=extra,
2832 extra=extra,
2833 editor=editor)
2833 editor=editor)
2834
2834
2835 newdesc = changelog.stripdesc(new.description())
2835 newdesc = changelog.stripdesc(new.description())
2836 if ((not node)
2836 if ((not node)
2837 and newdesc == old.description()
2837 and newdesc == old.description()
2838 and user == old.user()
2838 and user == old.user()
2839 and date == old.date()
2839 and date == old.date()
2840 and pureextra == old.extra()):
2840 and pureextra == old.extra()):
2841 # nothing changed. continuing here would create a new node
2841 # nothing changed. continuing here would create a new node
2842 # anyway because of the amend_source noise.
2842 # anyway because of the amend_source noise.
2843 #
2843 #
2844 # This not what we expect from amend.
2844 # This not what we expect from amend.
2845 return old.node()
2845 return old.node()
2846
2846
2847 ph = repo.ui.config('phases', 'new-commit', phases.draft)
2847 ph = repo.ui.config('phases', 'new-commit', phases.draft)
2848 try:
2848 try:
2849 if opts.get('secret'):
2849 if opts.get('secret'):
2850 commitphase = 'secret'
2850 commitphase = 'secret'
2851 else:
2851 else:
2852 commitphase = old.phase()
2852 commitphase = old.phase()
2853 repo.ui.setconfig('phases', 'new-commit', commitphase, 'amend')
2853 repo.ui.setconfig('phases', 'new-commit', commitphase, 'amend')
2854 newid = repo.commitctx(new)
2854 newid = repo.commitctx(new)
2855 finally:
2855 finally:
2856 repo.ui.setconfig('phases', 'new-commit', ph, 'amend')
2856 repo.ui.setconfig('phases', 'new-commit', ph, 'amend')
2857 if newid != old.node():
2857 if newid != old.node():
2858 # Reroute the working copy parent to the new changeset
2858 # Reroute the working copy parent to the new changeset
2859 repo.setparents(newid, nullid)
2859 repo.setparents(newid, nullid)
2860
2860
2861 # Move bookmarks from old parent to amend commit
2861 # Move bookmarks from old parent to amend commit
2862 bms = repo.nodebookmarks(old.node())
2862 bms = repo.nodebookmarks(old.node())
2863 if bms:
2863 if bms:
2864 marks = repo._bookmarks
2864 marks = repo._bookmarks
2865 for bm in bms:
2865 for bm in bms:
2866 ui.debug('moving bookmarks %r from %s to %s\n' %
2866 ui.debug('moving bookmarks %r from %s to %s\n' %
2867 (marks, old.hex(), hex(newid)))
2867 (marks, old.hex(), hex(newid)))
2868 marks[bm] = newid
2868 marks[bm] = newid
2869 marks.recordchange(tr)
2869 marks.recordchange(tr)
2870 #commit the whole amend process
2870 #commit the whole amend process
2871 if createmarkers:
2871 if createmarkers:
2872 # mark the new changeset as successor of the rewritten one
2872 # mark the new changeset as successor of the rewritten one
2873 new = repo[newid]
2873 new = repo[newid]
2874 obs = [(old, (new,))]
2874 obs = [(old, (new,))]
2875 if node:
2875 if node:
2876 obs.append((ctx, ()))
2876 obs.append((ctx, ()))
2877
2877
2878 obsolete.createmarkers(repo, obs, operation='amend')
2878 obsolete.createmarkers(repo, obs, operation='amend')
2879 if not createmarkers and newid != old.node():
2879 if not createmarkers and newid != old.node():
2880 # Strip the intermediate commit (if there was one) and the amended
2880 # Strip the intermediate commit (if there was one) and the amended
2881 # commit
2881 # commit
2882 if node:
2882 if node:
2883 ui.note(_('stripping intermediate changeset %s\n') % ctx)
2883 ui.note(_('stripping intermediate changeset %s\n') % ctx)
2884 ui.note(_('stripping amended changeset %s\n') % old)
2884 ui.note(_('stripping amended changeset %s\n') % old)
2885 repair.strip(ui, repo, old.node(), topic='amend-backup')
2885 repair.strip(ui, repo, old.node(), topic='amend-backup')
2886 finally:
2886 finally:
2887 lockmod.release(lock, wlock)
2887 lockmod.release(lock, wlock)
2888 return newid
2888 return newid
2889
2889
2890 def commiteditor(repo, ctx, subs, editform=''):
2890 def commiteditor(repo, ctx, subs, editform=''):
2891 if ctx.description():
2891 if ctx.description():
2892 return ctx.description()
2892 return ctx.description()
2893 return commitforceeditor(repo, ctx, subs, editform=editform,
2893 return commitforceeditor(repo, ctx, subs, editform=editform,
2894 unchangedmessagedetection=True)
2894 unchangedmessagedetection=True)
2895
2895
2896 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
2896 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
2897 editform='', unchangedmessagedetection=False):
2897 editform='', unchangedmessagedetection=False):
2898 if not extramsg:
2898 if not extramsg:
2899 extramsg = _("Leave message empty to abort commit.")
2899 extramsg = _("Leave message empty to abort commit.")
2900
2900
2901 forms = [e for e in editform.split('.') if e]
2901 forms = [e for e in editform.split('.') if e]
2902 forms.insert(0, 'changeset')
2902 forms.insert(0, 'changeset')
2903 templatetext = None
2903 templatetext = None
2904 while forms:
2904 while forms:
2905 tmpl = repo.ui.config('committemplate', '.'.join(forms))
2905 tmpl = repo.ui.config('committemplate', '.'.join(forms))
2906 if tmpl:
2906 if tmpl:
2907 tmpl = templater.unquotestring(tmpl)
2907 tmpl = templater.unquotestring(tmpl)
2908 templatetext = committext = buildcommittemplate(
2908 templatetext = committext = buildcommittemplate(
2909 repo, ctx, subs, extramsg, tmpl)
2909 repo, ctx, subs, extramsg, tmpl)
2910 break
2910 break
2911 forms.pop()
2911 forms.pop()
2912 else:
2912 else:
2913 committext = buildcommittext(repo, ctx, subs, extramsg)
2913 committext = buildcommittext(repo, ctx, subs, extramsg)
2914
2914
2915 # run editor in the repository root
2915 # run editor in the repository root
2916 olddir = pycompat.getcwd()
2916 olddir = pycompat.getcwd()
2917 os.chdir(repo.root)
2917 os.chdir(repo.root)
2918
2918
2919 # make in-memory changes visible to external process
2919 # make in-memory changes visible to external process
2920 tr = repo.currenttransaction()
2920 tr = repo.currenttransaction()
2921 repo.dirstate.write(tr)
2921 repo.dirstate.write(tr)
2922 pending = tr and tr.writepending() and repo.root
2922 pending = tr and tr.writepending() and repo.root
2923
2923
2924 editortext = repo.ui.edit(committext, ctx.user(), ctx.extra(),
2924 editortext = repo.ui.edit(committext, ctx.user(), ctx.extra(),
2925 editform=editform, pending=pending,
2925 editform=editform, pending=pending,
2926 repopath=repo.path)
2926 repopath=repo.path)
2927 text = editortext
2927 text = editortext
2928
2928
2929 # strip away anything below this special string (used for editors that want
2929 # strip away anything below this special string (used for editors that want
2930 # to display the diff)
2930 # to display the diff)
2931 stripbelow = re.search(_linebelow, text, flags=re.MULTILINE)
2931 stripbelow = re.search(_linebelow, text, flags=re.MULTILINE)
2932 if stripbelow:
2932 if stripbelow:
2933 text = text[:stripbelow.start()]
2933 text = text[:stripbelow.start()]
2934
2934
2935 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
2935 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
2936 os.chdir(olddir)
2936 os.chdir(olddir)
2937
2937
2938 if finishdesc:
2938 if finishdesc:
2939 text = finishdesc(text)
2939 text = finishdesc(text)
2940 if not text.strip():
2940 if not text.strip():
2941 raise error.Abort(_("empty commit message"))
2941 raise error.Abort(_("empty commit message"))
2942 if unchangedmessagedetection and editortext == templatetext:
2942 if unchangedmessagedetection and editortext == templatetext:
2943 raise error.Abort(_("commit message unchanged"))
2943 raise error.Abort(_("commit message unchanged"))
2944
2944
2945 return text
2945 return text
2946
2946
2947 def buildcommittemplate(repo, ctx, subs, extramsg, tmpl):
2947 def buildcommittemplate(repo, ctx, subs, extramsg, tmpl):
2948 ui = repo.ui
2948 ui = repo.ui
2949 tmpl, mapfile = gettemplate(ui, tmpl, None)
2949 tmpl, mapfile = gettemplate(ui, tmpl, None)
2950
2950
2951 t = changeset_templater(ui, repo, None, {}, tmpl, mapfile, False)
2951 t = changeset_templater(ui, repo, None, {}, tmpl, mapfile, False)
2952
2952
2953 for k, v in repo.ui.configitems('committemplate'):
2953 for k, v in repo.ui.configitems('committemplate'):
2954 if k != 'changeset':
2954 if k != 'changeset':
2955 t.t.cache[k] = v
2955 t.t.cache[k] = v
2956
2956
2957 if not extramsg:
2957 if not extramsg:
2958 extramsg = '' # ensure that extramsg is string
2958 extramsg = '' # ensure that extramsg is string
2959
2959
2960 ui.pushbuffer()
2960 ui.pushbuffer()
2961 t.show(ctx, extramsg=extramsg)
2961 t.show(ctx, extramsg=extramsg)
2962 return ui.popbuffer()
2962 return ui.popbuffer()
2963
2963
2964 def hgprefix(msg):
2964 def hgprefix(msg):
2965 return "\n".join(["HG: %s" % a for a in msg.split("\n") if a])
2965 return "\n".join(["HG: %s" % a for a in msg.split("\n") if a])
2966
2966
2967 def buildcommittext(repo, ctx, subs, extramsg):
2967 def buildcommittext(repo, ctx, subs, extramsg):
2968 edittext = []
2968 edittext = []
2969 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2969 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2970 if ctx.description():
2970 if ctx.description():
2971 edittext.append(ctx.description())
2971 edittext.append(ctx.description())
2972 edittext.append("")
2972 edittext.append("")
2973 edittext.append("") # Empty line between message and comments.
2973 edittext.append("") # Empty line between message and comments.
2974 edittext.append(hgprefix(_("Enter commit message."
2974 edittext.append(hgprefix(_("Enter commit message."
2975 " Lines beginning with 'HG:' are removed.")))
2975 " Lines beginning with 'HG:' are removed.")))
2976 edittext.append(hgprefix(extramsg))
2976 edittext.append(hgprefix(extramsg))
2977 edittext.append("HG: --")
2977 edittext.append("HG: --")
2978 edittext.append(hgprefix(_("user: %s") % ctx.user()))
2978 edittext.append(hgprefix(_("user: %s") % ctx.user()))
2979 if ctx.p2():
2979 if ctx.p2():
2980 edittext.append(hgprefix(_("branch merge")))
2980 edittext.append(hgprefix(_("branch merge")))
2981 if ctx.branch():
2981 if ctx.branch():
2982 edittext.append(hgprefix(_("branch '%s'") % ctx.branch()))
2982 edittext.append(hgprefix(_("branch '%s'") % ctx.branch()))
2983 if bookmarks.isactivewdirparent(repo):
2983 if bookmarks.isactivewdirparent(repo):
2984 edittext.append(hgprefix(_("bookmark '%s'") % repo._activebookmark))
2984 edittext.append(hgprefix(_("bookmark '%s'") % repo._activebookmark))
2985 edittext.extend([hgprefix(_("subrepo %s") % s) for s in subs])
2985 edittext.extend([hgprefix(_("subrepo %s") % s) for s in subs])
2986 edittext.extend([hgprefix(_("added %s") % f) for f in added])
2986 edittext.extend([hgprefix(_("added %s") % f) for f in added])
2987 edittext.extend([hgprefix(_("changed %s") % f) for f in modified])
2987 edittext.extend([hgprefix(_("changed %s") % f) for f in modified])
2988 edittext.extend([hgprefix(_("removed %s") % f) for f in removed])
2988 edittext.extend([hgprefix(_("removed %s") % f) for f in removed])
2989 if not added and not modified and not removed:
2989 if not added and not modified and not removed:
2990 edittext.append(hgprefix(_("no files changed")))
2990 edittext.append(hgprefix(_("no files changed")))
2991 edittext.append("")
2991 edittext.append("")
2992
2992
2993 return "\n".join(edittext)
2993 return "\n".join(edittext)
2994
2994
2995 def commitstatus(repo, node, branch, bheads=None, opts=None):
2995 def commitstatus(repo, node, branch, bheads=None, opts=None):
2996 if opts is None:
2996 if opts is None:
2997 opts = {}
2997 opts = {}
2998 ctx = repo[node]
2998 ctx = repo[node]
2999 parents = ctx.parents()
2999 parents = ctx.parents()
3000
3000
3001 if (not opts.get('amend') and bheads and node not in bheads and not
3001 if (not opts.get('amend') and bheads and node not in bheads and not
3002 [x for x in parents if x.node() in bheads and x.branch() == branch]):
3002 [x for x in parents if x.node() in bheads and x.branch() == branch]):
3003 repo.ui.status(_('created new head\n'))
3003 repo.ui.status(_('created new head\n'))
3004 # The message is not printed for initial roots. For the other
3004 # The message is not printed for initial roots. For the other
3005 # changesets, it is printed in the following situations:
3005 # changesets, it is printed in the following situations:
3006 #
3006 #
3007 # Par column: for the 2 parents with ...
3007 # Par column: for the 2 parents with ...
3008 # N: null or no parent
3008 # N: null or no parent
3009 # B: parent is on another named branch
3009 # B: parent is on another named branch
3010 # C: parent is a regular non head changeset
3010 # C: parent is a regular non head changeset
3011 # H: parent was a branch head of the current branch
3011 # H: parent was a branch head of the current branch
3012 # Msg column: whether we print "created new head" message
3012 # Msg column: whether we print "created new head" message
3013 # In the following, it is assumed that there already exists some
3013 # In the following, it is assumed that there already exists some
3014 # initial branch heads of the current branch, otherwise nothing is
3014 # initial branch heads of the current branch, otherwise nothing is
3015 # printed anyway.
3015 # printed anyway.
3016 #
3016 #
3017 # Par Msg Comment
3017 # Par Msg Comment
3018 # N N y additional topo root
3018 # N N y additional topo root
3019 #
3019 #
3020 # B N y additional branch root
3020 # B N y additional branch root
3021 # C N y additional topo head
3021 # C N y additional topo head
3022 # H N n usual case
3022 # H N n usual case
3023 #
3023 #
3024 # B B y weird additional branch root
3024 # B B y weird additional branch root
3025 # C B y branch merge
3025 # C B y branch merge
3026 # H B n merge with named branch
3026 # H B n merge with named branch
3027 #
3027 #
3028 # C C y additional head from merge
3028 # C C y additional head from merge
3029 # C H n merge with a head
3029 # C H n merge with a head
3030 #
3030 #
3031 # H H n head merge: head count decreases
3031 # H H n head merge: head count decreases
3032
3032
3033 if not opts.get('close_branch'):
3033 if not opts.get('close_branch'):
3034 for r in parents:
3034 for r in parents:
3035 if r.closesbranch() and r.branch() == branch:
3035 if r.closesbranch() and r.branch() == branch:
3036 repo.ui.status(_('reopening closed branch head %d\n') % r)
3036 repo.ui.status(_('reopening closed branch head %d\n') % r)
3037
3037
3038 if repo.ui.debugflag:
3038 if repo.ui.debugflag:
3039 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
3039 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
3040 elif repo.ui.verbose:
3040 elif repo.ui.verbose:
3041 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
3041 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
3042
3042
3043 def postcommitstatus(repo, pats, opts):
3043 def postcommitstatus(repo, pats, opts):
3044 return repo.status(match=scmutil.match(repo[None], pats, opts))
3044 return repo.status(match=scmutil.match(repo[None], pats, opts))
3045
3045
3046 def revert(ui, repo, ctx, parents, *pats, **opts):
3046 def revert(ui, repo, ctx, parents, *pats, **opts):
3047 parent, p2 = parents
3047 parent, p2 = parents
3048 node = ctx.node()
3048 node = ctx.node()
3049
3049
3050 mf = ctx.manifest()
3050 mf = ctx.manifest()
3051 if node == p2:
3051 if node == p2:
3052 parent = p2
3052 parent = p2
3053
3053
3054 # need all matching names in dirstate and manifest of target rev,
3054 # need all matching names in dirstate and manifest of target rev,
3055 # so have to walk both. do not print errors if files exist in one
3055 # so have to walk both. do not print errors if files exist in one
3056 # but not other. in both cases, filesets should be evaluated against
3056 # but not other. in both cases, filesets should be evaluated against
3057 # workingctx to get consistent result (issue4497). this means 'set:**'
3057 # workingctx to get consistent result (issue4497). this means 'set:**'
3058 # cannot be used to select missing files from target rev.
3058 # cannot be used to select missing files from target rev.
3059
3059
3060 # `names` is a mapping for all elements in working copy and target revision
3060 # `names` is a mapping for all elements in working copy and target revision
3061 # The mapping is in the form:
3061 # The mapping is in the form:
3062 # <asb path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
3062 # <asb path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
3063 names = {}
3063 names = {}
3064
3064
3065 with repo.wlock():
3065 with repo.wlock():
3066 ## filling of the `names` mapping
3066 ## filling of the `names` mapping
3067 # walk dirstate to fill `names`
3067 # walk dirstate to fill `names`
3068
3068
3069 interactive = opts.get('interactive', False)
3069 interactive = opts.get('interactive', False)
3070 wctx = repo[None]
3070 wctx = repo[None]
3071 m = scmutil.match(wctx, pats, opts)
3071 m = scmutil.match(wctx, pats, opts)
3072
3072
3073 # we'll need this later
3073 # we'll need this later
3074 targetsubs = sorted(s for s in wctx.substate if m(s))
3074 targetsubs = sorted(s for s in wctx.substate if m(s))
3075
3075
3076 if not m.always():
3076 if not m.always():
3077 matcher = matchmod.badmatch(m, lambda x, y: False)
3077 matcher = matchmod.badmatch(m, lambda x, y: False)
3078 for abs in wctx.walk(matcher):
3078 for abs in wctx.walk(matcher):
3079 names[abs] = m.rel(abs), m.exact(abs)
3079 names[abs] = m.rel(abs), m.exact(abs)
3080
3080
3081 # walk target manifest to fill `names`
3081 # walk target manifest to fill `names`
3082
3082
3083 def badfn(path, msg):
3083 def badfn(path, msg):
3084 if path in names:
3084 if path in names:
3085 return
3085 return
3086 if path in ctx.substate:
3086 if path in ctx.substate:
3087 return
3087 return
3088 path_ = path + '/'
3088 path_ = path + '/'
3089 for f in names:
3089 for f in names:
3090 if f.startswith(path_):
3090 if f.startswith(path_):
3091 return
3091 return
3092 ui.warn("%s: %s\n" % (m.rel(path), msg))
3092 ui.warn("%s: %s\n" % (m.rel(path), msg))
3093
3093
3094 for abs in ctx.walk(matchmod.badmatch(m, badfn)):
3094 for abs in ctx.walk(matchmod.badmatch(m, badfn)):
3095 if abs not in names:
3095 if abs not in names:
3096 names[abs] = m.rel(abs), m.exact(abs)
3096 names[abs] = m.rel(abs), m.exact(abs)
3097
3097
3098 # Find status of all file in `names`.
3098 # Find status of all file in `names`.
3099 m = scmutil.matchfiles(repo, names)
3099 m = scmutil.matchfiles(repo, names)
3100
3100
3101 changes = repo.status(node1=node, match=m,
3101 changes = repo.status(node1=node, match=m,
3102 unknown=True, ignored=True, clean=True)
3102 unknown=True, ignored=True, clean=True)
3103 else:
3103 else:
3104 changes = repo.status(node1=node, match=m)
3104 changes = repo.status(node1=node, match=m)
3105 for kind in changes:
3105 for kind in changes:
3106 for abs in kind:
3106 for abs in kind:
3107 names[abs] = m.rel(abs), m.exact(abs)
3107 names[abs] = m.rel(abs), m.exact(abs)
3108
3108
3109 m = scmutil.matchfiles(repo, names)
3109 m = scmutil.matchfiles(repo, names)
3110
3110
3111 modified = set(changes.modified)
3111 modified = set(changes.modified)
3112 added = set(changes.added)
3112 added = set(changes.added)
3113 removed = set(changes.removed)
3113 removed = set(changes.removed)
3114 _deleted = set(changes.deleted)
3114 _deleted = set(changes.deleted)
3115 unknown = set(changes.unknown)
3115 unknown = set(changes.unknown)
3116 unknown.update(changes.ignored)
3116 unknown.update(changes.ignored)
3117 clean = set(changes.clean)
3117 clean = set(changes.clean)
3118 modadded = set()
3118 modadded = set()
3119
3119
3120 # We need to account for the state of the file in the dirstate,
3120 # We need to account for the state of the file in the dirstate,
3121 # even when we revert against something else than parent. This will
3121 # even when we revert against something else than parent. This will
3122 # slightly alter the behavior of revert (doing back up or not, delete
3122 # slightly alter the behavior of revert (doing back up or not, delete
3123 # or just forget etc).
3123 # or just forget etc).
3124 if parent == node:
3124 if parent == node:
3125 dsmodified = modified
3125 dsmodified = modified
3126 dsadded = added
3126 dsadded = added
3127 dsremoved = removed
3127 dsremoved = removed
3128 # store all local modifications, useful later for rename detection
3128 # store all local modifications, useful later for rename detection
3129 localchanges = dsmodified | dsadded
3129 localchanges = dsmodified | dsadded
3130 modified, added, removed = set(), set(), set()
3130 modified, added, removed = set(), set(), set()
3131 else:
3131 else:
3132 changes = repo.status(node1=parent, match=m)
3132 changes = repo.status(node1=parent, match=m)
3133 dsmodified = set(changes.modified)
3133 dsmodified = set(changes.modified)
3134 dsadded = set(changes.added)
3134 dsadded = set(changes.added)
3135 dsremoved = set(changes.removed)
3135 dsremoved = set(changes.removed)
3136 # store all local modifications, useful later for rename detection
3136 # store all local modifications, useful later for rename detection
3137 localchanges = dsmodified | dsadded
3137 localchanges = dsmodified | dsadded
3138
3138
3139 # only take into account for removes between wc and target
3139 # only take into account for removes between wc and target
3140 clean |= dsremoved - removed
3140 clean |= dsremoved - removed
3141 dsremoved &= removed
3141 dsremoved &= removed
3142 # distinct between dirstate remove and other
3142 # distinct between dirstate remove and other
3143 removed -= dsremoved
3143 removed -= dsremoved
3144
3144
3145 modadded = added & dsmodified
3145 modadded = added & dsmodified
3146 added -= modadded
3146 added -= modadded
3147
3147
3148 # tell newly modified apart.
3148 # tell newly modified apart.
3149 dsmodified &= modified
3149 dsmodified &= modified
3150 dsmodified |= modified & dsadded # dirstate added may need backup
3150 dsmodified |= modified & dsadded # dirstate added may need backup
3151 modified -= dsmodified
3151 modified -= dsmodified
3152
3152
3153 # We need to wait for some post-processing to update this set
3153 # We need to wait for some post-processing to update this set
3154 # before making the distinction. The dirstate will be used for
3154 # before making the distinction. The dirstate will be used for
3155 # that purpose.
3155 # that purpose.
3156 dsadded = added
3156 dsadded = added
3157
3157
3158 # in case of merge, files that are actually added can be reported as
3158 # in case of merge, files that are actually added can be reported as
3159 # modified, we need to post process the result
3159 # modified, we need to post process the result
3160 if p2 != nullid:
3160 if p2 != nullid:
3161 mergeadd = set(dsmodified)
3161 mergeadd = set(dsmodified)
3162 for path in dsmodified:
3162 for path in dsmodified:
3163 if path in mf:
3163 if path in mf:
3164 mergeadd.remove(path)
3164 mergeadd.remove(path)
3165 dsadded |= mergeadd
3165 dsadded |= mergeadd
3166 dsmodified -= mergeadd
3166 dsmodified -= mergeadd
3167
3167
3168 # if f is a rename, update `names` to also revert the source
3168 # if f is a rename, update `names` to also revert the source
3169 cwd = repo.getcwd()
3169 cwd = repo.getcwd()
3170 for f in localchanges:
3170 for f in localchanges:
3171 src = repo.dirstate.copied(f)
3171 src = repo.dirstate.copied(f)
3172 # XXX should we check for rename down to target node?
3172 # XXX should we check for rename down to target node?
3173 if src and src not in names and repo.dirstate[src] == 'r':
3173 if src and src not in names and repo.dirstate[src] == 'r':
3174 dsremoved.add(src)
3174 dsremoved.add(src)
3175 names[src] = (repo.pathto(src, cwd), True)
3175 names[src] = (repo.pathto(src, cwd), True)
3176
3176
3177 # determine the exact nature of the deleted changesets
3177 # determine the exact nature of the deleted changesets
3178 deladded = set(_deleted)
3178 deladded = set(_deleted)
3179 for path in _deleted:
3179 for path in _deleted:
3180 if path in mf:
3180 if path in mf:
3181 deladded.remove(path)
3181 deladded.remove(path)
3182 deleted = _deleted - deladded
3182 deleted = _deleted - deladded
3183
3183
3184 # distinguish between file to forget and the other
3184 # distinguish between file to forget and the other
3185 added = set()
3185 added = set()
3186 for abs in dsadded:
3186 for abs in dsadded:
3187 if repo.dirstate[abs] != 'a':
3187 if repo.dirstate[abs] != 'a':
3188 added.add(abs)
3188 added.add(abs)
3189 dsadded -= added
3189 dsadded -= added
3190
3190
3191 for abs in deladded:
3191 for abs in deladded:
3192 if repo.dirstate[abs] == 'a':
3192 if repo.dirstate[abs] == 'a':
3193 dsadded.add(abs)
3193 dsadded.add(abs)
3194 deladded -= dsadded
3194 deladded -= dsadded
3195
3195
3196 # For files marked as removed, we check if an unknown file is present at
3196 # For files marked as removed, we check if an unknown file is present at
3197 # the same path. If a such file exists it may need to be backed up.
3197 # the same path. If a such file exists it may need to be backed up.
3198 # Making the distinction at this stage helps have simpler backup
3198 # Making the distinction at this stage helps have simpler backup
3199 # logic.
3199 # logic.
3200 removunk = set()
3200 removunk = set()
3201 for abs in removed:
3201 for abs in removed:
3202 target = repo.wjoin(abs)
3202 target = repo.wjoin(abs)
3203 if os.path.lexists(target):
3203 if os.path.lexists(target):
3204 removunk.add(abs)
3204 removunk.add(abs)
3205 removed -= removunk
3205 removed -= removunk
3206
3206
3207 dsremovunk = set()
3207 dsremovunk = set()
3208 for abs in dsremoved:
3208 for abs in dsremoved:
3209 target = repo.wjoin(abs)
3209 target = repo.wjoin(abs)
3210 if os.path.lexists(target):
3210 if os.path.lexists(target):
3211 dsremovunk.add(abs)
3211 dsremovunk.add(abs)
3212 dsremoved -= dsremovunk
3212 dsremoved -= dsremovunk
3213
3213
3214 # action to be actually performed by revert
3214 # action to be actually performed by revert
3215 # (<list of file>, message>) tuple
3215 # (<list of file>, message>) tuple
3216 actions = {'revert': ([], _('reverting %s\n')),
3216 actions = {'revert': ([], _('reverting %s\n')),
3217 'add': ([], _('adding %s\n')),
3217 'add': ([], _('adding %s\n')),
3218 'remove': ([], _('removing %s\n')),
3218 'remove': ([], _('removing %s\n')),
3219 'drop': ([], _('removing %s\n')),
3219 'drop': ([], _('removing %s\n')),
3220 'forget': ([], _('forgetting %s\n')),
3220 'forget': ([], _('forgetting %s\n')),
3221 'undelete': ([], _('undeleting %s\n')),
3221 'undelete': ([], _('undeleting %s\n')),
3222 'noop': (None, _('no changes needed to %s\n')),
3222 'noop': (None, _('no changes needed to %s\n')),
3223 'unknown': (None, _('file not managed: %s\n')),
3223 'unknown': (None, _('file not managed: %s\n')),
3224 }
3224 }
3225
3225
3226 # "constant" that convey the backup strategy.
3226 # "constant" that convey the backup strategy.
3227 # All set to `discard` if `no-backup` is set do avoid checking
3227 # All set to `discard` if `no-backup` is set do avoid checking
3228 # no_backup lower in the code.
3228 # no_backup lower in the code.
3229 # These values are ordered for comparison purposes
3229 # These values are ordered for comparison purposes
3230 backupinteractive = 3 # do backup if interactively modified
3230 backupinteractive = 3 # do backup if interactively modified
3231 backup = 2 # unconditionally do backup
3231 backup = 2 # unconditionally do backup
3232 check = 1 # check if the existing file differs from target
3232 check = 1 # check if the existing file differs from target
3233 discard = 0 # never do backup
3233 discard = 0 # never do backup
3234 if opts.get('no_backup'):
3234 if opts.get('no_backup'):
3235 backupinteractive = backup = check = discard
3235 backupinteractive = backup = check = discard
3236 if interactive:
3236 if interactive:
3237 dsmodifiedbackup = backupinteractive
3237 dsmodifiedbackup = backupinteractive
3238 else:
3238 else:
3239 dsmodifiedbackup = backup
3239 dsmodifiedbackup = backup
3240 tobackup = set()
3240 tobackup = set()
3241
3241
3242 backupanddel = actions['remove']
3242 backupanddel = actions['remove']
3243 if not opts.get('no_backup'):
3243 if not opts.get('no_backup'):
3244 backupanddel = actions['drop']
3244 backupanddel = actions['drop']
3245
3245
3246 disptable = (
3246 disptable = (
3247 # dispatch table:
3247 # dispatch table:
3248 # file state
3248 # file state
3249 # action
3249 # action
3250 # make backup
3250 # make backup
3251
3251
3252 ## Sets that results that will change file on disk
3252 ## Sets that results that will change file on disk
3253 # Modified compared to target, no local change
3253 # Modified compared to target, no local change
3254 (modified, actions['revert'], discard),
3254 (modified, actions['revert'], discard),
3255 # Modified compared to target, but local file is deleted
3255 # Modified compared to target, but local file is deleted
3256 (deleted, actions['revert'], discard),
3256 (deleted, actions['revert'], discard),
3257 # Modified compared to target, local change
3257 # Modified compared to target, local change
3258 (dsmodified, actions['revert'], dsmodifiedbackup),
3258 (dsmodified, actions['revert'], dsmodifiedbackup),
3259 # Added since target
3259 # Added since target
3260 (added, actions['remove'], discard),
3260 (added, actions['remove'], discard),
3261 # Added in working directory
3261 # Added in working directory
3262 (dsadded, actions['forget'], discard),
3262 (dsadded, actions['forget'], discard),
3263 # Added since target, have local modification
3263 # Added since target, have local modification
3264 (modadded, backupanddel, backup),
3264 (modadded, backupanddel, backup),
3265 # Added since target but file is missing in working directory
3265 # Added since target but file is missing in working directory
3266 (deladded, actions['drop'], discard),
3266 (deladded, actions['drop'], discard),
3267 # Removed since target, before working copy parent
3267 # Removed since target, before working copy parent
3268 (removed, actions['add'], discard),
3268 (removed, actions['add'], discard),
3269 # Same as `removed` but an unknown file exists at the same path
3269 # Same as `removed` but an unknown file exists at the same path
3270 (removunk, actions['add'], check),
3270 (removunk, actions['add'], check),
3271 # Removed since targe, marked as such in working copy parent
3271 # Removed since targe, marked as such in working copy parent
3272 (dsremoved, actions['undelete'], discard),
3272 (dsremoved, actions['undelete'], discard),
3273 # Same as `dsremoved` but an unknown file exists at the same path
3273 # Same as `dsremoved` but an unknown file exists at the same path
3274 (dsremovunk, actions['undelete'], check),
3274 (dsremovunk, actions['undelete'], check),
3275 ## the following sets does not result in any file changes
3275 ## the following sets does not result in any file changes
3276 # File with no modification
3276 # File with no modification
3277 (clean, actions['noop'], discard),
3277 (clean, actions['noop'], discard),
3278 # Existing file, not tracked anywhere
3278 # Existing file, not tracked anywhere
3279 (unknown, actions['unknown'], discard),
3279 (unknown, actions['unknown'], discard),
3280 )
3280 )
3281
3281
3282 for abs, (rel, exact) in sorted(names.items()):
3282 for abs, (rel, exact) in sorted(names.items()):
3283 # target file to be touch on disk (relative to cwd)
3283 # target file to be touch on disk (relative to cwd)
3284 target = repo.wjoin(abs)
3284 target = repo.wjoin(abs)
3285 # search the entry in the dispatch table.
3285 # search the entry in the dispatch table.
3286 # if the file is in any of these sets, it was touched in the working
3286 # if the file is in any of these sets, it was touched in the working
3287 # directory parent and we are sure it needs to be reverted.
3287 # directory parent and we are sure it needs to be reverted.
3288 for table, (xlist, msg), dobackup in disptable:
3288 for table, (xlist, msg), dobackup in disptable:
3289 if abs not in table:
3289 if abs not in table:
3290 continue
3290 continue
3291 if xlist is not None:
3291 if xlist is not None:
3292 xlist.append(abs)
3292 xlist.append(abs)
3293 if dobackup:
3293 if dobackup:
3294 # If in interactive mode, don't automatically create
3294 # If in interactive mode, don't automatically create
3295 # .orig files (issue4793)
3295 # .orig files (issue4793)
3296 if dobackup == backupinteractive:
3296 if dobackup == backupinteractive:
3297 tobackup.add(abs)
3297 tobackup.add(abs)
3298 elif (backup <= dobackup or wctx[abs].cmp(ctx[abs])):
3298 elif (backup <= dobackup or wctx[abs].cmp(ctx[abs])):
3299 bakname = scmutil.origpath(ui, repo, rel)
3299 bakname = scmutil.origpath(ui, repo, rel)
3300 ui.note(_('saving current version of %s as %s\n') %
3300 ui.note(_('saving current version of %s as %s\n') %
3301 (rel, bakname))
3301 (rel, bakname))
3302 if not opts.get('dry_run'):
3302 if not opts.get('dry_run'):
3303 if interactive:
3303 if interactive:
3304 util.copyfile(target, bakname)
3304 util.copyfile(target, bakname)
3305 else:
3305 else:
3306 util.rename(target, bakname)
3306 util.rename(target, bakname)
3307 if ui.verbose or not exact:
3307 if ui.verbose or not exact:
3308 if not isinstance(msg, basestring):
3308 if not isinstance(msg, basestring):
3309 msg = msg(abs)
3309 msg = msg(abs)
3310 ui.status(msg % rel)
3310 ui.status(msg % rel)
3311 elif exact:
3311 elif exact:
3312 ui.warn(msg % rel)
3312 ui.warn(msg % rel)
3313 break
3313 break
3314
3314
3315 if not opts.get('dry_run'):
3315 if not opts.get('dry_run'):
3316 needdata = ('revert', 'add', 'undelete')
3316 needdata = ('revert', 'add', 'undelete')
3317 _revertprefetch(repo, ctx, *[actions[name][0] for name in needdata])
3317 _revertprefetch(repo, ctx, *[actions[name][0] for name in needdata])
3318 _performrevert(repo, parents, ctx, actions, interactive, tobackup)
3318 _performrevert(repo, parents, ctx, actions, interactive, tobackup)
3319
3319
3320 if targetsubs:
3320 if targetsubs:
3321 # Revert the subrepos on the revert list
3321 # Revert the subrepos on the revert list
3322 for sub in targetsubs:
3322 for sub in targetsubs:
3323 try:
3323 try:
3324 wctx.sub(sub).revert(ctx.substate[sub], *pats, **opts)
3324 wctx.sub(sub).revert(ctx.substate[sub], *pats, **opts)
3325 except KeyError:
3325 except KeyError:
3326 raise error.Abort("subrepository '%s' does not exist in %s!"
3326 raise error.Abort("subrepository '%s' does not exist in %s!"
3327 % (sub, short(ctx.node())))
3327 % (sub, short(ctx.node())))
3328
3328
3329 def _revertprefetch(repo, ctx, *files):
3329 def _revertprefetch(repo, ctx, *files):
3330 """Let extension changing the storage layer prefetch content"""
3330 """Let extension changing the storage layer prefetch content"""
3331 pass
3331 pass
3332
3332
3333 def _performrevert(repo, parents, ctx, actions, interactive=False,
3333 def _performrevert(repo, parents, ctx, actions, interactive=False,
3334 tobackup=None):
3334 tobackup=None):
3335 """function that actually perform all the actions computed for revert
3335 """function that actually perform all the actions computed for revert
3336
3336
3337 This is an independent function to let extension to plug in and react to
3337 This is an independent function to let extension to plug in and react to
3338 the imminent revert.
3338 the imminent revert.
3339
3339
3340 Make sure you have the working directory locked when calling this function.
3340 Make sure you have the working directory locked when calling this function.
3341 """
3341 """
3342 parent, p2 = parents
3342 parent, p2 = parents
3343 node = ctx.node()
3343 node = ctx.node()
3344 excluded_files = []
3344 excluded_files = []
3345 matcher_opts = {"exclude": excluded_files}
3345 matcher_opts = {"exclude": excluded_files}
3346
3346
3347 def checkout(f):
3347 def checkout(f):
3348 fc = ctx[f]
3348 fc = ctx[f]
3349 repo.wwrite(f, fc.data(), fc.flags())
3349 repo.wwrite(f, fc.data(), fc.flags())
3350
3350
3351 def doremove(f):
3351 def doremove(f):
3352 try:
3352 try:
3353 repo.wvfs.unlinkpath(f)
3353 repo.wvfs.unlinkpath(f)
3354 except OSError:
3354 except OSError:
3355 pass
3355 pass
3356 repo.dirstate.remove(f)
3356 repo.dirstate.remove(f)
3357
3357
3358 audit_path = pathutil.pathauditor(repo.root)
3358 audit_path = pathutil.pathauditor(repo.root)
3359 for f in actions['forget'][0]:
3359 for f in actions['forget'][0]:
3360 if interactive:
3360 if interactive:
3361 choice = repo.ui.promptchoice(
3361 choice = repo.ui.promptchoice(
3362 _("forget added file %s (Yn)?$$ &Yes $$ &No") % f)
3362 _("forget added file %s (Yn)?$$ &Yes $$ &No") % f)
3363 if choice == 0:
3363 if choice == 0:
3364 repo.dirstate.drop(f)
3364 repo.dirstate.drop(f)
3365 else:
3365 else:
3366 excluded_files.append(repo.wjoin(f))
3366 excluded_files.append(repo.wjoin(f))
3367 else:
3367 else:
3368 repo.dirstate.drop(f)
3368 repo.dirstate.drop(f)
3369 for f in actions['remove'][0]:
3369 for f in actions['remove'][0]:
3370 audit_path(f)
3370 audit_path(f)
3371 if interactive:
3371 if interactive:
3372 choice = repo.ui.promptchoice(
3372 choice = repo.ui.promptchoice(
3373 _("remove added file %s (Yn)?$$ &Yes $$ &No") % f)
3373 _("remove added file %s (Yn)?$$ &Yes $$ &No") % f)
3374 if choice == 0:
3374 if choice == 0:
3375 doremove(f)
3375 doremove(f)
3376 else:
3376 else:
3377 excluded_files.append(repo.wjoin(f))
3377 excluded_files.append(repo.wjoin(f))
3378 else:
3378 else:
3379 doremove(f)
3379 doremove(f)
3380 for f in actions['drop'][0]:
3380 for f in actions['drop'][0]:
3381 audit_path(f)
3381 audit_path(f)
3382 repo.dirstate.remove(f)
3382 repo.dirstate.remove(f)
3383
3383
3384 normal = None
3384 normal = None
3385 if node == parent:
3385 if node == parent:
3386 # We're reverting to our parent. If possible, we'd like status
3386 # We're reverting to our parent. If possible, we'd like status
3387 # to report the file as clean. We have to use normallookup for
3387 # to report the file as clean. We have to use normallookup for
3388 # merges to avoid losing information about merged/dirty files.
3388 # merges to avoid losing information about merged/dirty files.
3389 if p2 != nullid:
3389 if p2 != nullid:
3390 normal = repo.dirstate.normallookup
3390 normal = repo.dirstate.normallookup
3391 else:
3391 else:
3392 normal = repo.dirstate.normal
3392 normal = repo.dirstate.normal
3393
3393
3394 newlyaddedandmodifiedfiles = set()
3394 newlyaddedandmodifiedfiles = set()
3395 if interactive:
3395 if interactive:
3396 # Prompt the user for changes to revert
3396 # Prompt the user for changes to revert
3397 torevert = [repo.wjoin(f) for f in actions['revert'][0]]
3397 torevert = [repo.wjoin(f) for f in actions['revert'][0]]
3398 m = scmutil.match(ctx, torevert, matcher_opts)
3398 m = scmutil.match(ctx, torevert, matcher_opts)
3399 diffopts = patch.difffeatureopts(repo.ui, whitespace=True)
3399 diffopts = patch.difffeatureopts(repo.ui, whitespace=True)
3400 diffopts.nodates = True
3400 diffopts.nodates = True
3401 diffopts.git = True
3401 diffopts.git = True
3402 operation = 'discard'
3402 operation = 'discard'
3403 reversehunks = True
3403 reversehunks = True
3404 if node != parent:
3404 if node != parent:
3405 operation = 'revert'
3405 operation = 'revert'
3406 reversehunks = repo.ui.configbool('experimental',
3406 reversehunks = repo.ui.configbool('experimental',
3407 'revertalternateinteractivemode',
3407 'revertalternateinteractivemode',
3408 True)
3408 True)
3409 if reversehunks:
3409 if reversehunks:
3410 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
3410 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
3411 else:
3411 else:
3412 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
3412 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
3413 originalchunks = patch.parsepatch(diff)
3413 originalchunks = patch.parsepatch(diff)
3414
3414
3415 try:
3415 try:
3416
3416
3417 chunks, opts = recordfilter(repo.ui, originalchunks,
3417 chunks, opts = recordfilter(repo.ui, originalchunks,
3418 operation=operation)
3418 operation=operation)
3419 if reversehunks:
3419 if reversehunks:
3420 chunks = patch.reversehunks(chunks)
3420 chunks = patch.reversehunks(chunks)
3421
3421
3422 except patch.PatchError as err:
3422 except patch.PatchError as err:
3423 raise error.Abort(_('error parsing patch: %s') % err)
3423 raise error.Abort(_('error parsing patch: %s') % err)
3424
3424
3425 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
3425 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
3426 if tobackup is None:
3426 if tobackup is None:
3427 tobackup = set()
3427 tobackup = set()
3428 # Apply changes
3428 # Apply changes
3429 fp = stringio()
3429 fp = stringio()
3430 for c in chunks:
3430 for c in chunks:
3431 # Create a backup file only if this hunk should be backed up
3431 # Create a backup file only if this hunk should be backed up
3432 if ishunk(c) and c.header.filename() in tobackup:
3432 if ishunk(c) and c.header.filename() in tobackup:
3433 abs = c.header.filename()
3433 abs = c.header.filename()
3434 target = repo.wjoin(abs)
3434 target = repo.wjoin(abs)
3435 bakname = scmutil.origpath(repo.ui, repo, m.rel(abs))
3435 bakname = scmutil.origpath(repo.ui, repo, m.rel(abs))
3436 util.copyfile(target, bakname)
3436 util.copyfile(target, bakname)
3437 tobackup.remove(abs)
3437 tobackup.remove(abs)
3438 c.write(fp)
3438 c.write(fp)
3439 dopatch = fp.tell()
3439 dopatch = fp.tell()
3440 fp.seek(0)
3440 fp.seek(0)
3441 if dopatch:
3441 if dopatch:
3442 try:
3442 try:
3443 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3443 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3444 except patch.PatchError as err:
3444 except patch.PatchError as err:
3445 raise error.Abort(str(err))
3445 raise error.Abort(str(err))
3446 del fp
3446 del fp
3447 else:
3447 else:
3448 for f in actions['revert'][0]:
3448 for f in actions['revert'][0]:
3449 checkout(f)
3449 checkout(f)
3450 if normal:
3450 if normal:
3451 normal(f)
3451 normal(f)
3452
3452
3453 for f in actions['add'][0]:
3453 for f in actions['add'][0]:
3454 # Don't checkout modified files, they are already created by the diff
3454 # Don't checkout modified files, they are already created by the diff
3455 if f not in newlyaddedandmodifiedfiles:
3455 if f not in newlyaddedandmodifiedfiles:
3456 checkout(f)
3456 checkout(f)
3457 repo.dirstate.add(f)
3457 repo.dirstate.add(f)
3458
3458
3459 normal = repo.dirstate.normallookup
3459 normal = repo.dirstate.normallookup
3460 if node == parent and p2 == nullid:
3460 if node == parent and p2 == nullid:
3461 normal = repo.dirstate.normal
3461 normal = repo.dirstate.normal
3462 for f in actions['undelete'][0]:
3462 for f in actions['undelete'][0]:
3463 checkout(f)
3463 checkout(f)
3464 normal(f)
3464 normal(f)
3465
3465
3466 copied = copies.pathcopies(repo[parent], ctx)
3466 copied = copies.pathcopies(repo[parent], ctx)
3467
3467
3468 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
3468 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
3469 if f in copied:
3469 if f in copied:
3470 repo.dirstate.copy(copied[f], f)
3470 repo.dirstate.copy(copied[f], f)
3471
3471
3472 class command(registrar.command):
3472 class command(registrar.command):
3473 def _doregister(self, func, name, *args, **kwargs):
3473 def _doregister(self, func, name, *args, **kwargs):
3474 func._deprecatedregistrar = True # flag for deprecwarn in extensions.py
3474 func._deprecatedregistrar = True # flag for deprecwarn in extensions.py
3475 return super(command, self)._doregister(func, name, *args, **kwargs)
3475 return super(command, self)._doregister(func, name, *args, **kwargs)
3476
3476
3477 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3477 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3478 # commands.outgoing. "missing" is "missing" of the result of
3478 # commands.outgoing. "missing" is "missing" of the result of
3479 # "findcommonoutgoing()"
3479 # "findcommonoutgoing()"
3480 outgoinghooks = util.hooks()
3480 outgoinghooks = util.hooks()
3481
3481
3482 # a list of (ui, repo) functions called by commands.summary
3482 # a list of (ui, repo) functions called by commands.summary
3483 summaryhooks = util.hooks()
3483 summaryhooks = util.hooks()
3484
3484
3485 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3485 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3486 #
3486 #
3487 # functions should return tuple of booleans below, if 'changes' is None:
3487 # functions should return tuple of booleans below, if 'changes' is None:
3488 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3488 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3489 #
3489 #
3490 # otherwise, 'changes' is a tuple of tuples below:
3490 # otherwise, 'changes' is a tuple of tuples below:
3491 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3491 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3492 # - (desturl, destbranch, destpeer, outgoing)
3492 # - (desturl, destbranch, destpeer, outgoing)
3493 summaryremotehooks = util.hooks()
3493 summaryremotehooks = util.hooks()
3494
3494
3495 # A list of state files kept by multistep operations like graft.
3495 # A list of state files kept by multistep operations like graft.
3496 # Since graft cannot be aborted, it is considered 'clearable' by update.
3496 # Since graft cannot be aborted, it is considered 'clearable' by update.
3497 # note: bisect is intentionally excluded
3497 # note: bisect is intentionally excluded
3498 # (state file, clearable, allowcommit, error, hint)
3498 # (state file, clearable, allowcommit, error, hint)
3499 unfinishedstates = [
3499 unfinishedstates = [
3500 ('graftstate', True, False, _('graft in progress'),
3500 ('graftstate', True, False, _('graft in progress'),
3501 _("use 'hg graft --continue' or 'hg update' to abort")),
3501 _("use 'hg graft --continue' or 'hg update' to abort")),
3502 ('updatestate', True, False, _('last update was interrupted'),
3502 ('updatestate', True, False, _('last update was interrupted'),
3503 _("use 'hg update' to get a consistent checkout"))
3503 _("use 'hg update' to get a consistent checkout"))
3504 ]
3504 ]
3505
3505
3506 def checkunfinished(repo, commit=False):
3506 def checkunfinished(repo, commit=False):
3507 '''Look for an unfinished multistep operation, like graft, and abort
3507 '''Look for an unfinished multistep operation, like graft, and abort
3508 if found. It's probably good to check this right before
3508 if found. It's probably good to check this right before
3509 bailifchanged().
3509 bailifchanged().
3510 '''
3510 '''
3511 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3511 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3512 if commit and allowcommit:
3512 if commit and allowcommit:
3513 continue
3513 continue
3514 if repo.vfs.exists(f):
3514 if repo.vfs.exists(f):
3515 raise error.Abort(msg, hint=hint)
3515 raise error.Abort(msg, hint=hint)
3516
3516
3517 def clearunfinished(repo):
3517 def clearunfinished(repo):
3518 '''Check for unfinished operations (as above), and clear the ones
3518 '''Check for unfinished operations (as above), and clear the ones
3519 that are clearable.
3519 that are clearable.
3520 '''
3520 '''
3521 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3521 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3522 if not clearable and repo.vfs.exists(f):
3522 if not clearable and repo.vfs.exists(f):
3523 raise error.Abort(msg, hint=hint)
3523 raise error.Abort(msg, hint=hint)
3524 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3524 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3525 if clearable and repo.vfs.exists(f):
3525 if clearable and repo.vfs.exists(f):
3526 util.unlink(repo.vfs.join(f))
3526 util.unlink(repo.vfs.join(f))
3527
3527
3528 afterresolvedstates = [
3528 afterresolvedstates = [
3529 ('graftstate',
3529 ('graftstate',
3530 _('hg graft --continue')),
3530 _('hg graft --continue')),
3531 ]
3531 ]
3532
3532
3533 def howtocontinue(repo):
3533 def howtocontinue(repo):
3534 '''Check for an unfinished operation and return the command to finish
3534 '''Check for an unfinished operation and return the command to finish
3535 it.
3535 it.
3536
3536
3537 afterresolvedstates tuples define a .hg/{file} and the corresponding
3537 afterresolvedstates tuples define a .hg/{file} and the corresponding
3538 command needed to finish it.
3538 command needed to finish it.
3539
3539
3540 Returns a (msg, warning) tuple. 'msg' is a string and 'warning' is
3540 Returns a (msg, warning) tuple. 'msg' is a string and 'warning' is
3541 a boolean.
3541 a boolean.
3542 '''
3542 '''
3543 contmsg = _("continue: %s")
3543 contmsg = _("continue: %s")
3544 for f, msg in afterresolvedstates:
3544 for f, msg in afterresolvedstates:
3545 if repo.vfs.exists(f):
3545 if repo.vfs.exists(f):
3546 return contmsg % msg, True
3546 return contmsg % msg, True
3547 workingctx = repo[None]
3547 workingctx = repo[None]
3548 dirty = any(repo.status()) or any(workingctx.sub(s).dirty()
3548 dirty = any(repo.status()) or any(workingctx.sub(s).dirty()
3549 for s in workingctx.substate)
3549 for s in workingctx.substate)
3550 if dirty:
3550 if dirty:
3551 return contmsg % _("hg commit"), False
3551 return contmsg % _("hg commit"), False
3552 return None, None
3552 return None, None
3553
3553
3554 def checkafterresolved(repo):
3554 def checkafterresolved(repo):
3555 '''Inform the user about the next action after completing hg resolve
3555 '''Inform the user about the next action after completing hg resolve
3556
3556
3557 If there's a matching afterresolvedstates, howtocontinue will yield
3557 If there's a matching afterresolvedstates, howtocontinue will yield
3558 repo.ui.warn as the reporter.
3558 repo.ui.warn as the reporter.
3559
3559
3560 Otherwise, it will yield repo.ui.note.
3560 Otherwise, it will yield repo.ui.note.
3561 '''
3561 '''
3562 msg, warning = howtocontinue(repo)
3562 msg, warning = howtocontinue(repo)
3563 if msg is not None:
3563 if msg is not None:
3564 if warning:
3564 if warning:
3565 repo.ui.warn("%s\n" % msg)
3565 repo.ui.warn("%s\n" % msg)
3566 else:
3566 else:
3567 repo.ui.note("%s\n" % msg)
3567 repo.ui.note("%s\n" % msg)
3568
3568
3569 def wrongtooltocontinue(repo, task):
3569 def wrongtooltocontinue(repo, task):
3570 '''Raise an abort suggesting how to properly continue if there is an
3570 '''Raise an abort suggesting how to properly continue if there is an
3571 active task.
3571 active task.
3572
3572
3573 Uses howtocontinue() to find the active task.
3573 Uses howtocontinue() to find the active task.
3574
3574
3575 If there's no task (repo.ui.note for 'hg commit'), it does not offer
3575 If there's no task (repo.ui.note for 'hg commit'), it does not offer
3576 a hint.
3576 a hint.
3577 '''
3577 '''
3578 after = howtocontinue(repo)
3578 after = howtocontinue(repo)
3579 hint = None
3579 hint = None
3580 if after[1]:
3580 if after[1]:
3581 hint = after[0]
3581 hint = after[0]
3582 raise error.Abort(_('no %s in progress') % task, hint=hint)
3582 raise error.Abort(_('no %s in progress') % task, hint=hint)
@@ -1,5489 +1,5490 b''
1 # commands.py - command processing for mercurial
1 # commands.py - command processing for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import difflib
10 import difflib
11 import errno
11 import errno
12 import os
12 import os
13 import re
13 import re
14
14
15 from .i18n import _
15 from .i18n import _
16 from .node import (
16 from .node import (
17 hex,
17 hex,
18 nullid,
18 nullid,
19 nullrev,
19 nullrev,
20 short,
20 short,
21 )
21 )
22 from . import (
22 from . import (
23 archival,
23 archival,
24 bookmarks,
24 bookmarks,
25 bundle2,
25 bundle2,
26 changegroup,
26 changegroup,
27 cmdutil,
27 cmdutil,
28 copies,
28 copies,
29 debugcommands as debugcommandsmod,
29 debugcommands as debugcommandsmod,
30 destutil,
30 destutil,
31 dirstateguard,
31 dirstateguard,
32 discovery,
32 discovery,
33 encoding,
33 encoding,
34 error,
34 error,
35 exchange,
35 exchange,
36 extensions,
36 extensions,
37 graphmod,
37 graphmod,
38 hbisect,
38 hbisect,
39 help,
39 help,
40 hg,
40 hg,
41 lock as lockmod,
41 lock as lockmod,
42 merge as mergemod,
42 merge as mergemod,
43 obsolete,
43 obsolete,
44 patch,
44 patch,
45 phases,
45 phases,
46 pycompat,
46 pycompat,
47 rcutil,
47 rcutil,
48 registrar,
48 registrar,
49 revsetlang,
49 revsetlang,
50 scmutil,
50 scmutil,
51 server,
51 server,
52 sshserver,
52 sshserver,
53 streamclone,
53 streamclone,
54 tags as tagsmod,
54 tags as tagsmod,
55 templatekw,
55 templatekw,
56 ui as uimod,
56 ui as uimod,
57 util,
57 util,
58 )
58 )
59
59
60 release = lockmod.release
60 release = lockmod.release
61
61
62 table = {}
62 table = {}
63 table.update(debugcommandsmod.command._table)
63 table.update(debugcommandsmod.command._table)
64
64
65 command = registrar.command(table)
65 command = registrar.command(table)
66
66
67 # label constants
67 # label constants
68 # until 3.5, bookmarks.current was the advertised name, not
68 # until 3.5, bookmarks.current was the advertised name, not
69 # bookmarks.active, so we must use both to avoid breaking old
69 # bookmarks.active, so we must use both to avoid breaking old
70 # custom styles
70 # custom styles
71 activebookmarklabel = 'bookmarks.active bookmarks.current'
71 activebookmarklabel = 'bookmarks.active bookmarks.current'
72
72
73 # common command options
73 # common command options
74
74
75 globalopts = [
75 globalopts = [
76 ('R', 'repository', '',
76 ('R', 'repository', '',
77 _('repository root directory or name of overlay bundle file'),
77 _('repository root directory or name of overlay bundle file'),
78 _('REPO')),
78 _('REPO')),
79 ('', 'cwd', '',
79 ('', 'cwd', '',
80 _('change working directory'), _('DIR')),
80 _('change working directory'), _('DIR')),
81 ('y', 'noninteractive', None,
81 ('y', 'noninteractive', None,
82 _('do not prompt, automatically pick the first choice for all prompts')),
82 _('do not prompt, automatically pick the first choice for all prompts')),
83 ('q', 'quiet', None, _('suppress output')),
83 ('q', 'quiet', None, _('suppress output')),
84 ('v', 'verbose', None, _('enable additional output')),
84 ('v', 'verbose', None, _('enable additional output')),
85 ('', 'color', '',
85 ('', 'color', '',
86 # i18n: 'always', 'auto', 'never', and 'debug' are keywords
86 # i18n: 'always', 'auto', 'never', and 'debug' are keywords
87 # and should not be translated
87 # and should not be translated
88 _("when to colorize (boolean, always, auto, never, or debug)"),
88 _("when to colorize (boolean, always, auto, never, or debug)"),
89 _('TYPE')),
89 _('TYPE')),
90 ('', 'config', [],
90 ('', 'config', [],
91 _('set/override config option (use \'section.name=value\')'),
91 _('set/override config option (use \'section.name=value\')'),
92 _('CONFIG')),
92 _('CONFIG')),
93 ('', 'debug', None, _('enable debugging output')),
93 ('', 'debug', None, _('enable debugging output')),
94 ('', 'debugger', None, _('start debugger')),
94 ('', 'debugger', None, _('start debugger')),
95 ('', 'encoding', encoding.encoding, _('set the charset encoding'),
95 ('', 'encoding', encoding.encoding, _('set the charset encoding'),
96 _('ENCODE')),
96 _('ENCODE')),
97 ('', 'encodingmode', encoding.encodingmode,
97 ('', 'encodingmode', encoding.encodingmode,
98 _('set the charset encoding mode'), _('MODE')),
98 _('set the charset encoding mode'), _('MODE')),
99 ('', 'traceback', None, _('always print a traceback on exception')),
99 ('', 'traceback', None, _('always print a traceback on exception')),
100 ('', 'time', None, _('time how long the command takes')),
100 ('', 'time', None, _('time how long the command takes')),
101 ('', 'profile', None, _('print command execution profile')),
101 ('', 'profile', None, _('print command execution profile')),
102 ('', 'version', None, _('output version information and exit')),
102 ('', 'version', None, _('output version information and exit')),
103 ('h', 'help', None, _('display help and exit')),
103 ('h', 'help', None, _('display help and exit')),
104 ('', 'hidden', False, _('consider hidden changesets')),
104 ('', 'hidden', False, _('consider hidden changesets')),
105 ('', 'pager', 'auto',
105 ('', 'pager', 'auto',
106 _("when to paginate (boolean, always, auto, or never)"), _('TYPE')),
106 _("when to paginate (boolean, always, auto, or never)"), _('TYPE')),
107 ]
107 ]
108
108
109 dryrunopts = cmdutil.dryrunopts
109 dryrunopts = cmdutil.dryrunopts
110 remoteopts = cmdutil.remoteopts
110 remoteopts = cmdutil.remoteopts
111 walkopts = cmdutil.walkopts
111 walkopts = cmdutil.walkopts
112 commitopts = cmdutil.commitopts
112 commitopts = cmdutil.commitopts
113 commitopts2 = cmdutil.commitopts2
113 commitopts2 = cmdutil.commitopts2
114 formatteropts = cmdutil.formatteropts
114 formatteropts = cmdutil.formatteropts
115 templateopts = cmdutil.templateopts
115 templateopts = cmdutil.templateopts
116 logopts = cmdutil.logopts
116 logopts = cmdutil.logopts
117 diffopts = cmdutil.diffopts
117 diffopts = cmdutil.diffopts
118 diffwsopts = cmdutil.diffwsopts
118 diffwsopts = cmdutil.diffwsopts
119 diffopts2 = cmdutil.diffopts2
119 diffopts2 = cmdutil.diffopts2
120 mergetoolopts = cmdutil.mergetoolopts
120 mergetoolopts = cmdutil.mergetoolopts
121 similarityopts = cmdutil.similarityopts
121 similarityopts = cmdutil.similarityopts
122 subrepoopts = cmdutil.subrepoopts
122 subrepoopts = cmdutil.subrepoopts
123 debugrevlogopts = cmdutil.debugrevlogopts
123 debugrevlogopts = cmdutil.debugrevlogopts
124
124
125 # Commands start here, listed alphabetically
125 # Commands start here, listed alphabetically
126
126
127 @command('^add',
127 @command('^add',
128 walkopts + subrepoopts + dryrunopts,
128 walkopts + subrepoopts + dryrunopts,
129 _('[OPTION]... [FILE]...'),
129 _('[OPTION]... [FILE]...'),
130 inferrepo=True)
130 inferrepo=True)
131 def add(ui, repo, *pats, **opts):
131 def add(ui, repo, *pats, **opts):
132 """add the specified files on the next commit
132 """add the specified files on the next commit
133
133
134 Schedule files to be version controlled and added to the
134 Schedule files to be version controlled and added to the
135 repository.
135 repository.
136
136
137 The files will be added to the repository at the next commit. To
137 The files will be added to the repository at the next commit. To
138 undo an add before that, see :hg:`forget`.
138 undo an add before that, see :hg:`forget`.
139
139
140 If no names are given, add all files to the repository (except
140 If no names are given, add all files to the repository (except
141 files matching ``.hgignore``).
141 files matching ``.hgignore``).
142
142
143 .. container:: verbose
143 .. container:: verbose
144
144
145 Examples:
145 Examples:
146
146
147 - New (unknown) files are added
147 - New (unknown) files are added
148 automatically by :hg:`add`::
148 automatically by :hg:`add`::
149
149
150 $ ls
150 $ ls
151 foo.c
151 foo.c
152 $ hg status
152 $ hg status
153 ? foo.c
153 ? foo.c
154 $ hg add
154 $ hg add
155 adding foo.c
155 adding foo.c
156 $ hg status
156 $ hg status
157 A foo.c
157 A foo.c
158
158
159 - Specific files to be added can be specified::
159 - Specific files to be added can be specified::
160
160
161 $ ls
161 $ ls
162 bar.c foo.c
162 bar.c foo.c
163 $ hg status
163 $ hg status
164 ? bar.c
164 ? bar.c
165 ? foo.c
165 ? foo.c
166 $ hg add bar.c
166 $ hg add bar.c
167 $ hg status
167 $ hg status
168 A bar.c
168 A bar.c
169 ? foo.c
169 ? foo.c
170
170
171 Returns 0 if all files are successfully added.
171 Returns 0 if all files are successfully added.
172 """
172 """
173
173
174 m = scmutil.match(repo[None], pats, pycompat.byteskwargs(opts))
174 m = scmutil.match(repo[None], pats, pycompat.byteskwargs(opts))
175 rejected = cmdutil.add(ui, repo, m, "", False, **opts)
175 rejected = cmdutil.add(ui, repo, m, "", False, **opts)
176 return rejected and 1 or 0
176 return rejected and 1 or 0
177
177
178 @command('addremove',
178 @command('addremove',
179 similarityopts + subrepoopts + walkopts + dryrunopts,
179 similarityopts + subrepoopts + walkopts + dryrunopts,
180 _('[OPTION]... [FILE]...'),
180 _('[OPTION]... [FILE]...'),
181 inferrepo=True)
181 inferrepo=True)
182 def addremove(ui, repo, *pats, **opts):
182 def addremove(ui, repo, *pats, **opts):
183 """add all new files, delete all missing files
183 """add all new files, delete all missing files
184
184
185 Add all new files and remove all missing files from the
185 Add all new files and remove all missing files from the
186 repository.
186 repository.
187
187
188 Unless names are given, new files are ignored if they match any of
188 Unless names are given, new files are ignored if they match any of
189 the patterns in ``.hgignore``. As with add, these changes take
189 the patterns in ``.hgignore``. As with add, these changes take
190 effect at the next commit.
190 effect at the next commit.
191
191
192 Use the -s/--similarity option to detect renamed files. This
192 Use the -s/--similarity option to detect renamed files. This
193 option takes a percentage between 0 (disabled) and 100 (files must
193 option takes a percentage between 0 (disabled) and 100 (files must
194 be identical) as its parameter. With a parameter greater than 0,
194 be identical) as its parameter. With a parameter greater than 0,
195 this compares every removed file with every added file and records
195 this compares every removed file with every added file and records
196 those similar enough as renames. Detecting renamed files this way
196 those similar enough as renames. Detecting renamed files this way
197 can be expensive. After using this option, :hg:`status -C` can be
197 can be expensive. After using this option, :hg:`status -C` can be
198 used to check which files were identified as moved or renamed. If
198 used to check which files were identified as moved or renamed. If
199 not specified, -s/--similarity defaults to 100 and only renames of
199 not specified, -s/--similarity defaults to 100 and only renames of
200 identical files are detected.
200 identical files are detected.
201
201
202 .. container:: verbose
202 .. container:: verbose
203
203
204 Examples:
204 Examples:
205
205
206 - A number of files (bar.c and foo.c) are new,
206 - A number of files (bar.c and foo.c) are new,
207 while foobar.c has been removed (without using :hg:`remove`)
207 while foobar.c has been removed (without using :hg:`remove`)
208 from the repository::
208 from the repository::
209
209
210 $ ls
210 $ ls
211 bar.c foo.c
211 bar.c foo.c
212 $ hg status
212 $ hg status
213 ! foobar.c
213 ! foobar.c
214 ? bar.c
214 ? bar.c
215 ? foo.c
215 ? foo.c
216 $ hg addremove
216 $ hg addremove
217 adding bar.c
217 adding bar.c
218 adding foo.c
218 adding foo.c
219 removing foobar.c
219 removing foobar.c
220 $ hg status
220 $ hg status
221 A bar.c
221 A bar.c
222 A foo.c
222 A foo.c
223 R foobar.c
223 R foobar.c
224
224
225 - A file foobar.c was moved to foo.c without using :hg:`rename`.
225 - A file foobar.c was moved to foo.c without using :hg:`rename`.
226 Afterwards, it was edited slightly::
226 Afterwards, it was edited slightly::
227
227
228 $ ls
228 $ ls
229 foo.c
229 foo.c
230 $ hg status
230 $ hg status
231 ! foobar.c
231 ! foobar.c
232 ? foo.c
232 ? foo.c
233 $ hg addremove --similarity 90
233 $ hg addremove --similarity 90
234 removing foobar.c
234 removing foobar.c
235 adding foo.c
235 adding foo.c
236 recording removal of foobar.c as rename to foo.c (94% similar)
236 recording removal of foobar.c as rename to foo.c (94% similar)
237 $ hg status -C
237 $ hg status -C
238 A foo.c
238 A foo.c
239 foobar.c
239 foobar.c
240 R foobar.c
240 R foobar.c
241
241
242 Returns 0 if all files are successfully added.
242 Returns 0 if all files are successfully added.
243 """
243 """
244 opts = pycompat.byteskwargs(opts)
244 opts = pycompat.byteskwargs(opts)
245 try:
245 try:
246 sim = float(opts.get('similarity') or 100)
246 sim = float(opts.get('similarity') or 100)
247 except ValueError:
247 except ValueError:
248 raise error.Abort(_('similarity must be a number'))
248 raise error.Abort(_('similarity must be a number'))
249 if sim < 0 or sim > 100:
249 if sim < 0 or sim > 100:
250 raise error.Abort(_('similarity must be between 0 and 100'))
250 raise error.Abort(_('similarity must be between 0 and 100'))
251 matcher = scmutil.match(repo[None], pats, opts)
251 matcher = scmutil.match(repo[None], pats, opts)
252 return scmutil.addremove(repo, matcher, "", opts, similarity=sim / 100.0)
252 return scmutil.addremove(repo, matcher, "", opts, similarity=sim / 100.0)
253
253
254 @command('^annotate|blame',
254 @command('^annotate|blame',
255 [('r', 'rev', '', _('annotate the specified revision'), _('REV')),
255 [('r', 'rev', '', _('annotate the specified revision'), _('REV')),
256 ('', 'follow', None,
256 ('', 'follow', None,
257 _('follow copies/renames and list the filename (DEPRECATED)')),
257 _('follow copies/renames and list the filename (DEPRECATED)')),
258 ('', 'no-follow', None, _("don't follow copies and renames")),
258 ('', 'no-follow', None, _("don't follow copies and renames")),
259 ('a', 'text', None, _('treat all files as text')),
259 ('a', 'text', None, _('treat all files as text')),
260 ('u', 'user', None, _('list the author (long with -v)')),
260 ('u', 'user', None, _('list the author (long with -v)')),
261 ('f', 'file', None, _('list the filename')),
261 ('f', 'file', None, _('list the filename')),
262 ('d', 'date', None, _('list the date (short with -q)')),
262 ('d', 'date', None, _('list the date (short with -q)')),
263 ('n', 'number', None, _('list the revision number (default)')),
263 ('n', 'number', None, _('list the revision number (default)')),
264 ('c', 'changeset', None, _('list the changeset')),
264 ('c', 'changeset', None, _('list the changeset')),
265 ('l', 'line-number', None, _('show line number at the first appearance')),
265 ('l', 'line-number', None, _('show line number at the first appearance')),
266 ('', 'skip', [], _('revision to not display (EXPERIMENTAL)'), _('REV')),
266 ('', 'skip', [], _('revision to not display (EXPERIMENTAL)'), _('REV')),
267 ] + diffwsopts + walkopts + formatteropts,
267 ] + diffwsopts + walkopts + formatteropts,
268 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'),
268 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'),
269 inferrepo=True)
269 inferrepo=True)
270 def annotate(ui, repo, *pats, **opts):
270 def annotate(ui, repo, *pats, **opts):
271 """show changeset information by line for each file
271 """show changeset information by line for each file
272
272
273 List changes in files, showing the revision id responsible for
273 List changes in files, showing the revision id responsible for
274 each line.
274 each line.
275
275
276 This command is useful for discovering when a change was made and
276 This command is useful for discovering when a change was made and
277 by whom.
277 by whom.
278
278
279 If you include --file, --user, or --date, the revision number is
279 If you include --file, --user, or --date, the revision number is
280 suppressed unless you also include --number.
280 suppressed unless you also include --number.
281
281
282 Without the -a/--text option, annotate will avoid processing files
282 Without the -a/--text option, annotate will avoid processing files
283 it detects as binary. With -a, annotate will annotate the file
283 it detects as binary. With -a, annotate will annotate the file
284 anyway, although the results will probably be neither useful
284 anyway, although the results will probably be neither useful
285 nor desirable.
285 nor desirable.
286
286
287 Returns 0 on success.
287 Returns 0 on success.
288 """
288 """
289 opts = pycompat.byteskwargs(opts)
289 opts = pycompat.byteskwargs(opts)
290 if not pats:
290 if not pats:
291 raise error.Abort(_('at least one filename or pattern is required'))
291 raise error.Abort(_('at least one filename or pattern is required'))
292
292
293 if opts.get('follow'):
293 if opts.get('follow'):
294 # --follow is deprecated and now just an alias for -f/--file
294 # --follow is deprecated and now just an alias for -f/--file
295 # to mimic the behavior of Mercurial before version 1.5
295 # to mimic the behavior of Mercurial before version 1.5
296 opts['file'] = True
296 opts['file'] = True
297
297
298 ctx = scmutil.revsingle(repo, opts.get('rev'))
298 ctx = scmutil.revsingle(repo, opts.get('rev'))
299
299
300 fm = ui.formatter('annotate', opts)
300 fm = ui.formatter('annotate', opts)
301 if ui.quiet:
301 if ui.quiet:
302 datefunc = util.shortdate
302 datefunc = util.shortdate
303 else:
303 else:
304 datefunc = util.datestr
304 datefunc = util.datestr
305 if ctx.rev() is None:
305 if ctx.rev() is None:
306 def hexfn(node):
306 def hexfn(node):
307 if node is None:
307 if node is None:
308 return None
308 return None
309 else:
309 else:
310 return fm.hexfunc(node)
310 return fm.hexfunc(node)
311 if opts.get('changeset'):
311 if opts.get('changeset'):
312 # omit "+" suffix which is appended to node hex
312 # omit "+" suffix which is appended to node hex
313 def formatrev(rev):
313 def formatrev(rev):
314 if rev is None:
314 if rev is None:
315 return '%d' % ctx.p1().rev()
315 return '%d' % ctx.p1().rev()
316 else:
316 else:
317 return '%d' % rev
317 return '%d' % rev
318 else:
318 else:
319 def formatrev(rev):
319 def formatrev(rev):
320 if rev is None:
320 if rev is None:
321 return '%d+' % ctx.p1().rev()
321 return '%d+' % ctx.p1().rev()
322 else:
322 else:
323 return '%d ' % rev
323 return '%d ' % rev
324 def formathex(hex):
324 def formathex(hex):
325 if hex is None:
325 if hex is None:
326 return '%s+' % fm.hexfunc(ctx.p1().node())
326 return '%s+' % fm.hexfunc(ctx.p1().node())
327 else:
327 else:
328 return '%s ' % hex
328 return '%s ' % hex
329 else:
329 else:
330 hexfn = fm.hexfunc
330 hexfn = fm.hexfunc
331 formatrev = formathex = str
331 formatrev = formathex = str
332
332
333 opmap = [('user', ' ', lambda x: x[0].user(), ui.shortuser),
333 opmap = [('user', ' ', lambda x: x[0].user(), ui.shortuser),
334 ('number', ' ', lambda x: x[0].rev(), formatrev),
334 ('number', ' ', lambda x: x[0].rev(), formatrev),
335 ('changeset', ' ', lambda x: hexfn(x[0].node()), formathex),
335 ('changeset', ' ', lambda x: hexfn(x[0].node()), formathex),
336 ('date', ' ', lambda x: x[0].date(), util.cachefunc(datefunc)),
336 ('date', ' ', lambda x: x[0].date(), util.cachefunc(datefunc)),
337 ('file', ' ', lambda x: x[0].path(), str),
337 ('file', ' ', lambda x: x[0].path(), str),
338 ('line_number', ':', lambda x: x[1], str),
338 ('line_number', ':', lambda x: x[1], str),
339 ]
339 ]
340 fieldnamemap = {'number': 'rev', 'changeset': 'node'}
340 fieldnamemap = {'number': 'rev', 'changeset': 'node'}
341
341
342 if (not opts.get('user') and not opts.get('changeset')
342 if (not opts.get('user') and not opts.get('changeset')
343 and not opts.get('date') and not opts.get('file')):
343 and not opts.get('date') and not opts.get('file')):
344 opts['number'] = True
344 opts['number'] = True
345
345
346 linenumber = opts.get('line_number') is not None
346 linenumber = opts.get('line_number') is not None
347 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
347 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
348 raise error.Abort(_('at least one of -n/-c is required for -l'))
348 raise error.Abort(_('at least one of -n/-c is required for -l'))
349
349
350 ui.pager('annotate')
350 ui.pager('annotate')
351
351
352 if fm.isplain():
352 if fm.isplain():
353 def makefunc(get, fmt):
353 def makefunc(get, fmt):
354 return lambda x: fmt(get(x))
354 return lambda x: fmt(get(x))
355 else:
355 else:
356 def makefunc(get, fmt):
356 def makefunc(get, fmt):
357 return get
357 return get
358 funcmap = [(makefunc(get, fmt), sep) for op, sep, get, fmt in opmap
358 funcmap = [(makefunc(get, fmt), sep) for op, sep, get, fmt in opmap
359 if opts.get(op)]
359 if opts.get(op)]
360 funcmap[0] = (funcmap[0][0], '') # no separator in front of first column
360 funcmap[0] = (funcmap[0][0], '') # no separator in front of first column
361 fields = ' '.join(fieldnamemap.get(op, op) for op, sep, get, fmt in opmap
361 fields = ' '.join(fieldnamemap.get(op, op) for op, sep, get, fmt in opmap
362 if opts.get(op))
362 if opts.get(op))
363
363
364 def bad(x, y):
364 def bad(x, y):
365 raise error.Abort("%s: %s" % (x, y))
365 raise error.Abort("%s: %s" % (x, y))
366
366
367 m = scmutil.match(ctx, pats, opts, badfn=bad)
367 m = scmutil.match(ctx, pats, opts, badfn=bad)
368
368
369 follow = not opts.get('no_follow')
369 follow = not opts.get('no_follow')
370 diffopts = patch.difffeatureopts(ui, opts, section='annotate',
370 diffopts = patch.difffeatureopts(ui, opts, section='annotate',
371 whitespace=True)
371 whitespace=True)
372 skiprevs = opts.get('skip')
372 skiprevs = opts.get('skip')
373 if skiprevs:
373 if skiprevs:
374 skiprevs = scmutil.revrange(repo, skiprevs)
374 skiprevs = scmutil.revrange(repo, skiprevs)
375
375
376 for abs in ctx.walk(m):
376 for abs in ctx.walk(m):
377 fctx = ctx[abs]
377 fctx = ctx[abs]
378 if not opts.get('text') and fctx.isbinary():
378 if not opts.get('text') and fctx.isbinary():
379 fm.plain(_("%s: binary file\n") % ((pats and m.rel(abs)) or abs))
379 fm.plain(_("%s: binary file\n") % ((pats and m.rel(abs)) or abs))
380 continue
380 continue
381
381
382 lines = fctx.annotate(follow=follow, linenumber=linenumber,
382 lines = fctx.annotate(follow=follow, linenumber=linenumber,
383 skiprevs=skiprevs, diffopts=diffopts)
383 skiprevs=skiprevs, diffopts=diffopts)
384 if not lines:
384 if not lines:
385 continue
385 continue
386 formats = []
386 formats = []
387 pieces = []
387 pieces = []
388
388
389 for f, sep in funcmap:
389 for f, sep in funcmap:
390 l = [f(n) for n, dummy in lines]
390 l = [f(n) for n, dummy in lines]
391 if fm.isplain():
391 if fm.isplain():
392 sizes = [encoding.colwidth(x) for x in l]
392 sizes = [encoding.colwidth(x) for x in l]
393 ml = max(sizes)
393 ml = max(sizes)
394 formats.append([sep + ' ' * (ml - w) + '%s' for w in sizes])
394 formats.append([sep + ' ' * (ml - w) + '%s' for w in sizes])
395 else:
395 else:
396 formats.append(['%s' for x in l])
396 formats.append(['%s' for x in l])
397 pieces.append(l)
397 pieces.append(l)
398
398
399 for f, p, l in zip(zip(*formats), zip(*pieces), lines):
399 for f, p, l in zip(zip(*formats), zip(*pieces), lines):
400 fm.startitem()
400 fm.startitem()
401 fm.write(fields, "".join(f), *p)
401 fm.write(fields, "".join(f), *p)
402 fm.write('line', ": %s", l[1])
402 fm.write('line', ": %s", l[1])
403
403
404 if not lines[-1][1].endswith('\n'):
404 if not lines[-1][1].endswith('\n'):
405 fm.plain('\n')
405 fm.plain('\n')
406
406
407 fm.end()
407 fm.end()
408
408
409 @command('archive',
409 @command('archive',
410 [('', 'no-decode', None, _('do not pass files through decoders')),
410 [('', 'no-decode', None, _('do not pass files through decoders')),
411 ('p', 'prefix', '', _('directory prefix for files in archive'),
411 ('p', 'prefix', '', _('directory prefix for files in archive'),
412 _('PREFIX')),
412 _('PREFIX')),
413 ('r', 'rev', '', _('revision to distribute'), _('REV')),
413 ('r', 'rev', '', _('revision to distribute'), _('REV')),
414 ('t', 'type', '', _('type of distribution to create'), _('TYPE')),
414 ('t', 'type', '', _('type of distribution to create'), _('TYPE')),
415 ] + subrepoopts + walkopts,
415 ] + subrepoopts + walkopts,
416 _('[OPTION]... DEST'))
416 _('[OPTION]... DEST'))
417 def archive(ui, repo, dest, **opts):
417 def archive(ui, repo, dest, **opts):
418 '''create an unversioned archive of a repository revision
418 '''create an unversioned archive of a repository revision
419
419
420 By default, the revision used is the parent of the working
420 By default, the revision used is the parent of the working
421 directory; use -r/--rev to specify a different revision.
421 directory; use -r/--rev to specify a different revision.
422
422
423 The archive type is automatically detected based on file
423 The archive type is automatically detected based on file
424 extension (to override, use -t/--type).
424 extension (to override, use -t/--type).
425
425
426 .. container:: verbose
426 .. container:: verbose
427
427
428 Examples:
428 Examples:
429
429
430 - create a zip file containing the 1.0 release::
430 - create a zip file containing the 1.0 release::
431
431
432 hg archive -r 1.0 project-1.0.zip
432 hg archive -r 1.0 project-1.0.zip
433
433
434 - create a tarball excluding .hg files::
434 - create a tarball excluding .hg files::
435
435
436 hg archive project.tar.gz -X ".hg*"
436 hg archive project.tar.gz -X ".hg*"
437
437
438 Valid types are:
438 Valid types are:
439
439
440 :``files``: a directory full of files (default)
440 :``files``: a directory full of files (default)
441 :``tar``: tar archive, uncompressed
441 :``tar``: tar archive, uncompressed
442 :``tbz2``: tar archive, compressed using bzip2
442 :``tbz2``: tar archive, compressed using bzip2
443 :``tgz``: tar archive, compressed using gzip
443 :``tgz``: tar archive, compressed using gzip
444 :``uzip``: zip archive, uncompressed
444 :``uzip``: zip archive, uncompressed
445 :``zip``: zip archive, compressed using deflate
445 :``zip``: zip archive, compressed using deflate
446
446
447 The exact name of the destination archive or directory is given
447 The exact name of the destination archive or directory is given
448 using a format string; see :hg:`help export` for details.
448 using a format string; see :hg:`help export` for details.
449
449
450 Each member added to an archive file has a directory prefix
450 Each member added to an archive file has a directory prefix
451 prepended. Use -p/--prefix to specify a format string for the
451 prepended. Use -p/--prefix to specify a format string for the
452 prefix. The default is the basename of the archive, with suffixes
452 prefix. The default is the basename of the archive, with suffixes
453 removed.
453 removed.
454
454
455 Returns 0 on success.
455 Returns 0 on success.
456 '''
456 '''
457
457
458 opts = pycompat.byteskwargs(opts)
458 opts = pycompat.byteskwargs(opts)
459 ctx = scmutil.revsingle(repo, opts.get('rev'))
459 ctx = scmutil.revsingle(repo, opts.get('rev'))
460 if not ctx:
460 if not ctx:
461 raise error.Abort(_('no working directory: please specify a revision'))
461 raise error.Abort(_('no working directory: please specify a revision'))
462 node = ctx.node()
462 node = ctx.node()
463 dest = cmdutil.makefilename(repo, dest, node)
463 dest = cmdutil.makefilename(repo, dest, node)
464 if os.path.realpath(dest) == repo.root:
464 if os.path.realpath(dest) == repo.root:
465 raise error.Abort(_('repository root cannot be destination'))
465 raise error.Abort(_('repository root cannot be destination'))
466
466
467 kind = opts.get('type') or archival.guesskind(dest) or 'files'
467 kind = opts.get('type') or archival.guesskind(dest) or 'files'
468 prefix = opts.get('prefix')
468 prefix = opts.get('prefix')
469
469
470 if dest == '-':
470 if dest == '-':
471 if kind == 'files':
471 if kind == 'files':
472 raise error.Abort(_('cannot archive plain files to stdout'))
472 raise error.Abort(_('cannot archive plain files to stdout'))
473 dest = cmdutil.makefileobj(repo, dest)
473 dest = cmdutil.makefileobj(repo, dest)
474 if not prefix:
474 if not prefix:
475 prefix = os.path.basename(repo.root) + '-%h'
475 prefix = os.path.basename(repo.root) + '-%h'
476
476
477 prefix = cmdutil.makefilename(repo, prefix, node)
477 prefix = cmdutil.makefilename(repo, prefix, node)
478 matchfn = scmutil.match(ctx, [], opts)
478 matchfn = scmutil.match(ctx, [], opts)
479 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
479 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
480 matchfn, prefix, subrepos=opts.get('subrepos'))
480 matchfn, prefix, subrepos=opts.get('subrepos'))
481
481
482 @command('backout',
482 @command('backout',
483 [('', 'merge', None, _('merge with old dirstate parent after backout')),
483 [('', 'merge', None, _('merge with old dirstate parent after backout')),
484 ('', 'commit', None,
484 ('', 'commit', None,
485 _('commit if no conflicts were encountered (DEPRECATED)')),
485 _('commit if no conflicts were encountered (DEPRECATED)')),
486 ('', 'no-commit', None, _('do not commit')),
486 ('', 'no-commit', None, _('do not commit')),
487 ('', 'parent', '',
487 ('', 'parent', '',
488 _('parent to choose when backing out merge (DEPRECATED)'), _('REV')),
488 _('parent to choose when backing out merge (DEPRECATED)'), _('REV')),
489 ('r', 'rev', '', _('revision to backout'), _('REV')),
489 ('r', 'rev', '', _('revision to backout'), _('REV')),
490 ('e', 'edit', False, _('invoke editor on commit messages')),
490 ('e', 'edit', False, _('invoke editor on commit messages')),
491 ] + mergetoolopts + walkopts + commitopts + commitopts2,
491 ] + mergetoolopts + walkopts + commitopts + commitopts2,
492 _('[OPTION]... [-r] REV'))
492 _('[OPTION]... [-r] REV'))
493 def backout(ui, repo, node=None, rev=None, **opts):
493 def backout(ui, repo, node=None, rev=None, **opts):
494 '''reverse effect of earlier changeset
494 '''reverse effect of earlier changeset
495
495
496 Prepare a new changeset with the effect of REV undone in the
496 Prepare a new changeset with the effect of REV undone in the
497 current working directory. If no conflicts were encountered,
497 current working directory. If no conflicts were encountered,
498 it will be committed immediately.
498 it will be committed immediately.
499
499
500 If REV is the parent of the working directory, then this new changeset
500 If REV is the parent of the working directory, then this new changeset
501 is committed automatically (unless --no-commit is specified).
501 is committed automatically (unless --no-commit is specified).
502
502
503 .. note::
503 .. note::
504
504
505 :hg:`backout` cannot be used to fix either an unwanted or
505 :hg:`backout` cannot be used to fix either an unwanted or
506 incorrect merge.
506 incorrect merge.
507
507
508 .. container:: verbose
508 .. container:: verbose
509
509
510 Examples:
510 Examples:
511
511
512 - Reverse the effect of the parent of the working directory.
512 - Reverse the effect of the parent of the working directory.
513 This backout will be committed immediately::
513 This backout will be committed immediately::
514
514
515 hg backout -r .
515 hg backout -r .
516
516
517 - Reverse the effect of previous bad revision 23::
517 - Reverse the effect of previous bad revision 23::
518
518
519 hg backout -r 23
519 hg backout -r 23
520
520
521 - Reverse the effect of previous bad revision 23 and
521 - Reverse the effect of previous bad revision 23 and
522 leave changes uncommitted::
522 leave changes uncommitted::
523
523
524 hg backout -r 23 --no-commit
524 hg backout -r 23 --no-commit
525 hg commit -m "Backout revision 23"
525 hg commit -m "Backout revision 23"
526
526
527 By default, the pending changeset will have one parent,
527 By default, the pending changeset will have one parent,
528 maintaining a linear history. With --merge, the pending
528 maintaining a linear history. With --merge, the pending
529 changeset will instead have two parents: the old parent of the
529 changeset will instead have two parents: the old parent of the
530 working directory and a new child of REV that simply undoes REV.
530 working directory and a new child of REV that simply undoes REV.
531
531
532 Before version 1.7, the behavior without --merge was equivalent
532 Before version 1.7, the behavior without --merge was equivalent
533 to specifying --merge followed by :hg:`update --clean .` to
533 to specifying --merge followed by :hg:`update --clean .` to
534 cancel the merge and leave the child of REV as a head to be
534 cancel the merge and leave the child of REV as a head to be
535 merged separately.
535 merged separately.
536
536
537 See :hg:`help dates` for a list of formats valid for -d/--date.
537 See :hg:`help dates` for a list of formats valid for -d/--date.
538
538
539 See :hg:`help revert` for a way to restore files to the state
539 See :hg:`help revert` for a way to restore files to the state
540 of another revision.
540 of another revision.
541
541
542 Returns 0 on success, 1 if nothing to backout or there are unresolved
542 Returns 0 on success, 1 if nothing to backout or there are unresolved
543 files.
543 files.
544 '''
544 '''
545 wlock = lock = None
545 wlock = lock = None
546 try:
546 try:
547 wlock = repo.wlock()
547 wlock = repo.wlock()
548 lock = repo.lock()
548 lock = repo.lock()
549 return _dobackout(ui, repo, node, rev, **opts)
549 return _dobackout(ui, repo, node, rev, **opts)
550 finally:
550 finally:
551 release(lock, wlock)
551 release(lock, wlock)
552
552
553 def _dobackout(ui, repo, node=None, rev=None, **opts):
553 def _dobackout(ui, repo, node=None, rev=None, **opts):
554 opts = pycompat.byteskwargs(opts)
554 opts = pycompat.byteskwargs(opts)
555 if opts.get('commit') and opts.get('no_commit'):
555 if opts.get('commit') and opts.get('no_commit'):
556 raise error.Abort(_("cannot use --commit with --no-commit"))
556 raise error.Abort(_("cannot use --commit with --no-commit"))
557 if opts.get('merge') and opts.get('no_commit'):
557 if opts.get('merge') and opts.get('no_commit'):
558 raise error.Abort(_("cannot use --merge with --no-commit"))
558 raise error.Abort(_("cannot use --merge with --no-commit"))
559
559
560 if rev and node:
560 if rev and node:
561 raise error.Abort(_("please specify just one revision"))
561 raise error.Abort(_("please specify just one revision"))
562
562
563 if not rev:
563 if not rev:
564 rev = node
564 rev = node
565
565
566 if not rev:
566 if not rev:
567 raise error.Abort(_("please specify a revision to backout"))
567 raise error.Abort(_("please specify a revision to backout"))
568
568
569 date = opts.get('date')
569 date = opts.get('date')
570 if date:
570 if date:
571 opts['date'] = util.parsedate(date)
571 opts['date'] = util.parsedate(date)
572
572
573 cmdutil.checkunfinished(repo)
573 cmdutil.checkunfinished(repo)
574 cmdutil.bailifchanged(repo)
574 cmdutil.bailifchanged(repo)
575 node = scmutil.revsingle(repo, rev).node()
575 node = scmutil.revsingle(repo, rev).node()
576
576
577 op1, op2 = repo.dirstate.parents()
577 op1, op2 = repo.dirstate.parents()
578 if not repo.changelog.isancestor(node, op1):
578 if not repo.changelog.isancestor(node, op1):
579 raise error.Abort(_('cannot backout change that is not an ancestor'))
579 raise error.Abort(_('cannot backout change that is not an ancestor'))
580
580
581 p1, p2 = repo.changelog.parents(node)
581 p1, p2 = repo.changelog.parents(node)
582 if p1 == nullid:
582 if p1 == nullid:
583 raise error.Abort(_('cannot backout a change with no parents'))
583 raise error.Abort(_('cannot backout a change with no parents'))
584 if p2 != nullid:
584 if p2 != nullid:
585 if not opts.get('parent'):
585 if not opts.get('parent'):
586 raise error.Abort(_('cannot backout a merge changeset'))
586 raise error.Abort(_('cannot backout a merge changeset'))
587 p = repo.lookup(opts['parent'])
587 p = repo.lookup(opts['parent'])
588 if p not in (p1, p2):
588 if p not in (p1, p2):
589 raise error.Abort(_('%s is not a parent of %s') %
589 raise error.Abort(_('%s is not a parent of %s') %
590 (short(p), short(node)))
590 (short(p), short(node)))
591 parent = p
591 parent = p
592 else:
592 else:
593 if opts.get('parent'):
593 if opts.get('parent'):
594 raise error.Abort(_('cannot use --parent on non-merge changeset'))
594 raise error.Abort(_('cannot use --parent on non-merge changeset'))
595 parent = p1
595 parent = p1
596
596
597 # the backout should appear on the same branch
597 # the backout should appear on the same branch
598 branch = repo.dirstate.branch()
598 branch = repo.dirstate.branch()
599 bheads = repo.branchheads(branch)
599 bheads = repo.branchheads(branch)
600 rctx = scmutil.revsingle(repo, hex(parent))
600 rctx = scmutil.revsingle(repo, hex(parent))
601 if not opts.get('merge') and op1 != node:
601 if not opts.get('merge') and op1 != node:
602 dsguard = dirstateguard.dirstateguard(repo, 'backout')
602 dsguard = dirstateguard.dirstateguard(repo, 'backout')
603 try:
603 try:
604 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
604 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
605 'backout')
605 'backout')
606 stats = mergemod.update(repo, parent, True, True, node, False)
606 stats = mergemod.update(repo, parent, True, True, node, False)
607 repo.setparents(op1, op2)
607 repo.setparents(op1, op2)
608 dsguard.close()
608 dsguard.close()
609 hg._showstats(repo, stats)
609 hg._showstats(repo, stats)
610 if stats[3]:
610 if stats[3]:
611 repo.ui.status(_("use 'hg resolve' to retry unresolved "
611 repo.ui.status(_("use 'hg resolve' to retry unresolved "
612 "file merges\n"))
612 "file merges\n"))
613 return 1
613 return 1
614 finally:
614 finally:
615 ui.setconfig('ui', 'forcemerge', '', '')
615 ui.setconfig('ui', 'forcemerge', '', '')
616 lockmod.release(dsguard)
616 lockmod.release(dsguard)
617 else:
617 else:
618 hg.clean(repo, node, show_stats=False)
618 hg.clean(repo, node, show_stats=False)
619 repo.dirstate.setbranch(branch)
619 repo.dirstate.setbranch(branch)
620 cmdutil.revert(ui, repo, rctx, repo.dirstate.parents())
620 cmdutil.revert(ui, repo, rctx, repo.dirstate.parents())
621
621
622 if opts.get('no_commit'):
622 if opts.get('no_commit'):
623 msg = _("changeset %s backed out, "
623 msg = _("changeset %s backed out, "
624 "don't forget to commit.\n")
624 "don't forget to commit.\n")
625 ui.status(msg % short(node))
625 ui.status(msg % short(node))
626 return 0
626 return 0
627
627
628 def commitfunc(ui, repo, message, match, opts):
628 def commitfunc(ui, repo, message, match, opts):
629 editform = 'backout'
629 editform = 'backout'
630 e = cmdutil.getcommiteditor(editform=editform,
630 e = cmdutil.getcommiteditor(editform=editform,
631 **pycompat.strkwargs(opts))
631 **pycompat.strkwargs(opts))
632 if not message:
632 if not message:
633 # we don't translate commit messages
633 # we don't translate commit messages
634 message = "Backed out changeset %s" % short(node)
634 message = "Backed out changeset %s" % short(node)
635 e = cmdutil.getcommiteditor(edit=True, editform=editform)
635 e = cmdutil.getcommiteditor(edit=True, editform=editform)
636 return repo.commit(message, opts.get('user'), opts.get('date'),
636 return repo.commit(message, opts.get('user'), opts.get('date'),
637 match, editor=e)
637 match, editor=e)
638 newnode = cmdutil.commit(ui, repo, commitfunc, [], opts)
638 newnode = cmdutil.commit(ui, repo, commitfunc, [], opts)
639 if not newnode:
639 if not newnode:
640 ui.status(_("nothing changed\n"))
640 ui.status(_("nothing changed\n"))
641 return 1
641 return 1
642 cmdutil.commitstatus(repo, newnode, branch, bheads)
642 cmdutil.commitstatus(repo, newnode, branch, bheads)
643
643
644 def nice(node):
644 def nice(node):
645 return '%d:%s' % (repo.changelog.rev(node), short(node))
645 return '%d:%s' % (repo.changelog.rev(node), short(node))
646 ui.status(_('changeset %s backs out changeset %s\n') %
646 ui.status(_('changeset %s backs out changeset %s\n') %
647 (nice(repo.changelog.tip()), nice(node)))
647 (nice(repo.changelog.tip()), nice(node)))
648 if opts.get('merge') and op1 != node:
648 if opts.get('merge') and op1 != node:
649 hg.clean(repo, op1, show_stats=False)
649 hg.clean(repo, op1, show_stats=False)
650 ui.status(_('merging with changeset %s\n')
650 ui.status(_('merging with changeset %s\n')
651 % nice(repo.changelog.tip()))
651 % nice(repo.changelog.tip()))
652 try:
652 try:
653 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
653 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
654 'backout')
654 'backout')
655 return hg.merge(repo, hex(repo.changelog.tip()))
655 return hg.merge(repo, hex(repo.changelog.tip()))
656 finally:
656 finally:
657 ui.setconfig('ui', 'forcemerge', '', '')
657 ui.setconfig('ui', 'forcemerge', '', '')
658 return 0
658 return 0
659
659
660 @command('bisect',
660 @command('bisect',
661 [('r', 'reset', False, _('reset bisect state')),
661 [('r', 'reset', False, _('reset bisect state')),
662 ('g', 'good', False, _('mark changeset good')),
662 ('g', 'good', False, _('mark changeset good')),
663 ('b', 'bad', False, _('mark changeset bad')),
663 ('b', 'bad', False, _('mark changeset bad')),
664 ('s', 'skip', False, _('skip testing changeset')),
664 ('s', 'skip', False, _('skip testing changeset')),
665 ('e', 'extend', False, _('extend the bisect range')),
665 ('e', 'extend', False, _('extend the bisect range')),
666 ('c', 'command', '', _('use command to check changeset state'), _('CMD')),
666 ('c', 'command', '', _('use command to check changeset state'), _('CMD')),
667 ('U', 'noupdate', False, _('do not update to target'))],
667 ('U', 'noupdate', False, _('do not update to target'))],
668 _("[-gbsr] [-U] [-c CMD] [REV]"))
668 _("[-gbsr] [-U] [-c CMD] [REV]"))
669 def bisect(ui, repo, rev=None, extra=None, command=None,
669 def bisect(ui, repo, rev=None, extra=None, command=None,
670 reset=None, good=None, bad=None, skip=None, extend=None,
670 reset=None, good=None, bad=None, skip=None, extend=None,
671 noupdate=None):
671 noupdate=None):
672 """subdivision search of changesets
672 """subdivision search of changesets
673
673
674 This command helps to find changesets which introduce problems. To
674 This command helps to find changesets which introduce problems. To
675 use, mark the earliest changeset you know exhibits the problem as
675 use, mark the earliest changeset you know exhibits the problem as
676 bad, then mark the latest changeset which is free from the problem
676 bad, then mark the latest changeset which is free from the problem
677 as good. Bisect will update your working directory to a revision
677 as good. Bisect will update your working directory to a revision
678 for testing (unless the -U/--noupdate option is specified). Once
678 for testing (unless the -U/--noupdate option is specified). Once
679 you have performed tests, mark the working directory as good or
679 you have performed tests, mark the working directory as good or
680 bad, and bisect will either update to another candidate changeset
680 bad, and bisect will either update to another candidate changeset
681 or announce that it has found the bad revision.
681 or announce that it has found the bad revision.
682
682
683 As a shortcut, you can also use the revision argument to mark a
683 As a shortcut, you can also use the revision argument to mark a
684 revision as good or bad without checking it out first.
684 revision as good or bad without checking it out first.
685
685
686 If you supply a command, it will be used for automatic bisection.
686 If you supply a command, it will be used for automatic bisection.
687 The environment variable HG_NODE will contain the ID of the
687 The environment variable HG_NODE will contain the ID of the
688 changeset being tested. The exit status of the command will be
688 changeset being tested. The exit status of the command will be
689 used to mark revisions as good or bad: status 0 means good, 125
689 used to mark revisions as good or bad: status 0 means good, 125
690 means to skip the revision, 127 (command not found) will abort the
690 means to skip the revision, 127 (command not found) will abort the
691 bisection, and any other non-zero exit status means the revision
691 bisection, and any other non-zero exit status means the revision
692 is bad.
692 is bad.
693
693
694 .. container:: verbose
694 .. container:: verbose
695
695
696 Some examples:
696 Some examples:
697
697
698 - start a bisection with known bad revision 34, and good revision 12::
698 - start a bisection with known bad revision 34, and good revision 12::
699
699
700 hg bisect --bad 34
700 hg bisect --bad 34
701 hg bisect --good 12
701 hg bisect --good 12
702
702
703 - advance the current bisection by marking current revision as good or
703 - advance the current bisection by marking current revision as good or
704 bad::
704 bad::
705
705
706 hg bisect --good
706 hg bisect --good
707 hg bisect --bad
707 hg bisect --bad
708
708
709 - mark the current revision, or a known revision, to be skipped (e.g. if
709 - mark the current revision, or a known revision, to be skipped (e.g. if
710 that revision is not usable because of another issue)::
710 that revision is not usable because of another issue)::
711
711
712 hg bisect --skip
712 hg bisect --skip
713 hg bisect --skip 23
713 hg bisect --skip 23
714
714
715 - skip all revisions that do not touch directories ``foo`` or ``bar``::
715 - skip all revisions that do not touch directories ``foo`` or ``bar``::
716
716
717 hg bisect --skip "!( file('path:foo') & file('path:bar') )"
717 hg bisect --skip "!( file('path:foo') & file('path:bar') )"
718
718
719 - forget the current bisection::
719 - forget the current bisection::
720
720
721 hg bisect --reset
721 hg bisect --reset
722
722
723 - use 'make && make tests' to automatically find the first broken
723 - use 'make && make tests' to automatically find the first broken
724 revision::
724 revision::
725
725
726 hg bisect --reset
726 hg bisect --reset
727 hg bisect --bad 34
727 hg bisect --bad 34
728 hg bisect --good 12
728 hg bisect --good 12
729 hg bisect --command "make && make tests"
729 hg bisect --command "make && make tests"
730
730
731 - see all changesets whose states are already known in the current
731 - see all changesets whose states are already known in the current
732 bisection::
732 bisection::
733
733
734 hg log -r "bisect(pruned)"
734 hg log -r "bisect(pruned)"
735
735
736 - see the changeset currently being bisected (especially useful
736 - see the changeset currently being bisected (especially useful
737 if running with -U/--noupdate)::
737 if running with -U/--noupdate)::
738
738
739 hg log -r "bisect(current)"
739 hg log -r "bisect(current)"
740
740
741 - see all changesets that took part in the current bisection::
741 - see all changesets that took part in the current bisection::
742
742
743 hg log -r "bisect(range)"
743 hg log -r "bisect(range)"
744
744
745 - you can even get a nice graph::
745 - you can even get a nice graph::
746
746
747 hg log --graph -r "bisect(range)"
747 hg log --graph -r "bisect(range)"
748
748
749 See :hg:`help revisions.bisect` for more about the `bisect()` predicate.
749 See :hg:`help revisions.bisect` for more about the `bisect()` predicate.
750
750
751 Returns 0 on success.
751 Returns 0 on success.
752 """
752 """
753 # backward compatibility
753 # backward compatibility
754 if rev in "good bad reset init".split():
754 if rev in "good bad reset init".split():
755 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
755 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
756 cmd, rev, extra = rev, extra, None
756 cmd, rev, extra = rev, extra, None
757 if cmd == "good":
757 if cmd == "good":
758 good = True
758 good = True
759 elif cmd == "bad":
759 elif cmd == "bad":
760 bad = True
760 bad = True
761 else:
761 else:
762 reset = True
762 reset = True
763 elif extra or good + bad + skip + reset + extend + bool(command) > 1:
763 elif extra or good + bad + skip + reset + extend + bool(command) > 1:
764 raise error.Abort(_('incompatible arguments'))
764 raise error.Abort(_('incompatible arguments'))
765
765
766 if reset:
766 if reset:
767 hbisect.resetstate(repo)
767 hbisect.resetstate(repo)
768 return
768 return
769
769
770 state = hbisect.load_state(repo)
770 state = hbisect.load_state(repo)
771
771
772 # update state
772 # update state
773 if good or bad or skip:
773 if good or bad or skip:
774 if rev:
774 if rev:
775 nodes = [repo.lookup(i) for i in scmutil.revrange(repo, [rev])]
775 nodes = [repo.lookup(i) for i in scmutil.revrange(repo, [rev])]
776 else:
776 else:
777 nodes = [repo.lookup('.')]
777 nodes = [repo.lookup('.')]
778 if good:
778 if good:
779 state['good'] += nodes
779 state['good'] += nodes
780 elif bad:
780 elif bad:
781 state['bad'] += nodes
781 state['bad'] += nodes
782 elif skip:
782 elif skip:
783 state['skip'] += nodes
783 state['skip'] += nodes
784 hbisect.save_state(repo, state)
784 hbisect.save_state(repo, state)
785 if not (state['good'] and state['bad']):
785 if not (state['good'] and state['bad']):
786 return
786 return
787
787
788 def mayupdate(repo, node, show_stats=True):
788 def mayupdate(repo, node, show_stats=True):
789 """common used update sequence"""
789 """common used update sequence"""
790 if noupdate:
790 if noupdate:
791 return
791 return
792 cmdutil.checkunfinished(repo)
792 cmdutil.checkunfinished(repo)
793 cmdutil.bailifchanged(repo)
793 cmdutil.bailifchanged(repo)
794 return hg.clean(repo, node, show_stats=show_stats)
794 return hg.clean(repo, node, show_stats=show_stats)
795
795
796 displayer = cmdutil.show_changeset(ui, repo, {})
796 displayer = cmdutil.show_changeset(ui, repo, {})
797
797
798 if command:
798 if command:
799 changesets = 1
799 changesets = 1
800 if noupdate:
800 if noupdate:
801 try:
801 try:
802 node = state['current'][0]
802 node = state['current'][0]
803 except LookupError:
803 except LookupError:
804 raise error.Abort(_('current bisect revision is unknown - '
804 raise error.Abort(_('current bisect revision is unknown - '
805 'start a new bisect to fix'))
805 'start a new bisect to fix'))
806 else:
806 else:
807 node, p2 = repo.dirstate.parents()
807 node, p2 = repo.dirstate.parents()
808 if p2 != nullid:
808 if p2 != nullid:
809 raise error.Abort(_('current bisect revision is a merge'))
809 raise error.Abort(_('current bisect revision is a merge'))
810 if rev:
810 if rev:
811 node = repo[scmutil.revsingle(repo, rev, node)].node()
811 node = repo[scmutil.revsingle(repo, rev, node)].node()
812 try:
812 try:
813 while changesets:
813 while changesets:
814 # update state
814 # update state
815 state['current'] = [node]
815 state['current'] = [node]
816 hbisect.save_state(repo, state)
816 hbisect.save_state(repo, state)
817 status = ui.system(command, environ={'HG_NODE': hex(node)},
817 status = ui.system(command, environ={'HG_NODE': hex(node)},
818 blockedtag='bisect_check')
818 blockedtag='bisect_check')
819 if status == 125:
819 if status == 125:
820 transition = "skip"
820 transition = "skip"
821 elif status == 0:
821 elif status == 0:
822 transition = "good"
822 transition = "good"
823 # status < 0 means process was killed
823 # status < 0 means process was killed
824 elif status == 127:
824 elif status == 127:
825 raise error.Abort(_("failed to execute %s") % command)
825 raise error.Abort(_("failed to execute %s") % command)
826 elif status < 0:
826 elif status < 0:
827 raise error.Abort(_("%s killed") % command)
827 raise error.Abort(_("%s killed") % command)
828 else:
828 else:
829 transition = "bad"
829 transition = "bad"
830 state[transition].append(node)
830 state[transition].append(node)
831 ctx = repo[node]
831 ctx = repo[node]
832 ui.status(_('changeset %d:%s: %s\n') % (ctx, ctx, transition))
832 ui.status(_('changeset %d:%s: %s\n') % (ctx, ctx, transition))
833 hbisect.checkstate(state)
833 hbisect.checkstate(state)
834 # bisect
834 # bisect
835 nodes, changesets, bgood = hbisect.bisect(repo.changelog, state)
835 nodes, changesets, bgood = hbisect.bisect(repo.changelog, state)
836 # update to next check
836 # update to next check
837 node = nodes[0]
837 node = nodes[0]
838 mayupdate(repo, node, show_stats=False)
838 mayupdate(repo, node, show_stats=False)
839 finally:
839 finally:
840 state['current'] = [node]
840 state['current'] = [node]
841 hbisect.save_state(repo, state)
841 hbisect.save_state(repo, state)
842 hbisect.printresult(ui, repo, state, displayer, nodes, bgood)
842 hbisect.printresult(ui, repo, state, displayer, nodes, bgood)
843 return
843 return
844
844
845 hbisect.checkstate(state)
845 hbisect.checkstate(state)
846
846
847 # actually bisect
847 # actually bisect
848 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
848 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
849 if extend:
849 if extend:
850 if not changesets:
850 if not changesets:
851 extendnode = hbisect.extendrange(repo, state, nodes, good)
851 extendnode = hbisect.extendrange(repo, state, nodes, good)
852 if extendnode is not None:
852 if extendnode is not None:
853 ui.write(_("Extending search to changeset %d:%s\n")
853 ui.write(_("Extending search to changeset %d:%s\n")
854 % (extendnode.rev(), extendnode))
854 % (extendnode.rev(), extendnode))
855 state['current'] = [extendnode.node()]
855 state['current'] = [extendnode.node()]
856 hbisect.save_state(repo, state)
856 hbisect.save_state(repo, state)
857 return mayupdate(repo, extendnode.node())
857 return mayupdate(repo, extendnode.node())
858 raise error.Abort(_("nothing to extend"))
858 raise error.Abort(_("nothing to extend"))
859
859
860 if changesets == 0:
860 if changesets == 0:
861 hbisect.printresult(ui, repo, state, displayer, nodes, good)
861 hbisect.printresult(ui, repo, state, displayer, nodes, good)
862 else:
862 else:
863 assert len(nodes) == 1 # only a single node can be tested next
863 assert len(nodes) == 1 # only a single node can be tested next
864 node = nodes[0]
864 node = nodes[0]
865 # compute the approximate number of remaining tests
865 # compute the approximate number of remaining tests
866 tests, size = 0, 2
866 tests, size = 0, 2
867 while size <= changesets:
867 while size <= changesets:
868 tests, size = tests + 1, size * 2
868 tests, size = tests + 1, size * 2
869 rev = repo.changelog.rev(node)
869 rev = repo.changelog.rev(node)
870 ui.write(_("Testing changeset %d:%s "
870 ui.write(_("Testing changeset %d:%s "
871 "(%d changesets remaining, ~%d tests)\n")
871 "(%d changesets remaining, ~%d tests)\n")
872 % (rev, short(node), changesets, tests))
872 % (rev, short(node), changesets, tests))
873 state['current'] = [node]
873 state['current'] = [node]
874 hbisect.save_state(repo, state)
874 hbisect.save_state(repo, state)
875 return mayupdate(repo, node)
875 return mayupdate(repo, node)
876
876
877 @command('bookmarks|bookmark',
877 @command('bookmarks|bookmark',
878 [('f', 'force', False, _('force')),
878 [('f', 'force', False, _('force')),
879 ('r', 'rev', '', _('revision for bookmark action'), _('REV')),
879 ('r', 'rev', '', _('revision for bookmark action'), _('REV')),
880 ('d', 'delete', False, _('delete a given bookmark')),
880 ('d', 'delete', False, _('delete a given bookmark')),
881 ('m', 'rename', '', _('rename a given bookmark'), _('OLD')),
881 ('m', 'rename', '', _('rename a given bookmark'), _('OLD')),
882 ('i', 'inactive', False, _('mark a bookmark inactive')),
882 ('i', 'inactive', False, _('mark a bookmark inactive')),
883 ] + formatteropts,
883 ] + formatteropts,
884 _('hg bookmarks [OPTIONS]... [NAME]...'))
884 _('hg bookmarks [OPTIONS]... [NAME]...'))
885 def bookmark(ui, repo, *names, **opts):
885 def bookmark(ui, repo, *names, **opts):
886 '''create a new bookmark or list existing bookmarks
886 '''create a new bookmark or list existing bookmarks
887
887
888 Bookmarks are labels on changesets to help track lines of development.
888 Bookmarks are labels on changesets to help track lines of development.
889 Bookmarks are unversioned and can be moved, renamed and deleted.
889 Bookmarks are unversioned and can be moved, renamed and deleted.
890 Deleting or moving a bookmark has no effect on the associated changesets.
890 Deleting or moving a bookmark has no effect on the associated changesets.
891
891
892 Creating or updating to a bookmark causes it to be marked as 'active'.
892 Creating or updating to a bookmark causes it to be marked as 'active'.
893 The active bookmark is indicated with a '*'.
893 The active bookmark is indicated with a '*'.
894 When a commit is made, the active bookmark will advance to the new commit.
894 When a commit is made, the active bookmark will advance to the new commit.
895 A plain :hg:`update` will also advance an active bookmark, if possible.
895 A plain :hg:`update` will also advance an active bookmark, if possible.
896 Updating away from a bookmark will cause it to be deactivated.
896 Updating away from a bookmark will cause it to be deactivated.
897
897
898 Bookmarks can be pushed and pulled between repositories (see
898 Bookmarks can be pushed and pulled between repositories (see
899 :hg:`help push` and :hg:`help pull`). If a shared bookmark has
899 :hg:`help push` and :hg:`help pull`). If a shared bookmark has
900 diverged, a new 'divergent bookmark' of the form 'name@path' will
900 diverged, a new 'divergent bookmark' of the form 'name@path' will
901 be created. Using :hg:`merge` will resolve the divergence.
901 be created. Using :hg:`merge` will resolve the divergence.
902
902
903 A bookmark named '@' has the special property that :hg:`clone` will
903 A bookmark named '@' has the special property that :hg:`clone` will
904 check it out by default if it exists.
904 check it out by default if it exists.
905
905
906 .. container:: verbose
906 .. container:: verbose
907
907
908 Examples:
908 Examples:
909
909
910 - create an active bookmark for a new line of development::
910 - create an active bookmark for a new line of development::
911
911
912 hg book new-feature
912 hg book new-feature
913
913
914 - create an inactive bookmark as a place marker::
914 - create an inactive bookmark as a place marker::
915
915
916 hg book -i reviewed
916 hg book -i reviewed
917
917
918 - create an inactive bookmark on another changeset::
918 - create an inactive bookmark on another changeset::
919
919
920 hg book -r .^ tested
920 hg book -r .^ tested
921
921
922 - rename bookmark turkey to dinner::
922 - rename bookmark turkey to dinner::
923
923
924 hg book -m turkey dinner
924 hg book -m turkey dinner
925
925
926 - move the '@' bookmark from another branch::
926 - move the '@' bookmark from another branch::
927
927
928 hg book -f @
928 hg book -f @
929 '''
929 '''
930 opts = pycompat.byteskwargs(opts)
930 opts = pycompat.byteskwargs(opts)
931 force = opts.get('force')
931 force = opts.get('force')
932 rev = opts.get('rev')
932 rev = opts.get('rev')
933 delete = opts.get('delete')
933 delete = opts.get('delete')
934 rename = opts.get('rename')
934 rename = opts.get('rename')
935 inactive = opts.get('inactive')
935 inactive = opts.get('inactive')
936
936
937 def checkformat(mark):
937 def checkformat(mark):
938 mark = mark.strip()
938 mark = mark.strip()
939 if not mark:
939 if not mark:
940 raise error.Abort(_("bookmark names cannot consist entirely of "
940 raise error.Abort(_("bookmark names cannot consist entirely of "
941 "whitespace"))
941 "whitespace"))
942 scmutil.checknewlabel(repo, mark, 'bookmark')
942 scmutil.checknewlabel(repo, mark, 'bookmark')
943 return mark
943 return mark
944
944
945 def checkconflict(repo, mark, cur, force=False, target=None):
945 def checkconflict(repo, mark, cur, force=False, target=None):
946 if mark in marks and not force:
946 if mark in marks and not force:
947 if target:
947 if target:
948 if marks[mark] == target and target == cur:
948 if marks[mark] == target and target == cur:
949 # re-activating a bookmark
949 # re-activating a bookmark
950 return
950 return
951 anc = repo.changelog.ancestors([repo[target].rev()])
951 anc = repo.changelog.ancestors([repo[target].rev()])
952 bmctx = repo[marks[mark]]
952 bmctx = repo[marks[mark]]
953 divs = [repo[b].node() for b in marks
953 divs = [repo[b].node() for b in marks
954 if b.split('@', 1)[0] == mark.split('@', 1)[0]]
954 if b.split('@', 1)[0] == mark.split('@', 1)[0]]
955
955
956 # allow resolving a single divergent bookmark even if moving
956 # allow resolving a single divergent bookmark even if moving
957 # the bookmark across branches when a revision is specified
957 # the bookmark across branches when a revision is specified
958 # that contains a divergent bookmark
958 # that contains a divergent bookmark
959 if bmctx.rev() not in anc and target in divs:
959 if bmctx.rev() not in anc and target in divs:
960 bookmarks.deletedivergent(repo, [target], mark)
960 bookmarks.deletedivergent(repo, [target], mark)
961 return
961 return
962
962
963 deletefrom = [b for b in divs
963 deletefrom = [b for b in divs
964 if repo[b].rev() in anc or b == target]
964 if repo[b].rev() in anc or b == target]
965 bookmarks.deletedivergent(repo, deletefrom, mark)
965 bookmarks.deletedivergent(repo, deletefrom, mark)
966 if bookmarks.validdest(repo, bmctx, repo[target]):
966 if bookmarks.validdest(repo, bmctx, repo[target]):
967 ui.status(_("moving bookmark '%s' forward from %s\n") %
967 ui.status(_("moving bookmark '%s' forward from %s\n") %
968 (mark, short(bmctx.node())))
968 (mark, short(bmctx.node())))
969 return
969 return
970 raise error.Abort(_("bookmark '%s' already exists "
970 raise error.Abort(_("bookmark '%s' already exists "
971 "(use -f to force)") % mark)
971 "(use -f to force)") % mark)
972 if ((mark in repo.branchmap() or mark == repo.dirstate.branch())
972 if ((mark in repo.branchmap() or mark == repo.dirstate.branch())
973 and not force):
973 and not force):
974 raise error.Abort(
974 raise error.Abort(
975 _("a bookmark cannot have the name of an existing branch"))
975 _("a bookmark cannot have the name of an existing branch"))
976 if len(mark) > 3 and not force:
976 if len(mark) > 3 and not force:
977 try:
977 try:
978 shadowhash = (mark in repo)
978 shadowhash = (mark in repo)
979 except error.LookupError: # ambiguous identifier
979 except error.LookupError: # ambiguous identifier
980 shadowhash = False
980 shadowhash = False
981 if shadowhash:
981 if shadowhash:
982 repo.ui.warn(
982 repo.ui.warn(
983 _("bookmark %s matches a changeset hash\n"
983 _("bookmark %s matches a changeset hash\n"
984 "(did you leave a -r out of an 'hg bookmark' command?)\n")
984 "(did you leave a -r out of an 'hg bookmark' command?)\n")
985 % mark)
985 % mark)
986
986
987 if delete and rename:
987 if delete and rename:
988 raise error.Abort(_("--delete and --rename are incompatible"))
988 raise error.Abort(_("--delete and --rename are incompatible"))
989 if delete and rev:
989 if delete and rev:
990 raise error.Abort(_("--rev is incompatible with --delete"))
990 raise error.Abort(_("--rev is incompatible with --delete"))
991 if rename and rev:
991 if rename and rev:
992 raise error.Abort(_("--rev is incompatible with --rename"))
992 raise error.Abort(_("--rev is incompatible with --rename"))
993 if not names and (delete or rev):
993 if not names and (delete or rev):
994 raise error.Abort(_("bookmark name required"))
994 raise error.Abort(_("bookmark name required"))
995
995
996 if delete or rename or names or inactive:
996 if delete or rename or names or inactive:
997 wlock = lock = tr = None
997 wlock = lock = tr = None
998 try:
998 try:
999 wlock = repo.wlock()
999 wlock = repo.wlock()
1000 lock = repo.lock()
1000 lock = repo.lock()
1001 cur = repo.changectx('.').node()
1001 cur = repo.changectx('.').node()
1002 marks = repo._bookmarks
1002 marks = repo._bookmarks
1003 if delete:
1003 if delete:
1004 tr = repo.transaction('bookmark')
1004 tr = repo.transaction('bookmark')
1005 for mark in names:
1005 for mark in names:
1006 if mark not in marks:
1006 if mark not in marks:
1007 raise error.Abort(_("bookmark '%s' does not exist") %
1007 raise error.Abort(_("bookmark '%s' does not exist") %
1008 mark)
1008 mark)
1009 if mark == repo._activebookmark:
1009 if mark == repo._activebookmark:
1010 bookmarks.deactivate(repo)
1010 bookmarks.deactivate(repo)
1011 del marks[mark]
1011 del marks[mark]
1012
1012
1013 elif rename:
1013 elif rename:
1014 tr = repo.transaction('bookmark')
1014 tr = repo.transaction('bookmark')
1015 if not names:
1015 if not names:
1016 raise error.Abort(_("new bookmark name required"))
1016 raise error.Abort(_("new bookmark name required"))
1017 elif len(names) > 1:
1017 elif len(names) > 1:
1018 raise error.Abort(_("only one new bookmark name allowed"))
1018 raise error.Abort(_("only one new bookmark name allowed"))
1019 mark = checkformat(names[0])
1019 mark = checkformat(names[0])
1020 if rename not in marks:
1020 if rename not in marks:
1021 raise error.Abort(_("bookmark '%s' does not exist")
1021 raise error.Abort(_("bookmark '%s' does not exist")
1022 % rename)
1022 % rename)
1023 checkconflict(repo, mark, cur, force)
1023 checkconflict(repo, mark, cur, force)
1024 marks[mark] = marks[rename]
1024 marks[mark] = marks[rename]
1025 if repo._activebookmark == rename and not inactive:
1025 if repo._activebookmark == rename and not inactive:
1026 bookmarks.activate(repo, mark)
1026 bookmarks.activate(repo, mark)
1027 del marks[rename]
1027 del marks[rename]
1028 elif names:
1028 elif names:
1029 tr = repo.transaction('bookmark')
1029 tr = repo.transaction('bookmark')
1030 newact = None
1030 newact = None
1031 for mark in names:
1031 for mark in names:
1032 mark = checkformat(mark)
1032 mark = checkformat(mark)
1033 if newact is None:
1033 if newact is None:
1034 newact = mark
1034 newact = mark
1035 if inactive and mark == repo._activebookmark:
1035 if inactive and mark == repo._activebookmark:
1036 bookmarks.deactivate(repo)
1036 bookmarks.deactivate(repo)
1037 return
1037 return
1038 tgt = cur
1038 tgt = cur
1039 if rev:
1039 if rev:
1040 tgt = scmutil.revsingle(repo, rev).node()
1040 tgt = scmutil.revsingle(repo, rev).node()
1041 checkconflict(repo, mark, cur, force, tgt)
1041 checkconflict(repo, mark, cur, force, tgt)
1042 marks[mark] = tgt
1042 marks[mark] = tgt
1043 if not inactive and cur == marks[newact] and not rev:
1043 if not inactive and cur == marks[newact] and not rev:
1044 bookmarks.activate(repo, newact)
1044 bookmarks.activate(repo, newact)
1045 elif cur != tgt and newact == repo._activebookmark:
1045 elif cur != tgt and newact == repo._activebookmark:
1046 bookmarks.deactivate(repo)
1046 bookmarks.deactivate(repo)
1047 elif inactive:
1047 elif inactive:
1048 if len(marks) == 0:
1048 if len(marks) == 0:
1049 ui.status(_("no bookmarks set\n"))
1049 ui.status(_("no bookmarks set\n"))
1050 elif not repo._activebookmark:
1050 elif not repo._activebookmark:
1051 ui.status(_("no active bookmark\n"))
1051 ui.status(_("no active bookmark\n"))
1052 else:
1052 else:
1053 bookmarks.deactivate(repo)
1053 bookmarks.deactivate(repo)
1054 if tr is not None:
1054 if tr is not None:
1055 marks.recordchange(tr)
1055 marks.recordchange(tr)
1056 tr.close()
1056 tr.close()
1057 finally:
1057 finally:
1058 lockmod.release(tr, lock, wlock)
1058 lockmod.release(tr, lock, wlock)
1059 else: # show bookmarks
1059 else: # show bookmarks
1060 fm = ui.formatter('bookmarks', opts)
1060 fm = ui.formatter('bookmarks', opts)
1061 hexfn = fm.hexfunc
1061 hexfn = fm.hexfunc
1062 marks = repo._bookmarks
1062 marks = repo._bookmarks
1063 if len(marks) == 0 and fm.isplain():
1063 if len(marks) == 0 and fm.isplain():
1064 ui.status(_("no bookmarks set\n"))
1064 ui.status(_("no bookmarks set\n"))
1065 for bmark, n in sorted(marks.iteritems()):
1065 for bmark, n in sorted(marks.iteritems()):
1066 active = repo._activebookmark
1066 active = repo._activebookmark
1067 if bmark == active:
1067 if bmark == active:
1068 prefix, label = '*', activebookmarklabel
1068 prefix, label = '*', activebookmarklabel
1069 else:
1069 else:
1070 prefix, label = ' ', ''
1070 prefix, label = ' ', ''
1071
1071
1072 fm.startitem()
1072 fm.startitem()
1073 if not ui.quiet:
1073 if not ui.quiet:
1074 fm.plain(' %s ' % prefix, label=label)
1074 fm.plain(' %s ' % prefix, label=label)
1075 fm.write('bookmark', '%s', bmark, label=label)
1075 fm.write('bookmark', '%s', bmark, label=label)
1076 pad = " " * (25 - encoding.colwidth(bmark))
1076 pad = " " * (25 - encoding.colwidth(bmark))
1077 fm.condwrite(not ui.quiet, 'rev node', pad + ' %d:%s',
1077 fm.condwrite(not ui.quiet, 'rev node', pad + ' %d:%s',
1078 repo.changelog.rev(n), hexfn(n), label=label)
1078 repo.changelog.rev(n), hexfn(n), label=label)
1079 fm.data(active=(bmark == active))
1079 fm.data(active=(bmark == active))
1080 fm.plain('\n')
1080 fm.plain('\n')
1081 fm.end()
1081 fm.end()
1082
1082
1083 @command('branch',
1083 @command('branch',
1084 [('f', 'force', None,
1084 [('f', 'force', None,
1085 _('set branch name even if it shadows an existing branch')),
1085 _('set branch name even if it shadows an existing branch')),
1086 ('C', 'clean', None, _('reset branch name to parent branch name'))],
1086 ('C', 'clean', None, _('reset branch name to parent branch name'))],
1087 _('[-fC] [NAME]'))
1087 _('[-fC] [NAME]'))
1088 def branch(ui, repo, label=None, **opts):
1088 def branch(ui, repo, label=None, **opts):
1089 """set or show the current branch name
1089 """set or show the current branch name
1090
1090
1091 .. note::
1091 .. note::
1092
1092
1093 Branch names are permanent and global. Use :hg:`bookmark` to create a
1093 Branch names are permanent and global. Use :hg:`bookmark` to create a
1094 light-weight bookmark instead. See :hg:`help glossary` for more
1094 light-weight bookmark instead. See :hg:`help glossary` for more
1095 information about named branches and bookmarks.
1095 information about named branches and bookmarks.
1096
1096
1097 With no argument, show the current branch name. With one argument,
1097 With no argument, show the current branch name. With one argument,
1098 set the working directory branch name (the branch will not exist
1098 set the working directory branch name (the branch will not exist
1099 in the repository until the next commit). Standard practice
1099 in the repository until the next commit). Standard practice
1100 recommends that primary development take place on the 'default'
1100 recommends that primary development take place on the 'default'
1101 branch.
1101 branch.
1102
1102
1103 Unless -f/--force is specified, branch will not let you set a
1103 Unless -f/--force is specified, branch will not let you set a
1104 branch name that already exists.
1104 branch name that already exists.
1105
1105
1106 Use -C/--clean to reset the working directory branch to that of
1106 Use -C/--clean to reset the working directory branch to that of
1107 the parent of the working directory, negating a previous branch
1107 the parent of the working directory, negating a previous branch
1108 change.
1108 change.
1109
1109
1110 Use the command :hg:`update` to switch to an existing branch. Use
1110 Use the command :hg:`update` to switch to an existing branch. Use
1111 :hg:`commit --close-branch` to mark this branch head as closed.
1111 :hg:`commit --close-branch` to mark this branch head as closed.
1112 When all heads of a branch are closed, the branch will be
1112 When all heads of a branch are closed, the branch will be
1113 considered closed.
1113 considered closed.
1114
1114
1115 Returns 0 on success.
1115 Returns 0 on success.
1116 """
1116 """
1117 opts = pycompat.byteskwargs(opts)
1117 opts = pycompat.byteskwargs(opts)
1118 if label:
1118 if label:
1119 label = label.strip()
1119 label = label.strip()
1120
1120
1121 if not opts.get('clean') and not label:
1121 if not opts.get('clean') and not label:
1122 ui.write("%s\n" % repo.dirstate.branch())
1122 ui.write("%s\n" % repo.dirstate.branch())
1123 return
1123 return
1124
1124
1125 with repo.wlock():
1125 with repo.wlock():
1126 if opts.get('clean'):
1126 if opts.get('clean'):
1127 label = repo[None].p1().branch()
1127 label = repo[None].p1().branch()
1128 repo.dirstate.setbranch(label)
1128 repo.dirstate.setbranch(label)
1129 ui.status(_('reset working directory to branch %s\n') % label)
1129 ui.status(_('reset working directory to branch %s\n') % label)
1130 elif label:
1130 elif label:
1131 if not opts.get('force') and label in repo.branchmap():
1131 if not opts.get('force') and label in repo.branchmap():
1132 if label not in [p.branch() for p in repo[None].parents()]:
1132 if label not in [p.branch() for p in repo[None].parents()]:
1133 raise error.Abort(_('a branch of the same name already'
1133 raise error.Abort(_('a branch of the same name already'
1134 ' exists'),
1134 ' exists'),
1135 # i18n: "it" refers to an existing branch
1135 # i18n: "it" refers to an existing branch
1136 hint=_("use 'hg update' to switch to it"))
1136 hint=_("use 'hg update' to switch to it"))
1137 scmutil.checknewlabel(repo, label, 'branch')
1137 scmutil.checknewlabel(repo, label, 'branch')
1138 repo.dirstate.setbranch(label)
1138 repo.dirstate.setbranch(label)
1139 ui.status(_('marked working directory as branch %s\n') % label)
1139 ui.status(_('marked working directory as branch %s\n') % label)
1140
1140
1141 # find any open named branches aside from default
1141 # find any open named branches aside from default
1142 others = [n for n, h, t, c in repo.branchmap().iterbranches()
1142 others = [n for n, h, t, c in repo.branchmap().iterbranches()
1143 if n != "default" and not c]
1143 if n != "default" and not c]
1144 if not others:
1144 if not others:
1145 ui.status(_('(branches are permanent and global, '
1145 ui.status(_('(branches are permanent and global, '
1146 'did you want a bookmark?)\n'))
1146 'did you want a bookmark?)\n'))
1147
1147
1148 @command('branches',
1148 @command('branches',
1149 [('a', 'active', False,
1149 [('a', 'active', False,
1150 _('show only branches that have unmerged heads (DEPRECATED)')),
1150 _('show only branches that have unmerged heads (DEPRECATED)')),
1151 ('c', 'closed', False, _('show normal and closed branches')),
1151 ('c', 'closed', False, _('show normal and closed branches')),
1152 ] + formatteropts,
1152 ] + formatteropts,
1153 _('[-c]'))
1153 _('[-c]'))
1154 def branches(ui, repo, active=False, closed=False, **opts):
1154 def branches(ui, repo, active=False, closed=False, **opts):
1155 """list repository named branches
1155 """list repository named branches
1156
1156
1157 List the repository's named branches, indicating which ones are
1157 List the repository's named branches, indicating which ones are
1158 inactive. If -c/--closed is specified, also list branches which have
1158 inactive. If -c/--closed is specified, also list branches which have
1159 been marked closed (see :hg:`commit --close-branch`).
1159 been marked closed (see :hg:`commit --close-branch`).
1160
1160
1161 Use the command :hg:`update` to switch to an existing branch.
1161 Use the command :hg:`update` to switch to an existing branch.
1162
1162
1163 Returns 0.
1163 Returns 0.
1164 """
1164 """
1165
1165
1166 opts = pycompat.byteskwargs(opts)
1166 opts = pycompat.byteskwargs(opts)
1167 ui.pager('branches')
1167 ui.pager('branches')
1168 fm = ui.formatter('branches', opts)
1168 fm = ui.formatter('branches', opts)
1169 hexfunc = fm.hexfunc
1169 hexfunc = fm.hexfunc
1170
1170
1171 allheads = set(repo.heads())
1171 allheads = set(repo.heads())
1172 branches = []
1172 branches = []
1173 for tag, heads, tip, isclosed in repo.branchmap().iterbranches():
1173 for tag, heads, tip, isclosed in repo.branchmap().iterbranches():
1174 isactive = not isclosed and bool(set(heads) & allheads)
1174 isactive = not isclosed and bool(set(heads) & allheads)
1175 branches.append((tag, repo[tip], isactive, not isclosed))
1175 branches.append((tag, repo[tip], isactive, not isclosed))
1176 branches.sort(key=lambda i: (i[2], i[1].rev(), i[0], i[3]),
1176 branches.sort(key=lambda i: (i[2], i[1].rev(), i[0], i[3]),
1177 reverse=True)
1177 reverse=True)
1178
1178
1179 for tag, ctx, isactive, isopen in branches:
1179 for tag, ctx, isactive, isopen in branches:
1180 if active and not isactive:
1180 if active and not isactive:
1181 continue
1181 continue
1182 if isactive:
1182 if isactive:
1183 label = 'branches.active'
1183 label = 'branches.active'
1184 notice = ''
1184 notice = ''
1185 elif not isopen:
1185 elif not isopen:
1186 if not closed:
1186 if not closed:
1187 continue
1187 continue
1188 label = 'branches.closed'
1188 label = 'branches.closed'
1189 notice = _(' (closed)')
1189 notice = _(' (closed)')
1190 else:
1190 else:
1191 label = 'branches.inactive'
1191 label = 'branches.inactive'
1192 notice = _(' (inactive)')
1192 notice = _(' (inactive)')
1193 current = (tag == repo.dirstate.branch())
1193 current = (tag == repo.dirstate.branch())
1194 if current:
1194 if current:
1195 label = 'branches.current'
1195 label = 'branches.current'
1196
1196
1197 fm.startitem()
1197 fm.startitem()
1198 fm.write('branch', '%s', tag, label=label)
1198 fm.write('branch', '%s', tag, label=label)
1199 rev = ctx.rev()
1199 rev = ctx.rev()
1200 padsize = max(31 - len(str(rev)) - encoding.colwidth(tag), 0)
1200 padsize = max(31 - len(str(rev)) - encoding.colwidth(tag), 0)
1201 fmt = ' ' * padsize + ' %d:%s'
1201 fmt = ' ' * padsize + ' %d:%s'
1202 fm.condwrite(not ui.quiet, 'rev node', fmt, rev, hexfunc(ctx.node()),
1202 fm.condwrite(not ui.quiet, 'rev node', fmt, rev, hexfunc(ctx.node()),
1203 label='log.changeset changeset.%s' % ctx.phasestr())
1203 label='log.changeset changeset.%s' % ctx.phasestr())
1204 fm.context(ctx=ctx)
1204 fm.context(ctx=ctx)
1205 fm.data(active=isactive, closed=not isopen, current=current)
1205 fm.data(active=isactive, closed=not isopen, current=current)
1206 if not ui.quiet:
1206 if not ui.quiet:
1207 fm.plain(notice)
1207 fm.plain(notice)
1208 fm.plain('\n')
1208 fm.plain('\n')
1209 fm.end()
1209 fm.end()
1210
1210
1211 @command('bundle',
1211 @command('bundle',
1212 [('f', 'force', None, _('run even when the destination is unrelated')),
1212 [('f', 'force', None, _('run even when the destination is unrelated')),
1213 ('r', 'rev', [], _('a changeset intended to be added to the destination'),
1213 ('r', 'rev', [], _('a changeset intended to be added to the destination'),
1214 _('REV')),
1214 _('REV')),
1215 ('b', 'branch', [], _('a specific branch you would like to bundle'),
1215 ('b', 'branch', [], _('a specific branch you would like to bundle'),
1216 _('BRANCH')),
1216 _('BRANCH')),
1217 ('', 'base', [],
1217 ('', 'base', [],
1218 _('a base changeset assumed to be available at the destination'),
1218 _('a base changeset assumed to be available at the destination'),
1219 _('REV')),
1219 _('REV')),
1220 ('a', 'all', None, _('bundle all changesets in the repository')),
1220 ('a', 'all', None, _('bundle all changesets in the repository')),
1221 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE')),
1221 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE')),
1222 ] + remoteopts,
1222 ] + remoteopts,
1223 _('[-f] [-t BUNDLESPEC] [-a] [-r REV]... [--base REV]... FILE [DEST]'))
1223 _('[-f] [-t BUNDLESPEC] [-a] [-r REV]... [--base REV]... FILE [DEST]'))
1224 def bundle(ui, repo, fname, dest=None, **opts):
1224 def bundle(ui, repo, fname, dest=None, **opts):
1225 """create a bundle file
1225 """create a bundle file
1226
1226
1227 Generate a bundle file containing data to be added to a repository.
1227 Generate a bundle file containing data to be added to a repository.
1228
1228
1229 To create a bundle containing all changesets, use -a/--all
1229 To create a bundle containing all changesets, use -a/--all
1230 (or --base null). Otherwise, hg assumes the destination will have
1230 (or --base null). Otherwise, hg assumes the destination will have
1231 all the nodes you specify with --base parameters. Otherwise, hg
1231 all the nodes you specify with --base parameters. Otherwise, hg
1232 will assume the repository has all the nodes in destination, or
1232 will assume the repository has all the nodes in destination, or
1233 default-push/default if no destination is specified.
1233 default-push/default if no destination is specified.
1234
1234
1235 You can change bundle format with the -t/--type option. See
1235 You can change bundle format with the -t/--type option. See
1236 :hg:`help bundlespec` for documentation on this format. By default,
1236 :hg:`help bundlespec` for documentation on this format. By default,
1237 the most appropriate format is used and compression defaults to
1237 the most appropriate format is used and compression defaults to
1238 bzip2.
1238 bzip2.
1239
1239
1240 The bundle file can then be transferred using conventional means
1240 The bundle file can then be transferred using conventional means
1241 and applied to another repository with the unbundle or pull
1241 and applied to another repository with the unbundle or pull
1242 command. This is useful when direct push and pull are not
1242 command. This is useful when direct push and pull are not
1243 available or when exporting an entire repository is undesirable.
1243 available or when exporting an entire repository is undesirable.
1244
1244
1245 Applying bundles preserves all changeset contents including
1245 Applying bundles preserves all changeset contents including
1246 permissions, copy/rename information, and revision history.
1246 permissions, copy/rename information, and revision history.
1247
1247
1248 Returns 0 on success, 1 if no changes found.
1248 Returns 0 on success, 1 if no changes found.
1249 """
1249 """
1250 opts = pycompat.byteskwargs(opts)
1250 opts = pycompat.byteskwargs(opts)
1251 revs = None
1251 revs = None
1252 if 'rev' in opts:
1252 if 'rev' in opts:
1253 revstrings = opts['rev']
1253 revstrings = opts['rev']
1254 revs = scmutil.revrange(repo, revstrings)
1254 revs = scmutil.revrange(repo, revstrings)
1255 if revstrings and not revs:
1255 if revstrings and not revs:
1256 raise error.Abort(_('no commits to bundle'))
1256 raise error.Abort(_('no commits to bundle'))
1257
1257
1258 bundletype = opts.get('type', 'bzip2').lower()
1258 bundletype = opts.get('type', 'bzip2').lower()
1259 try:
1259 try:
1260 bcompression, cgversion, params = exchange.parsebundlespec(
1260 bcompression, cgversion, params = exchange.parsebundlespec(
1261 repo, bundletype, strict=False)
1261 repo, bundletype, strict=False)
1262 except error.UnsupportedBundleSpecification as e:
1262 except error.UnsupportedBundleSpecification as e:
1263 raise error.Abort(str(e),
1263 raise error.Abort(str(e),
1264 hint=_("see 'hg help bundlespec' for supported "
1264 hint=_("see 'hg help bundlespec' for supported "
1265 "values for --type"))
1265 "values for --type"))
1266
1266
1267 # Packed bundles are a pseudo bundle format for now.
1267 # Packed bundles are a pseudo bundle format for now.
1268 if cgversion == 's1':
1268 if cgversion == 's1':
1269 raise error.Abort(_('packed bundles cannot be produced by "hg bundle"'),
1269 raise error.Abort(_('packed bundles cannot be produced by "hg bundle"'),
1270 hint=_("use 'hg debugcreatestreamclonebundle'"))
1270 hint=_("use 'hg debugcreatestreamclonebundle'"))
1271
1271
1272 if opts.get('all'):
1272 if opts.get('all'):
1273 if dest:
1273 if dest:
1274 raise error.Abort(_("--all is incompatible with specifying "
1274 raise error.Abort(_("--all is incompatible with specifying "
1275 "a destination"))
1275 "a destination"))
1276 if opts.get('base'):
1276 if opts.get('base'):
1277 ui.warn(_("ignoring --base because --all was specified\n"))
1277 ui.warn(_("ignoring --base because --all was specified\n"))
1278 base = ['null']
1278 base = ['null']
1279 else:
1279 else:
1280 base = scmutil.revrange(repo, opts.get('base'))
1280 base = scmutil.revrange(repo, opts.get('base'))
1281 if cgversion not in changegroup.supportedoutgoingversions(repo):
1281 if cgversion not in changegroup.supportedoutgoingversions(repo):
1282 raise error.Abort(_("repository does not support bundle version %s") %
1282 raise error.Abort(_("repository does not support bundle version %s") %
1283 cgversion)
1283 cgversion)
1284
1284
1285 if base:
1285 if base:
1286 if dest:
1286 if dest:
1287 raise error.Abort(_("--base is incompatible with specifying "
1287 raise error.Abort(_("--base is incompatible with specifying "
1288 "a destination"))
1288 "a destination"))
1289 common = [repo.lookup(rev) for rev in base]
1289 common = [repo.lookup(rev) for rev in base]
1290 heads = revs and map(repo.lookup, revs) or None
1290 heads = revs and map(repo.lookup, revs) or None
1291 outgoing = discovery.outgoing(repo, common, heads)
1291 outgoing = discovery.outgoing(repo, common, heads)
1292 else:
1292 else:
1293 dest = ui.expandpath(dest or 'default-push', dest or 'default')
1293 dest = ui.expandpath(dest or 'default-push', dest or 'default')
1294 dest, branches = hg.parseurl(dest, opts.get('branch'))
1294 dest, branches = hg.parseurl(dest, opts.get('branch'))
1295 other = hg.peer(repo, opts, dest)
1295 other = hg.peer(repo, opts, dest)
1296 revs, checkout = hg.addbranchrevs(repo, repo, branches, revs)
1296 revs, checkout = hg.addbranchrevs(repo, repo, branches, revs)
1297 heads = revs and map(repo.lookup, revs) or revs
1297 heads = revs and map(repo.lookup, revs) or revs
1298 outgoing = discovery.findcommonoutgoing(repo, other,
1298 outgoing = discovery.findcommonoutgoing(repo, other,
1299 onlyheads=heads,
1299 onlyheads=heads,
1300 force=opts.get('force'),
1300 force=opts.get('force'),
1301 portable=True)
1301 portable=True)
1302
1302
1303 if not outgoing.missing:
1303 if not outgoing.missing:
1304 scmutil.nochangesfound(ui, repo, not base and outgoing.excluded)
1304 scmutil.nochangesfound(ui, repo, not base and outgoing.excluded)
1305 return 1
1305 return 1
1306
1306
1307 if cgversion == '01': #bundle1
1307 if cgversion == '01': #bundle1
1308 if bcompression is None:
1308 if bcompression is None:
1309 bcompression = 'UN'
1309 bcompression = 'UN'
1310 bversion = 'HG10' + bcompression
1310 bversion = 'HG10' + bcompression
1311 bcompression = None
1311 bcompression = None
1312 elif cgversion in ('02', '03'):
1312 elif cgversion in ('02', '03'):
1313 bversion = 'HG20'
1313 bversion = 'HG20'
1314 else:
1314 else:
1315 raise error.ProgrammingError(
1315 raise error.ProgrammingError(
1316 'bundle: unexpected changegroup version %s' % cgversion)
1316 'bundle: unexpected changegroup version %s' % cgversion)
1317
1317
1318 # TODO compression options should be derived from bundlespec parsing.
1318 # TODO compression options should be derived from bundlespec parsing.
1319 # This is a temporary hack to allow adjusting bundle compression
1319 # This is a temporary hack to allow adjusting bundle compression
1320 # level without a) formalizing the bundlespec changes to declare it
1320 # level without a) formalizing the bundlespec changes to declare it
1321 # b) introducing a command flag.
1321 # b) introducing a command flag.
1322 compopts = {}
1322 compopts = {}
1323 complevel = ui.configint('experimental', 'bundlecomplevel')
1323 complevel = ui.configint('experimental', 'bundlecomplevel')
1324 if complevel is not None:
1324 if complevel is not None:
1325 compopts['level'] = complevel
1325 compopts['level'] = complevel
1326
1326
1327
1327
1328 contentopts = {'cg.version': cgversion}
1328 contentopts = {'cg.version': cgversion}
1329 if repo.ui.configbool('experimental', 'evolution.bundle-obsmarker', False):
1329 if repo.ui.configbool('experimental', 'evolution.bundle-obsmarker', False):
1330 contentopts['obsolescence'] = True
1330 contentopts['obsolescence'] = True
1331 bundle2.writenewbundle(ui, repo, 'bundle', fname, bversion, outgoing,
1331 bundle2.writenewbundle(ui, repo, 'bundle', fname, bversion, outgoing,
1332 contentopts, compression=bcompression,
1332 contentopts, compression=bcompression,
1333 compopts=compopts)
1333 compopts=compopts)
1334
1334
1335 @command('cat',
1335 @command('cat',
1336 [('o', 'output', '',
1336 [('o', 'output', '',
1337 _('print output to file with formatted name'), _('FORMAT')),
1337 _('print output to file with formatted name'), _('FORMAT')),
1338 ('r', 'rev', '', _('print the given revision'), _('REV')),
1338 ('r', 'rev', '', _('print the given revision'), _('REV')),
1339 ('', 'decode', None, _('apply any matching decode filter')),
1339 ('', 'decode', None, _('apply any matching decode filter')),
1340 ] + walkopts,
1340 ] + walkopts,
1341 _('[OPTION]... FILE...'),
1341 _('[OPTION]... FILE...'),
1342 inferrepo=True)
1342 inferrepo=True)
1343 def cat(ui, repo, file1, *pats, **opts):
1343 def cat(ui, repo, file1, *pats, **opts):
1344 """output the current or given revision of files
1344 """output the current or given revision of files
1345
1345
1346 Print the specified files as they were at the given revision. If
1346 Print the specified files as they were at the given revision. If
1347 no revision is given, the parent of the working directory is used.
1347 no revision is given, the parent of the working directory is used.
1348
1348
1349 Output may be to a file, in which case the name of the file is
1349 Output may be to a file, in which case the name of the file is
1350 given using a format string. The formatting rules as follows:
1350 given using a format string. The formatting rules as follows:
1351
1351
1352 :``%%``: literal "%" character
1352 :``%%``: literal "%" character
1353 :``%s``: basename of file being printed
1353 :``%s``: basename of file being printed
1354 :``%d``: dirname of file being printed, or '.' if in repository root
1354 :``%d``: dirname of file being printed, or '.' if in repository root
1355 :``%p``: root-relative path name of file being printed
1355 :``%p``: root-relative path name of file being printed
1356 :``%H``: changeset hash (40 hexadecimal digits)
1356 :``%H``: changeset hash (40 hexadecimal digits)
1357 :``%R``: changeset revision number
1357 :``%R``: changeset revision number
1358 :``%h``: short-form changeset hash (12 hexadecimal digits)
1358 :``%h``: short-form changeset hash (12 hexadecimal digits)
1359 :``%r``: zero-padded changeset revision number
1359 :``%r``: zero-padded changeset revision number
1360 :``%b``: basename of the exporting repository
1360 :``%b``: basename of the exporting repository
1361
1361
1362 Returns 0 on success.
1362 Returns 0 on success.
1363 """
1363 """
1364 ctx = scmutil.revsingle(repo, opts.get('rev'))
1364 ctx = scmutil.revsingle(repo, opts.get('rev'))
1365 m = scmutil.match(ctx, (file1,) + pats, opts)
1365 m = scmutil.match(ctx, (file1,) + pats, opts)
1366 fntemplate = opts.pop('output', '')
1366
1367
1367 ui.pager('cat')
1368 ui.pager('cat')
1368 return cmdutil.cat(ui, repo, ctx, m, '', **opts)
1369 return cmdutil.cat(ui, repo, ctx, m, fntemplate, '', **opts)
1369
1370
1370 @command('^clone',
1371 @command('^clone',
1371 [('U', 'noupdate', None, _('the clone will include an empty working '
1372 [('U', 'noupdate', None, _('the clone will include an empty working '
1372 'directory (only a repository)')),
1373 'directory (only a repository)')),
1373 ('u', 'updaterev', '', _('revision, tag, or branch to check out'),
1374 ('u', 'updaterev', '', _('revision, tag, or branch to check out'),
1374 _('REV')),
1375 _('REV')),
1375 ('r', 'rev', [], _('include the specified changeset'), _('REV')),
1376 ('r', 'rev', [], _('include the specified changeset'), _('REV')),
1376 ('b', 'branch', [], _('clone only the specified branch'), _('BRANCH')),
1377 ('b', 'branch', [], _('clone only the specified branch'), _('BRANCH')),
1377 ('', 'pull', None, _('use pull protocol to copy metadata')),
1378 ('', 'pull', None, _('use pull protocol to copy metadata')),
1378 ('', 'uncompressed', None, _('use uncompressed transfer (fast over LAN)')),
1379 ('', 'uncompressed', None, _('use uncompressed transfer (fast over LAN)')),
1379 ] + remoteopts,
1380 ] + remoteopts,
1380 _('[OPTION]... SOURCE [DEST]'),
1381 _('[OPTION]... SOURCE [DEST]'),
1381 norepo=True)
1382 norepo=True)
1382 def clone(ui, source, dest=None, **opts):
1383 def clone(ui, source, dest=None, **opts):
1383 """make a copy of an existing repository
1384 """make a copy of an existing repository
1384
1385
1385 Create a copy of an existing repository in a new directory.
1386 Create a copy of an existing repository in a new directory.
1386
1387
1387 If no destination directory name is specified, it defaults to the
1388 If no destination directory name is specified, it defaults to the
1388 basename of the source.
1389 basename of the source.
1389
1390
1390 The location of the source is added to the new repository's
1391 The location of the source is added to the new repository's
1391 ``.hg/hgrc`` file, as the default to be used for future pulls.
1392 ``.hg/hgrc`` file, as the default to be used for future pulls.
1392
1393
1393 Only local paths and ``ssh://`` URLs are supported as
1394 Only local paths and ``ssh://`` URLs are supported as
1394 destinations. For ``ssh://`` destinations, no working directory or
1395 destinations. For ``ssh://`` destinations, no working directory or
1395 ``.hg/hgrc`` will be created on the remote side.
1396 ``.hg/hgrc`` will be created on the remote side.
1396
1397
1397 If the source repository has a bookmark called '@' set, that
1398 If the source repository has a bookmark called '@' set, that
1398 revision will be checked out in the new repository by default.
1399 revision will be checked out in the new repository by default.
1399
1400
1400 To check out a particular version, use -u/--update, or
1401 To check out a particular version, use -u/--update, or
1401 -U/--noupdate to create a clone with no working directory.
1402 -U/--noupdate to create a clone with no working directory.
1402
1403
1403 To pull only a subset of changesets, specify one or more revisions
1404 To pull only a subset of changesets, specify one or more revisions
1404 identifiers with -r/--rev or branches with -b/--branch. The
1405 identifiers with -r/--rev or branches with -b/--branch. The
1405 resulting clone will contain only the specified changesets and
1406 resulting clone will contain only the specified changesets and
1406 their ancestors. These options (or 'clone src#rev dest') imply
1407 their ancestors. These options (or 'clone src#rev dest') imply
1407 --pull, even for local source repositories.
1408 --pull, even for local source repositories.
1408
1409
1409 .. note::
1410 .. note::
1410
1411
1411 Specifying a tag will include the tagged changeset but not the
1412 Specifying a tag will include the tagged changeset but not the
1412 changeset containing the tag.
1413 changeset containing the tag.
1413
1414
1414 .. container:: verbose
1415 .. container:: verbose
1415
1416
1416 For efficiency, hardlinks are used for cloning whenever the
1417 For efficiency, hardlinks are used for cloning whenever the
1417 source and destination are on the same filesystem (note this
1418 source and destination are on the same filesystem (note this
1418 applies only to the repository data, not to the working
1419 applies only to the repository data, not to the working
1419 directory). Some filesystems, such as AFS, implement hardlinking
1420 directory). Some filesystems, such as AFS, implement hardlinking
1420 incorrectly, but do not report errors. In these cases, use the
1421 incorrectly, but do not report errors. In these cases, use the
1421 --pull option to avoid hardlinking.
1422 --pull option to avoid hardlinking.
1422
1423
1423 In some cases, you can clone repositories and the working
1424 In some cases, you can clone repositories and the working
1424 directory using full hardlinks with ::
1425 directory using full hardlinks with ::
1425
1426
1426 $ cp -al REPO REPOCLONE
1427 $ cp -al REPO REPOCLONE
1427
1428
1428 This is the fastest way to clone, but it is not always safe. The
1429 This is the fastest way to clone, but it is not always safe. The
1429 operation is not atomic (making sure REPO is not modified during
1430 operation is not atomic (making sure REPO is not modified during
1430 the operation is up to you) and you have to make sure your
1431 the operation is up to you) and you have to make sure your
1431 editor breaks hardlinks (Emacs and most Linux Kernel tools do
1432 editor breaks hardlinks (Emacs and most Linux Kernel tools do
1432 so). Also, this is not compatible with certain extensions that
1433 so). Also, this is not compatible with certain extensions that
1433 place their metadata under the .hg directory, such as mq.
1434 place their metadata under the .hg directory, such as mq.
1434
1435
1435 Mercurial will update the working directory to the first applicable
1436 Mercurial will update the working directory to the first applicable
1436 revision from this list:
1437 revision from this list:
1437
1438
1438 a) null if -U or the source repository has no changesets
1439 a) null if -U or the source repository has no changesets
1439 b) if -u . and the source repository is local, the first parent of
1440 b) if -u . and the source repository is local, the first parent of
1440 the source repository's working directory
1441 the source repository's working directory
1441 c) the changeset specified with -u (if a branch name, this means the
1442 c) the changeset specified with -u (if a branch name, this means the
1442 latest head of that branch)
1443 latest head of that branch)
1443 d) the changeset specified with -r
1444 d) the changeset specified with -r
1444 e) the tipmost head specified with -b
1445 e) the tipmost head specified with -b
1445 f) the tipmost head specified with the url#branch source syntax
1446 f) the tipmost head specified with the url#branch source syntax
1446 g) the revision marked with the '@' bookmark, if present
1447 g) the revision marked with the '@' bookmark, if present
1447 h) the tipmost head of the default branch
1448 h) the tipmost head of the default branch
1448 i) tip
1449 i) tip
1449
1450
1450 When cloning from servers that support it, Mercurial may fetch
1451 When cloning from servers that support it, Mercurial may fetch
1451 pre-generated data from a server-advertised URL. When this is done,
1452 pre-generated data from a server-advertised URL. When this is done,
1452 hooks operating on incoming changesets and changegroups may fire twice,
1453 hooks operating on incoming changesets and changegroups may fire twice,
1453 once for the bundle fetched from the URL and another for any additional
1454 once for the bundle fetched from the URL and another for any additional
1454 data not fetched from this URL. In addition, if an error occurs, the
1455 data not fetched from this URL. In addition, if an error occurs, the
1455 repository may be rolled back to a partial clone. This behavior may
1456 repository may be rolled back to a partial clone. This behavior may
1456 change in future releases. See :hg:`help -e clonebundles` for more.
1457 change in future releases. See :hg:`help -e clonebundles` for more.
1457
1458
1458 Examples:
1459 Examples:
1459
1460
1460 - clone a remote repository to a new directory named hg/::
1461 - clone a remote repository to a new directory named hg/::
1461
1462
1462 hg clone https://www.mercurial-scm.org/repo/hg/
1463 hg clone https://www.mercurial-scm.org/repo/hg/
1463
1464
1464 - create a lightweight local clone::
1465 - create a lightweight local clone::
1465
1466
1466 hg clone project/ project-feature/
1467 hg clone project/ project-feature/
1467
1468
1468 - clone from an absolute path on an ssh server (note double-slash)::
1469 - clone from an absolute path on an ssh server (note double-slash)::
1469
1470
1470 hg clone ssh://user@server//home/projects/alpha/
1471 hg clone ssh://user@server//home/projects/alpha/
1471
1472
1472 - do a high-speed clone over a LAN while checking out a
1473 - do a high-speed clone over a LAN while checking out a
1473 specified version::
1474 specified version::
1474
1475
1475 hg clone --uncompressed http://server/repo -u 1.5
1476 hg clone --uncompressed http://server/repo -u 1.5
1476
1477
1477 - create a repository without changesets after a particular revision::
1478 - create a repository without changesets after a particular revision::
1478
1479
1479 hg clone -r 04e544 experimental/ good/
1480 hg clone -r 04e544 experimental/ good/
1480
1481
1481 - clone (and track) a particular named branch::
1482 - clone (and track) a particular named branch::
1482
1483
1483 hg clone https://www.mercurial-scm.org/repo/hg/#stable
1484 hg clone https://www.mercurial-scm.org/repo/hg/#stable
1484
1485
1485 See :hg:`help urls` for details on specifying URLs.
1486 See :hg:`help urls` for details on specifying URLs.
1486
1487
1487 Returns 0 on success.
1488 Returns 0 on success.
1488 """
1489 """
1489 opts = pycompat.byteskwargs(opts)
1490 opts = pycompat.byteskwargs(opts)
1490 if opts.get('noupdate') and opts.get('updaterev'):
1491 if opts.get('noupdate') and opts.get('updaterev'):
1491 raise error.Abort(_("cannot specify both --noupdate and --updaterev"))
1492 raise error.Abort(_("cannot specify both --noupdate and --updaterev"))
1492
1493
1493 r = hg.clone(ui, opts, source, dest,
1494 r = hg.clone(ui, opts, source, dest,
1494 pull=opts.get('pull'),
1495 pull=opts.get('pull'),
1495 stream=opts.get('uncompressed'),
1496 stream=opts.get('uncompressed'),
1496 rev=opts.get('rev'),
1497 rev=opts.get('rev'),
1497 update=opts.get('updaterev') or not opts.get('noupdate'),
1498 update=opts.get('updaterev') or not opts.get('noupdate'),
1498 branch=opts.get('branch'),
1499 branch=opts.get('branch'),
1499 shareopts=opts.get('shareopts'))
1500 shareopts=opts.get('shareopts'))
1500
1501
1501 return r is None
1502 return r is None
1502
1503
1503 @command('^commit|ci',
1504 @command('^commit|ci',
1504 [('A', 'addremove', None,
1505 [('A', 'addremove', None,
1505 _('mark new/missing files as added/removed before committing')),
1506 _('mark new/missing files as added/removed before committing')),
1506 ('', 'close-branch', None,
1507 ('', 'close-branch', None,
1507 _('mark a branch head as closed')),
1508 _('mark a branch head as closed')),
1508 ('', 'amend', None, _('amend the parent of the working directory')),
1509 ('', 'amend', None, _('amend the parent of the working directory')),
1509 ('s', 'secret', None, _('use the secret phase for committing')),
1510 ('s', 'secret', None, _('use the secret phase for committing')),
1510 ('e', 'edit', None, _('invoke editor on commit messages')),
1511 ('e', 'edit', None, _('invoke editor on commit messages')),
1511 ('i', 'interactive', None, _('use interactive mode')),
1512 ('i', 'interactive', None, _('use interactive mode')),
1512 ] + walkopts + commitopts + commitopts2 + subrepoopts,
1513 ] + walkopts + commitopts + commitopts2 + subrepoopts,
1513 _('[OPTION]... [FILE]...'),
1514 _('[OPTION]... [FILE]...'),
1514 inferrepo=True)
1515 inferrepo=True)
1515 def commit(ui, repo, *pats, **opts):
1516 def commit(ui, repo, *pats, **opts):
1516 """commit the specified files or all outstanding changes
1517 """commit the specified files or all outstanding changes
1517
1518
1518 Commit changes to the given files into the repository. Unlike a
1519 Commit changes to the given files into the repository. Unlike a
1519 centralized SCM, this operation is a local operation. See
1520 centralized SCM, this operation is a local operation. See
1520 :hg:`push` for a way to actively distribute your changes.
1521 :hg:`push` for a way to actively distribute your changes.
1521
1522
1522 If a list of files is omitted, all changes reported by :hg:`status`
1523 If a list of files is omitted, all changes reported by :hg:`status`
1523 will be committed.
1524 will be committed.
1524
1525
1525 If you are committing the result of a merge, do not provide any
1526 If you are committing the result of a merge, do not provide any
1526 filenames or -I/-X filters.
1527 filenames or -I/-X filters.
1527
1528
1528 If no commit message is specified, Mercurial starts your
1529 If no commit message is specified, Mercurial starts your
1529 configured editor where you can enter a message. In case your
1530 configured editor where you can enter a message. In case your
1530 commit fails, you will find a backup of your message in
1531 commit fails, you will find a backup of your message in
1531 ``.hg/last-message.txt``.
1532 ``.hg/last-message.txt``.
1532
1533
1533 The --close-branch flag can be used to mark the current branch
1534 The --close-branch flag can be used to mark the current branch
1534 head closed. When all heads of a branch are closed, the branch
1535 head closed. When all heads of a branch are closed, the branch
1535 will be considered closed and no longer listed.
1536 will be considered closed and no longer listed.
1536
1537
1537 The --amend flag can be used to amend the parent of the
1538 The --amend flag can be used to amend the parent of the
1538 working directory with a new commit that contains the changes
1539 working directory with a new commit that contains the changes
1539 in the parent in addition to those currently reported by :hg:`status`,
1540 in the parent in addition to those currently reported by :hg:`status`,
1540 if there are any. The old commit is stored in a backup bundle in
1541 if there are any. The old commit is stored in a backup bundle in
1541 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
1542 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
1542 on how to restore it).
1543 on how to restore it).
1543
1544
1544 Message, user and date are taken from the amended commit unless
1545 Message, user and date are taken from the amended commit unless
1545 specified. When a message isn't specified on the command line,
1546 specified. When a message isn't specified on the command line,
1546 the editor will open with the message of the amended commit.
1547 the editor will open with the message of the amended commit.
1547
1548
1548 It is not possible to amend public changesets (see :hg:`help phases`)
1549 It is not possible to amend public changesets (see :hg:`help phases`)
1549 or changesets that have children.
1550 or changesets that have children.
1550
1551
1551 See :hg:`help dates` for a list of formats valid for -d/--date.
1552 See :hg:`help dates` for a list of formats valid for -d/--date.
1552
1553
1553 Returns 0 on success, 1 if nothing changed.
1554 Returns 0 on success, 1 if nothing changed.
1554
1555
1555 .. container:: verbose
1556 .. container:: verbose
1556
1557
1557 Examples:
1558 Examples:
1558
1559
1559 - commit all files ending in .py::
1560 - commit all files ending in .py::
1560
1561
1561 hg commit --include "set:**.py"
1562 hg commit --include "set:**.py"
1562
1563
1563 - commit all non-binary files::
1564 - commit all non-binary files::
1564
1565
1565 hg commit --exclude "set:binary()"
1566 hg commit --exclude "set:binary()"
1566
1567
1567 - amend the current commit and set the date to now::
1568 - amend the current commit and set the date to now::
1568
1569
1569 hg commit --amend --date now
1570 hg commit --amend --date now
1570 """
1571 """
1571 wlock = lock = None
1572 wlock = lock = None
1572 try:
1573 try:
1573 wlock = repo.wlock()
1574 wlock = repo.wlock()
1574 lock = repo.lock()
1575 lock = repo.lock()
1575 return _docommit(ui, repo, *pats, **opts)
1576 return _docommit(ui, repo, *pats, **opts)
1576 finally:
1577 finally:
1577 release(lock, wlock)
1578 release(lock, wlock)
1578
1579
1579 def _docommit(ui, repo, *pats, **opts):
1580 def _docommit(ui, repo, *pats, **opts):
1580 if opts.get(r'interactive'):
1581 if opts.get(r'interactive'):
1581 opts.pop(r'interactive')
1582 opts.pop(r'interactive')
1582 ret = cmdutil.dorecord(ui, repo, commit, None, False,
1583 ret = cmdutil.dorecord(ui, repo, commit, None, False,
1583 cmdutil.recordfilter, *pats,
1584 cmdutil.recordfilter, *pats,
1584 **opts)
1585 **opts)
1585 # ret can be 0 (no changes to record) or the value returned by
1586 # ret can be 0 (no changes to record) or the value returned by
1586 # commit(), 1 if nothing changed or None on success.
1587 # commit(), 1 if nothing changed or None on success.
1587 return 1 if ret == 0 else ret
1588 return 1 if ret == 0 else ret
1588
1589
1589 opts = pycompat.byteskwargs(opts)
1590 opts = pycompat.byteskwargs(opts)
1590 if opts.get('subrepos'):
1591 if opts.get('subrepos'):
1591 if opts.get('amend'):
1592 if opts.get('amend'):
1592 raise error.Abort(_('cannot amend with --subrepos'))
1593 raise error.Abort(_('cannot amend with --subrepos'))
1593 # Let --subrepos on the command line override config setting.
1594 # Let --subrepos on the command line override config setting.
1594 ui.setconfig('ui', 'commitsubrepos', True, 'commit')
1595 ui.setconfig('ui', 'commitsubrepos', True, 'commit')
1595
1596
1596 cmdutil.checkunfinished(repo, commit=True)
1597 cmdutil.checkunfinished(repo, commit=True)
1597
1598
1598 branch = repo[None].branch()
1599 branch = repo[None].branch()
1599 bheads = repo.branchheads(branch)
1600 bheads = repo.branchheads(branch)
1600
1601
1601 extra = {}
1602 extra = {}
1602 if opts.get('close_branch'):
1603 if opts.get('close_branch'):
1603 extra['close'] = 1
1604 extra['close'] = 1
1604
1605
1605 if not bheads:
1606 if not bheads:
1606 raise error.Abort(_('can only close branch heads'))
1607 raise error.Abort(_('can only close branch heads'))
1607 elif opts.get('amend'):
1608 elif opts.get('amend'):
1608 if repo[None].parents()[0].p1().branch() != branch and \
1609 if repo[None].parents()[0].p1().branch() != branch and \
1609 repo[None].parents()[0].p2().branch() != branch:
1610 repo[None].parents()[0].p2().branch() != branch:
1610 raise error.Abort(_('can only close branch heads'))
1611 raise error.Abort(_('can only close branch heads'))
1611
1612
1612 if opts.get('amend'):
1613 if opts.get('amend'):
1613 if ui.configbool('ui', 'commitsubrepos'):
1614 if ui.configbool('ui', 'commitsubrepos'):
1614 raise error.Abort(_('cannot amend with ui.commitsubrepos enabled'))
1615 raise error.Abort(_('cannot amend with ui.commitsubrepos enabled'))
1615
1616
1616 old = repo['.']
1617 old = repo['.']
1617 if not old.mutable():
1618 if not old.mutable():
1618 raise error.Abort(_('cannot amend public changesets'))
1619 raise error.Abort(_('cannot amend public changesets'))
1619 if len(repo[None].parents()) > 1:
1620 if len(repo[None].parents()) > 1:
1620 raise error.Abort(_('cannot amend while merging'))
1621 raise error.Abort(_('cannot amend while merging'))
1621 allowunstable = obsolete.isenabled(repo, obsolete.allowunstableopt)
1622 allowunstable = obsolete.isenabled(repo, obsolete.allowunstableopt)
1622 if not allowunstable and old.children():
1623 if not allowunstable and old.children():
1623 raise error.Abort(_('cannot amend changeset with children'))
1624 raise error.Abort(_('cannot amend changeset with children'))
1624
1625
1625 # Currently histedit gets confused if an amend happens while histedit
1626 # Currently histedit gets confused if an amend happens while histedit
1626 # is in progress. Since we have a checkunfinished command, we are
1627 # is in progress. Since we have a checkunfinished command, we are
1627 # temporarily honoring it.
1628 # temporarily honoring it.
1628 #
1629 #
1629 # Note: eventually this guard will be removed. Please do not expect
1630 # Note: eventually this guard will be removed. Please do not expect
1630 # this behavior to remain.
1631 # this behavior to remain.
1631 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
1632 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
1632 cmdutil.checkunfinished(repo)
1633 cmdutil.checkunfinished(repo)
1633
1634
1634 # commitfunc is used only for temporary amend commit by cmdutil.amend
1635 # commitfunc is used only for temporary amend commit by cmdutil.amend
1635 def commitfunc(ui, repo, message, match, opts):
1636 def commitfunc(ui, repo, message, match, opts):
1636 return repo.commit(message,
1637 return repo.commit(message,
1637 opts.get('user') or old.user(),
1638 opts.get('user') or old.user(),
1638 opts.get('date') or old.date(),
1639 opts.get('date') or old.date(),
1639 match,
1640 match,
1640 extra=extra)
1641 extra=extra)
1641
1642
1642 node = cmdutil.amend(ui, repo, commitfunc, old, extra, pats, opts)
1643 node = cmdutil.amend(ui, repo, commitfunc, old, extra, pats, opts)
1643 if node == old.node():
1644 if node == old.node():
1644 ui.status(_("nothing changed\n"))
1645 ui.status(_("nothing changed\n"))
1645 return 1
1646 return 1
1646 else:
1647 else:
1647 def commitfunc(ui, repo, message, match, opts):
1648 def commitfunc(ui, repo, message, match, opts):
1648 overrides = {}
1649 overrides = {}
1649 if opts.get('secret'):
1650 if opts.get('secret'):
1650 overrides[('phases', 'new-commit')] = 'secret'
1651 overrides[('phases', 'new-commit')] = 'secret'
1651
1652
1652 baseui = repo.baseui
1653 baseui = repo.baseui
1653 with baseui.configoverride(overrides, 'commit'):
1654 with baseui.configoverride(overrides, 'commit'):
1654 with ui.configoverride(overrides, 'commit'):
1655 with ui.configoverride(overrides, 'commit'):
1655 editform = cmdutil.mergeeditform(repo[None],
1656 editform = cmdutil.mergeeditform(repo[None],
1656 'commit.normal')
1657 'commit.normal')
1657 editor = cmdutil.getcommiteditor(
1658 editor = cmdutil.getcommiteditor(
1658 editform=editform, **pycompat.strkwargs(opts))
1659 editform=editform, **pycompat.strkwargs(opts))
1659 return repo.commit(message,
1660 return repo.commit(message,
1660 opts.get('user'),
1661 opts.get('user'),
1661 opts.get('date'),
1662 opts.get('date'),
1662 match,
1663 match,
1663 editor=editor,
1664 editor=editor,
1664 extra=extra)
1665 extra=extra)
1665
1666
1666 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
1667 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
1667
1668
1668 if not node:
1669 if not node:
1669 stat = cmdutil.postcommitstatus(repo, pats, opts)
1670 stat = cmdutil.postcommitstatus(repo, pats, opts)
1670 if stat[3]:
1671 if stat[3]:
1671 ui.status(_("nothing changed (%d missing files, see "
1672 ui.status(_("nothing changed (%d missing files, see "
1672 "'hg status')\n") % len(stat[3]))
1673 "'hg status')\n") % len(stat[3]))
1673 else:
1674 else:
1674 ui.status(_("nothing changed\n"))
1675 ui.status(_("nothing changed\n"))
1675 return 1
1676 return 1
1676
1677
1677 cmdutil.commitstatus(repo, node, branch, bheads, opts)
1678 cmdutil.commitstatus(repo, node, branch, bheads, opts)
1678
1679
1679 @command('config|showconfig|debugconfig',
1680 @command('config|showconfig|debugconfig',
1680 [('u', 'untrusted', None, _('show untrusted configuration options')),
1681 [('u', 'untrusted', None, _('show untrusted configuration options')),
1681 ('e', 'edit', None, _('edit user config')),
1682 ('e', 'edit', None, _('edit user config')),
1682 ('l', 'local', None, _('edit repository config')),
1683 ('l', 'local', None, _('edit repository config')),
1683 ('g', 'global', None, _('edit global config'))] + formatteropts,
1684 ('g', 'global', None, _('edit global config'))] + formatteropts,
1684 _('[-u] [NAME]...'),
1685 _('[-u] [NAME]...'),
1685 optionalrepo=True)
1686 optionalrepo=True)
1686 def config(ui, repo, *values, **opts):
1687 def config(ui, repo, *values, **opts):
1687 """show combined config settings from all hgrc files
1688 """show combined config settings from all hgrc files
1688
1689
1689 With no arguments, print names and values of all config items.
1690 With no arguments, print names and values of all config items.
1690
1691
1691 With one argument of the form section.name, print just the value
1692 With one argument of the form section.name, print just the value
1692 of that config item.
1693 of that config item.
1693
1694
1694 With multiple arguments, print names and values of all config
1695 With multiple arguments, print names and values of all config
1695 items with matching section names.
1696 items with matching section names.
1696
1697
1697 With --edit, start an editor on the user-level config file. With
1698 With --edit, start an editor on the user-level config file. With
1698 --global, edit the system-wide config file. With --local, edit the
1699 --global, edit the system-wide config file. With --local, edit the
1699 repository-level config file.
1700 repository-level config file.
1700
1701
1701 With --debug, the source (filename and line number) is printed
1702 With --debug, the source (filename and line number) is printed
1702 for each config item.
1703 for each config item.
1703
1704
1704 See :hg:`help config` for more information about config files.
1705 See :hg:`help config` for more information about config files.
1705
1706
1706 Returns 0 on success, 1 if NAME does not exist.
1707 Returns 0 on success, 1 if NAME does not exist.
1707
1708
1708 """
1709 """
1709
1710
1710 opts = pycompat.byteskwargs(opts)
1711 opts = pycompat.byteskwargs(opts)
1711 if opts.get('edit') or opts.get('local') or opts.get('global'):
1712 if opts.get('edit') or opts.get('local') or opts.get('global'):
1712 if opts.get('local') and opts.get('global'):
1713 if opts.get('local') and opts.get('global'):
1713 raise error.Abort(_("can't use --local and --global together"))
1714 raise error.Abort(_("can't use --local and --global together"))
1714
1715
1715 if opts.get('local'):
1716 if opts.get('local'):
1716 if not repo:
1717 if not repo:
1717 raise error.Abort(_("can't use --local outside a repository"))
1718 raise error.Abort(_("can't use --local outside a repository"))
1718 paths = [repo.vfs.join('hgrc')]
1719 paths = [repo.vfs.join('hgrc')]
1719 elif opts.get('global'):
1720 elif opts.get('global'):
1720 paths = rcutil.systemrcpath()
1721 paths = rcutil.systemrcpath()
1721 else:
1722 else:
1722 paths = rcutil.userrcpath()
1723 paths = rcutil.userrcpath()
1723
1724
1724 for f in paths:
1725 for f in paths:
1725 if os.path.exists(f):
1726 if os.path.exists(f):
1726 break
1727 break
1727 else:
1728 else:
1728 if opts.get('global'):
1729 if opts.get('global'):
1729 samplehgrc = uimod.samplehgrcs['global']
1730 samplehgrc = uimod.samplehgrcs['global']
1730 elif opts.get('local'):
1731 elif opts.get('local'):
1731 samplehgrc = uimod.samplehgrcs['local']
1732 samplehgrc = uimod.samplehgrcs['local']
1732 else:
1733 else:
1733 samplehgrc = uimod.samplehgrcs['user']
1734 samplehgrc = uimod.samplehgrcs['user']
1734
1735
1735 f = paths[0]
1736 f = paths[0]
1736 fp = open(f, "w")
1737 fp = open(f, "w")
1737 fp.write(samplehgrc)
1738 fp.write(samplehgrc)
1738 fp.close()
1739 fp.close()
1739
1740
1740 editor = ui.geteditor()
1741 editor = ui.geteditor()
1741 ui.system("%s \"%s\"" % (editor, f),
1742 ui.system("%s \"%s\"" % (editor, f),
1742 onerr=error.Abort, errprefix=_("edit failed"),
1743 onerr=error.Abort, errprefix=_("edit failed"),
1743 blockedtag='config_edit')
1744 blockedtag='config_edit')
1744 return
1745 return
1745 ui.pager('config')
1746 ui.pager('config')
1746 fm = ui.formatter('config', opts)
1747 fm = ui.formatter('config', opts)
1747 for t, f in rcutil.rccomponents():
1748 for t, f in rcutil.rccomponents():
1748 if t == 'path':
1749 if t == 'path':
1749 ui.debug('read config from: %s\n' % f)
1750 ui.debug('read config from: %s\n' % f)
1750 elif t == 'items':
1751 elif t == 'items':
1751 for section, name, value, source in f:
1752 for section, name, value, source in f:
1752 ui.debug('set config by: %s\n' % source)
1753 ui.debug('set config by: %s\n' % source)
1753 else:
1754 else:
1754 raise error.ProgrammingError('unknown rctype: %s' % t)
1755 raise error.ProgrammingError('unknown rctype: %s' % t)
1755 untrusted = bool(opts.get('untrusted'))
1756 untrusted = bool(opts.get('untrusted'))
1756 if values:
1757 if values:
1757 sections = [v for v in values if '.' not in v]
1758 sections = [v for v in values if '.' not in v]
1758 items = [v for v in values if '.' in v]
1759 items = [v for v in values if '.' in v]
1759 if len(items) > 1 or items and sections:
1760 if len(items) > 1 or items and sections:
1760 raise error.Abort(_('only one config item permitted'))
1761 raise error.Abort(_('only one config item permitted'))
1761 matched = False
1762 matched = False
1762 for section, name, value in ui.walkconfig(untrusted=untrusted):
1763 for section, name, value in ui.walkconfig(untrusted=untrusted):
1763 source = ui.configsource(section, name, untrusted)
1764 source = ui.configsource(section, name, untrusted)
1764 value = pycompat.bytestr(value)
1765 value = pycompat.bytestr(value)
1765 if fm.isplain():
1766 if fm.isplain():
1766 source = source or 'none'
1767 source = source or 'none'
1767 value = value.replace('\n', '\\n')
1768 value = value.replace('\n', '\\n')
1768 entryname = section + '.' + name
1769 entryname = section + '.' + name
1769 if values:
1770 if values:
1770 for v in values:
1771 for v in values:
1771 if v == section:
1772 if v == section:
1772 fm.startitem()
1773 fm.startitem()
1773 fm.condwrite(ui.debugflag, 'source', '%s: ', source)
1774 fm.condwrite(ui.debugflag, 'source', '%s: ', source)
1774 fm.write('name value', '%s=%s\n', entryname, value)
1775 fm.write('name value', '%s=%s\n', entryname, value)
1775 matched = True
1776 matched = True
1776 elif v == entryname:
1777 elif v == entryname:
1777 fm.startitem()
1778 fm.startitem()
1778 fm.condwrite(ui.debugflag, 'source', '%s: ', source)
1779 fm.condwrite(ui.debugflag, 'source', '%s: ', source)
1779 fm.write('value', '%s\n', value)
1780 fm.write('value', '%s\n', value)
1780 fm.data(name=entryname)
1781 fm.data(name=entryname)
1781 matched = True
1782 matched = True
1782 else:
1783 else:
1783 fm.startitem()
1784 fm.startitem()
1784 fm.condwrite(ui.debugflag, 'source', '%s: ', source)
1785 fm.condwrite(ui.debugflag, 'source', '%s: ', source)
1785 fm.write('name value', '%s=%s\n', entryname, value)
1786 fm.write('name value', '%s=%s\n', entryname, value)
1786 matched = True
1787 matched = True
1787 fm.end()
1788 fm.end()
1788 if matched:
1789 if matched:
1789 return 0
1790 return 0
1790 return 1
1791 return 1
1791
1792
1792 @command('copy|cp',
1793 @command('copy|cp',
1793 [('A', 'after', None, _('record a copy that has already occurred')),
1794 [('A', 'after', None, _('record a copy that has already occurred')),
1794 ('f', 'force', None, _('forcibly copy over an existing managed file')),
1795 ('f', 'force', None, _('forcibly copy over an existing managed file')),
1795 ] + walkopts + dryrunopts,
1796 ] + walkopts + dryrunopts,
1796 _('[OPTION]... [SOURCE]... DEST'))
1797 _('[OPTION]... [SOURCE]... DEST'))
1797 def copy(ui, repo, *pats, **opts):
1798 def copy(ui, repo, *pats, **opts):
1798 """mark files as copied for the next commit
1799 """mark files as copied for the next commit
1799
1800
1800 Mark dest as having copies of source files. If dest is a
1801 Mark dest as having copies of source files. If dest is a
1801 directory, copies are put in that directory. If dest is a file,
1802 directory, copies are put in that directory. If dest is a file,
1802 the source must be a single file.
1803 the source must be a single file.
1803
1804
1804 By default, this command copies the contents of files as they
1805 By default, this command copies the contents of files as they
1805 exist in the working directory. If invoked with -A/--after, the
1806 exist in the working directory. If invoked with -A/--after, the
1806 operation is recorded, but no copying is performed.
1807 operation is recorded, but no copying is performed.
1807
1808
1808 This command takes effect with the next commit. To undo a copy
1809 This command takes effect with the next commit. To undo a copy
1809 before that, see :hg:`revert`.
1810 before that, see :hg:`revert`.
1810
1811
1811 Returns 0 on success, 1 if errors are encountered.
1812 Returns 0 on success, 1 if errors are encountered.
1812 """
1813 """
1813 opts = pycompat.byteskwargs(opts)
1814 opts = pycompat.byteskwargs(opts)
1814 with repo.wlock(False):
1815 with repo.wlock(False):
1815 return cmdutil.copy(ui, repo, pats, opts)
1816 return cmdutil.copy(ui, repo, pats, opts)
1816
1817
1817 @command('debugcommands', [], _('[COMMAND]'), norepo=True)
1818 @command('debugcommands', [], _('[COMMAND]'), norepo=True)
1818 def debugcommands(ui, cmd='', *args):
1819 def debugcommands(ui, cmd='', *args):
1819 """list all available commands and options"""
1820 """list all available commands and options"""
1820 for cmd, vals in sorted(table.iteritems()):
1821 for cmd, vals in sorted(table.iteritems()):
1821 cmd = cmd.split('|')[0].strip('^')
1822 cmd = cmd.split('|')[0].strip('^')
1822 opts = ', '.join([i[1] for i in vals[1]])
1823 opts = ', '.join([i[1] for i in vals[1]])
1823 ui.write('%s: %s\n' % (cmd, opts))
1824 ui.write('%s: %s\n' % (cmd, opts))
1824
1825
1825 @command('debugcomplete',
1826 @command('debugcomplete',
1826 [('o', 'options', None, _('show the command options'))],
1827 [('o', 'options', None, _('show the command options'))],
1827 _('[-o] CMD'),
1828 _('[-o] CMD'),
1828 norepo=True)
1829 norepo=True)
1829 def debugcomplete(ui, cmd='', **opts):
1830 def debugcomplete(ui, cmd='', **opts):
1830 """returns the completion list associated with the given command"""
1831 """returns the completion list associated with the given command"""
1831
1832
1832 if opts.get('options'):
1833 if opts.get('options'):
1833 options = []
1834 options = []
1834 otables = [globalopts]
1835 otables = [globalopts]
1835 if cmd:
1836 if cmd:
1836 aliases, entry = cmdutil.findcmd(cmd, table, False)
1837 aliases, entry = cmdutil.findcmd(cmd, table, False)
1837 otables.append(entry[1])
1838 otables.append(entry[1])
1838 for t in otables:
1839 for t in otables:
1839 for o in t:
1840 for o in t:
1840 if "(DEPRECATED)" in o[3]:
1841 if "(DEPRECATED)" in o[3]:
1841 continue
1842 continue
1842 if o[0]:
1843 if o[0]:
1843 options.append('-%s' % o[0])
1844 options.append('-%s' % o[0])
1844 options.append('--%s' % o[1])
1845 options.append('--%s' % o[1])
1845 ui.write("%s\n" % "\n".join(options))
1846 ui.write("%s\n" % "\n".join(options))
1846 return
1847 return
1847
1848
1848 cmdlist, unused_allcmds = cmdutil.findpossible(cmd, table)
1849 cmdlist, unused_allcmds = cmdutil.findpossible(cmd, table)
1849 if ui.verbose:
1850 if ui.verbose:
1850 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
1851 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
1851 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
1852 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
1852
1853
1853 @command('^diff',
1854 @command('^diff',
1854 [('r', 'rev', [], _('revision'), _('REV')),
1855 [('r', 'rev', [], _('revision'), _('REV')),
1855 ('c', 'change', '', _('change made by revision'), _('REV'))
1856 ('c', 'change', '', _('change made by revision'), _('REV'))
1856 ] + diffopts + diffopts2 + walkopts + subrepoopts,
1857 ] + diffopts + diffopts2 + walkopts + subrepoopts,
1857 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'),
1858 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'),
1858 inferrepo=True)
1859 inferrepo=True)
1859 def diff(ui, repo, *pats, **opts):
1860 def diff(ui, repo, *pats, **opts):
1860 """diff repository (or selected files)
1861 """diff repository (or selected files)
1861
1862
1862 Show differences between revisions for the specified files.
1863 Show differences between revisions for the specified files.
1863
1864
1864 Differences between files are shown using the unified diff format.
1865 Differences between files are shown using the unified diff format.
1865
1866
1866 .. note::
1867 .. note::
1867
1868
1868 :hg:`diff` may generate unexpected results for merges, as it will
1869 :hg:`diff` may generate unexpected results for merges, as it will
1869 default to comparing against the working directory's first
1870 default to comparing against the working directory's first
1870 parent changeset if no revisions are specified.
1871 parent changeset if no revisions are specified.
1871
1872
1872 When two revision arguments are given, then changes are shown
1873 When two revision arguments are given, then changes are shown
1873 between those revisions. If only one revision is specified then
1874 between those revisions. If only one revision is specified then
1874 that revision is compared to the working directory, and, when no
1875 that revision is compared to the working directory, and, when no
1875 revisions are specified, the working directory files are compared
1876 revisions are specified, the working directory files are compared
1876 to its first parent.
1877 to its first parent.
1877
1878
1878 Alternatively you can specify -c/--change with a revision to see
1879 Alternatively you can specify -c/--change with a revision to see
1879 the changes in that changeset relative to its first parent.
1880 the changes in that changeset relative to its first parent.
1880
1881
1881 Without the -a/--text option, diff will avoid generating diffs of
1882 Without the -a/--text option, diff will avoid generating diffs of
1882 files it detects as binary. With -a, diff will generate a diff
1883 files it detects as binary. With -a, diff will generate a diff
1883 anyway, probably with undesirable results.
1884 anyway, probably with undesirable results.
1884
1885
1885 Use the -g/--git option to generate diffs in the git extended diff
1886 Use the -g/--git option to generate diffs in the git extended diff
1886 format. For more information, read :hg:`help diffs`.
1887 format. For more information, read :hg:`help diffs`.
1887
1888
1888 .. container:: verbose
1889 .. container:: verbose
1889
1890
1890 Examples:
1891 Examples:
1891
1892
1892 - compare a file in the current working directory to its parent::
1893 - compare a file in the current working directory to its parent::
1893
1894
1894 hg diff foo.c
1895 hg diff foo.c
1895
1896
1896 - compare two historical versions of a directory, with rename info::
1897 - compare two historical versions of a directory, with rename info::
1897
1898
1898 hg diff --git -r 1.0:1.2 lib/
1899 hg diff --git -r 1.0:1.2 lib/
1899
1900
1900 - get change stats relative to the last change on some date::
1901 - get change stats relative to the last change on some date::
1901
1902
1902 hg diff --stat -r "date('may 2')"
1903 hg diff --stat -r "date('may 2')"
1903
1904
1904 - diff all newly-added files that contain a keyword::
1905 - diff all newly-added files that contain a keyword::
1905
1906
1906 hg diff "set:added() and grep(GNU)"
1907 hg diff "set:added() and grep(GNU)"
1907
1908
1908 - compare a revision and its parents::
1909 - compare a revision and its parents::
1909
1910
1910 hg diff -c 9353 # compare against first parent
1911 hg diff -c 9353 # compare against first parent
1911 hg diff -r 9353^:9353 # same using revset syntax
1912 hg diff -r 9353^:9353 # same using revset syntax
1912 hg diff -r 9353^2:9353 # compare against the second parent
1913 hg diff -r 9353^2:9353 # compare against the second parent
1913
1914
1914 Returns 0 on success.
1915 Returns 0 on success.
1915 """
1916 """
1916
1917
1917 opts = pycompat.byteskwargs(opts)
1918 opts = pycompat.byteskwargs(opts)
1918 revs = opts.get('rev')
1919 revs = opts.get('rev')
1919 change = opts.get('change')
1920 change = opts.get('change')
1920 stat = opts.get('stat')
1921 stat = opts.get('stat')
1921 reverse = opts.get('reverse')
1922 reverse = opts.get('reverse')
1922
1923
1923 if revs and change:
1924 if revs and change:
1924 msg = _('cannot specify --rev and --change at the same time')
1925 msg = _('cannot specify --rev and --change at the same time')
1925 raise error.Abort(msg)
1926 raise error.Abort(msg)
1926 elif change:
1927 elif change:
1927 node2 = scmutil.revsingle(repo, change, None).node()
1928 node2 = scmutil.revsingle(repo, change, None).node()
1928 node1 = repo[node2].p1().node()
1929 node1 = repo[node2].p1().node()
1929 else:
1930 else:
1930 node1, node2 = scmutil.revpair(repo, revs)
1931 node1, node2 = scmutil.revpair(repo, revs)
1931
1932
1932 if reverse:
1933 if reverse:
1933 node1, node2 = node2, node1
1934 node1, node2 = node2, node1
1934
1935
1935 diffopts = patch.diffallopts(ui, opts)
1936 diffopts = patch.diffallopts(ui, opts)
1936 m = scmutil.match(repo[node2], pats, opts)
1937 m = scmutil.match(repo[node2], pats, opts)
1937 ui.pager('diff')
1938 ui.pager('diff')
1938 cmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
1939 cmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
1939 listsubrepos=opts.get('subrepos'),
1940 listsubrepos=opts.get('subrepos'),
1940 root=opts.get('root'))
1941 root=opts.get('root'))
1941
1942
1942 @command('^export',
1943 @command('^export',
1943 [('o', 'output', '',
1944 [('o', 'output', '',
1944 _('print output to file with formatted name'), _('FORMAT')),
1945 _('print output to file with formatted name'), _('FORMAT')),
1945 ('', 'switch-parent', None, _('diff against the second parent')),
1946 ('', 'switch-parent', None, _('diff against the second parent')),
1946 ('r', 'rev', [], _('revisions to export'), _('REV')),
1947 ('r', 'rev', [], _('revisions to export'), _('REV')),
1947 ] + diffopts,
1948 ] + diffopts,
1948 _('[OPTION]... [-o OUTFILESPEC] [-r] [REV]...'))
1949 _('[OPTION]... [-o OUTFILESPEC] [-r] [REV]...'))
1949 def export(ui, repo, *changesets, **opts):
1950 def export(ui, repo, *changesets, **opts):
1950 """dump the header and diffs for one or more changesets
1951 """dump the header and diffs for one or more changesets
1951
1952
1952 Print the changeset header and diffs for one or more revisions.
1953 Print the changeset header and diffs for one or more revisions.
1953 If no revision is given, the parent of the working directory is used.
1954 If no revision is given, the parent of the working directory is used.
1954
1955
1955 The information shown in the changeset header is: author, date,
1956 The information shown in the changeset header is: author, date,
1956 branch name (if non-default), changeset hash, parent(s) and commit
1957 branch name (if non-default), changeset hash, parent(s) and commit
1957 comment.
1958 comment.
1958
1959
1959 .. note::
1960 .. note::
1960
1961
1961 :hg:`export` may generate unexpected diff output for merge
1962 :hg:`export` may generate unexpected diff output for merge
1962 changesets, as it will compare the merge changeset against its
1963 changesets, as it will compare the merge changeset against its
1963 first parent only.
1964 first parent only.
1964
1965
1965 Output may be to a file, in which case the name of the file is
1966 Output may be to a file, in which case the name of the file is
1966 given using a format string. The formatting rules are as follows:
1967 given using a format string. The formatting rules are as follows:
1967
1968
1968 :``%%``: literal "%" character
1969 :``%%``: literal "%" character
1969 :``%H``: changeset hash (40 hexadecimal digits)
1970 :``%H``: changeset hash (40 hexadecimal digits)
1970 :``%N``: number of patches being generated
1971 :``%N``: number of patches being generated
1971 :``%R``: changeset revision number
1972 :``%R``: changeset revision number
1972 :``%b``: basename of the exporting repository
1973 :``%b``: basename of the exporting repository
1973 :``%h``: short-form changeset hash (12 hexadecimal digits)
1974 :``%h``: short-form changeset hash (12 hexadecimal digits)
1974 :``%m``: first line of the commit message (only alphanumeric characters)
1975 :``%m``: first line of the commit message (only alphanumeric characters)
1975 :``%n``: zero-padded sequence number, starting at 1
1976 :``%n``: zero-padded sequence number, starting at 1
1976 :``%r``: zero-padded changeset revision number
1977 :``%r``: zero-padded changeset revision number
1977
1978
1978 Without the -a/--text option, export will avoid generating diffs
1979 Without the -a/--text option, export will avoid generating diffs
1979 of files it detects as binary. With -a, export will generate a
1980 of files it detects as binary. With -a, export will generate a
1980 diff anyway, probably with undesirable results.
1981 diff anyway, probably with undesirable results.
1981
1982
1982 Use the -g/--git option to generate diffs in the git extended diff
1983 Use the -g/--git option to generate diffs in the git extended diff
1983 format. See :hg:`help diffs` for more information.
1984 format. See :hg:`help diffs` for more information.
1984
1985
1985 With the --switch-parent option, the diff will be against the
1986 With the --switch-parent option, the diff will be against the
1986 second parent. It can be useful to review a merge.
1987 second parent. It can be useful to review a merge.
1987
1988
1988 .. container:: verbose
1989 .. container:: verbose
1989
1990
1990 Examples:
1991 Examples:
1991
1992
1992 - use export and import to transplant a bugfix to the current
1993 - use export and import to transplant a bugfix to the current
1993 branch::
1994 branch::
1994
1995
1995 hg export -r 9353 | hg import -
1996 hg export -r 9353 | hg import -
1996
1997
1997 - export all the changesets between two revisions to a file with
1998 - export all the changesets between two revisions to a file with
1998 rename information::
1999 rename information::
1999
2000
2000 hg export --git -r 123:150 > changes.txt
2001 hg export --git -r 123:150 > changes.txt
2001
2002
2002 - split outgoing changes into a series of patches with
2003 - split outgoing changes into a series of patches with
2003 descriptive names::
2004 descriptive names::
2004
2005
2005 hg export -r "outgoing()" -o "%n-%m.patch"
2006 hg export -r "outgoing()" -o "%n-%m.patch"
2006
2007
2007 Returns 0 on success.
2008 Returns 0 on success.
2008 """
2009 """
2009 opts = pycompat.byteskwargs(opts)
2010 opts = pycompat.byteskwargs(opts)
2010 changesets += tuple(opts.get('rev', []))
2011 changesets += tuple(opts.get('rev', []))
2011 if not changesets:
2012 if not changesets:
2012 changesets = ['.']
2013 changesets = ['.']
2013 revs = scmutil.revrange(repo, changesets)
2014 revs = scmutil.revrange(repo, changesets)
2014 if not revs:
2015 if not revs:
2015 raise error.Abort(_("export requires at least one changeset"))
2016 raise error.Abort(_("export requires at least one changeset"))
2016 if len(revs) > 1:
2017 if len(revs) > 1:
2017 ui.note(_('exporting patches:\n'))
2018 ui.note(_('exporting patches:\n'))
2018 else:
2019 else:
2019 ui.note(_('exporting patch:\n'))
2020 ui.note(_('exporting patch:\n'))
2020 ui.pager('export')
2021 ui.pager('export')
2021 cmdutil.export(repo, revs, fntemplate=opts.get('output'),
2022 cmdutil.export(repo, revs, fntemplate=opts.get('output'),
2022 switch_parent=opts.get('switch_parent'),
2023 switch_parent=opts.get('switch_parent'),
2023 opts=patch.diffallopts(ui, opts))
2024 opts=patch.diffallopts(ui, opts))
2024
2025
2025 @command('files',
2026 @command('files',
2026 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
2027 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
2027 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
2028 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
2028 ] + walkopts + formatteropts + subrepoopts,
2029 ] + walkopts + formatteropts + subrepoopts,
2029 _('[OPTION]... [FILE]...'))
2030 _('[OPTION]... [FILE]...'))
2030 def files(ui, repo, *pats, **opts):
2031 def files(ui, repo, *pats, **opts):
2031 """list tracked files
2032 """list tracked files
2032
2033
2033 Print files under Mercurial control in the working directory or
2034 Print files under Mercurial control in the working directory or
2034 specified revision for given files (excluding removed files).
2035 specified revision for given files (excluding removed files).
2035 Files can be specified as filenames or filesets.
2036 Files can be specified as filenames or filesets.
2036
2037
2037 If no files are given to match, this command prints the names
2038 If no files are given to match, this command prints the names
2038 of all files under Mercurial control.
2039 of all files under Mercurial control.
2039
2040
2040 .. container:: verbose
2041 .. container:: verbose
2041
2042
2042 Examples:
2043 Examples:
2043
2044
2044 - list all files under the current directory::
2045 - list all files under the current directory::
2045
2046
2046 hg files .
2047 hg files .
2047
2048
2048 - shows sizes and flags for current revision::
2049 - shows sizes and flags for current revision::
2049
2050
2050 hg files -vr .
2051 hg files -vr .
2051
2052
2052 - list all files named README::
2053 - list all files named README::
2053
2054
2054 hg files -I "**/README"
2055 hg files -I "**/README"
2055
2056
2056 - list all binary files::
2057 - list all binary files::
2057
2058
2058 hg files "set:binary()"
2059 hg files "set:binary()"
2059
2060
2060 - find files containing a regular expression::
2061 - find files containing a regular expression::
2061
2062
2062 hg files "set:grep('bob')"
2063 hg files "set:grep('bob')"
2063
2064
2064 - search tracked file contents with xargs and grep::
2065 - search tracked file contents with xargs and grep::
2065
2066
2066 hg files -0 | xargs -0 grep foo
2067 hg files -0 | xargs -0 grep foo
2067
2068
2068 See :hg:`help patterns` and :hg:`help filesets` for more information
2069 See :hg:`help patterns` and :hg:`help filesets` for more information
2069 on specifying file patterns.
2070 on specifying file patterns.
2070
2071
2071 Returns 0 if a match is found, 1 otherwise.
2072 Returns 0 if a match is found, 1 otherwise.
2072
2073
2073 """
2074 """
2074
2075
2075 opts = pycompat.byteskwargs(opts)
2076 opts = pycompat.byteskwargs(opts)
2076 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
2077 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
2077
2078
2078 end = '\n'
2079 end = '\n'
2079 if opts.get('print0'):
2080 if opts.get('print0'):
2080 end = '\0'
2081 end = '\0'
2081 fmt = '%s' + end
2082 fmt = '%s' + end
2082
2083
2083 m = scmutil.match(ctx, pats, opts)
2084 m = scmutil.match(ctx, pats, opts)
2084 ui.pager('files')
2085 ui.pager('files')
2085 with ui.formatter('files', opts) as fm:
2086 with ui.formatter('files', opts) as fm:
2086 return cmdutil.files(ui, ctx, m, fm, fmt, opts.get('subrepos'))
2087 return cmdutil.files(ui, ctx, m, fm, fmt, opts.get('subrepos'))
2087
2088
2088 @command('^forget', walkopts, _('[OPTION]... FILE...'), inferrepo=True)
2089 @command('^forget', walkopts, _('[OPTION]... FILE...'), inferrepo=True)
2089 def forget(ui, repo, *pats, **opts):
2090 def forget(ui, repo, *pats, **opts):
2090 """forget the specified files on the next commit
2091 """forget the specified files on the next commit
2091
2092
2092 Mark the specified files so they will no longer be tracked
2093 Mark the specified files so they will no longer be tracked
2093 after the next commit.
2094 after the next commit.
2094
2095
2095 This only removes files from the current branch, not from the
2096 This only removes files from the current branch, not from the
2096 entire project history, and it does not delete them from the
2097 entire project history, and it does not delete them from the
2097 working directory.
2098 working directory.
2098
2099
2099 To delete the file from the working directory, see :hg:`remove`.
2100 To delete the file from the working directory, see :hg:`remove`.
2100
2101
2101 To undo a forget before the next commit, see :hg:`add`.
2102 To undo a forget before the next commit, see :hg:`add`.
2102
2103
2103 .. container:: verbose
2104 .. container:: verbose
2104
2105
2105 Examples:
2106 Examples:
2106
2107
2107 - forget newly-added binary files::
2108 - forget newly-added binary files::
2108
2109
2109 hg forget "set:added() and binary()"
2110 hg forget "set:added() and binary()"
2110
2111
2111 - forget files that would be excluded by .hgignore::
2112 - forget files that would be excluded by .hgignore::
2112
2113
2113 hg forget "set:hgignore()"
2114 hg forget "set:hgignore()"
2114
2115
2115 Returns 0 on success.
2116 Returns 0 on success.
2116 """
2117 """
2117
2118
2118 opts = pycompat.byteskwargs(opts)
2119 opts = pycompat.byteskwargs(opts)
2119 if not pats:
2120 if not pats:
2120 raise error.Abort(_('no files specified'))
2121 raise error.Abort(_('no files specified'))
2121
2122
2122 m = scmutil.match(repo[None], pats, opts)
2123 m = scmutil.match(repo[None], pats, opts)
2123 rejected = cmdutil.forget(ui, repo, m, prefix="", explicitonly=False)[0]
2124 rejected = cmdutil.forget(ui, repo, m, prefix="", explicitonly=False)[0]
2124 return rejected and 1 or 0
2125 return rejected and 1 or 0
2125
2126
2126 @command(
2127 @command(
2127 'graft',
2128 'graft',
2128 [('r', 'rev', [], _('revisions to graft'), _('REV')),
2129 [('r', 'rev', [], _('revisions to graft'), _('REV')),
2129 ('c', 'continue', False, _('resume interrupted graft')),
2130 ('c', 'continue', False, _('resume interrupted graft')),
2130 ('e', 'edit', False, _('invoke editor on commit messages')),
2131 ('e', 'edit', False, _('invoke editor on commit messages')),
2131 ('', 'log', None, _('append graft info to log message')),
2132 ('', 'log', None, _('append graft info to log message')),
2132 ('f', 'force', False, _('force graft')),
2133 ('f', 'force', False, _('force graft')),
2133 ('D', 'currentdate', False,
2134 ('D', 'currentdate', False,
2134 _('record the current date as commit date')),
2135 _('record the current date as commit date')),
2135 ('U', 'currentuser', False,
2136 ('U', 'currentuser', False,
2136 _('record the current user as committer'), _('DATE'))]
2137 _('record the current user as committer'), _('DATE'))]
2137 + commitopts2 + mergetoolopts + dryrunopts,
2138 + commitopts2 + mergetoolopts + dryrunopts,
2138 _('[OPTION]... [-r REV]... REV...'))
2139 _('[OPTION]... [-r REV]... REV...'))
2139 def graft(ui, repo, *revs, **opts):
2140 def graft(ui, repo, *revs, **opts):
2140 '''copy changes from other branches onto the current branch
2141 '''copy changes from other branches onto the current branch
2141
2142
2142 This command uses Mercurial's merge logic to copy individual
2143 This command uses Mercurial's merge logic to copy individual
2143 changes from other branches without merging branches in the
2144 changes from other branches without merging branches in the
2144 history graph. This is sometimes known as 'backporting' or
2145 history graph. This is sometimes known as 'backporting' or
2145 'cherry-picking'. By default, graft will copy user, date, and
2146 'cherry-picking'. By default, graft will copy user, date, and
2146 description from the source changesets.
2147 description from the source changesets.
2147
2148
2148 Changesets that are ancestors of the current revision, that have
2149 Changesets that are ancestors of the current revision, that have
2149 already been grafted, or that are merges will be skipped.
2150 already been grafted, or that are merges will be skipped.
2150
2151
2151 If --log is specified, log messages will have a comment appended
2152 If --log is specified, log messages will have a comment appended
2152 of the form::
2153 of the form::
2153
2154
2154 (grafted from CHANGESETHASH)
2155 (grafted from CHANGESETHASH)
2155
2156
2156 If --force is specified, revisions will be grafted even if they
2157 If --force is specified, revisions will be grafted even if they
2157 are already ancestors of or have been grafted to the destination.
2158 are already ancestors of or have been grafted to the destination.
2158 This is useful when the revisions have since been backed out.
2159 This is useful when the revisions have since been backed out.
2159
2160
2160 If a graft merge results in conflicts, the graft process is
2161 If a graft merge results in conflicts, the graft process is
2161 interrupted so that the current merge can be manually resolved.
2162 interrupted so that the current merge can be manually resolved.
2162 Once all conflicts are addressed, the graft process can be
2163 Once all conflicts are addressed, the graft process can be
2163 continued with the -c/--continue option.
2164 continued with the -c/--continue option.
2164
2165
2165 .. note::
2166 .. note::
2166
2167
2167 The -c/--continue option does not reapply earlier options, except
2168 The -c/--continue option does not reapply earlier options, except
2168 for --force.
2169 for --force.
2169
2170
2170 .. container:: verbose
2171 .. container:: verbose
2171
2172
2172 Examples:
2173 Examples:
2173
2174
2174 - copy a single change to the stable branch and edit its description::
2175 - copy a single change to the stable branch and edit its description::
2175
2176
2176 hg update stable
2177 hg update stable
2177 hg graft --edit 9393
2178 hg graft --edit 9393
2178
2179
2179 - graft a range of changesets with one exception, updating dates::
2180 - graft a range of changesets with one exception, updating dates::
2180
2181
2181 hg graft -D "2085::2093 and not 2091"
2182 hg graft -D "2085::2093 and not 2091"
2182
2183
2183 - continue a graft after resolving conflicts::
2184 - continue a graft after resolving conflicts::
2184
2185
2185 hg graft -c
2186 hg graft -c
2186
2187
2187 - show the source of a grafted changeset::
2188 - show the source of a grafted changeset::
2188
2189
2189 hg log --debug -r .
2190 hg log --debug -r .
2190
2191
2191 - show revisions sorted by date::
2192 - show revisions sorted by date::
2192
2193
2193 hg log -r "sort(all(), date)"
2194 hg log -r "sort(all(), date)"
2194
2195
2195 See :hg:`help revisions` for more about specifying revisions.
2196 See :hg:`help revisions` for more about specifying revisions.
2196
2197
2197 Returns 0 on successful completion.
2198 Returns 0 on successful completion.
2198 '''
2199 '''
2199 with repo.wlock():
2200 with repo.wlock():
2200 return _dograft(ui, repo, *revs, **opts)
2201 return _dograft(ui, repo, *revs, **opts)
2201
2202
2202 def _dograft(ui, repo, *revs, **opts):
2203 def _dograft(ui, repo, *revs, **opts):
2203 opts = pycompat.byteskwargs(opts)
2204 opts = pycompat.byteskwargs(opts)
2204 if revs and opts.get('rev'):
2205 if revs and opts.get('rev'):
2205 ui.warn(_('warning: inconsistent use of --rev might give unexpected '
2206 ui.warn(_('warning: inconsistent use of --rev might give unexpected '
2206 'revision ordering!\n'))
2207 'revision ordering!\n'))
2207
2208
2208 revs = list(revs)
2209 revs = list(revs)
2209 revs.extend(opts.get('rev'))
2210 revs.extend(opts.get('rev'))
2210
2211
2211 if not opts.get('user') and opts.get('currentuser'):
2212 if not opts.get('user') and opts.get('currentuser'):
2212 opts['user'] = ui.username()
2213 opts['user'] = ui.username()
2213 if not opts.get('date') and opts.get('currentdate'):
2214 if not opts.get('date') and opts.get('currentdate'):
2214 opts['date'] = "%d %d" % util.makedate()
2215 opts['date'] = "%d %d" % util.makedate()
2215
2216
2216 editor = cmdutil.getcommiteditor(editform='graft',
2217 editor = cmdutil.getcommiteditor(editform='graft',
2217 **pycompat.strkwargs(opts))
2218 **pycompat.strkwargs(opts))
2218
2219
2219 cont = False
2220 cont = False
2220 if opts.get('continue'):
2221 if opts.get('continue'):
2221 cont = True
2222 cont = True
2222 if revs:
2223 if revs:
2223 raise error.Abort(_("can't specify --continue and revisions"))
2224 raise error.Abort(_("can't specify --continue and revisions"))
2224 # read in unfinished revisions
2225 # read in unfinished revisions
2225 try:
2226 try:
2226 nodes = repo.vfs.read('graftstate').splitlines()
2227 nodes = repo.vfs.read('graftstate').splitlines()
2227 revs = [repo[node].rev() for node in nodes]
2228 revs = [repo[node].rev() for node in nodes]
2228 except IOError as inst:
2229 except IOError as inst:
2229 if inst.errno != errno.ENOENT:
2230 if inst.errno != errno.ENOENT:
2230 raise
2231 raise
2231 cmdutil.wrongtooltocontinue(repo, _('graft'))
2232 cmdutil.wrongtooltocontinue(repo, _('graft'))
2232 else:
2233 else:
2233 cmdutil.checkunfinished(repo)
2234 cmdutil.checkunfinished(repo)
2234 cmdutil.bailifchanged(repo)
2235 cmdutil.bailifchanged(repo)
2235 if not revs:
2236 if not revs:
2236 raise error.Abort(_('no revisions specified'))
2237 raise error.Abort(_('no revisions specified'))
2237 revs = scmutil.revrange(repo, revs)
2238 revs = scmutil.revrange(repo, revs)
2238
2239
2239 skipped = set()
2240 skipped = set()
2240 # check for merges
2241 # check for merges
2241 for rev in repo.revs('%ld and merge()', revs):
2242 for rev in repo.revs('%ld and merge()', revs):
2242 ui.warn(_('skipping ungraftable merge revision %s\n') % rev)
2243 ui.warn(_('skipping ungraftable merge revision %s\n') % rev)
2243 skipped.add(rev)
2244 skipped.add(rev)
2244 revs = [r for r in revs if r not in skipped]
2245 revs = [r for r in revs if r not in skipped]
2245 if not revs:
2246 if not revs:
2246 return -1
2247 return -1
2247
2248
2248 # Don't check in the --continue case, in effect retaining --force across
2249 # Don't check in the --continue case, in effect retaining --force across
2249 # --continues. That's because without --force, any revisions we decided to
2250 # --continues. That's because without --force, any revisions we decided to
2250 # skip would have been filtered out here, so they wouldn't have made their
2251 # skip would have been filtered out here, so they wouldn't have made their
2251 # way to the graftstate. With --force, any revisions we would have otherwise
2252 # way to the graftstate. With --force, any revisions we would have otherwise
2252 # skipped would not have been filtered out, and if they hadn't been applied
2253 # skipped would not have been filtered out, and if they hadn't been applied
2253 # already, they'd have been in the graftstate.
2254 # already, they'd have been in the graftstate.
2254 if not (cont or opts.get('force')):
2255 if not (cont or opts.get('force')):
2255 # check for ancestors of dest branch
2256 # check for ancestors of dest branch
2256 crev = repo['.'].rev()
2257 crev = repo['.'].rev()
2257 ancestors = repo.changelog.ancestors([crev], inclusive=True)
2258 ancestors = repo.changelog.ancestors([crev], inclusive=True)
2258 # XXX make this lazy in the future
2259 # XXX make this lazy in the future
2259 # don't mutate while iterating, create a copy
2260 # don't mutate while iterating, create a copy
2260 for rev in list(revs):
2261 for rev in list(revs):
2261 if rev in ancestors:
2262 if rev in ancestors:
2262 ui.warn(_('skipping ancestor revision %d:%s\n') %
2263 ui.warn(_('skipping ancestor revision %d:%s\n') %
2263 (rev, repo[rev]))
2264 (rev, repo[rev]))
2264 # XXX remove on list is slow
2265 # XXX remove on list is slow
2265 revs.remove(rev)
2266 revs.remove(rev)
2266 if not revs:
2267 if not revs:
2267 return -1
2268 return -1
2268
2269
2269 # analyze revs for earlier grafts
2270 # analyze revs for earlier grafts
2270 ids = {}
2271 ids = {}
2271 for ctx in repo.set("%ld", revs):
2272 for ctx in repo.set("%ld", revs):
2272 ids[ctx.hex()] = ctx.rev()
2273 ids[ctx.hex()] = ctx.rev()
2273 n = ctx.extra().get('source')
2274 n = ctx.extra().get('source')
2274 if n:
2275 if n:
2275 ids[n] = ctx.rev()
2276 ids[n] = ctx.rev()
2276
2277
2277 # check ancestors for earlier grafts
2278 # check ancestors for earlier grafts
2278 ui.debug('scanning for duplicate grafts\n')
2279 ui.debug('scanning for duplicate grafts\n')
2279
2280
2280 # The only changesets we can be sure doesn't contain grafts of any
2281 # The only changesets we can be sure doesn't contain grafts of any
2281 # revs, are the ones that are common ancestors of *all* revs:
2282 # revs, are the ones that are common ancestors of *all* revs:
2282 for rev in repo.revs('only(%d,ancestor(%ld))', crev, revs):
2283 for rev in repo.revs('only(%d,ancestor(%ld))', crev, revs):
2283 ctx = repo[rev]
2284 ctx = repo[rev]
2284 n = ctx.extra().get('source')
2285 n = ctx.extra().get('source')
2285 if n in ids:
2286 if n in ids:
2286 try:
2287 try:
2287 r = repo[n].rev()
2288 r = repo[n].rev()
2288 except error.RepoLookupError:
2289 except error.RepoLookupError:
2289 r = None
2290 r = None
2290 if r in revs:
2291 if r in revs:
2291 ui.warn(_('skipping revision %d:%s '
2292 ui.warn(_('skipping revision %d:%s '
2292 '(already grafted to %d:%s)\n')
2293 '(already grafted to %d:%s)\n')
2293 % (r, repo[r], rev, ctx))
2294 % (r, repo[r], rev, ctx))
2294 revs.remove(r)
2295 revs.remove(r)
2295 elif ids[n] in revs:
2296 elif ids[n] in revs:
2296 if r is None:
2297 if r is None:
2297 ui.warn(_('skipping already grafted revision %d:%s '
2298 ui.warn(_('skipping already grafted revision %d:%s '
2298 '(%d:%s also has unknown origin %s)\n')
2299 '(%d:%s also has unknown origin %s)\n')
2299 % (ids[n], repo[ids[n]], rev, ctx, n[:12]))
2300 % (ids[n], repo[ids[n]], rev, ctx, n[:12]))
2300 else:
2301 else:
2301 ui.warn(_('skipping already grafted revision %d:%s '
2302 ui.warn(_('skipping already grafted revision %d:%s '
2302 '(%d:%s also has origin %d:%s)\n')
2303 '(%d:%s also has origin %d:%s)\n')
2303 % (ids[n], repo[ids[n]], rev, ctx, r, n[:12]))
2304 % (ids[n], repo[ids[n]], rev, ctx, r, n[:12]))
2304 revs.remove(ids[n])
2305 revs.remove(ids[n])
2305 elif ctx.hex() in ids:
2306 elif ctx.hex() in ids:
2306 r = ids[ctx.hex()]
2307 r = ids[ctx.hex()]
2307 ui.warn(_('skipping already grafted revision %d:%s '
2308 ui.warn(_('skipping already grafted revision %d:%s '
2308 '(was grafted from %d:%s)\n') %
2309 '(was grafted from %d:%s)\n') %
2309 (r, repo[r], rev, ctx))
2310 (r, repo[r], rev, ctx))
2310 revs.remove(r)
2311 revs.remove(r)
2311 if not revs:
2312 if not revs:
2312 return -1
2313 return -1
2313
2314
2314 for pos, ctx in enumerate(repo.set("%ld", revs)):
2315 for pos, ctx in enumerate(repo.set("%ld", revs)):
2315 desc = '%d:%s "%s"' % (ctx.rev(), ctx,
2316 desc = '%d:%s "%s"' % (ctx.rev(), ctx,
2316 ctx.description().split('\n', 1)[0])
2317 ctx.description().split('\n', 1)[0])
2317 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
2318 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
2318 if names:
2319 if names:
2319 desc += ' (%s)' % ' '.join(names)
2320 desc += ' (%s)' % ' '.join(names)
2320 ui.status(_('grafting %s\n') % desc)
2321 ui.status(_('grafting %s\n') % desc)
2321 if opts.get('dry_run'):
2322 if opts.get('dry_run'):
2322 continue
2323 continue
2323
2324
2324 source = ctx.extra().get('source')
2325 source = ctx.extra().get('source')
2325 extra = {}
2326 extra = {}
2326 if source:
2327 if source:
2327 extra['source'] = source
2328 extra['source'] = source
2328 extra['intermediate-source'] = ctx.hex()
2329 extra['intermediate-source'] = ctx.hex()
2329 else:
2330 else:
2330 extra['source'] = ctx.hex()
2331 extra['source'] = ctx.hex()
2331 user = ctx.user()
2332 user = ctx.user()
2332 if opts.get('user'):
2333 if opts.get('user'):
2333 user = opts['user']
2334 user = opts['user']
2334 date = ctx.date()
2335 date = ctx.date()
2335 if opts.get('date'):
2336 if opts.get('date'):
2336 date = opts['date']
2337 date = opts['date']
2337 message = ctx.description()
2338 message = ctx.description()
2338 if opts.get('log'):
2339 if opts.get('log'):
2339 message += '\n(grafted from %s)' % ctx.hex()
2340 message += '\n(grafted from %s)' % ctx.hex()
2340
2341
2341 # we don't merge the first commit when continuing
2342 # we don't merge the first commit when continuing
2342 if not cont:
2343 if not cont:
2343 # perform the graft merge with p1(rev) as 'ancestor'
2344 # perform the graft merge with p1(rev) as 'ancestor'
2344 try:
2345 try:
2345 # ui.forcemerge is an internal variable, do not document
2346 # ui.forcemerge is an internal variable, do not document
2346 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
2347 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
2347 'graft')
2348 'graft')
2348 stats = mergemod.graft(repo, ctx, ctx.p1(),
2349 stats = mergemod.graft(repo, ctx, ctx.p1(),
2349 ['local', 'graft'])
2350 ['local', 'graft'])
2350 finally:
2351 finally:
2351 repo.ui.setconfig('ui', 'forcemerge', '', 'graft')
2352 repo.ui.setconfig('ui', 'forcemerge', '', 'graft')
2352 # report any conflicts
2353 # report any conflicts
2353 if stats and stats[3] > 0:
2354 if stats and stats[3] > 0:
2354 # write out state for --continue
2355 # write out state for --continue
2355 nodelines = [repo[rev].hex() + "\n" for rev in revs[pos:]]
2356 nodelines = [repo[rev].hex() + "\n" for rev in revs[pos:]]
2356 repo.vfs.write('graftstate', ''.join(nodelines))
2357 repo.vfs.write('graftstate', ''.join(nodelines))
2357 extra = ''
2358 extra = ''
2358 if opts.get('user'):
2359 if opts.get('user'):
2359 extra += ' --user %s' % util.shellquote(opts['user'])
2360 extra += ' --user %s' % util.shellquote(opts['user'])
2360 if opts.get('date'):
2361 if opts.get('date'):
2361 extra += ' --date %s' % util.shellquote(opts['date'])
2362 extra += ' --date %s' % util.shellquote(opts['date'])
2362 if opts.get('log'):
2363 if opts.get('log'):
2363 extra += ' --log'
2364 extra += ' --log'
2364 hint=_("use 'hg resolve' and 'hg graft --continue%s'") % extra
2365 hint=_("use 'hg resolve' and 'hg graft --continue%s'") % extra
2365 raise error.Abort(
2366 raise error.Abort(
2366 _("unresolved conflicts, can't continue"),
2367 _("unresolved conflicts, can't continue"),
2367 hint=hint)
2368 hint=hint)
2368 else:
2369 else:
2369 cont = False
2370 cont = False
2370
2371
2371 # commit
2372 # commit
2372 node = repo.commit(text=message, user=user,
2373 node = repo.commit(text=message, user=user,
2373 date=date, extra=extra, editor=editor)
2374 date=date, extra=extra, editor=editor)
2374 if node is None:
2375 if node is None:
2375 ui.warn(
2376 ui.warn(
2376 _('note: graft of %d:%s created no changes to commit\n') %
2377 _('note: graft of %d:%s created no changes to commit\n') %
2377 (ctx.rev(), ctx))
2378 (ctx.rev(), ctx))
2378
2379
2379 # remove state when we complete successfully
2380 # remove state when we complete successfully
2380 if not opts.get('dry_run'):
2381 if not opts.get('dry_run'):
2381 repo.vfs.unlinkpath('graftstate', ignoremissing=True)
2382 repo.vfs.unlinkpath('graftstate', ignoremissing=True)
2382
2383
2383 return 0
2384 return 0
2384
2385
2385 @command('grep',
2386 @command('grep',
2386 [('0', 'print0', None, _('end fields with NUL')),
2387 [('0', 'print0', None, _('end fields with NUL')),
2387 ('', 'all', None, _('print all revisions that match')),
2388 ('', 'all', None, _('print all revisions that match')),
2388 ('a', 'text', None, _('treat all files as text')),
2389 ('a', 'text', None, _('treat all files as text')),
2389 ('f', 'follow', None,
2390 ('f', 'follow', None,
2390 _('follow changeset history,'
2391 _('follow changeset history,'
2391 ' or file history across copies and renames')),
2392 ' or file history across copies and renames')),
2392 ('i', 'ignore-case', None, _('ignore case when matching')),
2393 ('i', 'ignore-case', None, _('ignore case when matching')),
2393 ('l', 'files-with-matches', None,
2394 ('l', 'files-with-matches', None,
2394 _('print only filenames and revisions that match')),
2395 _('print only filenames and revisions that match')),
2395 ('n', 'line-number', None, _('print matching line numbers')),
2396 ('n', 'line-number', None, _('print matching line numbers')),
2396 ('r', 'rev', [],
2397 ('r', 'rev', [],
2397 _('only search files changed within revision range'), _('REV')),
2398 _('only search files changed within revision range'), _('REV')),
2398 ('u', 'user', None, _('list the author (long with -v)')),
2399 ('u', 'user', None, _('list the author (long with -v)')),
2399 ('d', 'date', None, _('list the date (short with -q)')),
2400 ('d', 'date', None, _('list the date (short with -q)')),
2400 ] + formatteropts + walkopts,
2401 ] + formatteropts + walkopts,
2401 _('[OPTION]... PATTERN [FILE]...'),
2402 _('[OPTION]... PATTERN [FILE]...'),
2402 inferrepo=True)
2403 inferrepo=True)
2403 def grep(ui, repo, pattern, *pats, **opts):
2404 def grep(ui, repo, pattern, *pats, **opts):
2404 """search revision history for a pattern in specified files
2405 """search revision history for a pattern in specified files
2405
2406
2406 Search revision history for a regular expression in the specified
2407 Search revision history for a regular expression in the specified
2407 files or the entire project.
2408 files or the entire project.
2408
2409
2409 By default, grep prints the most recent revision number for each
2410 By default, grep prints the most recent revision number for each
2410 file in which it finds a match. To get it to print every revision
2411 file in which it finds a match. To get it to print every revision
2411 that contains a change in match status ("-" for a match that becomes
2412 that contains a change in match status ("-" for a match that becomes
2412 a non-match, or "+" for a non-match that becomes a match), use the
2413 a non-match, or "+" for a non-match that becomes a match), use the
2413 --all flag.
2414 --all flag.
2414
2415
2415 PATTERN can be any Python (roughly Perl-compatible) regular
2416 PATTERN can be any Python (roughly Perl-compatible) regular
2416 expression.
2417 expression.
2417
2418
2418 If no FILEs are specified (and -f/--follow isn't set), all files in
2419 If no FILEs are specified (and -f/--follow isn't set), all files in
2419 the repository are searched, including those that don't exist in the
2420 the repository are searched, including those that don't exist in the
2420 current branch or have been deleted in a prior changeset.
2421 current branch or have been deleted in a prior changeset.
2421
2422
2422 Returns 0 if a match is found, 1 otherwise.
2423 Returns 0 if a match is found, 1 otherwise.
2423 """
2424 """
2424 opts = pycompat.byteskwargs(opts)
2425 opts = pycompat.byteskwargs(opts)
2425 reflags = re.M
2426 reflags = re.M
2426 if opts.get('ignore_case'):
2427 if opts.get('ignore_case'):
2427 reflags |= re.I
2428 reflags |= re.I
2428 try:
2429 try:
2429 regexp = util.re.compile(pattern, reflags)
2430 regexp = util.re.compile(pattern, reflags)
2430 except re.error as inst:
2431 except re.error as inst:
2431 ui.warn(_("grep: invalid match pattern: %s\n") % inst)
2432 ui.warn(_("grep: invalid match pattern: %s\n") % inst)
2432 return 1
2433 return 1
2433 sep, eol = ':', '\n'
2434 sep, eol = ':', '\n'
2434 if opts.get('print0'):
2435 if opts.get('print0'):
2435 sep = eol = '\0'
2436 sep = eol = '\0'
2436
2437
2437 getfile = util.lrucachefunc(repo.file)
2438 getfile = util.lrucachefunc(repo.file)
2438
2439
2439 def matchlines(body):
2440 def matchlines(body):
2440 begin = 0
2441 begin = 0
2441 linenum = 0
2442 linenum = 0
2442 while begin < len(body):
2443 while begin < len(body):
2443 match = regexp.search(body, begin)
2444 match = regexp.search(body, begin)
2444 if not match:
2445 if not match:
2445 break
2446 break
2446 mstart, mend = match.span()
2447 mstart, mend = match.span()
2447 linenum += body.count('\n', begin, mstart) + 1
2448 linenum += body.count('\n', begin, mstart) + 1
2448 lstart = body.rfind('\n', begin, mstart) + 1 or begin
2449 lstart = body.rfind('\n', begin, mstart) + 1 or begin
2449 begin = body.find('\n', mend) + 1 or len(body) + 1
2450 begin = body.find('\n', mend) + 1 or len(body) + 1
2450 lend = begin - 1
2451 lend = begin - 1
2451 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
2452 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
2452
2453
2453 class linestate(object):
2454 class linestate(object):
2454 def __init__(self, line, linenum, colstart, colend):
2455 def __init__(self, line, linenum, colstart, colend):
2455 self.line = line
2456 self.line = line
2456 self.linenum = linenum
2457 self.linenum = linenum
2457 self.colstart = colstart
2458 self.colstart = colstart
2458 self.colend = colend
2459 self.colend = colend
2459
2460
2460 def __hash__(self):
2461 def __hash__(self):
2461 return hash((self.linenum, self.line))
2462 return hash((self.linenum, self.line))
2462
2463
2463 def __eq__(self, other):
2464 def __eq__(self, other):
2464 return self.line == other.line
2465 return self.line == other.line
2465
2466
2466 def findpos(self):
2467 def findpos(self):
2467 """Iterate all (start, end) indices of matches"""
2468 """Iterate all (start, end) indices of matches"""
2468 yield self.colstart, self.colend
2469 yield self.colstart, self.colend
2469 p = self.colend
2470 p = self.colend
2470 while p < len(self.line):
2471 while p < len(self.line):
2471 m = regexp.search(self.line, p)
2472 m = regexp.search(self.line, p)
2472 if not m:
2473 if not m:
2473 break
2474 break
2474 yield m.span()
2475 yield m.span()
2475 p = m.end()
2476 p = m.end()
2476
2477
2477 matches = {}
2478 matches = {}
2478 copies = {}
2479 copies = {}
2479 def grepbody(fn, rev, body):
2480 def grepbody(fn, rev, body):
2480 matches[rev].setdefault(fn, [])
2481 matches[rev].setdefault(fn, [])
2481 m = matches[rev][fn]
2482 m = matches[rev][fn]
2482 for lnum, cstart, cend, line in matchlines(body):
2483 for lnum, cstart, cend, line in matchlines(body):
2483 s = linestate(line, lnum, cstart, cend)
2484 s = linestate(line, lnum, cstart, cend)
2484 m.append(s)
2485 m.append(s)
2485
2486
2486 def difflinestates(a, b):
2487 def difflinestates(a, b):
2487 sm = difflib.SequenceMatcher(None, a, b)
2488 sm = difflib.SequenceMatcher(None, a, b)
2488 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
2489 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
2489 if tag == 'insert':
2490 if tag == 'insert':
2490 for i in xrange(blo, bhi):
2491 for i in xrange(blo, bhi):
2491 yield ('+', b[i])
2492 yield ('+', b[i])
2492 elif tag == 'delete':
2493 elif tag == 'delete':
2493 for i in xrange(alo, ahi):
2494 for i in xrange(alo, ahi):
2494 yield ('-', a[i])
2495 yield ('-', a[i])
2495 elif tag == 'replace':
2496 elif tag == 'replace':
2496 for i in xrange(alo, ahi):
2497 for i in xrange(alo, ahi):
2497 yield ('-', a[i])
2498 yield ('-', a[i])
2498 for i in xrange(blo, bhi):
2499 for i in xrange(blo, bhi):
2499 yield ('+', b[i])
2500 yield ('+', b[i])
2500
2501
2501 def display(fm, fn, ctx, pstates, states):
2502 def display(fm, fn, ctx, pstates, states):
2502 rev = ctx.rev()
2503 rev = ctx.rev()
2503 if fm.isplain():
2504 if fm.isplain():
2504 formatuser = ui.shortuser
2505 formatuser = ui.shortuser
2505 else:
2506 else:
2506 formatuser = str
2507 formatuser = str
2507 if ui.quiet:
2508 if ui.quiet:
2508 datefmt = '%Y-%m-%d'
2509 datefmt = '%Y-%m-%d'
2509 else:
2510 else:
2510 datefmt = '%a %b %d %H:%M:%S %Y %1%2'
2511 datefmt = '%a %b %d %H:%M:%S %Y %1%2'
2511 found = False
2512 found = False
2512 @util.cachefunc
2513 @util.cachefunc
2513 def binary():
2514 def binary():
2514 flog = getfile(fn)
2515 flog = getfile(fn)
2515 return util.binary(flog.read(ctx.filenode(fn)))
2516 return util.binary(flog.read(ctx.filenode(fn)))
2516
2517
2517 fieldnamemap = {'filename': 'file', 'linenumber': 'line_number'}
2518 fieldnamemap = {'filename': 'file', 'linenumber': 'line_number'}
2518 if opts.get('all'):
2519 if opts.get('all'):
2519 iter = difflinestates(pstates, states)
2520 iter = difflinestates(pstates, states)
2520 else:
2521 else:
2521 iter = [('', l) for l in states]
2522 iter = [('', l) for l in states]
2522 for change, l in iter:
2523 for change, l in iter:
2523 fm.startitem()
2524 fm.startitem()
2524 fm.data(node=fm.hexfunc(ctx.node()))
2525 fm.data(node=fm.hexfunc(ctx.node()))
2525 cols = [
2526 cols = [
2526 ('filename', fn, True),
2527 ('filename', fn, True),
2527 ('rev', rev, True),
2528 ('rev', rev, True),
2528 ('linenumber', l.linenum, opts.get('line_number')),
2529 ('linenumber', l.linenum, opts.get('line_number')),
2529 ]
2530 ]
2530 if opts.get('all'):
2531 if opts.get('all'):
2531 cols.append(('change', change, True))
2532 cols.append(('change', change, True))
2532 cols.extend([
2533 cols.extend([
2533 ('user', formatuser(ctx.user()), opts.get('user')),
2534 ('user', formatuser(ctx.user()), opts.get('user')),
2534 ('date', fm.formatdate(ctx.date(), datefmt), opts.get('date')),
2535 ('date', fm.formatdate(ctx.date(), datefmt), opts.get('date')),
2535 ])
2536 ])
2536 lastcol = next(name for name, data, cond in reversed(cols) if cond)
2537 lastcol = next(name for name, data, cond in reversed(cols) if cond)
2537 for name, data, cond in cols:
2538 for name, data, cond in cols:
2538 field = fieldnamemap.get(name, name)
2539 field = fieldnamemap.get(name, name)
2539 fm.condwrite(cond, field, '%s', data, label='grep.%s' % name)
2540 fm.condwrite(cond, field, '%s', data, label='grep.%s' % name)
2540 if cond and name != lastcol:
2541 if cond and name != lastcol:
2541 fm.plain(sep, label='grep.sep')
2542 fm.plain(sep, label='grep.sep')
2542 if not opts.get('files_with_matches'):
2543 if not opts.get('files_with_matches'):
2543 fm.plain(sep, label='grep.sep')
2544 fm.plain(sep, label='grep.sep')
2544 if not opts.get('text') and binary():
2545 if not opts.get('text') and binary():
2545 fm.plain(_(" Binary file matches"))
2546 fm.plain(_(" Binary file matches"))
2546 else:
2547 else:
2547 displaymatches(fm.nested('texts'), l)
2548 displaymatches(fm.nested('texts'), l)
2548 fm.plain(eol)
2549 fm.plain(eol)
2549 found = True
2550 found = True
2550 if opts.get('files_with_matches'):
2551 if opts.get('files_with_matches'):
2551 break
2552 break
2552 return found
2553 return found
2553
2554
2554 def displaymatches(fm, l):
2555 def displaymatches(fm, l):
2555 p = 0
2556 p = 0
2556 for s, e in l.findpos():
2557 for s, e in l.findpos():
2557 if p < s:
2558 if p < s:
2558 fm.startitem()
2559 fm.startitem()
2559 fm.write('text', '%s', l.line[p:s])
2560 fm.write('text', '%s', l.line[p:s])
2560 fm.data(matched=False)
2561 fm.data(matched=False)
2561 fm.startitem()
2562 fm.startitem()
2562 fm.write('text', '%s', l.line[s:e], label='grep.match')
2563 fm.write('text', '%s', l.line[s:e], label='grep.match')
2563 fm.data(matched=True)
2564 fm.data(matched=True)
2564 p = e
2565 p = e
2565 if p < len(l.line):
2566 if p < len(l.line):
2566 fm.startitem()
2567 fm.startitem()
2567 fm.write('text', '%s', l.line[p:])
2568 fm.write('text', '%s', l.line[p:])
2568 fm.data(matched=False)
2569 fm.data(matched=False)
2569 fm.end()
2570 fm.end()
2570
2571
2571 skip = {}
2572 skip = {}
2572 revfiles = {}
2573 revfiles = {}
2573 matchfn = scmutil.match(repo[None], pats, opts)
2574 matchfn = scmutil.match(repo[None], pats, opts)
2574 found = False
2575 found = False
2575 follow = opts.get('follow')
2576 follow = opts.get('follow')
2576
2577
2577 def prep(ctx, fns):
2578 def prep(ctx, fns):
2578 rev = ctx.rev()
2579 rev = ctx.rev()
2579 pctx = ctx.p1()
2580 pctx = ctx.p1()
2580 parent = pctx.rev()
2581 parent = pctx.rev()
2581 matches.setdefault(rev, {})
2582 matches.setdefault(rev, {})
2582 matches.setdefault(parent, {})
2583 matches.setdefault(parent, {})
2583 files = revfiles.setdefault(rev, [])
2584 files = revfiles.setdefault(rev, [])
2584 for fn in fns:
2585 for fn in fns:
2585 flog = getfile(fn)
2586 flog = getfile(fn)
2586 try:
2587 try:
2587 fnode = ctx.filenode(fn)
2588 fnode = ctx.filenode(fn)
2588 except error.LookupError:
2589 except error.LookupError:
2589 continue
2590 continue
2590
2591
2591 copied = flog.renamed(fnode)
2592 copied = flog.renamed(fnode)
2592 copy = follow and copied and copied[0]
2593 copy = follow and copied and copied[0]
2593 if copy:
2594 if copy:
2594 copies.setdefault(rev, {})[fn] = copy
2595 copies.setdefault(rev, {})[fn] = copy
2595 if fn in skip:
2596 if fn in skip:
2596 if copy:
2597 if copy:
2597 skip[copy] = True
2598 skip[copy] = True
2598 continue
2599 continue
2599 files.append(fn)
2600 files.append(fn)
2600
2601
2601 if fn not in matches[rev]:
2602 if fn not in matches[rev]:
2602 grepbody(fn, rev, flog.read(fnode))
2603 grepbody(fn, rev, flog.read(fnode))
2603
2604
2604 pfn = copy or fn
2605 pfn = copy or fn
2605 if pfn not in matches[parent]:
2606 if pfn not in matches[parent]:
2606 try:
2607 try:
2607 fnode = pctx.filenode(pfn)
2608 fnode = pctx.filenode(pfn)
2608 grepbody(pfn, parent, flog.read(fnode))
2609 grepbody(pfn, parent, flog.read(fnode))
2609 except error.LookupError:
2610 except error.LookupError:
2610 pass
2611 pass
2611
2612
2612 ui.pager('grep')
2613 ui.pager('grep')
2613 fm = ui.formatter('grep', opts)
2614 fm = ui.formatter('grep', opts)
2614 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
2615 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
2615 rev = ctx.rev()
2616 rev = ctx.rev()
2616 parent = ctx.p1().rev()
2617 parent = ctx.p1().rev()
2617 for fn in sorted(revfiles.get(rev, [])):
2618 for fn in sorted(revfiles.get(rev, [])):
2618 states = matches[rev][fn]
2619 states = matches[rev][fn]
2619 copy = copies.get(rev, {}).get(fn)
2620 copy = copies.get(rev, {}).get(fn)
2620 if fn in skip:
2621 if fn in skip:
2621 if copy:
2622 if copy:
2622 skip[copy] = True
2623 skip[copy] = True
2623 continue
2624 continue
2624 pstates = matches.get(parent, {}).get(copy or fn, [])
2625 pstates = matches.get(parent, {}).get(copy or fn, [])
2625 if pstates or states:
2626 if pstates or states:
2626 r = display(fm, fn, ctx, pstates, states)
2627 r = display(fm, fn, ctx, pstates, states)
2627 found = found or r
2628 found = found or r
2628 if r and not opts.get('all'):
2629 if r and not opts.get('all'):
2629 skip[fn] = True
2630 skip[fn] = True
2630 if copy:
2631 if copy:
2631 skip[copy] = True
2632 skip[copy] = True
2632 del matches[rev]
2633 del matches[rev]
2633 del revfiles[rev]
2634 del revfiles[rev]
2634 fm.end()
2635 fm.end()
2635
2636
2636 return not found
2637 return not found
2637
2638
2638 @command('heads',
2639 @command('heads',
2639 [('r', 'rev', '',
2640 [('r', 'rev', '',
2640 _('show only heads which are descendants of STARTREV'), _('STARTREV')),
2641 _('show only heads which are descendants of STARTREV'), _('STARTREV')),
2641 ('t', 'topo', False, _('show topological heads only')),
2642 ('t', 'topo', False, _('show topological heads only')),
2642 ('a', 'active', False, _('show active branchheads only (DEPRECATED)')),
2643 ('a', 'active', False, _('show active branchheads only (DEPRECATED)')),
2643 ('c', 'closed', False, _('show normal and closed branch heads')),
2644 ('c', 'closed', False, _('show normal and closed branch heads')),
2644 ] + templateopts,
2645 ] + templateopts,
2645 _('[-ct] [-r STARTREV] [REV]...'))
2646 _('[-ct] [-r STARTREV] [REV]...'))
2646 def heads(ui, repo, *branchrevs, **opts):
2647 def heads(ui, repo, *branchrevs, **opts):
2647 """show branch heads
2648 """show branch heads
2648
2649
2649 With no arguments, show all open branch heads in the repository.
2650 With no arguments, show all open branch heads in the repository.
2650 Branch heads are changesets that have no descendants on the
2651 Branch heads are changesets that have no descendants on the
2651 same branch. They are where development generally takes place and
2652 same branch. They are where development generally takes place and
2652 are the usual targets for update and merge operations.
2653 are the usual targets for update and merge operations.
2653
2654
2654 If one or more REVs are given, only open branch heads on the
2655 If one or more REVs are given, only open branch heads on the
2655 branches associated with the specified changesets are shown. This
2656 branches associated with the specified changesets are shown. This
2656 means that you can use :hg:`heads .` to see the heads on the
2657 means that you can use :hg:`heads .` to see the heads on the
2657 currently checked-out branch.
2658 currently checked-out branch.
2658
2659
2659 If -c/--closed is specified, also show branch heads marked closed
2660 If -c/--closed is specified, also show branch heads marked closed
2660 (see :hg:`commit --close-branch`).
2661 (see :hg:`commit --close-branch`).
2661
2662
2662 If STARTREV is specified, only those heads that are descendants of
2663 If STARTREV is specified, only those heads that are descendants of
2663 STARTREV will be displayed.
2664 STARTREV will be displayed.
2664
2665
2665 If -t/--topo is specified, named branch mechanics will be ignored and only
2666 If -t/--topo is specified, named branch mechanics will be ignored and only
2666 topological heads (changesets with no children) will be shown.
2667 topological heads (changesets with no children) will be shown.
2667
2668
2668 Returns 0 if matching heads are found, 1 if not.
2669 Returns 0 if matching heads are found, 1 if not.
2669 """
2670 """
2670
2671
2671 opts = pycompat.byteskwargs(opts)
2672 opts = pycompat.byteskwargs(opts)
2672 start = None
2673 start = None
2673 if 'rev' in opts:
2674 if 'rev' in opts:
2674 start = scmutil.revsingle(repo, opts['rev'], None).node()
2675 start = scmutil.revsingle(repo, opts['rev'], None).node()
2675
2676
2676 if opts.get('topo'):
2677 if opts.get('topo'):
2677 heads = [repo[h] for h in repo.heads(start)]
2678 heads = [repo[h] for h in repo.heads(start)]
2678 else:
2679 else:
2679 heads = []
2680 heads = []
2680 for branch in repo.branchmap():
2681 for branch in repo.branchmap():
2681 heads += repo.branchheads(branch, start, opts.get('closed'))
2682 heads += repo.branchheads(branch, start, opts.get('closed'))
2682 heads = [repo[h] for h in heads]
2683 heads = [repo[h] for h in heads]
2683
2684
2684 if branchrevs:
2685 if branchrevs:
2685 branches = set(repo[br].branch() for br in branchrevs)
2686 branches = set(repo[br].branch() for br in branchrevs)
2686 heads = [h for h in heads if h.branch() in branches]
2687 heads = [h for h in heads if h.branch() in branches]
2687
2688
2688 if opts.get('active') and branchrevs:
2689 if opts.get('active') and branchrevs:
2689 dagheads = repo.heads(start)
2690 dagheads = repo.heads(start)
2690 heads = [h for h in heads if h.node() in dagheads]
2691 heads = [h for h in heads if h.node() in dagheads]
2691
2692
2692 if branchrevs:
2693 if branchrevs:
2693 haveheads = set(h.branch() for h in heads)
2694 haveheads = set(h.branch() for h in heads)
2694 if branches - haveheads:
2695 if branches - haveheads:
2695 headless = ', '.join(b for b in branches - haveheads)
2696 headless = ', '.join(b for b in branches - haveheads)
2696 msg = _('no open branch heads found on branches %s')
2697 msg = _('no open branch heads found on branches %s')
2697 if opts.get('rev'):
2698 if opts.get('rev'):
2698 msg += _(' (started at %s)') % opts['rev']
2699 msg += _(' (started at %s)') % opts['rev']
2699 ui.warn((msg + '\n') % headless)
2700 ui.warn((msg + '\n') % headless)
2700
2701
2701 if not heads:
2702 if not heads:
2702 return 1
2703 return 1
2703
2704
2704 ui.pager('heads')
2705 ui.pager('heads')
2705 heads = sorted(heads, key=lambda x: -x.rev())
2706 heads = sorted(heads, key=lambda x: -x.rev())
2706 displayer = cmdutil.show_changeset(ui, repo, opts)
2707 displayer = cmdutil.show_changeset(ui, repo, opts)
2707 for ctx in heads:
2708 for ctx in heads:
2708 displayer.show(ctx)
2709 displayer.show(ctx)
2709 displayer.close()
2710 displayer.close()
2710
2711
2711 @command('help',
2712 @command('help',
2712 [('e', 'extension', None, _('show only help for extensions')),
2713 [('e', 'extension', None, _('show only help for extensions')),
2713 ('c', 'command', None, _('show only help for commands')),
2714 ('c', 'command', None, _('show only help for commands')),
2714 ('k', 'keyword', None, _('show topics matching keyword')),
2715 ('k', 'keyword', None, _('show topics matching keyword')),
2715 ('s', 'system', [], _('show help for specific platform(s)')),
2716 ('s', 'system', [], _('show help for specific platform(s)')),
2716 ],
2717 ],
2717 _('[-ecks] [TOPIC]'),
2718 _('[-ecks] [TOPIC]'),
2718 norepo=True)
2719 norepo=True)
2719 def help_(ui, name=None, **opts):
2720 def help_(ui, name=None, **opts):
2720 """show help for a given topic or a help overview
2721 """show help for a given topic or a help overview
2721
2722
2722 With no arguments, print a list of commands with short help messages.
2723 With no arguments, print a list of commands with short help messages.
2723
2724
2724 Given a topic, extension, or command name, print help for that
2725 Given a topic, extension, or command name, print help for that
2725 topic.
2726 topic.
2726
2727
2727 Returns 0 if successful.
2728 Returns 0 if successful.
2728 """
2729 """
2729
2730
2730 keep = opts.get(r'system') or []
2731 keep = opts.get(r'system') or []
2731 if len(keep) == 0:
2732 if len(keep) == 0:
2732 if pycompat.sysplatform.startswith('win'):
2733 if pycompat.sysplatform.startswith('win'):
2733 keep.append('windows')
2734 keep.append('windows')
2734 elif pycompat.sysplatform == 'OpenVMS':
2735 elif pycompat.sysplatform == 'OpenVMS':
2735 keep.append('vms')
2736 keep.append('vms')
2736 elif pycompat.sysplatform == 'plan9':
2737 elif pycompat.sysplatform == 'plan9':
2737 keep.append('plan9')
2738 keep.append('plan9')
2738 else:
2739 else:
2739 keep.append('unix')
2740 keep.append('unix')
2740 keep.append(pycompat.sysplatform.lower())
2741 keep.append(pycompat.sysplatform.lower())
2741 if ui.verbose:
2742 if ui.verbose:
2742 keep.append('verbose')
2743 keep.append('verbose')
2743
2744
2744 formatted = help.formattedhelp(ui, name, keep=keep, **opts)
2745 formatted = help.formattedhelp(ui, name, keep=keep, **opts)
2745 ui.pager('help')
2746 ui.pager('help')
2746 ui.write(formatted)
2747 ui.write(formatted)
2747
2748
2748
2749
2749 @command('identify|id',
2750 @command('identify|id',
2750 [('r', 'rev', '',
2751 [('r', 'rev', '',
2751 _('identify the specified revision'), _('REV')),
2752 _('identify the specified revision'), _('REV')),
2752 ('n', 'num', None, _('show local revision number')),
2753 ('n', 'num', None, _('show local revision number')),
2753 ('i', 'id', None, _('show global revision id')),
2754 ('i', 'id', None, _('show global revision id')),
2754 ('b', 'branch', None, _('show branch')),
2755 ('b', 'branch', None, _('show branch')),
2755 ('t', 'tags', None, _('show tags')),
2756 ('t', 'tags', None, _('show tags')),
2756 ('B', 'bookmarks', None, _('show bookmarks')),
2757 ('B', 'bookmarks', None, _('show bookmarks')),
2757 ] + remoteopts,
2758 ] + remoteopts,
2758 _('[-nibtB] [-r REV] [SOURCE]'),
2759 _('[-nibtB] [-r REV] [SOURCE]'),
2759 optionalrepo=True)
2760 optionalrepo=True)
2760 def identify(ui, repo, source=None, rev=None,
2761 def identify(ui, repo, source=None, rev=None,
2761 num=None, id=None, branch=None, tags=None, bookmarks=None, **opts):
2762 num=None, id=None, branch=None, tags=None, bookmarks=None, **opts):
2762 """identify the working directory or specified revision
2763 """identify the working directory or specified revision
2763
2764
2764 Print a summary identifying the repository state at REV using one or
2765 Print a summary identifying the repository state at REV using one or
2765 two parent hash identifiers, followed by a "+" if the working
2766 two parent hash identifiers, followed by a "+" if the working
2766 directory has uncommitted changes, the branch name (if not default),
2767 directory has uncommitted changes, the branch name (if not default),
2767 a list of tags, and a list of bookmarks.
2768 a list of tags, and a list of bookmarks.
2768
2769
2769 When REV is not given, print a summary of the current state of the
2770 When REV is not given, print a summary of the current state of the
2770 repository.
2771 repository.
2771
2772
2772 Specifying a path to a repository root or Mercurial bundle will
2773 Specifying a path to a repository root or Mercurial bundle will
2773 cause lookup to operate on that repository/bundle.
2774 cause lookup to operate on that repository/bundle.
2774
2775
2775 .. container:: verbose
2776 .. container:: verbose
2776
2777
2777 Examples:
2778 Examples:
2778
2779
2779 - generate a build identifier for the working directory::
2780 - generate a build identifier for the working directory::
2780
2781
2781 hg id --id > build-id.dat
2782 hg id --id > build-id.dat
2782
2783
2783 - find the revision corresponding to a tag::
2784 - find the revision corresponding to a tag::
2784
2785
2785 hg id -n -r 1.3
2786 hg id -n -r 1.3
2786
2787
2787 - check the most recent revision of a remote repository::
2788 - check the most recent revision of a remote repository::
2788
2789
2789 hg id -r tip https://www.mercurial-scm.org/repo/hg/
2790 hg id -r tip https://www.mercurial-scm.org/repo/hg/
2790
2791
2791 See :hg:`log` for generating more information about specific revisions,
2792 See :hg:`log` for generating more information about specific revisions,
2792 including full hash identifiers.
2793 including full hash identifiers.
2793
2794
2794 Returns 0 if successful.
2795 Returns 0 if successful.
2795 """
2796 """
2796
2797
2797 opts = pycompat.byteskwargs(opts)
2798 opts = pycompat.byteskwargs(opts)
2798 if not repo and not source:
2799 if not repo and not source:
2799 raise error.Abort(_("there is no Mercurial repository here "
2800 raise error.Abort(_("there is no Mercurial repository here "
2800 "(.hg not found)"))
2801 "(.hg not found)"))
2801
2802
2802 if ui.debugflag:
2803 if ui.debugflag:
2803 hexfunc = hex
2804 hexfunc = hex
2804 else:
2805 else:
2805 hexfunc = short
2806 hexfunc = short
2806 default = not (num or id or branch or tags or bookmarks)
2807 default = not (num or id or branch or tags or bookmarks)
2807 output = []
2808 output = []
2808 revs = []
2809 revs = []
2809
2810
2810 if source:
2811 if source:
2811 source, branches = hg.parseurl(ui.expandpath(source))
2812 source, branches = hg.parseurl(ui.expandpath(source))
2812 peer = hg.peer(repo or ui, opts, source) # only pass ui when no repo
2813 peer = hg.peer(repo or ui, opts, source) # only pass ui when no repo
2813 repo = peer.local()
2814 repo = peer.local()
2814 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
2815 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
2815
2816
2816 if not repo:
2817 if not repo:
2817 if num or branch or tags:
2818 if num or branch or tags:
2818 raise error.Abort(
2819 raise error.Abort(
2819 _("can't query remote revision number, branch, or tags"))
2820 _("can't query remote revision number, branch, or tags"))
2820 if not rev and revs:
2821 if not rev and revs:
2821 rev = revs[0]
2822 rev = revs[0]
2822 if not rev:
2823 if not rev:
2823 rev = "tip"
2824 rev = "tip"
2824
2825
2825 remoterev = peer.lookup(rev)
2826 remoterev = peer.lookup(rev)
2826 if default or id:
2827 if default or id:
2827 output = [hexfunc(remoterev)]
2828 output = [hexfunc(remoterev)]
2828
2829
2829 def getbms():
2830 def getbms():
2830 bms = []
2831 bms = []
2831
2832
2832 if 'bookmarks' in peer.listkeys('namespaces'):
2833 if 'bookmarks' in peer.listkeys('namespaces'):
2833 hexremoterev = hex(remoterev)
2834 hexremoterev = hex(remoterev)
2834 bms = [bm for bm, bmr in peer.listkeys('bookmarks').iteritems()
2835 bms = [bm for bm, bmr in peer.listkeys('bookmarks').iteritems()
2835 if bmr == hexremoterev]
2836 if bmr == hexremoterev]
2836
2837
2837 return sorted(bms)
2838 return sorted(bms)
2838
2839
2839 if bookmarks:
2840 if bookmarks:
2840 output.extend(getbms())
2841 output.extend(getbms())
2841 elif default and not ui.quiet:
2842 elif default and not ui.quiet:
2842 # multiple bookmarks for a single parent separated by '/'
2843 # multiple bookmarks for a single parent separated by '/'
2843 bm = '/'.join(getbms())
2844 bm = '/'.join(getbms())
2844 if bm:
2845 if bm:
2845 output.append(bm)
2846 output.append(bm)
2846 else:
2847 else:
2847 ctx = scmutil.revsingle(repo, rev, None)
2848 ctx = scmutil.revsingle(repo, rev, None)
2848
2849
2849 if ctx.rev() is None:
2850 if ctx.rev() is None:
2850 ctx = repo[None]
2851 ctx = repo[None]
2851 parents = ctx.parents()
2852 parents = ctx.parents()
2852 taglist = []
2853 taglist = []
2853 for p in parents:
2854 for p in parents:
2854 taglist.extend(p.tags())
2855 taglist.extend(p.tags())
2855
2856
2856 changed = ""
2857 changed = ""
2857 if default or id or num:
2858 if default or id or num:
2858 if (any(repo.status())
2859 if (any(repo.status())
2859 or any(ctx.sub(s).dirty() for s in ctx.substate)):
2860 or any(ctx.sub(s).dirty() for s in ctx.substate)):
2860 changed = '+'
2861 changed = '+'
2861 if default or id:
2862 if default or id:
2862 output = ["%s%s" %
2863 output = ["%s%s" %
2863 ('+'.join([hexfunc(p.node()) for p in parents]), changed)]
2864 ('+'.join([hexfunc(p.node()) for p in parents]), changed)]
2864 if num:
2865 if num:
2865 output.append("%s%s" %
2866 output.append("%s%s" %
2866 ('+'.join([str(p.rev()) for p in parents]), changed))
2867 ('+'.join([str(p.rev()) for p in parents]), changed))
2867 else:
2868 else:
2868 if default or id:
2869 if default or id:
2869 output = [hexfunc(ctx.node())]
2870 output = [hexfunc(ctx.node())]
2870 if num:
2871 if num:
2871 output.append(str(ctx.rev()))
2872 output.append(str(ctx.rev()))
2872 taglist = ctx.tags()
2873 taglist = ctx.tags()
2873
2874
2874 if default and not ui.quiet:
2875 if default and not ui.quiet:
2875 b = ctx.branch()
2876 b = ctx.branch()
2876 if b != 'default':
2877 if b != 'default':
2877 output.append("(%s)" % b)
2878 output.append("(%s)" % b)
2878
2879
2879 # multiple tags for a single parent separated by '/'
2880 # multiple tags for a single parent separated by '/'
2880 t = '/'.join(taglist)
2881 t = '/'.join(taglist)
2881 if t:
2882 if t:
2882 output.append(t)
2883 output.append(t)
2883
2884
2884 # multiple bookmarks for a single parent separated by '/'
2885 # multiple bookmarks for a single parent separated by '/'
2885 bm = '/'.join(ctx.bookmarks())
2886 bm = '/'.join(ctx.bookmarks())
2886 if bm:
2887 if bm:
2887 output.append(bm)
2888 output.append(bm)
2888 else:
2889 else:
2889 if branch:
2890 if branch:
2890 output.append(ctx.branch())
2891 output.append(ctx.branch())
2891
2892
2892 if tags:
2893 if tags:
2893 output.extend(taglist)
2894 output.extend(taglist)
2894
2895
2895 if bookmarks:
2896 if bookmarks:
2896 output.extend(ctx.bookmarks())
2897 output.extend(ctx.bookmarks())
2897
2898
2898 ui.write("%s\n" % ' '.join(output))
2899 ui.write("%s\n" % ' '.join(output))
2899
2900
2900 @command('import|patch',
2901 @command('import|patch',
2901 [('p', 'strip', 1,
2902 [('p', 'strip', 1,
2902 _('directory strip option for patch. This has the same '
2903 _('directory strip option for patch. This has the same '
2903 'meaning as the corresponding patch option'), _('NUM')),
2904 'meaning as the corresponding patch option'), _('NUM')),
2904 ('b', 'base', '', _('base path (DEPRECATED)'), _('PATH')),
2905 ('b', 'base', '', _('base path (DEPRECATED)'), _('PATH')),
2905 ('e', 'edit', False, _('invoke editor on commit messages')),
2906 ('e', 'edit', False, _('invoke editor on commit messages')),
2906 ('f', 'force', None,
2907 ('f', 'force', None,
2907 _('skip check for outstanding uncommitted changes (DEPRECATED)')),
2908 _('skip check for outstanding uncommitted changes (DEPRECATED)')),
2908 ('', 'no-commit', None,
2909 ('', 'no-commit', None,
2909 _("don't commit, just update the working directory")),
2910 _("don't commit, just update the working directory")),
2910 ('', 'bypass', None,
2911 ('', 'bypass', None,
2911 _("apply patch without touching the working directory")),
2912 _("apply patch without touching the working directory")),
2912 ('', 'partial', None,
2913 ('', 'partial', None,
2913 _('commit even if some hunks fail')),
2914 _('commit even if some hunks fail')),
2914 ('', 'exact', None,
2915 ('', 'exact', None,
2915 _('abort if patch would apply lossily')),
2916 _('abort if patch would apply lossily')),
2916 ('', 'prefix', '',
2917 ('', 'prefix', '',
2917 _('apply patch to subdirectory'), _('DIR')),
2918 _('apply patch to subdirectory'), _('DIR')),
2918 ('', 'import-branch', None,
2919 ('', 'import-branch', None,
2919 _('use any branch information in patch (implied by --exact)'))] +
2920 _('use any branch information in patch (implied by --exact)'))] +
2920 commitopts + commitopts2 + similarityopts,
2921 commitopts + commitopts2 + similarityopts,
2921 _('[OPTION]... PATCH...'))
2922 _('[OPTION]... PATCH...'))
2922 def import_(ui, repo, patch1=None, *patches, **opts):
2923 def import_(ui, repo, patch1=None, *patches, **opts):
2923 """import an ordered set of patches
2924 """import an ordered set of patches
2924
2925
2925 Import a list of patches and commit them individually (unless
2926 Import a list of patches and commit them individually (unless
2926 --no-commit is specified).
2927 --no-commit is specified).
2927
2928
2928 To read a patch from standard input (stdin), use "-" as the patch
2929 To read a patch from standard input (stdin), use "-" as the patch
2929 name. If a URL is specified, the patch will be downloaded from
2930 name. If a URL is specified, the patch will be downloaded from
2930 there.
2931 there.
2931
2932
2932 Import first applies changes to the working directory (unless
2933 Import first applies changes to the working directory (unless
2933 --bypass is specified), import will abort if there are outstanding
2934 --bypass is specified), import will abort if there are outstanding
2934 changes.
2935 changes.
2935
2936
2936 Use --bypass to apply and commit patches directly to the
2937 Use --bypass to apply and commit patches directly to the
2937 repository, without affecting the working directory. Without
2938 repository, without affecting the working directory. Without
2938 --exact, patches will be applied on top of the working directory
2939 --exact, patches will be applied on top of the working directory
2939 parent revision.
2940 parent revision.
2940
2941
2941 You can import a patch straight from a mail message. Even patches
2942 You can import a patch straight from a mail message. Even patches
2942 as attachments work (to use the body part, it must have type
2943 as attachments work (to use the body part, it must have type
2943 text/plain or text/x-patch). From and Subject headers of email
2944 text/plain or text/x-patch). From and Subject headers of email
2944 message are used as default committer and commit message. All
2945 message are used as default committer and commit message. All
2945 text/plain body parts before first diff are added to the commit
2946 text/plain body parts before first diff are added to the commit
2946 message.
2947 message.
2947
2948
2948 If the imported patch was generated by :hg:`export`, user and
2949 If the imported patch was generated by :hg:`export`, user and
2949 description from patch override values from message headers and
2950 description from patch override values from message headers and
2950 body. Values given on command line with -m/--message and -u/--user
2951 body. Values given on command line with -m/--message and -u/--user
2951 override these.
2952 override these.
2952
2953
2953 If --exact is specified, import will set the working directory to
2954 If --exact is specified, import will set the working directory to
2954 the parent of each patch before applying it, and will abort if the
2955 the parent of each patch before applying it, and will abort if the
2955 resulting changeset has a different ID than the one recorded in
2956 resulting changeset has a different ID than the one recorded in
2956 the patch. This will guard against various ways that portable
2957 the patch. This will guard against various ways that portable
2957 patch formats and mail systems might fail to transfer Mercurial
2958 patch formats and mail systems might fail to transfer Mercurial
2958 data or metadata. See :hg:`bundle` for lossless transmission.
2959 data or metadata. See :hg:`bundle` for lossless transmission.
2959
2960
2960 Use --partial to ensure a changeset will be created from the patch
2961 Use --partial to ensure a changeset will be created from the patch
2961 even if some hunks fail to apply. Hunks that fail to apply will be
2962 even if some hunks fail to apply. Hunks that fail to apply will be
2962 written to a <target-file>.rej file. Conflicts can then be resolved
2963 written to a <target-file>.rej file. Conflicts can then be resolved
2963 by hand before :hg:`commit --amend` is run to update the created
2964 by hand before :hg:`commit --amend` is run to update the created
2964 changeset. This flag exists to let people import patches that
2965 changeset. This flag exists to let people import patches that
2965 partially apply without losing the associated metadata (author,
2966 partially apply without losing the associated metadata (author,
2966 date, description, ...).
2967 date, description, ...).
2967
2968
2968 .. note::
2969 .. note::
2969
2970
2970 When no hunks apply cleanly, :hg:`import --partial` will create
2971 When no hunks apply cleanly, :hg:`import --partial` will create
2971 an empty changeset, importing only the patch metadata.
2972 an empty changeset, importing only the patch metadata.
2972
2973
2973 With -s/--similarity, hg will attempt to discover renames and
2974 With -s/--similarity, hg will attempt to discover renames and
2974 copies in the patch in the same way as :hg:`addremove`.
2975 copies in the patch in the same way as :hg:`addremove`.
2975
2976
2976 It is possible to use external patch programs to perform the patch
2977 It is possible to use external patch programs to perform the patch
2977 by setting the ``ui.patch`` configuration option. For the default
2978 by setting the ``ui.patch`` configuration option. For the default
2978 internal tool, the fuzz can also be configured via ``patch.fuzz``.
2979 internal tool, the fuzz can also be configured via ``patch.fuzz``.
2979 See :hg:`help config` for more information about configuration
2980 See :hg:`help config` for more information about configuration
2980 files and how to use these options.
2981 files and how to use these options.
2981
2982
2982 See :hg:`help dates` for a list of formats valid for -d/--date.
2983 See :hg:`help dates` for a list of formats valid for -d/--date.
2983
2984
2984 .. container:: verbose
2985 .. container:: verbose
2985
2986
2986 Examples:
2987 Examples:
2987
2988
2988 - import a traditional patch from a website and detect renames::
2989 - import a traditional patch from a website and detect renames::
2989
2990
2990 hg import -s 80 http://example.com/bugfix.patch
2991 hg import -s 80 http://example.com/bugfix.patch
2991
2992
2992 - import a changeset from an hgweb server::
2993 - import a changeset from an hgweb server::
2993
2994
2994 hg import https://www.mercurial-scm.org/repo/hg/rev/5ca8c111e9aa
2995 hg import https://www.mercurial-scm.org/repo/hg/rev/5ca8c111e9aa
2995
2996
2996 - import all the patches in an Unix-style mbox::
2997 - import all the patches in an Unix-style mbox::
2997
2998
2998 hg import incoming-patches.mbox
2999 hg import incoming-patches.mbox
2999
3000
3000 - import patches from stdin::
3001 - import patches from stdin::
3001
3002
3002 hg import -
3003 hg import -
3003
3004
3004 - attempt to exactly restore an exported changeset (not always
3005 - attempt to exactly restore an exported changeset (not always
3005 possible)::
3006 possible)::
3006
3007
3007 hg import --exact proposed-fix.patch
3008 hg import --exact proposed-fix.patch
3008
3009
3009 - use an external tool to apply a patch which is too fuzzy for
3010 - use an external tool to apply a patch which is too fuzzy for
3010 the default internal tool.
3011 the default internal tool.
3011
3012
3012 hg import --config ui.patch="patch --merge" fuzzy.patch
3013 hg import --config ui.patch="patch --merge" fuzzy.patch
3013
3014
3014 - change the default fuzzing from 2 to a less strict 7
3015 - change the default fuzzing from 2 to a less strict 7
3015
3016
3016 hg import --config ui.fuzz=7 fuzz.patch
3017 hg import --config ui.fuzz=7 fuzz.patch
3017
3018
3018 Returns 0 on success, 1 on partial success (see --partial).
3019 Returns 0 on success, 1 on partial success (see --partial).
3019 """
3020 """
3020
3021
3021 opts = pycompat.byteskwargs(opts)
3022 opts = pycompat.byteskwargs(opts)
3022 if not patch1:
3023 if not patch1:
3023 raise error.Abort(_('need at least one patch to import'))
3024 raise error.Abort(_('need at least one patch to import'))
3024
3025
3025 patches = (patch1,) + patches
3026 patches = (patch1,) + patches
3026
3027
3027 date = opts.get('date')
3028 date = opts.get('date')
3028 if date:
3029 if date:
3029 opts['date'] = util.parsedate(date)
3030 opts['date'] = util.parsedate(date)
3030
3031
3031 exact = opts.get('exact')
3032 exact = opts.get('exact')
3032 update = not opts.get('bypass')
3033 update = not opts.get('bypass')
3033 if not update and opts.get('no_commit'):
3034 if not update and opts.get('no_commit'):
3034 raise error.Abort(_('cannot use --no-commit with --bypass'))
3035 raise error.Abort(_('cannot use --no-commit with --bypass'))
3035 try:
3036 try:
3036 sim = float(opts.get('similarity') or 0)
3037 sim = float(opts.get('similarity') or 0)
3037 except ValueError:
3038 except ValueError:
3038 raise error.Abort(_('similarity must be a number'))
3039 raise error.Abort(_('similarity must be a number'))
3039 if sim < 0 or sim > 100:
3040 if sim < 0 or sim > 100:
3040 raise error.Abort(_('similarity must be between 0 and 100'))
3041 raise error.Abort(_('similarity must be between 0 and 100'))
3041 if sim and not update:
3042 if sim and not update:
3042 raise error.Abort(_('cannot use --similarity with --bypass'))
3043 raise error.Abort(_('cannot use --similarity with --bypass'))
3043 if exact:
3044 if exact:
3044 if opts.get('edit'):
3045 if opts.get('edit'):
3045 raise error.Abort(_('cannot use --exact with --edit'))
3046 raise error.Abort(_('cannot use --exact with --edit'))
3046 if opts.get('prefix'):
3047 if opts.get('prefix'):
3047 raise error.Abort(_('cannot use --exact with --prefix'))
3048 raise error.Abort(_('cannot use --exact with --prefix'))
3048
3049
3049 base = opts["base"]
3050 base = opts["base"]
3050 wlock = dsguard = lock = tr = None
3051 wlock = dsguard = lock = tr = None
3051 msgs = []
3052 msgs = []
3052 ret = 0
3053 ret = 0
3053
3054
3054
3055
3055 try:
3056 try:
3056 wlock = repo.wlock()
3057 wlock = repo.wlock()
3057
3058
3058 if update:
3059 if update:
3059 cmdutil.checkunfinished(repo)
3060 cmdutil.checkunfinished(repo)
3060 if (exact or not opts.get('force')):
3061 if (exact or not opts.get('force')):
3061 cmdutil.bailifchanged(repo)
3062 cmdutil.bailifchanged(repo)
3062
3063
3063 if not opts.get('no_commit'):
3064 if not opts.get('no_commit'):
3064 lock = repo.lock()
3065 lock = repo.lock()
3065 tr = repo.transaction('import')
3066 tr = repo.transaction('import')
3066 else:
3067 else:
3067 dsguard = dirstateguard.dirstateguard(repo, 'import')
3068 dsguard = dirstateguard.dirstateguard(repo, 'import')
3068 parents = repo[None].parents()
3069 parents = repo[None].parents()
3069 for patchurl in patches:
3070 for patchurl in patches:
3070 if patchurl == '-':
3071 if patchurl == '-':
3071 ui.status(_('applying patch from stdin\n'))
3072 ui.status(_('applying patch from stdin\n'))
3072 patchfile = ui.fin
3073 patchfile = ui.fin
3073 patchurl = 'stdin' # for error message
3074 patchurl = 'stdin' # for error message
3074 else:
3075 else:
3075 patchurl = os.path.join(base, patchurl)
3076 patchurl = os.path.join(base, patchurl)
3076 ui.status(_('applying %s\n') % patchurl)
3077 ui.status(_('applying %s\n') % patchurl)
3077 patchfile = hg.openpath(ui, patchurl)
3078 patchfile = hg.openpath(ui, patchurl)
3078
3079
3079 haspatch = False
3080 haspatch = False
3080 for hunk in patch.split(patchfile):
3081 for hunk in patch.split(patchfile):
3081 (msg, node, rej) = cmdutil.tryimportone(ui, repo, hunk,
3082 (msg, node, rej) = cmdutil.tryimportone(ui, repo, hunk,
3082 parents, opts,
3083 parents, opts,
3083 msgs, hg.clean)
3084 msgs, hg.clean)
3084 if msg:
3085 if msg:
3085 haspatch = True
3086 haspatch = True
3086 ui.note(msg + '\n')
3087 ui.note(msg + '\n')
3087 if update or exact:
3088 if update or exact:
3088 parents = repo[None].parents()
3089 parents = repo[None].parents()
3089 else:
3090 else:
3090 parents = [repo[node]]
3091 parents = [repo[node]]
3091 if rej:
3092 if rej:
3092 ui.write_err(_("patch applied partially\n"))
3093 ui.write_err(_("patch applied partially\n"))
3093 ui.write_err(_("(fix the .rej files and run "
3094 ui.write_err(_("(fix the .rej files and run "
3094 "`hg commit --amend`)\n"))
3095 "`hg commit --amend`)\n"))
3095 ret = 1
3096 ret = 1
3096 break
3097 break
3097
3098
3098 if not haspatch:
3099 if not haspatch:
3099 raise error.Abort(_('%s: no diffs found') % patchurl)
3100 raise error.Abort(_('%s: no diffs found') % patchurl)
3100
3101
3101 if tr:
3102 if tr:
3102 tr.close()
3103 tr.close()
3103 if msgs:
3104 if msgs:
3104 repo.savecommitmessage('\n* * *\n'.join(msgs))
3105 repo.savecommitmessage('\n* * *\n'.join(msgs))
3105 if dsguard:
3106 if dsguard:
3106 dsguard.close()
3107 dsguard.close()
3107 return ret
3108 return ret
3108 finally:
3109 finally:
3109 if tr:
3110 if tr:
3110 tr.release()
3111 tr.release()
3111 release(lock, dsguard, wlock)
3112 release(lock, dsguard, wlock)
3112
3113
3113 @command('incoming|in',
3114 @command('incoming|in',
3114 [('f', 'force', None,
3115 [('f', 'force', None,
3115 _('run even if remote repository is unrelated')),
3116 _('run even if remote repository is unrelated')),
3116 ('n', 'newest-first', None, _('show newest record first')),
3117 ('n', 'newest-first', None, _('show newest record first')),
3117 ('', 'bundle', '',
3118 ('', 'bundle', '',
3118 _('file to store the bundles into'), _('FILE')),
3119 _('file to store the bundles into'), _('FILE')),
3119 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
3120 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
3120 ('B', 'bookmarks', False, _("compare bookmarks")),
3121 ('B', 'bookmarks', False, _("compare bookmarks")),
3121 ('b', 'branch', [],
3122 ('b', 'branch', [],
3122 _('a specific branch you would like to pull'), _('BRANCH')),
3123 _('a specific branch you would like to pull'), _('BRANCH')),
3123 ] + logopts + remoteopts + subrepoopts,
3124 ] + logopts + remoteopts + subrepoopts,
3124 _('[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'))
3125 _('[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'))
3125 def incoming(ui, repo, source="default", **opts):
3126 def incoming(ui, repo, source="default", **opts):
3126 """show new changesets found in source
3127 """show new changesets found in source
3127
3128
3128 Show new changesets found in the specified path/URL or the default
3129 Show new changesets found in the specified path/URL or the default
3129 pull location. These are the changesets that would have been pulled
3130 pull location. These are the changesets that would have been pulled
3130 if a pull at the time you issued this command.
3131 if a pull at the time you issued this command.
3131
3132
3132 See pull for valid source format details.
3133 See pull for valid source format details.
3133
3134
3134 .. container:: verbose
3135 .. container:: verbose
3135
3136
3136 With -B/--bookmarks, the result of bookmark comparison between
3137 With -B/--bookmarks, the result of bookmark comparison between
3137 local and remote repositories is displayed. With -v/--verbose,
3138 local and remote repositories is displayed. With -v/--verbose,
3138 status is also displayed for each bookmark like below::
3139 status is also displayed for each bookmark like below::
3139
3140
3140 BM1 01234567890a added
3141 BM1 01234567890a added
3141 BM2 1234567890ab advanced
3142 BM2 1234567890ab advanced
3142 BM3 234567890abc diverged
3143 BM3 234567890abc diverged
3143 BM4 34567890abcd changed
3144 BM4 34567890abcd changed
3144
3145
3145 The action taken locally when pulling depends on the
3146 The action taken locally when pulling depends on the
3146 status of each bookmark:
3147 status of each bookmark:
3147
3148
3148 :``added``: pull will create it
3149 :``added``: pull will create it
3149 :``advanced``: pull will update it
3150 :``advanced``: pull will update it
3150 :``diverged``: pull will create a divergent bookmark
3151 :``diverged``: pull will create a divergent bookmark
3151 :``changed``: result depends on remote changesets
3152 :``changed``: result depends on remote changesets
3152
3153
3153 From the point of view of pulling behavior, bookmark
3154 From the point of view of pulling behavior, bookmark
3154 existing only in the remote repository are treated as ``added``,
3155 existing only in the remote repository are treated as ``added``,
3155 even if it is in fact locally deleted.
3156 even if it is in fact locally deleted.
3156
3157
3157 .. container:: verbose
3158 .. container:: verbose
3158
3159
3159 For remote repository, using --bundle avoids downloading the
3160 For remote repository, using --bundle avoids downloading the
3160 changesets twice if the incoming is followed by a pull.
3161 changesets twice if the incoming is followed by a pull.
3161
3162
3162 Examples:
3163 Examples:
3163
3164
3164 - show incoming changes with patches and full description::
3165 - show incoming changes with patches and full description::
3165
3166
3166 hg incoming -vp
3167 hg incoming -vp
3167
3168
3168 - show incoming changes excluding merges, store a bundle::
3169 - show incoming changes excluding merges, store a bundle::
3169
3170
3170 hg in -vpM --bundle incoming.hg
3171 hg in -vpM --bundle incoming.hg
3171 hg pull incoming.hg
3172 hg pull incoming.hg
3172
3173
3173 - briefly list changes inside a bundle::
3174 - briefly list changes inside a bundle::
3174
3175
3175 hg in changes.hg -T "{desc|firstline}\\n"
3176 hg in changes.hg -T "{desc|firstline}\\n"
3176
3177
3177 Returns 0 if there are incoming changes, 1 otherwise.
3178 Returns 0 if there are incoming changes, 1 otherwise.
3178 """
3179 """
3179 opts = pycompat.byteskwargs(opts)
3180 opts = pycompat.byteskwargs(opts)
3180 if opts.get('graph'):
3181 if opts.get('graph'):
3181 cmdutil.checkunsupportedgraphflags([], opts)
3182 cmdutil.checkunsupportedgraphflags([], opts)
3182 def display(other, chlist, displayer):
3183 def display(other, chlist, displayer):
3183 revdag = cmdutil.graphrevs(other, chlist, opts)
3184 revdag = cmdutil.graphrevs(other, chlist, opts)
3184 cmdutil.displaygraph(ui, repo, revdag, displayer,
3185 cmdutil.displaygraph(ui, repo, revdag, displayer,
3185 graphmod.asciiedges)
3186 graphmod.asciiedges)
3186
3187
3187 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
3188 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
3188 return 0
3189 return 0
3189
3190
3190 if opts.get('bundle') and opts.get('subrepos'):
3191 if opts.get('bundle') and opts.get('subrepos'):
3191 raise error.Abort(_('cannot combine --bundle and --subrepos'))
3192 raise error.Abort(_('cannot combine --bundle and --subrepos'))
3192
3193
3193 if opts.get('bookmarks'):
3194 if opts.get('bookmarks'):
3194 source, branches = hg.parseurl(ui.expandpath(source),
3195 source, branches = hg.parseurl(ui.expandpath(source),
3195 opts.get('branch'))
3196 opts.get('branch'))
3196 other = hg.peer(repo, opts, source)
3197 other = hg.peer(repo, opts, source)
3197 if 'bookmarks' not in other.listkeys('namespaces'):
3198 if 'bookmarks' not in other.listkeys('namespaces'):
3198 ui.warn(_("remote doesn't support bookmarks\n"))
3199 ui.warn(_("remote doesn't support bookmarks\n"))
3199 return 0
3200 return 0
3200 ui.pager('incoming')
3201 ui.pager('incoming')
3201 ui.status(_('comparing with %s\n') % util.hidepassword(source))
3202 ui.status(_('comparing with %s\n') % util.hidepassword(source))
3202 return bookmarks.incoming(ui, repo, other)
3203 return bookmarks.incoming(ui, repo, other)
3203
3204
3204 repo._subtoppath = ui.expandpath(source)
3205 repo._subtoppath = ui.expandpath(source)
3205 try:
3206 try:
3206 return hg.incoming(ui, repo, source, opts)
3207 return hg.incoming(ui, repo, source, opts)
3207 finally:
3208 finally:
3208 del repo._subtoppath
3209 del repo._subtoppath
3209
3210
3210
3211
3211 @command('^init', remoteopts, _('[-e CMD] [--remotecmd CMD] [DEST]'),
3212 @command('^init', remoteopts, _('[-e CMD] [--remotecmd CMD] [DEST]'),
3212 norepo=True)
3213 norepo=True)
3213 def init(ui, dest=".", **opts):
3214 def init(ui, dest=".", **opts):
3214 """create a new repository in the given directory
3215 """create a new repository in the given directory
3215
3216
3216 Initialize a new repository in the given directory. If the given
3217 Initialize a new repository in the given directory. If the given
3217 directory does not exist, it will be created.
3218 directory does not exist, it will be created.
3218
3219
3219 If no directory is given, the current directory is used.
3220 If no directory is given, the current directory is used.
3220
3221
3221 It is possible to specify an ``ssh://`` URL as the destination.
3222 It is possible to specify an ``ssh://`` URL as the destination.
3222 See :hg:`help urls` for more information.
3223 See :hg:`help urls` for more information.
3223
3224
3224 Returns 0 on success.
3225 Returns 0 on success.
3225 """
3226 """
3226 opts = pycompat.byteskwargs(opts)
3227 opts = pycompat.byteskwargs(opts)
3227 hg.peer(ui, opts, ui.expandpath(dest), create=True)
3228 hg.peer(ui, opts, ui.expandpath(dest), create=True)
3228
3229
3229 @command('locate',
3230 @command('locate',
3230 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
3231 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
3231 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
3232 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
3232 ('f', 'fullpath', None, _('print complete paths from the filesystem root')),
3233 ('f', 'fullpath', None, _('print complete paths from the filesystem root')),
3233 ] + walkopts,
3234 ] + walkopts,
3234 _('[OPTION]... [PATTERN]...'))
3235 _('[OPTION]... [PATTERN]...'))
3235 def locate(ui, repo, *pats, **opts):
3236 def locate(ui, repo, *pats, **opts):
3236 """locate files matching specific patterns (DEPRECATED)
3237 """locate files matching specific patterns (DEPRECATED)
3237
3238
3238 Print files under Mercurial control in the working directory whose
3239 Print files under Mercurial control in the working directory whose
3239 names match the given patterns.
3240 names match the given patterns.
3240
3241
3241 By default, this command searches all directories in the working
3242 By default, this command searches all directories in the working
3242 directory. To search just the current directory and its
3243 directory. To search just the current directory and its
3243 subdirectories, use "--include .".
3244 subdirectories, use "--include .".
3244
3245
3245 If no patterns are given to match, this command prints the names
3246 If no patterns are given to match, this command prints the names
3246 of all files under Mercurial control in the working directory.
3247 of all files under Mercurial control in the working directory.
3247
3248
3248 If you want to feed the output of this command into the "xargs"
3249 If you want to feed the output of this command into the "xargs"
3249 command, use the -0 option to both this command and "xargs". This
3250 command, use the -0 option to both this command and "xargs". This
3250 will avoid the problem of "xargs" treating single filenames that
3251 will avoid the problem of "xargs" treating single filenames that
3251 contain whitespace as multiple filenames.
3252 contain whitespace as multiple filenames.
3252
3253
3253 See :hg:`help files` for a more versatile command.
3254 See :hg:`help files` for a more versatile command.
3254
3255
3255 Returns 0 if a match is found, 1 otherwise.
3256 Returns 0 if a match is found, 1 otherwise.
3256 """
3257 """
3257 opts = pycompat.byteskwargs(opts)
3258 opts = pycompat.byteskwargs(opts)
3258 if opts.get('print0'):
3259 if opts.get('print0'):
3259 end = '\0'
3260 end = '\0'
3260 else:
3261 else:
3261 end = '\n'
3262 end = '\n'
3262 rev = scmutil.revsingle(repo, opts.get('rev'), None).node()
3263 rev = scmutil.revsingle(repo, opts.get('rev'), None).node()
3263
3264
3264 ret = 1
3265 ret = 1
3265 ctx = repo[rev]
3266 ctx = repo[rev]
3266 m = scmutil.match(ctx, pats, opts, default='relglob',
3267 m = scmutil.match(ctx, pats, opts, default='relglob',
3267 badfn=lambda x, y: False)
3268 badfn=lambda x, y: False)
3268
3269
3269 ui.pager('locate')
3270 ui.pager('locate')
3270 for abs in ctx.matches(m):
3271 for abs in ctx.matches(m):
3271 if opts.get('fullpath'):
3272 if opts.get('fullpath'):
3272 ui.write(repo.wjoin(abs), end)
3273 ui.write(repo.wjoin(abs), end)
3273 else:
3274 else:
3274 ui.write(((pats and m.rel(abs)) or abs), end)
3275 ui.write(((pats and m.rel(abs)) or abs), end)
3275 ret = 0
3276 ret = 0
3276
3277
3277 return ret
3278 return ret
3278
3279
3279 @command('^log|history',
3280 @command('^log|history',
3280 [('f', 'follow', None,
3281 [('f', 'follow', None,
3281 _('follow changeset history, or file history across copies and renames')),
3282 _('follow changeset history, or file history across copies and renames')),
3282 ('', 'follow-first', None,
3283 ('', 'follow-first', None,
3283 _('only follow the first parent of merge changesets (DEPRECATED)')),
3284 _('only follow the first parent of merge changesets (DEPRECATED)')),
3284 ('d', 'date', '', _('show revisions matching date spec'), _('DATE')),
3285 ('d', 'date', '', _('show revisions matching date spec'), _('DATE')),
3285 ('C', 'copies', None, _('show copied files')),
3286 ('C', 'copies', None, _('show copied files')),
3286 ('k', 'keyword', [],
3287 ('k', 'keyword', [],
3287 _('do case-insensitive search for a given text'), _('TEXT')),
3288 _('do case-insensitive search for a given text'), _('TEXT')),
3288 ('r', 'rev', [], _('show the specified revision or revset'), _('REV')),
3289 ('r', 'rev', [], _('show the specified revision or revset'), _('REV')),
3289 ('', 'removed', None, _('include revisions where files were removed')),
3290 ('', 'removed', None, _('include revisions where files were removed')),
3290 ('m', 'only-merges', None, _('show only merges (DEPRECATED)')),
3291 ('m', 'only-merges', None, _('show only merges (DEPRECATED)')),
3291 ('u', 'user', [], _('revisions committed by user'), _('USER')),
3292 ('u', 'user', [], _('revisions committed by user'), _('USER')),
3292 ('', 'only-branch', [],
3293 ('', 'only-branch', [],
3293 _('show only changesets within the given named branch (DEPRECATED)'),
3294 _('show only changesets within the given named branch (DEPRECATED)'),
3294 _('BRANCH')),
3295 _('BRANCH')),
3295 ('b', 'branch', [],
3296 ('b', 'branch', [],
3296 _('show changesets within the given named branch'), _('BRANCH')),
3297 _('show changesets within the given named branch'), _('BRANCH')),
3297 ('P', 'prune', [],
3298 ('P', 'prune', [],
3298 _('do not display revision or any of its ancestors'), _('REV')),
3299 _('do not display revision or any of its ancestors'), _('REV')),
3299 ] + logopts + walkopts,
3300 ] + logopts + walkopts,
3300 _('[OPTION]... [FILE]'),
3301 _('[OPTION]... [FILE]'),
3301 inferrepo=True)
3302 inferrepo=True)
3302 def log(ui, repo, *pats, **opts):
3303 def log(ui, repo, *pats, **opts):
3303 """show revision history of entire repository or files
3304 """show revision history of entire repository or files
3304
3305
3305 Print the revision history of the specified files or the entire
3306 Print the revision history of the specified files or the entire
3306 project.
3307 project.
3307
3308
3308 If no revision range is specified, the default is ``tip:0`` unless
3309 If no revision range is specified, the default is ``tip:0`` unless
3309 --follow is set, in which case the working directory parent is
3310 --follow is set, in which case the working directory parent is
3310 used as the starting revision.
3311 used as the starting revision.
3311
3312
3312 File history is shown without following rename or copy history of
3313 File history is shown without following rename or copy history of
3313 files. Use -f/--follow with a filename to follow history across
3314 files. Use -f/--follow with a filename to follow history across
3314 renames and copies. --follow without a filename will only show
3315 renames and copies. --follow without a filename will only show
3315 ancestors or descendants of the starting revision.
3316 ancestors or descendants of the starting revision.
3316
3317
3317 By default this command prints revision number and changeset id,
3318 By default this command prints revision number and changeset id,
3318 tags, non-trivial parents, user, date and time, and a summary for
3319 tags, non-trivial parents, user, date and time, and a summary for
3319 each commit. When the -v/--verbose switch is used, the list of
3320 each commit. When the -v/--verbose switch is used, the list of
3320 changed files and full commit message are shown.
3321 changed files and full commit message are shown.
3321
3322
3322 With --graph the revisions are shown as an ASCII art DAG with the most
3323 With --graph the revisions are shown as an ASCII art DAG with the most
3323 recent changeset at the top.
3324 recent changeset at the top.
3324 'o' is a changeset, '@' is a working directory parent, 'x' is obsolete,
3325 'o' is a changeset, '@' is a working directory parent, 'x' is obsolete,
3325 and '+' represents a fork where the changeset from the lines below is a
3326 and '+' represents a fork where the changeset from the lines below is a
3326 parent of the 'o' merge on the same line.
3327 parent of the 'o' merge on the same line.
3327 Paths in the DAG are represented with '|', '/' and so forth. ':' in place
3328 Paths in the DAG are represented with '|', '/' and so forth. ':' in place
3328 of a '|' indicates one or more revisions in a path are omitted.
3329 of a '|' indicates one or more revisions in a path are omitted.
3329
3330
3330 .. note::
3331 .. note::
3331
3332
3332 :hg:`log --patch` may generate unexpected diff output for merge
3333 :hg:`log --patch` may generate unexpected diff output for merge
3333 changesets, as it will only compare the merge changeset against
3334 changesets, as it will only compare the merge changeset against
3334 its first parent. Also, only files different from BOTH parents
3335 its first parent. Also, only files different from BOTH parents
3335 will appear in files:.
3336 will appear in files:.
3336
3337
3337 .. note::
3338 .. note::
3338
3339
3339 For performance reasons, :hg:`log FILE` may omit duplicate changes
3340 For performance reasons, :hg:`log FILE` may omit duplicate changes
3340 made on branches and will not show removals or mode changes. To
3341 made on branches and will not show removals or mode changes. To
3341 see all such changes, use the --removed switch.
3342 see all such changes, use the --removed switch.
3342
3343
3343 .. container:: verbose
3344 .. container:: verbose
3344
3345
3345 Some examples:
3346 Some examples:
3346
3347
3347 - changesets with full descriptions and file lists::
3348 - changesets with full descriptions and file lists::
3348
3349
3349 hg log -v
3350 hg log -v
3350
3351
3351 - changesets ancestral to the working directory::
3352 - changesets ancestral to the working directory::
3352
3353
3353 hg log -f
3354 hg log -f
3354
3355
3355 - last 10 commits on the current branch::
3356 - last 10 commits on the current branch::
3356
3357
3357 hg log -l 10 -b .
3358 hg log -l 10 -b .
3358
3359
3359 - changesets showing all modifications of a file, including removals::
3360 - changesets showing all modifications of a file, including removals::
3360
3361
3361 hg log --removed file.c
3362 hg log --removed file.c
3362
3363
3363 - all changesets that touch a directory, with diffs, excluding merges::
3364 - all changesets that touch a directory, with diffs, excluding merges::
3364
3365
3365 hg log -Mp lib/
3366 hg log -Mp lib/
3366
3367
3367 - all revision numbers that match a keyword::
3368 - all revision numbers that match a keyword::
3368
3369
3369 hg log -k bug --template "{rev}\\n"
3370 hg log -k bug --template "{rev}\\n"
3370
3371
3371 - the full hash identifier of the working directory parent::
3372 - the full hash identifier of the working directory parent::
3372
3373
3373 hg log -r . --template "{node}\\n"
3374 hg log -r . --template "{node}\\n"
3374
3375
3375 - list available log templates::
3376 - list available log templates::
3376
3377
3377 hg log -T list
3378 hg log -T list
3378
3379
3379 - check if a given changeset is included in a tagged release::
3380 - check if a given changeset is included in a tagged release::
3380
3381
3381 hg log -r "a21ccf and ancestor(1.9)"
3382 hg log -r "a21ccf and ancestor(1.9)"
3382
3383
3383 - find all changesets by some user in a date range::
3384 - find all changesets by some user in a date range::
3384
3385
3385 hg log -k alice -d "may 2008 to jul 2008"
3386 hg log -k alice -d "may 2008 to jul 2008"
3386
3387
3387 - summary of all changesets after the last tag::
3388 - summary of all changesets after the last tag::
3388
3389
3389 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
3390 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
3390
3391
3391 See :hg:`help dates` for a list of formats valid for -d/--date.
3392 See :hg:`help dates` for a list of formats valid for -d/--date.
3392
3393
3393 See :hg:`help revisions` for more about specifying and ordering
3394 See :hg:`help revisions` for more about specifying and ordering
3394 revisions.
3395 revisions.
3395
3396
3396 See :hg:`help templates` for more about pre-packaged styles and
3397 See :hg:`help templates` for more about pre-packaged styles and
3397 specifying custom templates.
3398 specifying custom templates.
3398
3399
3399 Returns 0 on success.
3400 Returns 0 on success.
3400
3401
3401 """
3402 """
3402 opts = pycompat.byteskwargs(opts)
3403 opts = pycompat.byteskwargs(opts)
3403 if opts.get('follow') and opts.get('rev'):
3404 if opts.get('follow') and opts.get('rev'):
3404 opts['rev'] = [revsetlang.formatspec('reverse(::%lr)', opts.get('rev'))]
3405 opts['rev'] = [revsetlang.formatspec('reverse(::%lr)', opts.get('rev'))]
3405 del opts['follow']
3406 del opts['follow']
3406
3407
3407 if opts.get('graph'):
3408 if opts.get('graph'):
3408 return cmdutil.graphlog(ui, repo, pats, opts)
3409 return cmdutil.graphlog(ui, repo, pats, opts)
3409
3410
3410 revs, expr, filematcher = cmdutil.getlogrevs(repo, pats, opts)
3411 revs, expr, filematcher = cmdutil.getlogrevs(repo, pats, opts)
3411 limit = cmdutil.loglimit(opts)
3412 limit = cmdutil.loglimit(opts)
3412 count = 0
3413 count = 0
3413
3414
3414 getrenamed = None
3415 getrenamed = None
3415 if opts.get('copies'):
3416 if opts.get('copies'):
3416 endrev = None
3417 endrev = None
3417 if opts.get('rev'):
3418 if opts.get('rev'):
3418 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
3419 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
3419 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
3420 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
3420
3421
3421 ui.pager('log')
3422 ui.pager('log')
3422 displayer = cmdutil.show_changeset(ui, repo, opts, buffered=True)
3423 displayer = cmdutil.show_changeset(ui, repo, opts, buffered=True)
3423 for rev in revs:
3424 for rev in revs:
3424 if count == limit:
3425 if count == limit:
3425 break
3426 break
3426 ctx = repo[rev]
3427 ctx = repo[rev]
3427 copies = None
3428 copies = None
3428 if getrenamed is not None and rev:
3429 if getrenamed is not None and rev:
3429 copies = []
3430 copies = []
3430 for fn in ctx.files():
3431 for fn in ctx.files():
3431 rename = getrenamed(fn, rev)
3432 rename = getrenamed(fn, rev)
3432 if rename:
3433 if rename:
3433 copies.append((fn, rename[0]))
3434 copies.append((fn, rename[0]))
3434 if filematcher:
3435 if filematcher:
3435 revmatchfn = filematcher(ctx.rev())
3436 revmatchfn = filematcher(ctx.rev())
3436 else:
3437 else:
3437 revmatchfn = None
3438 revmatchfn = None
3438 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
3439 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
3439 if displayer.flush(ctx):
3440 if displayer.flush(ctx):
3440 count += 1
3441 count += 1
3441
3442
3442 displayer.close()
3443 displayer.close()
3443
3444
3444 @command('manifest',
3445 @command('manifest',
3445 [('r', 'rev', '', _('revision to display'), _('REV')),
3446 [('r', 'rev', '', _('revision to display'), _('REV')),
3446 ('', 'all', False, _("list files from all revisions"))]
3447 ('', 'all', False, _("list files from all revisions"))]
3447 + formatteropts,
3448 + formatteropts,
3448 _('[-r REV]'))
3449 _('[-r REV]'))
3449 def manifest(ui, repo, node=None, rev=None, **opts):
3450 def manifest(ui, repo, node=None, rev=None, **opts):
3450 """output the current or given revision of the project manifest
3451 """output the current or given revision of the project manifest
3451
3452
3452 Print a list of version controlled files for the given revision.
3453 Print a list of version controlled files for the given revision.
3453 If no revision is given, the first parent of the working directory
3454 If no revision is given, the first parent of the working directory
3454 is used, or the null revision if no revision is checked out.
3455 is used, or the null revision if no revision is checked out.
3455
3456
3456 With -v, print file permissions, symlink and executable bits.
3457 With -v, print file permissions, symlink and executable bits.
3457 With --debug, print file revision hashes.
3458 With --debug, print file revision hashes.
3458
3459
3459 If option --all is specified, the list of all files from all revisions
3460 If option --all is specified, the list of all files from all revisions
3460 is printed. This includes deleted and renamed files.
3461 is printed. This includes deleted and renamed files.
3461
3462
3462 Returns 0 on success.
3463 Returns 0 on success.
3463 """
3464 """
3464 opts = pycompat.byteskwargs(opts)
3465 opts = pycompat.byteskwargs(opts)
3465 fm = ui.formatter('manifest', opts)
3466 fm = ui.formatter('manifest', opts)
3466
3467
3467 if opts.get('all'):
3468 if opts.get('all'):
3468 if rev or node:
3469 if rev or node:
3469 raise error.Abort(_("can't specify a revision with --all"))
3470 raise error.Abort(_("can't specify a revision with --all"))
3470
3471
3471 res = []
3472 res = []
3472 prefix = "data/"
3473 prefix = "data/"
3473 suffix = ".i"
3474 suffix = ".i"
3474 plen = len(prefix)
3475 plen = len(prefix)
3475 slen = len(suffix)
3476 slen = len(suffix)
3476 with repo.lock():
3477 with repo.lock():
3477 for fn, b, size in repo.store.datafiles():
3478 for fn, b, size in repo.store.datafiles():
3478 if size != 0 and fn[-slen:] == suffix and fn[:plen] == prefix:
3479 if size != 0 and fn[-slen:] == suffix and fn[:plen] == prefix:
3479 res.append(fn[plen:-slen])
3480 res.append(fn[plen:-slen])
3480 ui.pager('manifest')
3481 ui.pager('manifest')
3481 for f in res:
3482 for f in res:
3482 fm.startitem()
3483 fm.startitem()
3483 fm.write("path", '%s\n', f)
3484 fm.write("path", '%s\n', f)
3484 fm.end()
3485 fm.end()
3485 return
3486 return
3486
3487
3487 if rev and node:
3488 if rev and node:
3488 raise error.Abort(_("please specify just one revision"))
3489 raise error.Abort(_("please specify just one revision"))
3489
3490
3490 if not node:
3491 if not node:
3491 node = rev
3492 node = rev
3492
3493
3493 char = {'l': '@', 'x': '*', '': ''}
3494 char = {'l': '@', 'x': '*', '': ''}
3494 mode = {'l': '644', 'x': '755', '': '644'}
3495 mode = {'l': '644', 'x': '755', '': '644'}
3495 ctx = scmutil.revsingle(repo, node)
3496 ctx = scmutil.revsingle(repo, node)
3496 mf = ctx.manifest()
3497 mf = ctx.manifest()
3497 ui.pager('manifest')
3498 ui.pager('manifest')
3498 for f in ctx:
3499 for f in ctx:
3499 fm.startitem()
3500 fm.startitem()
3500 fl = ctx[f].flags()
3501 fl = ctx[f].flags()
3501 fm.condwrite(ui.debugflag, 'hash', '%s ', hex(mf[f]))
3502 fm.condwrite(ui.debugflag, 'hash', '%s ', hex(mf[f]))
3502 fm.condwrite(ui.verbose, 'mode type', '%s %1s ', mode[fl], char[fl])
3503 fm.condwrite(ui.verbose, 'mode type', '%s %1s ', mode[fl], char[fl])
3503 fm.write('path', '%s\n', f)
3504 fm.write('path', '%s\n', f)
3504 fm.end()
3505 fm.end()
3505
3506
3506 @command('^merge',
3507 @command('^merge',
3507 [('f', 'force', None,
3508 [('f', 'force', None,
3508 _('force a merge including outstanding changes (DEPRECATED)')),
3509 _('force a merge including outstanding changes (DEPRECATED)')),
3509 ('r', 'rev', '', _('revision to merge'), _('REV')),
3510 ('r', 'rev', '', _('revision to merge'), _('REV')),
3510 ('P', 'preview', None,
3511 ('P', 'preview', None,
3511 _('review revisions to merge (no merge is performed)'))
3512 _('review revisions to merge (no merge is performed)'))
3512 ] + mergetoolopts,
3513 ] + mergetoolopts,
3513 _('[-P] [[-r] REV]'))
3514 _('[-P] [[-r] REV]'))
3514 def merge(ui, repo, node=None, **opts):
3515 def merge(ui, repo, node=None, **opts):
3515 """merge another revision into working directory
3516 """merge another revision into working directory
3516
3517
3517 The current working directory is updated with all changes made in
3518 The current working directory is updated with all changes made in
3518 the requested revision since the last common predecessor revision.
3519 the requested revision since the last common predecessor revision.
3519
3520
3520 Files that changed between either parent are marked as changed for
3521 Files that changed between either parent are marked as changed for
3521 the next commit and a commit must be performed before any further
3522 the next commit and a commit must be performed before any further
3522 updates to the repository are allowed. The next commit will have
3523 updates to the repository are allowed. The next commit will have
3523 two parents.
3524 two parents.
3524
3525
3525 ``--tool`` can be used to specify the merge tool used for file
3526 ``--tool`` can be used to specify the merge tool used for file
3526 merges. It overrides the HGMERGE environment variable and your
3527 merges. It overrides the HGMERGE environment variable and your
3527 configuration files. See :hg:`help merge-tools` for options.
3528 configuration files. See :hg:`help merge-tools` for options.
3528
3529
3529 If no revision is specified, the working directory's parent is a
3530 If no revision is specified, the working directory's parent is a
3530 head revision, and the current branch contains exactly one other
3531 head revision, and the current branch contains exactly one other
3531 head, the other head is merged with by default. Otherwise, an
3532 head, the other head is merged with by default. Otherwise, an
3532 explicit revision with which to merge with must be provided.
3533 explicit revision with which to merge with must be provided.
3533
3534
3534 See :hg:`help resolve` for information on handling file conflicts.
3535 See :hg:`help resolve` for information on handling file conflicts.
3535
3536
3536 To undo an uncommitted merge, use :hg:`update --clean .` which
3537 To undo an uncommitted merge, use :hg:`update --clean .` which
3537 will check out a clean copy of the original merge parent, losing
3538 will check out a clean copy of the original merge parent, losing
3538 all changes.
3539 all changes.
3539
3540
3540 Returns 0 on success, 1 if there are unresolved files.
3541 Returns 0 on success, 1 if there are unresolved files.
3541 """
3542 """
3542
3543
3543 opts = pycompat.byteskwargs(opts)
3544 opts = pycompat.byteskwargs(opts)
3544 if opts.get('rev') and node:
3545 if opts.get('rev') and node:
3545 raise error.Abort(_("please specify just one revision"))
3546 raise error.Abort(_("please specify just one revision"))
3546 if not node:
3547 if not node:
3547 node = opts.get('rev')
3548 node = opts.get('rev')
3548
3549
3549 if node:
3550 if node:
3550 node = scmutil.revsingle(repo, node).node()
3551 node = scmutil.revsingle(repo, node).node()
3551
3552
3552 if not node:
3553 if not node:
3553 node = repo[destutil.destmerge(repo)].node()
3554 node = repo[destutil.destmerge(repo)].node()
3554
3555
3555 if opts.get('preview'):
3556 if opts.get('preview'):
3556 # find nodes that are ancestors of p2 but not of p1
3557 # find nodes that are ancestors of p2 but not of p1
3557 p1 = repo.lookup('.')
3558 p1 = repo.lookup('.')
3558 p2 = repo.lookup(node)
3559 p2 = repo.lookup(node)
3559 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
3560 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
3560
3561
3561 displayer = cmdutil.show_changeset(ui, repo, opts)
3562 displayer = cmdutil.show_changeset(ui, repo, opts)
3562 for node in nodes:
3563 for node in nodes:
3563 displayer.show(repo[node])
3564 displayer.show(repo[node])
3564 displayer.close()
3565 displayer.close()
3565 return 0
3566 return 0
3566
3567
3567 try:
3568 try:
3568 # ui.forcemerge is an internal variable, do not document
3569 # ui.forcemerge is an internal variable, do not document
3569 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''), 'merge')
3570 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''), 'merge')
3570 force = opts.get('force')
3571 force = opts.get('force')
3571 labels = ['working copy', 'merge rev']
3572 labels = ['working copy', 'merge rev']
3572 return hg.merge(repo, node, force=force, mergeforce=force,
3573 return hg.merge(repo, node, force=force, mergeforce=force,
3573 labels=labels)
3574 labels=labels)
3574 finally:
3575 finally:
3575 ui.setconfig('ui', 'forcemerge', '', 'merge')
3576 ui.setconfig('ui', 'forcemerge', '', 'merge')
3576
3577
3577 @command('outgoing|out',
3578 @command('outgoing|out',
3578 [('f', 'force', None, _('run even when the destination is unrelated')),
3579 [('f', 'force', None, _('run even when the destination is unrelated')),
3579 ('r', 'rev', [],
3580 ('r', 'rev', [],
3580 _('a changeset intended to be included in the destination'), _('REV')),
3581 _('a changeset intended to be included in the destination'), _('REV')),
3581 ('n', 'newest-first', None, _('show newest record first')),
3582 ('n', 'newest-first', None, _('show newest record first')),
3582 ('B', 'bookmarks', False, _('compare bookmarks')),
3583 ('B', 'bookmarks', False, _('compare bookmarks')),
3583 ('b', 'branch', [], _('a specific branch you would like to push'),
3584 ('b', 'branch', [], _('a specific branch you would like to push'),
3584 _('BRANCH')),
3585 _('BRANCH')),
3585 ] + logopts + remoteopts + subrepoopts,
3586 ] + logopts + remoteopts + subrepoopts,
3586 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]'))
3587 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]'))
3587 def outgoing(ui, repo, dest=None, **opts):
3588 def outgoing(ui, repo, dest=None, **opts):
3588 """show changesets not found in the destination
3589 """show changesets not found in the destination
3589
3590
3590 Show changesets not found in the specified destination repository
3591 Show changesets not found in the specified destination repository
3591 or the default push location. These are the changesets that would
3592 or the default push location. These are the changesets that would
3592 be pushed if a push was requested.
3593 be pushed if a push was requested.
3593
3594
3594 See pull for details of valid destination formats.
3595 See pull for details of valid destination formats.
3595
3596
3596 .. container:: verbose
3597 .. container:: verbose
3597
3598
3598 With -B/--bookmarks, the result of bookmark comparison between
3599 With -B/--bookmarks, the result of bookmark comparison between
3599 local and remote repositories is displayed. With -v/--verbose,
3600 local and remote repositories is displayed. With -v/--verbose,
3600 status is also displayed for each bookmark like below::
3601 status is also displayed for each bookmark like below::
3601
3602
3602 BM1 01234567890a added
3603 BM1 01234567890a added
3603 BM2 deleted
3604 BM2 deleted
3604 BM3 234567890abc advanced
3605 BM3 234567890abc advanced
3605 BM4 34567890abcd diverged
3606 BM4 34567890abcd diverged
3606 BM5 4567890abcde changed
3607 BM5 4567890abcde changed
3607
3608
3608 The action taken when pushing depends on the
3609 The action taken when pushing depends on the
3609 status of each bookmark:
3610 status of each bookmark:
3610
3611
3611 :``added``: push with ``-B`` will create it
3612 :``added``: push with ``-B`` will create it
3612 :``deleted``: push with ``-B`` will delete it
3613 :``deleted``: push with ``-B`` will delete it
3613 :``advanced``: push will update it
3614 :``advanced``: push will update it
3614 :``diverged``: push with ``-B`` will update it
3615 :``diverged``: push with ``-B`` will update it
3615 :``changed``: push with ``-B`` will update it
3616 :``changed``: push with ``-B`` will update it
3616
3617
3617 From the point of view of pushing behavior, bookmarks
3618 From the point of view of pushing behavior, bookmarks
3618 existing only in the remote repository are treated as
3619 existing only in the remote repository are treated as
3619 ``deleted``, even if it is in fact added remotely.
3620 ``deleted``, even if it is in fact added remotely.
3620
3621
3621 Returns 0 if there are outgoing changes, 1 otherwise.
3622 Returns 0 if there are outgoing changes, 1 otherwise.
3622 """
3623 """
3623 opts = pycompat.byteskwargs(opts)
3624 opts = pycompat.byteskwargs(opts)
3624 if opts.get('graph'):
3625 if opts.get('graph'):
3625 cmdutil.checkunsupportedgraphflags([], opts)
3626 cmdutil.checkunsupportedgraphflags([], opts)
3626 o, other = hg._outgoing(ui, repo, dest, opts)
3627 o, other = hg._outgoing(ui, repo, dest, opts)
3627 if not o:
3628 if not o:
3628 cmdutil.outgoinghooks(ui, repo, other, opts, o)
3629 cmdutil.outgoinghooks(ui, repo, other, opts, o)
3629 return
3630 return
3630
3631
3631 revdag = cmdutil.graphrevs(repo, o, opts)
3632 revdag = cmdutil.graphrevs(repo, o, opts)
3632 ui.pager('outgoing')
3633 ui.pager('outgoing')
3633 displayer = cmdutil.show_changeset(ui, repo, opts, buffered=True)
3634 displayer = cmdutil.show_changeset(ui, repo, opts, buffered=True)
3634 cmdutil.displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges)
3635 cmdutil.displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges)
3635 cmdutil.outgoinghooks(ui, repo, other, opts, o)
3636 cmdutil.outgoinghooks(ui, repo, other, opts, o)
3636 return 0
3637 return 0
3637
3638
3638 if opts.get('bookmarks'):
3639 if opts.get('bookmarks'):
3639 dest = ui.expandpath(dest or 'default-push', dest or 'default')
3640 dest = ui.expandpath(dest or 'default-push', dest or 'default')
3640 dest, branches = hg.parseurl(dest, opts.get('branch'))
3641 dest, branches = hg.parseurl(dest, opts.get('branch'))
3641 other = hg.peer(repo, opts, dest)
3642 other = hg.peer(repo, opts, dest)
3642 if 'bookmarks' not in other.listkeys('namespaces'):
3643 if 'bookmarks' not in other.listkeys('namespaces'):
3643 ui.warn(_("remote doesn't support bookmarks\n"))
3644 ui.warn(_("remote doesn't support bookmarks\n"))
3644 return 0
3645 return 0
3645 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
3646 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
3646 ui.pager('outgoing')
3647 ui.pager('outgoing')
3647 return bookmarks.outgoing(ui, repo, other)
3648 return bookmarks.outgoing(ui, repo, other)
3648
3649
3649 repo._subtoppath = ui.expandpath(dest or 'default-push', dest or 'default')
3650 repo._subtoppath = ui.expandpath(dest or 'default-push', dest or 'default')
3650 try:
3651 try:
3651 return hg.outgoing(ui, repo, dest, opts)
3652 return hg.outgoing(ui, repo, dest, opts)
3652 finally:
3653 finally:
3653 del repo._subtoppath
3654 del repo._subtoppath
3654
3655
3655 @command('parents',
3656 @command('parents',
3656 [('r', 'rev', '', _('show parents of the specified revision'), _('REV')),
3657 [('r', 'rev', '', _('show parents of the specified revision'), _('REV')),
3657 ] + templateopts,
3658 ] + templateopts,
3658 _('[-r REV] [FILE]'),
3659 _('[-r REV] [FILE]'),
3659 inferrepo=True)
3660 inferrepo=True)
3660 def parents(ui, repo, file_=None, **opts):
3661 def parents(ui, repo, file_=None, **opts):
3661 """show the parents of the working directory or revision (DEPRECATED)
3662 """show the parents of the working directory or revision (DEPRECATED)
3662
3663
3663 Print the working directory's parent revisions. If a revision is
3664 Print the working directory's parent revisions. If a revision is
3664 given via -r/--rev, the parent of that revision will be printed.
3665 given via -r/--rev, the parent of that revision will be printed.
3665 If a file argument is given, the revision in which the file was
3666 If a file argument is given, the revision in which the file was
3666 last changed (before the working directory revision or the
3667 last changed (before the working directory revision or the
3667 argument to --rev if given) is printed.
3668 argument to --rev if given) is printed.
3668
3669
3669 This command is equivalent to::
3670 This command is equivalent to::
3670
3671
3671 hg log -r "p1()+p2()" or
3672 hg log -r "p1()+p2()" or
3672 hg log -r "p1(REV)+p2(REV)" or
3673 hg log -r "p1(REV)+p2(REV)" or
3673 hg log -r "max(::p1() and file(FILE))+max(::p2() and file(FILE))" or
3674 hg log -r "max(::p1() and file(FILE))+max(::p2() and file(FILE))" or
3674 hg log -r "max(::p1(REV) and file(FILE))+max(::p2(REV) and file(FILE))"
3675 hg log -r "max(::p1(REV) and file(FILE))+max(::p2(REV) and file(FILE))"
3675
3676
3676 See :hg:`summary` and :hg:`help revsets` for related information.
3677 See :hg:`summary` and :hg:`help revsets` for related information.
3677
3678
3678 Returns 0 on success.
3679 Returns 0 on success.
3679 """
3680 """
3680
3681
3681 opts = pycompat.byteskwargs(opts)
3682 opts = pycompat.byteskwargs(opts)
3682 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
3683 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
3683
3684
3684 if file_:
3685 if file_:
3685 m = scmutil.match(ctx, (file_,), opts)
3686 m = scmutil.match(ctx, (file_,), opts)
3686 if m.anypats() or len(m.files()) != 1:
3687 if m.anypats() or len(m.files()) != 1:
3687 raise error.Abort(_('can only specify an explicit filename'))
3688 raise error.Abort(_('can only specify an explicit filename'))
3688 file_ = m.files()[0]
3689 file_ = m.files()[0]
3689 filenodes = []
3690 filenodes = []
3690 for cp in ctx.parents():
3691 for cp in ctx.parents():
3691 if not cp:
3692 if not cp:
3692 continue
3693 continue
3693 try:
3694 try:
3694 filenodes.append(cp.filenode(file_))
3695 filenodes.append(cp.filenode(file_))
3695 except error.LookupError:
3696 except error.LookupError:
3696 pass
3697 pass
3697 if not filenodes:
3698 if not filenodes:
3698 raise error.Abort(_("'%s' not found in manifest!") % file_)
3699 raise error.Abort(_("'%s' not found in manifest!") % file_)
3699 p = []
3700 p = []
3700 for fn in filenodes:
3701 for fn in filenodes:
3701 fctx = repo.filectx(file_, fileid=fn)
3702 fctx = repo.filectx(file_, fileid=fn)
3702 p.append(fctx.node())
3703 p.append(fctx.node())
3703 else:
3704 else:
3704 p = [cp.node() for cp in ctx.parents()]
3705 p = [cp.node() for cp in ctx.parents()]
3705
3706
3706 displayer = cmdutil.show_changeset(ui, repo, opts)
3707 displayer = cmdutil.show_changeset(ui, repo, opts)
3707 for n in p:
3708 for n in p:
3708 if n != nullid:
3709 if n != nullid:
3709 displayer.show(repo[n])
3710 displayer.show(repo[n])
3710 displayer.close()
3711 displayer.close()
3711
3712
3712 @command('paths', formatteropts, _('[NAME]'), optionalrepo=True)
3713 @command('paths', formatteropts, _('[NAME]'), optionalrepo=True)
3713 def paths(ui, repo, search=None, **opts):
3714 def paths(ui, repo, search=None, **opts):
3714 """show aliases for remote repositories
3715 """show aliases for remote repositories
3715
3716
3716 Show definition of symbolic path name NAME. If no name is given,
3717 Show definition of symbolic path name NAME. If no name is given,
3717 show definition of all available names.
3718 show definition of all available names.
3718
3719
3719 Option -q/--quiet suppresses all output when searching for NAME
3720 Option -q/--quiet suppresses all output when searching for NAME
3720 and shows only the path names when listing all definitions.
3721 and shows only the path names when listing all definitions.
3721
3722
3722 Path names are defined in the [paths] section of your
3723 Path names are defined in the [paths] section of your
3723 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
3724 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
3724 repository, ``.hg/hgrc`` is used, too.
3725 repository, ``.hg/hgrc`` is used, too.
3725
3726
3726 The path names ``default`` and ``default-push`` have a special
3727 The path names ``default`` and ``default-push`` have a special
3727 meaning. When performing a push or pull operation, they are used
3728 meaning. When performing a push or pull operation, they are used
3728 as fallbacks if no location is specified on the command-line.
3729 as fallbacks if no location is specified on the command-line.
3729 When ``default-push`` is set, it will be used for push and
3730 When ``default-push`` is set, it will be used for push and
3730 ``default`` will be used for pull; otherwise ``default`` is used
3731 ``default`` will be used for pull; otherwise ``default`` is used
3731 as the fallback for both. When cloning a repository, the clone
3732 as the fallback for both. When cloning a repository, the clone
3732 source is written as ``default`` in ``.hg/hgrc``.
3733 source is written as ``default`` in ``.hg/hgrc``.
3733
3734
3734 .. note::
3735 .. note::
3735
3736
3736 ``default`` and ``default-push`` apply to all inbound (e.g.
3737 ``default`` and ``default-push`` apply to all inbound (e.g.
3737 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email`
3738 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email`
3738 and :hg:`bundle`) operations.
3739 and :hg:`bundle`) operations.
3739
3740
3740 See :hg:`help urls` for more information.
3741 See :hg:`help urls` for more information.
3741
3742
3742 Returns 0 on success.
3743 Returns 0 on success.
3743 """
3744 """
3744
3745
3745 opts = pycompat.byteskwargs(opts)
3746 opts = pycompat.byteskwargs(opts)
3746 ui.pager('paths')
3747 ui.pager('paths')
3747 if search:
3748 if search:
3748 pathitems = [(name, path) for name, path in ui.paths.iteritems()
3749 pathitems = [(name, path) for name, path in ui.paths.iteritems()
3749 if name == search]
3750 if name == search]
3750 else:
3751 else:
3751 pathitems = sorted(ui.paths.iteritems())
3752 pathitems = sorted(ui.paths.iteritems())
3752
3753
3753 fm = ui.formatter('paths', opts)
3754 fm = ui.formatter('paths', opts)
3754 if fm.isplain():
3755 if fm.isplain():
3755 hidepassword = util.hidepassword
3756 hidepassword = util.hidepassword
3756 else:
3757 else:
3757 hidepassword = str
3758 hidepassword = str
3758 if ui.quiet:
3759 if ui.quiet:
3759 namefmt = '%s\n'
3760 namefmt = '%s\n'
3760 else:
3761 else:
3761 namefmt = '%s = '
3762 namefmt = '%s = '
3762 showsubopts = not search and not ui.quiet
3763 showsubopts = not search and not ui.quiet
3763
3764
3764 for name, path in pathitems:
3765 for name, path in pathitems:
3765 fm.startitem()
3766 fm.startitem()
3766 fm.condwrite(not search, 'name', namefmt, name)
3767 fm.condwrite(not search, 'name', namefmt, name)
3767 fm.condwrite(not ui.quiet, 'url', '%s\n', hidepassword(path.rawloc))
3768 fm.condwrite(not ui.quiet, 'url', '%s\n', hidepassword(path.rawloc))
3768 for subopt, value in sorted(path.suboptions.items()):
3769 for subopt, value in sorted(path.suboptions.items()):
3769 assert subopt not in ('name', 'url')
3770 assert subopt not in ('name', 'url')
3770 if showsubopts:
3771 if showsubopts:
3771 fm.plain('%s:%s = ' % (name, subopt))
3772 fm.plain('%s:%s = ' % (name, subopt))
3772 fm.condwrite(showsubopts, subopt, '%s\n', value)
3773 fm.condwrite(showsubopts, subopt, '%s\n', value)
3773
3774
3774 fm.end()
3775 fm.end()
3775
3776
3776 if search and not pathitems:
3777 if search and not pathitems:
3777 if not ui.quiet:
3778 if not ui.quiet:
3778 ui.warn(_("not found!\n"))
3779 ui.warn(_("not found!\n"))
3779 return 1
3780 return 1
3780 else:
3781 else:
3781 return 0
3782 return 0
3782
3783
3783 @command('phase',
3784 @command('phase',
3784 [('p', 'public', False, _('set changeset phase to public')),
3785 [('p', 'public', False, _('set changeset phase to public')),
3785 ('d', 'draft', False, _('set changeset phase to draft')),
3786 ('d', 'draft', False, _('set changeset phase to draft')),
3786 ('s', 'secret', False, _('set changeset phase to secret')),
3787 ('s', 'secret', False, _('set changeset phase to secret')),
3787 ('f', 'force', False, _('allow to move boundary backward')),
3788 ('f', 'force', False, _('allow to move boundary backward')),
3788 ('r', 'rev', [], _('target revision'), _('REV')),
3789 ('r', 'rev', [], _('target revision'), _('REV')),
3789 ],
3790 ],
3790 _('[-p|-d|-s] [-f] [-r] [REV...]'))
3791 _('[-p|-d|-s] [-f] [-r] [REV...]'))
3791 def phase(ui, repo, *revs, **opts):
3792 def phase(ui, repo, *revs, **opts):
3792 """set or show the current phase name
3793 """set or show the current phase name
3793
3794
3794 With no argument, show the phase name of the current revision(s).
3795 With no argument, show the phase name of the current revision(s).
3795
3796
3796 With one of -p/--public, -d/--draft or -s/--secret, change the
3797 With one of -p/--public, -d/--draft or -s/--secret, change the
3797 phase value of the specified revisions.
3798 phase value of the specified revisions.
3798
3799
3799 Unless -f/--force is specified, :hg:`phase` won't move changeset from a
3800 Unless -f/--force is specified, :hg:`phase` won't move changeset from a
3800 lower phase to an higher phase. Phases are ordered as follows::
3801 lower phase to an higher phase. Phases are ordered as follows::
3801
3802
3802 public < draft < secret
3803 public < draft < secret
3803
3804
3804 Returns 0 on success, 1 if some phases could not be changed.
3805 Returns 0 on success, 1 if some phases could not be changed.
3805
3806
3806 (For more information about the phases concept, see :hg:`help phases`.)
3807 (For more information about the phases concept, see :hg:`help phases`.)
3807 """
3808 """
3808 opts = pycompat.byteskwargs(opts)
3809 opts = pycompat.byteskwargs(opts)
3809 # search for a unique phase argument
3810 # search for a unique phase argument
3810 targetphase = None
3811 targetphase = None
3811 for idx, name in enumerate(phases.phasenames):
3812 for idx, name in enumerate(phases.phasenames):
3812 if opts[name]:
3813 if opts[name]:
3813 if targetphase is not None:
3814 if targetphase is not None:
3814 raise error.Abort(_('only one phase can be specified'))
3815 raise error.Abort(_('only one phase can be specified'))
3815 targetphase = idx
3816 targetphase = idx
3816
3817
3817 # look for specified revision
3818 # look for specified revision
3818 revs = list(revs)
3819 revs = list(revs)
3819 revs.extend(opts['rev'])
3820 revs.extend(opts['rev'])
3820 if not revs:
3821 if not revs:
3821 # display both parents as the second parent phase can influence
3822 # display both parents as the second parent phase can influence
3822 # the phase of a merge commit
3823 # the phase of a merge commit
3823 revs = [c.rev() for c in repo[None].parents()]
3824 revs = [c.rev() for c in repo[None].parents()]
3824
3825
3825 revs = scmutil.revrange(repo, revs)
3826 revs = scmutil.revrange(repo, revs)
3826
3827
3827 lock = None
3828 lock = None
3828 ret = 0
3829 ret = 0
3829 if targetphase is None:
3830 if targetphase is None:
3830 # display
3831 # display
3831 for r in revs:
3832 for r in revs:
3832 ctx = repo[r]
3833 ctx = repo[r]
3833 ui.write('%i: %s\n' % (ctx.rev(), ctx.phasestr()))
3834 ui.write('%i: %s\n' % (ctx.rev(), ctx.phasestr()))
3834 else:
3835 else:
3835 tr = None
3836 tr = None
3836 lock = repo.lock()
3837 lock = repo.lock()
3837 try:
3838 try:
3838 tr = repo.transaction("phase")
3839 tr = repo.transaction("phase")
3839 # set phase
3840 # set phase
3840 if not revs:
3841 if not revs:
3841 raise error.Abort(_('empty revision set'))
3842 raise error.Abort(_('empty revision set'))
3842 nodes = [repo[r].node() for r in revs]
3843 nodes = [repo[r].node() for r in revs]
3843 # moving revision from public to draft may hide them
3844 # moving revision from public to draft may hide them
3844 # We have to check result on an unfiltered repository
3845 # We have to check result on an unfiltered repository
3845 unfi = repo.unfiltered()
3846 unfi = repo.unfiltered()
3846 getphase = unfi._phasecache.phase
3847 getphase = unfi._phasecache.phase
3847 olddata = [getphase(unfi, r) for r in unfi]
3848 olddata = [getphase(unfi, r) for r in unfi]
3848 phases.advanceboundary(repo, tr, targetphase, nodes)
3849 phases.advanceboundary(repo, tr, targetphase, nodes)
3849 if opts['force']:
3850 if opts['force']:
3850 phases.retractboundary(repo, tr, targetphase, nodes)
3851 phases.retractboundary(repo, tr, targetphase, nodes)
3851 tr.close()
3852 tr.close()
3852 finally:
3853 finally:
3853 if tr is not None:
3854 if tr is not None:
3854 tr.release()
3855 tr.release()
3855 lock.release()
3856 lock.release()
3856 getphase = unfi._phasecache.phase
3857 getphase = unfi._phasecache.phase
3857 newdata = [getphase(unfi, r) for r in unfi]
3858 newdata = [getphase(unfi, r) for r in unfi]
3858 changes = sum(newdata[r] != olddata[r] for r in unfi)
3859 changes = sum(newdata[r] != olddata[r] for r in unfi)
3859 cl = unfi.changelog
3860 cl = unfi.changelog
3860 rejected = [n for n in nodes
3861 rejected = [n for n in nodes
3861 if newdata[cl.rev(n)] < targetphase]
3862 if newdata[cl.rev(n)] < targetphase]
3862 if rejected:
3863 if rejected:
3863 ui.warn(_('cannot move %i changesets to a higher '
3864 ui.warn(_('cannot move %i changesets to a higher '
3864 'phase, use --force\n') % len(rejected))
3865 'phase, use --force\n') % len(rejected))
3865 ret = 1
3866 ret = 1
3866 if changes:
3867 if changes:
3867 msg = _('phase changed for %i changesets\n') % changes
3868 msg = _('phase changed for %i changesets\n') % changes
3868 if ret:
3869 if ret:
3869 ui.status(msg)
3870 ui.status(msg)
3870 else:
3871 else:
3871 ui.note(msg)
3872 ui.note(msg)
3872 else:
3873 else:
3873 ui.warn(_('no phases changed\n'))
3874 ui.warn(_('no phases changed\n'))
3874 return ret
3875 return ret
3875
3876
3876 def postincoming(ui, repo, modheads, optupdate, checkout, brev):
3877 def postincoming(ui, repo, modheads, optupdate, checkout, brev):
3877 """Run after a changegroup has been added via pull/unbundle
3878 """Run after a changegroup has been added via pull/unbundle
3878
3879
3879 This takes arguments below:
3880 This takes arguments below:
3880
3881
3881 :modheads: change of heads by pull/unbundle
3882 :modheads: change of heads by pull/unbundle
3882 :optupdate: updating working directory is needed or not
3883 :optupdate: updating working directory is needed or not
3883 :checkout: update destination revision (or None to default destination)
3884 :checkout: update destination revision (or None to default destination)
3884 :brev: a name, which might be a bookmark to be activated after updating
3885 :brev: a name, which might be a bookmark to be activated after updating
3885 """
3886 """
3886 if modheads == 0:
3887 if modheads == 0:
3887 return
3888 return
3888 if optupdate:
3889 if optupdate:
3889 try:
3890 try:
3890 return hg.updatetotally(ui, repo, checkout, brev)
3891 return hg.updatetotally(ui, repo, checkout, brev)
3891 except error.UpdateAbort as inst:
3892 except error.UpdateAbort as inst:
3892 msg = _("not updating: %s") % str(inst)
3893 msg = _("not updating: %s") % str(inst)
3893 hint = inst.hint
3894 hint = inst.hint
3894 raise error.UpdateAbort(msg, hint=hint)
3895 raise error.UpdateAbort(msg, hint=hint)
3895 if modheads > 1:
3896 if modheads > 1:
3896 currentbranchheads = len(repo.branchheads())
3897 currentbranchheads = len(repo.branchheads())
3897 if currentbranchheads == modheads:
3898 if currentbranchheads == modheads:
3898 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
3899 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
3899 elif currentbranchheads > 1:
3900 elif currentbranchheads > 1:
3900 ui.status(_("(run 'hg heads .' to see heads, 'hg merge' to "
3901 ui.status(_("(run 'hg heads .' to see heads, 'hg merge' to "
3901 "merge)\n"))
3902 "merge)\n"))
3902 else:
3903 else:
3903 ui.status(_("(run 'hg heads' to see heads)\n"))
3904 ui.status(_("(run 'hg heads' to see heads)\n"))
3904 else:
3905 else:
3905 ui.status(_("(run 'hg update' to get a working copy)\n"))
3906 ui.status(_("(run 'hg update' to get a working copy)\n"))
3906
3907
3907 @command('^pull',
3908 @command('^pull',
3908 [('u', 'update', None,
3909 [('u', 'update', None,
3909 _('update to new branch head if changesets were pulled')),
3910 _('update to new branch head if changesets were pulled')),
3910 ('f', 'force', None, _('run even when remote repository is unrelated')),
3911 ('f', 'force', None, _('run even when remote repository is unrelated')),
3911 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
3912 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
3912 ('B', 'bookmark', [], _("bookmark to pull"), _('BOOKMARK')),
3913 ('B', 'bookmark', [], _("bookmark to pull"), _('BOOKMARK')),
3913 ('b', 'branch', [], _('a specific branch you would like to pull'),
3914 ('b', 'branch', [], _('a specific branch you would like to pull'),
3914 _('BRANCH')),
3915 _('BRANCH')),
3915 ] + remoteopts,
3916 ] + remoteopts,
3916 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'))
3917 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'))
3917 def pull(ui, repo, source="default", **opts):
3918 def pull(ui, repo, source="default", **opts):
3918 """pull changes from the specified source
3919 """pull changes from the specified source
3919
3920
3920 Pull changes from a remote repository to a local one.
3921 Pull changes from a remote repository to a local one.
3921
3922
3922 This finds all changes from the repository at the specified path
3923 This finds all changes from the repository at the specified path
3923 or URL and adds them to a local repository (the current one unless
3924 or URL and adds them to a local repository (the current one unless
3924 -R is specified). By default, this does not update the copy of the
3925 -R is specified). By default, this does not update the copy of the
3925 project in the working directory.
3926 project in the working directory.
3926
3927
3927 Use :hg:`incoming` if you want to see what would have been added
3928 Use :hg:`incoming` if you want to see what would have been added
3928 by a pull at the time you issued this command. If you then decide
3929 by a pull at the time you issued this command. If you then decide
3929 to add those changes to the repository, you should use :hg:`pull
3930 to add those changes to the repository, you should use :hg:`pull
3930 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
3931 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
3931
3932
3932 If SOURCE is omitted, the 'default' path will be used.
3933 If SOURCE is omitted, the 'default' path will be used.
3933 See :hg:`help urls` for more information.
3934 See :hg:`help urls` for more information.
3934
3935
3935 Specifying bookmark as ``.`` is equivalent to specifying the active
3936 Specifying bookmark as ``.`` is equivalent to specifying the active
3936 bookmark's name.
3937 bookmark's name.
3937
3938
3938 Returns 0 on success, 1 if an update had unresolved files.
3939 Returns 0 on success, 1 if an update had unresolved files.
3939 """
3940 """
3940
3941
3941 opts = pycompat.byteskwargs(opts)
3942 opts = pycompat.byteskwargs(opts)
3942 if ui.configbool('commands', 'update.requiredest') and opts.get('update'):
3943 if ui.configbool('commands', 'update.requiredest') and opts.get('update'):
3943 msg = _('update destination required by configuration')
3944 msg = _('update destination required by configuration')
3944 hint = _('use hg pull followed by hg update DEST')
3945 hint = _('use hg pull followed by hg update DEST')
3945 raise error.Abort(msg, hint=hint)
3946 raise error.Abort(msg, hint=hint)
3946
3947
3947 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
3948 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
3948 ui.status(_('pulling from %s\n') % util.hidepassword(source))
3949 ui.status(_('pulling from %s\n') % util.hidepassword(source))
3949 other = hg.peer(repo, opts, source)
3950 other = hg.peer(repo, opts, source)
3950 try:
3951 try:
3951 revs, checkout = hg.addbranchrevs(repo, other, branches,
3952 revs, checkout = hg.addbranchrevs(repo, other, branches,
3952 opts.get('rev'))
3953 opts.get('rev'))
3953
3954
3954
3955
3955 pullopargs = {}
3956 pullopargs = {}
3956 if opts.get('bookmark'):
3957 if opts.get('bookmark'):
3957 if not revs:
3958 if not revs:
3958 revs = []
3959 revs = []
3959 # The list of bookmark used here is not the one used to actually
3960 # The list of bookmark used here is not the one used to actually
3960 # update the bookmark name. This can result in the revision pulled
3961 # update the bookmark name. This can result in the revision pulled
3961 # not ending up with the name of the bookmark because of a race
3962 # not ending up with the name of the bookmark because of a race
3962 # condition on the server. (See issue 4689 for details)
3963 # condition on the server. (See issue 4689 for details)
3963 remotebookmarks = other.listkeys('bookmarks')
3964 remotebookmarks = other.listkeys('bookmarks')
3964 pullopargs['remotebookmarks'] = remotebookmarks
3965 pullopargs['remotebookmarks'] = remotebookmarks
3965 for b in opts['bookmark']:
3966 for b in opts['bookmark']:
3966 b = repo._bookmarks.expandname(b)
3967 b = repo._bookmarks.expandname(b)
3967 if b not in remotebookmarks:
3968 if b not in remotebookmarks:
3968 raise error.Abort(_('remote bookmark %s not found!') % b)
3969 raise error.Abort(_('remote bookmark %s not found!') % b)
3969 revs.append(remotebookmarks[b])
3970 revs.append(remotebookmarks[b])
3970
3971
3971 if revs:
3972 if revs:
3972 try:
3973 try:
3973 # When 'rev' is a bookmark name, we cannot guarantee that it
3974 # When 'rev' is a bookmark name, we cannot guarantee that it
3974 # will be updated with that name because of a race condition
3975 # will be updated with that name because of a race condition
3975 # server side. (See issue 4689 for details)
3976 # server side. (See issue 4689 for details)
3976 oldrevs = revs
3977 oldrevs = revs
3977 revs = [] # actually, nodes
3978 revs = [] # actually, nodes
3978 for r in oldrevs:
3979 for r in oldrevs:
3979 node = other.lookup(r)
3980 node = other.lookup(r)
3980 revs.append(node)
3981 revs.append(node)
3981 if r == checkout:
3982 if r == checkout:
3982 checkout = node
3983 checkout = node
3983 except error.CapabilityError:
3984 except error.CapabilityError:
3984 err = _("other repository doesn't support revision lookup, "
3985 err = _("other repository doesn't support revision lookup, "
3985 "so a rev cannot be specified.")
3986 "so a rev cannot be specified.")
3986 raise error.Abort(err)
3987 raise error.Abort(err)
3987
3988
3988 pullopargs.update(opts.get('opargs', {}))
3989 pullopargs.update(opts.get('opargs', {}))
3989 modheads = exchange.pull(repo, other, heads=revs,
3990 modheads = exchange.pull(repo, other, heads=revs,
3990 force=opts.get('force'),
3991 force=opts.get('force'),
3991 bookmarks=opts.get('bookmark', ()),
3992 bookmarks=opts.get('bookmark', ()),
3992 opargs=pullopargs).cgresult
3993 opargs=pullopargs).cgresult
3993
3994
3994 # brev is a name, which might be a bookmark to be activated at
3995 # brev is a name, which might be a bookmark to be activated at
3995 # the end of the update. In other words, it is an explicit
3996 # the end of the update. In other words, it is an explicit
3996 # destination of the update
3997 # destination of the update
3997 brev = None
3998 brev = None
3998
3999
3999 if checkout:
4000 if checkout:
4000 checkout = str(repo.changelog.rev(checkout))
4001 checkout = str(repo.changelog.rev(checkout))
4001
4002
4002 # order below depends on implementation of
4003 # order below depends on implementation of
4003 # hg.addbranchrevs(). opts['bookmark'] is ignored,
4004 # hg.addbranchrevs(). opts['bookmark'] is ignored,
4004 # because 'checkout' is determined without it.
4005 # because 'checkout' is determined without it.
4005 if opts.get('rev'):
4006 if opts.get('rev'):
4006 brev = opts['rev'][0]
4007 brev = opts['rev'][0]
4007 elif opts.get('branch'):
4008 elif opts.get('branch'):
4008 brev = opts['branch'][0]
4009 brev = opts['branch'][0]
4009 else:
4010 else:
4010 brev = branches[0]
4011 brev = branches[0]
4011 repo._subtoppath = source
4012 repo._subtoppath = source
4012 try:
4013 try:
4013 ret = postincoming(ui, repo, modheads, opts.get('update'),
4014 ret = postincoming(ui, repo, modheads, opts.get('update'),
4014 checkout, brev)
4015 checkout, brev)
4015
4016
4016 finally:
4017 finally:
4017 del repo._subtoppath
4018 del repo._subtoppath
4018
4019
4019 finally:
4020 finally:
4020 other.close()
4021 other.close()
4021 return ret
4022 return ret
4022
4023
4023 @command('^push',
4024 @command('^push',
4024 [('f', 'force', None, _('force push')),
4025 [('f', 'force', None, _('force push')),
4025 ('r', 'rev', [],
4026 ('r', 'rev', [],
4026 _('a changeset intended to be included in the destination'),
4027 _('a changeset intended to be included in the destination'),
4027 _('REV')),
4028 _('REV')),
4028 ('B', 'bookmark', [], _("bookmark to push"), _('BOOKMARK')),
4029 ('B', 'bookmark', [], _("bookmark to push"), _('BOOKMARK')),
4029 ('b', 'branch', [],
4030 ('b', 'branch', [],
4030 _('a specific branch you would like to push'), _('BRANCH')),
4031 _('a specific branch you would like to push'), _('BRANCH')),
4031 ('', 'new-branch', False, _('allow pushing a new branch')),
4032 ('', 'new-branch', False, _('allow pushing a new branch')),
4032 ] + remoteopts,
4033 ] + remoteopts,
4033 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'))
4034 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'))
4034 def push(ui, repo, dest=None, **opts):
4035 def push(ui, repo, dest=None, **opts):
4035 """push changes to the specified destination
4036 """push changes to the specified destination
4036
4037
4037 Push changesets from the local repository to the specified
4038 Push changesets from the local repository to the specified
4038 destination.
4039 destination.
4039
4040
4040 This operation is symmetrical to pull: it is identical to a pull
4041 This operation is symmetrical to pull: it is identical to a pull
4041 in the destination repository from the current one.
4042 in the destination repository from the current one.
4042
4043
4043 By default, push will not allow creation of new heads at the
4044 By default, push will not allow creation of new heads at the
4044 destination, since multiple heads would make it unclear which head
4045 destination, since multiple heads would make it unclear which head
4045 to use. In this situation, it is recommended to pull and merge
4046 to use. In this situation, it is recommended to pull and merge
4046 before pushing.
4047 before pushing.
4047
4048
4048 Use --new-branch if you want to allow push to create a new named
4049 Use --new-branch if you want to allow push to create a new named
4049 branch that is not present at the destination. This allows you to
4050 branch that is not present at the destination. This allows you to
4050 only create a new branch without forcing other changes.
4051 only create a new branch without forcing other changes.
4051
4052
4052 .. note::
4053 .. note::
4053
4054
4054 Extra care should be taken with the -f/--force option,
4055 Extra care should be taken with the -f/--force option,
4055 which will push all new heads on all branches, an action which will
4056 which will push all new heads on all branches, an action which will
4056 almost always cause confusion for collaborators.
4057 almost always cause confusion for collaborators.
4057
4058
4058 If -r/--rev is used, the specified revision and all its ancestors
4059 If -r/--rev is used, the specified revision and all its ancestors
4059 will be pushed to the remote repository.
4060 will be pushed to the remote repository.
4060
4061
4061 If -B/--bookmark is used, the specified bookmarked revision, its
4062 If -B/--bookmark is used, the specified bookmarked revision, its
4062 ancestors, and the bookmark will be pushed to the remote
4063 ancestors, and the bookmark will be pushed to the remote
4063 repository. Specifying ``.`` is equivalent to specifying the active
4064 repository. Specifying ``.`` is equivalent to specifying the active
4064 bookmark's name.
4065 bookmark's name.
4065
4066
4066 Please see :hg:`help urls` for important details about ``ssh://``
4067 Please see :hg:`help urls` for important details about ``ssh://``
4067 URLs. If DESTINATION is omitted, a default path will be used.
4068 URLs. If DESTINATION is omitted, a default path will be used.
4068
4069
4069 Returns 0 if push was successful, 1 if nothing to push.
4070 Returns 0 if push was successful, 1 if nothing to push.
4070 """
4071 """
4071
4072
4072 opts = pycompat.byteskwargs(opts)
4073 opts = pycompat.byteskwargs(opts)
4073 if opts.get('bookmark'):
4074 if opts.get('bookmark'):
4074 ui.setconfig('bookmarks', 'pushing', opts['bookmark'], 'push')
4075 ui.setconfig('bookmarks', 'pushing', opts['bookmark'], 'push')
4075 for b in opts['bookmark']:
4076 for b in opts['bookmark']:
4076 # translate -B options to -r so changesets get pushed
4077 # translate -B options to -r so changesets get pushed
4077 b = repo._bookmarks.expandname(b)
4078 b = repo._bookmarks.expandname(b)
4078 if b in repo._bookmarks:
4079 if b in repo._bookmarks:
4079 opts.setdefault('rev', []).append(b)
4080 opts.setdefault('rev', []).append(b)
4080 else:
4081 else:
4081 # if we try to push a deleted bookmark, translate it to null
4082 # if we try to push a deleted bookmark, translate it to null
4082 # this lets simultaneous -r, -b options continue working
4083 # this lets simultaneous -r, -b options continue working
4083 opts.setdefault('rev', []).append("null")
4084 opts.setdefault('rev', []).append("null")
4084
4085
4085 path = ui.paths.getpath(dest, default=('default-push', 'default'))
4086 path = ui.paths.getpath(dest, default=('default-push', 'default'))
4086 if not path:
4087 if not path:
4087 raise error.Abort(_('default repository not configured!'),
4088 raise error.Abort(_('default repository not configured!'),
4088 hint=_("see 'hg help config.paths'"))
4089 hint=_("see 'hg help config.paths'"))
4089 dest = path.pushloc or path.loc
4090 dest = path.pushloc or path.loc
4090 branches = (path.branch, opts.get('branch') or [])
4091 branches = (path.branch, opts.get('branch') or [])
4091 ui.status(_('pushing to %s\n') % util.hidepassword(dest))
4092 ui.status(_('pushing to %s\n') % util.hidepassword(dest))
4092 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
4093 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
4093 other = hg.peer(repo, opts, dest)
4094 other = hg.peer(repo, opts, dest)
4094
4095
4095 if revs:
4096 if revs:
4096 revs = [repo.lookup(r) for r in scmutil.revrange(repo, revs)]
4097 revs = [repo.lookup(r) for r in scmutil.revrange(repo, revs)]
4097 if not revs:
4098 if not revs:
4098 raise error.Abort(_("specified revisions evaluate to an empty set"),
4099 raise error.Abort(_("specified revisions evaluate to an empty set"),
4099 hint=_("use different revision arguments"))
4100 hint=_("use different revision arguments"))
4100 elif path.pushrev:
4101 elif path.pushrev:
4101 # It doesn't make any sense to specify ancestor revisions. So limit
4102 # It doesn't make any sense to specify ancestor revisions. So limit
4102 # to DAG heads to make discovery simpler.
4103 # to DAG heads to make discovery simpler.
4103 expr = revsetlang.formatspec('heads(%r)', path.pushrev)
4104 expr = revsetlang.formatspec('heads(%r)', path.pushrev)
4104 revs = scmutil.revrange(repo, [expr])
4105 revs = scmutil.revrange(repo, [expr])
4105 revs = [repo[rev].node() for rev in revs]
4106 revs = [repo[rev].node() for rev in revs]
4106 if not revs:
4107 if not revs:
4107 raise error.Abort(_('default push revset for path evaluates to an '
4108 raise error.Abort(_('default push revset for path evaluates to an '
4108 'empty set'))
4109 'empty set'))
4109
4110
4110 repo._subtoppath = dest
4111 repo._subtoppath = dest
4111 try:
4112 try:
4112 # push subrepos depth-first for coherent ordering
4113 # push subrepos depth-first for coherent ordering
4113 c = repo['']
4114 c = repo['']
4114 subs = c.substate # only repos that are committed
4115 subs = c.substate # only repos that are committed
4115 for s in sorted(subs):
4116 for s in sorted(subs):
4116 result = c.sub(s).push(opts)
4117 result = c.sub(s).push(opts)
4117 if result == 0:
4118 if result == 0:
4118 return not result
4119 return not result
4119 finally:
4120 finally:
4120 del repo._subtoppath
4121 del repo._subtoppath
4121 pushop = exchange.push(repo, other, opts.get('force'), revs=revs,
4122 pushop = exchange.push(repo, other, opts.get('force'), revs=revs,
4122 newbranch=opts.get('new_branch'),
4123 newbranch=opts.get('new_branch'),
4123 bookmarks=opts.get('bookmark', ()),
4124 bookmarks=opts.get('bookmark', ()),
4124 opargs=opts.get('opargs'))
4125 opargs=opts.get('opargs'))
4125
4126
4126 result = not pushop.cgresult
4127 result = not pushop.cgresult
4127
4128
4128 if pushop.bkresult is not None:
4129 if pushop.bkresult is not None:
4129 if pushop.bkresult == 2:
4130 if pushop.bkresult == 2:
4130 result = 2
4131 result = 2
4131 elif not result and pushop.bkresult:
4132 elif not result and pushop.bkresult:
4132 result = 2
4133 result = 2
4133
4134
4134 return result
4135 return result
4135
4136
4136 @command('recover', [])
4137 @command('recover', [])
4137 def recover(ui, repo):
4138 def recover(ui, repo):
4138 """roll back an interrupted transaction
4139 """roll back an interrupted transaction
4139
4140
4140 Recover from an interrupted commit or pull.
4141 Recover from an interrupted commit or pull.
4141
4142
4142 This command tries to fix the repository status after an
4143 This command tries to fix the repository status after an
4143 interrupted operation. It should only be necessary when Mercurial
4144 interrupted operation. It should only be necessary when Mercurial
4144 suggests it.
4145 suggests it.
4145
4146
4146 Returns 0 if successful, 1 if nothing to recover or verify fails.
4147 Returns 0 if successful, 1 if nothing to recover or verify fails.
4147 """
4148 """
4148 if repo.recover():
4149 if repo.recover():
4149 return hg.verify(repo)
4150 return hg.verify(repo)
4150 return 1
4151 return 1
4151
4152
4152 @command('^remove|rm',
4153 @command('^remove|rm',
4153 [('A', 'after', None, _('record delete for missing files')),
4154 [('A', 'after', None, _('record delete for missing files')),
4154 ('f', 'force', None,
4155 ('f', 'force', None,
4155 _('forget added files, delete modified files')),
4156 _('forget added files, delete modified files')),
4156 ] + subrepoopts + walkopts,
4157 ] + subrepoopts + walkopts,
4157 _('[OPTION]... FILE...'),
4158 _('[OPTION]... FILE...'),
4158 inferrepo=True)
4159 inferrepo=True)
4159 def remove(ui, repo, *pats, **opts):
4160 def remove(ui, repo, *pats, **opts):
4160 """remove the specified files on the next commit
4161 """remove the specified files on the next commit
4161
4162
4162 Schedule the indicated files for removal from the current branch.
4163 Schedule the indicated files for removal from the current branch.
4163
4164
4164 This command schedules the files to be removed at the next commit.
4165 This command schedules the files to be removed at the next commit.
4165 To undo a remove before that, see :hg:`revert`. To undo added
4166 To undo a remove before that, see :hg:`revert`. To undo added
4166 files, see :hg:`forget`.
4167 files, see :hg:`forget`.
4167
4168
4168 .. container:: verbose
4169 .. container:: verbose
4169
4170
4170 -A/--after can be used to remove only files that have already
4171 -A/--after can be used to remove only files that have already
4171 been deleted, -f/--force can be used to force deletion, and -Af
4172 been deleted, -f/--force can be used to force deletion, and -Af
4172 can be used to remove files from the next revision without
4173 can be used to remove files from the next revision without
4173 deleting them from the working directory.
4174 deleting them from the working directory.
4174
4175
4175 The following table details the behavior of remove for different
4176 The following table details the behavior of remove for different
4176 file states (columns) and option combinations (rows). The file
4177 file states (columns) and option combinations (rows). The file
4177 states are Added [A], Clean [C], Modified [M] and Missing [!]
4178 states are Added [A], Clean [C], Modified [M] and Missing [!]
4178 (as reported by :hg:`status`). The actions are Warn, Remove
4179 (as reported by :hg:`status`). The actions are Warn, Remove
4179 (from branch) and Delete (from disk):
4180 (from branch) and Delete (from disk):
4180
4181
4181 ========= == == == ==
4182 ========= == == == ==
4182 opt/state A C M !
4183 opt/state A C M !
4183 ========= == == == ==
4184 ========= == == == ==
4184 none W RD W R
4185 none W RD W R
4185 -f R RD RD R
4186 -f R RD RD R
4186 -A W W W R
4187 -A W W W R
4187 -Af R R R R
4188 -Af R R R R
4188 ========= == == == ==
4189 ========= == == == ==
4189
4190
4190 .. note::
4191 .. note::
4191
4192
4192 :hg:`remove` never deletes files in Added [A] state from the
4193 :hg:`remove` never deletes files in Added [A] state from the
4193 working directory, not even if ``--force`` is specified.
4194 working directory, not even if ``--force`` is specified.
4194
4195
4195 Returns 0 on success, 1 if any warnings encountered.
4196 Returns 0 on success, 1 if any warnings encountered.
4196 """
4197 """
4197
4198
4198 opts = pycompat.byteskwargs(opts)
4199 opts = pycompat.byteskwargs(opts)
4199 after, force = opts.get('after'), opts.get('force')
4200 after, force = opts.get('after'), opts.get('force')
4200 if not pats and not after:
4201 if not pats and not after:
4201 raise error.Abort(_('no files specified'))
4202 raise error.Abort(_('no files specified'))
4202
4203
4203 m = scmutil.match(repo[None], pats, opts)
4204 m = scmutil.match(repo[None], pats, opts)
4204 subrepos = opts.get('subrepos')
4205 subrepos = opts.get('subrepos')
4205 return cmdutil.remove(ui, repo, m, "", after, force, subrepos)
4206 return cmdutil.remove(ui, repo, m, "", after, force, subrepos)
4206
4207
4207 @command('rename|move|mv',
4208 @command('rename|move|mv',
4208 [('A', 'after', None, _('record a rename that has already occurred')),
4209 [('A', 'after', None, _('record a rename that has already occurred')),
4209 ('f', 'force', None, _('forcibly copy over an existing managed file')),
4210 ('f', 'force', None, _('forcibly copy over an existing managed file')),
4210 ] + walkopts + dryrunopts,
4211 ] + walkopts + dryrunopts,
4211 _('[OPTION]... SOURCE... DEST'))
4212 _('[OPTION]... SOURCE... DEST'))
4212 def rename(ui, repo, *pats, **opts):
4213 def rename(ui, repo, *pats, **opts):
4213 """rename files; equivalent of copy + remove
4214 """rename files; equivalent of copy + remove
4214
4215
4215 Mark dest as copies of sources; mark sources for deletion. If dest
4216 Mark dest as copies of sources; mark sources for deletion. If dest
4216 is a directory, copies are put in that directory. If dest is a
4217 is a directory, copies are put in that directory. If dest is a
4217 file, there can only be one source.
4218 file, there can only be one source.
4218
4219
4219 By default, this command copies the contents of files as they
4220 By default, this command copies the contents of files as they
4220 exist in the working directory. If invoked with -A/--after, the
4221 exist in the working directory. If invoked with -A/--after, the
4221 operation is recorded, but no copying is performed.
4222 operation is recorded, but no copying is performed.
4222
4223
4223 This command takes effect at the next commit. To undo a rename
4224 This command takes effect at the next commit. To undo a rename
4224 before that, see :hg:`revert`.
4225 before that, see :hg:`revert`.
4225
4226
4226 Returns 0 on success, 1 if errors are encountered.
4227 Returns 0 on success, 1 if errors are encountered.
4227 """
4228 """
4228 opts = pycompat.byteskwargs(opts)
4229 opts = pycompat.byteskwargs(opts)
4229 with repo.wlock(False):
4230 with repo.wlock(False):
4230 return cmdutil.copy(ui, repo, pats, opts, rename=True)
4231 return cmdutil.copy(ui, repo, pats, opts, rename=True)
4231
4232
4232 @command('resolve',
4233 @command('resolve',
4233 [('a', 'all', None, _('select all unresolved files')),
4234 [('a', 'all', None, _('select all unresolved files')),
4234 ('l', 'list', None, _('list state of files needing merge')),
4235 ('l', 'list', None, _('list state of files needing merge')),
4235 ('m', 'mark', None, _('mark files as resolved')),
4236 ('m', 'mark', None, _('mark files as resolved')),
4236 ('u', 'unmark', None, _('mark files as unresolved')),
4237 ('u', 'unmark', None, _('mark files as unresolved')),
4237 ('n', 'no-status', None, _('hide status prefix'))]
4238 ('n', 'no-status', None, _('hide status prefix'))]
4238 + mergetoolopts + walkopts + formatteropts,
4239 + mergetoolopts + walkopts + formatteropts,
4239 _('[OPTION]... [FILE]...'),
4240 _('[OPTION]... [FILE]...'),
4240 inferrepo=True)
4241 inferrepo=True)
4241 def resolve(ui, repo, *pats, **opts):
4242 def resolve(ui, repo, *pats, **opts):
4242 """redo merges or set/view the merge status of files
4243 """redo merges or set/view the merge status of files
4243
4244
4244 Merges with unresolved conflicts are often the result of
4245 Merges with unresolved conflicts are often the result of
4245 non-interactive merging using the ``internal:merge`` configuration
4246 non-interactive merging using the ``internal:merge`` configuration
4246 setting, or a command-line merge tool like ``diff3``. The resolve
4247 setting, or a command-line merge tool like ``diff3``. The resolve
4247 command is used to manage the files involved in a merge, after
4248 command is used to manage the files involved in a merge, after
4248 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
4249 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
4249 working directory must have two parents). See :hg:`help
4250 working directory must have two parents). See :hg:`help
4250 merge-tools` for information on configuring merge tools.
4251 merge-tools` for information on configuring merge tools.
4251
4252
4252 The resolve command can be used in the following ways:
4253 The resolve command can be used in the following ways:
4253
4254
4254 - :hg:`resolve [--tool TOOL] FILE...`: attempt to re-merge the specified
4255 - :hg:`resolve [--tool TOOL] FILE...`: attempt to re-merge the specified
4255 files, discarding any previous merge attempts. Re-merging is not
4256 files, discarding any previous merge attempts. Re-merging is not
4256 performed for files already marked as resolved. Use ``--all/-a``
4257 performed for files already marked as resolved. Use ``--all/-a``
4257 to select all unresolved files. ``--tool`` can be used to specify
4258 to select all unresolved files. ``--tool`` can be used to specify
4258 the merge tool used for the given files. It overrides the HGMERGE
4259 the merge tool used for the given files. It overrides the HGMERGE
4259 environment variable and your configuration files. Previous file
4260 environment variable and your configuration files. Previous file
4260 contents are saved with a ``.orig`` suffix.
4261 contents are saved with a ``.orig`` suffix.
4261
4262
4262 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
4263 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
4263 (e.g. after having manually fixed-up the files). The default is
4264 (e.g. after having manually fixed-up the files). The default is
4264 to mark all unresolved files.
4265 to mark all unresolved files.
4265
4266
4266 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
4267 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
4267 default is to mark all resolved files.
4268 default is to mark all resolved files.
4268
4269
4269 - :hg:`resolve -l`: list files which had or still have conflicts.
4270 - :hg:`resolve -l`: list files which had or still have conflicts.
4270 In the printed list, ``U`` = unresolved and ``R`` = resolved.
4271 In the printed list, ``U`` = unresolved and ``R`` = resolved.
4271 You can use ``set:unresolved()`` or ``set:resolved()`` to filter
4272 You can use ``set:unresolved()`` or ``set:resolved()`` to filter
4272 the list. See :hg:`help filesets` for details.
4273 the list. See :hg:`help filesets` for details.
4273
4274
4274 .. note::
4275 .. note::
4275
4276
4276 Mercurial will not let you commit files with unresolved merge
4277 Mercurial will not let you commit files with unresolved merge
4277 conflicts. You must use :hg:`resolve -m ...` before you can
4278 conflicts. You must use :hg:`resolve -m ...` before you can
4278 commit after a conflicting merge.
4279 commit after a conflicting merge.
4279
4280
4280 Returns 0 on success, 1 if any files fail a resolve attempt.
4281 Returns 0 on success, 1 if any files fail a resolve attempt.
4281 """
4282 """
4282
4283
4283 opts = pycompat.byteskwargs(opts)
4284 opts = pycompat.byteskwargs(opts)
4284 flaglist = 'all mark unmark list no_status'.split()
4285 flaglist = 'all mark unmark list no_status'.split()
4285 all, mark, unmark, show, nostatus = \
4286 all, mark, unmark, show, nostatus = \
4286 [opts.get(o) for o in flaglist]
4287 [opts.get(o) for o in flaglist]
4287
4288
4288 if (show and (mark or unmark)) or (mark and unmark):
4289 if (show and (mark or unmark)) or (mark and unmark):
4289 raise error.Abort(_("too many options specified"))
4290 raise error.Abort(_("too many options specified"))
4290 if pats and all:
4291 if pats and all:
4291 raise error.Abort(_("can't specify --all and patterns"))
4292 raise error.Abort(_("can't specify --all and patterns"))
4292 if not (all or pats or show or mark or unmark):
4293 if not (all or pats or show or mark or unmark):
4293 raise error.Abort(_('no files or directories specified'),
4294 raise error.Abort(_('no files or directories specified'),
4294 hint=('use --all to re-merge all unresolved files'))
4295 hint=('use --all to re-merge all unresolved files'))
4295
4296
4296 if show:
4297 if show:
4297 ui.pager('resolve')
4298 ui.pager('resolve')
4298 fm = ui.formatter('resolve', opts)
4299 fm = ui.formatter('resolve', opts)
4299 ms = mergemod.mergestate.read(repo)
4300 ms = mergemod.mergestate.read(repo)
4300 m = scmutil.match(repo[None], pats, opts)
4301 m = scmutil.match(repo[None], pats, opts)
4301 for f in ms:
4302 for f in ms:
4302 if not m(f):
4303 if not m(f):
4303 continue
4304 continue
4304 l = 'resolve.' + {'u': 'unresolved', 'r': 'resolved',
4305 l = 'resolve.' + {'u': 'unresolved', 'r': 'resolved',
4305 'd': 'driverresolved'}[ms[f]]
4306 'd': 'driverresolved'}[ms[f]]
4306 fm.startitem()
4307 fm.startitem()
4307 fm.condwrite(not nostatus, 'status', '%s ', ms[f].upper(), label=l)
4308 fm.condwrite(not nostatus, 'status', '%s ', ms[f].upper(), label=l)
4308 fm.write('path', '%s\n', f, label=l)
4309 fm.write('path', '%s\n', f, label=l)
4309 fm.end()
4310 fm.end()
4310 return 0
4311 return 0
4311
4312
4312 with repo.wlock():
4313 with repo.wlock():
4313 ms = mergemod.mergestate.read(repo)
4314 ms = mergemod.mergestate.read(repo)
4314
4315
4315 if not (ms.active() or repo.dirstate.p2() != nullid):
4316 if not (ms.active() or repo.dirstate.p2() != nullid):
4316 raise error.Abort(
4317 raise error.Abort(
4317 _('resolve command not applicable when not merging'))
4318 _('resolve command not applicable when not merging'))
4318
4319
4319 wctx = repo[None]
4320 wctx = repo[None]
4320
4321
4321 if ms.mergedriver and ms.mdstate() == 'u':
4322 if ms.mergedriver and ms.mdstate() == 'u':
4322 proceed = mergemod.driverpreprocess(repo, ms, wctx)
4323 proceed = mergemod.driverpreprocess(repo, ms, wctx)
4323 ms.commit()
4324 ms.commit()
4324 # allow mark and unmark to go through
4325 # allow mark and unmark to go through
4325 if not mark and not unmark and not proceed:
4326 if not mark and not unmark and not proceed:
4326 return 1
4327 return 1
4327
4328
4328 m = scmutil.match(wctx, pats, opts)
4329 m = scmutil.match(wctx, pats, opts)
4329 ret = 0
4330 ret = 0
4330 didwork = False
4331 didwork = False
4331 runconclude = False
4332 runconclude = False
4332
4333
4333 tocomplete = []
4334 tocomplete = []
4334 for f in ms:
4335 for f in ms:
4335 if not m(f):
4336 if not m(f):
4336 continue
4337 continue
4337
4338
4338 didwork = True
4339 didwork = True
4339
4340
4340 # don't let driver-resolved files be marked, and run the conclude
4341 # don't let driver-resolved files be marked, and run the conclude
4341 # step if asked to resolve
4342 # step if asked to resolve
4342 if ms[f] == "d":
4343 if ms[f] == "d":
4343 exact = m.exact(f)
4344 exact = m.exact(f)
4344 if mark:
4345 if mark:
4345 if exact:
4346 if exact:
4346 ui.warn(_('not marking %s as it is driver-resolved\n')
4347 ui.warn(_('not marking %s as it is driver-resolved\n')
4347 % f)
4348 % f)
4348 elif unmark:
4349 elif unmark:
4349 if exact:
4350 if exact:
4350 ui.warn(_('not unmarking %s as it is driver-resolved\n')
4351 ui.warn(_('not unmarking %s as it is driver-resolved\n')
4351 % f)
4352 % f)
4352 else:
4353 else:
4353 runconclude = True
4354 runconclude = True
4354 continue
4355 continue
4355
4356
4356 if mark:
4357 if mark:
4357 ms.mark(f, "r")
4358 ms.mark(f, "r")
4358 elif unmark:
4359 elif unmark:
4359 ms.mark(f, "u")
4360 ms.mark(f, "u")
4360 else:
4361 else:
4361 # backup pre-resolve (merge uses .orig for its own purposes)
4362 # backup pre-resolve (merge uses .orig for its own purposes)
4362 a = repo.wjoin(f)
4363 a = repo.wjoin(f)
4363 try:
4364 try:
4364 util.copyfile(a, a + ".resolve")
4365 util.copyfile(a, a + ".resolve")
4365 except (IOError, OSError) as inst:
4366 except (IOError, OSError) as inst:
4366 if inst.errno != errno.ENOENT:
4367 if inst.errno != errno.ENOENT:
4367 raise
4368 raise
4368
4369
4369 try:
4370 try:
4370 # preresolve file
4371 # preresolve file
4371 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
4372 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
4372 'resolve')
4373 'resolve')
4373 complete, r = ms.preresolve(f, wctx)
4374 complete, r = ms.preresolve(f, wctx)
4374 if not complete:
4375 if not complete:
4375 tocomplete.append(f)
4376 tocomplete.append(f)
4376 elif r:
4377 elif r:
4377 ret = 1
4378 ret = 1
4378 finally:
4379 finally:
4379 ui.setconfig('ui', 'forcemerge', '', 'resolve')
4380 ui.setconfig('ui', 'forcemerge', '', 'resolve')
4380 ms.commit()
4381 ms.commit()
4381
4382
4382 # replace filemerge's .orig file with our resolve file, but only
4383 # replace filemerge's .orig file with our resolve file, but only
4383 # for merges that are complete
4384 # for merges that are complete
4384 if complete:
4385 if complete:
4385 try:
4386 try:
4386 util.rename(a + ".resolve",
4387 util.rename(a + ".resolve",
4387 scmutil.origpath(ui, repo, a))
4388 scmutil.origpath(ui, repo, a))
4388 except OSError as inst:
4389 except OSError as inst:
4389 if inst.errno != errno.ENOENT:
4390 if inst.errno != errno.ENOENT:
4390 raise
4391 raise
4391
4392
4392 for f in tocomplete:
4393 for f in tocomplete:
4393 try:
4394 try:
4394 # resolve file
4395 # resolve file
4395 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
4396 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
4396 'resolve')
4397 'resolve')
4397 r = ms.resolve(f, wctx)
4398 r = ms.resolve(f, wctx)
4398 if r:
4399 if r:
4399 ret = 1
4400 ret = 1
4400 finally:
4401 finally:
4401 ui.setconfig('ui', 'forcemerge', '', 'resolve')
4402 ui.setconfig('ui', 'forcemerge', '', 'resolve')
4402 ms.commit()
4403 ms.commit()
4403
4404
4404 # replace filemerge's .orig file with our resolve file
4405 # replace filemerge's .orig file with our resolve file
4405 a = repo.wjoin(f)
4406 a = repo.wjoin(f)
4406 try:
4407 try:
4407 util.rename(a + ".resolve", scmutil.origpath(ui, repo, a))
4408 util.rename(a + ".resolve", scmutil.origpath(ui, repo, a))
4408 except OSError as inst:
4409 except OSError as inst:
4409 if inst.errno != errno.ENOENT:
4410 if inst.errno != errno.ENOENT:
4410 raise
4411 raise
4411
4412
4412 ms.commit()
4413 ms.commit()
4413 ms.recordactions()
4414 ms.recordactions()
4414
4415
4415 if not didwork and pats:
4416 if not didwork and pats:
4416 hint = None
4417 hint = None
4417 if not any([p for p in pats if p.find(':') >= 0]):
4418 if not any([p for p in pats if p.find(':') >= 0]):
4418 pats = ['path:%s' % p for p in pats]
4419 pats = ['path:%s' % p for p in pats]
4419 m = scmutil.match(wctx, pats, opts)
4420 m = scmutil.match(wctx, pats, opts)
4420 for f in ms:
4421 for f in ms:
4421 if not m(f):
4422 if not m(f):
4422 continue
4423 continue
4423 flags = ''.join(['-%s ' % o[0] for o in flaglist
4424 flags = ''.join(['-%s ' % o[0] for o in flaglist
4424 if opts.get(o)])
4425 if opts.get(o)])
4425 hint = _("(try: hg resolve %s%s)\n") % (
4426 hint = _("(try: hg resolve %s%s)\n") % (
4426 flags,
4427 flags,
4427 ' '.join(pats))
4428 ' '.join(pats))
4428 break
4429 break
4429 ui.warn(_("arguments do not match paths that need resolving\n"))
4430 ui.warn(_("arguments do not match paths that need resolving\n"))
4430 if hint:
4431 if hint:
4431 ui.warn(hint)
4432 ui.warn(hint)
4432 elif ms.mergedriver and ms.mdstate() != 's':
4433 elif ms.mergedriver and ms.mdstate() != 's':
4433 # run conclude step when either a driver-resolved file is requested
4434 # run conclude step when either a driver-resolved file is requested
4434 # or there are no driver-resolved files
4435 # or there are no driver-resolved files
4435 # we can't use 'ret' to determine whether any files are unresolved
4436 # we can't use 'ret' to determine whether any files are unresolved
4436 # because we might not have tried to resolve some
4437 # because we might not have tried to resolve some
4437 if ((runconclude or not list(ms.driverresolved()))
4438 if ((runconclude or not list(ms.driverresolved()))
4438 and not list(ms.unresolved())):
4439 and not list(ms.unresolved())):
4439 proceed = mergemod.driverconclude(repo, ms, wctx)
4440 proceed = mergemod.driverconclude(repo, ms, wctx)
4440 ms.commit()
4441 ms.commit()
4441 if not proceed:
4442 if not proceed:
4442 return 1
4443 return 1
4443
4444
4444 # Nudge users into finishing an unfinished operation
4445 # Nudge users into finishing an unfinished operation
4445 unresolvedf = list(ms.unresolved())
4446 unresolvedf = list(ms.unresolved())
4446 driverresolvedf = list(ms.driverresolved())
4447 driverresolvedf = list(ms.driverresolved())
4447 if not unresolvedf and not driverresolvedf:
4448 if not unresolvedf and not driverresolvedf:
4448 ui.status(_('(no more unresolved files)\n'))
4449 ui.status(_('(no more unresolved files)\n'))
4449 cmdutil.checkafterresolved(repo)
4450 cmdutil.checkafterresolved(repo)
4450 elif not unresolvedf:
4451 elif not unresolvedf:
4451 ui.status(_('(no more unresolved files -- '
4452 ui.status(_('(no more unresolved files -- '
4452 'run "hg resolve --all" to conclude)\n'))
4453 'run "hg resolve --all" to conclude)\n'))
4453
4454
4454 return ret
4455 return ret
4455
4456
4456 @command('revert',
4457 @command('revert',
4457 [('a', 'all', None, _('revert all changes when no arguments given')),
4458 [('a', 'all', None, _('revert all changes when no arguments given')),
4458 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
4459 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
4459 ('r', 'rev', '', _('revert to the specified revision'), _('REV')),
4460 ('r', 'rev', '', _('revert to the specified revision'), _('REV')),
4460 ('C', 'no-backup', None, _('do not save backup copies of files')),
4461 ('C', 'no-backup', None, _('do not save backup copies of files')),
4461 ('i', 'interactive', None,
4462 ('i', 'interactive', None,
4462 _('interactively select the changes (EXPERIMENTAL)')),
4463 _('interactively select the changes (EXPERIMENTAL)')),
4463 ] + walkopts + dryrunopts,
4464 ] + walkopts + dryrunopts,
4464 _('[OPTION]... [-r REV] [NAME]...'))
4465 _('[OPTION]... [-r REV] [NAME]...'))
4465 def revert(ui, repo, *pats, **opts):
4466 def revert(ui, repo, *pats, **opts):
4466 """restore files to their checkout state
4467 """restore files to their checkout state
4467
4468
4468 .. note::
4469 .. note::
4469
4470
4470 To check out earlier revisions, you should use :hg:`update REV`.
4471 To check out earlier revisions, you should use :hg:`update REV`.
4471 To cancel an uncommitted merge (and lose your changes),
4472 To cancel an uncommitted merge (and lose your changes),
4472 use :hg:`update --clean .`.
4473 use :hg:`update --clean .`.
4473
4474
4474 With no revision specified, revert the specified files or directories
4475 With no revision specified, revert the specified files or directories
4475 to the contents they had in the parent of the working directory.
4476 to the contents they had in the parent of the working directory.
4476 This restores the contents of files to an unmodified
4477 This restores the contents of files to an unmodified
4477 state and unschedules adds, removes, copies, and renames. If the
4478 state and unschedules adds, removes, copies, and renames. If the
4478 working directory has two parents, you must explicitly specify a
4479 working directory has two parents, you must explicitly specify a
4479 revision.
4480 revision.
4480
4481
4481 Using the -r/--rev or -d/--date options, revert the given files or
4482 Using the -r/--rev or -d/--date options, revert the given files or
4482 directories to their states as of a specific revision. Because
4483 directories to their states as of a specific revision. Because
4483 revert does not change the working directory parents, this will
4484 revert does not change the working directory parents, this will
4484 cause these files to appear modified. This can be helpful to "back
4485 cause these files to appear modified. This can be helpful to "back
4485 out" some or all of an earlier change. See :hg:`backout` for a
4486 out" some or all of an earlier change. See :hg:`backout` for a
4486 related method.
4487 related method.
4487
4488
4488 Modified files are saved with a .orig suffix before reverting.
4489 Modified files are saved with a .orig suffix before reverting.
4489 To disable these backups, use --no-backup. It is possible to store
4490 To disable these backups, use --no-backup. It is possible to store
4490 the backup files in a custom directory relative to the root of the
4491 the backup files in a custom directory relative to the root of the
4491 repository by setting the ``ui.origbackuppath`` configuration
4492 repository by setting the ``ui.origbackuppath`` configuration
4492 option.
4493 option.
4493
4494
4494 See :hg:`help dates` for a list of formats valid for -d/--date.
4495 See :hg:`help dates` for a list of formats valid for -d/--date.
4495
4496
4496 See :hg:`help backout` for a way to reverse the effect of an
4497 See :hg:`help backout` for a way to reverse the effect of an
4497 earlier changeset.
4498 earlier changeset.
4498
4499
4499 Returns 0 on success.
4500 Returns 0 on success.
4500 """
4501 """
4501
4502
4502 if opts.get("date"):
4503 if opts.get("date"):
4503 if opts.get("rev"):
4504 if opts.get("rev"):
4504 raise error.Abort(_("you can't specify a revision and a date"))
4505 raise error.Abort(_("you can't specify a revision and a date"))
4505 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
4506 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
4506
4507
4507 parent, p2 = repo.dirstate.parents()
4508 parent, p2 = repo.dirstate.parents()
4508 if not opts.get('rev') and p2 != nullid:
4509 if not opts.get('rev') and p2 != nullid:
4509 # revert after merge is a trap for new users (issue2915)
4510 # revert after merge is a trap for new users (issue2915)
4510 raise error.Abort(_('uncommitted merge with no revision specified'),
4511 raise error.Abort(_('uncommitted merge with no revision specified'),
4511 hint=_("use 'hg update' or see 'hg help revert'"))
4512 hint=_("use 'hg update' or see 'hg help revert'"))
4512
4513
4513 ctx = scmutil.revsingle(repo, opts.get('rev'))
4514 ctx = scmutil.revsingle(repo, opts.get('rev'))
4514
4515
4515 if (not (pats or opts.get('include') or opts.get('exclude') or
4516 if (not (pats or opts.get('include') or opts.get('exclude') or
4516 opts.get('all') or opts.get('interactive'))):
4517 opts.get('all') or opts.get('interactive'))):
4517 msg = _("no files or directories specified")
4518 msg = _("no files or directories specified")
4518 if p2 != nullid:
4519 if p2 != nullid:
4519 hint = _("uncommitted merge, use --all to discard all changes,"
4520 hint = _("uncommitted merge, use --all to discard all changes,"
4520 " or 'hg update -C .' to abort the merge")
4521 " or 'hg update -C .' to abort the merge")
4521 raise error.Abort(msg, hint=hint)
4522 raise error.Abort(msg, hint=hint)
4522 dirty = any(repo.status())
4523 dirty = any(repo.status())
4523 node = ctx.node()
4524 node = ctx.node()
4524 if node != parent:
4525 if node != parent:
4525 if dirty:
4526 if dirty:
4526 hint = _("uncommitted changes, use --all to discard all"
4527 hint = _("uncommitted changes, use --all to discard all"
4527 " changes, or 'hg update %s' to update") % ctx.rev()
4528 " changes, or 'hg update %s' to update") % ctx.rev()
4528 else:
4529 else:
4529 hint = _("use --all to revert all files,"
4530 hint = _("use --all to revert all files,"
4530 " or 'hg update %s' to update") % ctx.rev()
4531 " or 'hg update %s' to update") % ctx.rev()
4531 elif dirty:
4532 elif dirty:
4532 hint = _("uncommitted changes, use --all to discard all changes")
4533 hint = _("uncommitted changes, use --all to discard all changes")
4533 else:
4534 else:
4534 hint = _("use --all to revert all files")
4535 hint = _("use --all to revert all files")
4535 raise error.Abort(msg, hint=hint)
4536 raise error.Abort(msg, hint=hint)
4536
4537
4537 return cmdutil.revert(ui, repo, ctx, (parent, p2), *pats, **opts)
4538 return cmdutil.revert(ui, repo, ctx, (parent, p2), *pats, **opts)
4538
4539
4539 @command('rollback', dryrunopts +
4540 @command('rollback', dryrunopts +
4540 [('f', 'force', False, _('ignore safety measures'))])
4541 [('f', 'force', False, _('ignore safety measures'))])
4541 def rollback(ui, repo, **opts):
4542 def rollback(ui, repo, **opts):
4542 """roll back the last transaction (DANGEROUS) (DEPRECATED)
4543 """roll back the last transaction (DANGEROUS) (DEPRECATED)
4543
4544
4544 Please use :hg:`commit --amend` instead of rollback to correct
4545 Please use :hg:`commit --amend` instead of rollback to correct
4545 mistakes in the last commit.
4546 mistakes in the last commit.
4546
4547
4547 This command should be used with care. There is only one level of
4548 This command should be used with care. There is only one level of
4548 rollback, and there is no way to undo a rollback. It will also
4549 rollback, and there is no way to undo a rollback. It will also
4549 restore the dirstate at the time of the last transaction, losing
4550 restore the dirstate at the time of the last transaction, losing
4550 any dirstate changes since that time. This command does not alter
4551 any dirstate changes since that time. This command does not alter
4551 the working directory.
4552 the working directory.
4552
4553
4553 Transactions are used to encapsulate the effects of all commands
4554 Transactions are used to encapsulate the effects of all commands
4554 that create new changesets or propagate existing changesets into a
4555 that create new changesets or propagate existing changesets into a
4555 repository.
4556 repository.
4556
4557
4557 .. container:: verbose
4558 .. container:: verbose
4558
4559
4559 For example, the following commands are transactional, and their
4560 For example, the following commands are transactional, and their
4560 effects can be rolled back:
4561 effects can be rolled back:
4561
4562
4562 - commit
4563 - commit
4563 - import
4564 - import
4564 - pull
4565 - pull
4565 - push (with this repository as the destination)
4566 - push (with this repository as the destination)
4566 - unbundle
4567 - unbundle
4567
4568
4568 To avoid permanent data loss, rollback will refuse to rollback a
4569 To avoid permanent data loss, rollback will refuse to rollback a
4569 commit transaction if it isn't checked out. Use --force to
4570 commit transaction if it isn't checked out. Use --force to
4570 override this protection.
4571 override this protection.
4571
4572
4572 The rollback command can be entirely disabled by setting the
4573 The rollback command can be entirely disabled by setting the
4573 ``ui.rollback`` configuration setting to false. If you're here
4574 ``ui.rollback`` configuration setting to false. If you're here
4574 because you want to use rollback and it's disabled, you can
4575 because you want to use rollback and it's disabled, you can
4575 re-enable the command by setting ``ui.rollback`` to true.
4576 re-enable the command by setting ``ui.rollback`` to true.
4576
4577
4577 This command is not intended for use on public repositories. Once
4578 This command is not intended for use on public repositories. Once
4578 changes are visible for pull by other users, rolling a transaction
4579 changes are visible for pull by other users, rolling a transaction
4579 back locally is ineffective (someone else may already have pulled
4580 back locally is ineffective (someone else may already have pulled
4580 the changes). Furthermore, a race is possible with readers of the
4581 the changes). Furthermore, a race is possible with readers of the
4581 repository; for example an in-progress pull from the repository
4582 repository; for example an in-progress pull from the repository
4582 may fail if a rollback is performed.
4583 may fail if a rollback is performed.
4583
4584
4584 Returns 0 on success, 1 if no rollback data is available.
4585 Returns 0 on success, 1 if no rollback data is available.
4585 """
4586 """
4586 if not ui.configbool('ui', 'rollback', True):
4587 if not ui.configbool('ui', 'rollback', True):
4587 raise error.Abort(_('rollback is disabled because it is unsafe'),
4588 raise error.Abort(_('rollback is disabled because it is unsafe'),
4588 hint=('see `hg help -v rollback` for information'))
4589 hint=('see `hg help -v rollback` for information'))
4589 return repo.rollback(dryrun=opts.get(r'dry_run'),
4590 return repo.rollback(dryrun=opts.get(r'dry_run'),
4590 force=opts.get(r'force'))
4591 force=opts.get(r'force'))
4591
4592
4592 @command('root', [])
4593 @command('root', [])
4593 def root(ui, repo):
4594 def root(ui, repo):
4594 """print the root (top) of the current working directory
4595 """print the root (top) of the current working directory
4595
4596
4596 Print the root directory of the current repository.
4597 Print the root directory of the current repository.
4597
4598
4598 Returns 0 on success.
4599 Returns 0 on success.
4599 """
4600 """
4600 ui.write(repo.root + "\n")
4601 ui.write(repo.root + "\n")
4601
4602
4602 @command('^serve',
4603 @command('^serve',
4603 [('A', 'accesslog', '', _('name of access log file to write to'),
4604 [('A', 'accesslog', '', _('name of access log file to write to'),
4604 _('FILE')),
4605 _('FILE')),
4605 ('d', 'daemon', None, _('run server in background')),
4606 ('d', 'daemon', None, _('run server in background')),
4606 ('', 'daemon-postexec', [], _('used internally by daemon mode')),
4607 ('', 'daemon-postexec', [], _('used internally by daemon mode')),
4607 ('E', 'errorlog', '', _('name of error log file to write to'), _('FILE')),
4608 ('E', 'errorlog', '', _('name of error log file to write to'), _('FILE')),
4608 # use string type, then we can check if something was passed
4609 # use string type, then we can check if something was passed
4609 ('p', 'port', '', _('port to listen on (default: 8000)'), _('PORT')),
4610 ('p', 'port', '', _('port to listen on (default: 8000)'), _('PORT')),
4610 ('a', 'address', '', _('address to listen on (default: all interfaces)'),
4611 ('a', 'address', '', _('address to listen on (default: all interfaces)'),
4611 _('ADDR')),
4612 _('ADDR')),
4612 ('', 'prefix', '', _('prefix path to serve from (default: server root)'),
4613 ('', 'prefix', '', _('prefix path to serve from (default: server root)'),
4613 _('PREFIX')),
4614 _('PREFIX')),
4614 ('n', 'name', '',
4615 ('n', 'name', '',
4615 _('name to show in web pages (default: working directory)'), _('NAME')),
4616 _('name to show in web pages (default: working directory)'), _('NAME')),
4616 ('', 'web-conf', '',
4617 ('', 'web-conf', '',
4617 _("name of the hgweb config file (see 'hg help hgweb')"), _('FILE')),
4618 _("name of the hgweb config file (see 'hg help hgweb')"), _('FILE')),
4618 ('', 'webdir-conf', '', _('name of the hgweb config file (DEPRECATED)'),
4619 ('', 'webdir-conf', '', _('name of the hgweb config file (DEPRECATED)'),
4619 _('FILE')),
4620 _('FILE')),
4620 ('', 'pid-file', '', _('name of file to write process ID to'), _('FILE')),
4621 ('', 'pid-file', '', _('name of file to write process ID to'), _('FILE')),
4621 ('', 'stdio', None, _('for remote clients (ADVANCED)')),
4622 ('', 'stdio', None, _('for remote clients (ADVANCED)')),
4622 ('', 'cmdserver', '', _('for remote clients (ADVANCED)'), _('MODE')),
4623 ('', 'cmdserver', '', _('for remote clients (ADVANCED)'), _('MODE')),
4623 ('t', 'templates', '', _('web templates to use'), _('TEMPLATE')),
4624 ('t', 'templates', '', _('web templates to use'), _('TEMPLATE')),
4624 ('', 'style', '', _('template style to use'), _('STYLE')),
4625 ('', 'style', '', _('template style to use'), _('STYLE')),
4625 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
4626 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
4626 ('', 'certificate', '', _('SSL certificate file'), _('FILE'))]
4627 ('', 'certificate', '', _('SSL certificate file'), _('FILE'))]
4627 + subrepoopts,
4628 + subrepoopts,
4628 _('[OPTION]...'),
4629 _('[OPTION]...'),
4629 optionalrepo=True)
4630 optionalrepo=True)
4630 def serve(ui, repo, **opts):
4631 def serve(ui, repo, **opts):
4631 """start stand-alone webserver
4632 """start stand-alone webserver
4632
4633
4633 Start a local HTTP repository browser and pull server. You can use
4634 Start a local HTTP repository browser and pull server. You can use
4634 this for ad-hoc sharing and browsing of repositories. It is
4635 this for ad-hoc sharing and browsing of repositories. It is
4635 recommended to use a real web server to serve a repository for
4636 recommended to use a real web server to serve a repository for
4636 longer periods of time.
4637 longer periods of time.
4637
4638
4638 Please note that the server does not implement access control.
4639 Please note that the server does not implement access control.
4639 This means that, by default, anybody can read from the server and
4640 This means that, by default, anybody can read from the server and
4640 nobody can write to it by default. Set the ``web.allow_push``
4641 nobody can write to it by default. Set the ``web.allow_push``
4641 option to ``*`` to allow everybody to push to the server. You
4642 option to ``*`` to allow everybody to push to the server. You
4642 should use a real web server if you need to authenticate users.
4643 should use a real web server if you need to authenticate users.
4643
4644
4644 By default, the server logs accesses to stdout and errors to
4645 By default, the server logs accesses to stdout and errors to
4645 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
4646 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
4646 files.
4647 files.
4647
4648
4648 To have the server choose a free port number to listen on, specify
4649 To have the server choose a free port number to listen on, specify
4649 a port number of 0; in this case, the server will print the port
4650 a port number of 0; in this case, the server will print the port
4650 number it uses.
4651 number it uses.
4651
4652
4652 Returns 0 on success.
4653 Returns 0 on success.
4653 """
4654 """
4654
4655
4655 opts = pycompat.byteskwargs(opts)
4656 opts = pycompat.byteskwargs(opts)
4656 if opts["stdio"] and opts["cmdserver"]:
4657 if opts["stdio"] and opts["cmdserver"]:
4657 raise error.Abort(_("cannot use --stdio with --cmdserver"))
4658 raise error.Abort(_("cannot use --stdio with --cmdserver"))
4658
4659
4659 if opts["stdio"]:
4660 if opts["stdio"]:
4660 if repo is None:
4661 if repo is None:
4661 raise error.RepoError(_("there is no Mercurial repository here"
4662 raise error.RepoError(_("there is no Mercurial repository here"
4662 " (.hg not found)"))
4663 " (.hg not found)"))
4663 s = sshserver.sshserver(ui, repo)
4664 s = sshserver.sshserver(ui, repo)
4664 s.serve_forever()
4665 s.serve_forever()
4665
4666
4666 service = server.createservice(ui, repo, opts)
4667 service = server.createservice(ui, repo, opts)
4667 return server.runservice(opts, initfn=service.init, runfn=service.run)
4668 return server.runservice(opts, initfn=service.init, runfn=service.run)
4668
4669
4669 @command('^status|st',
4670 @command('^status|st',
4670 [('A', 'all', None, _('show status of all files')),
4671 [('A', 'all', None, _('show status of all files')),
4671 ('m', 'modified', None, _('show only modified files')),
4672 ('m', 'modified', None, _('show only modified files')),
4672 ('a', 'added', None, _('show only added files')),
4673 ('a', 'added', None, _('show only added files')),
4673 ('r', 'removed', None, _('show only removed files')),
4674 ('r', 'removed', None, _('show only removed files')),
4674 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
4675 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
4675 ('c', 'clean', None, _('show only files without changes')),
4676 ('c', 'clean', None, _('show only files without changes')),
4676 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
4677 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
4677 ('i', 'ignored', None, _('show only ignored files')),
4678 ('i', 'ignored', None, _('show only ignored files')),
4678 ('n', 'no-status', None, _('hide status prefix')),
4679 ('n', 'no-status', None, _('hide status prefix')),
4679 ('C', 'copies', None, _('show source of copied files')),
4680 ('C', 'copies', None, _('show source of copied files')),
4680 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
4681 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
4681 ('', 'rev', [], _('show difference from revision'), _('REV')),
4682 ('', 'rev', [], _('show difference from revision'), _('REV')),
4682 ('', 'change', '', _('list the changed files of a revision'), _('REV')),
4683 ('', 'change', '', _('list the changed files of a revision'), _('REV')),
4683 ] + walkopts + subrepoopts + formatteropts,
4684 ] + walkopts + subrepoopts + formatteropts,
4684 _('[OPTION]... [FILE]...'),
4685 _('[OPTION]... [FILE]...'),
4685 inferrepo=True)
4686 inferrepo=True)
4686 def status(ui, repo, *pats, **opts):
4687 def status(ui, repo, *pats, **opts):
4687 """show changed files in the working directory
4688 """show changed files in the working directory
4688
4689
4689 Show status of files in the repository. If names are given, only
4690 Show status of files in the repository. If names are given, only
4690 files that match are shown. Files that are clean or ignored or
4691 files that match are shown. Files that are clean or ignored or
4691 the source of a copy/move operation, are not listed unless
4692 the source of a copy/move operation, are not listed unless
4692 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
4693 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
4693 Unless options described with "show only ..." are given, the
4694 Unless options described with "show only ..." are given, the
4694 options -mardu are used.
4695 options -mardu are used.
4695
4696
4696 Option -q/--quiet hides untracked (unknown and ignored) files
4697 Option -q/--quiet hides untracked (unknown and ignored) files
4697 unless explicitly requested with -u/--unknown or -i/--ignored.
4698 unless explicitly requested with -u/--unknown or -i/--ignored.
4698
4699
4699 .. note::
4700 .. note::
4700
4701
4701 :hg:`status` may appear to disagree with diff if permissions have
4702 :hg:`status` may appear to disagree with diff if permissions have
4702 changed or a merge has occurred. The standard diff format does
4703 changed or a merge has occurred. The standard diff format does
4703 not report permission changes and diff only reports changes
4704 not report permission changes and diff only reports changes
4704 relative to one merge parent.
4705 relative to one merge parent.
4705
4706
4706 If one revision is given, it is used as the base revision.
4707 If one revision is given, it is used as the base revision.
4707 If two revisions are given, the differences between them are
4708 If two revisions are given, the differences between them are
4708 shown. The --change option can also be used as a shortcut to list
4709 shown. The --change option can also be used as a shortcut to list
4709 the changed files of a revision from its first parent.
4710 the changed files of a revision from its first parent.
4710
4711
4711 The codes used to show the status of files are::
4712 The codes used to show the status of files are::
4712
4713
4713 M = modified
4714 M = modified
4714 A = added
4715 A = added
4715 R = removed
4716 R = removed
4716 C = clean
4717 C = clean
4717 ! = missing (deleted by non-hg command, but still tracked)
4718 ! = missing (deleted by non-hg command, but still tracked)
4718 ? = not tracked
4719 ? = not tracked
4719 I = ignored
4720 I = ignored
4720 = origin of the previous file (with --copies)
4721 = origin of the previous file (with --copies)
4721
4722
4722 .. container:: verbose
4723 .. container:: verbose
4723
4724
4724 Examples:
4725 Examples:
4725
4726
4726 - show changes in the working directory relative to a
4727 - show changes in the working directory relative to a
4727 changeset::
4728 changeset::
4728
4729
4729 hg status --rev 9353
4730 hg status --rev 9353
4730
4731
4731 - show changes in the working directory relative to the
4732 - show changes in the working directory relative to the
4732 current directory (see :hg:`help patterns` for more information)::
4733 current directory (see :hg:`help patterns` for more information)::
4733
4734
4734 hg status re:
4735 hg status re:
4735
4736
4736 - show all changes including copies in an existing changeset::
4737 - show all changes including copies in an existing changeset::
4737
4738
4738 hg status --copies --change 9353
4739 hg status --copies --change 9353
4739
4740
4740 - get a NUL separated list of added files, suitable for xargs::
4741 - get a NUL separated list of added files, suitable for xargs::
4741
4742
4742 hg status -an0
4743 hg status -an0
4743
4744
4744 Returns 0 on success.
4745 Returns 0 on success.
4745 """
4746 """
4746
4747
4747 opts = pycompat.byteskwargs(opts)
4748 opts = pycompat.byteskwargs(opts)
4748 revs = opts.get('rev')
4749 revs = opts.get('rev')
4749 change = opts.get('change')
4750 change = opts.get('change')
4750
4751
4751 if revs and change:
4752 if revs and change:
4752 msg = _('cannot specify --rev and --change at the same time')
4753 msg = _('cannot specify --rev and --change at the same time')
4753 raise error.Abort(msg)
4754 raise error.Abort(msg)
4754 elif change:
4755 elif change:
4755 node2 = scmutil.revsingle(repo, change, None).node()
4756 node2 = scmutil.revsingle(repo, change, None).node()
4756 node1 = repo[node2].p1().node()
4757 node1 = repo[node2].p1().node()
4757 else:
4758 else:
4758 node1, node2 = scmutil.revpair(repo, revs)
4759 node1, node2 = scmutil.revpair(repo, revs)
4759
4760
4760 if pats or ui.configbool('commands', 'status.relative'):
4761 if pats or ui.configbool('commands', 'status.relative'):
4761 cwd = repo.getcwd()
4762 cwd = repo.getcwd()
4762 else:
4763 else:
4763 cwd = ''
4764 cwd = ''
4764
4765
4765 if opts.get('print0'):
4766 if opts.get('print0'):
4766 end = '\0'
4767 end = '\0'
4767 else:
4768 else:
4768 end = '\n'
4769 end = '\n'
4769 copy = {}
4770 copy = {}
4770 states = 'modified added removed deleted unknown ignored clean'.split()
4771 states = 'modified added removed deleted unknown ignored clean'.split()
4771 show = [k for k in states if opts.get(k)]
4772 show = [k for k in states if opts.get(k)]
4772 if opts.get('all'):
4773 if opts.get('all'):
4773 show += ui.quiet and (states[:4] + ['clean']) or states
4774 show += ui.quiet and (states[:4] + ['clean']) or states
4774 if not show:
4775 if not show:
4775 if ui.quiet:
4776 if ui.quiet:
4776 show = states[:4]
4777 show = states[:4]
4777 else:
4778 else:
4778 show = states[:5]
4779 show = states[:5]
4779
4780
4780 m = scmutil.match(repo[node2], pats, opts)
4781 m = scmutil.match(repo[node2], pats, opts)
4781 stat = repo.status(node1, node2, m,
4782 stat = repo.status(node1, node2, m,
4782 'ignored' in show, 'clean' in show, 'unknown' in show,
4783 'ignored' in show, 'clean' in show, 'unknown' in show,
4783 opts.get('subrepos'))
4784 opts.get('subrepos'))
4784 changestates = zip(states, pycompat.iterbytestr('MAR!?IC'), stat)
4785 changestates = zip(states, pycompat.iterbytestr('MAR!?IC'), stat)
4785
4786
4786 if (opts.get('all') or opts.get('copies')
4787 if (opts.get('all') or opts.get('copies')
4787 or ui.configbool('ui', 'statuscopies')) and not opts.get('no_status'):
4788 or ui.configbool('ui', 'statuscopies')) and not opts.get('no_status'):
4788 copy = copies.pathcopies(repo[node1], repo[node2], m)
4789 copy = copies.pathcopies(repo[node1], repo[node2], m)
4789
4790
4790 ui.pager('status')
4791 ui.pager('status')
4791 fm = ui.formatter('status', opts)
4792 fm = ui.formatter('status', opts)
4792 fmt = '%s' + end
4793 fmt = '%s' + end
4793 showchar = not opts.get('no_status')
4794 showchar = not opts.get('no_status')
4794
4795
4795 for state, char, files in changestates:
4796 for state, char, files in changestates:
4796 if state in show:
4797 if state in show:
4797 label = 'status.' + state
4798 label = 'status.' + state
4798 for f in files:
4799 for f in files:
4799 fm.startitem()
4800 fm.startitem()
4800 fm.condwrite(showchar, 'status', '%s ', char, label=label)
4801 fm.condwrite(showchar, 'status', '%s ', char, label=label)
4801 fm.write('path', fmt, repo.pathto(f, cwd), label=label)
4802 fm.write('path', fmt, repo.pathto(f, cwd), label=label)
4802 if f in copy:
4803 if f in copy:
4803 fm.write("copy", ' %s' + end, repo.pathto(copy[f], cwd),
4804 fm.write("copy", ' %s' + end, repo.pathto(copy[f], cwd),
4804 label='status.copied')
4805 label='status.copied')
4805 fm.end()
4806 fm.end()
4806
4807
4807 @command('^summary|sum',
4808 @command('^summary|sum',
4808 [('', 'remote', None, _('check for push and pull'))], '[--remote]')
4809 [('', 'remote', None, _('check for push and pull'))], '[--remote]')
4809 def summary(ui, repo, **opts):
4810 def summary(ui, repo, **opts):
4810 """summarize working directory state
4811 """summarize working directory state
4811
4812
4812 This generates a brief summary of the working directory state,
4813 This generates a brief summary of the working directory state,
4813 including parents, branch, commit status, phase and available updates.
4814 including parents, branch, commit status, phase and available updates.
4814
4815
4815 With the --remote option, this will check the default paths for
4816 With the --remote option, this will check the default paths for
4816 incoming and outgoing changes. This can be time-consuming.
4817 incoming and outgoing changes. This can be time-consuming.
4817
4818
4818 Returns 0 on success.
4819 Returns 0 on success.
4819 """
4820 """
4820
4821
4821 opts = pycompat.byteskwargs(opts)
4822 opts = pycompat.byteskwargs(opts)
4822 ui.pager('summary')
4823 ui.pager('summary')
4823 ctx = repo[None]
4824 ctx = repo[None]
4824 parents = ctx.parents()
4825 parents = ctx.parents()
4825 pnode = parents[0].node()
4826 pnode = parents[0].node()
4826 marks = []
4827 marks = []
4827
4828
4828 ms = None
4829 ms = None
4829 try:
4830 try:
4830 ms = mergemod.mergestate.read(repo)
4831 ms = mergemod.mergestate.read(repo)
4831 except error.UnsupportedMergeRecords as e:
4832 except error.UnsupportedMergeRecords as e:
4832 s = ' '.join(e.recordtypes)
4833 s = ' '.join(e.recordtypes)
4833 ui.warn(
4834 ui.warn(
4834 _('warning: merge state has unsupported record types: %s\n') % s)
4835 _('warning: merge state has unsupported record types: %s\n') % s)
4835 unresolved = 0
4836 unresolved = 0
4836 else:
4837 else:
4837 unresolved = [f for f in ms if ms[f] == 'u']
4838 unresolved = [f for f in ms if ms[f] == 'u']
4838
4839
4839 for p in parents:
4840 for p in parents:
4840 # label with log.changeset (instead of log.parent) since this
4841 # label with log.changeset (instead of log.parent) since this
4841 # shows a working directory parent *changeset*:
4842 # shows a working directory parent *changeset*:
4842 # i18n: column positioning for "hg summary"
4843 # i18n: column positioning for "hg summary"
4843 ui.write(_('parent: %d:%s ') % (p.rev(), p),
4844 ui.write(_('parent: %d:%s ') % (p.rev(), p),
4844 label=cmdutil._changesetlabels(p))
4845 label=cmdutil._changesetlabels(p))
4845 ui.write(' '.join(p.tags()), label='log.tag')
4846 ui.write(' '.join(p.tags()), label='log.tag')
4846 if p.bookmarks():
4847 if p.bookmarks():
4847 marks.extend(p.bookmarks())
4848 marks.extend(p.bookmarks())
4848 if p.rev() == -1:
4849 if p.rev() == -1:
4849 if not len(repo):
4850 if not len(repo):
4850 ui.write(_(' (empty repository)'))
4851 ui.write(_(' (empty repository)'))
4851 else:
4852 else:
4852 ui.write(_(' (no revision checked out)'))
4853 ui.write(_(' (no revision checked out)'))
4853 if p.obsolete():
4854 if p.obsolete():
4854 ui.write(_(' (obsolete)'))
4855 ui.write(_(' (obsolete)'))
4855 if p.troubled():
4856 if p.troubled():
4856 ui.write(' ('
4857 ui.write(' ('
4857 + ', '.join(ui.label(trouble, 'trouble.%s' % trouble)
4858 + ', '.join(ui.label(trouble, 'trouble.%s' % trouble)
4858 for trouble in p.troubles())
4859 for trouble in p.troubles())
4859 + ')')
4860 + ')')
4860 ui.write('\n')
4861 ui.write('\n')
4861 if p.description():
4862 if p.description():
4862 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
4863 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
4863 label='log.summary')
4864 label='log.summary')
4864
4865
4865 branch = ctx.branch()
4866 branch = ctx.branch()
4866 bheads = repo.branchheads(branch)
4867 bheads = repo.branchheads(branch)
4867 # i18n: column positioning for "hg summary"
4868 # i18n: column positioning for "hg summary"
4868 m = _('branch: %s\n') % branch
4869 m = _('branch: %s\n') % branch
4869 if branch != 'default':
4870 if branch != 'default':
4870 ui.write(m, label='log.branch')
4871 ui.write(m, label='log.branch')
4871 else:
4872 else:
4872 ui.status(m, label='log.branch')
4873 ui.status(m, label='log.branch')
4873
4874
4874 if marks:
4875 if marks:
4875 active = repo._activebookmark
4876 active = repo._activebookmark
4876 # i18n: column positioning for "hg summary"
4877 # i18n: column positioning for "hg summary"
4877 ui.write(_('bookmarks:'), label='log.bookmark')
4878 ui.write(_('bookmarks:'), label='log.bookmark')
4878 if active is not None:
4879 if active is not None:
4879 if active in marks:
4880 if active in marks:
4880 ui.write(' *' + active, label=activebookmarklabel)
4881 ui.write(' *' + active, label=activebookmarklabel)
4881 marks.remove(active)
4882 marks.remove(active)
4882 else:
4883 else:
4883 ui.write(' [%s]' % active, label=activebookmarklabel)
4884 ui.write(' [%s]' % active, label=activebookmarklabel)
4884 for m in marks:
4885 for m in marks:
4885 ui.write(' ' + m, label='log.bookmark')
4886 ui.write(' ' + m, label='log.bookmark')
4886 ui.write('\n', label='log.bookmark')
4887 ui.write('\n', label='log.bookmark')
4887
4888
4888 status = repo.status(unknown=True)
4889 status = repo.status(unknown=True)
4889
4890
4890 c = repo.dirstate.copies()
4891 c = repo.dirstate.copies()
4891 copied, renamed = [], []
4892 copied, renamed = [], []
4892 for d, s in c.iteritems():
4893 for d, s in c.iteritems():
4893 if s in status.removed:
4894 if s in status.removed:
4894 status.removed.remove(s)
4895 status.removed.remove(s)
4895 renamed.append(d)
4896 renamed.append(d)
4896 else:
4897 else:
4897 copied.append(d)
4898 copied.append(d)
4898 if d in status.added:
4899 if d in status.added:
4899 status.added.remove(d)
4900 status.added.remove(d)
4900
4901
4901 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
4902 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
4902
4903
4903 labels = [(ui.label(_('%d modified'), 'status.modified'), status.modified),
4904 labels = [(ui.label(_('%d modified'), 'status.modified'), status.modified),
4904 (ui.label(_('%d added'), 'status.added'), status.added),
4905 (ui.label(_('%d added'), 'status.added'), status.added),
4905 (ui.label(_('%d removed'), 'status.removed'), status.removed),
4906 (ui.label(_('%d removed'), 'status.removed'), status.removed),
4906 (ui.label(_('%d renamed'), 'status.copied'), renamed),
4907 (ui.label(_('%d renamed'), 'status.copied'), renamed),
4907 (ui.label(_('%d copied'), 'status.copied'), copied),
4908 (ui.label(_('%d copied'), 'status.copied'), copied),
4908 (ui.label(_('%d deleted'), 'status.deleted'), status.deleted),
4909 (ui.label(_('%d deleted'), 'status.deleted'), status.deleted),
4909 (ui.label(_('%d unknown'), 'status.unknown'), status.unknown),
4910 (ui.label(_('%d unknown'), 'status.unknown'), status.unknown),
4910 (ui.label(_('%d unresolved'), 'resolve.unresolved'), unresolved),
4911 (ui.label(_('%d unresolved'), 'resolve.unresolved'), unresolved),
4911 (ui.label(_('%d subrepos'), 'status.modified'), subs)]
4912 (ui.label(_('%d subrepos'), 'status.modified'), subs)]
4912 t = []
4913 t = []
4913 for l, s in labels:
4914 for l, s in labels:
4914 if s:
4915 if s:
4915 t.append(l % len(s))
4916 t.append(l % len(s))
4916
4917
4917 t = ', '.join(t)
4918 t = ', '.join(t)
4918 cleanworkdir = False
4919 cleanworkdir = False
4919
4920
4920 if repo.vfs.exists('graftstate'):
4921 if repo.vfs.exists('graftstate'):
4921 t += _(' (graft in progress)')
4922 t += _(' (graft in progress)')
4922 if repo.vfs.exists('updatestate'):
4923 if repo.vfs.exists('updatestate'):
4923 t += _(' (interrupted update)')
4924 t += _(' (interrupted update)')
4924 elif len(parents) > 1:
4925 elif len(parents) > 1:
4925 t += _(' (merge)')
4926 t += _(' (merge)')
4926 elif branch != parents[0].branch():
4927 elif branch != parents[0].branch():
4927 t += _(' (new branch)')
4928 t += _(' (new branch)')
4928 elif (parents[0].closesbranch() and
4929 elif (parents[0].closesbranch() and
4929 pnode in repo.branchheads(branch, closed=True)):
4930 pnode in repo.branchheads(branch, closed=True)):
4930 t += _(' (head closed)')
4931 t += _(' (head closed)')
4931 elif not (status.modified or status.added or status.removed or renamed or
4932 elif not (status.modified or status.added or status.removed or renamed or
4932 copied or subs):
4933 copied or subs):
4933 t += _(' (clean)')
4934 t += _(' (clean)')
4934 cleanworkdir = True
4935 cleanworkdir = True
4935 elif pnode not in bheads:
4936 elif pnode not in bheads:
4936 t += _(' (new branch head)')
4937 t += _(' (new branch head)')
4937
4938
4938 if parents:
4939 if parents:
4939 pendingphase = max(p.phase() for p in parents)
4940 pendingphase = max(p.phase() for p in parents)
4940 else:
4941 else:
4941 pendingphase = phases.public
4942 pendingphase = phases.public
4942
4943
4943 if pendingphase > phases.newcommitphase(ui):
4944 if pendingphase > phases.newcommitphase(ui):
4944 t += ' (%s)' % phases.phasenames[pendingphase]
4945 t += ' (%s)' % phases.phasenames[pendingphase]
4945
4946
4946 if cleanworkdir:
4947 if cleanworkdir:
4947 # i18n: column positioning for "hg summary"
4948 # i18n: column positioning for "hg summary"
4948 ui.status(_('commit: %s\n') % t.strip())
4949 ui.status(_('commit: %s\n') % t.strip())
4949 else:
4950 else:
4950 # i18n: column positioning for "hg summary"
4951 # i18n: column positioning for "hg summary"
4951 ui.write(_('commit: %s\n') % t.strip())
4952 ui.write(_('commit: %s\n') % t.strip())
4952
4953
4953 # all ancestors of branch heads - all ancestors of parent = new csets
4954 # all ancestors of branch heads - all ancestors of parent = new csets
4954 new = len(repo.changelog.findmissing([pctx.node() for pctx in parents],
4955 new = len(repo.changelog.findmissing([pctx.node() for pctx in parents],
4955 bheads))
4956 bheads))
4956
4957
4957 if new == 0:
4958 if new == 0:
4958 # i18n: column positioning for "hg summary"
4959 # i18n: column positioning for "hg summary"
4959 ui.status(_('update: (current)\n'))
4960 ui.status(_('update: (current)\n'))
4960 elif pnode not in bheads:
4961 elif pnode not in bheads:
4961 # i18n: column positioning for "hg summary"
4962 # i18n: column positioning for "hg summary"
4962 ui.write(_('update: %d new changesets (update)\n') % new)
4963 ui.write(_('update: %d new changesets (update)\n') % new)
4963 else:
4964 else:
4964 # i18n: column positioning for "hg summary"
4965 # i18n: column positioning for "hg summary"
4965 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
4966 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
4966 (new, len(bheads)))
4967 (new, len(bheads)))
4967
4968
4968 t = []
4969 t = []
4969 draft = len(repo.revs('draft()'))
4970 draft = len(repo.revs('draft()'))
4970 if draft:
4971 if draft:
4971 t.append(_('%d draft') % draft)
4972 t.append(_('%d draft') % draft)
4972 secret = len(repo.revs('secret()'))
4973 secret = len(repo.revs('secret()'))
4973 if secret:
4974 if secret:
4974 t.append(_('%d secret') % secret)
4975 t.append(_('%d secret') % secret)
4975
4976
4976 if draft or secret:
4977 if draft or secret:
4977 ui.status(_('phases: %s\n') % ', '.join(t))
4978 ui.status(_('phases: %s\n') % ', '.join(t))
4978
4979
4979 if obsolete.isenabled(repo, obsolete.createmarkersopt):
4980 if obsolete.isenabled(repo, obsolete.createmarkersopt):
4980 for trouble in ("unstable", "divergent", "bumped"):
4981 for trouble in ("unstable", "divergent", "bumped"):
4981 numtrouble = len(repo.revs(trouble + "()"))
4982 numtrouble = len(repo.revs(trouble + "()"))
4982 # We write all the possibilities to ease translation
4983 # We write all the possibilities to ease translation
4983 troublemsg = {
4984 troublemsg = {
4984 "unstable": _("unstable: %d changesets"),
4985 "unstable": _("unstable: %d changesets"),
4985 "divergent": _("divergent: %d changesets"),
4986 "divergent": _("divergent: %d changesets"),
4986 "bumped": _("bumped: %d changesets"),
4987 "bumped": _("bumped: %d changesets"),
4987 }
4988 }
4988 if numtrouble > 0:
4989 if numtrouble > 0:
4989 ui.status(troublemsg[trouble] % numtrouble + "\n")
4990 ui.status(troublemsg[trouble] % numtrouble + "\n")
4990
4991
4991 cmdutil.summaryhooks(ui, repo)
4992 cmdutil.summaryhooks(ui, repo)
4992
4993
4993 if opts.get('remote'):
4994 if opts.get('remote'):
4994 needsincoming, needsoutgoing = True, True
4995 needsincoming, needsoutgoing = True, True
4995 else:
4996 else:
4996 needsincoming, needsoutgoing = False, False
4997 needsincoming, needsoutgoing = False, False
4997 for i, o in cmdutil.summaryremotehooks(ui, repo, opts, None):
4998 for i, o in cmdutil.summaryremotehooks(ui, repo, opts, None):
4998 if i:
4999 if i:
4999 needsincoming = True
5000 needsincoming = True
5000 if o:
5001 if o:
5001 needsoutgoing = True
5002 needsoutgoing = True
5002 if not needsincoming and not needsoutgoing:
5003 if not needsincoming and not needsoutgoing:
5003 return
5004 return
5004
5005
5005 def getincoming():
5006 def getincoming():
5006 source, branches = hg.parseurl(ui.expandpath('default'))
5007 source, branches = hg.parseurl(ui.expandpath('default'))
5007 sbranch = branches[0]
5008 sbranch = branches[0]
5008 try:
5009 try:
5009 other = hg.peer(repo, {}, source)
5010 other = hg.peer(repo, {}, source)
5010 except error.RepoError:
5011 except error.RepoError:
5011 if opts.get('remote'):
5012 if opts.get('remote'):
5012 raise
5013 raise
5013 return source, sbranch, None, None, None
5014 return source, sbranch, None, None, None
5014 revs, checkout = hg.addbranchrevs(repo, other, branches, None)
5015 revs, checkout = hg.addbranchrevs(repo, other, branches, None)
5015 if revs:
5016 if revs:
5016 revs = [other.lookup(rev) for rev in revs]
5017 revs = [other.lookup(rev) for rev in revs]
5017 ui.debug('comparing with %s\n' % util.hidepassword(source))
5018 ui.debug('comparing with %s\n' % util.hidepassword(source))
5018 repo.ui.pushbuffer()
5019 repo.ui.pushbuffer()
5019 commoninc = discovery.findcommonincoming(repo, other, heads=revs)
5020 commoninc = discovery.findcommonincoming(repo, other, heads=revs)
5020 repo.ui.popbuffer()
5021 repo.ui.popbuffer()
5021 return source, sbranch, other, commoninc, commoninc[1]
5022 return source, sbranch, other, commoninc, commoninc[1]
5022
5023
5023 if needsincoming:
5024 if needsincoming:
5024 source, sbranch, sother, commoninc, incoming = getincoming()
5025 source, sbranch, sother, commoninc, incoming = getincoming()
5025 else:
5026 else:
5026 source = sbranch = sother = commoninc = incoming = None
5027 source = sbranch = sother = commoninc = incoming = None
5027
5028
5028 def getoutgoing():
5029 def getoutgoing():
5029 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
5030 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
5030 dbranch = branches[0]
5031 dbranch = branches[0]
5031 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
5032 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
5032 if source != dest:
5033 if source != dest:
5033 try:
5034 try:
5034 dother = hg.peer(repo, {}, dest)
5035 dother = hg.peer(repo, {}, dest)
5035 except error.RepoError:
5036 except error.RepoError:
5036 if opts.get('remote'):
5037 if opts.get('remote'):
5037 raise
5038 raise
5038 return dest, dbranch, None, None
5039 return dest, dbranch, None, None
5039 ui.debug('comparing with %s\n' % util.hidepassword(dest))
5040 ui.debug('comparing with %s\n' % util.hidepassword(dest))
5040 elif sother is None:
5041 elif sother is None:
5041 # there is no explicit destination peer, but source one is invalid
5042 # there is no explicit destination peer, but source one is invalid
5042 return dest, dbranch, None, None
5043 return dest, dbranch, None, None
5043 else:
5044 else:
5044 dother = sother
5045 dother = sother
5045 if (source != dest or (sbranch is not None and sbranch != dbranch)):
5046 if (source != dest or (sbranch is not None and sbranch != dbranch)):
5046 common = None
5047 common = None
5047 else:
5048 else:
5048 common = commoninc
5049 common = commoninc
5049 if revs:
5050 if revs:
5050 revs = [repo.lookup(rev) for rev in revs]
5051 revs = [repo.lookup(rev) for rev in revs]
5051 repo.ui.pushbuffer()
5052 repo.ui.pushbuffer()
5052 outgoing = discovery.findcommonoutgoing(repo, dother, onlyheads=revs,
5053 outgoing = discovery.findcommonoutgoing(repo, dother, onlyheads=revs,
5053 commoninc=common)
5054 commoninc=common)
5054 repo.ui.popbuffer()
5055 repo.ui.popbuffer()
5055 return dest, dbranch, dother, outgoing
5056 return dest, dbranch, dother, outgoing
5056
5057
5057 if needsoutgoing:
5058 if needsoutgoing:
5058 dest, dbranch, dother, outgoing = getoutgoing()
5059 dest, dbranch, dother, outgoing = getoutgoing()
5059 else:
5060 else:
5060 dest = dbranch = dother = outgoing = None
5061 dest = dbranch = dother = outgoing = None
5061
5062
5062 if opts.get('remote'):
5063 if opts.get('remote'):
5063 t = []
5064 t = []
5064 if incoming:
5065 if incoming:
5065 t.append(_('1 or more incoming'))
5066 t.append(_('1 or more incoming'))
5066 o = outgoing.missing
5067 o = outgoing.missing
5067 if o:
5068 if o:
5068 t.append(_('%d outgoing') % len(o))
5069 t.append(_('%d outgoing') % len(o))
5069 other = dother or sother
5070 other = dother or sother
5070 if 'bookmarks' in other.listkeys('namespaces'):
5071 if 'bookmarks' in other.listkeys('namespaces'):
5071 counts = bookmarks.summary(repo, other)
5072 counts = bookmarks.summary(repo, other)
5072 if counts[0] > 0:
5073 if counts[0] > 0:
5073 t.append(_('%d incoming bookmarks') % counts[0])
5074 t.append(_('%d incoming bookmarks') % counts[0])
5074 if counts[1] > 0:
5075 if counts[1] > 0:
5075 t.append(_('%d outgoing bookmarks') % counts[1])
5076 t.append(_('%d outgoing bookmarks') % counts[1])
5076
5077
5077 if t:
5078 if t:
5078 # i18n: column positioning for "hg summary"
5079 # i18n: column positioning for "hg summary"
5079 ui.write(_('remote: %s\n') % (', '.join(t)))
5080 ui.write(_('remote: %s\n') % (', '.join(t)))
5080 else:
5081 else:
5081 # i18n: column positioning for "hg summary"
5082 # i18n: column positioning for "hg summary"
5082 ui.status(_('remote: (synced)\n'))
5083 ui.status(_('remote: (synced)\n'))
5083
5084
5084 cmdutil.summaryremotehooks(ui, repo, opts,
5085 cmdutil.summaryremotehooks(ui, repo, opts,
5085 ((source, sbranch, sother, commoninc),
5086 ((source, sbranch, sother, commoninc),
5086 (dest, dbranch, dother, outgoing)))
5087 (dest, dbranch, dother, outgoing)))
5087
5088
5088 @command('tag',
5089 @command('tag',
5089 [('f', 'force', None, _('force tag')),
5090 [('f', 'force', None, _('force tag')),
5090 ('l', 'local', None, _('make the tag local')),
5091 ('l', 'local', None, _('make the tag local')),
5091 ('r', 'rev', '', _('revision to tag'), _('REV')),
5092 ('r', 'rev', '', _('revision to tag'), _('REV')),
5092 ('', 'remove', None, _('remove a tag')),
5093 ('', 'remove', None, _('remove a tag')),
5093 # -l/--local is already there, commitopts cannot be used
5094 # -l/--local is already there, commitopts cannot be used
5094 ('e', 'edit', None, _('invoke editor on commit messages')),
5095 ('e', 'edit', None, _('invoke editor on commit messages')),
5095 ('m', 'message', '', _('use text as commit message'), _('TEXT')),
5096 ('m', 'message', '', _('use text as commit message'), _('TEXT')),
5096 ] + commitopts2,
5097 ] + commitopts2,
5097 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'))
5098 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'))
5098 def tag(ui, repo, name1, *names, **opts):
5099 def tag(ui, repo, name1, *names, **opts):
5099 """add one or more tags for the current or given revision
5100 """add one or more tags for the current or given revision
5100
5101
5101 Name a particular revision using <name>.
5102 Name a particular revision using <name>.
5102
5103
5103 Tags are used to name particular revisions of the repository and are
5104 Tags are used to name particular revisions of the repository and are
5104 very useful to compare different revisions, to go back to significant
5105 very useful to compare different revisions, to go back to significant
5105 earlier versions or to mark branch points as releases, etc. Changing
5106 earlier versions or to mark branch points as releases, etc. Changing
5106 an existing tag is normally disallowed; use -f/--force to override.
5107 an existing tag is normally disallowed; use -f/--force to override.
5107
5108
5108 If no revision is given, the parent of the working directory is
5109 If no revision is given, the parent of the working directory is
5109 used.
5110 used.
5110
5111
5111 To facilitate version control, distribution, and merging of tags,
5112 To facilitate version control, distribution, and merging of tags,
5112 they are stored as a file named ".hgtags" which is managed similarly
5113 they are stored as a file named ".hgtags" which is managed similarly
5113 to other project files and can be hand-edited if necessary. This
5114 to other project files and can be hand-edited if necessary. This
5114 also means that tagging creates a new commit. The file
5115 also means that tagging creates a new commit. The file
5115 ".hg/localtags" is used for local tags (not shared among
5116 ".hg/localtags" is used for local tags (not shared among
5116 repositories).
5117 repositories).
5117
5118
5118 Tag commits are usually made at the head of a branch. If the parent
5119 Tag commits are usually made at the head of a branch. If the parent
5119 of the working directory is not a branch head, :hg:`tag` aborts; use
5120 of the working directory is not a branch head, :hg:`tag` aborts; use
5120 -f/--force to force the tag commit to be based on a non-head
5121 -f/--force to force the tag commit to be based on a non-head
5121 changeset.
5122 changeset.
5122
5123
5123 See :hg:`help dates` for a list of formats valid for -d/--date.
5124 See :hg:`help dates` for a list of formats valid for -d/--date.
5124
5125
5125 Since tag names have priority over branch names during revision
5126 Since tag names have priority over branch names during revision
5126 lookup, using an existing branch name as a tag name is discouraged.
5127 lookup, using an existing branch name as a tag name is discouraged.
5127
5128
5128 Returns 0 on success.
5129 Returns 0 on success.
5129 """
5130 """
5130 opts = pycompat.byteskwargs(opts)
5131 opts = pycompat.byteskwargs(opts)
5131 wlock = lock = None
5132 wlock = lock = None
5132 try:
5133 try:
5133 wlock = repo.wlock()
5134 wlock = repo.wlock()
5134 lock = repo.lock()
5135 lock = repo.lock()
5135 rev_ = "."
5136 rev_ = "."
5136 names = [t.strip() for t in (name1,) + names]
5137 names = [t.strip() for t in (name1,) + names]
5137 if len(names) != len(set(names)):
5138 if len(names) != len(set(names)):
5138 raise error.Abort(_('tag names must be unique'))
5139 raise error.Abort(_('tag names must be unique'))
5139 for n in names:
5140 for n in names:
5140 scmutil.checknewlabel(repo, n, 'tag')
5141 scmutil.checknewlabel(repo, n, 'tag')
5141 if not n:
5142 if not n:
5142 raise error.Abort(_('tag names cannot consist entirely of '
5143 raise error.Abort(_('tag names cannot consist entirely of '
5143 'whitespace'))
5144 'whitespace'))
5144 if opts.get('rev') and opts.get('remove'):
5145 if opts.get('rev') and opts.get('remove'):
5145 raise error.Abort(_("--rev and --remove are incompatible"))
5146 raise error.Abort(_("--rev and --remove are incompatible"))
5146 if opts.get('rev'):
5147 if opts.get('rev'):
5147 rev_ = opts['rev']
5148 rev_ = opts['rev']
5148 message = opts.get('message')
5149 message = opts.get('message')
5149 if opts.get('remove'):
5150 if opts.get('remove'):
5150 if opts.get('local'):
5151 if opts.get('local'):
5151 expectedtype = 'local'
5152 expectedtype = 'local'
5152 else:
5153 else:
5153 expectedtype = 'global'
5154 expectedtype = 'global'
5154
5155
5155 for n in names:
5156 for n in names:
5156 if not repo.tagtype(n):
5157 if not repo.tagtype(n):
5157 raise error.Abort(_("tag '%s' does not exist") % n)
5158 raise error.Abort(_("tag '%s' does not exist") % n)
5158 if repo.tagtype(n) != expectedtype:
5159 if repo.tagtype(n) != expectedtype:
5159 if expectedtype == 'global':
5160 if expectedtype == 'global':
5160 raise error.Abort(_("tag '%s' is not a global tag") % n)
5161 raise error.Abort(_("tag '%s' is not a global tag") % n)
5161 else:
5162 else:
5162 raise error.Abort(_("tag '%s' is not a local tag") % n)
5163 raise error.Abort(_("tag '%s' is not a local tag") % n)
5163 rev_ = 'null'
5164 rev_ = 'null'
5164 if not message:
5165 if not message:
5165 # we don't translate commit messages
5166 # we don't translate commit messages
5166 message = 'Removed tag %s' % ', '.join(names)
5167 message = 'Removed tag %s' % ', '.join(names)
5167 elif not opts.get('force'):
5168 elif not opts.get('force'):
5168 for n in names:
5169 for n in names:
5169 if n in repo.tags():
5170 if n in repo.tags():
5170 raise error.Abort(_("tag '%s' already exists "
5171 raise error.Abort(_("tag '%s' already exists "
5171 "(use -f to force)") % n)
5172 "(use -f to force)") % n)
5172 if not opts.get('local'):
5173 if not opts.get('local'):
5173 p1, p2 = repo.dirstate.parents()
5174 p1, p2 = repo.dirstate.parents()
5174 if p2 != nullid:
5175 if p2 != nullid:
5175 raise error.Abort(_('uncommitted merge'))
5176 raise error.Abort(_('uncommitted merge'))
5176 bheads = repo.branchheads()
5177 bheads = repo.branchheads()
5177 if not opts.get('force') and bheads and p1 not in bheads:
5178 if not opts.get('force') and bheads and p1 not in bheads:
5178 raise error.Abort(_('working directory is not at a branch head '
5179 raise error.Abort(_('working directory is not at a branch head '
5179 '(use -f to force)'))
5180 '(use -f to force)'))
5180 r = scmutil.revsingle(repo, rev_).node()
5181 r = scmutil.revsingle(repo, rev_).node()
5181
5182
5182 if not message:
5183 if not message:
5183 # we don't translate commit messages
5184 # we don't translate commit messages
5184 message = ('Added tag %s for changeset %s' %
5185 message = ('Added tag %s for changeset %s' %
5185 (', '.join(names), short(r)))
5186 (', '.join(names), short(r)))
5186
5187
5187 date = opts.get('date')
5188 date = opts.get('date')
5188 if date:
5189 if date:
5189 date = util.parsedate(date)
5190 date = util.parsedate(date)
5190
5191
5191 if opts.get('remove'):
5192 if opts.get('remove'):
5192 editform = 'tag.remove'
5193 editform = 'tag.remove'
5193 else:
5194 else:
5194 editform = 'tag.add'
5195 editform = 'tag.add'
5195 editor = cmdutil.getcommiteditor(editform=editform,
5196 editor = cmdutil.getcommiteditor(editform=editform,
5196 **pycompat.strkwargs(opts))
5197 **pycompat.strkwargs(opts))
5197
5198
5198 # don't allow tagging the null rev
5199 # don't allow tagging the null rev
5199 if (not opts.get('remove') and
5200 if (not opts.get('remove') and
5200 scmutil.revsingle(repo, rev_).rev() == nullrev):
5201 scmutil.revsingle(repo, rev_).rev() == nullrev):
5201 raise error.Abort(_("cannot tag null revision"))
5202 raise error.Abort(_("cannot tag null revision"))
5202
5203
5203 tagsmod.tag(repo, names, r, message, opts.get('local'),
5204 tagsmod.tag(repo, names, r, message, opts.get('local'),
5204 opts.get('user'), date, editor=editor)
5205 opts.get('user'), date, editor=editor)
5205 finally:
5206 finally:
5206 release(lock, wlock)
5207 release(lock, wlock)
5207
5208
5208 @command('tags', formatteropts, '')
5209 @command('tags', formatteropts, '')
5209 def tags(ui, repo, **opts):
5210 def tags(ui, repo, **opts):
5210 """list repository tags
5211 """list repository tags
5211
5212
5212 This lists both regular and local tags. When the -v/--verbose
5213 This lists both regular and local tags. When the -v/--verbose
5213 switch is used, a third column "local" is printed for local tags.
5214 switch is used, a third column "local" is printed for local tags.
5214 When the -q/--quiet switch is used, only the tag name is printed.
5215 When the -q/--quiet switch is used, only the tag name is printed.
5215
5216
5216 Returns 0 on success.
5217 Returns 0 on success.
5217 """
5218 """
5218
5219
5219 opts = pycompat.byteskwargs(opts)
5220 opts = pycompat.byteskwargs(opts)
5220 ui.pager('tags')
5221 ui.pager('tags')
5221 fm = ui.formatter('tags', opts)
5222 fm = ui.formatter('tags', opts)
5222 hexfunc = fm.hexfunc
5223 hexfunc = fm.hexfunc
5223 tagtype = ""
5224 tagtype = ""
5224
5225
5225 for t, n in reversed(repo.tagslist()):
5226 for t, n in reversed(repo.tagslist()):
5226 hn = hexfunc(n)
5227 hn = hexfunc(n)
5227 label = 'tags.normal'
5228 label = 'tags.normal'
5228 tagtype = ''
5229 tagtype = ''
5229 if repo.tagtype(t) == 'local':
5230 if repo.tagtype(t) == 'local':
5230 label = 'tags.local'
5231 label = 'tags.local'
5231 tagtype = 'local'
5232 tagtype = 'local'
5232
5233
5233 fm.startitem()
5234 fm.startitem()
5234 fm.write('tag', '%s', t, label=label)
5235 fm.write('tag', '%s', t, label=label)
5235 fmt = " " * (30 - encoding.colwidth(t)) + ' %5d:%s'
5236 fmt = " " * (30 - encoding.colwidth(t)) + ' %5d:%s'
5236 fm.condwrite(not ui.quiet, 'rev node', fmt,
5237 fm.condwrite(not ui.quiet, 'rev node', fmt,
5237 repo.changelog.rev(n), hn, label=label)
5238 repo.changelog.rev(n), hn, label=label)
5238 fm.condwrite(ui.verbose and tagtype, 'type', ' %s',
5239 fm.condwrite(ui.verbose and tagtype, 'type', ' %s',
5239 tagtype, label=label)
5240 tagtype, label=label)
5240 fm.plain('\n')
5241 fm.plain('\n')
5241 fm.end()
5242 fm.end()
5242
5243
5243 @command('tip',
5244 @command('tip',
5244 [('p', 'patch', None, _('show patch')),
5245 [('p', 'patch', None, _('show patch')),
5245 ('g', 'git', None, _('use git extended diff format')),
5246 ('g', 'git', None, _('use git extended diff format')),
5246 ] + templateopts,
5247 ] + templateopts,
5247 _('[-p] [-g]'))
5248 _('[-p] [-g]'))
5248 def tip(ui, repo, **opts):
5249 def tip(ui, repo, **opts):
5249 """show the tip revision (DEPRECATED)
5250 """show the tip revision (DEPRECATED)
5250
5251
5251 The tip revision (usually just called the tip) is the changeset
5252 The tip revision (usually just called the tip) is the changeset
5252 most recently added to the repository (and therefore the most
5253 most recently added to the repository (and therefore the most
5253 recently changed head).
5254 recently changed head).
5254
5255
5255 If you have just made a commit, that commit will be the tip. If
5256 If you have just made a commit, that commit will be the tip. If
5256 you have just pulled changes from another repository, the tip of
5257 you have just pulled changes from another repository, the tip of
5257 that repository becomes the current tip. The "tip" tag is special
5258 that repository becomes the current tip. The "tip" tag is special
5258 and cannot be renamed or assigned to a different changeset.
5259 and cannot be renamed or assigned to a different changeset.
5259
5260
5260 This command is deprecated, please use :hg:`heads` instead.
5261 This command is deprecated, please use :hg:`heads` instead.
5261
5262
5262 Returns 0 on success.
5263 Returns 0 on success.
5263 """
5264 """
5264 opts = pycompat.byteskwargs(opts)
5265 opts = pycompat.byteskwargs(opts)
5265 displayer = cmdutil.show_changeset(ui, repo, opts)
5266 displayer = cmdutil.show_changeset(ui, repo, opts)
5266 displayer.show(repo['tip'])
5267 displayer.show(repo['tip'])
5267 displayer.close()
5268 displayer.close()
5268
5269
5269 @command('unbundle',
5270 @command('unbundle',
5270 [('u', 'update', None,
5271 [('u', 'update', None,
5271 _('update to new branch head if changesets were unbundled'))],
5272 _('update to new branch head if changesets were unbundled'))],
5272 _('[-u] FILE...'))
5273 _('[-u] FILE...'))
5273 def unbundle(ui, repo, fname1, *fnames, **opts):
5274 def unbundle(ui, repo, fname1, *fnames, **opts):
5274 """apply one or more bundle files
5275 """apply one or more bundle files
5275
5276
5276 Apply one or more bundle files generated by :hg:`bundle`.
5277 Apply one or more bundle files generated by :hg:`bundle`.
5277
5278
5278 Returns 0 on success, 1 if an update has unresolved files.
5279 Returns 0 on success, 1 if an update has unresolved files.
5279 """
5280 """
5280 fnames = (fname1,) + fnames
5281 fnames = (fname1,) + fnames
5281
5282
5282 with repo.lock():
5283 with repo.lock():
5283 for fname in fnames:
5284 for fname in fnames:
5284 f = hg.openpath(ui, fname)
5285 f = hg.openpath(ui, fname)
5285 gen = exchange.readbundle(ui, f, fname)
5286 gen = exchange.readbundle(ui, f, fname)
5286 if isinstance(gen, bundle2.unbundle20):
5287 if isinstance(gen, bundle2.unbundle20):
5287 tr = repo.transaction('unbundle')
5288 tr = repo.transaction('unbundle')
5288 try:
5289 try:
5289 op = bundle2.applybundle(repo, gen, tr, source='unbundle',
5290 op = bundle2.applybundle(repo, gen, tr, source='unbundle',
5290 url='bundle:' + fname)
5291 url='bundle:' + fname)
5291 tr.close()
5292 tr.close()
5292 except error.BundleUnknownFeatureError as exc:
5293 except error.BundleUnknownFeatureError as exc:
5293 raise error.Abort(_('%s: unknown bundle feature, %s')
5294 raise error.Abort(_('%s: unknown bundle feature, %s')
5294 % (fname, exc),
5295 % (fname, exc),
5295 hint=_("see https://mercurial-scm.org/"
5296 hint=_("see https://mercurial-scm.org/"
5296 "wiki/BundleFeature for more "
5297 "wiki/BundleFeature for more "
5297 "information"))
5298 "information"))
5298 finally:
5299 finally:
5299 if tr:
5300 if tr:
5300 tr.release()
5301 tr.release()
5301 changes = [r.get('return', 0)
5302 changes = [r.get('return', 0)
5302 for r in op.records['changegroup']]
5303 for r in op.records['changegroup']]
5303 modheads = changegroup.combineresults(changes)
5304 modheads = changegroup.combineresults(changes)
5304 elif isinstance(gen, streamclone.streamcloneapplier):
5305 elif isinstance(gen, streamclone.streamcloneapplier):
5305 raise error.Abort(
5306 raise error.Abort(
5306 _('packed bundles cannot be applied with '
5307 _('packed bundles cannot be applied with '
5307 '"hg unbundle"'),
5308 '"hg unbundle"'),
5308 hint=_('use "hg debugapplystreamclonebundle"'))
5309 hint=_('use "hg debugapplystreamclonebundle"'))
5309 else:
5310 else:
5310 modheads = gen.apply(repo, 'unbundle', 'bundle:' + fname)
5311 modheads = gen.apply(repo, 'unbundle', 'bundle:' + fname)
5311
5312
5312 return postincoming(ui, repo, modheads, opts.get(r'update'), None, None)
5313 return postincoming(ui, repo, modheads, opts.get(r'update'), None, None)
5313
5314
5314 @command('^update|up|checkout|co',
5315 @command('^update|up|checkout|co',
5315 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
5316 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
5316 ('c', 'check', None, _('require clean working directory')),
5317 ('c', 'check', None, _('require clean working directory')),
5317 ('m', 'merge', None, _('merge uncommitted changes')),
5318 ('m', 'merge', None, _('merge uncommitted changes')),
5318 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
5319 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
5319 ('r', 'rev', '', _('revision'), _('REV'))
5320 ('r', 'rev', '', _('revision'), _('REV'))
5320 ] + mergetoolopts,
5321 ] + mergetoolopts,
5321 _('[-C|-c|-m] [-d DATE] [[-r] REV]'))
5322 _('[-C|-c|-m] [-d DATE] [[-r] REV]'))
5322 def update(ui, repo, node=None, rev=None, clean=False, date=None, check=False,
5323 def update(ui, repo, node=None, rev=None, clean=False, date=None, check=False,
5323 merge=None, tool=None):
5324 merge=None, tool=None):
5324 """update working directory (or switch revisions)
5325 """update working directory (or switch revisions)
5325
5326
5326 Update the repository's working directory to the specified
5327 Update the repository's working directory to the specified
5327 changeset. If no changeset is specified, update to the tip of the
5328 changeset. If no changeset is specified, update to the tip of the
5328 current named branch and move the active bookmark (see :hg:`help
5329 current named branch and move the active bookmark (see :hg:`help
5329 bookmarks`).
5330 bookmarks`).
5330
5331
5331 Update sets the working directory's parent revision to the specified
5332 Update sets the working directory's parent revision to the specified
5332 changeset (see :hg:`help parents`).
5333 changeset (see :hg:`help parents`).
5333
5334
5334 If the changeset is not a descendant or ancestor of the working
5335 If the changeset is not a descendant or ancestor of the working
5335 directory's parent and there are uncommitted changes, the update is
5336 directory's parent and there are uncommitted changes, the update is
5336 aborted. With the -c/--check option, the working directory is checked
5337 aborted. With the -c/--check option, the working directory is checked
5337 for uncommitted changes; if none are found, the working directory is
5338 for uncommitted changes; if none are found, the working directory is
5338 updated to the specified changeset.
5339 updated to the specified changeset.
5339
5340
5340 .. container:: verbose
5341 .. container:: verbose
5341
5342
5342 The -C/--clean, -c/--check, and -m/--merge options control what
5343 The -C/--clean, -c/--check, and -m/--merge options control what
5343 happens if the working directory contains uncommitted changes.
5344 happens if the working directory contains uncommitted changes.
5344 At most of one of them can be specified.
5345 At most of one of them can be specified.
5345
5346
5346 1. If no option is specified, and if
5347 1. If no option is specified, and if
5347 the requested changeset is an ancestor or descendant of
5348 the requested changeset is an ancestor or descendant of
5348 the working directory's parent, the uncommitted changes
5349 the working directory's parent, the uncommitted changes
5349 are merged into the requested changeset and the merged
5350 are merged into the requested changeset and the merged
5350 result is left uncommitted. If the requested changeset is
5351 result is left uncommitted. If the requested changeset is
5351 not an ancestor or descendant (that is, it is on another
5352 not an ancestor or descendant (that is, it is on another
5352 branch), the update is aborted and the uncommitted changes
5353 branch), the update is aborted and the uncommitted changes
5353 are preserved.
5354 are preserved.
5354
5355
5355 2. With the -m/--merge option, the update is allowed even if the
5356 2. With the -m/--merge option, the update is allowed even if the
5356 requested changeset is not an ancestor or descendant of
5357 requested changeset is not an ancestor or descendant of
5357 the working directory's parent.
5358 the working directory's parent.
5358
5359
5359 3. With the -c/--check option, the update is aborted and the
5360 3. With the -c/--check option, the update is aborted and the
5360 uncommitted changes are preserved.
5361 uncommitted changes are preserved.
5361
5362
5362 4. With the -C/--clean option, uncommitted changes are discarded and
5363 4. With the -C/--clean option, uncommitted changes are discarded and
5363 the working directory is updated to the requested changeset.
5364 the working directory is updated to the requested changeset.
5364
5365
5365 To cancel an uncommitted merge (and lose your changes), use
5366 To cancel an uncommitted merge (and lose your changes), use
5366 :hg:`update --clean .`.
5367 :hg:`update --clean .`.
5367
5368
5368 Use null as the changeset to remove the working directory (like
5369 Use null as the changeset to remove the working directory (like
5369 :hg:`clone -U`).
5370 :hg:`clone -U`).
5370
5371
5371 If you want to revert just one file to an older revision, use
5372 If you want to revert just one file to an older revision, use
5372 :hg:`revert [-r REV] NAME`.
5373 :hg:`revert [-r REV] NAME`.
5373
5374
5374 See :hg:`help dates` for a list of formats valid for -d/--date.
5375 See :hg:`help dates` for a list of formats valid for -d/--date.
5375
5376
5376 Returns 0 on success, 1 if there are unresolved files.
5377 Returns 0 on success, 1 if there are unresolved files.
5377 """
5378 """
5378 if rev and node:
5379 if rev and node:
5379 raise error.Abort(_("please specify just one revision"))
5380 raise error.Abort(_("please specify just one revision"))
5380
5381
5381 if ui.configbool('commands', 'update.requiredest'):
5382 if ui.configbool('commands', 'update.requiredest'):
5382 if not node and not rev and not date:
5383 if not node and not rev and not date:
5383 raise error.Abort(_('you must specify a destination'),
5384 raise error.Abort(_('you must specify a destination'),
5384 hint=_('for example: hg update ".::"'))
5385 hint=_('for example: hg update ".::"'))
5385
5386
5386 if rev is None or rev == '':
5387 if rev is None or rev == '':
5387 rev = node
5388 rev = node
5388
5389
5389 if date and rev is not None:
5390 if date and rev is not None:
5390 raise error.Abort(_("you can't specify a revision and a date"))
5391 raise error.Abort(_("you can't specify a revision and a date"))
5391
5392
5392 if len([x for x in (clean, check, merge) if x]) > 1:
5393 if len([x for x in (clean, check, merge) if x]) > 1:
5393 raise error.Abort(_("can only specify one of -C/--clean, -c/--check, "
5394 raise error.Abort(_("can only specify one of -C/--clean, -c/--check, "
5394 "or -m/merge"))
5395 "or -m/merge"))
5395
5396
5396 updatecheck = None
5397 updatecheck = None
5397 if check:
5398 if check:
5398 updatecheck = 'abort'
5399 updatecheck = 'abort'
5399 elif merge:
5400 elif merge:
5400 updatecheck = 'none'
5401 updatecheck = 'none'
5401
5402
5402 with repo.wlock():
5403 with repo.wlock():
5403 cmdutil.clearunfinished(repo)
5404 cmdutil.clearunfinished(repo)
5404
5405
5405 if date:
5406 if date:
5406 rev = cmdutil.finddate(ui, repo, date)
5407 rev = cmdutil.finddate(ui, repo, date)
5407
5408
5408 # if we defined a bookmark, we have to remember the original name
5409 # if we defined a bookmark, we have to remember the original name
5409 brev = rev
5410 brev = rev
5410 rev = scmutil.revsingle(repo, rev, rev).rev()
5411 rev = scmutil.revsingle(repo, rev, rev).rev()
5411
5412
5412 repo.ui.setconfig('ui', 'forcemerge', tool, 'update')
5413 repo.ui.setconfig('ui', 'forcemerge', tool, 'update')
5413
5414
5414 return hg.updatetotally(ui, repo, rev, brev, clean=clean,
5415 return hg.updatetotally(ui, repo, rev, brev, clean=clean,
5415 updatecheck=updatecheck)
5416 updatecheck=updatecheck)
5416
5417
5417 @command('verify', [])
5418 @command('verify', [])
5418 def verify(ui, repo):
5419 def verify(ui, repo):
5419 """verify the integrity of the repository
5420 """verify the integrity of the repository
5420
5421
5421 Verify the integrity of the current repository.
5422 Verify the integrity of the current repository.
5422
5423
5423 This will perform an extensive check of the repository's
5424 This will perform an extensive check of the repository's
5424 integrity, validating the hashes and checksums of each entry in
5425 integrity, validating the hashes and checksums of each entry in
5425 the changelog, manifest, and tracked files, as well as the
5426 the changelog, manifest, and tracked files, as well as the
5426 integrity of their crosslinks and indices.
5427 integrity of their crosslinks and indices.
5427
5428
5428 Please see https://mercurial-scm.org/wiki/RepositoryCorruption
5429 Please see https://mercurial-scm.org/wiki/RepositoryCorruption
5429 for more information about recovery from corruption of the
5430 for more information about recovery from corruption of the
5430 repository.
5431 repository.
5431
5432
5432 Returns 0 on success, 1 if errors are encountered.
5433 Returns 0 on success, 1 if errors are encountered.
5433 """
5434 """
5434 return hg.verify(repo)
5435 return hg.verify(repo)
5435
5436
5436 @command('version', [] + formatteropts, norepo=True)
5437 @command('version', [] + formatteropts, norepo=True)
5437 def version_(ui, **opts):
5438 def version_(ui, **opts):
5438 """output version and copyright information"""
5439 """output version and copyright information"""
5439 opts = pycompat.byteskwargs(opts)
5440 opts = pycompat.byteskwargs(opts)
5440 if ui.verbose:
5441 if ui.verbose:
5441 ui.pager('version')
5442 ui.pager('version')
5442 fm = ui.formatter("version", opts)
5443 fm = ui.formatter("version", opts)
5443 fm.startitem()
5444 fm.startitem()
5444 fm.write("ver", _("Mercurial Distributed SCM (version %s)\n"),
5445 fm.write("ver", _("Mercurial Distributed SCM (version %s)\n"),
5445 util.version())
5446 util.version())
5446 license = _(
5447 license = _(
5447 "(see https://mercurial-scm.org for more information)\n"
5448 "(see https://mercurial-scm.org for more information)\n"
5448 "\nCopyright (C) 2005-2017 Matt Mackall and others\n"
5449 "\nCopyright (C) 2005-2017 Matt Mackall and others\n"
5449 "This is free software; see the source for copying conditions. "
5450 "This is free software; see the source for copying conditions. "
5450 "There is NO\nwarranty; "
5451 "There is NO\nwarranty; "
5451 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
5452 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
5452 )
5453 )
5453 if not ui.quiet:
5454 if not ui.quiet:
5454 fm.plain(license)
5455 fm.plain(license)
5455
5456
5456 if ui.verbose:
5457 if ui.verbose:
5457 fm.plain(_("\nEnabled extensions:\n\n"))
5458 fm.plain(_("\nEnabled extensions:\n\n"))
5458 # format names and versions into columns
5459 # format names and versions into columns
5459 names = []
5460 names = []
5460 vers = []
5461 vers = []
5461 isinternals = []
5462 isinternals = []
5462 for name, module in extensions.extensions():
5463 for name, module in extensions.extensions():
5463 names.append(name)
5464 names.append(name)
5464 vers.append(extensions.moduleversion(module) or None)
5465 vers.append(extensions.moduleversion(module) or None)
5465 isinternals.append(extensions.ismoduleinternal(module))
5466 isinternals.append(extensions.ismoduleinternal(module))
5466 fn = fm.nested("extensions")
5467 fn = fm.nested("extensions")
5467 if names:
5468 if names:
5468 namefmt = " %%-%ds " % max(len(n) for n in names)
5469 namefmt = " %%-%ds " % max(len(n) for n in names)
5469 places = [_("external"), _("internal")]
5470 places = [_("external"), _("internal")]
5470 for n, v, p in zip(names, vers, isinternals):
5471 for n, v, p in zip(names, vers, isinternals):
5471 fn.startitem()
5472 fn.startitem()
5472 fn.condwrite(ui.verbose, "name", namefmt, n)
5473 fn.condwrite(ui.verbose, "name", namefmt, n)
5473 if ui.verbose:
5474 if ui.verbose:
5474 fn.plain("%s " % places[p])
5475 fn.plain("%s " % places[p])
5475 fn.data(bundled=p)
5476 fn.data(bundled=p)
5476 fn.condwrite(ui.verbose and v, "ver", "%s", v)
5477 fn.condwrite(ui.verbose and v, "ver", "%s", v)
5477 if ui.verbose:
5478 if ui.verbose:
5478 fn.plain("\n")
5479 fn.plain("\n")
5479 fn.end()
5480 fn.end()
5480 fm.end()
5481 fm.end()
5481
5482
5482 def loadcmdtable(ui, name, cmdtable):
5483 def loadcmdtable(ui, name, cmdtable):
5483 """Load command functions from specified cmdtable
5484 """Load command functions from specified cmdtable
5484 """
5485 """
5485 overrides = [cmd for cmd in cmdtable if cmd in table]
5486 overrides = [cmd for cmd in cmdtable if cmd in table]
5486 if overrides:
5487 if overrides:
5487 ui.warn(_("extension '%s' overrides commands: %s\n")
5488 ui.warn(_("extension '%s' overrides commands: %s\n")
5488 % (name, " ".join(overrides)))
5489 % (name, " ".join(overrides)))
5489 table.update(cmdtable)
5490 table.update(cmdtable)
@@ -1,1987 +1,1988 b''
1 # subrepo.py - sub-repository handling for Mercurial
1 # subrepo.py - sub-repository handling for Mercurial
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 posixpath
14 import posixpath
15 import re
15 import re
16 import stat
16 import stat
17 import subprocess
17 import subprocess
18 import sys
18 import sys
19 import tarfile
19 import tarfile
20 import xml.dom.minidom
20 import xml.dom.minidom
21
21
22
22
23 from .i18n import _
23 from .i18n import _
24 from . import (
24 from . import (
25 cmdutil,
25 cmdutil,
26 config,
26 config,
27 encoding,
27 encoding,
28 error,
28 error,
29 exchange,
29 exchange,
30 filemerge,
30 filemerge,
31 match as matchmod,
31 match as matchmod,
32 node,
32 node,
33 pathutil,
33 pathutil,
34 phases,
34 phases,
35 pycompat,
35 pycompat,
36 scmutil,
36 scmutil,
37 util,
37 util,
38 vfs as vfsmod,
38 vfs as vfsmod,
39 )
39 )
40
40
41 hg = None
41 hg = None
42 propertycache = util.propertycache
42 propertycache = util.propertycache
43
43
44 nullstate = ('', '', 'empty')
44 nullstate = ('', '', 'empty')
45
45
46 def _expandedabspath(path):
46 def _expandedabspath(path):
47 '''
47 '''
48 get a path or url and if it is a path expand it and return an absolute path
48 get a path or url and if it is a path expand it and return an absolute path
49 '''
49 '''
50 expandedpath = util.urllocalpath(util.expandpath(path))
50 expandedpath = util.urllocalpath(util.expandpath(path))
51 u = util.url(expandedpath)
51 u = util.url(expandedpath)
52 if not u.scheme:
52 if not u.scheme:
53 path = util.normpath(os.path.abspath(u.path))
53 path = util.normpath(os.path.abspath(u.path))
54 return path
54 return path
55
55
56 def _getstorehashcachename(remotepath):
56 def _getstorehashcachename(remotepath):
57 '''get a unique filename for the store hash cache of a remote repository'''
57 '''get a unique filename for the store hash cache of a remote repository'''
58 return hashlib.sha1(_expandedabspath(remotepath)).hexdigest()[0:12]
58 return hashlib.sha1(_expandedabspath(remotepath)).hexdigest()[0:12]
59
59
60 class SubrepoAbort(error.Abort):
60 class SubrepoAbort(error.Abort):
61 """Exception class used to avoid handling a subrepo error more than once"""
61 """Exception class used to avoid handling a subrepo error more than once"""
62 def __init__(self, *args, **kw):
62 def __init__(self, *args, **kw):
63 self.subrepo = kw.pop('subrepo', None)
63 self.subrepo = kw.pop('subrepo', None)
64 self.cause = kw.pop('cause', None)
64 self.cause = kw.pop('cause', None)
65 error.Abort.__init__(self, *args, **kw)
65 error.Abort.__init__(self, *args, **kw)
66
66
67 def annotatesubrepoerror(func):
67 def annotatesubrepoerror(func):
68 def decoratedmethod(self, *args, **kargs):
68 def decoratedmethod(self, *args, **kargs):
69 try:
69 try:
70 res = func(self, *args, **kargs)
70 res = func(self, *args, **kargs)
71 except SubrepoAbort as ex:
71 except SubrepoAbort as ex:
72 # This exception has already been handled
72 # This exception has already been handled
73 raise ex
73 raise ex
74 except error.Abort as ex:
74 except error.Abort as ex:
75 subrepo = subrelpath(self)
75 subrepo = subrelpath(self)
76 errormsg = str(ex) + ' ' + _('(in subrepo %s)') % subrepo
76 errormsg = str(ex) + ' ' + _('(in subrepo %s)') % subrepo
77 # avoid handling this exception by raising a SubrepoAbort exception
77 # avoid handling this exception by raising a SubrepoAbort exception
78 raise SubrepoAbort(errormsg, hint=ex.hint, subrepo=subrepo,
78 raise SubrepoAbort(errormsg, hint=ex.hint, subrepo=subrepo,
79 cause=sys.exc_info())
79 cause=sys.exc_info())
80 return res
80 return res
81 return decoratedmethod
81 return decoratedmethod
82
82
83 def state(ctx, ui):
83 def state(ctx, ui):
84 """return a state dict, mapping subrepo paths configured in .hgsub
84 """return a state dict, mapping subrepo paths configured in .hgsub
85 to tuple: (source from .hgsub, revision from .hgsubstate, kind
85 to tuple: (source from .hgsub, revision from .hgsubstate, kind
86 (key in types dict))
86 (key in types dict))
87 """
87 """
88 p = config.config()
88 p = config.config()
89 repo = ctx.repo()
89 repo = ctx.repo()
90 def read(f, sections=None, remap=None):
90 def read(f, sections=None, remap=None):
91 if f in ctx:
91 if f in ctx:
92 try:
92 try:
93 data = ctx[f].data()
93 data = ctx[f].data()
94 except IOError as err:
94 except IOError as err:
95 if err.errno != errno.ENOENT:
95 if err.errno != errno.ENOENT:
96 raise
96 raise
97 # handle missing subrepo spec files as removed
97 # handle missing subrepo spec files as removed
98 ui.warn(_("warning: subrepo spec file \'%s\' not found\n") %
98 ui.warn(_("warning: subrepo spec file \'%s\' not found\n") %
99 repo.pathto(f))
99 repo.pathto(f))
100 return
100 return
101 p.parse(f, data, sections, remap, read)
101 p.parse(f, data, sections, remap, read)
102 else:
102 else:
103 raise error.Abort(_("subrepo spec file \'%s\' not found") %
103 raise error.Abort(_("subrepo spec file \'%s\' not found") %
104 repo.pathto(f))
104 repo.pathto(f))
105 if '.hgsub' in ctx:
105 if '.hgsub' in ctx:
106 read('.hgsub')
106 read('.hgsub')
107
107
108 for path, src in ui.configitems('subpaths'):
108 for path, src in ui.configitems('subpaths'):
109 p.set('subpaths', path, src, ui.configsource('subpaths', path))
109 p.set('subpaths', path, src, ui.configsource('subpaths', path))
110
110
111 rev = {}
111 rev = {}
112 if '.hgsubstate' in ctx:
112 if '.hgsubstate' in ctx:
113 try:
113 try:
114 for i, l in enumerate(ctx['.hgsubstate'].data().splitlines()):
114 for i, l in enumerate(ctx['.hgsubstate'].data().splitlines()):
115 l = l.lstrip()
115 l = l.lstrip()
116 if not l:
116 if not l:
117 continue
117 continue
118 try:
118 try:
119 revision, path = l.split(" ", 1)
119 revision, path = l.split(" ", 1)
120 except ValueError:
120 except ValueError:
121 raise error.Abort(_("invalid subrepository revision "
121 raise error.Abort(_("invalid subrepository revision "
122 "specifier in \'%s\' line %d")
122 "specifier in \'%s\' line %d")
123 % (repo.pathto('.hgsubstate'), (i + 1)))
123 % (repo.pathto('.hgsubstate'), (i + 1)))
124 rev[path] = revision
124 rev[path] = revision
125 except IOError as err:
125 except IOError as err:
126 if err.errno != errno.ENOENT:
126 if err.errno != errno.ENOENT:
127 raise
127 raise
128
128
129 def remap(src):
129 def remap(src):
130 for pattern, repl in p.items('subpaths'):
130 for pattern, repl in p.items('subpaths'):
131 # Turn r'C:\foo\bar' into r'C:\\foo\\bar' since re.sub
131 # Turn r'C:\foo\bar' into r'C:\\foo\\bar' since re.sub
132 # does a string decode.
132 # does a string decode.
133 repl = util.escapestr(repl)
133 repl = util.escapestr(repl)
134 # However, we still want to allow back references to go
134 # However, we still want to allow back references to go
135 # through unharmed, so we turn r'\\1' into r'\1'. Again,
135 # through unharmed, so we turn r'\\1' into r'\1'. Again,
136 # extra escapes are needed because re.sub string decodes.
136 # extra escapes are needed because re.sub string decodes.
137 repl = re.sub(r'\\\\([0-9]+)', r'\\\1', repl)
137 repl = re.sub(r'\\\\([0-9]+)', r'\\\1', repl)
138 try:
138 try:
139 src = re.sub(pattern, repl, src, 1)
139 src = re.sub(pattern, repl, src, 1)
140 except re.error as e:
140 except re.error as e:
141 raise error.Abort(_("bad subrepository pattern in %s: %s")
141 raise error.Abort(_("bad subrepository pattern in %s: %s")
142 % (p.source('subpaths', pattern), e))
142 % (p.source('subpaths', pattern), e))
143 return src
143 return src
144
144
145 state = {}
145 state = {}
146 for path, src in p[''].items():
146 for path, src in p[''].items():
147 kind = 'hg'
147 kind = 'hg'
148 if src.startswith('['):
148 if src.startswith('['):
149 if ']' not in src:
149 if ']' not in src:
150 raise error.Abort(_('missing ] in subrepo source'))
150 raise error.Abort(_('missing ] in subrepo source'))
151 kind, src = src.split(']', 1)
151 kind, src = src.split(']', 1)
152 kind = kind[1:]
152 kind = kind[1:]
153 src = src.lstrip() # strip any extra whitespace after ']'
153 src = src.lstrip() # strip any extra whitespace after ']'
154
154
155 if not util.url(src).isabs():
155 if not util.url(src).isabs():
156 parent = _abssource(repo, abort=False)
156 parent = _abssource(repo, abort=False)
157 if parent:
157 if parent:
158 parent = util.url(parent)
158 parent = util.url(parent)
159 parent.path = posixpath.join(parent.path or '', src)
159 parent.path = posixpath.join(parent.path or '', src)
160 parent.path = posixpath.normpath(parent.path)
160 parent.path = posixpath.normpath(parent.path)
161 joined = str(parent)
161 joined = str(parent)
162 # Remap the full joined path and use it if it changes,
162 # Remap the full joined path and use it if it changes,
163 # else remap the original source.
163 # else remap the original source.
164 remapped = remap(joined)
164 remapped = remap(joined)
165 if remapped == joined:
165 if remapped == joined:
166 src = remap(src)
166 src = remap(src)
167 else:
167 else:
168 src = remapped
168 src = remapped
169
169
170 src = remap(src)
170 src = remap(src)
171 state[util.pconvert(path)] = (src.strip(), rev.get(path, ''), kind)
171 state[util.pconvert(path)] = (src.strip(), rev.get(path, ''), kind)
172
172
173 return state
173 return state
174
174
175 def writestate(repo, state):
175 def writestate(repo, state):
176 """rewrite .hgsubstate in (outer) repo with these subrepo states"""
176 """rewrite .hgsubstate in (outer) repo with these subrepo states"""
177 lines = ['%s %s\n' % (state[s][1], s) for s in sorted(state)
177 lines = ['%s %s\n' % (state[s][1], s) for s in sorted(state)
178 if state[s][1] != nullstate[1]]
178 if state[s][1] != nullstate[1]]
179 repo.wwrite('.hgsubstate', ''.join(lines), '')
179 repo.wwrite('.hgsubstate', ''.join(lines), '')
180
180
181 def submerge(repo, wctx, mctx, actx, overwrite, labels=None):
181 def submerge(repo, wctx, mctx, actx, overwrite, labels=None):
182 """delegated from merge.applyupdates: merging of .hgsubstate file
182 """delegated from merge.applyupdates: merging of .hgsubstate file
183 in working context, merging context and ancestor context"""
183 in working context, merging context and ancestor context"""
184 if mctx == actx: # backwards?
184 if mctx == actx: # backwards?
185 actx = wctx.p1()
185 actx = wctx.p1()
186 s1 = wctx.substate
186 s1 = wctx.substate
187 s2 = mctx.substate
187 s2 = mctx.substate
188 sa = actx.substate
188 sa = actx.substate
189 sm = {}
189 sm = {}
190
190
191 repo.ui.debug("subrepo merge %s %s %s\n" % (wctx, mctx, actx))
191 repo.ui.debug("subrepo merge %s %s %s\n" % (wctx, mctx, actx))
192
192
193 def debug(s, msg, r=""):
193 def debug(s, msg, r=""):
194 if r:
194 if r:
195 r = "%s:%s:%s" % r
195 r = "%s:%s:%s" % r
196 repo.ui.debug(" subrepo %s: %s %s\n" % (s, msg, r))
196 repo.ui.debug(" subrepo %s: %s %s\n" % (s, msg, r))
197
197
198 promptssrc = filemerge.partextras(labels)
198 promptssrc = filemerge.partextras(labels)
199 for s, l in sorted(s1.iteritems()):
199 for s, l in sorted(s1.iteritems()):
200 prompts = None
200 prompts = None
201 a = sa.get(s, nullstate)
201 a = sa.get(s, nullstate)
202 ld = l # local state with possible dirty flag for compares
202 ld = l # local state with possible dirty flag for compares
203 if wctx.sub(s).dirty():
203 if wctx.sub(s).dirty():
204 ld = (l[0], l[1] + "+")
204 ld = (l[0], l[1] + "+")
205 if wctx == actx: # overwrite
205 if wctx == actx: # overwrite
206 a = ld
206 a = ld
207
207
208 prompts = promptssrc.copy()
208 prompts = promptssrc.copy()
209 prompts['s'] = s
209 prompts['s'] = s
210 if s in s2:
210 if s in s2:
211 r = s2[s]
211 r = s2[s]
212 if ld == r or r == a: # no change or local is newer
212 if ld == r or r == a: # no change or local is newer
213 sm[s] = l
213 sm[s] = l
214 continue
214 continue
215 elif ld == a: # other side changed
215 elif ld == a: # other side changed
216 debug(s, "other changed, get", r)
216 debug(s, "other changed, get", r)
217 wctx.sub(s).get(r, overwrite)
217 wctx.sub(s).get(r, overwrite)
218 sm[s] = r
218 sm[s] = r
219 elif ld[0] != r[0]: # sources differ
219 elif ld[0] != r[0]: # sources differ
220 prompts['lo'] = l[0]
220 prompts['lo'] = l[0]
221 prompts['ro'] = r[0]
221 prompts['ro'] = r[0]
222 if repo.ui.promptchoice(
222 if repo.ui.promptchoice(
223 _(' subrepository sources for %(s)s differ\n'
223 _(' subrepository sources for %(s)s differ\n'
224 'use (l)ocal%(l)s source (%(lo)s)'
224 'use (l)ocal%(l)s source (%(lo)s)'
225 ' or (r)emote%(o)s source (%(ro)s)?'
225 ' or (r)emote%(o)s source (%(ro)s)?'
226 '$$ &Local $$ &Remote') % prompts, 0):
226 '$$ &Local $$ &Remote') % prompts, 0):
227 debug(s, "prompt changed, get", r)
227 debug(s, "prompt changed, get", r)
228 wctx.sub(s).get(r, overwrite)
228 wctx.sub(s).get(r, overwrite)
229 sm[s] = r
229 sm[s] = r
230 elif ld[1] == a[1]: # local side is unchanged
230 elif ld[1] == a[1]: # local side is unchanged
231 debug(s, "other side changed, get", r)
231 debug(s, "other side changed, get", r)
232 wctx.sub(s).get(r, overwrite)
232 wctx.sub(s).get(r, overwrite)
233 sm[s] = r
233 sm[s] = r
234 else:
234 else:
235 debug(s, "both sides changed")
235 debug(s, "both sides changed")
236 srepo = wctx.sub(s)
236 srepo = wctx.sub(s)
237 prompts['sl'] = srepo.shortid(l[1])
237 prompts['sl'] = srepo.shortid(l[1])
238 prompts['sr'] = srepo.shortid(r[1])
238 prompts['sr'] = srepo.shortid(r[1])
239 option = repo.ui.promptchoice(
239 option = repo.ui.promptchoice(
240 _(' subrepository %(s)s diverged (local revision: %(sl)s, '
240 _(' subrepository %(s)s diverged (local revision: %(sl)s, '
241 'remote revision: %(sr)s)\n'
241 'remote revision: %(sr)s)\n'
242 '(M)erge, keep (l)ocal%(l)s or keep (r)emote%(o)s?'
242 '(M)erge, keep (l)ocal%(l)s or keep (r)emote%(o)s?'
243 '$$ &Merge $$ &Local $$ &Remote')
243 '$$ &Merge $$ &Local $$ &Remote')
244 % prompts, 0)
244 % prompts, 0)
245 if option == 0:
245 if option == 0:
246 wctx.sub(s).merge(r)
246 wctx.sub(s).merge(r)
247 sm[s] = l
247 sm[s] = l
248 debug(s, "merge with", r)
248 debug(s, "merge with", r)
249 elif option == 1:
249 elif option == 1:
250 sm[s] = l
250 sm[s] = l
251 debug(s, "keep local subrepo revision", l)
251 debug(s, "keep local subrepo revision", l)
252 else:
252 else:
253 wctx.sub(s).get(r, overwrite)
253 wctx.sub(s).get(r, overwrite)
254 sm[s] = r
254 sm[s] = r
255 debug(s, "get remote subrepo revision", r)
255 debug(s, "get remote subrepo revision", r)
256 elif ld == a: # remote removed, local unchanged
256 elif ld == a: # remote removed, local unchanged
257 debug(s, "remote removed, remove")
257 debug(s, "remote removed, remove")
258 wctx.sub(s).remove()
258 wctx.sub(s).remove()
259 elif a == nullstate: # not present in remote or ancestor
259 elif a == nullstate: # not present in remote or ancestor
260 debug(s, "local added, keep")
260 debug(s, "local added, keep")
261 sm[s] = l
261 sm[s] = l
262 continue
262 continue
263 else:
263 else:
264 if repo.ui.promptchoice(
264 if repo.ui.promptchoice(
265 _(' local%(l)s changed subrepository %(s)s'
265 _(' local%(l)s changed subrepository %(s)s'
266 ' which remote%(o)s removed\n'
266 ' which remote%(o)s removed\n'
267 'use (c)hanged version or (d)elete?'
267 'use (c)hanged version or (d)elete?'
268 '$$ &Changed $$ &Delete') % prompts, 0):
268 '$$ &Changed $$ &Delete') % prompts, 0):
269 debug(s, "prompt remove")
269 debug(s, "prompt remove")
270 wctx.sub(s).remove()
270 wctx.sub(s).remove()
271
271
272 for s, r in sorted(s2.items()):
272 for s, r in sorted(s2.items()):
273 prompts = None
273 prompts = None
274 if s in s1:
274 if s in s1:
275 continue
275 continue
276 elif s not in sa:
276 elif s not in sa:
277 debug(s, "remote added, get", r)
277 debug(s, "remote added, get", r)
278 mctx.sub(s).get(r)
278 mctx.sub(s).get(r)
279 sm[s] = r
279 sm[s] = r
280 elif r != sa[s]:
280 elif r != sa[s]:
281 prompts = promptssrc.copy()
281 prompts = promptssrc.copy()
282 prompts['s'] = s
282 prompts['s'] = s
283 if repo.ui.promptchoice(
283 if repo.ui.promptchoice(
284 _(' remote%(o)s changed subrepository %(s)s'
284 _(' remote%(o)s changed subrepository %(s)s'
285 ' which local%(l)s removed\n'
285 ' which local%(l)s removed\n'
286 'use (c)hanged version or (d)elete?'
286 'use (c)hanged version or (d)elete?'
287 '$$ &Changed $$ &Delete') % prompts, 0) == 0:
287 '$$ &Changed $$ &Delete') % prompts, 0) == 0:
288 debug(s, "prompt recreate", r)
288 debug(s, "prompt recreate", r)
289 mctx.sub(s).get(r)
289 mctx.sub(s).get(r)
290 sm[s] = r
290 sm[s] = r
291
291
292 # record merged .hgsubstate
292 # record merged .hgsubstate
293 writestate(repo, sm)
293 writestate(repo, sm)
294 return sm
294 return sm
295
295
296 def _updateprompt(ui, sub, dirty, local, remote):
296 def _updateprompt(ui, sub, dirty, local, remote):
297 if dirty:
297 if dirty:
298 msg = (_(' subrepository sources for %s differ\n'
298 msg = (_(' subrepository sources for %s differ\n'
299 'use (l)ocal source (%s) or (r)emote source (%s)?'
299 'use (l)ocal source (%s) or (r)emote source (%s)?'
300 '$$ &Local $$ &Remote')
300 '$$ &Local $$ &Remote')
301 % (subrelpath(sub), local, remote))
301 % (subrelpath(sub), local, remote))
302 else:
302 else:
303 msg = (_(' subrepository sources for %s differ (in checked out '
303 msg = (_(' subrepository sources for %s differ (in checked out '
304 'version)\n'
304 'version)\n'
305 'use (l)ocal source (%s) or (r)emote source (%s)?'
305 'use (l)ocal source (%s) or (r)emote source (%s)?'
306 '$$ &Local $$ &Remote')
306 '$$ &Local $$ &Remote')
307 % (subrelpath(sub), local, remote))
307 % (subrelpath(sub), local, remote))
308 return ui.promptchoice(msg, 0)
308 return ui.promptchoice(msg, 0)
309
309
310 def reporelpath(repo):
310 def reporelpath(repo):
311 """return path to this (sub)repo as seen from outermost repo"""
311 """return path to this (sub)repo as seen from outermost repo"""
312 parent = repo
312 parent = repo
313 while util.safehasattr(parent, '_subparent'):
313 while util.safehasattr(parent, '_subparent'):
314 parent = parent._subparent
314 parent = parent._subparent
315 return repo.root[len(pathutil.normasprefix(parent.root)):]
315 return repo.root[len(pathutil.normasprefix(parent.root)):]
316
316
317 def subrelpath(sub):
317 def subrelpath(sub):
318 """return path to this subrepo as seen from outermost repo"""
318 """return path to this subrepo as seen from outermost repo"""
319 return sub._relpath
319 return sub._relpath
320
320
321 def _abssource(repo, push=False, abort=True):
321 def _abssource(repo, push=False, abort=True):
322 """return pull/push path of repo - either based on parent repo .hgsub info
322 """return pull/push path of repo - either based on parent repo .hgsub info
323 or on the top repo config. Abort or return None if no source found."""
323 or on the top repo config. Abort or return None if no source found."""
324 if util.safehasattr(repo, '_subparent'):
324 if util.safehasattr(repo, '_subparent'):
325 source = util.url(repo._subsource)
325 source = util.url(repo._subsource)
326 if source.isabs():
326 if source.isabs():
327 return str(source)
327 return str(source)
328 source.path = posixpath.normpath(source.path)
328 source.path = posixpath.normpath(source.path)
329 parent = _abssource(repo._subparent, push, abort=False)
329 parent = _abssource(repo._subparent, push, abort=False)
330 if parent:
330 if parent:
331 parent = util.url(util.pconvert(parent))
331 parent = util.url(util.pconvert(parent))
332 parent.path = posixpath.join(parent.path or '', source.path)
332 parent.path = posixpath.join(parent.path or '', source.path)
333 parent.path = posixpath.normpath(parent.path)
333 parent.path = posixpath.normpath(parent.path)
334 return str(parent)
334 return str(parent)
335 else: # recursion reached top repo
335 else: # recursion reached top repo
336 if util.safehasattr(repo, '_subtoppath'):
336 if util.safehasattr(repo, '_subtoppath'):
337 return repo._subtoppath
337 return repo._subtoppath
338 if push and repo.ui.config('paths', 'default-push'):
338 if push and repo.ui.config('paths', 'default-push'):
339 return repo.ui.config('paths', 'default-push')
339 return repo.ui.config('paths', 'default-push')
340 if repo.ui.config('paths', 'default'):
340 if repo.ui.config('paths', 'default'):
341 return repo.ui.config('paths', 'default')
341 return repo.ui.config('paths', 'default')
342 if repo.shared():
342 if repo.shared():
343 # chop off the .hg component to get the default path form
343 # chop off the .hg component to get the default path form
344 return os.path.dirname(repo.sharedpath)
344 return os.path.dirname(repo.sharedpath)
345 if abort:
345 if abort:
346 raise error.Abort(_("default path for subrepository not found"))
346 raise error.Abort(_("default path for subrepository not found"))
347
347
348 def _sanitize(ui, vfs, ignore):
348 def _sanitize(ui, vfs, ignore):
349 for dirname, dirs, names in vfs.walk():
349 for dirname, dirs, names in vfs.walk():
350 for i, d in enumerate(dirs):
350 for i, d in enumerate(dirs):
351 if d.lower() == ignore:
351 if d.lower() == ignore:
352 del dirs[i]
352 del dirs[i]
353 break
353 break
354 if vfs.basename(dirname).lower() != '.hg':
354 if vfs.basename(dirname).lower() != '.hg':
355 continue
355 continue
356 for f in names:
356 for f in names:
357 if f.lower() == 'hgrc':
357 if f.lower() == 'hgrc':
358 ui.warn(_("warning: removing potentially hostile 'hgrc' "
358 ui.warn(_("warning: removing potentially hostile 'hgrc' "
359 "in '%s'\n") % vfs.join(dirname))
359 "in '%s'\n") % vfs.join(dirname))
360 vfs.unlink(vfs.reljoin(dirname, f))
360 vfs.unlink(vfs.reljoin(dirname, f))
361
361
362 def subrepo(ctx, path, allowwdir=False, allowcreate=True):
362 def subrepo(ctx, path, allowwdir=False, allowcreate=True):
363 """return instance of the right subrepo class for subrepo in path"""
363 """return instance of the right subrepo class for subrepo in path"""
364 # subrepo inherently violates our import layering rules
364 # subrepo inherently violates our import layering rules
365 # because it wants to make repo objects from deep inside the stack
365 # because it wants to make repo objects from deep inside the stack
366 # so we manually delay the circular imports to not break
366 # so we manually delay the circular imports to not break
367 # scripts that don't use our demand-loading
367 # scripts that don't use our demand-loading
368 global hg
368 global hg
369 from . import hg as h
369 from . import hg as h
370 hg = h
370 hg = h
371
371
372 pathutil.pathauditor(ctx.repo().root)(path)
372 pathutil.pathauditor(ctx.repo().root)(path)
373 state = ctx.substate[path]
373 state = ctx.substate[path]
374 if state[2] not in types:
374 if state[2] not in types:
375 raise error.Abort(_('unknown subrepo type %s') % state[2])
375 raise error.Abort(_('unknown subrepo type %s') % state[2])
376 if allowwdir:
376 if allowwdir:
377 state = (state[0], ctx.subrev(path), state[2])
377 state = (state[0], ctx.subrev(path), state[2])
378 return types[state[2]](ctx, path, state[:2], allowcreate)
378 return types[state[2]](ctx, path, state[:2], allowcreate)
379
379
380 def nullsubrepo(ctx, path, pctx):
380 def nullsubrepo(ctx, path, pctx):
381 """return an empty subrepo in pctx for the extant subrepo in ctx"""
381 """return an empty subrepo in pctx for the extant subrepo in ctx"""
382 # subrepo inherently violates our import layering rules
382 # subrepo inherently violates our import layering rules
383 # because it wants to make repo objects from deep inside the stack
383 # because it wants to make repo objects from deep inside the stack
384 # so we manually delay the circular imports to not break
384 # so we manually delay the circular imports to not break
385 # scripts that don't use our demand-loading
385 # scripts that don't use our demand-loading
386 global hg
386 global hg
387 from . import hg as h
387 from . import hg as h
388 hg = h
388 hg = h
389
389
390 pathutil.pathauditor(ctx.repo().root)(path)
390 pathutil.pathauditor(ctx.repo().root)(path)
391 state = ctx.substate[path]
391 state = ctx.substate[path]
392 if state[2] not in types:
392 if state[2] not in types:
393 raise error.Abort(_('unknown subrepo type %s') % state[2])
393 raise error.Abort(_('unknown subrepo type %s') % state[2])
394 subrev = ''
394 subrev = ''
395 if state[2] == 'hg':
395 if state[2] == 'hg':
396 subrev = "0" * 40
396 subrev = "0" * 40
397 return types[state[2]](pctx, path, (state[0], subrev), True)
397 return types[state[2]](pctx, path, (state[0], subrev), True)
398
398
399 def newcommitphase(ui, ctx):
399 def newcommitphase(ui, ctx):
400 commitphase = phases.newcommitphase(ui)
400 commitphase = phases.newcommitphase(ui)
401 substate = getattr(ctx, "substate", None)
401 substate = getattr(ctx, "substate", None)
402 if not substate:
402 if not substate:
403 return commitphase
403 return commitphase
404 check = ui.config('phases', 'checksubrepos', 'follow')
404 check = ui.config('phases', 'checksubrepos', 'follow')
405 if check not in ('ignore', 'follow', 'abort'):
405 if check not in ('ignore', 'follow', 'abort'):
406 raise error.Abort(_('invalid phases.checksubrepos configuration: %s')
406 raise error.Abort(_('invalid phases.checksubrepos configuration: %s')
407 % (check))
407 % (check))
408 if check == 'ignore':
408 if check == 'ignore':
409 return commitphase
409 return commitphase
410 maxphase = phases.public
410 maxphase = phases.public
411 maxsub = None
411 maxsub = None
412 for s in sorted(substate):
412 for s in sorted(substate):
413 sub = ctx.sub(s)
413 sub = ctx.sub(s)
414 subphase = sub.phase(substate[s][1])
414 subphase = sub.phase(substate[s][1])
415 if maxphase < subphase:
415 if maxphase < subphase:
416 maxphase = subphase
416 maxphase = subphase
417 maxsub = s
417 maxsub = s
418 if commitphase < maxphase:
418 if commitphase < maxphase:
419 if check == 'abort':
419 if check == 'abort':
420 raise error.Abort(_("can't commit in %s phase"
420 raise error.Abort(_("can't commit in %s phase"
421 " conflicting %s from subrepository %s") %
421 " conflicting %s from subrepository %s") %
422 (phases.phasenames[commitphase],
422 (phases.phasenames[commitphase],
423 phases.phasenames[maxphase], maxsub))
423 phases.phasenames[maxphase], maxsub))
424 ui.warn(_("warning: changes are committed in"
424 ui.warn(_("warning: changes are committed in"
425 " %s phase from subrepository %s\n") %
425 " %s phase from subrepository %s\n") %
426 (phases.phasenames[maxphase], maxsub))
426 (phases.phasenames[maxphase], maxsub))
427 return maxphase
427 return maxphase
428 return commitphase
428 return commitphase
429
429
430 # subrepo classes need to implement the following abstract class:
430 # subrepo classes need to implement the following abstract class:
431
431
432 class abstractsubrepo(object):
432 class abstractsubrepo(object):
433
433
434 def __init__(self, ctx, path):
434 def __init__(self, ctx, path):
435 """Initialize abstractsubrepo part
435 """Initialize abstractsubrepo part
436
436
437 ``ctx`` is the context referring this subrepository in the
437 ``ctx`` is the context referring this subrepository in the
438 parent repository.
438 parent repository.
439
439
440 ``path`` is the path to this subrepository as seen from
440 ``path`` is the path to this subrepository as seen from
441 innermost repository.
441 innermost repository.
442 """
442 """
443 self.ui = ctx.repo().ui
443 self.ui = ctx.repo().ui
444 self._ctx = ctx
444 self._ctx = ctx
445 self._path = path
445 self._path = path
446
446
447 def addwebdirpath(self, serverpath, webconf):
447 def addwebdirpath(self, serverpath, webconf):
448 """Add the hgwebdir entries for this subrepo, and any of its subrepos.
448 """Add the hgwebdir entries for this subrepo, and any of its subrepos.
449
449
450 ``serverpath`` is the path component of the URL for this repo.
450 ``serverpath`` is the path component of the URL for this repo.
451
451
452 ``webconf`` is the dictionary of hgwebdir entries.
452 ``webconf`` is the dictionary of hgwebdir entries.
453 """
453 """
454 pass
454 pass
455
455
456 def storeclean(self, path):
456 def storeclean(self, path):
457 """
457 """
458 returns true if the repository has not changed since it was last
458 returns true if the repository has not changed since it was last
459 cloned from or pushed to a given repository.
459 cloned from or pushed to a given repository.
460 """
460 """
461 return False
461 return False
462
462
463 def dirty(self, ignoreupdate=False):
463 def dirty(self, ignoreupdate=False):
464 """returns true if the dirstate of the subrepo is dirty or does not
464 """returns true if the dirstate of the subrepo is dirty or does not
465 match current stored state. If ignoreupdate is true, only check
465 match current stored state. If ignoreupdate is true, only check
466 whether the subrepo has uncommitted changes in its dirstate.
466 whether the subrepo has uncommitted changes in its dirstate.
467 """
467 """
468 raise NotImplementedError
468 raise NotImplementedError
469
469
470 def dirtyreason(self, ignoreupdate=False):
470 def dirtyreason(self, ignoreupdate=False):
471 """return reason string if it is ``dirty()``
471 """return reason string if it is ``dirty()``
472
472
473 Returned string should have enough information for the message
473 Returned string should have enough information for the message
474 of exception.
474 of exception.
475
475
476 This returns None, otherwise.
476 This returns None, otherwise.
477 """
477 """
478 if self.dirty(ignoreupdate=ignoreupdate):
478 if self.dirty(ignoreupdate=ignoreupdate):
479 return _("uncommitted changes in subrepository '%s'"
479 return _("uncommitted changes in subrepository '%s'"
480 ) % subrelpath(self)
480 ) % subrelpath(self)
481
481
482 def bailifchanged(self, ignoreupdate=False, hint=None):
482 def bailifchanged(self, ignoreupdate=False, hint=None):
483 """raise Abort if subrepository is ``dirty()``
483 """raise Abort if subrepository is ``dirty()``
484 """
484 """
485 dirtyreason = self.dirtyreason(ignoreupdate=ignoreupdate)
485 dirtyreason = self.dirtyreason(ignoreupdate=ignoreupdate)
486 if dirtyreason:
486 if dirtyreason:
487 raise error.Abort(dirtyreason, hint=hint)
487 raise error.Abort(dirtyreason, hint=hint)
488
488
489 def basestate(self):
489 def basestate(self):
490 """current working directory base state, disregarding .hgsubstate
490 """current working directory base state, disregarding .hgsubstate
491 state and working directory modifications"""
491 state and working directory modifications"""
492 raise NotImplementedError
492 raise NotImplementedError
493
493
494 def checknested(self, path):
494 def checknested(self, path):
495 """check if path is a subrepository within this repository"""
495 """check if path is a subrepository within this repository"""
496 return False
496 return False
497
497
498 def commit(self, text, user, date):
498 def commit(self, text, user, date):
499 """commit the current changes to the subrepo with the given
499 """commit the current changes to the subrepo with the given
500 log message. Use given user and date if possible. Return the
500 log message. Use given user and date if possible. Return the
501 new state of the subrepo.
501 new state of the subrepo.
502 """
502 """
503 raise NotImplementedError
503 raise NotImplementedError
504
504
505 def phase(self, state):
505 def phase(self, state):
506 """returns phase of specified state in the subrepository.
506 """returns phase of specified state in the subrepository.
507 """
507 """
508 return phases.public
508 return phases.public
509
509
510 def remove(self):
510 def remove(self):
511 """remove the subrepo
511 """remove the subrepo
512
512
513 (should verify the dirstate is not dirty first)
513 (should verify the dirstate is not dirty first)
514 """
514 """
515 raise NotImplementedError
515 raise NotImplementedError
516
516
517 def get(self, state, overwrite=False):
517 def get(self, state, overwrite=False):
518 """run whatever commands are needed to put the subrepo into
518 """run whatever commands are needed to put the subrepo into
519 this state
519 this state
520 """
520 """
521 raise NotImplementedError
521 raise NotImplementedError
522
522
523 def merge(self, state):
523 def merge(self, state):
524 """merge currently-saved state with the new state."""
524 """merge currently-saved state with the new state."""
525 raise NotImplementedError
525 raise NotImplementedError
526
526
527 def push(self, opts):
527 def push(self, opts):
528 """perform whatever action is analogous to 'hg push'
528 """perform whatever action is analogous to 'hg push'
529
529
530 This may be a no-op on some systems.
530 This may be a no-op on some systems.
531 """
531 """
532 raise NotImplementedError
532 raise NotImplementedError
533
533
534 def add(self, ui, match, prefix, explicitonly, **opts):
534 def add(self, ui, match, prefix, explicitonly, **opts):
535 return []
535 return []
536
536
537 def addremove(self, matcher, prefix, opts, dry_run, similarity):
537 def addremove(self, matcher, prefix, opts, dry_run, similarity):
538 self.ui.warn("%s: %s" % (prefix, _("addremove is not supported")))
538 self.ui.warn("%s: %s" % (prefix, _("addremove is not supported")))
539 return 1
539 return 1
540
540
541 def cat(self, match, prefix, **opts):
541 def cat(self, match, fntemplate, prefix, **opts):
542 return 1
542 return 1
543
543
544 def status(self, rev2, **opts):
544 def status(self, rev2, **opts):
545 return scmutil.status([], [], [], [], [], [], [])
545 return scmutil.status([], [], [], [], [], [], [])
546
546
547 def diff(self, ui, diffopts, node2, match, prefix, **opts):
547 def diff(self, ui, diffopts, node2, match, prefix, **opts):
548 pass
548 pass
549
549
550 def outgoing(self, ui, dest, opts):
550 def outgoing(self, ui, dest, opts):
551 return 1
551 return 1
552
552
553 def incoming(self, ui, source, opts):
553 def incoming(self, ui, source, opts):
554 return 1
554 return 1
555
555
556 def files(self):
556 def files(self):
557 """return filename iterator"""
557 """return filename iterator"""
558 raise NotImplementedError
558 raise NotImplementedError
559
559
560 def filedata(self, name, decode):
560 def filedata(self, name, decode):
561 """return file data, optionally passed through repo decoders"""
561 """return file data, optionally passed through repo decoders"""
562 raise NotImplementedError
562 raise NotImplementedError
563
563
564 def fileflags(self, name):
564 def fileflags(self, name):
565 """return file flags"""
565 """return file flags"""
566 return ''
566 return ''
567
567
568 def getfileset(self, expr):
568 def getfileset(self, expr):
569 """Resolve the fileset expression for this repo"""
569 """Resolve the fileset expression for this repo"""
570 return set()
570 return set()
571
571
572 def printfiles(self, ui, m, fm, fmt, subrepos):
572 def printfiles(self, ui, m, fm, fmt, subrepos):
573 """handle the files command for this subrepo"""
573 """handle the files command for this subrepo"""
574 return 1
574 return 1
575
575
576 def archive(self, archiver, prefix, match=None, decode=True):
576 def archive(self, archiver, prefix, match=None, decode=True):
577 if match is not None:
577 if match is not None:
578 files = [f for f in self.files() if match(f)]
578 files = [f for f in self.files() if match(f)]
579 else:
579 else:
580 files = self.files()
580 files = self.files()
581 total = len(files)
581 total = len(files)
582 relpath = subrelpath(self)
582 relpath = subrelpath(self)
583 self.ui.progress(_('archiving (%s)') % relpath, 0,
583 self.ui.progress(_('archiving (%s)') % relpath, 0,
584 unit=_('files'), total=total)
584 unit=_('files'), total=total)
585 for i, name in enumerate(files):
585 for i, name in enumerate(files):
586 flags = self.fileflags(name)
586 flags = self.fileflags(name)
587 mode = 'x' in flags and 0o755 or 0o644
587 mode = 'x' in flags and 0o755 or 0o644
588 symlink = 'l' in flags
588 symlink = 'l' in flags
589 archiver.addfile(prefix + self._path + '/' + name,
589 archiver.addfile(prefix + self._path + '/' + name,
590 mode, symlink, self.filedata(name, decode))
590 mode, symlink, self.filedata(name, decode))
591 self.ui.progress(_('archiving (%s)') % relpath, i + 1,
591 self.ui.progress(_('archiving (%s)') % relpath, i + 1,
592 unit=_('files'), total=total)
592 unit=_('files'), total=total)
593 self.ui.progress(_('archiving (%s)') % relpath, None)
593 self.ui.progress(_('archiving (%s)') % relpath, None)
594 return total
594 return total
595
595
596 def walk(self, match):
596 def walk(self, match):
597 '''
597 '''
598 walk recursively through the directory tree, finding all files
598 walk recursively through the directory tree, finding all files
599 matched by the match function
599 matched by the match function
600 '''
600 '''
601 pass
601 pass
602
602
603 def forget(self, match, prefix):
603 def forget(self, match, prefix):
604 return ([], [])
604 return ([], [])
605
605
606 def removefiles(self, matcher, prefix, after, force, subrepos, warnings):
606 def removefiles(self, matcher, prefix, after, force, subrepos, warnings):
607 """remove the matched files from the subrepository and the filesystem,
607 """remove the matched files from the subrepository and the filesystem,
608 possibly by force and/or after the file has been removed from the
608 possibly by force and/or after the file has been removed from the
609 filesystem. Return 0 on success, 1 on any warning.
609 filesystem. Return 0 on success, 1 on any warning.
610 """
610 """
611 warnings.append(_("warning: removefiles not implemented (%s)")
611 warnings.append(_("warning: removefiles not implemented (%s)")
612 % self._path)
612 % self._path)
613 return 1
613 return 1
614
614
615 def revert(self, substate, *pats, **opts):
615 def revert(self, substate, *pats, **opts):
616 self.ui.warn(_('%s: reverting %s subrepos is unsupported\n') \
616 self.ui.warn(_('%s: reverting %s subrepos is unsupported\n') \
617 % (substate[0], substate[2]))
617 % (substate[0], substate[2]))
618 return []
618 return []
619
619
620 def shortid(self, revid):
620 def shortid(self, revid):
621 return revid
621 return revid
622
622
623 def verify(self):
623 def verify(self):
624 '''verify the integrity of the repository. Return 0 on success or
624 '''verify the integrity of the repository. Return 0 on success or
625 warning, 1 on any error.
625 warning, 1 on any error.
626 '''
626 '''
627 return 0
627 return 0
628
628
629 @propertycache
629 @propertycache
630 def wvfs(self):
630 def wvfs(self):
631 """return vfs to access the working directory of this subrepository
631 """return vfs to access the working directory of this subrepository
632 """
632 """
633 return vfsmod.vfs(self._ctx.repo().wvfs.join(self._path))
633 return vfsmod.vfs(self._ctx.repo().wvfs.join(self._path))
634
634
635 @propertycache
635 @propertycache
636 def _relpath(self):
636 def _relpath(self):
637 """return path to this subrepository as seen from outermost repository
637 """return path to this subrepository as seen from outermost repository
638 """
638 """
639 return self.wvfs.reljoin(reporelpath(self._ctx.repo()), self._path)
639 return self.wvfs.reljoin(reporelpath(self._ctx.repo()), self._path)
640
640
641 class hgsubrepo(abstractsubrepo):
641 class hgsubrepo(abstractsubrepo):
642 def __init__(self, ctx, path, state, allowcreate):
642 def __init__(self, ctx, path, state, allowcreate):
643 super(hgsubrepo, self).__init__(ctx, path)
643 super(hgsubrepo, self).__init__(ctx, path)
644 self._state = state
644 self._state = state
645 r = ctx.repo()
645 r = ctx.repo()
646 root = r.wjoin(path)
646 root = r.wjoin(path)
647 create = allowcreate and not r.wvfs.exists('%s/.hg' % path)
647 create = allowcreate and not r.wvfs.exists('%s/.hg' % path)
648 self._repo = hg.repository(r.baseui, root, create=create)
648 self._repo = hg.repository(r.baseui, root, create=create)
649
649
650 # Propagate the parent's --hidden option
650 # Propagate the parent's --hidden option
651 if r is r.unfiltered():
651 if r is r.unfiltered():
652 self._repo = self._repo.unfiltered()
652 self._repo = self._repo.unfiltered()
653
653
654 self.ui = self._repo.ui
654 self.ui = self._repo.ui
655 for s, k in [('ui', 'commitsubrepos')]:
655 for s, k in [('ui', 'commitsubrepos')]:
656 v = r.ui.config(s, k)
656 v = r.ui.config(s, k)
657 if v:
657 if v:
658 self.ui.setconfig(s, k, v, 'subrepo')
658 self.ui.setconfig(s, k, v, 'subrepo')
659 # internal config: ui._usedassubrepo
659 # internal config: ui._usedassubrepo
660 self.ui.setconfig('ui', '_usedassubrepo', 'True', 'subrepo')
660 self.ui.setconfig('ui', '_usedassubrepo', 'True', 'subrepo')
661 self._initrepo(r, state[0], create)
661 self._initrepo(r, state[0], create)
662
662
663 @annotatesubrepoerror
663 @annotatesubrepoerror
664 def addwebdirpath(self, serverpath, webconf):
664 def addwebdirpath(self, serverpath, webconf):
665 cmdutil.addwebdirpath(self._repo, subrelpath(self), webconf)
665 cmdutil.addwebdirpath(self._repo, subrelpath(self), webconf)
666
666
667 def storeclean(self, path):
667 def storeclean(self, path):
668 with self._repo.lock():
668 with self._repo.lock():
669 return self._storeclean(path)
669 return self._storeclean(path)
670
670
671 def _storeclean(self, path):
671 def _storeclean(self, path):
672 clean = True
672 clean = True
673 itercache = self._calcstorehash(path)
673 itercache = self._calcstorehash(path)
674 for filehash in self._readstorehashcache(path):
674 for filehash in self._readstorehashcache(path):
675 if filehash != next(itercache, None):
675 if filehash != next(itercache, None):
676 clean = False
676 clean = False
677 break
677 break
678 if clean:
678 if clean:
679 # if not empty:
679 # if not empty:
680 # the cached and current pull states have a different size
680 # the cached and current pull states have a different size
681 clean = next(itercache, None) is None
681 clean = next(itercache, None) is None
682 return clean
682 return clean
683
683
684 def _calcstorehash(self, remotepath):
684 def _calcstorehash(self, remotepath):
685 '''calculate a unique "store hash"
685 '''calculate a unique "store hash"
686
686
687 This method is used to to detect when there are changes that may
687 This method is used to to detect when there are changes that may
688 require a push to a given remote path.'''
688 require a push to a given remote path.'''
689 # sort the files that will be hashed in increasing (likely) file size
689 # sort the files that will be hashed in increasing (likely) file size
690 filelist = ('bookmarks', 'store/phaseroots', 'store/00changelog.i')
690 filelist = ('bookmarks', 'store/phaseroots', 'store/00changelog.i')
691 yield '# %s\n' % _expandedabspath(remotepath)
691 yield '# %s\n' % _expandedabspath(remotepath)
692 vfs = self._repo.vfs
692 vfs = self._repo.vfs
693 for relname in filelist:
693 for relname in filelist:
694 filehash = hashlib.sha1(vfs.tryread(relname)).hexdigest()
694 filehash = hashlib.sha1(vfs.tryread(relname)).hexdigest()
695 yield '%s = %s\n' % (relname, filehash)
695 yield '%s = %s\n' % (relname, filehash)
696
696
697 @propertycache
697 @propertycache
698 def _cachestorehashvfs(self):
698 def _cachestorehashvfs(self):
699 return vfsmod.vfs(self._repo.vfs.join('cache/storehash'))
699 return vfsmod.vfs(self._repo.vfs.join('cache/storehash'))
700
700
701 def _readstorehashcache(self, remotepath):
701 def _readstorehashcache(self, remotepath):
702 '''read the store hash cache for a given remote repository'''
702 '''read the store hash cache for a given remote repository'''
703 cachefile = _getstorehashcachename(remotepath)
703 cachefile = _getstorehashcachename(remotepath)
704 return self._cachestorehashvfs.tryreadlines(cachefile, 'r')
704 return self._cachestorehashvfs.tryreadlines(cachefile, 'r')
705
705
706 def _cachestorehash(self, remotepath):
706 def _cachestorehash(self, remotepath):
707 '''cache the current store hash
707 '''cache the current store hash
708
708
709 Each remote repo requires its own store hash cache, because a subrepo
709 Each remote repo requires its own store hash cache, because a subrepo
710 store may be "clean" versus a given remote repo, but not versus another
710 store may be "clean" versus a given remote repo, but not versus another
711 '''
711 '''
712 cachefile = _getstorehashcachename(remotepath)
712 cachefile = _getstorehashcachename(remotepath)
713 with self._repo.lock():
713 with self._repo.lock():
714 storehash = list(self._calcstorehash(remotepath))
714 storehash = list(self._calcstorehash(remotepath))
715 vfs = self._cachestorehashvfs
715 vfs = self._cachestorehashvfs
716 vfs.writelines(cachefile, storehash, mode='w', notindexed=True)
716 vfs.writelines(cachefile, storehash, mode='w', notindexed=True)
717
717
718 def _getctx(self):
718 def _getctx(self):
719 '''fetch the context for this subrepo revision, possibly a workingctx
719 '''fetch the context for this subrepo revision, possibly a workingctx
720 '''
720 '''
721 if self._ctx.rev() is None:
721 if self._ctx.rev() is None:
722 return self._repo[None] # workingctx if parent is workingctx
722 return self._repo[None] # workingctx if parent is workingctx
723 else:
723 else:
724 rev = self._state[1]
724 rev = self._state[1]
725 return self._repo[rev]
725 return self._repo[rev]
726
726
727 @annotatesubrepoerror
727 @annotatesubrepoerror
728 def _initrepo(self, parentrepo, source, create):
728 def _initrepo(self, parentrepo, source, create):
729 self._repo._subparent = parentrepo
729 self._repo._subparent = parentrepo
730 self._repo._subsource = source
730 self._repo._subsource = source
731
731
732 if create:
732 if create:
733 lines = ['[paths]\n']
733 lines = ['[paths]\n']
734
734
735 def addpathconfig(key, value):
735 def addpathconfig(key, value):
736 if value:
736 if value:
737 lines.append('%s = %s\n' % (key, value))
737 lines.append('%s = %s\n' % (key, value))
738 self.ui.setconfig('paths', key, value, 'subrepo')
738 self.ui.setconfig('paths', key, value, 'subrepo')
739
739
740 defpath = _abssource(self._repo, abort=False)
740 defpath = _abssource(self._repo, abort=False)
741 defpushpath = _abssource(self._repo, True, abort=False)
741 defpushpath = _abssource(self._repo, True, abort=False)
742 addpathconfig('default', defpath)
742 addpathconfig('default', defpath)
743 if defpath != defpushpath:
743 if defpath != defpushpath:
744 addpathconfig('default-push', defpushpath)
744 addpathconfig('default-push', defpushpath)
745
745
746 fp = self._repo.vfs("hgrc", "w", text=True)
746 fp = self._repo.vfs("hgrc", "w", text=True)
747 try:
747 try:
748 fp.write(''.join(lines))
748 fp.write(''.join(lines))
749 finally:
749 finally:
750 fp.close()
750 fp.close()
751
751
752 @annotatesubrepoerror
752 @annotatesubrepoerror
753 def add(self, ui, match, prefix, explicitonly, **opts):
753 def add(self, ui, match, prefix, explicitonly, **opts):
754 return cmdutil.add(ui, self._repo, match,
754 return cmdutil.add(ui, self._repo, match,
755 self.wvfs.reljoin(prefix, self._path),
755 self.wvfs.reljoin(prefix, self._path),
756 explicitonly, **opts)
756 explicitonly, **opts)
757
757
758 @annotatesubrepoerror
758 @annotatesubrepoerror
759 def addremove(self, m, prefix, opts, dry_run, similarity):
759 def addremove(self, m, prefix, opts, dry_run, similarity):
760 # In the same way as sub directories are processed, once in a subrepo,
760 # In the same way as sub directories are processed, once in a subrepo,
761 # always entry any of its subrepos. Don't corrupt the options that will
761 # always entry any of its subrepos. Don't corrupt the options that will
762 # be used to process sibling subrepos however.
762 # be used to process sibling subrepos however.
763 opts = copy.copy(opts)
763 opts = copy.copy(opts)
764 opts['subrepos'] = True
764 opts['subrepos'] = True
765 return scmutil.addremove(self._repo, m,
765 return scmutil.addremove(self._repo, m,
766 self.wvfs.reljoin(prefix, self._path), opts,
766 self.wvfs.reljoin(prefix, self._path), opts,
767 dry_run, similarity)
767 dry_run, similarity)
768
768
769 @annotatesubrepoerror
769 @annotatesubrepoerror
770 def cat(self, match, prefix, **opts):
770 def cat(self, match, fntemplate, prefix, **opts):
771 rev = self._state[1]
771 rev = self._state[1]
772 ctx = self._repo[rev]
772 ctx = self._repo[rev]
773 return cmdutil.cat(self.ui, self._repo, ctx, match, prefix, **opts)
773 return cmdutil.cat(self.ui, self._repo, ctx, match, fntemplate, prefix,
774 **opts)
774
775
775 @annotatesubrepoerror
776 @annotatesubrepoerror
776 def status(self, rev2, **opts):
777 def status(self, rev2, **opts):
777 try:
778 try:
778 rev1 = self._state[1]
779 rev1 = self._state[1]
779 ctx1 = self._repo[rev1]
780 ctx1 = self._repo[rev1]
780 ctx2 = self._repo[rev2]
781 ctx2 = self._repo[rev2]
781 return self._repo.status(ctx1, ctx2, **opts)
782 return self._repo.status(ctx1, ctx2, **opts)
782 except error.RepoLookupError as inst:
783 except error.RepoLookupError as inst:
783 self.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
784 self.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
784 % (inst, subrelpath(self)))
785 % (inst, subrelpath(self)))
785 return scmutil.status([], [], [], [], [], [], [])
786 return scmutil.status([], [], [], [], [], [], [])
786
787
787 @annotatesubrepoerror
788 @annotatesubrepoerror
788 def diff(self, ui, diffopts, node2, match, prefix, **opts):
789 def diff(self, ui, diffopts, node2, match, prefix, **opts):
789 try:
790 try:
790 node1 = node.bin(self._state[1])
791 node1 = node.bin(self._state[1])
791 # We currently expect node2 to come from substate and be
792 # We currently expect node2 to come from substate and be
792 # in hex format
793 # in hex format
793 if node2 is not None:
794 if node2 is not None:
794 node2 = node.bin(node2)
795 node2 = node.bin(node2)
795 cmdutil.diffordiffstat(ui, self._repo, diffopts,
796 cmdutil.diffordiffstat(ui, self._repo, diffopts,
796 node1, node2, match,
797 node1, node2, match,
797 prefix=posixpath.join(prefix, self._path),
798 prefix=posixpath.join(prefix, self._path),
798 listsubrepos=True, **opts)
799 listsubrepos=True, **opts)
799 except error.RepoLookupError as inst:
800 except error.RepoLookupError as inst:
800 self.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
801 self.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
801 % (inst, subrelpath(self)))
802 % (inst, subrelpath(self)))
802
803
803 @annotatesubrepoerror
804 @annotatesubrepoerror
804 def archive(self, archiver, prefix, match=None, decode=True):
805 def archive(self, archiver, prefix, match=None, decode=True):
805 self._get(self._state + ('hg',))
806 self._get(self._state + ('hg',))
806 total = abstractsubrepo.archive(self, archiver, prefix, match)
807 total = abstractsubrepo.archive(self, archiver, prefix, match)
807 rev = self._state[1]
808 rev = self._state[1]
808 ctx = self._repo[rev]
809 ctx = self._repo[rev]
809 for subpath in ctx.substate:
810 for subpath in ctx.substate:
810 s = subrepo(ctx, subpath, True)
811 s = subrepo(ctx, subpath, True)
811 submatch = matchmod.subdirmatcher(subpath, match)
812 submatch = matchmod.subdirmatcher(subpath, match)
812 total += s.archive(archiver, prefix + self._path + '/', submatch,
813 total += s.archive(archiver, prefix + self._path + '/', submatch,
813 decode)
814 decode)
814 return total
815 return total
815
816
816 @annotatesubrepoerror
817 @annotatesubrepoerror
817 def dirty(self, ignoreupdate=False):
818 def dirty(self, ignoreupdate=False):
818 r = self._state[1]
819 r = self._state[1]
819 if r == '' and not ignoreupdate: # no state recorded
820 if r == '' and not ignoreupdate: # no state recorded
820 return True
821 return True
821 w = self._repo[None]
822 w = self._repo[None]
822 if r != w.p1().hex() and not ignoreupdate:
823 if r != w.p1().hex() and not ignoreupdate:
823 # different version checked out
824 # different version checked out
824 return True
825 return True
825 return w.dirty() # working directory changed
826 return w.dirty() # working directory changed
826
827
827 def basestate(self):
828 def basestate(self):
828 return self._repo['.'].hex()
829 return self._repo['.'].hex()
829
830
830 def checknested(self, path):
831 def checknested(self, path):
831 return self._repo._checknested(self._repo.wjoin(path))
832 return self._repo._checknested(self._repo.wjoin(path))
832
833
833 @annotatesubrepoerror
834 @annotatesubrepoerror
834 def commit(self, text, user, date):
835 def commit(self, text, user, date):
835 # don't bother committing in the subrepo if it's only been
836 # don't bother committing in the subrepo if it's only been
836 # updated
837 # updated
837 if not self.dirty(True):
838 if not self.dirty(True):
838 return self._repo['.'].hex()
839 return self._repo['.'].hex()
839 self.ui.debug("committing subrepo %s\n" % subrelpath(self))
840 self.ui.debug("committing subrepo %s\n" % subrelpath(self))
840 n = self._repo.commit(text, user, date)
841 n = self._repo.commit(text, user, date)
841 if not n:
842 if not n:
842 return self._repo['.'].hex() # different version checked out
843 return self._repo['.'].hex() # different version checked out
843 return node.hex(n)
844 return node.hex(n)
844
845
845 @annotatesubrepoerror
846 @annotatesubrepoerror
846 def phase(self, state):
847 def phase(self, state):
847 return self._repo[state].phase()
848 return self._repo[state].phase()
848
849
849 @annotatesubrepoerror
850 @annotatesubrepoerror
850 def remove(self):
851 def remove(self):
851 # we can't fully delete the repository as it may contain
852 # we can't fully delete the repository as it may contain
852 # local-only history
853 # local-only history
853 self.ui.note(_('removing subrepo %s\n') % subrelpath(self))
854 self.ui.note(_('removing subrepo %s\n') % subrelpath(self))
854 hg.clean(self._repo, node.nullid, False)
855 hg.clean(self._repo, node.nullid, False)
855
856
856 def _get(self, state):
857 def _get(self, state):
857 source, revision, kind = state
858 source, revision, kind = state
858 if revision in self._repo.unfiltered():
859 if revision in self._repo.unfiltered():
859 return True
860 return True
860 self._repo._subsource = source
861 self._repo._subsource = source
861 srcurl = _abssource(self._repo)
862 srcurl = _abssource(self._repo)
862 other = hg.peer(self._repo, {}, srcurl)
863 other = hg.peer(self._repo, {}, srcurl)
863 if len(self._repo) == 0:
864 if len(self._repo) == 0:
864 self.ui.status(_('cloning subrepo %s from %s\n')
865 self.ui.status(_('cloning subrepo %s from %s\n')
865 % (subrelpath(self), srcurl))
866 % (subrelpath(self), srcurl))
866 parentrepo = self._repo._subparent
867 parentrepo = self._repo._subparent
867 # use self._repo.vfs instead of self.wvfs to remove .hg only
868 # use self._repo.vfs instead of self.wvfs to remove .hg only
868 self._repo.vfs.rmtree()
869 self._repo.vfs.rmtree()
869 other, cloned = hg.clone(self._repo._subparent.baseui, {},
870 other, cloned = hg.clone(self._repo._subparent.baseui, {},
870 other, self._repo.root,
871 other, self._repo.root,
871 update=False)
872 update=False)
872 self._repo = cloned.local()
873 self._repo = cloned.local()
873 self._initrepo(parentrepo, source, create=True)
874 self._initrepo(parentrepo, source, create=True)
874 self._cachestorehash(srcurl)
875 self._cachestorehash(srcurl)
875 else:
876 else:
876 self.ui.status(_('pulling subrepo %s from %s\n')
877 self.ui.status(_('pulling subrepo %s from %s\n')
877 % (subrelpath(self), srcurl))
878 % (subrelpath(self), srcurl))
878 cleansub = self.storeclean(srcurl)
879 cleansub = self.storeclean(srcurl)
879 exchange.pull(self._repo, other)
880 exchange.pull(self._repo, other)
880 if cleansub:
881 if cleansub:
881 # keep the repo clean after pull
882 # keep the repo clean after pull
882 self._cachestorehash(srcurl)
883 self._cachestorehash(srcurl)
883 return False
884 return False
884
885
885 @annotatesubrepoerror
886 @annotatesubrepoerror
886 def get(self, state, overwrite=False):
887 def get(self, state, overwrite=False):
887 inrepo = self._get(state)
888 inrepo = self._get(state)
888 source, revision, kind = state
889 source, revision, kind = state
889 repo = self._repo
890 repo = self._repo
890 repo.ui.debug("getting subrepo %s\n" % self._path)
891 repo.ui.debug("getting subrepo %s\n" % self._path)
891 if inrepo:
892 if inrepo:
892 urepo = repo.unfiltered()
893 urepo = repo.unfiltered()
893 ctx = urepo[revision]
894 ctx = urepo[revision]
894 if ctx.hidden():
895 if ctx.hidden():
895 urepo.ui.warn(
896 urepo.ui.warn(
896 _('revision %s in subrepo %s is hidden\n') \
897 _('revision %s in subrepo %s is hidden\n') \
897 % (revision[0:12], self._path))
898 % (revision[0:12], self._path))
898 repo = urepo
899 repo = urepo
899 hg.updaterepo(repo, revision, overwrite)
900 hg.updaterepo(repo, revision, overwrite)
900
901
901 @annotatesubrepoerror
902 @annotatesubrepoerror
902 def merge(self, state):
903 def merge(self, state):
903 self._get(state)
904 self._get(state)
904 cur = self._repo['.']
905 cur = self._repo['.']
905 dst = self._repo[state[1]]
906 dst = self._repo[state[1]]
906 anc = dst.ancestor(cur)
907 anc = dst.ancestor(cur)
907
908
908 def mergefunc():
909 def mergefunc():
909 if anc == cur and dst.branch() == cur.branch():
910 if anc == cur and dst.branch() == cur.branch():
910 self.ui.debug("updating subrepo %s\n" % subrelpath(self))
911 self.ui.debug("updating subrepo %s\n" % subrelpath(self))
911 hg.update(self._repo, state[1])
912 hg.update(self._repo, state[1])
912 elif anc == dst:
913 elif anc == dst:
913 self.ui.debug("skipping subrepo %s\n" % subrelpath(self))
914 self.ui.debug("skipping subrepo %s\n" % subrelpath(self))
914 else:
915 else:
915 self.ui.debug("merging subrepo %s\n" % subrelpath(self))
916 self.ui.debug("merging subrepo %s\n" % subrelpath(self))
916 hg.merge(self._repo, state[1], remind=False)
917 hg.merge(self._repo, state[1], remind=False)
917
918
918 wctx = self._repo[None]
919 wctx = self._repo[None]
919 if self.dirty():
920 if self.dirty():
920 if anc != dst:
921 if anc != dst:
921 if _updateprompt(self.ui, self, wctx.dirty(), cur, dst):
922 if _updateprompt(self.ui, self, wctx.dirty(), cur, dst):
922 mergefunc()
923 mergefunc()
923 else:
924 else:
924 mergefunc()
925 mergefunc()
925 else:
926 else:
926 mergefunc()
927 mergefunc()
927
928
928 @annotatesubrepoerror
929 @annotatesubrepoerror
929 def push(self, opts):
930 def push(self, opts):
930 force = opts.get('force')
931 force = opts.get('force')
931 newbranch = opts.get('new_branch')
932 newbranch = opts.get('new_branch')
932 ssh = opts.get('ssh')
933 ssh = opts.get('ssh')
933
934
934 # push subrepos depth-first for coherent ordering
935 # push subrepos depth-first for coherent ordering
935 c = self._repo['']
936 c = self._repo['']
936 subs = c.substate # only repos that are committed
937 subs = c.substate # only repos that are committed
937 for s in sorted(subs):
938 for s in sorted(subs):
938 if c.sub(s).push(opts) == 0:
939 if c.sub(s).push(opts) == 0:
939 return False
940 return False
940
941
941 dsturl = _abssource(self._repo, True)
942 dsturl = _abssource(self._repo, True)
942 if not force:
943 if not force:
943 if self.storeclean(dsturl):
944 if self.storeclean(dsturl):
944 self.ui.status(
945 self.ui.status(
945 _('no changes made to subrepo %s since last push to %s\n')
946 _('no changes made to subrepo %s since last push to %s\n')
946 % (subrelpath(self), dsturl))
947 % (subrelpath(self), dsturl))
947 return None
948 return None
948 self.ui.status(_('pushing subrepo %s to %s\n') %
949 self.ui.status(_('pushing subrepo %s to %s\n') %
949 (subrelpath(self), dsturl))
950 (subrelpath(self), dsturl))
950 other = hg.peer(self._repo, {'ssh': ssh}, dsturl)
951 other = hg.peer(self._repo, {'ssh': ssh}, dsturl)
951 res = exchange.push(self._repo, other, force, newbranch=newbranch)
952 res = exchange.push(self._repo, other, force, newbranch=newbranch)
952
953
953 # the repo is now clean
954 # the repo is now clean
954 self._cachestorehash(dsturl)
955 self._cachestorehash(dsturl)
955 return res.cgresult
956 return res.cgresult
956
957
957 @annotatesubrepoerror
958 @annotatesubrepoerror
958 def outgoing(self, ui, dest, opts):
959 def outgoing(self, ui, dest, opts):
959 if 'rev' in opts or 'branch' in opts:
960 if 'rev' in opts or 'branch' in opts:
960 opts = copy.copy(opts)
961 opts = copy.copy(opts)
961 opts.pop('rev', None)
962 opts.pop('rev', None)
962 opts.pop('branch', None)
963 opts.pop('branch', None)
963 return hg.outgoing(ui, self._repo, _abssource(self._repo, True), opts)
964 return hg.outgoing(ui, self._repo, _abssource(self._repo, True), opts)
964
965
965 @annotatesubrepoerror
966 @annotatesubrepoerror
966 def incoming(self, ui, source, opts):
967 def incoming(self, ui, source, opts):
967 if 'rev' in opts or 'branch' in opts:
968 if 'rev' in opts or 'branch' in opts:
968 opts = copy.copy(opts)
969 opts = copy.copy(opts)
969 opts.pop('rev', None)
970 opts.pop('rev', None)
970 opts.pop('branch', None)
971 opts.pop('branch', None)
971 return hg.incoming(ui, self._repo, _abssource(self._repo, False), opts)
972 return hg.incoming(ui, self._repo, _abssource(self._repo, False), opts)
972
973
973 @annotatesubrepoerror
974 @annotatesubrepoerror
974 def files(self):
975 def files(self):
975 rev = self._state[1]
976 rev = self._state[1]
976 ctx = self._repo[rev]
977 ctx = self._repo[rev]
977 return ctx.manifest().keys()
978 return ctx.manifest().keys()
978
979
979 def filedata(self, name, decode):
980 def filedata(self, name, decode):
980 rev = self._state[1]
981 rev = self._state[1]
981 data = self._repo[rev][name].data()
982 data = self._repo[rev][name].data()
982 if decode:
983 if decode:
983 data = self._repo.wwritedata(name, data)
984 data = self._repo.wwritedata(name, data)
984 return data
985 return data
985
986
986 def fileflags(self, name):
987 def fileflags(self, name):
987 rev = self._state[1]
988 rev = self._state[1]
988 ctx = self._repo[rev]
989 ctx = self._repo[rev]
989 return ctx.flags(name)
990 return ctx.flags(name)
990
991
991 @annotatesubrepoerror
992 @annotatesubrepoerror
992 def printfiles(self, ui, m, fm, fmt, subrepos):
993 def printfiles(self, ui, m, fm, fmt, subrepos):
993 # If the parent context is a workingctx, use the workingctx here for
994 # If the parent context is a workingctx, use the workingctx here for
994 # consistency.
995 # consistency.
995 if self._ctx.rev() is None:
996 if self._ctx.rev() is None:
996 ctx = self._repo[None]
997 ctx = self._repo[None]
997 else:
998 else:
998 rev = self._state[1]
999 rev = self._state[1]
999 ctx = self._repo[rev]
1000 ctx = self._repo[rev]
1000 return cmdutil.files(ui, ctx, m, fm, fmt, subrepos)
1001 return cmdutil.files(ui, ctx, m, fm, fmt, subrepos)
1001
1002
1002 @annotatesubrepoerror
1003 @annotatesubrepoerror
1003 def getfileset(self, expr):
1004 def getfileset(self, expr):
1004 if self._ctx.rev() is None:
1005 if self._ctx.rev() is None:
1005 ctx = self._repo[None]
1006 ctx = self._repo[None]
1006 else:
1007 else:
1007 rev = self._state[1]
1008 rev = self._state[1]
1008 ctx = self._repo[rev]
1009 ctx = self._repo[rev]
1009
1010
1010 files = ctx.getfileset(expr)
1011 files = ctx.getfileset(expr)
1011
1012
1012 for subpath in ctx.substate:
1013 for subpath in ctx.substate:
1013 sub = ctx.sub(subpath)
1014 sub = ctx.sub(subpath)
1014
1015
1015 try:
1016 try:
1016 files.extend(subpath + '/' + f for f in sub.getfileset(expr))
1017 files.extend(subpath + '/' + f for f in sub.getfileset(expr))
1017 except error.LookupError:
1018 except error.LookupError:
1018 self.ui.status(_("skipping missing subrepository: %s\n")
1019 self.ui.status(_("skipping missing subrepository: %s\n")
1019 % self.wvfs.reljoin(reporelpath(self), subpath))
1020 % self.wvfs.reljoin(reporelpath(self), subpath))
1020 return files
1021 return files
1021
1022
1022 def walk(self, match):
1023 def walk(self, match):
1023 ctx = self._repo[None]
1024 ctx = self._repo[None]
1024 return ctx.walk(match)
1025 return ctx.walk(match)
1025
1026
1026 @annotatesubrepoerror
1027 @annotatesubrepoerror
1027 def forget(self, match, prefix):
1028 def forget(self, match, prefix):
1028 return cmdutil.forget(self.ui, self._repo, match,
1029 return cmdutil.forget(self.ui, self._repo, match,
1029 self.wvfs.reljoin(prefix, self._path), True)
1030 self.wvfs.reljoin(prefix, self._path), True)
1030
1031
1031 @annotatesubrepoerror
1032 @annotatesubrepoerror
1032 def removefiles(self, matcher, prefix, after, force, subrepos, warnings):
1033 def removefiles(self, matcher, prefix, after, force, subrepos, warnings):
1033 return cmdutil.remove(self.ui, self._repo, matcher,
1034 return cmdutil.remove(self.ui, self._repo, matcher,
1034 self.wvfs.reljoin(prefix, self._path),
1035 self.wvfs.reljoin(prefix, self._path),
1035 after, force, subrepos)
1036 after, force, subrepos)
1036
1037
1037 @annotatesubrepoerror
1038 @annotatesubrepoerror
1038 def revert(self, substate, *pats, **opts):
1039 def revert(self, substate, *pats, **opts):
1039 # reverting a subrepo is a 2 step process:
1040 # reverting a subrepo is a 2 step process:
1040 # 1. if the no_backup is not set, revert all modified
1041 # 1. if the no_backup is not set, revert all modified
1041 # files inside the subrepo
1042 # files inside the subrepo
1042 # 2. update the subrepo to the revision specified in
1043 # 2. update the subrepo to the revision specified in
1043 # the corresponding substate dictionary
1044 # the corresponding substate dictionary
1044 self.ui.status(_('reverting subrepo %s\n') % substate[0])
1045 self.ui.status(_('reverting subrepo %s\n') % substate[0])
1045 if not opts.get('no_backup'):
1046 if not opts.get('no_backup'):
1046 # Revert all files on the subrepo, creating backups
1047 # Revert all files on the subrepo, creating backups
1047 # Note that this will not recursively revert subrepos
1048 # Note that this will not recursively revert subrepos
1048 # We could do it if there was a set:subrepos() predicate
1049 # We could do it if there was a set:subrepos() predicate
1049 opts = opts.copy()
1050 opts = opts.copy()
1050 opts['date'] = None
1051 opts['date'] = None
1051 opts['rev'] = substate[1]
1052 opts['rev'] = substate[1]
1052
1053
1053 self.filerevert(*pats, **opts)
1054 self.filerevert(*pats, **opts)
1054
1055
1055 # Update the repo to the revision specified in the given substate
1056 # Update the repo to the revision specified in the given substate
1056 if not opts.get('dry_run'):
1057 if not opts.get('dry_run'):
1057 self.get(substate, overwrite=True)
1058 self.get(substate, overwrite=True)
1058
1059
1059 def filerevert(self, *pats, **opts):
1060 def filerevert(self, *pats, **opts):
1060 ctx = self._repo[opts['rev']]
1061 ctx = self._repo[opts['rev']]
1061 parents = self._repo.dirstate.parents()
1062 parents = self._repo.dirstate.parents()
1062 if opts.get('all'):
1063 if opts.get('all'):
1063 pats = ['set:modified()']
1064 pats = ['set:modified()']
1064 else:
1065 else:
1065 pats = []
1066 pats = []
1066 cmdutil.revert(self.ui, self._repo, ctx, parents, *pats, **opts)
1067 cmdutil.revert(self.ui, self._repo, ctx, parents, *pats, **opts)
1067
1068
1068 def shortid(self, revid):
1069 def shortid(self, revid):
1069 return revid[:12]
1070 return revid[:12]
1070
1071
1071 def verify(self):
1072 def verify(self):
1072 try:
1073 try:
1073 rev = self._state[1]
1074 rev = self._state[1]
1074 ctx = self._repo.unfiltered()[rev]
1075 ctx = self._repo.unfiltered()[rev]
1075 if ctx.hidden():
1076 if ctx.hidden():
1076 # Since hidden revisions aren't pushed/pulled, it seems worth an
1077 # Since hidden revisions aren't pushed/pulled, it seems worth an
1077 # explicit warning.
1078 # explicit warning.
1078 ui = self._repo.ui
1079 ui = self._repo.ui
1079 ui.warn(_("subrepo '%s' is hidden in revision %s\n") %
1080 ui.warn(_("subrepo '%s' is hidden in revision %s\n") %
1080 (self._relpath, node.short(self._ctx.node())))
1081 (self._relpath, node.short(self._ctx.node())))
1081 return 0
1082 return 0
1082 except error.RepoLookupError:
1083 except error.RepoLookupError:
1083 # A missing subrepo revision may be a case of needing to pull it, so
1084 # A missing subrepo revision may be a case of needing to pull it, so
1084 # don't treat this as an error.
1085 # don't treat this as an error.
1085 self._repo.ui.warn(_("subrepo '%s' not found in revision %s\n") %
1086 self._repo.ui.warn(_("subrepo '%s' not found in revision %s\n") %
1086 (self._relpath, node.short(self._ctx.node())))
1087 (self._relpath, node.short(self._ctx.node())))
1087 return 0
1088 return 0
1088
1089
1089 @propertycache
1090 @propertycache
1090 def wvfs(self):
1091 def wvfs(self):
1091 """return own wvfs for efficiency and consistency
1092 """return own wvfs for efficiency and consistency
1092 """
1093 """
1093 return self._repo.wvfs
1094 return self._repo.wvfs
1094
1095
1095 @propertycache
1096 @propertycache
1096 def _relpath(self):
1097 def _relpath(self):
1097 """return path to this subrepository as seen from outermost repository
1098 """return path to this subrepository as seen from outermost repository
1098 """
1099 """
1099 # Keep consistent dir separators by avoiding vfs.join(self._path)
1100 # Keep consistent dir separators by avoiding vfs.join(self._path)
1100 return reporelpath(self._repo)
1101 return reporelpath(self._repo)
1101
1102
1102 class svnsubrepo(abstractsubrepo):
1103 class svnsubrepo(abstractsubrepo):
1103 def __init__(self, ctx, path, state, allowcreate):
1104 def __init__(self, ctx, path, state, allowcreate):
1104 super(svnsubrepo, self).__init__(ctx, path)
1105 super(svnsubrepo, self).__init__(ctx, path)
1105 self._state = state
1106 self._state = state
1106 self._exe = util.findexe('svn')
1107 self._exe = util.findexe('svn')
1107 if not self._exe:
1108 if not self._exe:
1108 raise error.Abort(_("'svn' executable not found for subrepo '%s'")
1109 raise error.Abort(_("'svn' executable not found for subrepo '%s'")
1109 % self._path)
1110 % self._path)
1110
1111
1111 def _svncommand(self, commands, filename='', failok=False):
1112 def _svncommand(self, commands, filename='', failok=False):
1112 cmd = [self._exe]
1113 cmd = [self._exe]
1113 extrakw = {}
1114 extrakw = {}
1114 if not self.ui.interactive():
1115 if not self.ui.interactive():
1115 # Making stdin be a pipe should prevent svn from behaving
1116 # Making stdin be a pipe should prevent svn from behaving
1116 # interactively even if we can't pass --non-interactive.
1117 # interactively even if we can't pass --non-interactive.
1117 extrakw['stdin'] = subprocess.PIPE
1118 extrakw['stdin'] = subprocess.PIPE
1118 # Starting in svn 1.5 --non-interactive is a global flag
1119 # Starting in svn 1.5 --non-interactive is a global flag
1119 # instead of being per-command, but we need to support 1.4 so
1120 # instead of being per-command, but we need to support 1.4 so
1120 # we have to be intelligent about what commands take
1121 # we have to be intelligent about what commands take
1121 # --non-interactive.
1122 # --non-interactive.
1122 if commands[0] in ('update', 'checkout', 'commit'):
1123 if commands[0] in ('update', 'checkout', 'commit'):
1123 cmd.append('--non-interactive')
1124 cmd.append('--non-interactive')
1124 cmd.extend(commands)
1125 cmd.extend(commands)
1125 if filename is not None:
1126 if filename is not None:
1126 path = self.wvfs.reljoin(self._ctx.repo().origroot,
1127 path = self.wvfs.reljoin(self._ctx.repo().origroot,
1127 self._path, filename)
1128 self._path, filename)
1128 cmd.append(path)
1129 cmd.append(path)
1129 env = dict(encoding.environ)
1130 env = dict(encoding.environ)
1130 # Avoid localized output, preserve current locale for everything else.
1131 # Avoid localized output, preserve current locale for everything else.
1131 lc_all = env.get('LC_ALL')
1132 lc_all = env.get('LC_ALL')
1132 if lc_all:
1133 if lc_all:
1133 env['LANG'] = lc_all
1134 env['LANG'] = lc_all
1134 del env['LC_ALL']
1135 del env['LC_ALL']
1135 env['LC_MESSAGES'] = 'C'
1136 env['LC_MESSAGES'] = 'C'
1136 p = subprocess.Popen(cmd, bufsize=-1, close_fds=util.closefds,
1137 p = subprocess.Popen(cmd, bufsize=-1, close_fds=util.closefds,
1137 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
1138 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
1138 universal_newlines=True, env=env, **extrakw)
1139 universal_newlines=True, env=env, **extrakw)
1139 stdout, stderr = p.communicate()
1140 stdout, stderr = p.communicate()
1140 stderr = stderr.strip()
1141 stderr = stderr.strip()
1141 if not failok:
1142 if not failok:
1142 if p.returncode:
1143 if p.returncode:
1143 raise error.Abort(stderr or 'exited with code %d'
1144 raise error.Abort(stderr or 'exited with code %d'
1144 % p.returncode)
1145 % p.returncode)
1145 if stderr:
1146 if stderr:
1146 self.ui.warn(stderr + '\n')
1147 self.ui.warn(stderr + '\n')
1147 return stdout, stderr
1148 return stdout, stderr
1148
1149
1149 @propertycache
1150 @propertycache
1150 def _svnversion(self):
1151 def _svnversion(self):
1151 output, err = self._svncommand(['--version', '--quiet'], filename=None)
1152 output, err = self._svncommand(['--version', '--quiet'], filename=None)
1152 m = re.search(r'^(\d+)\.(\d+)', output)
1153 m = re.search(r'^(\d+)\.(\d+)', output)
1153 if not m:
1154 if not m:
1154 raise error.Abort(_('cannot retrieve svn tool version'))
1155 raise error.Abort(_('cannot retrieve svn tool version'))
1155 return (int(m.group(1)), int(m.group(2)))
1156 return (int(m.group(1)), int(m.group(2)))
1156
1157
1157 def _wcrevs(self):
1158 def _wcrevs(self):
1158 # Get the working directory revision as well as the last
1159 # Get the working directory revision as well as the last
1159 # commit revision so we can compare the subrepo state with
1160 # commit revision so we can compare the subrepo state with
1160 # both. We used to store the working directory one.
1161 # both. We used to store the working directory one.
1161 output, err = self._svncommand(['info', '--xml'])
1162 output, err = self._svncommand(['info', '--xml'])
1162 doc = xml.dom.minidom.parseString(output)
1163 doc = xml.dom.minidom.parseString(output)
1163 entries = doc.getElementsByTagName('entry')
1164 entries = doc.getElementsByTagName('entry')
1164 lastrev, rev = '0', '0'
1165 lastrev, rev = '0', '0'
1165 if entries:
1166 if entries:
1166 rev = str(entries[0].getAttribute('revision')) or '0'
1167 rev = str(entries[0].getAttribute('revision')) or '0'
1167 commits = entries[0].getElementsByTagName('commit')
1168 commits = entries[0].getElementsByTagName('commit')
1168 if commits:
1169 if commits:
1169 lastrev = str(commits[0].getAttribute('revision')) or '0'
1170 lastrev = str(commits[0].getAttribute('revision')) or '0'
1170 return (lastrev, rev)
1171 return (lastrev, rev)
1171
1172
1172 def _wcrev(self):
1173 def _wcrev(self):
1173 return self._wcrevs()[0]
1174 return self._wcrevs()[0]
1174
1175
1175 def _wcchanged(self):
1176 def _wcchanged(self):
1176 """Return (changes, extchanges, missing) where changes is True
1177 """Return (changes, extchanges, missing) where changes is True
1177 if the working directory was changed, extchanges is
1178 if the working directory was changed, extchanges is
1178 True if any of these changes concern an external entry and missing
1179 True if any of these changes concern an external entry and missing
1179 is True if any change is a missing entry.
1180 is True if any change is a missing entry.
1180 """
1181 """
1181 output, err = self._svncommand(['status', '--xml'])
1182 output, err = self._svncommand(['status', '--xml'])
1182 externals, changes, missing = [], [], []
1183 externals, changes, missing = [], [], []
1183 doc = xml.dom.minidom.parseString(output)
1184 doc = xml.dom.minidom.parseString(output)
1184 for e in doc.getElementsByTagName('entry'):
1185 for e in doc.getElementsByTagName('entry'):
1185 s = e.getElementsByTagName('wc-status')
1186 s = e.getElementsByTagName('wc-status')
1186 if not s:
1187 if not s:
1187 continue
1188 continue
1188 item = s[0].getAttribute('item')
1189 item = s[0].getAttribute('item')
1189 props = s[0].getAttribute('props')
1190 props = s[0].getAttribute('props')
1190 path = e.getAttribute('path')
1191 path = e.getAttribute('path')
1191 if item == 'external':
1192 if item == 'external':
1192 externals.append(path)
1193 externals.append(path)
1193 elif item == 'missing':
1194 elif item == 'missing':
1194 missing.append(path)
1195 missing.append(path)
1195 if (item not in ('', 'normal', 'unversioned', 'external')
1196 if (item not in ('', 'normal', 'unversioned', 'external')
1196 or props not in ('', 'none', 'normal')):
1197 or props not in ('', 'none', 'normal')):
1197 changes.append(path)
1198 changes.append(path)
1198 for path in changes:
1199 for path in changes:
1199 for ext in externals:
1200 for ext in externals:
1200 if path == ext or path.startswith(ext + pycompat.ossep):
1201 if path == ext or path.startswith(ext + pycompat.ossep):
1201 return True, True, bool(missing)
1202 return True, True, bool(missing)
1202 return bool(changes), False, bool(missing)
1203 return bool(changes), False, bool(missing)
1203
1204
1204 def dirty(self, ignoreupdate=False):
1205 def dirty(self, ignoreupdate=False):
1205 if not self._wcchanged()[0]:
1206 if not self._wcchanged()[0]:
1206 if self._state[1] in self._wcrevs() or ignoreupdate:
1207 if self._state[1] in self._wcrevs() or ignoreupdate:
1207 return False
1208 return False
1208 return True
1209 return True
1209
1210
1210 def basestate(self):
1211 def basestate(self):
1211 lastrev, rev = self._wcrevs()
1212 lastrev, rev = self._wcrevs()
1212 if lastrev != rev:
1213 if lastrev != rev:
1213 # Last committed rev is not the same than rev. We would
1214 # Last committed rev is not the same than rev. We would
1214 # like to take lastrev but we do not know if the subrepo
1215 # like to take lastrev but we do not know if the subrepo
1215 # URL exists at lastrev. Test it and fallback to rev it
1216 # URL exists at lastrev. Test it and fallback to rev it
1216 # is not there.
1217 # is not there.
1217 try:
1218 try:
1218 self._svncommand(['list', '%s@%s' % (self._state[0], lastrev)])
1219 self._svncommand(['list', '%s@%s' % (self._state[0], lastrev)])
1219 return lastrev
1220 return lastrev
1220 except error.Abort:
1221 except error.Abort:
1221 pass
1222 pass
1222 return rev
1223 return rev
1223
1224
1224 @annotatesubrepoerror
1225 @annotatesubrepoerror
1225 def commit(self, text, user, date):
1226 def commit(self, text, user, date):
1226 # user and date are out of our hands since svn is centralized
1227 # user and date are out of our hands since svn is centralized
1227 changed, extchanged, missing = self._wcchanged()
1228 changed, extchanged, missing = self._wcchanged()
1228 if not changed:
1229 if not changed:
1229 return self.basestate()
1230 return self.basestate()
1230 if extchanged:
1231 if extchanged:
1231 # Do not try to commit externals
1232 # Do not try to commit externals
1232 raise error.Abort(_('cannot commit svn externals'))
1233 raise error.Abort(_('cannot commit svn externals'))
1233 if missing:
1234 if missing:
1234 # svn can commit with missing entries but aborting like hg
1235 # svn can commit with missing entries but aborting like hg
1235 # seems a better approach.
1236 # seems a better approach.
1236 raise error.Abort(_('cannot commit missing svn entries'))
1237 raise error.Abort(_('cannot commit missing svn entries'))
1237 commitinfo, err = self._svncommand(['commit', '-m', text])
1238 commitinfo, err = self._svncommand(['commit', '-m', text])
1238 self.ui.status(commitinfo)
1239 self.ui.status(commitinfo)
1239 newrev = re.search('Committed revision ([0-9]+).', commitinfo)
1240 newrev = re.search('Committed revision ([0-9]+).', commitinfo)
1240 if not newrev:
1241 if not newrev:
1241 if not commitinfo.strip():
1242 if not commitinfo.strip():
1242 # Sometimes, our definition of "changed" differs from
1243 # Sometimes, our definition of "changed" differs from
1243 # svn one. For instance, svn ignores missing files
1244 # svn one. For instance, svn ignores missing files
1244 # when committing. If there are only missing files, no
1245 # when committing. If there are only missing files, no
1245 # commit is made, no output and no error code.
1246 # commit is made, no output and no error code.
1246 raise error.Abort(_('failed to commit svn changes'))
1247 raise error.Abort(_('failed to commit svn changes'))
1247 raise error.Abort(commitinfo.splitlines()[-1])
1248 raise error.Abort(commitinfo.splitlines()[-1])
1248 newrev = newrev.groups()[0]
1249 newrev = newrev.groups()[0]
1249 self.ui.status(self._svncommand(['update', '-r', newrev])[0])
1250 self.ui.status(self._svncommand(['update', '-r', newrev])[0])
1250 return newrev
1251 return newrev
1251
1252
1252 @annotatesubrepoerror
1253 @annotatesubrepoerror
1253 def remove(self):
1254 def remove(self):
1254 if self.dirty():
1255 if self.dirty():
1255 self.ui.warn(_('not removing repo %s because '
1256 self.ui.warn(_('not removing repo %s because '
1256 'it has changes.\n') % self._path)
1257 'it has changes.\n') % self._path)
1257 return
1258 return
1258 self.ui.note(_('removing subrepo %s\n') % self._path)
1259 self.ui.note(_('removing subrepo %s\n') % self._path)
1259
1260
1260 self.wvfs.rmtree(forcibly=True)
1261 self.wvfs.rmtree(forcibly=True)
1261 try:
1262 try:
1262 pwvfs = self._ctx.repo().wvfs
1263 pwvfs = self._ctx.repo().wvfs
1263 pwvfs.removedirs(pwvfs.dirname(self._path))
1264 pwvfs.removedirs(pwvfs.dirname(self._path))
1264 except OSError:
1265 except OSError:
1265 pass
1266 pass
1266
1267
1267 @annotatesubrepoerror
1268 @annotatesubrepoerror
1268 def get(self, state, overwrite=False):
1269 def get(self, state, overwrite=False):
1269 if overwrite:
1270 if overwrite:
1270 self._svncommand(['revert', '--recursive'])
1271 self._svncommand(['revert', '--recursive'])
1271 args = ['checkout']
1272 args = ['checkout']
1272 if self._svnversion >= (1, 5):
1273 if self._svnversion >= (1, 5):
1273 args.append('--force')
1274 args.append('--force')
1274 # The revision must be specified at the end of the URL to properly
1275 # The revision must be specified at the end of the URL to properly
1275 # update to a directory which has since been deleted and recreated.
1276 # update to a directory which has since been deleted and recreated.
1276 args.append('%s@%s' % (state[0], state[1]))
1277 args.append('%s@%s' % (state[0], state[1]))
1277 status, err = self._svncommand(args, failok=True)
1278 status, err = self._svncommand(args, failok=True)
1278 _sanitize(self.ui, self.wvfs, '.svn')
1279 _sanitize(self.ui, self.wvfs, '.svn')
1279 if not re.search('Checked out revision [0-9]+.', status):
1280 if not re.search('Checked out revision [0-9]+.', status):
1280 if ('is already a working copy for a different URL' in err
1281 if ('is already a working copy for a different URL' in err
1281 and (self._wcchanged()[:2] == (False, False))):
1282 and (self._wcchanged()[:2] == (False, False))):
1282 # obstructed but clean working copy, so just blow it away.
1283 # obstructed but clean working copy, so just blow it away.
1283 self.remove()
1284 self.remove()
1284 self.get(state, overwrite=False)
1285 self.get(state, overwrite=False)
1285 return
1286 return
1286 raise error.Abort((status or err).splitlines()[-1])
1287 raise error.Abort((status or err).splitlines()[-1])
1287 self.ui.status(status)
1288 self.ui.status(status)
1288
1289
1289 @annotatesubrepoerror
1290 @annotatesubrepoerror
1290 def merge(self, state):
1291 def merge(self, state):
1291 old = self._state[1]
1292 old = self._state[1]
1292 new = state[1]
1293 new = state[1]
1293 wcrev = self._wcrev()
1294 wcrev = self._wcrev()
1294 if new != wcrev:
1295 if new != wcrev:
1295 dirty = old == wcrev or self._wcchanged()[0]
1296 dirty = old == wcrev or self._wcchanged()[0]
1296 if _updateprompt(self.ui, self, dirty, wcrev, new):
1297 if _updateprompt(self.ui, self, dirty, wcrev, new):
1297 self.get(state, False)
1298 self.get(state, False)
1298
1299
1299 def push(self, opts):
1300 def push(self, opts):
1300 # push is a no-op for SVN
1301 # push is a no-op for SVN
1301 return True
1302 return True
1302
1303
1303 @annotatesubrepoerror
1304 @annotatesubrepoerror
1304 def files(self):
1305 def files(self):
1305 output = self._svncommand(['list', '--recursive', '--xml'])[0]
1306 output = self._svncommand(['list', '--recursive', '--xml'])[0]
1306 doc = xml.dom.minidom.parseString(output)
1307 doc = xml.dom.minidom.parseString(output)
1307 paths = []
1308 paths = []
1308 for e in doc.getElementsByTagName('entry'):
1309 for e in doc.getElementsByTagName('entry'):
1309 kind = str(e.getAttribute('kind'))
1310 kind = str(e.getAttribute('kind'))
1310 if kind != 'file':
1311 if kind != 'file':
1311 continue
1312 continue
1312 name = ''.join(c.data for c
1313 name = ''.join(c.data for c
1313 in e.getElementsByTagName('name')[0].childNodes
1314 in e.getElementsByTagName('name')[0].childNodes
1314 if c.nodeType == c.TEXT_NODE)
1315 if c.nodeType == c.TEXT_NODE)
1315 paths.append(name.encode('utf-8'))
1316 paths.append(name.encode('utf-8'))
1316 return paths
1317 return paths
1317
1318
1318 def filedata(self, name, decode):
1319 def filedata(self, name, decode):
1319 return self._svncommand(['cat'], name)[0]
1320 return self._svncommand(['cat'], name)[0]
1320
1321
1321
1322
1322 class gitsubrepo(abstractsubrepo):
1323 class gitsubrepo(abstractsubrepo):
1323 def __init__(self, ctx, path, state, allowcreate):
1324 def __init__(self, ctx, path, state, allowcreate):
1324 super(gitsubrepo, self).__init__(ctx, path)
1325 super(gitsubrepo, self).__init__(ctx, path)
1325 self._state = state
1326 self._state = state
1326 self._abspath = ctx.repo().wjoin(path)
1327 self._abspath = ctx.repo().wjoin(path)
1327 self._subparent = ctx.repo()
1328 self._subparent = ctx.repo()
1328 self._ensuregit()
1329 self._ensuregit()
1329
1330
1330 def _ensuregit(self):
1331 def _ensuregit(self):
1331 try:
1332 try:
1332 self._gitexecutable = 'git'
1333 self._gitexecutable = 'git'
1333 out, err = self._gitnodir(['--version'])
1334 out, err = self._gitnodir(['--version'])
1334 except OSError as e:
1335 except OSError as e:
1335 genericerror = _("error executing git for subrepo '%s': %s")
1336 genericerror = _("error executing git for subrepo '%s': %s")
1336 notfoundhint = _("check git is installed and in your PATH")
1337 notfoundhint = _("check git is installed and in your PATH")
1337 if e.errno != errno.ENOENT:
1338 if e.errno != errno.ENOENT:
1338 raise error.Abort(genericerror % (self._path, e.strerror))
1339 raise error.Abort(genericerror % (self._path, e.strerror))
1339 elif pycompat.osname == 'nt':
1340 elif pycompat.osname == 'nt':
1340 try:
1341 try:
1341 self._gitexecutable = 'git.cmd'
1342 self._gitexecutable = 'git.cmd'
1342 out, err = self._gitnodir(['--version'])
1343 out, err = self._gitnodir(['--version'])
1343 except OSError as e2:
1344 except OSError as e2:
1344 if e2.errno == errno.ENOENT:
1345 if e2.errno == errno.ENOENT:
1345 raise error.Abort(_("couldn't find 'git' or 'git.cmd'"
1346 raise error.Abort(_("couldn't find 'git' or 'git.cmd'"
1346 " for subrepo '%s'") % self._path,
1347 " for subrepo '%s'") % self._path,
1347 hint=notfoundhint)
1348 hint=notfoundhint)
1348 else:
1349 else:
1349 raise error.Abort(genericerror % (self._path,
1350 raise error.Abort(genericerror % (self._path,
1350 e2.strerror))
1351 e2.strerror))
1351 else:
1352 else:
1352 raise error.Abort(_("couldn't find git for subrepo '%s'")
1353 raise error.Abort(_("couldn't find git for subrepo '%s'")
1353 % self._path, hint=notfoundhint)
1354 % self._path, hint=notfoundhint)
1354 versionstatus = self._checkversion(out)
1355 versionstatus = self._checkversion(out)
1355 if versionstatus == 'unknown':
1356 if versionstatus == 'unknown':
1356 self.ui.warn(_('cannot retrieve git version\n'))
1357 self.ui.warn(_('cannot retrieve git version\n'))
1357 elif versionstatus == 'abort':
1358 elif versionstatus == 'abort':
1358 raise error.Abort(_('git subrepo requires at least 1.6.0 or later'))
1359 raise error.Abort(_('git subrepo requires at least 1.6.0 or later'))
1359 elif versionstatus == 'warning':
1360 elif versionstatus == 'warning':
1360 self.ui.warn(_('git subrepo requires at least 1.6.0 or later\n'))
1361 self.ui.warn(_('git subrepo requires at least 1.6.0 or later\n'))
1361
1362
1362 @staticmethod
1363 @staticmethod
1363 def _gitversion(out):
1364 def _gitversion(out):
1364 m = re.search(r'^git version (\d+)\.(\d+)\.(\d+)', out)
1365 m = re.search(r'^git version (\d+)\.(\d+)\.(\d+)', out)
1365 if m:
1366 if m:
1366 return (int(m.group(1)), int(m.group(2)), int(m.group(3)))
1367 return (int(m.group(1)), int(m.group(2)), int(m.group(3)))
1367
1368
1368 m = re.search(r'^git version (\d+)\.(\d+)', out)
1369 m = re.search(r'^git version (\d+)\.(\d+)', out)
1369 if m:
1370 if m:
1370 return (int(m.group(1)), int(m.group(2)), 0)
1371 return (int(m.group(1)), int(m.group(2)), 0)
1371
1372
1372 return -1
1373 return -1
1373
1374
1374 @staticmethod
1375 @staticmethod
1375 def _checkversion(out):
1376 def _checkversion(out):
1376 '''ensure git version is new enough
1377 '''ensure git version is new enough
1377
1378
1378 >>> _checkversion = gitsubrepo._checkversion
1379 >>> _checkversion = gitsubrepo._checkversion
1379 >>> _checkversion('git version 1.6.0')
1380 >>> _checkversion('git version 1.6.0')
1380 'ok'
1381 'ok'
1381 >>> _checkversion('git version 1.8.5')
1382 >>> _checkversion('git version 1.8.5')
1382 'ok'
1383 'ok'
1383 >>> _checkversion('git version 1.4.0')
1384 >>> _checkversion('git version 1.4.0')
1384 'abort'
1385 'abort'
1385 >>> _checkversion('git version 1.5.0')
1386 >>> _checkversion('git version 1.5.0')
1386 'warning'
1387 'warning'
1387 >>> _checkversion('git version 1.9-rc0')
1388 >>> _checkversion('git version 1.9-rc0')
1388 'ok'
1389 'ok'
1389 >>> _checkversion('git version 1.9.0.265.g81cdec2')
1390 >>> _checkversion('git version 1.9.0.265.g81cdec2')
1390 'ok'
1391 'ok'
1391 >>> _checkversion('git version 1.9.0.GIT')
1392 >>> _checkversion('git version 1.9.0.GIT')
1392 'ok'
1393 'ok'
1393 >>> _checkversion('git version 12345')
1394 >>> _checkversion('git version 12345')
1394 'unknown'
1395 'unknown'
1395 >>> _checkversion('no')
1396 >>> _checkversion('no')
1396 'unknown'
1397 'unknown'
1397 '''
1398 '''
1398 version = gitsubrepo._gitversion(out)
1399 version = gitsubrepo._gitversion(out)
1399 # git 1.4.0 can't work at all, but 1.5.X can in at least some cases,
1400 # git 1.4.0 can't work at all, but 1.5.X can in at least some cases,
1400 # despite the docstring comment. For now, error on 1.4.0, warn on
1401 # despite the docstring comment. For now, error on 1.4.0, warn on
1401 # 1.5.0 but attempt to continue.
1402 # 1.5.0 but attempt to continue.
1402 if version == -1:
1403 if version == -1:
1403 return 'unknown'
1404 return 'unknown'
1404 if version < (1, 5, 0):
1405 if version < (1, 5, 0):
1405 return 'abort'
1406 return 'abort'
1406 elif version < (1, 6, 0):
1407 elif version < (1, 6, 0):
1407 return 'warning'
1408 return 'warning'
1408 return 'ok'
1409 return 'ok'
1409
1410
1410 def _gitcommand(self, commands, env=None, stream=False):
1411 def _gitcommand(self, commands, env=None, stream=False):
1411 return self._gitdir(commands, env=env, stream=stream)[0]
1412 return self._gitdir(commands, env=env, stream=stream)[0]
1412
1413
1413 def _gitdir(self, commands, env=None, stream=False):
1414 def _gitdir(self, commands, env=None, stream=False):
1414 return self._gitnodir(commands, env=env, stream=stream,
1415 return self._gitnodir(commands, env=env, stream=stream,
1415 cwd=self._abspath)
1416 cwd=self._abspath)
1416
1417
1417 def _gitnodir(self, commands, env=None, stream=False, cwd=None):
1418 def _gitnodir(self, commands, env=None, stream=False, cwd=None):
1418 """Calls the git command
1419 """Calls the git command
1419
1420
1420 The methods tries to call the git command. versions prior to 1.6.0
1421 The methods tries to call the git command. versions prior to 1.6.0
1421 are not supported and very probably fail.
1422 are not supported and very probably fail.
1422 """
1423 """
1423 self.ui.debug('%s: git %s\n' % (self._relpath, ' '.join(commands)))
1424 self.ui.debug('%s: git %s\n' % (self._relpath, ' '.join(commands)))
1424 if env is None:
1425 if env is None:
1425 env = encoding.environ.copy()
1426 env = encoding.environ.copy()
1426 # disable localization for Git output (issue5176)
1427 # disable localization for Git output (issue5176)
1427 env['LC_ALL'] = 'C'
1428 env['LC_ALL'] = 'C'
1428 # fix for Git CVE-2015-7545
1429 # fix for Git CVE-2015-7545
1429 if 'GIT_ALLOW_PROTOCOL' not in env:
1430 if 'GIT_ALLOW_PROTOCOL' not in env:
1430 env['GIT_ALLOW_PROTOCOL'] = 'file:git:http:https:ssh'
1431 env['GIT_ALLOW_PROTOCOL'] = 'file:git:http:https:ssh'
1431 # unless ui.quiet is set, print git's stderr,
1432 # unless ui.quiet is set, print git's stderr,
1432 # which is mostly progress and useful info
1433 # which is mostly progress and useful info
1433 errpipe = None
1434 errpipe = None
1434 if self.ui.quiet:
1435 if self.ui.quiet:
1435 errpipe = open(os.devnull, 'w')
1436 errpipe = open(os.devnull, 'w')
1436 if self.ui._colormode and len(commands) and commands[0] == "diff":
1437 if self.ui._colormode and len(commands) and commands[0] == "diff":
1437 # insert the argument in the front,
1438 # insert the argument in the front,
1438 # the end of git diff arguments is used for paths
1439 # the end of git diff arguments is used for paths
1439 commands.insert(1, '--color')
1440 commands.insert(1, '--color')
1440 p = subprocess.Popen([self._gitexecutable] + commands, bufsize=-1,
1441 p = subprocess.Popen([self._gitexecutable] + commands, bufsize=-1,
1441 cwd=cwd, env=env, close_fds=util.closefds,
1442 cwd=cwd, env=env, close_fds=util.closefds,
1442 stdout=subprocess.PIPE, stderr=errpipe)
1443 stdout=subprocess.PIPE, stderr=errpipe)
1443 if stream:
1444 if stream:
1444 return p.stdout, None
1445 return p.stdout, None
1445
1446
1446 retdata = p.stdout.read().strip()
1447 retdata = p.stdout.read().strip()
1447 # wait for the child to exit to avoid race condition.
1448 # wait for the child to exit to avoid race condition.
1448 p.wait()
1449 p.wait()
1449
1450
1450 if p.returncode != 0 and p.returncode != 1:
1451 if p.returncode != 0 and p.returncode != 1:
1451 # there are certain error codes that are ok
1452 # there are certain error codes that are ok
1452 command = commands[0]
1453 command = commands[0]
1453 if command in ('cat-file', 'symbolic-ref'):
1454 if command in ('cat-file', 'symbolic-ref'):
1454 return retdata, p.returncode
1455 return retdata, p.returncode
1455 # for all others, abort
1456 # for all others, abort
1456 raise error.Abort(_('git %s error %d in %s') %
1457 raise error.Abort(_('git %s error %d in %s') %
1457 (command, p.returncode, self._relpath))
1458 (command, p.returncode, self._relpath))
1458
1459
1459 return retdata, p.returncode
1460 return retdata, p.returncode
1460
1461
1461 def _gitmissing(self):
1462 def _gitmissing(self):
1462 return not self.wvfs.exists('.git')
1463 return not self.wvfs.exists('.git')
1463
1464
1464 def _gitstate(self):
1465 def _gitstate(self):
1465 return self._gitcommand(['rev-parse', 'HEAD'])
1466 return self._gitcommand(['rev-parse', 'HEAD'])
1466
1467
1467 def _gitcurrentbranch(self):
1468 def _gitcurrentbranch(self):
1468 current, err = self._gitdir(['symbolic-ref', 'HEAD', '--quiet'])
1469 current, err = self._gitdir(['symbolic-ref', 'HEAD', '--quiet'])
1469 if err:
1470 if err:
1470 current = None
1471 current = None
1471 return current
1472 return current
1472
1473
1473 def _gitremote(self, remote):
1474 def _gitremote(self, remote):
1474 out = self._gitcommand(['remote', 'show', '-n', remote])
1475 out = self._gitcommand(['remote', 'show', '-n', remote])
1475 line = out.split('\n')[1]
1476 line = out.split('\n')[1]
1476 i = line.index('URL: ') + len('URL: ')
1477 i = line.index('URL: ') + len('URL: ')
1477 return line[i:]
1478 return line[i:]
1478
1479
1479 def _githavelocally(self, revision):
1480 def _githavelocally(self, revision):
1480 out, code = self._gitdir(['cat-file', '-e', revision])
1481 out, code = self._gitdir(['cat-file', '-e', revision])
1481 return code == 0
1482 return code == 0
1482
1483
1483 def _gitisancestor(self, r1, r2):
1484 def _gitisancestor(self, r1, r2):
1484 base = self._gitcommand(['merge-base', r1, r2])
1485 base = self._gitcommand(['merge-base', r1, r2])
1485 return base == r1
1486 return base == r1
1486
1487
1487 def _gitisbare(self):
1488 def _gitisbare(self):
1488 return self._gitcommand(['config', '--bool', 'core.bare']) == 'true'
1489 return self._gitcommand(['config', '--bool', 'core.bare']) == 'true'
1489
1490
1490 def _gitupdatestat(self):
1491 def _gitupdatestat(self):
1491 """This must be run before git diff-index.
1492 """This must be run before git diff-index.
1492 diff-index only looks at changes to file stat;
1493 diff-index only looks at changes to file stat;
1493 this command looks at file contents and updates the stat."""
1494 this command looks at file contents and updates the stat."""
1494 self._gitcommand(['update-index', '-q', '--refresh'])
1495 self._gitcommand(['update-index', '-q', '--refresh'])
1495
1496
1496 def _gitbranchmap(self):
1497 def _gitbranchmap(self):
1497 '''returns 2 things:
1498 '''returns 2 things:
1498 a map from git branch to revision
1499 a map from git branch to revision
1499 a map from revision to branches'''
1500 a map from revision to branches'''
1500 branch2rev = {}
1501 branch2rev = {}
1501 rev2branch = {}
1502 rev2branch = {}
1502
1503
1503 out = self._gitcommand(['for-each-ref', '--format',
1504 out = self._gitcommand(['for-each-ref', '--format',
1504 '%(objectname) %(refname)'])
1505 '%(objectname) %(refname)'])
1505 for line in out.split('\n'):
1506 for line in out.split('\n'):
1506 revision, ref = line.split(' ')
1507 revision, ref = line.split(' ')
1507 if (not ref.startswith('refs/heads/') and
1508 if (not ref.startswith('refs/heads/') and
1508 not ref.startswith('refs/remotes/')):
1509 not ref.startswith('refs/remotes/')):
1509 continue
1510 continue
1510 if ref.startswith('refs/remotes/') and ref.endswith('/HEAD'):
1511 if ref.startswith('refs/remotes/') and ref.endswith('/HEAD'):
1511 continue # ignore remote/HEAD redirects
1512 continue # ignore remote/HEAD redirects
1512 branch2rev[ref] = revision
1513 branch2rev[ref] = revision
1513 rev2branch.setdefault(revision, []).append(ref)
1514 rev2branch.setdefault(revision, []).append(ref)
1514 return branch2rev, rev2branch
1515 return branch2rev, rev2branch
1515
1516
1516 def _gittracking(self, branches):
1517 def _gittracking(self, branches):
1517 'return map of remote branch to local tracking branch'
1518 'return map of remote branch to local tracking branch'
1518 # assumes no more than one local tracking branch for each remote
1519 # assumes no more than one local tracking branch for each remote
1519 tracking = {}
1520 tracking = {}
1520 for b in branches:
1521 for b in branches:
1521 if b.startswith('refs/remotes/'):
1522 if b.startswith('refs/remotes/'):
1522 continue
1523 continue
1523 bname = b.split('/', 2)[2]
1524 bname = b.split('/', 2)[2]
1524 remote = self._gitcommand(['config', 'branch.%s.remote' % bname])
1525 remote = self._gitcommand(['config', 'branch.%s.remote' % bname])
1525 if remote:
1526 if remote:
1526 ref = self._gitcommand(['config', 'branch.%s.merge' % bname])
1527 ref = self._gitcommand(['config', 'branch.%s.merge' % bname])
1527 tracking['refs/remotes/%s/%s' %
1528 tracking['refs/remotes/%s/%s' %
1528 (remote, ref.split('/', 2)[2])] = b
1529 (remote, ref.split('/', 2)[2])] = b
1529 return tracking
1530 return tracking
1530
1531
1531 def _abssource(self, source):
1532 def _abssource(self, source):
1532 if '://' not in source:
1533 if '://' not in source:
1533 # recognize the scp syntax as an absolute source
1534 # recognize the scp syntax as an absolute source
1534 colon = source.find(':')
1535 colon = source.find(':')
1535 if colon != -1 and '/' not in source[:colon]:
1536 if colon != -1 and '/' not in source[:colon]:
1536 return source
1537 return source
1537 self._subsource = source
1538 self._subsource = source
1538 return _abssource(self)
1539 return _abssource(self)
1539
1540
1540 def _fetch(self, source, revision):
1541 def _fetch(self, source, revision):
1541 if self._gitmissing():
1542 if self._gitmissing():
1542 source = self._abssource(source)
1543 source = self._abssource(source)
1543 self.ui.status(_('cloning subrepo %s from %s\n') %
1544 self.ui.status(_('cloning subrepo %s from %s\n') %
1544 (self._relpath, source))
1545 (self._relpath, source))
1545 self._gitnodir(['clone', source, self._abspath])
1546 self._gitnodir(['clone', source, self._abspath])
1546 if self._githavelocally(revision):
1547 if self._githavelocally(revision):
1547 return
1548 return
1548 self.ui.status(_('pulling subrepo %s from %s\n') %
1549 self.ui.status(_('pulling subrepo %s from %s\n') %
1549 (self._relpath, self._gitremote('origin')))
1550 (self._relpath, self._gitremote('origin')))
1550 # try only origin: the originally cloned repo
1551 # try only origin: the originally cloned repo
1551 self._gitcommand(['fetch'])
1552 self._gitcommand(['fetch'])
1552 if not self._githavelocally(revision):
1553 if not self._githavelocally(revision):
1553 raise error.Abort(_("revision %s does not exist in subrepo %s\n") %
1554 raise error.Abort(_("revision %s does not exist in subrepo %s\n") %
1554 (revision, self._relpath))
1555 (revision, self._relpath))
1555
1556
1556 @annotatesubrepoerror
1557 @annotatesubrepoerror
1557 def dirty(self, ignoreupdate=False):
1558 def dirty(self, ignoreupdate=False):
1558 if self._gitmissing():
1559 if self._gitmissing():
1559 return self._state[1] != ''
1560 return self._state[1] != ''
1560 if self._gitisbare():
1561 if self._gitisbare():
1561 return True
1562 return True
1562 if not ignoreupdate and self._state[1] != self._gitstate():
1563 if not ignoreupdate and self._state[1] != self._gitstate():
1563 # different version checked out
1564 # different version checked out
1564 return True
1565 return True
1565 # check for staged changes or modified files; ignore untracked files
1566 # check for staged changes or modified files; ignore untracked files
1566 self._gitupdatestat()
1567 self._gitupdatestat()
1567 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
1568 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
1568 return code == 1
1569 return code == 1
1569
1570
1570 def basestate(self):
1571 def basestate(self):
1571 return self._gitstate()
1572 return self._gitstate()
1572
1573
1573 @annotatesubrepoerror
1574 @annotatesubrepoerror
1574 def get(self, state, overwrite=False):
1575 def get(self, state, overwrite=False):
1575 source, revision, kind = state
1576 source, revision, kind = state
1576 if not revision:
1577 if not revision:
1577 self.remove()
1578 self.remove()
1578 return
1579 return
1579 self._fetch(source, revision)
1580 self._fetch(source, revision)
1580 # if the repo was set to be bare, unbare it
1581 # if the repo was set to be bare, unbare it
1581 if self._gitisbare():
1582 if self._gitisbare():
1582 self._gitcommand(['config', 'core.bare', 'false'])
1583 self._gitcommand(['config', 'core.bare', 'false'])
1583 if self._gitstate() == revision:
1584 if self._gitstate() == revision:
1584 self._gitcommand(['reset', '--hard', 'HEAD'])
1585 self._gitcommand(['reset', '--hard', 'HEAD'])
1585 return
1586 return
1586 elif self._gitstate() == revision:
1587 elif self._gitstate() == revision:
1587 if overwrite:
1588 if overwrite:
1588 # first reset the index to unmark new files for commit, because
1589 # first reset the index to unmark new files for commit, because
1589 # reset --hard will otherwise throw away files added for commit,
1590 # reset --hard will otherwise throw away files added for commit,
1590 # not just unmark them.
1591 # not just unmark them.
1591 self._gitcommand(['reset', 'HEAD'])
1592 self._gitcommand(['reset', 'HEAD'])
1592 self._gitcommand(['reset', '--hard', 'HEAD'])
1593 self._gitcommand(['reset', '--hard', 'HEAD'])
1593 return
1594 return
1594 branch2rev, rev2branch = self._gitbranchmap()
1595 branch2rev, rev2branch = self._gitbranchmap()
1595
1596
1596 def checkout(args):
1597 def checkout(args):
1597 cmd = ['checkout']
1598 cmd = ['checkout']
1598 if overwrite:
1599 if overwrite:
1599 # first reset the index to unmark new files for commit, because
1600 # first reset the index to unmark new files for commit, because
1600 # the -f option will otherwise throw away files added for
1601 # the -f option will otherwise throw away files added for
1601 # commit, not just unmark them.
1602 # commit, not just unmark them.
1602 self._gitcommand(['reset', 'HEAD'])
1603 self._gitcommand(['reset', 'HEAD'])
1603 cmd.append('-f')
1604 cmd.append('-f')
1604 self._gitcommand(cmd + args)
1605 self._gitcommand(cmd + args)
1605 _sanitize(self.ui, self.wvfs, '.git')
1606 _sanitize(self.ui, self.wvfs, '.git')
1606
1607
1607 def rawcheckout():
1608 def rawcheckout():
1608 # no branch to checkout, check it out with no branch
1609 # no branch to checkout, check it out with no branch
1609 self.ui.warn(_('checking out detached HEAD in subrepo %s\n') %
1610 self.ui.warn(_('checking out detached HEAD in subrepo %s\n') %
1610 self._relpath)
1611 self._relpath)
1611 self.ui.warn(_('check out a git branch if you intend '
1612 self.ui.warn(_('check out a git branch if you intend '
1612 'to make changes\n'))
1613 'to make changes\n'))
1613 checkout(['-q', revision])
1614 checkout(['-q', revision])
1614
1615
1615 if revision not in rev2branch:
1616 if revision not in rev2branch:
1616 rawcheckout()
1617 rawcheckout()
1617 return
1618 return
1618 branches = rev2branch[revision]
1619 branches = rev2branch[revision]
1619 firstlocalbranch = None
1620 firstlocalbranch = None
1620 for b in branches:
1621 for b in branches:
1621 if b == 'refs/heads/master':
1622 if b == 'refs/heads/master':
1622 # master trumps all other branches
1623 # master trumps all other branches
1623 checkout(['refs/heads/master'])
1624 checkout(['refs/heads/master'])
1624 return
1625 return
1625 if not firstlocalbranch and not b.startswith('refs/remotes/'):
1626 if not firstlocalbranch and not b.startswith('refs/remotes/'):
1626 firstlocalbranch = b
1627 firstlocalbranch = b
1627 if firstlocalbranch:
1628 if firstlocalbranch:
1628 checkout([firstlocalbranch])
1629 checkout([firstlocalbranch])
1629 return
1630 return
1630
1631
1631 tracking = self._gittracking(branch2rev.keys())
1632 tracking = self._gittracking(branch2rev.keys())
1632 # choose a remote branch already tracked if possible
1633 # choose a remote branch already tracked if possible
1633 remote = branches[0]
1634 remote = branches[0]
1634 if remote not in tracking:
1635 if remote not in tracking:
1635 for b in branches:
1636 for b in branches:
1636 if b in tracking:
1637 if b in tracking:
1637 remote = b
1638 remote = b
1638 break
1639 break
1639
1640
1640 if remote not in tracking:
1641 if remote not in tracking:
1641 # create a new local tracking branch
1642 # create a new local tracking branch
1642 local = remote.split('/', 3)[3]
1643 local = remote.split('/', 3)[3]
1643 checkout(['-b', local, remote])
1644 checkout(['-b', local, remote])
1644 elif self._gitisancestor(branch2rev[tracking[remote]], remote):
1645 elif self._gitisancestor(branch2rev[tracking[remote]], remote):
1645 # When updating to a tracked remote branch,
1646 # When updating to a tracked remote branch,
1646 # if the local tracking branch is downstream of it,
1647 # if the local tracking branch is downstream of it,
1647 # a normal `git pull` would have performed a "fast-forward merge"
1648 # a normal `git pull` would have performed a "fast-forward merge"
1648 # which is equivalent to updating the local branch to the remote.
1649 # which is equivalent to updating the local branch to the remote.
1649 # Since we are only looking at branching at update, we need to
1650 # Since we are only looking at branching at update, we need to
1650 # detect this situation and perform this action lazily.
1651 # detect this situation and perform this action lazily.
1651 if tracking[remote] != self._gitcurrentbranch():
1652 if tracking[remote] != self._gitcurrentbranch():
1652 checkout([tracking[remote]])
1653 checkout([tracking[remote]])
1653 self._gitcommand(['merge', '--ff', remote])
1654 self._gitcommand(['merge', '--ff', remote])
1654 _sanitize(self.ui, self.wvfs, '.git')
1655 _sanitize(self.ui, self.wvfs, '.git')
1655 else:
1656 else:
1656 # a real merge would be required, just checkout the revision
1657 # a real merge would be required, just checkout the revision
1657 rawcheckout()
1658 rawcheckout()
1658
1659
1659 @annotatesubrepoerror
1660 @annotatesubrepoerror
1660 def commit(self, text, user, date):
1661 def commit(self, text, user, date):
1661 if self._gitmissing():
1662 if self._gitmissing():
1662 raise error.Abort(_("subrepo %s is missing") % self._relpath)
1663 raise error.Abort(_("subrepo %s is missing") % self._relpath)
1663 cmd = ['commit', '-a', '-m', text]
1664 cmd = ['commit', '-a', '-m', text]
1664 env = encoding.environ.copy()
1665 env = encoding.environ.copy()
1665 if user:
1666 if user:
1666 cmd += ['--author', user]
1667 cmd += ['--author', user]
1667 if date:
1668 if date:
1668 # git's date parser silently ignores when seconds < 1e9
1669 # git's date parser silently ignores when seconds < 1e9
1669 # convert to ISO8601
1670 # convert to ISO8601
1670 env['GIT_AUTHOR_DATE'] = util.datestr(date,
1671 env['GIT_AUTHOR_DATE'] = util.datestr(date,
1671 '%Y-%m-%dT%H:%M:%S %1%2')
1672 '%Y-%m-%dT%H:%M:%S %1%2')
1672 self._gitcommand(cmd, env=env)
1673 self._gitcommand(cmd, env=env)
1673 # make sure commit works otherwise HEAD might not exist under certain
1674 # make sure commit works otherwise HEAD might not exist under certain
1674 # circumstances
1675 # circumstances
1675 return self._gitstate()
1676 return self._gitstate()
1676
1677
1677 @annotatesubrepoerror
1678 @annotatesubrepoerror
1678 def merge(self, state):
1679 def merge(self, state):
1679 source, revision, kind = state
1680 source, revision, kind = state
1680 self._fetch(source, revision)
1681 self._fetch(source, revision)
1681 base = self._gitcommand(['merge-base', revision, self._state[1]])
1682 base = self._gitcommand(['merge-base', revision, self._state[1]])
1682 self._gitupdatestat()
1683 self._gitupdatestat()
1683 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
1684 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
1684
1685
1685 def mergefunc():
1686 def mergefunc():
1686 if base == revision:
1687 if base == revision:
1687 self.get(state) # fast forward merge
1688 self.get(state) # fast forward merge
1688 elif base != self._state[1]:
1689 elif base != self._state[1]:
1689 self._gitcommand(['merge', '--no-commit', revision])
1690 self._gitcommand(['merge', '--no-commit', revision])
1690 _sanitize(self.ui, self.wvfs, '.git')
1691 _sanitize(self.ui, self.wvfs, '.git')
1691
1692
1692 if self.dirty():
1693 if self.dirty():
1693 if self._gitstate() != revision:
1694 if self._gitstate() != revision:
1694 dirty = self._gitstate() == self._state[1] or code != 0
1695 dirty = self._gitstate() == self._state[1] or code != 0
1695 if _updateprompt(self.ui, self, dirty,
1696 if _updateprompt(self.ui, self, dirty,
1696 self._state[1][:7], revision[:7]):
1697 self._state[1][:7], revision[:7]):
1697 mergefunc()
1698 mergefunc()
1698 else:
1699 else:
1699 mergefunc()
1700 mergefunc()
1700
1701
1701 @annotatesubrepoerror
1702 @annotatesubrepoerror
1702 def push(self, opts):
1703 def push(self, opts):
1703 force = opts.get('force')
1704 force = opts.get('force')
1704
1705
1705 if not self._state[1]:
1706 if not self._state[1]:
1706 return True
1707 return True
1707 if self._gitmissing():
1708 if self._gitmissing():
1708 raise error.Abort(_("subrepo %s is missing") % self._relpath)
1709 raise error.Abort(_("subrepo %s is missing") % self._relpath)
1709 # if a branch in origin contains the revision, nothing to do
1710 # if a branch in origin contains the revision, nothing to do
1710 branch2rev, rev2branch = self._gitbranchmap()
1711 branch2rev, rev2branch = self._gitbranchmap()
1711 if self._state[1] in rev2branch:
1712 if self._state[1] in rev2branch:
1712 for b in rev2branch[self._state[1]]:
1713 for b in rev2branch[self._state[1]]:
1713 if b.startswith('refs/remotes/origin/'):
1714 if b.startswith('refs/remotes/origin/'):
1714 return True
1715 return True
1715 for b, revision in branch2rev.iteritems():
1716 for b, revision in branch2rev.iteritems():
1716 if b.startswith('refs/remotes/origin/'):
1717 if b.startswith('refs/remotes/origin/'):
1717 if self._gitisancestor(self._state[1], revision):
1718 if self._gitisancestor(self._state[1], revision):
1718 return True
1719 return True
1719 # otherwise, try to push the currently checked out branch
1720 # otherwise, try to push the currently checked out branch
1720 cmd = ['push']
1721 cmd = ['push']
1721 if force:
1722 if force:
1722 cmd.append('--force')
1723 cmd.append('--force')
1723
1724
1724 current = self._gitcurrentbranch()
1725 current = self._gitcurrentbranch()
1725 if current:
1726 if current:
1726 # determine if the current branch is even useful
1727 # determine if the current branch is even useful
1727 if not self._gitisancestor(self._state[1], current):
1728 if not self._gitisancestor(self._state[1], current):
1728 self.ui.warn(_('unrelated git branch checked out '
1729 self.ui.warn(_('unrelated git branch checked out '
1729 'in subrepo %s\n') % self._relpath)
1730 'in subrepo %s\n') % self._relpath)
1730 return False
1731 return False
1731 self.ui.status(_('pushing branch %s of subrepo %s\n') %
1732 self.ui.status(_('pushing branch %s of subrepo %s\n') %
1732 (current.split('/', 2)[2], self._relpath))
1733 (current.split('/', 2)[2], self._relpath))
1733 ret = self._gitdir(cmd + ['origin', current])
1734 ret = self._gitdir(cmd + ['origin', current])
1734 return ret[1] == 0
1735 return ret[1] == 0
1735 else:
1736 else:
1736 self.ui.warn(_('no branch checked out in subrepo %s\n'
1737 self.ui.warn(_('no branch checked out in subrepo %s\n'
1737 'cannot push revision %s\n') %
1738 'cannot push revision %s\n') %
1738 (self._relpath, self._state[1]))
1739 (self._relpath, self._state[1]))
1739 return False
1740 return False
1740
1741
1741 @annotatesubrepoerror
1742 @annotatesubrepoerror
1742 def add(self, ui, match, prefix, explicitonly, **opts):
1743 def add(self, ui, match, prefix, explicitonly, **opts):
1743 if self._gitmissing():
1744 if self._gitmissing():
1744 return []
1745 return []
1745
1746
1746 (modified, added, removed,
1747 (modified, added, removed,
1747 deleted, unknown, ignored, clean) = self.status(None, unknown=True,
1748 deleted, unknown, ignored, clean) = self.status(None, unknown=True,
1748 clean=True)
1749 clean=True)
1749
1750
1750 tracked = set()
1751 tracked = set()
1751 # dirstates 'amn' warn, 'r' is added again
1752 # dirstates 'amn' warn, 'r' is added again
1752 for l in (modified, added, deleted, clean):
1753 for l in (modified, added, deleted, clean):
1753 tracked.update(l)
1754 tracked.update(l)
1754
1755
1755 # Unknown files not of interest will be rejected by the matcher
1756 # Unknown files not of interest will be rejected by the matcher
1756 files = unknown
1757 files = unknown
1757 files.extend(match.files())
1758 files.extend(match.files())
1758
1759
1759 rejected = []
1760 rejected = []
1760
1761
1761 files = [f for f in sorted(set(files)) if match(f)]
1762 files = [f for f in sorted(set(files)) if match(f)]
1762 for f in files:
1763 for f in files:
1763 exact = match.exact(f)
1764 exact = match.exact(f)
1764 command = ["add"]
1765 command = ["add"]
1765 if exact:
1766 if exact:
1766 command.append("-f") #should be added, even if ignored
1767 command.append("-f") #should be added, even if ignored
1767 if ui.verbose or not exact:
1768 if ui.verbose or not exact:
1768 ui.status(_('adding %s\n') % match.rel(f))
1769 ui.status(_('adding %s\n') % match.rel(f))
1769
1770
1770 if f in tracked: # hg prints 'adding' even if already tracked
1771 if f in tracked: # hg prints 'adding' even if already tracked
1771 if exact:
1772 if exact:
1772 rejected.append(f)
1773 rejected.append(f)
1773 continue
1774 continue
1774 if not opts.get(r'dry_run'):
1775 if not opts.get(r'dry_run'):
1775 self._gitcommand(command + [f])
1776 self._gitcommand(command + [f])
1776
1777
1777 for f in rejected:
1778 for f in rejected:
1778 ui.warn(_("%s already tracked!\n") % match.abs(f))
1779 ui.warn(_("%s already tracked!\n") % match.abs(f))
1779
1780
1780 return rejected
1781 return rejected
1781
1782
1782 @annotatesubrepoerror
1783 @annotatesubrepoerror
1783 def remove(self):
1784 def remove(self):
1784 if self._gitmissing():
1785 if self._gitmissing():
1785 return
1786 return
1786 if self.dirty():
1787 if self.dirty():
1787 self.ui.warn(_('not removing repo %s because '
1788 self.ui.warn(_('not removing repo %s because '
1788 'it has changes.\n') % self._relpath)
1789 'it has changes.\n') % self._relpath)
1789 return
1790 return
1790 # we can't fully delete the repository as it may contain
1791 # we can't fully delete the repository as it may contain
1791 # local-only history
1792 # local-only history
1792 self.ui.note(_('removing subrepo %s\n') % self._relpath)
1793 self.ui.note(_('removing subrepo %s\n') % self._relpath)
1793 self._gitcommand(['config', 'core.bare', 'true'])
1794 self._gitcommand(['config', 'core.bare', 'true'])
1794 for f, kind in self.wvfs.readdir():
1795 for f, kind in self.wvfs.readdir():
1795 if f == '.git':
1796 if f == '.git':
1796 continue
1797 continue
1797 if kind == stat.S_IFDIR:
1798 if kind == stat.S_IFDIR:
1798 self.wvfs.rmtree(f)
1799 self.wvfs.rmtree(f)
1799 else:
1800 else:
1800 self.wvfs.unlink(f)
1801 self.wvfs.unlink(f)
1801
1802
1802 def archive(self, archiver, prefix, match=None, decode=True):
1803 def archive(self, archiver, prefix, match=None, decode=True):
1803 total = 0
1804 total = 0
1804 source, revision = self._state
1805 source, revision = self._state
1805 if not revision:
1806 if not revision:
1806 return total
1807 return total
1807 self._fetch(source, revision)
1808 self._fetch(source, revision)
1808
1809
1809 # Parse git's native archive command.
1810 # Parse git's native archive command.
1810 # This should be much faster than manually traversing the trees
1811 # This should be much faster than manually traversing the trees
1811 # and objects with many subprocess calls.
1812 # and objects with many subprocess calls.
1812 tarstream = self._gitcommand(['archive', revision], stream=True)
1813 tarstream = self._gitcommand(['archive', revision], stream=True)
1813 tar = tarfile.open(fileobj=tarstream, mode='r|')
1814 tar = tarfile.open(fileobj=tarstream, mode='r|')
1814 relpath = subrelpath(self)
1815 relpath = subrelpath(self)
1815 self.ui.progress(_('archiving (%s)') % relpath, 0, unit=_('files'))
1816 self.ui.progress(_('archiving (%s)') % relpath, 0, unit=_('files'))
1816 for i, info in enumerate(tar):
1817 for i, info in enumerate(tar):
1817 if info.isdir():
1818 if info.isdir():
1818 continue
1819 continue
1819 if match and not match(info.name):
1820 if match and not match(info.name):
1820 continue
1821 continue
1821 if info.issym():
1822 if info.issym():
1822 data = info.linkname
1823 data = info.linkname
1823 else:
1824 else:
1824 data = tar.extractfile(info).read()
1825 data = tar.extractfile(info).read()
1825 archiver.addfile(prefix + self._path + '/' + info.name,
1826 archiver.addfile(prefix + self._path + '/' + info.name,
1826 info.mode, info.issym(), data)
1827 info.mode, info.issym(), data)
1827 total += 1
1828 total += 1
1828 self.ui.progress(_('archiving (%s)') % relpath, i + 1,
1829 self.ui.progress(_('archiving (%s)') % relpath, i + 1,
1829 unit=_('files'))
1830 unit=_('files'))
1830 self.ui.progress(_('archiving (%s)') % relpath, None)
1831 self.ui.progress(_('archiving (%s)') % relpath, None)
1831 return total
1832 return total
1832
1833
1833
1834
1834 @annotatesubrepoerror
1835 @annotatesubrepoerror
1835 def cat(self, match, prefix, **opts):
1836 def cat(self, match, fntemplate, prefix, **opts):
1836 rev = self._state[1]
1837 rev = self._state[1]
1837 if match.anypats():
1838 if match.anypats():
1838 return 1 #No support for include/exclude yet
1839 return 1 #No support for include/exclude yet
1839
1840
1840 if not match.files():
1841 if not match.files():
1841 return 1
1842 return 1
1842
1843
1843 for f in match.files():
1844 for f in match.files():
1844 output = self._gitcommand(["show", "%s:%s" % (rev, f)])
1845 output = self._gitcommand(["show", "%s:%s" % (rev, f)])
1845 fp = cmdutil.makefileobj(self._subparent, opts.get('output'),
1846 fp = cmdutil.makefileobj(self._subparent, fntemplate,
1846 self._ctx.node(),
1847 self._ctx.node(),
1847 pathname=self.wvfs.reljoin(prefix, f))
1848 pathname=self.wvfs.reljoin(prefix, f))
1848 fp.write(output)
1849 fp.write(output)
1849 fp.close()
1850 fp.close()
1850 return 0
1851 return 0
1851
1852
1852
1853
1853 @annotatesubrepoerror
1854 @annotatesubrepoerror
1854 def status(self, rev2, **opts):
1855 def status(self, rev2, **opts):
1855 rev1 = self._state[1]
1856 rev1 = self._state[1]
1856 if self._gitmissing() or not rev1:
1857 if self._gitmissing() or not rev1:
1857 # if the repo is missing, return no results
1858 # if the repo is missing, return no results
1858 return scmutil.status([], [], [], [], [], [], [])
1859 return scmutil.status([], [], [], [], [], [], [])
1859 modified, added, removed = [], [], []
1860 modified, added, removed = [], [], []
1860 self._gitupdatestat()
1861 self._gitupdatestat()
1861 if rev2:
1862 if rev2:
1862 command = ['diff-tree', '--no-renames', '-r', rev1, rev2]
1863 command = ['diff-tree', '--no-renames', '-r', rev1, rev2]
1863 else:
1864 else:
1864 command = ['diff-index', '--no-renames', rev1]
1865 command = ['diff-index', '--no-renames', rev1]
1865 out = self._gitcommand(command)
1866 out = self._gitcommand(command)
1866 for line in out.split('\n'):
1867 for line in out.split('\n'):
1867 tab = line.find('\t')
1868 tab = line.find('\t')
1868 if tab == -1:
1869 if tab == -1:
1869 continue
1870 continue
1870 status, f = line[tab - 1], line[tab + 1:]
1871 status, f = line[tab - 1], line[tab + 1:]
1871 if status == 'M':
1872 if status == 'M':
1872 modified.append(f)
1873 modified.append(f)
1873 elif status == 'A':
1874 elif status == 'A':
1874 added.append(f)
1875 added.append(f)
1875 elif status == 'D':
1876 elif status == 'D':
1876 removed.append(f)
1877 removed.append(f)
1877
1878
1878 deleted, unknown, ignored, clean = [], [], [], []
1879 deleted, unknown, ignored, clean = [], [], [], []
1879
1880
1880 command = ['status', '--porcelain', '-z']
1881 command = ['status', '--porcelain', '-z']
1881 if opts.get(r'unknown'):
1882 if opts.get(r'unknown'):
1882 command += ['--untracked-files=all']
1883 command += ['--untracked-files=all']
1883 if opts.get(r'ignored'):
1884 if opts.get(r'ignored'):
1884 command += ['--ignored']
1885 command += ['--ignored']
1885 out = self._gitcommand(command)
1886 out = self._gitcommand(command)
1886
1887
1887 changedfiles = set()
1888 changedfiles = set()
1888 changedfiles.update(modified)
1889 changedfiles.update(modified)
1889 changedfiles.update(added)
1890 changedfiles.update(added)
1890 changedfiles.update(removed)
1891 changedfiles.update(removed)
1891 for line in out.split('\0'):
1892 for line in out.split('\0'):
1892 if not line:
1893 if not line:
1893 continue
1894 continue
1894 st = line[0:2]
1895 st = line[0:2]
1895 #moves and copies show 2 files on one line
1896 #moves and copies show 2 files on one line
1896 if line.find('\0') >= 0:
1897 if line.find('\0') >= 0:
1897 filename1, filename2 = line[3:].split('\0')
1898 filename1, filename2 = line[3:].split('\0')
1898 else:
1899 else:
1899 filename1 = line[3:]
1900 filename1 = line[3:]
1900 filename2 = None
1901 filename2 = None
1901
1902
1902 changedfiles.add(filename1)
1903 changedfiles.add(filename1)
1903 if filename2:
1904 if filename2:
1904 changedfiles.add(filename2)
1905 changedfiles.add(filename2)
1905
1906
1906 if st == '??':
1907 if st == '??':
1907 unknown.append(filename1)
1908 unknown.append(filename1)
1908 elif st == '!!':
1909 elif st == '!!':
1909 ignored.append(filename1)
1910 ignored.append(filename1)
1910
1911
1911 if opts.get(r'clean'):
1912 if opts.get(r'clean'):
1912 out = self._gitcommand(['ls-files'])
1913 out = self._gitcommand(['ls-files'])
1913 for f in out.split('\n'):
1914 for f in out.split('\n'):
1914 if not f in changedfiles:
1915 if not f in changedfiles:
1915 clean.append(f)
1916 clean.append(f)
1916
1917
1917 return scmutil.status(modified, added, removed, deleted,
1918 return scmutil.status(modified, added, removed, deleted,
1918 unknown, ignored, clean)
1919 unknown, ignored, clean)
1919
1920
1920 @annotatesubrepoerror
1921 @annotatesubrepoerror
1921 def diff(self, ui, diffopts, node2, match, prefix, **opts):
1922 def diff(self, ui, diffopts, node2, match, prefix, **opts):
1922 node1 = self._state[1]
1923 node1 = self._state[1]
1923 cmd = ['diff', '--no-renames']
1924 cmd = ['diff', '--no-renames']
1924 if opts[r'stat']:
1925 if opts[r'stat']:
1925 cmd.append('--stat')
1926 cmd.append('--stat')
1926 else:
1927 else:
1927 # for Git, this also implies '-p'
1928 # for Git, this also implies '-p'
1928 cmd.append('-U%d' % diffopts.context)
1929 cmd.append('-U%d' % diffopts.context)
1929
1930
1930 gitprefix = self.wvfs.reljoin(prefix, self._path)
1931 gitprefix = self.wvfs.reljoin(prefix, self._path)
1931
1932
1932 if diffopts.noprefix:
1933 if diffopts.noprefix:
1933 cmd.extend(['--src-prefix=%s/' % gitprefix,
1934 cmd.extend(['--src-prefix=%s/' % gitprefix,
1934 '--dst-prefix=%s/' % gitprefix])
1935 '--dst-prefix=%s/' % gitprefix])
1935 else:
1936 else:
1936 cmd.extend(['--src-prefix=a/%s/' % gitprefix,
1937 cmd.extend(['--src-prefix=a/%s/' % gitprefix,
1937 '--dst-prefix=b/%s/' % gitprefix])
1938 '--dst-prefix=b/%s/' % gitprefix])
1938
1939
1939 if diffopts.ignorews:
1940 if diffopts.ignorews:
1940 cmd.append('--ignore-all-space')
1941 cmd.append('--ignore-all-space')
1941 if diffopts.ignorewsamount:
1942 if diffopts.ignorewsamount:
1942 cmd.append('--ignore-space-change')
1943 cmd.append('--ignore-space-change')
1943 if self._gitversion(self._gitcommand(['--version'])) >= (1, 8, 4) \
1944 if self._gitversion(self._gitcommand(['--version'])) >= (1, 8, 4) \
1944 and diffopts.ignoreblanklines:
1945 and diffopts.ignoreblanklines:
1945 cmd.append('--ignore-blank-lines')
1946 cmd.append('--ignore-blank-lines')
1946
1947
1947 cmd.append(node1)
1948 cmd.append(node1)
1948 if node2:
1949 if node2:
1949 cmd.append(node2)
1950 cmd.append(node2)
1950
1951
1951 output = ""
1952 output = ""
1952 if match.always():
1953 if match.always():
1953 output += self._gitcommand(cmd) + '\n'
1954 output += self._gitcommand(cmd) + '\n'
1954 else:
1955 else:
1955 st = self.status(node2)[:3]
1956 st = self.status(node2)[:3]
1956 files = [f for sublist in st for f in sublist]
1957 files = [f for sublist in st for f in sublist]
1957 for f in files:
1958 for f in files:
1958 if match(f):
1959 if match(f):
1959 output += self._gitcommand(cmd + ['--', f]) + '\n'
1960 output += self._gitcommand(cmd + ['--', f]) + '\n'
1960
1961
1961 if output.strip():
1962 if output.strip():
1962 ui.write(output)
1963 ui.write(output)
1963
1964
1964 @annotatesubrepoerror
1965 @annotatesubrepoerror
1965 def revert(self, substate, *pats, **opts):
1966 def revert(self, substate, *pats, **opts):
1966 self.ui.status(_('reverting subrepo %s\n') % substate[0])
1967 self.ui.status(_('reverting subrepo %s\n') % substate[0])
1967 if not opts.get(r'no_backup'):
1968 if not opts.get(r'no_backup'):
1968 status = self.status(None)
1969 status = self.status(None)
1969 names = status.modified
1970 names = status.modified
1970 for name in names:
1971 for name in names:
1971 bakname = scmutil.origpath(self.ui, self._subparent, name)
1972 bakname = scmutil.origpath(self.ui, self._subparent, name)
1972 self.ui.note(_('saving current version of %s as %s\n') %
1973 self.ui.note(_('saving current version of %s as %s\n') %
1973 (name, bakname))
1974 (name, bakname))
1974 self.wvfs.rename(name, bakname)
1975 self.wvfs.rename(name, bakname)
1975
1976
1976 if not opts.get(r'dry_run'):
1977 if not opts.get(r'dry_run'):
1977 self.get(substate, overwrite=True)
1978 self.get(substate, overwrite=True)
1978 return []
1979 return []
1979
1980
1980 def shortid(self, revid):
1981 def shortid(self, revid):
1981 return revid[:7]
1982 return revid[:7]
1982
1983
1983 types = {
1984 types = {
1984 'hg': hgsubrepo,
1985 'hg': hgsubrepo,
1985 'svn': svnsubrepo,
1986 'svn': svnsubrepo,
1986 'git': gitsubrepo,
1987 'git': gitsubrepo,
1987 }
1988 }
General Comments 0
You need to be logged in to leave comments. Login now