##// END OF EJS Templates
cat: add formatter support...
Yuya Nishihara -
r32578:746e12a7 default
parent child Browse files
Show More

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

@@ -1,3586 +1,3586 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, fntemplate, prefix, **opts):
2635 def cat(ui, repo, ctx, matcher, basefm, fntemplate, prefix, **opts):
2636 err = 1
2636 err = 1
2637
2637
2638 def write(path):
2638 def write(path):
2639 filename = None
2639 if fntemplate:
2640 if fntemplate:
2640 filename = makefilename(repo, fntemplate, ctx.node(),
2641 filename = makefilename(repo, fntemplate, ctx.node(),
2641 pathname=os.path.join(prefix, path))
2642 pathname=os.path.join(prefix, path))
2642 fp = open(filename, 'wb')
2643 with formatter.maybereopen(basefm, filename, opts) as fm:
2643 else:
2644 fp = _unclosablefile(ui.fout)
2645 with fp:
2646 data = ctx[path].data()
2644 data = ctx[path].data()
2647 if opts.get('decode'):
2645 if opts.get('decode'):
2648 data = repo.wwritedata(path, data)
2646 data = repo.wwritedata(path, data)
2649 fp.write(data)
2647 fm.startitem()
2648 fm.write('data', '%s', data)
2649 fm.data(abspath=path, path=matcher.rel(path))
2650
2650
2651 # Automation often uses hg cat on single files, so special case it
2651 # Automation often uses hg cat on single files, so special case it
2652 # for performance to avoid the cost of parsing the manifest.
2652 # for performance to avoid the cost of parsing the manifest.
2653 if len(matcher.files()) == 1 and not matcher.anypats():
2653 if len(matcher.files()) == 1 and not matcher.anypats():
2654 file = matcher.files()[0]
2654 file = matcher.files()[0]
2655 mfl = repo.manifestlog
2655 mfl = repo.manifestlog
2656 mfnode = ctx.manifestnode()
2656 mfnode = ctx.manifestnode()
2657 try:
2657 try:
2658 if mfnode and mfl[mfnode].find(file)[0]:
2658 if mfnode and mfl[mfnode].find(file)[0]:
2659 write(file)
2659 write(file)
2660 return 0
2660 return 0
2661 except KeyError:
2661 except KeyError:
2662 pass
2662 pass
2663
2663
2664 for abs in ctx.walk(matcher):
2664 for abs in ctx.walk(matcher):
2665 write(abs)
2665 write(abs)
2666 err = 0
2666 err = 0
2667
2667
2668 for subpath in sorted(ctx.substate):
2668 for subpath in sorted(ctx.substate):
2669 sub = ctx.sub(subpath)
2669 sub = ctx.sub(subpath)
2670 try:
2670 try:
2671 submatch = matchmod.subdirmatcher(subpath, matcher)
2671 submatch = matchmod.subdirmatcher(subpath, matcher)
2672
2672
2673 if not sub.cat(submatch, fntemplate,
2673 if not sub.cat(submatch, basefm, fntemplate,
2674 os.path.join(prefix, sub._path), **opts):
2674 os.path.join(prefix, sub._path), **opts):
2675 err = 0
2675 err = 0
2676 except error.RepoLookupError:
2676 except error.RepoLookupError:
2677 ui.status(_("skipping missing subrepository: %s\n")
2677 ui.status(_("skipping missing subrepository: %s\n")
2678 % os.path.join(prefix, subpath))
2678 % os.path.join(prefix, subpath))
2679
2679
2680 return err
2680 return err
2681
2681
2682 def commit(ui, repo, commitfunc, pats, opts):
2682 def commit(ui, repo, commitfunc, pats, opts):
2683 '''commit the specified files or all outstanding changes'''
2683 '''commit the specified files or all outstanding changes'''
2684 date = opts.get('date')
2684 date = opts.get('date')
2685 if date:
2685 if date:
2686 opts['date'] = util.parsedate(date)
2686 opts['date'] = util.parsedate(date)
2687 message = logmessage(ui, opts)
2687 message = logmessage(ui, opts)
2688 matcher = scmutil.match(repo[None], pats, opts)
2688 matcher = scmutil.match(repo[None], pats, opts)
2689
2689
2690 # extract addremove carefully -- this function can be called from a command
2690 # extract addremove carefully -- this function can be called from a command
2691 # that doesn't support addremove
2691 # that doesn't support addremove
2692 if opts.get('addremove'):
2692 if opts.get('addremove'):
2693 if scmutil.addremove(repo, matcher, "", opts) != 0:
2693 if scmutil.addremove(repo, matcher, "", opts) != 0:
2694 raise error.Abort(
2694 raise error.Abort(
2695 _("failed to mark all new/missing files as added/removed"))
2695 _("failed to mark all new/missing files as added/removed"))
2696
2696
2697 return commitfunc(ui, repo, message, matcher, opts)
2697 return commitfunc(ui, repo, message, matcher, opts)
2698
2698
2699 def samefile(f, ctx1, ctx2):
2699 def samefile(f, ctx1, ctx2):
2700 if f in ctx1.manifest():
2700 if f in ctx1.manifest():
2701 a = ctx1.filectx(f)
2701 a = ctx1.filectx(f)
2702 if f in ctx2.manifest():
2702 if f in ctx2.manifest():
2703 b = ctx2.filectx(f)
2703 b = ctx2.filectx(f)
2704 return (not a.cmp(b)
2704 return (not a.cmp(b)
2705 and a.flags() == b.flags())
2705 and a.flags() == b.flags())
2706 else:
2706 else:
2707 return False
2707 return False
2708 else:
2708 else:
2709 return f not in ctx2.manifest()
2709 return f not in ctx2.manifest()
2710
2710
2711 def amend(ui, repo, commitfunc, old, extra, pats, opts):
2711 def amend(ui, repo, commitfunc, old, extra, pats, opts):
2712 # avoid cycle context -> subrepo -> cmdutil
2712 # avoid cycle context -> subrepo -> cmdutil
2713 from . import context
2713 from . import context
2714
2714
2715 # amend will reuse the existing user if not specified, but the obsolete
2715 # amend will reuse the existing user if not specified, but the obsolete
2716 # marker creation requires that the current user's name is specified.
2716 # marker creation requires that the current user's name is specified.
2717 if obsolete.isenabled(repo, obsolete.createmarkersopt):
2717 if obsolete.isenabled(repo, obsolete.createmarkersopt):
2718 ui.username() # raise exception if username not set
2718 ui.username() # raise exception if username not set
2719
2719
2720 ui.note(_('amending changeset %s\n') % old)
2720 ui.note(_('amending changeset %s\n') % old)
2721 base = old.p1()
2721 base = old.p1()
2722 createmarkers = obsolete.isenabled(repo, obsolete.createmarkersopt)
2722 createmarkers = obsolete.isenabled(repo, obsolete.createmarkersopt)
2723
2723
2724 wlock = lock = newid = None
2724 wlock = lock = newid = None
2725 try:
2725 try:
2726 wlock = repo.wlock()
2726 wlock = repo.wlock()
2727 lock = repo.lock()
2727 lock = repo.lock()
2728 with repo.transaction('amend') as tr:
2728 with repo.transaction('amend') as tr:
2729 # See if we got a message from -m or -l, if not, open the editor
2729 # See if we got a message from -m or -l, if not, open the editor
2730 # with the message of the changeset to amend
2730 # with the message of the changeset to amend
2731 message = logmessage(ui, opts)
2731 message = logmessage(ui, opts)
2732 # ensure logfile does not conflict with later enforcement of the
2732 # ensure logfile does not conflict with later enforcement of the
2733 # message. potential logfile content has been processed by
2733 # message. potential logfile content has been processed by
2734 # `logmessage` anyway.
2734 # `logmessage` anyway.
2735 opts.pop('logfile')
2735 opts.pop('logfile')
2736 # First, do a regular commit to record all changes in the working
2736 # First, do a regular commit to record all changes in the working
2737 # directory (if there are any)
2737 # directory (if there are any)
2738 ui.callhooks = False
2738 ui.callhooks = False
2739 activebookmark = repo._bookmarks.active
2739 activebookmark = repo._bookmarks.active
2740 try:
2740 try:
2741 repo._bookmarks.active = None
2741 repo._bookmarks.active = None
2742 opts['message'] = 'temporary amend commit for %s' % old
2742 opts['message'] = 'temporary amend commit for %s' % old
2743 node = commit(ui, repo, commitfunc, pats, opts)
2743 node = commit(ui, repo, commitfunc, pats, opts)
2744 finally:
2744 finally:
2745 repo._bookmarks.active = activebookmark
2745 repo._bookmarks.active = activebookmark
2746 repo._bookmarks.recordchange(tr)
2746 repo._bookmarks.recordchange(tr)
2747 ui.callhooks = True
2747 ui.callhooks = True
2748 ctx = repo[node]
2748 ctx = repo[node]
2749
2749
2750 # Participating changesets:
2750 # Participating changesets:
2751 #
2751 #
2752 # node/ctx o - new (intermediate) commit that contains changes
2752 # node/ctx o - new (intermediate) commit that contains changes
2753 # | from working dir to go into amending commit
2753 # | from working dir to go into amending commit
2754 # | (or a workingctx if there were no changes)
2754 # | (or a workingctx if there were no changes)
2755 # |
2755 # |
2756 # old o - changeset to amend
2756 # old o - changeset to amend
2757 # |
2757 # |
2758 # base o - parent of amending changeset
2758 # base o - parent of amending changeset
2759
2759
2760 # Update extra dict from amended commit (e.g. to preserve graft
2760 # Update extra dict from amended commit (e.g. to preserve graft
2761 # source)
2761 # source)
2762 extra.update(old.extra())
2762 extra.update(old.extra())
2763
2763
2764 # Also update it from the intermediate commit or from the wctx
2764 # Also update it from the intermediate commit or from the wctx
2765 extra.update(ctx.extra())
2765 extra.update(ctx.extra())
2766
2766
2767 if len(old.parents()) > 1:
2767 if len(old.parents()) > 1:
2768 # ctx.files() isn't reliable for merges, so fall back to the
2768 # ctx.files() isn't reliable for merges, so fall back to the
2769 # slower repo.status() method
2769 # slower repo.status() method
2770 files = set([fn for st in repo.status(base, old)[:3]
2770 files = set([fn for st in repo.status(base, old)[:3]
2771 for fn in st])
2771 for fn in st])
2772 else:
2772 else:
2773 files = set(old.files())
2773 files = set(old.files())
2774
2774
2775 # Second, we use either the commit we just did, or if there were no
2775 # Second, we use either the commit we just did, or if there were no
2776 # changes the parent of the working directory as the version of the
2776 # changes the parent of the working directory as the version of the
2777 # files in the final amend commit
2777 # files in the final amend commit
2778 if node:
2778 if node:
2779 ui.note(_('copying changeset %s to %s\n') % (ctx, base))
2779 ui.note(_('copying changeset %s to %s\n') % (ctx, base))
2780
2780
2781 user = ctx.user()
2781 user = ctx.user()
2782 date = ctx.date()
2782 date = ctx.date()
2783 # Recompute copies (avoid recording a -> b -> a)
2783 # Recompute copies (avoid recording a -> b -> a)
2784 copied = copies.pathcopies(base, ctx)
2784 copied = copies.pathcopies(base, ctx)
2785 if old.p2:
2785 if old.p2:
2786 copied.update(copies.pathcopies(old.p2(), ctx))
2786 copied.update(copies.pathcopies(old.p2(), ctx))
2787
2787
2788 # Prune files which were reverted by the updates: if old
2788 # Prune files which were reverted by the updates: if old
2789 # introduced file X and our intermediate commit, node,
2789 # introduced file X and our intermediate commit, node,
2790 # renamed that file, then those two files are the same and
2790 # renamed that file, then those two files are the same and
2791 # we can discard X from our list of files. Likewise if X
2791 # we can discard X from our list of files. Likewise if X
2792 # was deleted, it's no longer relevant
2792 # was deleted, it's no longer relevant
2793 files.update(ctx.files())
2793 files.update(ctx.files())
2794 files = [f for f in files if not samefile(f, ctx, base)]
2794 files = [f for f in files if not samefile(f, ctx, base)]
2795
2795
2796 def filectxfn(repo, ctx_, path):
2796 def filectxfn(repo, ctx_, path):
2797 try:
2797 try:
2798 fctx = ctx[path]
2798 fctx = ctx[path]
2799 flags = fctx.flags()
2799 flags = fctx.flags()
2800 mctx = context.memfilectx(repo,
2800 mctx = context.memfilectx(repo,
2801 fctx.path(), fctx.data(),
2801 fctx.path(), fctx.data(),
2802 islink='l' in flags,
2802 islink='l' in flags,
2803 isexec='x' in flags,
2803 isexec='x' in flags,
2804 copied=copied.get(path))
2804 copied=copied.get(path))
2805 return mctx
2805 return mctx
2806 except KeyError:
2806 except KeyError:
2807 return None
2807 return None
2808 else:
2808 else:
2809 ui.note(_('copying changeset %s to %s\n') % (old, base))
2809 ui.note(_('copying changeset %s to %s\n') % (old, base))
2810
2810
2811 # Use version of files as in the old cset
2811 # Use version of files as in the old cset
2812 def filectxfn(repo, ctx_, path):
2812 def filectxfn(repo, ctx_, path):
2813 try:
2813 try:
2814 return old.filectx(path)
2814 return old.filectx(path)
2815 except KeyError:
2815 except KeyError:
2816 return None
2816 return None
2817
2817
2818 user = opts.get('user') or old.user()
2818 user = opts.get('user') or old.user()
2819 date = opts.get('date') or old.date()
2819 date = opts.get('date') or old.date()
2820 editform = mergeeditform(old, 'commit.amend')
2820 editform = mergeeditform(old, 'commit.amend')
2821 editor = getcommiteditor(editform=editform, **opts)
2821 editor = getcommiteditor(editform=editform, **opts)
2822 if not message:
2822 if not message:
2823 editor = getcommiteditor(edit=True, editform=editform)
2823 editor = getcommiteditor(edit=True, editform=editform)
2824 message = old.description()
2824 message = old.description()
2825
2825
2826 pureextra = extra.copy()
2826 pureextra = extra.copy()
2827 extra['amend_source'] = old.hex()
2827 extra['amend_source'] = old.hex()
2828
2828
2829 new = context.memctx(repo,
2829 new = context.memctx(repo,
2830 parents=[base.node(), old.p2().node()],
2830 parents=[base.node(), old.p2().node()],
2831 text=message,
2831 text=message,
2832 files=files,
2832 files=files,
2833 filectxfn=filectxfn,
2833 filectxfn=filectxfn,
2834 user=user,
2834 user=user,
2835 date=date,
2835 date=date,
2836 extra=extra,
2836 extra=extra,
2837 editor=editor)
2837 editor=editor)
2838
2838
2839 newdesc = changelog.stripdesc(new.description())
2839 newdesc = changelog.stripdesc(new.description())
2840 if ((not node)
2840 if ((not node)
2841 and newdesc == old.description()
2841 and newdesc == old.description()
2842 and user == old.user()
2842 and user == old.user()
2843 and date == old.date()
2843 and date == old.date()
2844 and pureextra == old.extra()):
2844 and pureextra == old.extra()):
2845 # nothing changed. continuing here would create a new node
2845 # nothing changed. continuing here would create a new node
2846 # anyway because of the amend_source noise.
2846 # anyway because of the amend_source noise.
2847 #
2847 #
2848 # This not what we expect from amend.
2848 # This not what we expect from amend.
2849 return old.node()
2849 return old.node()
2850
2850
2851 ph = repo.ui.config('phases', 'new-commit', phases.draft)
2851 ph = repo.ui.config('phases', 'new-commit', phases.draft)
2852 try:
2852 try:
2853 if opts.get('secret'):
2853 if opts.get('secret'):
2854 commitphase = 'secret'
2854 commitphase = 'secret'
2855 else:
2855 else:
2856 commitphase = old.phase()
2856 commitphase = old.phase()
2857 repo.ui.setconfig('phases', 'new-commit', commitphase, 'amend')
2857 repo.ui.setconfig('phases', 'new-commit', commitphase, 'amend')
2858 newid = repo.commitctx(new)
2858 newid = repo.commitctx(new)
2859 finally:
2859 finally:
2860 repo.ui.setconfig('phases', 'new-commit', ph, 'amend')
2860 repo.ui.setconfig('phases', 'new-commit', ph, 'amend')
2861 if newid != old.node():
2861 if newid != old.node():
2862 # Reroute the working copy parent to the new changeset
2862 # Reroute the working copy parent to the new changeset
2863 repo.setparents(newid, nullid)
2863 repo.setparents(newid, nullid)
2864
2864
2865 # Move bookmarks from old parent to amend commit
2865 # Move bookmarks from old parent to amend commit
2866 bms = repo.nodebookmarks(old.node())
2866 bms = repo.nodebookmarks(old.node())
2867 if bms:
2867 if bms:
2868 marks = repo._bookmarks
2868 marks = repo._bookmarks
2869 for bm in bms:
2869 for bm in bms:
2870 ui.debug('moving bookmarks %r from %s to %s\n' %
2870 ui.debug('moving bookmarks %r from %s to %s\n' %
2871 (marks, old.hex(), hex(newid)))
2871 (marks, old.hex(), hex(newid)))
2872 marks[bm] = newid
2872 marks[bm] = newid
2873 marks.recordchange(tr)
2873 marks.recordchange(tr)
2874 #commit the whole amend process
2874 #commit the whole amend process
2875 if createmarkers:
2875 if createmarkers:
2876 # mark the new changeset as successor of the rewritten one
2876 # mark the new changeset as successor of the rewritten one
2877 new = repo[newid]
2877 new = repo[newid]
2878 obs = [(old, (new,))]
2878 obs = [(old, (new,))]
2879 if node:
2879 if node:
2880 obs.append((ctx, ()))
2880 obs.append((ctx, ()))
2881
2881
2882 obsolete.createmarkers(repo, obs, operation='amend')
2882 obsolete.createmarkers(repo, obs, operation='amend')
2883 if not createmarkers and newid != old.node():
2883 if not createmarkers and newid != old.node():
2884 # Strip the intermediate commit (if there was one) and the amended
2884 # Strip the intermediate commit (if there was one) and the amended
2885 # commit
2885 # commit
2886 if node:
2886 if node:
2887 ui.note(_('stripping intermediate changeset %s\n') % ctx)
2887 ui.note(_('stripping intermediate changeset %s\n') % ctx)
2888 ui.note(_('stripping amended changeset %s\n') % old)
2888 ui.note(_('stripping amended changeset %s\n') % old)
2889 repair.strip(ui, repo, old.node(), topic='amend-backup')
2889 repair.strip(ui, repo, old.node(), topic='amend-backup')
2890 finally:
2890 finally:
2891 lockmod.release(lock, wlock)
2891 lockmod.release(lock, wlock)
2892 return newid
2892 return newid
2893
2893
2894 def commiteditor(repo, ctx, subs, editform=''):
2894 def commiteditor(repo, ctx, subs, editform=''):
2895 if ctx.description():
2895 if ctx.description():
2896 return ctx.description()
2896 return ctx.description()
2897 return commitforceeditor(repo, ctx, subs, editform=editform,
2897 return commitforceeditor(repo, ctx, subs, editform=editform,
2898 unchangedmessagedetection=True)
2898 unchangedmessagedetection=True)
2899
2899
2900 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
2900 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
2901 editform='', unchangedmessagedetection=False):
2901 editform='', unchangedmessagedetection=False):
2902 if not extramsg:
2902 if not extramsg:
2903 extramsg = _("Leave message empty to abort commit.")
2903 extramsg = _("Leave message empty to abort commit.")
2904
2904
2905 forms = [e for e in editform.split('.') if e]
2905 forms = [e for e in editform.split('.') if e]
2906 forms.insert(0, 'changeset')
2906 forms.insert(0, 'changeset')
2907 templatetext = None
2907 templatetext = None
2908 while forms:
2908 while forms:
2909 tmpl = repo.ui.config('committemplate', '.'.join(forms))
2909 tmpl = repo.ui.config('committemplate', '.'.join(forms))
2910 if tmpl:
2910 if tmpl:
2911 tmpl = templater.unquotestring(tmpl)
2911 tmpl = templater.unquotestring(tmpl)
2912 templatetext = committext = buildcommittemplate(
2912 templatetext = committext = buildcommittemplate(
2913 repo, ctx, subs, extramsg, tmpl)
2913 repo, ctx, subs, extramsg, tmpl)
2914 break
2914 break
2915 forms.pop()
2915 forms.pop()
2916 else:
2916 else:
2917 committext = buildcommittext(repo, ctx, subs, extramsg)
2917 committext = buildcommittext(repo, ctx, subs, extramsg)
2918
2918
2919 # run editor in the repository root
2919 # run editor in the repository root
2920 olddir = pycompat.getcwd()
2920 olddir = pycompat.getcwd()
2921 os.chdir(repo.root)
2921 os.chdir(repo.root)
2922
2922
2923 # make in-memory changes visible to external process
2923 # make in-memory changes visible to external process
2924 tr = repo.currenttransaction()
2924 tr = repo.currenttransaction()
2925 repo.dirstate.write(tr)
2925 repo.dirstate.write(tr)
2926 pending = tr and tr.writepending() and repo.root
2926 pending = tr and tr.writepending() and repo.root
2927
2927
2928 editortext = repo.ui.edit(committext, ctx.user(), ctx.extra(),
2928 editortext = repo.ui.edit(committext, ctx.user(), ctx.extra(),
2929 editform=editform, pending=pending,
2929 editform=editform, pending=pending,
2930 repopath=repo.path)
2930 repopath=repo.path)
2931 text = editortext
2931 text = editortext
2932
2932
2933 # strip away anything below this special string (used for editors that want
2933 # strip away anything below this special string (used for editors that want
2934 # to display the diff)
2934 # to display the diff)
2935 stripbelow = re.search(_linebelow, text, flags=re.MULTILINE)
2935 stripbelow = re.search(_linebelow, text, flags=re.MULTILINE)
2936 if stripbelow:
2936 if stripbelow:
2937 text = text[:stripbelow.start()]
2937 text = text[:stripbelow.start()]
2938
2938
2939 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
2939 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
2940 os.chdir(olddir)
2940 os.chdir(olddir)
2941
2941
2942 if finishdesc:
2942 if finishdesc:
2943 text = finishdesc(text)
2943 text = finishdesc(text)
2944 if not text.strip():
2944 if not text.strip():
2945 raise error.Abort(_("empty commit message"))
2945 raise error.Abort(_("empty commit message"))
2946 if unchangedmessagedetection and editortext == templatetext:
2946 if unchangedmessagedetection and editortext == templatetext:
2947 raise error.Abort(_("commit message unchanged"))
2947 raise error.Abort(_("commit message unchanged"))
2948
2948
2949 return text
2949 return text
2950
2950
2951 def buildcommittemplate(repo, ctx, subs, extramsg, tmpl):
2951 def buildcommittemplate(repo, ctx, subs, extramsg, tmpl):
2952 ui = repo.ui
2952 ui = repo.ui
2953 tmpl, mapfile = gettemplate(ui, tmpl, None)
2953 tmpl, mapfile = gettemplate(ui, tmpl, None)
2954
2954
2955 t = changeset_templater(ui, repo, None, {}, tmpl, mapfile, False)
2955 t = changeset_templater(ui, repo, None, {}, tmpl, mapfile, False)
2956
2956
2957 for k, v in repo.ui.configitems('committemplate'):
2957 for k, v in repo.ui.configitems('committemplate'):
2958 if k != 'changeset':
2958 if k != 'changeset':
2959 t.t.cache[k] = v
2959 t.t.cache[k] = v
2960
2960
2961 if not extramsg:
2961 if not extramsg:
2962 extramsg = '' # ensure that extramsg is string
2962 extramsg = '' # ensure that extramsg is string
2963
2963
2964 ui.pushbuffer()
2964 ui.pushbuffer()
2965 t.show(ctx, extramsg=extramsg)
2965 t.show(ctx, extramsg=extramsg)
2966 return ui.popbuffer()
2966 return ui.popbuffer()
2967
2967
2968 def hgprefix(msg):
2968 def hgprefix(msg):
2969 return "\n".join(["HG: %s" % a for a in msg.split("\n") if a])
2969 return "\n".join(["HG: %s" % a for a in msg.split("\n") if a])
2970
2970
2971 def buildcommittext(repo, ctx, subs, extramsg):
2971 def buildcommittext(repo, ctx, subs, extramsg):
2972 edittext = []
2972 edittext = []
2973 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2973 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2974 if ctx.description():
2974 if ctx.description():
2975 edittext.append(ctx.description())
2975 edittext.append(ctx.description())
2976 edittext.append("")
2976 edittext.append("")
2977 edittext.append("") # Empty line between message and comments.
2977 edittext.append("") # Empty line between message and comments.
2978 edittext.append(hgprefix(_("Enter commit message."
2978 edittext.append(hgprefix(_("Enter commit message."
2979 " Lines beginning with 'HG:' are removed.")))
2979 " Lines beginning with 'HG:' are removed.")))
2980 edittext.append(hgprefix(extramsg))
2980 edittext.append(hgprefix(extramsg))
2981 edittext.append("HG: --")
2981 edittext.append("HG: --")
2982 edittext.append(hgprefix(_("user: %s") % ctx.user()))
2982 edittext.append(hgprefix(_("user: %s") % ctx.user()))
2983 if ctx.p2():
2983 if ctx.p2():
2984 edittext.append(hgprefix(_("branch merge")))
2984 edittext.append(hgprefix(_("branch merge")))
2985 if ctx.branch():
2985 if ctx.branch():
2986 edittext.append(hgprefix(_("branch '%s'") % ctx.branch()))
2986 edittext.append(hgprefix(_("branch '%s'") % ctx.branch()))
2987 if bookmarks.isactivewdirparent(repo):
2987 if bookmarks.isactivewdirparent(repo):
2988 edittext.append(hgprefix(_("bookmark '%s'") % repo._activebookmark))
2988 edittext.append(hgprefix(_("bookmark '%s'") % repo._activebookmark))
2989 edittext.extend([hgprefix(_("subrepo %s") % s) for s in subs])
2989 edittext.extend([hgprefix(_("subrepo %s") % s) for s in subs])
2990 edittext.extend([hgprefix(_("added %s") % f) for f in added])
2990 edittext.extend([hgprefix(_("added %s") % f) for f in added])
2991 edittext.extend([hgprefix(_("changed %s") % f) for f in modified])
2991 edittext.extend([hgprefix(_("changed %s") % f) for f in modified])
2992 edittext.extend([hgprefix(_("removed %s") % f) for f in removed])
2992 edittext.extend([hgprefix(_("removed %s") % f) for f in removed])
2993 if not added and not modified and not removed:
2993 if not added and not modified and not removed:
2994 edittext.append(hgprefix(_("no files changed")))
2994 edittext.append(hgprefix(_("no files changed")))
2995 edittext.append("")
2995 edittext.append("")
2996
2996
2997 return "\n".join(edittext)
2997 return "\n".join(edittext)
2998
2998
2999 def commitstatus(repo, node, branch, bheads=None, opts=None):
2999 def commitstatus(repo, node, branch, bheads=None, opts=None):
3000 if opts is None:
3000 if opts is None:
3001 opts = {}
3001 opts = {}
3002 ctx = repo[node]
3002 ctx = repo[node]
3003 parents = ctx.parents()
3003 parents = ctx.parents()
3004
3004
3005 if (not opts.get('amend') and bheads and node not in bheads and not
3005 if (not opts.get('amend') and bheads and node not in bheads and not
3006 [x for x in parents if x.node() in bheads and x.branch() == branch]):
3006 [x for x in parents if x.node() in bheads and x.branch() == branch]):
3007 repo.ui.status(_('created new head\n'))
3007 repo.ui.status(_('created new head\n'))
3008 # The message is not printed for initial roots. For the other
3008 # The message is not printed for initial roots. For the other
3009 # changesets, it is printed in the following situations:
3009 # changesets, it is printed in the following situations:
3010 #
3010 #
3011 # Par column: for the 2 parents with ...
3011 # Par column: for the 2 parents with ...
3012 # N: null or no parent
3012 # N: null or no parent
3013 # B: parent is on another named branch
3013 # B: parent is on another named branch
3014 # C: parent is a regular non head changeset
3014 # C: parent is a regular non head changeset
3015 # H: parent was a branch head of the current branch
3015 # H: parent was a branch head of the current branch
3016 # Msg column: whether we print "created new head" message
3016 # Msg column: whether we print "created new head" message
3017 # In the following, it is assumed that there already exists some
3017 # In the following, it is assumed that there already exists some
3018 # initial branch heads of the current branch, otherwise nothing is
3018 # initial branch heads of the current branch, otherwise nothing is
3019 # printed anyway.
3019 # printed anyway.
3020 #
3020 #
3021 # Par Msg Comment
3021 # Par Msg Comment
3022 # N N y additional topo root
3022 # N N y additional topo root
3023 #
3023 #
3024 # B N y additional branch root
3024 # B N y additional branch root
3025 # C N y additional topo head
3025 # C N y additional topo head
3026 # H N n usual case
3026 # H N n usual case
3027 #
3027 #
3028 # B B y weird additional branch root
3028 # B B y weird additional branch root
3029 # C B y branch merge
3029 # C B y branch merge
3030 # H B n merge with named branch
3030 # H B n merge with named branch
3031 #
3031 #
3032 # C C y additional head from merge
3032 # C C y additional head from merge
3033 # C H n merge with a head
3033 # C H n merge with a head
3034 #
3034 #
3035 # H H n head merge: head count decreases
3035 # H H n head merge: head count decreases
3036
3036
3037 if not opts.get('close_branch'):
3037 if not opts.get('close_branch'):
3038 for r in parents:
3038 for r in parents:
3039 if r.closesbranch() and r.branch() == branch:
3039 if r.closesbranch() and r.branch() == branch:
3040 repo.ui.status(_('reopening closed branch head %d\n') % r)
3040 repo.ui.status(_('reopening closed branch head %d\n') % r)
3041
3041
3042 if repo.ui.debugflag:
3042 if repo.ui.debugflag:
3043 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
3043 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
3044 elif repo.ui.verbose:
3044 elif repo.ui.verbose:
3045 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
3045 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
3046
3046
3047 def postcommitstatus(repo, pats, opts):
3047 def postcommitstatus(repo, pats, opts):
3048 return repo.status(match=scmutil.match(repo[None], pats, opts))
3048 return repo.status(match=scmutil.match(repo[None], pats, opts))
3049
3049
3050 def revert(ui, repo, ctx, parents, *pats, **opts):
3050 def revert(ui, repo, ctx, parents, *pats, **opts):
3051 parent, p2 = parents
3051 parent, p2 = parents
3052 node = ctx.node()
3052 node = ctx.node()
3053
3053
3054 mf = ctx.manifest()
3054 mf = ctx.manifest()
3055 if node == p2:
3055 if node == p2:
3056 parent = p2
3056 parent = p2
3057
3057
3058 # need all matching names in dirstate and manifest of target rev,
3058 # need all matching names in dirstate and manifest of target rev,
3059 # so have to walk both. do not print errors if files exist in one
3059 # so have to walk both. do not print errors if files exist in one
3060 # but not other. in both cases, filesets should be evaluated against
3060 # but not other. in both cases, filesets should be evaluated against
3061 # workingctx to get consistent result (issue4497). this means 'set:**'
3061 # workingctx to get consistent result (issue4497). this means 'set:**'
3062 # cannot be used to select missing files from target rev.
3062 # cannot be used to select missing files from target rev.
3063
3063
3064 # `names` is a mapping for all elements in working copy and target revision
3064 # `names` is a mapping for all elements in working copy and target revision
3065 # The mapping is in the form:
3065 # The mapping is in the form:
3066 # <asb path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
3066 # <asb path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
3067 names = {}
3067 names = {}
3068
3068
3069 with repo.wlock():
3069 with repo.wlock():
3070 ## filling of the `names` mapping
3070 ## filling of the `names` mapping
3071 # walk dirstate to fill `names`
3071 # walk dirstate to fill `names`
3072
3072
3073 interactive = opts.get('interactive', False)
3073 interactive = opts.get('interactive', False)
3074 wctx = repo[None]
3074 wctx = repo[None]
3075 m = scmutil.match(wctx, pats, opts)
3075 m = scmutil.match(wctx, pats, opts)
3076
3076
3077 # we'll need this later
3077 # we'll need this later
3078 targetsubs = sorted(s for s in wctx.substate if m(s))
3078 targetsubs = sorted(s for s in wctx.substate if m(s))
3079
3079
3080 if not m.always():
3080 if not m.always():
3081 matcher = matchmod.badmatch(m, lambda x, y: False)
3081 matcher = matchmod.badmatch(m, lambda x, y: False)
3082 for abs in wctx.walk(matcher):
3082 for abs in wctx.walk(matcher):
3083 names[abs] = m.rel(abs), m.exact(abs)
3083 names[abs] = m.rel(abs), m.exact(abs)
3084
3084
3085 # walk target manifest to fill `names`
3085 # walk target manifest to fill `names`
3086
3086
3087 def badfn(path, msg):
3087 def badfn(path, msg):
3088 if path in names:
3088 if path in names:
3089 return
3089 return
3090 if path in ctx.substate:
3090 if path in ctx.substate:
3091 return
3091 return
3092 path_ = path + '/'
3092 path_ = path + '/'
3093 for f in names:
3093 for f in names:
3094 if f.startswith(path_):
3094 if f.startswith(path_):
3095 return
3095 return
3096 ui.warn("%s: %s\n" % (m.rel(path), msg))
3096 ui.warn("%s: %s\n" % (m.rel(path), msg))
3097
3097
3098 for abs in ctx.walk(matchmod.badmatch(m, badfn)):
3098 for abs in ctx.walk(matchmod.badmatch(m, badfn)):
3099 if abs not in names:
3099 if abs not in names:
3100 names[abs] = m.rel(abs), m.exact(abs)
3100 names[abs] = m.rel(abs), m.exact(abs)
3101
3101
3102 # Find status of all file in `names`.
3102 # Find status of all file in `names`.
3103 m = scmutil.matchfiles(repo, names)
3103 m = scmutil.matchfiles(repo, names)
3104
3104
3105 changes = repo.status(node1=node, match=m,
3105 changes = repo.status(node1=node, match=m,
3106 unknown=True, ignored=True, clean=True)
3106 unknown=True, ignored=True, clean=True)
3107 else:
3107 else:
3108 changes = repo.status(node1=node, match=m)
3108 changes = repo.status(node1=node, match=m)
3109 for kind in changes:
3109 for kind in changes:
3110 for abs in kind:
3110 for abs in kind:
3111 names[abs] = m.rel(abs), m.exact(abs)
3111 names[abs] = m.rel(abs), m.exact(abs)
3112
3112
3113 m = scmutil.matchfiles(repo, names)
3113 m = scmutil.matchfiles(repo, names)
3114
3114
3115 modified = set(changes.modified)
3115 modified = set(changes.modified)
3116 added = set(changes.added)
3116 added = set(changes.added)
3117 removed = set(changes.removed)
3117 removed = set(changes.removed)
3118 _deleted = set(changes.deleted)
3118 _deleted = set(changes.deleted)
3119 unknown = set(changes.unknown)
3119 unknown = set(changes.unknown)
3120 unknown.update(changes.ignored)
3120 unknown.update(changes.ignored)
3121 clean = set(changes.clean)
3121 clean = set(changes.clean)
3122 modadded = set()
3122 modadded = set()
3123
3123
3124 # We need to account for the state of the file in the dirstate,
3124 # We need to account for the state of the file in the dirstate,
3125 # even when we revert against something else than parent. This will
3125 # even when we revert against something else than parent. This will
3126 # slightly alter the behavior of revert (doing back up or not, delete
3126 # slightly alter the behavior of revert (doing back up or not, delete
3127 # or just forget etc).
3127 # or just forget etc).
3128 if parent == node:
3128 if parent == node:
3129 dsmodified = modified
3129 dsmodified = modified
3130 dsadded = added
3130 dsadded = added
3131 dsremoved = removed
3131 dsremoved = removed
3132 # store all local modifications, useful later for rename detection
3132 # store all local modifications, useful later for rename detection
3133 localchanges = dsmodified | dsadded
3133 localchanges = dsmodified | dsadded
3134 modified, added, removed = set(), set(), set()
3134 modified, added, removed = set(), set(), set()
3135 else:
3135 else:
3136 changes = repo.status(node1=parent, match=m)
3136 changes = repo.status(node1=parent, match=m)
3137 dsmodified = set(changes.modified)
3137 dsmodified = set(changes.modified)
3138 dsadded = set(changes.added)
3138 dsadded = set(changes.added)
3139 dsremoved = set(changes.removed)
3139 dsremoved = set(changes.removed)
3140 # store all local modifications, useful later for rename detection
3140 # store all local modifications, useful later for rename detection
3141 localchanges = dsmodified | dsadded
3141 localchanges = dsmodified | dsadded
3142
3142
3143 # only take into account for removes between wc and target
3143 # only take into account for removes between wc and target
3144 clean |= dsremoved - removed
3144 clean |= dsremoved - removed
3145 dsremoved &= removed
3145 dsremoved &= removed
3146 # distinct between dirstate remove and other
3146 # distinct between dirstate remove and other
3147 removed -= dsremoved
3147 removed -= dsremoved
3148
3148
3149 modadded = added & dsmodified
3149 modadded = added & dsmodified
3150 added -= modadded
3150 added -= modadded
3151
3151
3152 # tell newly modified apart.
3152 # tell newly modified apart.
3153 dsmodified &= modified
3153 dsmodified &= modified
3154 dsmodified |= modified & dsadded # dirstate added may need backup
3154 dsmodified |= modified & dsadded # dirstate added may need backup
3155 modified -= dsmodified
3155 modified -= dsmodified
3156
3156
3157 # We need to wait for some post-processing to update this set
3157 # We need to wait for some post-processing to update this set
3158 # before making the distinction. The dirstate will be used for
3158 # before making the distinction. The dirstate will be used for
3159 # that purpose.
3159 # that purpose.
3160 dsadded = added
3160 dsadded = added
3161
3161
3162 # in case of merge, files that are actually added can be reported as
3162 # in case of merge, files that are actually added can be reported as
3163 # modified, we need to post process the result
3163 # modified, we need to post process the result
3164 if p2 != nullid:
3164 if p2 != nullid:
3165 mergeadd = set(dsmodified)
3165 mergeadd = set(dsmodified)
3166 for path in dsmodified:
3166 for path in dsmodified:
3167 if path in mf:
3167 if path in mf:
3168 mergeadd.remove(path)
3168 mergeadd.remove(path)
3169 dsadded |= mergeadd
3169 dsadded |= mergeadd
3170 dsmodified -= mergeadd
3170 dsmodified -= mergeadd
3171
3171
3172 # if f is a rename, update `names` to also revert the source
3172 # if f is a rename, update `names` to also revert the source
3173 cwd = repo.getcwd()
3173 cwd = repo.getcwd()
3174 for f in localchanges:
3174 for f in localchanges:
3175 src = repo.dirstate.copied(f)
3175 src = repo.dirstate.copied(f)
3176 # XXX should we check for rename down to target node?
3176 # XXX should we check for rename down to target node?
3177 if src and src not in names and repo.dirstate[src] == 'r':
3177 if src and src not in names and repo.dirstate[src] == 'r':
3178 dsremoved.add(src)
3178 dsremoved.add(src)
3179 names[src] = (repo.pathto(src, cwd), True)
3179 names[src] = (repo.pathto(src, cwd), True)
3180
3180
3181 # determine the exact nature of the deleted changesets
3181 # determine the exact nature of the deleted changesets
3182 deladded = set(_deleted)
3182 deladded = set(_deleted)
3183 for path in _deleted:
3183 for path in _deleted:
3184 if path in mf:
3184 if path in mf:
3185 deladded.remove(path)
3185 deladded.remove(path)
3186 deleted = _deleted - deladded
3186 deleted = _deleted - deladded
3187
3187
3188 # distinguish between file to forget and the other
3188 # distinguish between file to forget and the other
3189 added = set()
3189 added = set()
3190 for abs in dsadded:
3190 for abs in dsadded:
3191 if repo.dirstate[abs] != 'a':
3191 if repo.dirstate[abs] != 'a':
3192 added.add(abs)
3192 added.add(abs)
3193 dsadded -= added
3193 dsadded -= added
3194
3194
3195 for abs in deladded:
3195 for abs in deladded:
3196 if repo.dirstate[abs] == 'a':
3196 if repo.dirstate[abs] == 'a':
3197 dsadded.add(abs)
3197 dsadded.add(abs)
3198 deladded -= dsadded
3198 deladded -= dsadded
3199
3199
3200 # For files marked as removed, we check if an unknown file is present at
3200 # For files marked as removed, we check if an unknown file is present at
3201 # the same path. If a such file exists it may need to be backed up.
3201 # the same path. If a such file exists it may need to be backed up.
3202 # Making the distinction at this stage helps have simpler backup
3202 # Making the distinction at this stage helps have simpler backup
3203 # logic.
3203 # logic.
3204 removunk = set()
3204 removunk = set()
3205 for abs in removed:
3205 for abs in removed:
3206 target = repo.wjoin(abs)
3206 target = repo.wjoin(abs)
3207 if os.path.lexists(target):
3207 if os.path.lexists(target):
3208 removunk.add(abs)
3208 removunk.add(abs)
3209 removed -= removunk
3209 removed -= removunk
3210
3210
3211 dsremovunk = set()
3211 dsremovunk = set()
3212 for abs in dsremoved:
3212 for abs in dsremoved:
3213 target = repo.wjoin(abs)
3213 target = repo.wjoin(abs)
3214 if os.path.lexists(target):
3214 if os.path.lexists(target):
3215 dsremovunk.add(abs)
3215 dsremovunk.add(abs)
3216 dsremoved -= dsremovunk
3216 dsremoved -= dsremovunk
3217
3217
3218 # action to be actually performed by revert
3218 # action to be actually performed by revert
3219 # (<list of file>, message>) tuple
3219 # (<list of file>, message>) tuple
3220 actions = {'revert': ([], _('reverting %s\n')),
3220 actions = {'revert': ([], _('reverting %s\n')),
3221 'add': ([], _('adding %s\n')),
3221 'add': ([], _('adding %s\n')),
3222 'remove': ([], _('removing %s\n')),
3222 'remove': ([], _('removing %s\n')),
3223 'drop': ([], _('removing %s\n')),
3223 'drop': ([], _('removing %s\n')),
3224 'forget': ([], _('forgetting %s\n')),
3224 'forget': ([], _('forgetting %s\n')),
3225 'undelete': ([], _('undeleting %s\n')),
3225 'undelete': ([], _('undeleting %s\n')),
3226 'noop': (None, _('no changes needed to %s\n')),
3226 'noop': (None, _('no changes needed to %s\n')),
3227 'unknown': (None, _('file not managed: %s\n')),
3227 'unknown': (None, _('file not managed: %s\n')),
3228 }
3228 }
3229
3229
3230 # "constant" that convey the backup strategy.
3230 # "constant" that convey the backup strategy.
3231 # All set to `discard` if `no-backup` is set do avoid checking
3231 # All set to `discard` if `no-backup` is set do avoid checking
3232 # no_backup lower in the code.
3232 # no_backup lower in the code.
3233 # These values are ordered for comparison purposes
3233 # These values are ordered for comparison purposes
3234 backupinteractive = 3 # do backup if interactively modified
3234 backupinteractive = 3 # do backup if interactively modified
3235 backup = 2 # unconditionally do backup
3235 backup = 2 # unconditionally do backup
3236 check = 1 # check if the existing file differs from target
3236 check = 1 # check if the existing file differs from target
3237 discard = 0 # never do backup
3237 discard = 0 # never do backup
3238 if opts.get('no_backup'):
3238 if opts.get('no_backup'):
3239 backupinteractive = backup = check = discard
3239 backupinteractive = backup = check = discard
3240 if interactive:
3240 if interactive:
3241 dsmodifiedbackup = backupinteractive
3241 dsmodifiedbackup = backupinteractive
3242 else:
3242 else:
3243 dsmodifiedbackup = backup
3243 dsmodifiedbackup = backup
3244 tobackup = set()
3244 tobackup = set()
3245
3245
3246 backupanddel = actions['remove']
3246 backupanddel = actions['remove']
3247 if not opts.get('no_backup'):
3247 if not opts.get('no_backup'):
3248 backupanddel = actions['drop']
3248 backupanddel = actions['drop']
3249
3249
3250 disptable = (
3250 disptable = (
3251 # dispatch table:
3251 # dispatch table:
3252 # file state
3252 # file state
3253 # action
3253 # action
3254 # make backup
3254 # make backup
3255
3255
3256 ## Sets that results that will change file on disk
3256 ## Sets that results that will change file on disk
3257 # Modified compared to target, no local change
3257 # Modified compared to target, no local change
3258 (modified, actions['revert'], discard),
3258 (modified, actions['revert'], discard),
3259 # Modified compared to target, but local file is deleted
3259 # Modified compared to target, but local file is deleted
3260 (deleted, actions['revert'], discard),
3260 (deleted, actions['revert'], discard),
3261 # Modified compared to target, local change
3261 # Modified compared to target, local change
3262 (dsmodified, actions['revert'], dsmodifiedbackup),
3262 (dsmodified, actions['revert'], dsmodifiedbackup),
3263 # Added since target
3263 # Added since target
3264 (added, actions['remove'], discard),
3264 (added, actions['remove'], discard),
3265 # Added in working directory
3265 # Added in working directory
3266 (dsadded, actions['forget'], discard),
3266 (dsadded, actions['forget'], discard),
3267 # Added since target, have local modification
3267 # Added since target, have local modification
3268 (modadded, backupanddel, backup),
3268 (modadded, backupanddel, backup),
3269 # Added since target but file is missing in working directory
3269 # Added since target but file is missing in working directory
3270 (deladded, actions['drop'], discard),
3270 (deladded, actions['drop'], discard),
3271 # Removed since target, before working copy parent
3271 # Removed since target, before working copy parent
3272 (removed, actions['add'], discard),
3272 (removed, actions['add'], discard),
3273 # Same as `removed` but an unknown file exists at the same path
3273 # Same as `removed` but an unknown file exists at the same path
3274 (removunk, actions['add'], check),
3274 (removunk, actions['add'], check),
3275 # Removed since targe, marked as such in working copy parent
3275 # Removed since targe, marked as such in working copy parent
3276 (dsremoved, actions['undelete'], discard),
3276 (dsremoved, actions['undelete'], discard),
3277 # Same as `dsremoved` but an unknown file exists at the same path
3277 # Same as `dsremoved` but an unknown file exists at the same path
3278 (dsremovunk, actions['undelete'], check),
3278 (dsremovunk, actions['undelete'], check),
3279 ## the following sets does not result in any file changes
3279 ## the following sets does not result in any file changes
3280 # File with no modification
3280 # File with no modification
3281 (clean, actions['noop'], discard),
3281 (clean, actions['noop'], discard),
3282 # Existing file, not tracked anywhere
3282 # Existing file, not tracked anywhere
3283 (unknown, actions['unknown'], discard),
3283 (unknown, actions['unknown'], discard),
3284 )
3284 )
3285
3285
3286 for abs, (rel, exact) in sorted(names.items()):
3286 for abs, (rel, exact) in sorted(names.items()):
3287 # target file to be touch on disk (relative to cwd)
3287 # target file to be touch on disk (relative to cwd)
3288 target = repo.wjoin(abs)
3288 target = repo.wjoin(abs)
3289 # search the entry in the dispatch table.
3289 # search the entry in the dispatch table.
3290 # if the file is in any of these sets, it was touched in the working
3290 # if the file is in any of these sets, it was touched in the working
3291 # directory parent and we are sure it needs to be reverted.
3291 # directory parent and we are sure it needs to be reverted.
3292 for table, (xlist, msg), dobackup in disptable:
3292 for table, (xlist, msg), dobackup in disptable:
3293 if abs not in table:
3293 if abs not in table:
3294 continue
3294 continue
3295 if xlist is not None:
3295 if xlist is not None:
3296 xlist.append(abs)
3296 xlist.append(abs)
3297 if dobackup:
3297 if dobackup:
3298 # If in interactive mode, don't automatically create
3298 # If in interactive mode, don't automatically create
3299 # .orig files (issue4793)
3299 # .orig files (issue4793)
3300 if dobackup == backupinteractive:
3300 if dobackup == backupinteractive:
3301 tobackup.add(abs)
3301 tobackup.add(abs)
3302 elif (backup <= dobackup or wctx[abs].cmp(ctx[abs])):
3302 elif (backup <= dobackup or wctx[abs].cmp(ctx[abs])):
3303 bakname = scmutil.origpath(ui, repo, rel)
3303 bakname = scmutil.origpath(ui, repo, rel)
3304 ui.note(_('saving current version of %s as %s\n') %
3304 ui.note(_('saving current version of %s as %s\n') %
3305 (rel, bakname))
3305 (rel, bakname))
3306 if not opts.get('dry_run'):
3306 if not opts.get('dry_run'):
3307 if interactive:
3307 if interactive:
3308 util.copyfile(target, bakname)
3308 util.copyfile(target, bakname)
3309 else:
3309 else:
3310 util.rename(target, bakname)
3310 util.rename(target, bakname)
3311 if ui.verbose or not exact:
3311 if ui.verbose or not exact:
3312 if not isinstance(msg, basestring):
3312 if not isinstance(msg, basestring):
3313 msg = msg(abs)
3313 msg = msg(abs)
3314 ui.status(msg % rel)
3314 ui.status(msg % rel)
3315 elif exact:
3315 elif exact:
3316 ui.warn(msg % rel)
3316 ui.warn(msg % rel)
3317 break
3317 break
3318
3318
3319 if not opts.get('dry_run'):
3319 if not opts.get('dry_run'):
3320 needdata = ('revert', 'add', 'undelete')
3320 needdata = ('revert', 'add', 'undelete')
3321 _revertprefetch(repo, ctx, *[actions[name][0] for name in needdata])
3321 _revertprefetch(repo, ctx, *[actions[name][0] for name in needdata])
3322 _performrevert(repo, parents, ctx, actions, interactive, tobackup)
3322 _performrevert(repo, parents, ctx, actions, interactive, tobackup)
3323
3323
3324 if targetsubs:
3324 if targetsubs:
3325 # Revert the subrepos on the revert list
3325 # Revert the subrepos on the revert list
3326 for sub in targetsubs:
3326 for sub in targetsubs:
3327 try:
3327 try:
3328 wctx.sub(sub).revert(ctx.substate[sub], *pats, **opts)
3328 wctx.sub(sub).revert(ctx.substate[sub], *pats, **opts)
3329 except KeyError:
3329 except KeyError:
3330 raise error.Abort("subrepository '%s' does not exist in %s!"
3330 raise error.Abort("subrepository '%s' does not exist in %s!"
3331 % (sub, short(ctx.node())))
3331 % (sub, short(ctx.node())))
3332
3332
3333 def _revertprefetch(repo, ctx, *files):
3333 def _revertprefetch(repo, ctx, *files):
3334 """Let extension changing the storage layer prefetch content"""
3334 """Let extension changing the storage layer prefetch content"""
3335 pass
3335 pass
3336
3336
3337 def _performrevert(repo, parents, ctx, actions, interactive=False,
3337 def _performrevert(repo, parents, ctx, actions, interactive=False,
3338 tobackup=None):
3338 tobackup=None):
3339 """function that actually perform all the actions computed for revert
3339 """function that actually perform all the actions computed for revert
3340
3340
3341 This is an independent function to let extension to plug in and react to
3341 This is an independent function to let extension to plug in and react to
3342 the imminent revert.
3342 the imminent revert.
3343
3343
3344 Make sure you have the working directory locked when calling this function.
3344 Make sure you have the working directory locked when calling this function.
3345 """
3345 """
3346 parent, p2 = parents
3346 parent, p2 = parents
3347 node = ctx.node()
3347 node = ctx.node()
3348 excluded_files = []
3348 excluded_files = []
3349 matcher_opts = {"exclude": excluded_files}
3349 matcher_opts = {"exclude": excluded_files}
3350
3350
3351 def checkout(f):
3351 def checkout(f):
3352 fc = ctx[f]
3352 fc = ctx[f]
3353 repo.wwrite(f, fc.data(), fc.flags())
3353 repo.wwrite(f, fc.data(), fc.flags())
3354
3354
3355 def doremove(f):
3355 def doremove(f):
3356 try:
3356 try:
3357 repo.wvfs.unlinkpath(f)
3357 repo.wvfs.unlinkpath(f)
3358 except OSError:
3358 except OSError:
3359 pass
3359 pass
3360 repo.dirstate.remove(f)
3360 repo.dirstate.remove(f)
3361
3361
3362 audit_path = pathutil.pathauditor(repo.root)
3362 audit_path = pathutil.pathauditor(repo.root)
3363 for f in actions['forget'][0]:
3363 for f in actions['forget'][0]:
3364 if interactive:
3364 if interactive:
3365 choice = repo.ui.promptchoice(
3365 choice = repo.ui.promptchoice(
3366 _("forget added file %s (Yn)?$$ &Yes $$ &No") % f)
3366 _("forget added file %s (Yn)?$$ &Yes $$ &No") % f)
3367 if choice == 0:
3367 if choice == 0:
3368 repo.dirstate.drop(f)
3368 repo.dirstate.drop(f)
3369 else:
3369 else:
3370 excluded_files.append(repo.wjoin(f))
3370 excluded_files.append(repo.wjoin(f))
3371 else:
3371 else:
3372 repo.dirstate.drop(f)
3372 repo.dirstate.drop(f)
3373 for f in actions['remove'][0]:
3373 for f in actions['remove'][0]:
3374 audit_path(f)
3374 audit_path(f)
3375 if interactive:
3375 if interactive:
3376 choice = repo.ui.promptchoice(
3376 choice = repo.ui.promptchoice(
3377 _("remove added file %s (Yn)?$$ &Yes $$ &No") % f)
3377 _("remove added file %s (Yn)?$$ &Yes $$ &No") % f)
3378 if choice == 0:
3378 if choice == 0:
3379 doremove(f)
3379 doremove(f)
3380 else:
3380 else:
3381 excluded_files.append(repo.wjoin(f))
3381 excluded_files.append(repo.wjoin(f))
3382 else:
3382 else:
3383 doremove(f)
3383 doremove(f)
3384 for f in actions['drop'][0]:
3384 for f in actions['drop'][0]:
3385 audit_path(f)
3385 audit_path(f)
3386 repo.dirstate.remove(f)
3386 repo.dirstate.remove(f)
3387
3387
3388 normal = None
3388 normal = None
3389 if node == parent:
3389 if node == parent:
3390 # We're reverting to our parent. If possible, we'd like status
3390 # We're reverting to our parent. If possible, we'd like status
3391 # to report the file as clean. We have to use normallookup for
3391 # to report the file as clean. We have to use normallookup for
3392 # merges to avoid losing information about merged/dirty files.
3392 # merges to avoid losing information about merged/dirty files.
3393 if p2 != nullid:
3393 if p2 != nullid:
3394 normal = repo.dirstate.normallookup
3394 normal = repo.dirstate.normallookup
3395 else:
3395 else:
3396 normal = repo.dirstate.normal
3396 normal = repo.dirstate.normal
3397
3397
3398 newlyaddedandmodifiedfiles = set()
3398 newlyaddedandmodifiedfiles = set()
3399 if interactive:
3399 if interactive:
3400 # Prompt the user for changes to revert
3400 # Prompt the user for changes to revert
3401 torevert = [repo.wjoin(f) for f in actions['revert'][0]]
3401 torevert = [repo.wjoin(f) for f in actions['revert'][0]]
3402 m = scmutil.match(ctx, torevert, matcher_opts)
3402 m = scmutil.match(ctx, torevert, matcher_opts)
3403 diffopts = patch.difffeatureopts(repo.ui, whitespace=True)
3403 diffopts = patch.difffeatureopts(repo.ui, whitespace=True)
3404 diffopts.nodates = True
3404 diffopts.nodates = True
3405 diffopts.git = True
3405 diffopts.git = True
3406 operation = 'discard'
3406 operation = 'discard'
3407 reversehunks = True
3407 reversehunks = True
3408 if node != parent:
3408 if node != parent:
3409 operation = 'revert'
3409 operation = 'revert'
3410 reversehunks = repo.ui.configbool('experimental',
3410 reversehunks = repo.ui.configbool('experimental',
3411 'revertalternateinteractivemode',
3411 'revertalternateinteractivemode',
3412 True)
3412 True)
3413 if reversehunks:
3413 if reversehunks:
3414 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
3414 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
3415 else:
3415 else:
3416 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
3416 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
3417 originalchunks = patch.parsepatch(diff)
3417 originalchunks = patch.parsepatch(diff)
3418
3418
3419 try:
3419 try:
3420
3420
3421 chunks, opts = recordfilter(repo.ui, originalchunks,
3421 chunks, opts = recordfilter(repo.ui, originalchunks,
3422 operation=operation)
3422 operation=operation)
3423 if reversehunks:
3423 if reversehunks:
3424 chunks = patch.reversehunks(chunks)
3424 chunks = patch.reversehunks(chunks)
3425
3425
3426 except patch.PatchError as err:
3426 except patch.PatchError as err:
3427 raise error.Abort(_('error parsing patch: %s') % err)
3427 raise error.Abort(_('error parsing patch: %s') % err)
3428
3428
3429 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
3429 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
3430 if tobackup is None:
3430 if tobackup is None:
3431 tobackup = set()
3431 tobackup = set()
3432 # Apply changes
3432 # Apply changes
3433 fp = stringio()
3433 fp = stringio()
3434 for c in chunks:
3434 for c in chunks:
3435 # Create a backup file only if this hunk should be backed up
3435 # Create a backup file only if this hunk should be backed up
3436 if ishunk(c) and c.header.filename() in tobackup:
3436 if ishunk(c) and c.header.filename() in tobackup:
3437 abs = c.header.filename()
3437 abs = c.header.filename()
3438 target = repo.wjoin(abs)
3438 target = repo.wjoin(abs)
3439 bakname = scmutil.origpath(repo.ui, repo, m.rel(abs))
3439 bakname = scmutil.origpath(repo.ui, repo, m.rel(abs))
3440 util.copyfile(target, bakname)
3440 util.copyfile(target, bakname)
3441 tobackup.remove(abs)
3441 tobackup.remove(abs)
3442 c.write(fp)
3442 c.write(fp)
3443 dopatch = fp.tell()
3443 dopatch = fp.tell()
3444 fp.seek(0)
3444 fp.seek(0)
3445 if dopatch:
3445 if dopatch:
3446 try:
3446 try:
3447 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3447 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3448 except patch.PatchError as err:
3448 except patch.PatchError as err:
3449 raise error.Abort(str(err))
3449 raise error.Abort(str(err))
3450 del fp
3450 del fp
3451 else:
3451 else:
3452 for f in actions['revert'][0]:
3452 for f in actions['revert'][0]:
3453 checkout(f)
3453 checkout(f)
3454 if normal:
3454 if normal:
3455 normal(f)
3455 normal(f)
3456
3456
3457 for f in actions['add'][0]:
3457 for f in actions['add'][0]:
3458 # Don't checkout modified files, they are already created by the diff
3458 # Don't checkout modified files, they are already created by the diff
3459 if f not in newlyaddedandmodifiedfiles:
3459 if f not in newlyaddedandmodifiedfiles:
3460 checkout(f)
3460 checkout(f)
3461 repo.dirstate.add(f)
3461 repo.dirstate.add(f)
3462
3462
3463 normal = repo.dirstate.normallookup
3463 normal = repo.dirstate.normallookup
3464 if node == parent and p2 == nullid:
3464 if node == parent and p2 == nullid:
3465 normal = repo.dirstate.normal
3465 normal = repo.dirstate.normal
3466 for f in actions['undelete'][0]:
3466 for f in actions['undelete'][0]:
3467 checkout(f)
3467 checkout(f)
3468 normal(f)
3468 normal(f)
3469
3469
3470 copied = copies.pathcopies(repo[parent], ctx)
3470 copied = copies.pathcopies(repo[parent], ctx)
3471
3471
3472 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
3472 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
3473 if f in copied:
3473 if f in copied:
3474 repo.dirstate.copy(copied[f], f)
3474 repo.dirstate.copy(copied[f], f)
3475
3475
3476 class command(registrar.command):
3476 class command(registrar.command):
3477 def _doregister(self, func, name, *args, **kwargs):
3477 def _doregister(self, func, name, *args, **kwargs):
3478 func._deprecatedregistrar = True # flag for deprecwarn in extensions.py
3478 func._deprecatedregistrar = True # flag for deprecwarn in extensions.py
3479 return super(command, self)._doregister(func, name, *args, **kwargs)
3479 return super(command, self)._doregister(func, name, *args, **kwargs)
3480
3480
3481 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3481 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3482 # commands.outgoing. "missing" is "missing" of the result of
3482 # commands.outgoing. "missing" is "missing" of the result of
3483 # "findcommonoutgoing()"
3483 # "findcommonoutgoing()"
3484 outgoinghooks = util.hooks()
3484 outgoinghooks = util.hooks()
3485
3485
3486 # a list of (ui, repo) functions called by commands.summary
3486 # a list of (ui, repo) functions called by commands.summary
3487 summaryhooks = util.hooks()
3487 summaryhooks = util.hooks()
3488
3488
3489 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3489 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3490 #
3490 #
3491 # functions should return tuple of booleans below, if 'changes' is None:
3491 # functions should return tuple of booleans below, if 'changes' is None:
3492 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3492 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3493 #
3493 #
3494 # otherwise, 'changes' is a tuple of tuples below:
3494 # otherwise, 'changes' is a tuple of tuples below:
3495 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3495 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3496 # - (desturl, destbranch, destpeer, outgoing)
3496 # - (desturl, destbranch, destpeer, outgoing)
3497 summaryremotehooks = util.hooks()
3497 summaryremotehooks = util.hooks()
3498
3498
3499 # A list of state files kept by multistep operations like graft.
3499 # A list of state files kept by multistep operations like graft.
3500 # Since graft cannot be aborted, it is considered 'clearable' by update.
3500 # Since graft cannot be aborted, it is considered 'clearable' by update.
3501 # note: bisect is intentionally excluded
3501 # note: bisect is intentionally excluded
3502 # (state file, clearable, allowcommit, error, hint)
3502 # (state file, clearable, allowcommit, error, hint)
3503 unfinishedstates = [
3503 unfinishedstates = [
3504 ('graftstate', True, False, _('graft in progress'),
3504 ('graftstate', True, False, _('graft in progress'),
3505 _("use 'hg graft --continue' or 'hg update' to abort")),
3505 _("use 'hg graft --continue' or 'hg update' to abort")),
3506 ('updatestate', True, False, _('last update was interrupted'),
3506 ('updatestate', True, False, _('last update was interrupted'),
3507 _("use 'hg update' to get a consistent checkout"))
3507 _("use 'hg update' to get a consistent checkout"))
3508 ]
3508 ]
3509
3509
3510 def checkunfinished(repo, commit=False):
3510 def checkunfinished(repo, commit=False):
3511 '''Look for an unfinished multistep operation, like graft, and abort
3511 '''Look for an unfinished multistep operation, like graft, and abort
3512 if found. It's probably good to check this right before
3512 if found. It's probably good to check this right before
3513 bailifchanged().
3513 bailifchanged().
3514 '''
3514 '''
3515 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3515 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3516 if commit and allowcommit:
3516 if commit and allowcommit:
3517 continue
3517 continue
3518 if repo.vfs.exists(f):
3518 if repo.vfs.exists(f):
3519 raise error.Abort(msg, hint=hint)
3519 raise error.Abort(msg, hint=hint)
3520
3520
3521 def clearunfinished(repo):
3521 def clearunfinished(repo):
3522 '''Check for unfinished operations (as above), and clear the ones
3522 '''Check for unfinished operations (as above), and clear the ones
3523 that are clearable.
3523 that are clearable.
3524 '''
3524 '''
3525 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3525 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3526 if not clearable and repo.vfs.exists(f):
3526 if not clearable and repo.vfs.exists(f):
3527 raise error.Abort(msg, hint=hint)
3527 raise error.Abort(msg, hint=hint)
3528 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3528 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3529 if clearable and repo.vfs.exists(f):
3529 if clearable and repo.vfs.exists(f):
3530 util.unlink(repo.vfs.join(f))
3530 util.unlink(repo.vfs.join(f))
3531
3531
3532 afterresolvedstates = [
3532 afterresolvedstates = [
3533 ('graftstate',
3533 ('graftstate',
3534 _('hg graft --continue')),
3534 _('hg graft --continue')),
3535 ]
3535 ]
3536
3536
3537 def howtocontinue(repo):
3537 def howtocontinue(repo):
3538 '''Check for an unfinished operation and return the command to finish
3538 '''Check for an unfinished operation and return the command to finish
3539 it.
3539 it.
3540
3540
3541 afterresolvedstates tuples define a .hg/{file} and the corresponding
3541 afterresolvedstates tuples define a .hg/{file} and the corresponding
3542 command needed to finish it.
3542 command needed to finish it.
3543
3543
3544 Returns a (msg, warning) tuple. 'msg' is a string and 'warning' is
3544 Returns a (msg, warning) tuple. 'msg' is a string and 'warning' is
3545 a boolean.
3545 a boolean.
3546 '''
3546 '''
3547 contmsg = _("continue: %s")
3547 contmsg = _("continue: %s")
3548 for f, msg in afterresolvedstates:
3548 for f, msg in afterresolvedstates:
3549 if repo.vfs.exists(f):
3549 if repo.vfs.exists(f):
3550 return contmsg % msg, True
3550 return contmsg % msg, True
3551 workingctx = repo[None]
3551 workingctx = repo[None]
3552 dirty = any(repo.status()) or any(workingctx.sub(s).dirty()
3552 dirty = any(repo.status()) or any(workingctx.sub(s).dirty()
3553 for s in workingctx.substate)
3553 for s in workingctx.substate)
3554 if dirty:
3554 if dirty:
3555 return contmsg % _("hg commit"), False
3555 return contmsg % _("hg commit"), False
3556 return None, None
3556 return None, None
3557
3557
3558 def checkafterresolved(repo):
3558 def checkafterresolved(repo):
3559 '''Inform the user about the next action after completing hg resolve
3559 '''Inform the user about the next action after completing hg resolve
3560
3560
3561 If there's a matching afterresolvedstates, howtocontinue will yield
3561 If there's a matching afterresolvedstates, howtocontinue will yield
3562 repo.ui.warn as the reporter.
3562 repo.ui.warn as the reporter.
3563
3563
3564 Otherwise, it will yield repo.ui.note.
3564 Otherwise, it will yield repo.ui.note.
3565 '''
3565 '''
3566 msg, warning = howtocontinue(repo)
3566 msg, warning = howtocontinue(repo)
3567 if msg is not None:
3567 if msg is not None:
3568 if warning:
3568 if warning:
3569 repo.ui.warn("%s\n" % msg)
3569 repo.ui.warn("%s\n" % msg)
3570 else:
3570 else:
3571 repo.ui.note("%s\n" % msg)
3571 repo.ui.note("%s\n" % msg)
3572
3572
3573 def wrongtooltocontinue(repo, task):
3573 def wrongtooltocontinue(repo, task):
3574 '''Raise an abort suggesting how to properly continue if there is an
3574 '''Raise an abort suggesting how to properly continue if there is an
3575 active task.
3575 active task.
3576
3576
3577 Uses howtocontinue() to find the active task.
3577 Uses howtocontinue() to find the active task.
3578
3578
3579 If there's no task (repo.ui.note for 'hg commit'), it does not offer
3579 If there's no task (repo.ui.note for 'hg commit'), it does not offer
3580 a hint.
3580 a hint.
3581 '''
3581 '''
3582 after = howtocontinue(repo)
3582 after = howtocontinue(repo)
3583 hint = None
3583 hint = None
3584 if after[1]:
3584 if after[1]:
3585 hint = after[0]
3585 hint = after[0]
3586 raise error.Abort(_('no %s in progress') % task, hint=hint)
3586 raise error.Abort(_('no %s in progress') % task, hint=hint)
@@ -1,5495 +1,5500 b''
1 # commands.py - command processing for mercurial
1 # commands.py - command processing for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import difflib
10 import difflib
11 import errno
11 import errno
12 import os
12 import os
13 import re
13 import re
14 import sys
14 import sys
15
15
16 from .i18n import _
16 from .i18n import _
17 from .node import (
17 from .node import (
18 hex,
18 hex,
19 nullid,
19 nullid,
20 nullrev,
20 nullrev,
21 short,
21 short,
22 )
22 )
23 from . import (
23 from . import (
24 archival,
24 archival,
25 bookmarks,
25 bookmarks,
26 bundle2,
26 bundle2,
27 changegroup,
27 changegroup,
28 cmdutil,
28 cmdutil,
29 copies,
29 copies,
30 debugcommands as debugcommandsmod,
30 debugcommands as debugcommandsmod,
31 destutil,
31 destutil,
32 dirstateguard,
32 dirstateguard,
33 discovery,
33 discovery,
34 encoding,
34 encoding,
35 error,
35 error,
36 exchange,
36 exchange,
37 extensions,
37 extensions,
38 formatter,
38 graphmod,
39 graphmod,
39 hbisect,
40 hbisect,
40 help,
41 help,
41 hg,
42 hg,
42 lock as lockmod,
43 lock as lockmod,
43 merge as mergemod,
44 merge as mergemod,
44 obsolete,
45 obsolete,
45 patch,
46 patch,
46 phases,
47 phases,
47 pycompat,
48 pycompat,
48 rcutil,
49 rcutil,
49 registrar,
50 registrar,
50 revsetlang,
51 revsetlang,
51 scmutil,
52 scmutil,
52 server,
53 server,
53 sshserver,
54 sshserver,
54 streamclone,
55 streamclone,
55 tags as tagsmod,
56 tags as tagsmod,
56 templatekw,
57 templatekw,
57 ui as uimod,
58 ui as uimod,
58 util,
59 util,
59 )
60 )
60
61
61 release = lockmod.release
62 release = lockmod.release
62
63
63 table = {}
64 table = {}
64 table.update(debugcommandsmod.command._table)
65 table.update(debugcommandsmod.command._table)
65
66
66 command = registrar.command(table)
67 command = registrar.command(table)
67
68
68 # label constants
69 # label constants
69 # until 3.5, bookmarks.current was the advertised name, not
70 # until 3.5, bookmarks.current was the advertised name, not
70 # bookmarks.active, so we must use both to avoid breaking old
71 # bookmarks.active, so we must use both to avoid breaking old
71 # custom styles
72 # custom styles
72 activebookmarklabel = 'bookmarks.active bookmarks.current'
73 activebookmarklabel = 'bookmarks.active bookmarks.current'
73
74
74 # common command options
75 # common command options
75
76
76 globalopts = [
77 globalopts = [
77 ('R', 'repository', '',
78 ('R', 'repository', '',
78 _('repository root directory or name of overlay bundle file'),
79 _('repository root directory or name of overlay bundle file'),
79 _('REPO')),
80 _('REPO')),
80 ('', 'cwd', '',
81 ('', 'cwd', '',
81 _('change working directory'), _('DIR')),
82 _('change working directory'), _('DIR')),
82 ('y', 'noninteractive', None,
83 ('y', 'noninteractive', None,
83 _('do not prompt, automatically pick the first choice for all prompts')),
84 _('do not prompt, automatically pick the first choice for all prompts')),
84 ('q', 'quiet', None, _('suppress output')),
85 ('q', 'quiet', None, _('suppress output')),
85 ('v', 'verbose', None, _('enable additional output')),
86 ('v', 'verbose', None, _('enable additional output')),
86 ('', 'color', '',
87 ('', 'color', '',
87 # i18n: 'always', 'auto', 'never', and 'debug' are keywords
88 # i18n: 'always', 'auto', 'never', and 'debug' are keywords
88 # and should not be translated
89 # and should not be translated
89 _("when to colorize (boolean, always, auto, never, or debug)"),
90 _("when to colorize (boolean, always, auto, never, or debug)"),
90 _('TYPE')),
91 _('TYPE')),
91 ('', 'config', [],
92 ('', 'config', [],
92 _('set/override config option (use \'section.name=value\')'),
93 _('set/override config option (use \'section.name=value\')'),
93 _('CONFIG')),
94 _('CONFIG')),
94 ('', 'debug', None, _('enable debugging output')),
95 ('', 'debug', None, _('enable debugging output')),
95 ('', 'debugger', None, _('start debugger')),
96 ('', 'debugger', None, _('start debugger')),
96 ('', 'encoding', encoding.encoding, _('set the charset encoding'),
97 ('', 'encoding', encoding.encoding, _('set the charset encoding'),
97 _('ENCODE')),
98 _('ENCODE')),
98 ('', 'encodingmode', encoding.encodingmode,
99 ('', 'encodingmode', encoding.encodingmode,
99 _('set the charset encoding mode'), _('MODE')),
100 _('set the charset encoding mode'), _('MODE')),
100 ('', 'traceback', None, _('always print a traceback on exception')),
101 ('', 'traceback', None, _('always print a traceback on exception')),
101 ('', 'time', None, _('time how long the command takes')),
102 ('', 'time', None, _('time how long the command takes')),
102 ('', 'profile', None, _('print command execution profile')),
103 ('', 'profile', None, _('print command execution profile')),
103 ('', 'version', None, _('output version information and exit')),
104 ('', 'version', None, _('output version information and exit')),
104 ('h', 'help', None, _('display help and exit')),
105 ('h', 'help', None, _('display help and exit')),
105 ('', 'hidden', False, _('consider hidden changesets')),
106 ('', 'hidden', False, _('consider hidden changesets')),
106 ('', 'pager', 'auto',
107 ('', 'pager', 'auto',
107 _("when to paginate (boolean, always, auto, or never)"), _('TYPE')),
108 _("when to paginate (boolean, always, auto, or never)"), _('TYPE')),
108 ]
109 ]
109
110
110 dryrunopts = cmdutil.dryrunopts
111 dryrunopts = cmdutil.dryrunopts
111 remoteopts = cmdutil.remoteopts
112 remoteopts = cmdutil.remoteopts
112 walkopts = cmdutil.walkopts
113 walkopts = cmdutil.walkopts
113 commitopts = cmdutil.commitopts
114 commitopts = cmdutil.commitopts
114 commitopts2 = cmdutil.commitopts2
115 commitopts2 = cmdutil.commitopts2
115 formatteropts = cmdutil.formatteropts
116 formatteropts = cmdutil.formatteropts
116 templateopts = cmdutil.templateopts
117 templateopts = cmdutil.templateopts
117 logopts = cmdutil.logopts
118 logopts = cmdutil.logopts
118 diffopts = cmdutil.diffopts
119 diffopts = cmdutil.diffopts
119 diffwsopts = cmdutil.diffwsopts
120 diffwsopts = cmdutil.diffwsopts
120 diffopts2 = cmdutil.diffopts2
121 diffopts2 = cmdutil.diffopts2
121 mergetoolopts = cmdutil.mergetoolopts
122 mergetoolopts = cmdutil.mergetoolopts
122 similarityopts = cmdutil.similarityopts
123 similarityopts = cmdutil.similarityopts
123 subrepoopts = cmdutil.subrepoopts
124 subrepoopts = cmdutil.subrepoopts
124 debugrevlogopts = cmdutil.debugrevlogopts
125 debugrevlogopts = cmdutil.debugrevlogopts
125
126
126 # Commands start here, listed alphabetically
127 # Commands start here, listed alphabetically
127
128
128 @command('^add',
129 @command('^add',
129 walkopts + subrepoopts + dryrunopts,
130 walkopts + subrepoopts + dryrunopts,
130 _('[OPTION]... [FILE]...'),
131 _('[OPTION]... [FILE]...'),
131 inferrepo=True)
132 inferrepo=True)
132 def add(ui, repo, *pats, **opts):
133 def add(ui, repo, *pats, **opts):
133 """add the specified files on the next commit
134 """add the specified files on the next commit
134
135
135 Schedule files to be version controlled and added to the
136 Schedule files to be version controlled and added to the
136 repository.
137 repository.
137
138
138 The files will be added to the repository at the next commit. To
139 The files will be added to the repository at the next commit. To
139 undo an add before that, see :hg:`forget`.
140 undo an add before that, see :hg:`forget`.
140
141
141 If no names are given, add all files to the repository (except
142 If no names are given, add all files to the repository (except
142 files matching ``.hgignore``).
143 files matching ``.hgignore``).
143
144
144 .. container:: verbose
145 .. container:: verbose
145
146
146 Examples:
147 Examples:
147
148
148 - New (unknown) files are added
149 - New (unknown) files are added
149 automatically by :hg:`add`::
150 automatically by :hg:`add`::
150
151
151 $ ls
152 $ ls
152 foo.c
153 foo.c
153 $ hg status
154 $ hg status
154 ? foo.c
155 ? foo.c
155 $ hg add
156 $ hg add
156 adding foo.c
157 adding foo.c
157 $ hg status
158 $ hg status
158 A foo.c
159 A foo.c
159
160
160 - Specific files to be added can be specified::
161 - Specific files to be added can be specified::
161
162
162 $ ls
163 $ ls
163 bar.c foo.c
164 bar.c foo.c
164 $ hg status
165 $ hg status
165 ? bar.c
166 ? bar.c
166 ? foo.c
167 ? foo.c
167 $ hg add bar.c
168 $ hg add bar.c
168 $ hg status
169 $ hg status
169 A bar.c
170 A bar.c
170 ? foo.c
171 ? foo.c
171
172
172 Returns 0 if all files are successfully added.
173 Returns 0 if all files are successfully added.
173 """
174 """
174
175
175 m = scmutil.match(repo[None], pats, pycompat.byteskwargs(opts))
176 m = scmutil.match(repo[None], pats, pycompat.byteskwargs(opts))
176 rejected = cmdutil.add(ui, repo, m, "", False, **opts)
177 rejected = cmdutil.add(ui, repo, m, "", False, **opts)
177 return rejected and 1 or 0
178 return rejected and 1 or 0
178
179
179 @command('addremove',
180 @command('addremove',
180 similarityopts + subrepoopts + walkopts + dryrunopts,
181 similarityopts + subrepoopts + walkopts + dryrunopts,
181 _('[OPTION]... [FILE]...'),
182 _('[OPTION]... [FILE]...'),
182 inferrepo=True)
183 inferrepo=True)
183 def addremove(ui, repo, *pats, **opts):
184 def addremove(ui, repo, *pats, **opts):
184 """add all new files, delete all missing files
185 """add all new files, delete all missing files
185
186
186 Add all new files and remove all missing files from the
187 Add all new files and remove all missing files from the
187 repository.
188 repository.
188
189
189 Unless names are given, new files are ignored if they match any of
190 Unless names are given, new files are ignored if they match any of
190 the patterns in ``.hgignore``. As with add, these changes take
191 the patterns in ``.hgignore``. As with add, these changes take
191 effect at the next commit.
192 effect at the next commit.
192
193
193 Use the -s/--similarity option to detect renamed files. This
194 Use the -s/--similarity option to detect renamed files. This
194 option takes a percentage between 0 (disabled) and 100 (files must
195 option takes a percentage between 0 (disabled) and 100 (files must
195 be identical) as its parameter. With a parameter greater than 0,
196 be identical) as its parameter. With a parameter greater than 0,
196 this compares every removed file with every added file and records
197 this compares every removed file with every added file and records
197 those similar enough as renames. Detecting renamed files this way
198 those similar enough as renames. Detecting renamed files this way
198 can be expensive. After using this option, :hg:`status -C` can be
199 can be expensive. After using this option, :hg:`status -C` can be
199 used to check which files were identified as moved or renamed. If
200 used to check which files were identified as moved or renamed. If
200 not specified, -s/--similarity defaults to 100 and only renames of
201 not specified, -s/--similarity defaults to 100 and only renames of
201 identical files are detected.
202 identical files are detected.
202
203
203 .. container:: verbose
204 .. container:: verbose
204
205
205 Examples:
206 Examples:
206
207
207 - A number of files (bar.c and foo.c) are new,
208 - A number of files (bar.c and foo.c) are new,
208 while foobar.c has been removed (without using :hg:`remove`)
209 while foobar.c has been removed (without using :hg:`remove`)
209 from the repository::
210 from the repository::
210
211
211 $ ls
212 $ ls
212 bar.c foo.c
213 bar.c foo.c
213 $ hg status
214 $ hg status
214 ! foobar.c
215 ! foobar.c
215 ? bar.c
216 ? bar.c
216 ? foo.c
217 ? foo.c
217 $ hg addremove
218 $ hg addremove
218 adding bar.c
219 adding bar.c
219 adding foo.c
220 adding foo.c
220 removing foobar.c
221 removing foobar.c
221 $ hg status
222 $ hg status
222 A bar.c
223 A bar.c
223 A foo.c
224 A foo.c
224 R foobar.c
225 R foobar.c
225
226
226 - A file foobar.c was moved to foo.c without using :hg:`rename`.
227 - A file foobar.c was moved to foo.c without using :hg:`rename`.
227 Afterwards, it was edited slightly::
228 Afterwards, it was edited slightly::
228
229
229 $ ls
230 $ ls
230 foo.c
231 foo.c
231 $ hg status
232 $ hg status
232 ! foobar.c
233 ! foobar.c
233 ? foo.c
234 ? foo.c
234 $ hg addremove --similarity 90
235 $ hg addremove --similarity 90
235 removing foobar.c
236 removing foobar.c
236 adding foo.c
237 adding foo.c
237 recording removal of foobar.c as rename to foo.c (94% similar)
238 recording removal of foobar.c as rename to foo.c (94% similar)
238 $ hg status -C
239 $ hg status -C
239 A foo.c
240 A foo.c
240 foobar.c
241 foobar.c
241 R foobar.c
242 R foobar.c
242
243
243 Returns 0 if all files are successfully added.
244 Returns 0 if all files are successfully added.
244 """
245 """
245 opts = pycompat.byteskwargs(opts)
246 opts = pycompat.byteskwargs(opts)
246 try:
247 try:
247 sim = float(opts.get('similarity') or 100)
248 sim = float(opts.get('similarity') or 100)
248 except ValueError:
249 except ValueError:
249 raise error.Abort(_('similarity must be a number'))
250 raise error.Abort(_('similarity must be a number'))
250 if sim < 0 or sim > 100:
251 if sim < 0 or sim > 100:
251 raise error.Abort(_('similarity must be between 0 and 100'))
252 raise error.Abort(_('similarity must be between 0 and 100'))
252 matcher = scmutil.match(repo[None], pats, opts)
253 matcher = scmutil.match(repo[None], pats, opts)
253 return scmutil.addremove(repo, matcher, "", opts, similarity=sim / 100.0)
254 return scmutil.addremove(repo, matcher, "", opts, similarity=sim / 100.0)
254
255
255 @command('^annotate|blame',
256 @command('^annotate|blame',
256 [('r', 'rev', '', _('annotate the specified revision'), _('REV')),
257 [('r', 'rev', '', _('annotate the specified revision'), _('REV')),
257 ('', 'follow', None,
258 ('', 'follow', None,
258 _('follow copies/renames and list the filename (DEPRECATED)')),
259 _('follow copies/renames and list the filename (DEPRECATED)')),
259 ('', 'no-follow', None, _("don't follow copies and renames")),
260 ('', 'no-follow', None, _("don't follow copies and renames")),
260 ('a', 'text', None, _('treat all files as text')),
261 ('a', 'text', None, _('treat all files as text')),
261 ('u', 'user', None, _('list the author (long with -v)')),
262 ('u', 'user', None, _('list the author (long with -v)')),
262 ('f', 'file', None, _('list the filename')),
263 ('f', 'file', None, _('list the filename')),
263 ('d', 'date', None, _('list the date (short with -q)')),
264 ('d', 'date', None, _('list the date (short with -q)')),
264 ('n', 'number', None, _('list the revision number (default)')),
265 ('n', 'number', None, _('list the revision number (default)')),
265 ('c', 'changeset', None, _('list the changeset')),
266 ('c', 'changeset', None, _('list the changeset')),
266 ('l', 'line-number', None, _('show line number at the first appearance')),
267 ('l', 'line-number', None, _('show line number at the first appearance')),
267 ('', 'skip', [], _('revision to not display (EXPERIMENTAL)'), _('REV')),
268 ('', 'skip', [], _('revision to not display (EXPERIMENTAL)'), _('REV')),
268 ] + diffwsopts + walkopts + formatteropts,
269 ] + diffwsopts + walkopts + formatteropts,
269 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'),
270 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'),
270 inferrepo=True)
271 inferrepo=True)
271 def annotate(ui, repo, *pats, **opts):
272 def annotate(ui, repo, *pats, **opts):
272 """show changeset information by line for each file
273 """show changeset information by line for each file
273
274
274 List changes in files, showing the revision id responsible for
275 List changes in files, showing the revision id responsible for
275 each line.
276 each line.
276
277
277 This command is useful for discovering when a change was made and
278 This command is useful for discovering when a change was made and
278 by whom.
279 by whom.
279
280
280 If you include --file, --user, or --date, the revision number is
281 If you include --file, --user, or --date, the revision number is
281 suppressed unless you also include --number.
282 suppressed unless you also include --number.
282
283
283 Without the -a/--text option, annotate will avoid processing files
284 Without the -a/--text option, annotate will avoid processing files
284 it detects as binary. With -a, annotate will annotate the file
285 it detects as binary. With -a, annotate will annotate the file
285 anyway, although the results will probably be neither useful
286 anyway, although the results will probably be neither useful
286 nor desirable.
287 nor desirable.
287
288
288 Returns 0 on success.
289 Returns 0 on success.
289 """
290 """
290 opts = pycompat.byteskwargs(opts)
291 opts = pycompat.byteskwargs(opts)
291 if not pats:
292 if not pats:
292 raise error.Abort(_('at least one filename or pattern is required'))
293 raise error.Abort(_('at least one filename or pattern is required'))
293
294
294 if opts.get('follow'):
295 if opts.get('follow'):
295 # --follow is deprecated and now just an alias for -f/--file
296 # --follow is deprecated and now just an alias for -f/--file
296 # to mimic the behavior of Mercurial before version 1.5
297 # to mimic the behavior of Mercurial before version 1.5
297 opts['file'] = True
298 opts['file'] = True
298
299
299 ctx = scmutil.revsingle(repo, opts.get('rev'))
300 ctx = scmutil.revsingle(repo, opts.get('rev'))
300
301
301 fm = ui.formatter('annotate', opts)
302 fm = ui.formatter('annotate', opts)
302 if ui.quiet:
303 if ui.quiet:
303 datefunc = util.shortdate
304 datefunc = util.shortdate
304 else:
305 else:
305 datefunc = util.datestr
306 datefunc = util.datestr
306 if ctx.rev() is None:
307 if ctx.rev() is None:
307 def hexfn(node):
308 def hexfn(node):
308 if node is None:
309 if node is None:
309 return None
310 return None
310 else:
311 else:
311 return fm.hexfunc(node)
312 return fm.hexfunc(node)
312 if opts.get('changeset'):
313 if opts.get('changeset'):
313 # omit "+" suffix which is appended to node hex
314 # omit "+" suffix which is appended to node hex
314 def formatrev(rev):
315 def formatrev(rev):
315 if rev is None:
316 if rev is None:
316 return '%d' % ctx.p1().rev()
317 return '%d' % ctx.p1().rev()
317 else:
318 else:
318 return '%d' % rev
319 return '%d' % rev
319 else:
320 else:
320 def formatrev(rev):
321 def formatrev(rev):
321 if rev is None:
322 if rev is None:
322 return '%d+' % ctx.p1().rev()
323 return '%d+' % ctx.p1().rev()
323 else:
324 else:
324 return '%d ' % rev
325 return '%d ' % rev
325 def formathex(hex):
326 def formathex(hex):
326 if hex is None:
327 if hex is None:
327 return '%s+' % fm.hexfunc(ctx.p1().node())
328 return '%s+' % fm.hexfunc(ctx.p1().node())
328 else:
329 else:
329 return '%s ' % hex
330 return '%s ' % hex
330 else:
331 else:
331 hexfn = fm.hexfunc
332 hexfn = fm.hexfunc
332 formatrev = formathex = str
333 formatrev = formathex = str
333
334
334 opmap = [('user', ' ', lambda x: x[0].user(), ui.shortuser),
335 opmap = [('user', ' ', lambda x: x[0].user(), ui.shortuser),
335 ('number', ' ', lambda x: x[0].rev(), formatrev),
336 ('number', ' ', lambda x: x[0].rev(), formatrev),
336 ('changeset', ' ', lambda x: hexfn(x[0].node()), formathex),
337 ('changeset', ' ', lambda x: hexfn(x[0].node()), formathex),
337 ('date', ' ', lambda x: x[0].date(), util.cachefunc(datefunc)),
338 ('date', ' ', lambda x: x[0].date(), util.cachefunc(datefunc)),
338 ('file', ' ', lambda x: x[0].path(), str),
339 ('file', ' ', lambda x: x[0].path(), str),
339 ('line_number', ':', lambda x: x[1], str),
340 ('line_number', ':', lambda x: x[1], str),
340 ]
341 ]
341 fieldnamemap = {'number': 'rev', 'changeset': 'node'}
342 fieldnamemap = {'number': 'rev', 'changeset': 'node'}
342
343
343 if (not opts.get('user') and not opts.get('changeset')
344 if (not opts.get('user') and not opts.get('changeset')
344 and not opts.get('date') and not opts.get('file')):
345 and not opts.get('date') and not opts.get('file')):
345 opts['number'] = True
346 opts['number'] = True
346
347
347 linenumber = opts.get('line_number') is not None
348 linenumber = opts.get('line_number') is not None
348 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
349 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
349 raise error.Abort(_('at least one of -n/-c is required for -l'))
350 raise error.Abort(_('at least one of -n/-c is required for -l'))
350
351
351 ui.pager('annotate')
352 ui.pager('annotate')
352
353
353 if fm.isplain():
354 if fm.isplain():
354 def makefunc(get, fmt):
355 def makefunc(get, fmt):
355 return lambda x: fmt(get(x))
356 return lambda x: fmt(get(x))
356 else:
357 else:
357 def makefunc(get, fmt):
358 def makefunc(get, fmt):
358 return get
359 return get
359 funcmap = [(makefunc(get, fmt), sep) for op, sep, get, fmt in opmap
360 funcmap = [(makefunc(get, fmt), sep) for op, sep, get, fmt in opmap
360 if opts.get(op)]
361 if opts.get(op)]
361 funcmap[0] = (funcmap[0][0], '') # no separator in front of first column
362 funcmap[0] = (funcmap[0][0], '') # no separator in front of first column
362 fields = ' '.join(fieldnamemap.get(op, op) for op, sep, get, fmt in opmap
363 fields = ' '.join(fieldnamemap.get(op, op) for op, sep, get, fmt in opmap
363 if opts.get(op))
364 if opts.get(op))
364
365
365 def bad(x, y):
366 def bad(x, y):
366 raise error.Abort("%s: %s" % (x, y))
367 raise error.Abort("%s: %s" % (x, y))
367
368
368 m = scmutil.match(ctx, pats, opts, badfn=bad)
369 m = scmutil.match(ctx, pats, opts, badfn=bad)
369
370
370 follow = not opts.get('no_follow')
371 follow = not opts.get('no_follow')
371 diffopts = patch.difffeatureopts(ui, opts, section='annotate',
372 diffopts = patch.difffeatureopts(ui, opts, section='annotate',
372 whitespace=True)
373 whitespace=True)
373 skiprevs = opts.get('skip')
374 skiprevs = opts.get('skip')
374 if skiprevs:
375 if skiprevs:
375 skiprevs = scmutil.revrange(repo, skiprevs)
376 skiprevs = scmutil.revrange(repo, skiprevs)
376
377
377 for abs in ctx.walk(m):
378 for abs in ctx.walk(m):
378 fctx = ctx[abs]
379 fctx = ctx[abs]
379 if not opts.get('text') and fctx.isbinary():
380 if not opts.get('text') and fctx.isbinary():
380 fm.plain(_("%s: binary file\n") % ((pats and m.rel(abs)) or abs))
381 fm.plain(_("%s: binary file\n") % ((pats and m.rel(abs)) or abs))
381 continue
382 continue
382
383
383 lines = fctx.annotate(follow=follow, linenumber=linenumber,
384 lines = fctx.annotate(follow=follow, linenumber=linenumber,
384 skiprevs=skiprevs, diffopts=diffopts)
385 skiprevs=skiprevs, diffopts=diffopts)
385 if not lines:
386 if not lines:
386 continue
387 continue
387 formats = []
388 formats = []
388 pieces = []
389 pieces = []
389
390
390 for f, sep in funcmap:
391 for f, sep in funcmap:
391 l = [f(n) for n, dummy in lines]
392 l = [f(n) for n, dummy in lines]
392 if fm.isplain():
393 if fm.isplain():
393 sizes = [encoding.colwidth(x) for x in l]
394 sizes = [encoding.colwidth(x) for x in l]
394 ml = max(sizes)
395 ml = max(sizes)
395 formats.append([sep + ' ' * (ml - w) + '%s' for w in sizes])
396 formats.append([sep + ' ' * (ml - w) + '%s' for w in sizes])
396 else:
397 else:
397 formats.append(['%s' for x in l])
398 formats.append(['%s' for x in l])
398 pieces.append(l)
399 pieces.append(l)
399
400
400 for f, p, l in zip(zip(*formats), zip(*pieces), lines):
401 for f, p, l in zip(zip(*formats), zip(*pieces), lines):
401 fm.startitem()
402 fm.startitem()
402 fm.write(fields, "".join(f), *p)
403 fm.write(fields, "".join(f), *p)
403 fm.write('line', ": %s", l[1])
404 fm.write('line', ": %s", l[1])
404
405
405 if not lines[-1][1].endswith('\n'):
406 if not lines[-1][1].endswith('\n'):
406 fm.plain('\n')
407 fm.plain('\n')
407
408
408 fm.end()
409 fm.end()
409
410
410 @command('archive',
411 @command('archive',
411 [('', 'no-decode', None, _('do not pass files through decoders')),
412 [('', 'no-decode', None, _('do not pass files through decoders')),
412 ('p', 'prefix', '', _('directory prefix for files in archive'),
413 ('p', 'prefix', '', _('directory prefix for files in archive'),
413 _('PREFIX')),
414 _('PREFIX')),
414 ('r', 'rev', '', _('revision to distribute'), _('REV')),
415 ('r', 'rev', '', _('revision to distribute'), _('REV')),
415 ('t', 'type', '', _('type of distribution to create'), _('TYPE')),
416 ('t', 'type', '', _('type of distribution to create'), _('TYPE')),
416 ] + subrepoopts + walkopts,
417 ] + subrepoopts + walkopts,
417 _('[OPTION]... DEST'))
418 _('[OPTION]... DEST'))
418 def archive(ui, repo, dest, **opts):
419 def archive(ui, repo, dest, **opts):
419 '''create an unversioned archive of a repository revision
420 '''create an unversioned archive of a repository revision
420
421
421 By default, the revision used is the parent of the working
422 By default, the revision used is the parent of the working
422 directory; use -r/--rev to specify a different revision.
423 directory; use -r/--rev to specify a different revision.
423
424
424 The archive type is automatically detected based on file
425 The archive type is automatically detected based on file
425 extension (to override, use -t/--type).
426 extension (to override, use -t/--type).
426
427
427 .. container:: verbose
428 .. container:: verbose
428
429
429 Examples:
430 Examples:
430
431
431 - create a zip file containing the 1.0 release::
432 - create a zip file containing the 1.0 release::
432
433
433 hg archive -r 1.0 project-1.0.zip
434 hg archive -r 1.0 project-1.0.zip
434
435
435 - create a tarball excluding .hg files::
436 - create a tarball excluding .hg files::
436
437
437 hg archive project.tar.gz -X ".hg*"
438 hg archive project.tar.gz -X ".hg*"
438
439
439 Valid types are:
440 Valid types are:
440
441
441 :``files``: a directory full of files (default)
442 :``files``: a directory full of files (default)
442 :``tar``: tar archive, uncompressed
443 :``tar``: tar archive, uncompressed
443 :``tbz2``: tar archive, compressed using bzip2
444 :``tbz2``: tar archive, compressed using bzip2
444 :``tgz``: tar archive, compressed using gzip
445 :``tgz``: tar archive, compressed using gzip
445 :``uzip``: zip archive, uncompressed
446 :``uzip``: zip archive, uncompressed
446 :``zip``: zip archive, compressed using deflate
447 :``zip``: zip archive, compressed using deflate
447
448
448 The exact name of the destination archive or directory is given
449 The exact name of the destination archive or directory is given
449 using a format string; see :hg:`help export` for details.
450 using a format string; see :hg:`help export` for details.
450
451
451 Each member added to an archive file has a directory prefix
452 Each member added to an archive file has a directory prefix
452 prepended. Use -p/--prefix to specify a format string for the
453 prepended. Use -p/--prefix to specify a format string for the
453 prefix. The default is the basename of the archive, with suffixes
454 prefix. The default is the basename of the archive, with suffixes
454 removed.
455 removed.
455
456
456 Returns 0 on success.
457 Returns 0 on success.
457 '''
458 '''
458
459
459 opts = pycompat.byteskwargs(opts)
460 opts = pycompat.byteskwargs(opts)
460 ctx = scmutil.revsingle(repo, opts.get('rev'))
461 ctx = scmutil.revsingle(repo, opts.get('rev'))
461 if not ctx:
462 if not ctx:
462 raise error.Abort(_('no working directory: please specify a revision'))
463 raise error.Abort(_('no working directory: please specify a revision'))
463 node = ctx.node()
464 node = ctx.node()
464 dest = cmdutil.makefilename(repo, dest, node)
465 dest = cmdutil.makefilename(repo, dest, node)
465 if os.path.realpath(dest) == repo.root:
466 if os.path.realpath(dest) == repo.root:
466 raise error.Abort(_('repository root cannot be destination'))
467 raise error.Abort(_('repository root cannot be destination'))
467
468
468 kind = opts.get('type') or archival.guesskind(dest) or 'files'
469 kind = opts.get('type') or archival.guesskind(dest) or 'files'
469 prefix = opts.get('prefix')
470 prefix = opts.get('prefix')
470
471
471 if dest == '-':
472 if dest == '-':
472 if kind == 'files':
473 if kind == 'files':
473 raise error.Abort(_('cannot archive plain files to stdout'))
474 raise error.Abort(_('cannot archive plain files to stdout'))
474 dest = cmdutil.makefileobj(repo, dest)
475 dest = cmdutil.makefileobj(repo, dest)
475 if not prefix:
476 if not prefix:
476 prefix = os.path.basename(repo.root) + '-%h'
477 prefix = os.path.basename(repo.root) + '-%h'
477
478
478 prefix = cmdutil.makefilename(repo, prefix, node)
479 prefix = cmdutil.makefilename(repo, prefix, node)
479 matchfn = scmutil.match(ctx, [], opts)
480 matchfn = scmutil.match(ctx, [], opts)
480 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
481 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
481 matchfn, prefix, subrepos=opts.get('subrepos'))
482 matchfn, prefix, subrepos=opts.get('subrepos'))
482
483
483 @command('backout',
484 @command('backout',
484 [('', 'merge', None, _('merge with old dirstate parent after backout')),
485 [('', 'merge', None, _('merge with old dirstate parent after backout')),
485 ('', 'commit', None,
486 ('', 'commit', None,
486 _('commit if no conflicts were encountered (DEPRECATED)')),
487 _('commit if no conflicts were encountered (DEPRECATED)')),
487 ('', 'no-commit', None, _('do not commit')),
488 ('', 'no-commit', None, _('do not commit')),
488 ('', 'parent', '',
489 ('', 'parent', '',
489 _('parent to choose when backing out merge (DEPRECATED)'), _('REV')),
490 _('parent to choose when backing out merge (DEPRECATED)'), _('REV')),
490 ('r', 'rev', '', _('revision to backout'), _('REV')),
491 ('r', 'rev', '', _('revision to backout'), _('REV')),
491 ('e', 'edit', False, _('invoke editor on commit messages')),
492 ('e', 'edit', False, _('invoke editor on commit messages')),
492 ] + mergetoolopts + walkopts + commitopts + commitopts2,
493 ] + mergetoolopts + walkopts + commitopts + commitopts2,
493 _('[OPTION]... [-r] REV'))
494 _('[OPTION]... [-r] REV'))
494 def backout(ui, repo, node=None, rev=None, **opts):
495 def backout(ui, repo, node=None, rev=None, **opts):
495 '''reverse effect of earlier changeset
496 '''reverse effect of earlier changeset
496
497
497 Prepare a new changeset with the effect of REV undone in the
498 Prepare a new changeset with the effect of REV undone in the
498 current working directory. If no conflicts were encountered,
499 current working directory. If no conflicts were encountered,
499 it will be committed immediately.
500 it will be committed immediately.
500
501
501 If REV is the parent of the working directory, then this new changeset
502 If REV is the parent of the working directory, then this new changeset
502 is committed automatically (unless --no-commit is specified).
503 is committed automatically (unless --no-commit is specified).
503
504
504 .. note::
505 .. note::
505
506
506 :hg:`backout` cannot be used to fix either an unwanted or
507 :hg:`backout` cannot be used to fix either an unwanted or
507 incorrect merge.
508 incorrect merge.
508
509
509 .. container:: verbose
510 .. container:: verbose
510
511
511 Examples:
512 Examples:
512
513
513 - Reverse the effect of the parent of the working directory.
514 - Reverse the effect of the parent of the working directory.
514 This backout will be committed immediately::
515 This backout will be committed immediately::
515
516
516 hg backout -r .
517 hg backout -r .
517
518
518 - Reverse the effect of previous bad revision 23::
519 - Reverse the effect of previous bad revision 23::
519
520
520 hg backout -r 23
521 hg backout -r 23
521
522
522 - Reverse the effect of previous bad revision 23 and
523 - Reverse the effect of previous bad revision 23 and
523 leave changes uncommitted::
524 leave changes uncommitted::
524
525
525 hg backout -r 23 --no-commit
526 hg backout -r 23 --no-commit
526 hg commit -m "Backout revision 23"
527 hg commit -m "Backout revision 23"
527
528
528 By default, the pending changeset will have one parent,
529 By default, the pending changeset will have one parent,
529 maintaining a linear history. With --merge, the pending
530 maintaining a linear history. With --merge, the pending
530 changeset will instead have two parents: the old parent of the
531 changeset will instead have two parents: the old parent of the
531 working directory and a new child of REV that simply undoes REV.
532 working directory and a new child of REV that simply undoes REV.
532
533
533 Before version 1.7, the behavior without --merge was equivalent
534 Before version 1.7, the behavior without --merge was equivalent
534 to specifying --merge followed by :hg:`update --clean .` to
535 to specifying --merge followed by :hg:`update --clean .` to
535 cancel the merge and leave the child of REV as a head to be
536 cancel the merge and leave the child of REV as a head to be
536 merged separately.
537 merged separately.
537
538
538 See :hg:`help dates` for a list of formats valid for -d/--date.
539 See :hg:`help dates` for a list of formats valid for -d/--date.
539
540
540 See :hg:`help revert` for a way to restore files to the state
541 See :hg:`help revert` for a way to restore files to the state
541 of another revision.
542 of another revision.
542
543
543 Returns 0 on success, 1 if nothing to backout or there are unresolved
544 Returns 0 on success, 1 if nothing to backout or there are unresolved
544 files.
545 files.
545 '''
546 '''
546 wlock = lock = None
547 wlock = lock = None
547 try:
548 try:
548 wlock = repo.wlock()
549 wlock = repo.wlock()
549 lock = repo.lock()
550 lock = repo.lock()
550 return _dobackout(ui, repo, node, rev, **opts)
551 return _dobackout(ui, repo, node, rev, **opts)
551 finally:
552 finally:
552 release(lock, wlock)
553 release(lock, wlock)
553
554
554 def _dobackout(ui, repo, node=None, rev=None, **opts):
555 def _dobackout(ui, repo, node=None, rev=None, **opts):
555 opts = pycompat.byteskwargs(opts)
556 opts = pycompat.byteskwargs(opts)
556 if opts.get('commit') and opts.get('no_commit'):
557 if opts.get('commit') and opts.get('no_commit'):
557 raise error.Abort(_("cannot use --commit with --no-commit"))
558 raise error.Abort(_("cannot use --commit with --no-commit"))
558 if opts.get('merge') and opts.get('no_commit'):
559 if opts.get('merge') and opts.get('no_commit'):
559 raise error.Abort(_("cannot use --merge with --no-commit"))
560 raise error.Abort(_("cannot use --merge with --no-commit"))
560
561
561 if rev and node:
562 if rev and node:
562 raise error.Abort(_("please specify just one revision"))
563 raise error.Abort(_("please specify just one revision"))
563
564
564 if not rev:
565 if not rev:
565 rev = node
566 rev = node
566
567
567 if not rev:
568 if not rev:
568 raise error.Abort(_("please specify a revision to backout"))
569 raise error.Abort(_("please specify a revision to backout"))
569
570
570 date = opts.get('date')
571 date = opts.get('date')
571 if date:
572 if date:
572 opts['date'] = util.parsedate(date)
573 opts['date'] = util.parsedate(date)
573
574
574 cmdutil.checkunfinished(repo)
575 cmdutil.checkunfinished(repo)
575 cmdutil.bailifchanged(repo)
576 cmdutil.bailifchanged(repo)
576 node = scmutil.revsingle(repo, rev).node()
577 node = scmutil.revsingle(repo, rev).node()
577
578
578 op1, op2 = repo.dirstate.parents()
579 op1, op2 = repo.dirstate.parents()
579 if not repo.changelog.isancestor(node, op1):
580 if not repo.changelog.isancestor(node, op1):
580 raise error.Abort(_('cannot backout change that is not an ancestor'))
581 raise error.Abort(_('cannot backout change that is not an ancestor'))
581
582
582 p1, p2 = repo.changelog.parents(node)
583 p1, p2 = repo.changelog.parents(node)
583 if p1 == nullid:
584 if p1 == nullid:
584 raise error.Abort(_('cannot backout a change with no parents'))
585 raise error.Abort(_('cannot backout a change with no parents'))
585 if p2 != nullid:
586 if p2 != nullid:
586 if not opts.get('parent'):
587 if not opts.get('parent'):
587 raise error.Abort(_('cannot backout a merge changeset'))
588 raise error.Abort(_('cannot backout a merge changeset'))
588 p = repo.lookup(opts['parent'])
589 p = repo.lookup(opts['parent'])
589 if p not in (p1, p2):
590 if p not in (p1, p2):
590 raise error.Abort(_('%s is not a parent of %s') %
591 raise error.Abort(_('%s is not a parent of %s') %
591 (short(p), short(node)))
592 (short(p), short(node)))
592 parent = p
593 parent = p
593 else:
594 else:
594 if opts.get('parent'):
595 if opts.get('parent'):
595 raise error.Abort(_('cannot use --parent on non-merge changeset'))
596 raise error.Abort(_('cannot use --parent on non-merge changeset'))
596 parent = p1
597 parent = p1
597
598
598 # the backout should appear on the same branch
599 # the backout should appear on the same branch
599 branch = repo.dirstate.branch()
600 branch = repo.dirstate.branch()
600 bheads = repo.branchheads(branch)
601 bheads = repo.branchheads(branch)
601 rctx = scmutil.revsingle(repo, hex(parent))
602 rctx = scmutil.revsingle(repo, hex(parent))
602 if not opts.get('merge') and op1 != node:
603 if not opts.get('merge') and op1 != node:
603 dsguard = dirstateguard.dirstateguard(repo, 'backout')
604 dsguard = dirstateguard.dirstateguard(repo, 'backout')
604 try:
605 try:
605 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
606 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
606 'backout')
607 'backout')
607 stats = mergemod.update(repo, parent, True, True, node, False)
608 stats = mergemod.update(repo, parent, True, True, node, False)
608 repo.setparents(op1, op2)
609 repo.setparents(op1, op2)
609 dsguard.close()
610 dsguard.close()
610 hg._showstats(repo, stats)
611 hg._showstats(repo, stats)
611 if stats[3]:
612 if stats[3]:
612 repo.ui.status(_("use 'hg resolve' to retry unresolved "
613 repo.ui.status(_("use 'hg resolve' to retry unresolved "
613 "file merges\n"))
614 "file merges\n"))
614 return 1
615 return 1
615 finally:
616 finally:
616 ui.setconfig('ui', 'forcemerge', '', '')
617 ui.setconfig('ui', 'forcemerge', '', '')
617 lockmod.release(dsguard)
618 lockmod.release(dsguard)
618 else:
619 else:
619 hg.clean(repo, node, show_stats=False)
620 hg.clean(repo, node, show_stats=False)
620 repo.dirstate.setbranch(branch)
621 repo.dirstate.setbranch(branch)
621 cmdutil.revert(ui, repo, rctx, repo.dirstate.parents())
622 cmdutil.revert(ui, repo, rctx, repo.dirstate.parents())
622
623
623 if opts.get('no_commit'):
624 if opts.get('no_commit'):
624 msg = _("changeset %s backed out, "
625 msg = _("changeset %s backed out, "
625 "don't forget to commit.\n")
626 "don't forget to commit.\n")
626 ui.status(msg % short(node))
627 ui.status(msg % short(node))
627 return 0
628 return 0
628
629
629 def commitfunc(ui, repo, message, match, opts):
630 def commitfunc(ui, repo, message, match, opts):
630 editform = 'backout'
631 editform = 'backout'
631 e = cmdutil.getcommiteditor(editform=editform,
632 e = cmdutil.getcommiteditor(editform=editform,
632 **pycompat.strkwargs(opts))
633 **pycompat.strkwargs(opts))
633 if not message:
634 if not message:
634 # we don't translate commit messages
635 # we don't translate commit messages
635 message = "Backed out changeset %s" % short(node)
636 message = "Backed out changeset %s" % short(node)
636 e = cmdutil.getcommiteditor(edit=True, editform=editform)
637 e = cmdutil.getcommiteditor(edit=True, editform=editform)
637 return repo.commit(message, opts.get('user'), opts.get('date'),
638 return repo.commit(message, opts.get('user'), opts.get('date'),
638 match, editor=e)
639 match, editor=e)
639 newnode = cmdutil.commit(ui, repo, commitfunc, [], opts)
640 newnode = cmdutil.commit(ui, repo, commitfunc, [], opts)
640 if not newnode:
641 if not newnode:
641 ui.status(_("nothing changed\n"))
642 ui.status(_("nothing changed\n"))
642 return 1
643 return 1
643 cmdutil.commitstatus(repo, newnode, branch, bheads)
644 cmdutil.commitstatus(repo, newnode, branch, bheads)
644
645
645 def nice(node):
646 def nice(node):
646 return '%d:%s' % (repo.changelog.rev(node), short(node))
647 return '%d:%s' % (repo.changelog.rev(node), short(node))
647 ui.status(_('changeset %s backs out changeset %s\n') %
648 ui.status(_('changeset %s backs out changeset %s\n') %
648 (nice(repo.changelog.tip()), nice(node)))
649 (nice(repo.changelog.tip()), nice(node)))
649 if opts.get('merge') and op1 != node:
650 if opts.get('merge') and op1 != node:
650 hg.clean(repo, op1, show_stats=False)
651 hg.clean(repo, op1, show_stats=False)
651 ui.status(_('merging with changeset %s\n')
652 ui.status(_('merging with changeset %s\n')
652 % nice(repo.changelog.tip()))
653 % nice(repo.changelog.tip()))
653 try:
654 try:
654 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
655 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
655 'backout')
656 'backout')
656 return hg.merge(repo, hex(repo.changelog.tip()))
657 return hg.merge(repo, hex(repo.changelog.tip()))
657 finally:
658 finally:
658 ui.setconfig('ui', 'forcemerge', '', '')
659 ui.setconfig('ui', 'forcemerge', '', '')
659 return 0
660 return 0
660
661
661 @command('bisect',
662 @command('bisect',
662 [('r', 'reset', False, _('reset bisect state')),
663 [('r', 'reset', False, _('reset bisect state')),
663 ('g', 'good', False, _('mark changeset good')),
664 ('g', 'good', False, _('mark changeset good')),
664 ('b', 'bad', False, _('mark changeset bad')),
665 ('b', 'bad', False, _('mark changeset bad')),
665 ('s', 'skip', False, _('skip testing changeset')),
666 ('s', 'skip', False, _('skip testing changeset')),
666 ('e', 'extend', False, _('extend the bisect range')),
667 ('e', 'extend', False, _('extend the bisect range')),
667 ('c', 'command', '', _('use command to check changeset state'), _('CMD')),
668 ('c', 'command', '', _('use command to check changeset state'), _('CMD')),
668 ('U', 'noupdate', False, _('do not update to target'))],
669 ('U', 'noupdate', False, _('do not update to target'))],
669 _("[-gbsr] [-U] [-c CMD] [REV]"))
670 _("[-gbsr] [-U] [-c CMD] [REV]"))
670 def bisect(ui, repo, rev=None, extra=None, command=None,
671 def bisect(ui, repo, rev=None, extra=None, command=None,
671 reset=None, good=None, bad=None, skip=None, extend=None,
672 reset=None, good=None, bad=None, skip=None, extend=None,
672 noupdate=None):
673 noupdate=None):
673 """subdivision search of changesets
674 """subdivision search of changesets
674
675
675 This command helps to find changesets which introduce problems. To
676 This command helps to find changesets which introduce problems. To
676 use, mark the earliest changeset you know exhibits the problem as
677 use, mark the earliest changeset you know exhibits the problem as
677 bad, then mark the latest changeset which is free from the problem
678 bad, then mark the latest changeset which is free from the problem
678 as good. Bisect will update your working directory to a revision
679 as good. Bisect will update your working directory to a revision
679 for testing (unless the -U/--noupdate option is specified). Once
680 for testing (unless the -U/--noupdate option is specified). Once
680 you have performed tests, mark the working directory as good or
681 you have performed tests, mark the working directory as good or
681 bad, and bisect will either update to another candidate changeset
682 bad, and bisect will either update to another candidate changeset
682 or announce that it has found the bad revision.
683 or announce that it has found the bad revision.
683
684
684 As a shortcut, you can also use the revision argument to mark a
685 As a shortcut, you can also use the revision argument to mark a
685 revision as good or bad without checking it out first.
686 revision as good or bad without checking it out first.
686
687
687 If you supply a command, it will be used for automatic bisection.
688 If you supply a command, it will be used for automatic bisection.
688 The environment variable HG_NODE will contain the ID of the
689 The environment variable HG_NODE will contain the ID of the
689 changeset being tested. The exit status of the command will be
690 changeset being tested. The exit status of the command will be
690 used to mark revisions as good or bad: status 0 means good, 125
691 used to mark revisions as good or bad: status 0 means good, 125
691 means to skip the revision, 127 (command not found) will abort the
692 means to skip the revision, 127 (command not found) will abort the
692 bisection, and any other non-zero exit status means the revision
693 bisection, and any other non-zero exit status means the revision
693 is bad.
694 is bad.
694
695
695 .. container:: verbose
696 .. container:: verbose
696
697
697 Some examples:
698 Some examples:
698
699
699 - start a bisection with known bad revision 34, and good revision 12::
700 - start a bisection with known bad revision 34, and good revision 12::
700
701
701 hg bisect --bad 34
702 hg bisect --bad 34
702 hg bisect --good 12
703 hg bisect --good 12
703
704
704 - advance the current bisection by marking current revision as good or
705 - advance the current bisection by marking current revision as good or
705 bad::
706 bad::
706
707
707 hg bisect --good
708 hg bisect --good
708 hg bisect --bad
709 hg bisect --bad
709
710
710 - mark the current revision, or a known revision, to be skipped (e.g. if
711 - mark the current revision, or a known revision, to be skipped (e.g. if
711 that revision is not usable because of another issue)::
712 that revision is not usable because of another issue)::
712
713
713 hg bisect --skip
714 hg bisect --skip
714 hg bisect --skip 23
715 hg bisect --skip 23
715
716
716 - skip all revisions that do not touch directories ``foo`` or ``bar``::
717 - skip all revisions that do not touch directories ``foo`` or ``bar``::
717
718
718 hg bisect --skip "!( file('path:foo') & file('path:bar') )"
719 hg bisect --skip "!( file('path:foo') & file('path:bar') )"
719
720
720 - forget the current bisection::
721 - forget the current bisection::
721
722
722 hg bisect --reset
723 hg bisect --reset
723
724
724 - use 'make && make tests' to automatically find the first broken
725 - use 'make && make tests' to automatically find the first broken
725 revision::
726 revision::
726
727
727 hg bisect --reset
728 hg bisect --reset
728 hg bisect --bad 34
729 hg bisect --bad 34
729 hg bisect --good 12
730 hg bisect --good 12
730 hg bisect --command "make && make tests"
731 hg bisect --command "make && make tests"
731
732
732 - see all changesets whose states are already known in the current
733 - see all changesets whose states are already known in the current
733 bisection::
734 bisection::
734
735
735 hg log -r "bisect(pruned)"
736 hg log -r "bisect(pruned)"
736
737
737 - see the changeset currently being bisected (especially useful
738 - see the changeset currently being bisected (especially useful
738 if running with -U/--noupdate)::
739 if running with -U/--noupdate)::
739
740
740 hg log -r "bisect(current)"
741 hg log -r "bisect(current)"
741
742
742 - see all changesets that took part in the current bisection::
743 - see all changesets that took part in the current bisection::
743
744
744 hg log -r "bisect(range)"
745 hg log -r "bisect(range)"
745
746
746 - you can even get a nice graph::
747 - you can even get a nice graph::
747
748
748 hg log --graph -r "bisect(range)"
749 hg log --graph -r "bisect(range)"
749
750
750 See :hg:`help revisions.bisect` for more about the `bisect()` predicate.
751 See :hg:`help revisions.bisect` for more about the `bisect()` predicate.
751
752
752 Returns 0 on success.
753 Returns 0 on success.
753 """
754 """
754 # backward compatibility
755 # backward compatibility
755 if rev in "good bad reset init".split():
756 if rev in "good bad reset init".split():
756 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
757 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
757 cmd, rev, extra = rev, extra, None
758 cmd, rev, extra = rev, extra, None
758 if cmd == "good":
759 if cmd == "good":
759 good = True
760 good = True
760 elif cmd == "bad":
761 elif cmd == "bad":
761 bad = True
762 bad = True
762 else:
763 else:
763 reset = True
764 reset = True
764 elif extra or good + bad + skip + reset + extend + bool(command) > 1:
765 elif extra or good + bad + skip + reset + extend + bool(command) > 1:
765 raise error.Abort(_('incompatible arguments'))
766 raise error.Abort(_('incompatible arguments'))
766
767
767 if reset:
768 if reset:
768 hbisect.resetstate(repo)
769 hbisect.resetstate(repo)
769 return
770 return
770
771
771 state = hbisect.load_state(repo)
772 state = hbisect.load_state(repo)
772
773
773 # update state
774 # update state
774 if good or bad or skip:
775 if good or bad or skip:
775 if rev:
776 if rev:
776 nodes = [repo.lookup(i) for i in scmutil.revrange(repo, [rev])]
777 nodes = [repo.lookup(i) for i in scmutil.revrange(repo, [rev])]
777 else:
778 else:
778 nodes = [repo.lookup('.')]
779 nodes = [repo.lookup('.')]
779 if good:
780 if good:
780 state['good'] += nodes
781 state['good'] += nodes
781 elif bad:
782 elif bad:
782 state['bad'] += nodes
783 state['bad'] += nodes
783 elif skip:
784 elif skip:
784 state['skip'] += nodes
785 state['skip'] += nodes
785 hbisect.save_state(repo, state)
786 hbisect.save_state(repo, state)
786 if not (state['good'] and state['bad']):
787 if not (state['good'] and state['bad']):
787 return
788 return
788
789
789 def mayupdate(repo, node, show_stats=True):
790 def mayupdate(repo, node, show_stats=True):
790 """common used update sequence"""
791 """common used update sequence"""
791 if noupdate:
792 if noupdate:
792 return
793 return
793 cmdutil.checkunfinished(repo)
794 cmdutil.checkunfinished(repo)
794 cmdutil.bailifchanged(repo)
795 cmdutil.bailifchanged(repo)
795 return hg.clean(repo, node, show_stats=show_stats)
796 return hg.clean(repo, node, show_stats=show_stats)
796
797
797 displayer = cmdutil.show_changeset(ui, repo, {})
798 displayer = cmdutil.show_changeset(ui, repo, {})
798
799
799 if command:
800 if command:
800 changesets = 1
801 changesets = 1
801 if noupdate:
802 if noupdate:
802 try:
803 try:
803 node = state['current'][0]
804 node = state['current'][0]
804 except LookupError:
805 except LookupError:
805 raise error.Abort(_('current bisect revision is unknown - '
806 raise error.Abort(_('current bisect revision is unknown - '
806 'start a new bisect to fix'))
807 'start a new bisect to fix'))
807 else:
808 else:
808 node, p2 = repo.dirstate.parents()
809 node, p2 = repo.dirstate.parents()
809 if p2 != nullid:
810 if p2 != nullid:
810 raise error.Abort(_('current bisect revision is a merge'))
811 raise error.Abort(_('current bisect revision is a merge'))
811 if rev:
812 if rev:
812 node = repo[scmutil.revsingle(repo, rev, node)].node()
813 node = repo[scmutil.revsingle(repo, rev, node)].node()
813 try:
814 try:
814 while changesets:
815 while changesets:
815 # update state
816 # update state
816 state['current'] = [node]
817 state['current'] = [node]
817 hbisect.save_state(repo, state)
818 hbisect.save_state(repo, state)
818 status = ui.system(command, environ={'HG_NODE': hex(node)},
819 status = ui.system(command, environ={'HG_NODE': hex(node)},
819 blockedtag='bisect_check')
820 blockedtag='bisect_check')
820 if status == 125:
821 if status == 125:
821 transition = "skip"
822 transition = "skip"
822 elif status == 0:
823 elif status == 0:
823 transition = "good"
824 transition = "good"
824 # status < 0 means process was killed
825 # status < 0 means process was killed
825 elif status == 127:
826 elif status == 127:
826 raise error.Abort(_("failed to execute %s") % command)
827 raise error.Abort(_("failed to execute %s") % command)
827 elif status < 0:
828 elif status < 0:
828 raise error.Abort(_("%s killed") % command)
829 raise error.Abort(_("%s killed") % command)
829 else:
830 else:
830 transition = "bad"
831 transition = "bad"
831 state[transition].append(node)
832 state[transition].append(node)
832 ctx = repo[node]
833 ctx = repo[node]
833 ui.status(_('changeset %d:%s: %s\n') % (ctx, ctx, transition))
834 ui.status(_('changeset %d:%s: %s\n') % (ctx, ctx, transition))
834 hbisect.checkstate(state)
835 hbisect.checkstate(state)
835 # bisect
836 # bisect
836 nodes, changesets, bgood = hbisect.bisect(repo.changelog, state)
837 nodes, changesets, bgood = hbisect.bisect(repo.changelog, state)
837 # update to next check
838 # update to next check
838 node = nodes[0]
839 node = nodes[0]
839 mayupdate(repo, node, show_stats=False)
840 mayupdate(repo, node, show_stats=False)
840 finally:
841 finally:
841 state['current'] = [node]
842 state['current'] = [node]
842 hbisect.save_state(repo, state)
843 hbisect.save_state(repo, state)
843 hbisect.printresult(ui, repo, state, displayer, nodes, bgood)
844 hbisect.printresult(ui, repo, state, displayer, nodes, bgood)
844 return
845 return
845
846
846 hbisect.checkstate(state)
847 hbisect.checkstate(state)
847
848
848 # actually bisect
849 # actually bisect
849 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
850 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
850 if extend:
851 if extend:
851 if not changesets:
852 if not changesets:
852 extendnode = hbisect.extendrange(repo, state, nodes, good)
853 extendnode = hbisect.extendrange(repo, state, nodes, good)
853 if extendnode is not None:
854 if extendnode is not None:
854 ui.write(_("Extending search to changeset %d:%s\n")
855 ui.write(_("Extending search to changeset %d:%s\n")
855 % (extendnode.rev(), extendnode))
856 % (extendnode.rev(), extendnode))
856 state['current'] = [extendnode.node()]
857 state['current'] = [extendnode.node()]
857 hbisect.save_state(repo, state)
858 hbisect.save_state(repo, state)
858 return mayupdate(repo, extendnode.node())
859 return mayupdate(repo, extendnode.node())
859 raise error.Abort(_("nothing to extend"))
860 raise error.Abort(_("nothing to extend"))
860
861
861 if changesets == 0:
862 if changesets == 0:
862 hbisect.printresult(ui, repo, state, displayer, nodes, good)
863 hbisect.printresult(ui, repo, state, displayer, nodes, good)
863 else:
864 else:
864 assert len(nodes) == 1 # only a single node can be tested next
865 assert len(nodes) == 1 # only a single node can be tested next
865 node = nodes[0]
866 node = nodes[0]
866 # compute the approximate number of remaining tests
867 # compute the approximate number of remaining tests
867 tests, size = 0, 2
868 tests, size = 0, 2
868 while size <= changesets:
869 while size <= changesets:
869 tests, size = tests + 1, size * 2
870 tests, size = tests + 1, size * 2
870 rev = repo.changelog.rev(node)
871 rev = repo.changelog.rev(node)
871 ui.write(_("Testing changeset %d:%s "
872 ui.write(_("Testing changeset %d:%s "
872 "(%d changesets remaining, ~%d tests)\n")
873 "(%d changesets remaining, ~%d tests)\n")
873 % (rev, short(node), changesets, tests))
874 % (rev, short(node), changesets, tests))
874 state['current'] = [node]
875 state['current'] = [node]
875 hbisect.save_state(repo, state)
876 hbisect.save_state(repo, state)
876 return mayupdate(repo, node)
877 return mayupdate(repo, node)
877
878
878 @command('bookmarks|bookmark',
879 @command('bookmarks|bookmark',
879 [('f', 'force', False, _('force')),
880 [('f', 'force', False, _('force')),
880 ('r', 'rev', '', _('revision for bookmark action'), _('REV')),
881 ('r', 'rev', '', _('revision for bookmark action'), _('REV')),
881 ('d', 'delete', False, _('delete a given bookmark')),
882 ('d', 'delete', False, _('delete a given bookmark')),
882 ('m', 'rename', '', _('rename a given bookmark'), _('OLD')),
883 ('m', 'rename', '', _('rename a given bookmark'), _('OLD')),
883 ('i', 'inactive', False, _('mark a bookmark inactive')),
884 ('i', 'inactive', False, _('mark a bookmark inactive')),
884 ] + formatteropts,
885 ] + formatteropts,
885 _('hg bookmarks [OPTIONS]... [NAME]...'))
886 _('hg bookmarks [OPTIONS]... [NAME]...'))
886 def bookmark(ui, repo, *names, **opts):
887 def bookmark(ui, repo, *names, **opts):
887 '''create a new bookmark or list existing bookmarks
888 '''create a new bookmark or list existing bookmarks
888
889
889 Bookmarks are labels on changesets to help track lines of development.
890 Bookmarks are labels on changesets to help track lines of development.
890 Bookmarks are unversioned and can be moved, renamed and deleted.
891 Bookmarks are unversioned and can be moved, renamed and deleted.
891 Deleting or moving a bookmark has no effect on the associated changesets.
892 Deleting or moving a bookmark has no effect on the associated changesets.
892
893
893 Creating or updating to a bookmark causes it to be marked as 'active'.
894 Creating or updating to a bookmark causes it to be marked as 'active'.
894 The active bookmark is indicated with a '*'.
895 The active bookmark is indicated with a '*'.
895 When a commit is made, the active bookmark will advance to the new commit.
896 When a commit is made, the active bookmark will advance to the new commit.
896 A plain :hg:`update` will also advance an active bookmark, if possible.
897 A plain :hg:`update` will also advance an active bookmark, if possible.
897 Updating away from a bookmark will cause it to be deactivated.
898 Updating away from a bookmark will cause it to be deactivated.
898
899
899 Bookmarks can be pushed and pulled between repositories (see
900 Bookmarks can be pushed and pulled between repositories (see
900 :hg:`help push` and :hg:`help pull`). If a shared bookmark has
901 :hg:`help push` and :hg:`help pull`). If a shared bookmark has
901 diverged, a new 'divergent bookmark' of the form 'name@path' will
902 diverged, a new 'divergent bookmark' of the form 'name@path' will
902 be created. Using :hg:`merge` will resolve the divergence.
903 be created. Using :hg:`merge` will resolve the divergence.
903
904
904 A bookmark named '@' has the special property that :hg:`clone` will
905 A bookmark named '@' has the special property that :hg:`clone` will
905 check it out by default if it exists.
906 check it out by default if it exists.
906
907
907 .. container:: verbose
908 .. container:: verbose
908
909
909 Examples:
910 Examples:
910
911
911 - create an active bookmark for a new line of development::
912 - create an active bookmark for a new line of development::
912
913
913 hg book new-feature
914 hg book new-feature
914
915
915 - create an inactive bookmark as a place marker::
916 - create an inactive bookmark as a place marker::
916
917
917 hg book -i reviewed
918 hg book -i reviewed
918
919
919 - create an inactive bookmark on another changeset::
920 - create an inactive bookmark on another changeset::
920
921
921 hg book -r .^ tested
922 hg book -r .^ tested
922
923
923 - rename bookmark turkey to dinner::
924 - rename bookmark turkey to dinner::
924
925
925 hg book -m turkey dinner
926 hg book -m turkey dinner
926
927
927 - move the '@' bookmark from another branch::
928 - move the '@' bookmark from another branch::
928
929
929 hg book -f @
930 hg book -f @
930 '''
931 '''
931 opts = pycompat.byteskwargs(opts)
932 opts = pycompat.byteskwargs(opts)
932 force = opts.get('force')
933 force = opts.get('force')
933 rev = opts.get('rev')
934 rev = opts.get('rev')
934 delete = opts.get('delete')
935 delete = opts.get('delete')
935 rename = opts.get('rename')
936 rename = opts.get('rename')
936 inactive = opts.get('inactive')
937 inactive = opts.get('inactive')
937
938
938 def checkformat(mark):
939 def checkformat(mark):
939 mark = mark.strip()
940 mark = mark.strip()
940 if not mark:
941 if not mark:
941 raise error.Abort(_("bookmark names cannot consist entirely of "
942 raise error.Abort(_("bookmark names cannot consist entirely of "
942 "whitespace"))
943 "whitespace"))
943 scmutil.checknewlabel(repo, mark, 'bookmark')
944 scmutil.checknewlabel(repo, mark, 'bookmark')
944 return mark
945 return mark
945
946
946 def checkconflict(repo, mark, cur, force=False, target=None):
947 def checkconflict(repo, mark, cur, force=False, target=None):
947 if mark in marks and not force:
948 if mark in marks and not force:
948 if target:
949 if target:
949 if marks[mark] == target and target == cur:
950 if marks[mark] == target and target == cur:
950 # re-activating a bookmark
951 # re-activating a bookmark
951 return
952 return
952 anc = repo.changelog.ancestors([repo[target].rev()])
953 anc = repo.changelog.ancestors([repo[target].rev()])
953 bmctx = repo[marks[mark]]
954 bmctx = repo[marks[mark]]
954 divs = [repo[b].node() for b in marks
955 divs = [repo[b].node() for b in marks
955 if b.split('@', 1)[0] == mark.split('@', 1)[0]]
956 if b.split('@', 1)[0] == mark.split('@', 1)[0]]
956
957
957 # allow resolving a single divergent bookmark even if moving
958 # allow resolving a single divergent bookmark even if moving
958 # the bookmark across branches when a revision is specified
959 # the bookmark across branches when a revision is specified
959 # that contains a divergent bookmark
960 # that contains a divergent bookmark
960 if bmctx.rev() not in anc and target in divs:
961 if bmctx.rev() not in anc and target in divs:
961 bookmarks.deletedivergent(repo, [target], mark)
962 bookmarks.deletedivergent(repo, [target], mark)
962 return
963 return
963
964
964 deletefrom = [b for b in divs
965 deletefrom = [b for b in divs
965 if repo[b].rev() in anc or b == target]
966 if repo[b].rev() in anc or b == target]
966 bookmarks.deletedivergent(repo, deletefrom, mark)
967 bookmarks.deletedivergent(repo, deletefrom, mark)
967 if bookmarks.validdest(repo, bmctx, repo[target]):
968 if bookmarks.validdest(repo, bmctx, repo[target]):
968 ui.status(_("moving bookmark '%s' forward from %s\n") %
969 ui.status(_("moving bookmark '%s' forward from %s\n") %
969 (mark, short(bmctx.node())))
970 (mark, short(bmctx.node())))
970 return
971 return
971 raise error.Abort(_("bookmark '%s' already exists "
972 raise error.Abort(_("bookmark '%s' already exists "
972 "(use -f to force)") % mark)
973 "(use -f to force)") % mark)
973 if ((mark in repo.branchmap() or mark == repo.dirstate.branch())
974 if ((mark in repo.branchmap() or mark == repo.dirstate.branch())
974 and not force):
975 and not force):
975 raise error.Abort(
976 raise error.Abort(
976 _("a bookmark cannot have the name of an existing branch"))
977 _("a bookmark cannot have the name of an existing branch"))
977 if len(mark) > 3 and not force:
978 if len(mark) > 3 and not force:
978 try:
979 try:
979 shadowhash = (mark in repo)
980 shadowhash = (mark in repo)
980 except error.LookupError: # ambiguous identifier
981 except error.LookupError: # ambiguous identifier
981 shadowhash = False
982 shadowhash = False
982 if shadowhash:
983 if shadowhash:
983 repo.ui.warn(
984 repo.ui.warn(
984 _("bookmark %s matches a changeset hash\n"
985 _("bookmark %s matches a changeset hash\n"
985 "(did you leave a -r out of an 'hg bookmark' command?)\n")
986 "(did you leave a -r out of an 'hg bookmark' command?)\n")
986 % mark)
987 % mark)
987
988
988 if delete and rename:
989 if delete and rename:
989 raise error.Abort(_("--delete and --rename are incompatible"))
990 raise error.Abort(_("--delete and --rename are incompatible"))
990 if delete and rev:
991 if delete and rev:
991 raise error.Abort(_("--rev is incompatible with --delete"))
992 raise error.Abort(_("--rev is incompatible with --delete"))
992 if rename and rev:
993 if rename and rev:
993 raise error.Abort(_("--rev is incompatible with --rename"))
994 raise error.Abort(_("--rev is incompatible with --rename"))
994 if not names and (delete or rev):
995 if not names and (delete or rev):
995 raise error.Abort(_("bookmark name required"))
996 raise error.Abort(_("bookmark name required"))
996
997
997 if delete or rename or names or inactive:
998 if delete or rename or names or inactive:
998 wlock = lock = tr = None
999 wlock = lock = tr = None
999 try:
1000 try:
1000 wlock = repo.wlock()
1001 wlock = repo.wlock()
1001 lock = repo.lock()
1002 lock = repo.lock()
1002 cur = repo.changectx('.').node()
1003 cur = repo.changectx('.').node()
1003 marks = repo._bookmarks
1004 marks = repo._bookmarks
1004 if delete:
1005 if delete:
1005 tr = repo.transaction('bookmark')
1006 tr = repo.transaction('bookmark')
1006 for mark in names:
1007 for mark in names:
1007 if mark not in marks:
1008 if mark not in marks:
1008 raise error.Abort(_("bookmark '%s' does not exist") %
1009 raise error.Abort(_("bookmark '%s' does not exist") %
1009 mark)
1010 mark)
1010 if mark == repo._activebookmark:
1011 if mark == repo._activebookmark:
1011 bookmarks.deactivate(repo)
1012 bookmarks.deactivate(repo)
1012 del marks[mark]
1013 del marks[mark]
1013
1014
1014 elif rename:
1015 elif rename:
1015 tr = repo.transaction('bookmark')
1016 tr = repo.transaction('bookmark')
1016 if not names:
1017 if not names:
1017 raise error.Abort(_("new bookmark name required"))
1018 raise error.Abort(_("new bookmark name required"))
1018 elif len(names) > 1:
1019 elif len(names) > 1:
1019 raise error.Abort(_("only one new bookmark name allowed"))
1020 raise error.Abort(_("only one new bookmark name allowed"))
1020 mark = checkformat(names[0])
1021 mark = checkformat(names[0])
1021 if rename not in marks:
1022 if rename not in marks:
1022 raise error.Abort(_("bookmark '%s' does not exist")
1023 raise error.Abort(_("bookmark '%s' does not exist")
1023 % rename)
1024 % rename)
1024 checkconflict(repo, mark, cur, force)
1025 checkconflict(repo, mark, cur, force)
1025 marks[mark] = marks[rename]
1026 marks[mark] = marks[rename]
1026 if repo._activebookmark == rename and not inactive:
1027 if repo._activebookmark == rename and not inactive:
1027 bookmarks.activate(repo, mark)
1028 bookmarks.activate(repo, mark)
1028 del marks[rename]
1029 del marks[rename]
1029 elif names:
1030 elif names:
1030 tr = repo.transaction('bookmark')
1031 tr = repo.transaction('bookmark')
1031 newact = None
1032 newact = None
1032 for mark in names:
1033 for mark in names:
1033 mark = checkformat(mark)
1034 mark = checkformat(mark)
1034 if newact is None:
1035 if newact is None:
1035 newact = mark
1036 newact = mark
1036 if inactive and mark == repo._activebookmark:
1037 if inactive and mark == repo._activebookmark:
1037 bookmarks.deactivate(repo)
1038 bookmarks.deactivate(repo)
1038 return
1039 return
1039 tgt = cur
1040 tgt = cur
1040 if rev:
1041 if rev:
1041 tgt = scmutil.revsingle(repo, rev).node()
1042 tgt = scmutil.revsingle(repo, rev).node()
1042 checkconflict(repo, mark, cur, force, tgt)
1043 checkconflict(repo, mark, cur, force, tgt)
1043 marks[mark] = tgt
1044 marks[mark] = tgt
1044 if not inactive and cur == marks[newact] and not rev:
1045 if not inactive and cur == marks[newact] and not rev:
1045 bookmarks.activate(repo, newact)
1046 bookmarks.activate(repo, newact)
1046 elif cur != tgt and newact == repo._activebookmark:
1047 elif cur != tgt and newact == repo._activebookmark:
1047 bookmarks.deactivate(repo)
1048 bookmarks.deactivate(repo)
1048 elif inactive:
1049 elif inactive:
1049 if len(marks) == 0:
1050 if len(marks) == 0:
1050 ui.status(_("no bookmarks set\n"))
1051 ui.status(_("no bookmarks set\n"))
1051 elif not repo._activebookmark:
1052 elif not repo._activebookmark:
1052 ui.status(_("no active bookmark\n"))
1053 ui.status(_("no active bookmark\n"))
1053 else:
1054 else:
1054 bookmarks.deactivate(repo)
1055 bookmarks.deactivate(repo)
1055 if tr is not None:
1056 if tr is not None:
1056 marks.recordchange(tr)
1057 marks.recordchange(tr)
1057 tr.close()
1058 tr.close()
1058 finally:
1059 finally:
1059 lockmod.release(tr, lock, wlock)
1060 lockmod.release(tr, lock, wlock)
1060 else: # show bookmarks
1061 else: # show bookmarks
1061 fm = ui.formatter('bookmarks', opts)
1062 fm = ui.formatter('bookmarks', opts)
1062 hexfn = fm.hexfunc
1063 hexfn = fm.hexfunc
1063 marks = repo._bookmarks
1064 marks = repo._bookmarks
1064 if len(marks) == 0 and fm.isplain():
1065 if len(marks) == 0 and fm.isplain():
1065 ui.status(_("no bookmarks set\n"))
1066 ui.status(_("no bookmarks set\n"))
1066 for bmark, n in sorted(marks.iteritems()):
1067 for bmark, n in sorted(marks.iteritems()):
1067 active = repo._activebookmark
1068 active = repo._activebookmark
1068 if bmark == active:
1069 if bmark == active:
1069 prefix, label = '*', activebookmarklabel
1070 prefix, label = '*', activebookmarklabel
1070 else:
1071 else:
1071 prefix, label = ' ', ''
1072 prefix, label = ' ', ''
1072
1073
1073 fm.startitem()
1074 fm.startitem()
1074 if not ui.quiet:
1075 if not ui.quiet:
1075 fm.plain(' %s ' % prefix, label=label)
1076 fm.plain(' %s ' % prefix, label=label)
1076 fm.write('bookmark', '%s', bmark, label=label)
1077 fm.write('bookmark', '%s', bmark, label=label)
1077 pad = " " * (25 - encoding.colwidth(bmark))
1078 pad = " " * (25 - encoding.colwidth(bmark))
1078 fm.condwrite(not ui.quiet, 'rev node', pad + ' %d:%s',
1079 fm.condwrite(not ui.quiet, 'rev node', pad + ' %d:%s',
1079 repo.changelog.rev(n), hexfn(n), label=label)
1080 repo.changelog.rev(n), hexfn(n), label=label)
1080 fm.data(active=(bmark == active))
1081 fm.data(active=(bmark == active))
1081 fm.plain('\n')
1082 fm.plain('\n')
1082 fm.end()
1083 fm.end()
1083
1084
1084 @command('branch',
1085 @command('branch',
1085 [('f', 'force', None,
1086 [('f', 'force', None,
1086 _('set branch name even if it shadows an existing branch')),
1087 _('set branch name even if it shadows an existing branch')),
1087 ('C', 'clean', None, _('reset branch name to parent branch name'))],
1088 ('C', 'clean', None, _('reset branch name to parent branch name'))],
1088 _('[-fC] [NAME]'))
1089 _('[-fC] [NAME]'))
1089 def branch(ui, repo, label=None, **opts):
1090 def branch(ui, repo, label=None, **opts):
1090 """set or show the current branch name
1091 """set or show the current branch name
1091
1092
1092 .. note::
1093 .. note::
1093
1094
1094 Branch names are permanent and global. Use :hg:`bookmark` to create a
1095 Branch names are permanent and global. Use :hg:`bookmark` to create a
1095 light-weight bookmark instead. See :hg:`help glossary` for more
1096 light-weight bookmark instead. See :hg:`help glossary` for more
1096 information about named branches and bookmarks.
1097 information about named branches and bookmarks.
1097
1098
1098 With no argument, show the current branch name. With one argument,
1099 With no argument, show the current branch name. With one argument,
1099 set the working directory branch name (the branch will not exist
1100 set the working directory branch name (the branch will not exist
1100 in the repository until the next commit). Standard practice
1101 in the repository until the next commit). Standard practice
1101 recommends that primary development take place on the 'default'
1102 recommends that primary development take place on the 'default'
1102 branch.
1103 branch.
1103
1104
1104 Unless -f/--force is specified, branch will not let you set a
1105 Unless -f/--force is specified, branch will not let you set a
1105 branch name that already exists.
1106 branch name that already exists.
1106
1107
1107 Use -C/--clean to reset the working directory branch to that of
1108 Use -C/--clean to reset the working directory branch to that of
1108 the parent of the working directory, negating a previous branch
1109 the parent of the working directory, negating a previous branch
1109 change.
1110 change.
1110
1111
1111 Use the command :hg:`update` to switch to an existing branch. Use
1112 Use the command :hg:`update` to switch to an existing branch. Use
1112 :hg:`commit --close-branch` to mark this branch head as closed.
1113 :hg:`commit --close-branch` to mark this branch head as closed.
1113 When all heads of a branch are closed, the branch will be
1114 When all heads of a branch are closed, the branch will be
1114 considered closed.
1115 considered closed.
1115
1116
1116 Returns 0 on success.
1117 Returns 0 on success.
1117 """
1118 """
1118 opts = pycompat.byteskwargs(opts)
1119 opts = pycompat.byteskwargs(opts)
1119 if label:
1120 if label:
1120 label = label.strip()
1121 label = label.strip()
1121
1122
1122 if not opts.get('clean') and not label:
1123 if not opts.get('clean') and not label:
1123 ui.write("%s\n" % repo.dirstate.branch())
1124 ui.write("%s\n" % repo.dirstate.branch())
1124 return
1125 return
1125
1126
1126 with repo.wlock():
1127 with repo.wlock():
1127 if opts.get('clean'):
1128 if opts.get('clean'):
1128 label = repo[None].p1().branch()
1129 label = repo[None].p1().branch()
1129 repo.dirstate.setbranch(label)
1130 repo.dirstate.setbranch(label)
1130 ui.status(_('reset working directory to branch %s\n') % label)
1131 ui.status(_('reset working directory to branch %s\n') % label)
1131 elif label:
1132 elif label:
1132 if not opts.get('force') and label in repo.branchmap():
1133 if not opts.get('force') and label in repo.branchmap():
1133 if label not in [p.branch() for p in repo[None].parents()]:
1134 if label not in [p.branch() for p in repo[None].parents()]:
1134 raise error.Abort(_('a branch of the same name already'
1135 raise error.Abort(_('a branch of the same name already'
1135 ' exists'),
1136 ' exists'),
1136 # i18n: "it" refers to an existing branch
1137 # i18n: "it" refers to an existing branch
1137 hint=_("use 'hg update' to switch to it"))
1138 hint=_("use 'hg update' to switch to it"))
1138 scmutil.checknewlabel(repo, label, 'branch')
1139 scmutil.checknewlabel(repo, label, 'branch')
1139 repo.dirstate.setbranch(label)
1140 repo.dirstate.setbranch(label)
1140 ui.status(_('marked working directory as branch %s\n') % label)
1141 ui.status(_('marked working directory as branch %s\n') % label)
1141
1142
1142 # find any open named branches aside from default
1143 # find any open named branches aside from default
1143 others = [n for n, h, t, c in repo.branchmap().iterbranches()
1144 others = [n for n, h, t, c in repo.branchmap().iterbranches()
1144 if n != "default" and not c]
1145 if n != "default" and not c]
1145 if not others:
1146 if not others:
1146 ui.status(_('(branches are permanent and global, '
1147 ui.status(_('(branches are permanent and global, '
1147 'did you want a bookmark?)\n'))
1148 'did you want a bookmark?)\n'))
1148
1149
1149 @command('branches',
1150 @command('branches',
1150 [('a', 'active', False,
1151 [('a', 'active', False,
1151 _('show only branches that have unmerged heads (DEPRECATED)')),
1152 _('show only branches that have unmerged heads (DEPRECATED)')),
1152 ('c', 'closed', False, _('show normal and closed branches')),
1153 ('c', 'closed', False, _('show normal and closed branches')),
1153 ] + formatteropts,
1154 ] + formatteropts,
1154 _('[-c]'))
1155 _('[-c]'))
1155 def branches(ui, repo, active=False, closed=False, **opts):
1156 def branches(ui, repo, active=False, closed=False, **opts):
1156 """list repository named branches
1157 """list repository named branches
1157
1158
1158 List the repository's named branches, indicating which ones are
1159 List the repository's named branches, indicating which ones are
1159 inactive. If -c/--closed is specified, also list branches which have
1160 inactive. If -c/--closed is specified, also list branches which have
1160 been marked closed (see :hg:`commit --close-branch`).
1161 been marked closed (see :hg:`commit --close-branch`).
1161
1162
1162 Use the command :hg:`update` to switch to an existing branch.
1163 Use the command :hg:`update` to switch to an existing branch.
1163
1164
1164 Returns 0.
1165 Returns 0.
1165 """
1166 """
1166
1167
1167 opts = pycompat.byteskwargs(opts)
1168 opts = pycompat.byteskwargs(opts)
1168 ui.pager('branches')
1169 ui.pager('branches')
1169 fm = ui.formatter('branches', opts)
1170 fm = ui.formatter('branches', opts)
1170 hexfunc = fm.hexfunc
1171 hexfunc = fm.hexfunc
1171
1172
1172 allheads = set(repo.heads())
1173 allheads = set(repo.heads())
1173 branches = []
1174 branches = []
1174 for tag, heads, tip, isclosed in repo.branchmap().iterbranches():
1175 for tag, heads, tip, isclosed in repo.branchmap().iterbranches():
1175 isactive = not isclosed and bool(set(heads) & allheads)
1176 isactive = not isclosed and bool(set(heads) & allheads)
1176 branches.append((tag, repo[tip], isactive, not isclosed))
1177 branches.append((tag, repo[tip], isactive, not isclosed))
1177 branches.sort(key=lambda i: (i[2], i[1].rev(), i[0], i[3]),
1178 branches.sort(key=lambda i: (i[2], i[1].rev(), i[0], i[3]),
1178 reverse=True)
1179 reverse=True)
1179
1180
1180 for tag, ctx, isactive, isopen in branches:
1181 for tag, ctx, isactive, isopen in branches:
1181 if active and not isactive:
1182 if active and not isactive:
1182 continue
1183 continue
1183 if isactive:
1184 if isactive:
1184 label = 'branches.active'
1185 label = 'branches.active'
1185 notice = ''
1186 notice = ''
1186 elif not isopen:
1187 elif not isopen:
1187 if not closed:
1188 if not closed:
1188 continue
1189 continue
1189 label = 'branches.closed'
1190 label = 'branches.closed'
1190 notice = _(' (closed)')
1191 notice = _(' (closed)')
1191 else:
1192 else:
1192 label = 'branches.inactive'
1193 label = 'branches.inactive'
1193 notice = _(' (inactive)')
1194 notice = _(' (inactive)')
1194 current = (tag == repo.dirstate.branch())
1195 current = (tag == repo.dirstate.branch())
1195 if current:
1196 if current:
1196 label = 'branches.current'
1197 label = 'branches.current'
1197
1198
1198 fm.startitem()
1199 fm.startitem()
1199 fm.write('branch', '%s', tag, label=label)
1200 fm.write('branch', '%s', tag, label=label)
1200 rev = ctx.rev()
1201 rev = ctx.rev()
1201 padsize = max(31 - len(str(rev)) - encoding.colwidth(tag), 0)
1202 padsize = max(31 - len(str(rev)) - encoding.colwidth(tag), 0)
1202 fmt = ' ' * padsize + ' %d:%s'
1203 fmt = ' ' * padsize + ' %d:%s'
1203 fm.condwrite(not ui.quiet, 'rev node', fmt, rev, hexfunc(ctx.node()),
1204 fm.condwrite(not ui.quiet, 'rev node', fmt, rev, hexfunc(ctx.node()),
1204 label='log.changeset changeset.%s' % ctx.phasestr())
1205 label='log.changeset changeset.%s' % ctx.phasestr())
1205 fm.context(ctx=ctx)
1206 fm.context(ctx=ctx)
1206 fm.data(active=isactive, closed=not isopen, current=current)
1207 fm.data(active=isactive, closed=not isopen, current=current)
1207 if not ui.quiet:
1208 if not ui.quiet:
1208 fm.plain(notice)
1209 fm.plain(notice)
1209 fm.plain('\n')
1210 fm.plain('\n')
1210 fm.end()
1211 fm.end()
1211
1212
1212 @command('bundle',
1213 @command('bundle',
1213 [('f', 'force', None, _('run even when the destination is unrelated')),
1214 [('f', 'force', None, _('run even when the destination is unrelated')),
1214 ('r', 'rev', [], _('a changeset intended to be added to the destination'),
1215 ('r', 'rev', [], _('a changeset intended to be added to the destination'),
1215 _('REV')),
1216 _('REV')),
1216 ('b', 'branch', [], _('a specific branch you would like to bundle'),
1217 ('b', 'branch', [], _('a specific branch you would like to bundle'),
1217 _('BRANCH')),
1218 _('BRANCH')),
1218 ('', 'base', [],
1219 ('', 'base', [],
1219 _('a base changeset assumed to be available at the destination'),
1220 _('a base changeset assumed to be available at the destination'),
1220 _('REV')),
1221 _('REV')),
1221 ('a', 'all', None, _('bundle all changesets in the repository')),
1222 ('a', 'all', None, _('bundle all changesets in the repository')),
1222 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE')),
1223 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE')),
1223 ] + remoteopts,
1224 ] + remoteopts,
1224 _('[-f] [-t BUNDLESPEC] [-a] [-r REV]... [--base REV]... FILE [DEST]'))
1225 _('[-f] [-t BUNDLESPEC] [-a] [-r REV]... [--base REV]... FILE [DEST]'))
1225 def bundle(ui, repo, fname, dest=None, **opts):
1226 def bundle(ui, repo, fname, dest=None, **opts):
1226 """create a bundle file
1227 """create a bundle file
1227
1228
1228 Generate a bundle file containing data to be added to a repository.
1229 Generate a bundle file containing data to be added to a repository.
1229
1230
1230 To create a bundle containing all changesets, use -a/--all
1231 To create a bundle containing all changesets, use -a/--all
1231 (or --base null). Otherwise, hg assumes the destination will have
1232 (or --base null). Otherwise, hg assumes the destination will have
1232 all the nodes you specify with --base parameters. Otherwise, hg
1233 all the nodes you specify with --base parameters. Otherwise, hg
1233 will assume the repository has all the nodes in destination, or
1234 will assume the repository has all the nodes in destination, or
1234 default-push/default if no destination is specified.
1235 default-push/default if no destination is specified.
1235
1236
1236 You can change bundle format with the -t/--type option. See
1237 You can change bundle format with the -t/--type option. See
1237 :hg:`help bundlespec` for documentation on this format. By default,
1238 :hg:`help bundlespec` for documentation on this format. By default,
1238 the most appropriate format is used and compression defaults to
1239 the most appropriate format is used and compression defaults to
1239 bzip2.
1240 bzip2.
1240
1241
1241 The bundle file can then be transferred using conventional means
1242 The bundle file can then be transferred using conventional means
1242 and applied to another repository with the unbundle or pull
1243 and applied to another repository with the unbundle or pull
1243 command. This is useful when direct push and pull are not
1244 command. This is useful when direct push and pull are not
1244 available or when exporting an entire repository is undesirable.
1245 available or when exporting an entire repository is undesirable.
1245
1246
1246 Applying bundles preserves all changeset contents including
1247 Applying bundles preserves all changeset contents including
1247 permissions, copy/rename information, and revision history.
1248 permissions, copy/rename information, and revision history.
1248
1249
1249 Returns 0 on success, 1 if no changes found.
1250 Returns 0 on success, 1 if no changes found.
1250 """
1251 """
1251 opts = pycompat.byteskwargs(opts)
1252 opts = pycompat.byteskwargs(opts)
1252 revs = None
1253 revs = None
1253 if 'rev' in opts:
1254 if 'rev' in opts:
1254 revstrings = opts['rev']
1255 revstrings = opts['rev']
1255 revs = scmutil.revrange(repo, revstrings)
1256 revs = scmutil.revrange(repo, revstrings)
1256 if revstrings and not revs:
1257 if revstrings and not revs:
1257 raise error.Abort(_('no commits to bundle'))
1258 raise error.Abort(_('no commits to bundle'))
1258
1259
1259 bundletype = opts.get('type', 'bzip2').lower()
1260 bundletype = opts.get('type', 'bzip2').lower()
1260 try:
1261 try:
1261 bcompression, cgversion, params = exchange.parsebundlespec(
1262 bcompression, cgversion, params = exchange.parsebundlespec(
1262 repo, bundletype, strict=False)
1263 repo, bundletype, strict=False)
1263 except error.UnsupportedBundleSpecification as e:
1264 except error.UnsupportedBundleSpecification as e:
1264 raise error.Abort(str(e),
1265 raise error.Abort(str(e),
1265 hint=_("see 'hg help bundlespec' for supported "
1266 hint=_("see 'hg help bundlespec' for supported "
1266 "values for --type"))
1267 "values for --type"))
1267
1268
1268 # Packed bundles are a pseudo bundle format for now.
1269 # Packed bundles are a pseudo bundle format for now.
1269 if cgversion == 's1':
1270 if cgversion == 's1':
1270 raise error.Abort(_('packed bundles cannot be produced by "hg bundle"'),
1271 raise error.Abort(_('packed bundles cannot be produced by "hg bundle"'),
1271 hint=_("use 'hg debugcreatestreamclonebundle'"))
1272 hint=_("use 'hg debugcreatestreamclonebundle'"))
1272
1273
1273 if opts.get('all'):
1274 if opts.get('all'):
1274 if dest:
1275 if dest:
1275 raise error.Abort(_("--all is incompatible with specifying "
1276 raise error.Abort(_("--all is incompatible with specifying "
1276 "a destination"))
1277 "a destination"))
1277 if opts.get('base'):
1278 if opts.get('base'):
1278 ui.warn(_("ignoring --base because --all was specified\n"))
1279 ui.warn(_("ignoring --base because --all was specified\n"))
1279 base = ['null']
1280 base = ['null']
1280 else:
1281 else:
1281 base = scmutil.revrange(repo, opts.get('base'))
1282 base = scmutil.revrange(repo, opts.get('base'))
1282 if cgversion not in changegroup.supportedoutgoingversions(repo):
1283 if cgversion not in changegroup.supportedoutgoingversions(repo):
1283 raise error.Abort(_("repository does not support bundle version %s") %
1284 raise error.Abort(_("repository does not support bundle version %s") %
1284 cgversion)
1285 cgversion)
1285
1286
1286 if base:
1287 if base:
1287 if dest:
1288 if dest:
1288 raise error.Abort(_("--base is incompatible with specifying "
1289 raise error.Abort(_("--base is incompatible with specifying "
1289 "a destination"))
1290 "a destination"))
1290 common = [repo.lookup(rev) for rev in base]
1291 common = [repo.lookup(rev) for rev in base]
1291 heads = revs and map(repo.lookup, revs) or None
1292 heads = revs and map(repo.lookup, revs) or None
1292 outgoing = discovery.outgoing(repo, common, heads)
1293 outgoing = discovery.outgoing(repo, common, heads)
1293 else:
1294 else:
1294 dest = ui.expandpath(dest or 'default-push', dest or 'default')
1295 dest = ui.expandpath(dest or 'default-push', dest or 'default')
1295 dest, branches = hg.parseurl(dest, opts.get('branch'))
1296 dest, branches = hg.parseurl(dest, opts.get('branch'))
1296 other = hg.peer(repo, opts, dest)
1297 other = hg.peer(repo, opts, dest)
1297 revs, checkout = hg.addbranchrevs(repo, repo, branches, revs)
1298 revs, checkout = hg.addbranchrevs(repo, repo, branches, revs)
1298 heads = revs and map(repo.lookup, revs) or revs
1299 heads = revs and map(repo.lookup, revs) or revs
1299 outgoing = discovery.findcommonoutgoing(repo, other,
1300 outgoing = discovery.findcommonoutgoing(repo, other,
1300 onlyheads=heads,
1301 onlyheads=heads,
1301 force=opts.get('force'),
1302 force=opts.get('force'),
1302 portable=True)
1303 portable=True)
1303
1304
1304 if not outgoing.missing:
1305 if not outgoing.missing:
1305 scmutil.nochangesfound(ui, repo, not base and outgoing.excluded)
1306 scmutil.nochangesfound(ui, repo, not base and outgoing.excluded)
1306 return 1
1307 return 1
1307
1308
1308 if cgversion == '01': #bundle1
1309 if cgversion == '01': #bundle1
1309 if bcompression is None:
1310 if bcompression is None:
1310 bcompression = 'UN'
1311 bcompression = 'UN'
1311 bversion = 'HG10' + bcompression
1312 bversion = 'HG10' + bcompression
1312 bcompression = None
1313 bcompression = None
1313 elif cgversion in ('02', '03'):
1314 elif cgversion in ('02', '03'):
1314 bversion = 'HG20'
1315 bversion = 'HG20'
1315 else:
1316 else:
1316 raise error.ProgrammingError(
1317 raise error.ProgrammingError(
1317 'bundle: unexpected changegroup version %s' % cgversion)
1318 'bundle: unexpected changegroup version %s' % cgversion)
1318
1319
1319 # TODO compression options should be derived from bundlespec parsing.
1320 # TODO compression options should be derived from bundlespec parsing.
1320 # This is a temporary hack to allow adjusting bundle compression
1321 # This is a temporary hack to allow adjusting bundle compression
1321 # level without a) formalizing the bundlespec changes to declare it
1322 # level without a) formalizing the bundlespec changes to declare it
1322 # b) introducing a command flag.
1323 # b) introducing a command flag.
1323 compopts = {}
1324 compopts = {}
1324 complevel = ui.configint('experimental', 'bundlecomplevel')
1325 complevel = ui.configint('experimental', 'bundlecomplevel')
1325 if complevel is not None:
1326 if complevel is not None:
1326 compopts['level'] = complevel
1327 compopts['level'] = complevel
1327
1328
1328
1329
1329 contentopts = {'cg.version': cgversion}
1330 contentopts = {'cg.version': cgversion}
1330 if repo.ui.configbool('experimental', 'evolution.bundle-obsmarker', False):
1331 if repo.ui.configbool('experimental', 'evolution.bundle-obsmarker', False):
1331 contentopts['obsolescence'] = True
1332 contentopts['obsolescence'] = True
1332 bundle2.writenewbundle(ui, repo, 'bundle', fname, bversion, outgoing,
1333 bundle2.writenewbundle(ui, repo, 'bundle', fname, bversion, outgoing,
1333 contentopts, compression=bcompression,
1334 contentopts, compression=bcompression,
1334 compopts=compopts)
1335 compopts=compopts)
1335
1336
1336 @command('cat',
1337 @command('cat',
1337 [('o', 'output', '',
1338 [('o', 'output', '',
1338 _('print output to file with formatted name'), _('FORMAT')),
1339 _('print output to file with formatted name'), _('FORMAT')),
1339 ('r', 'rev', '', _('print the given revision'), _('REV')),
1340 ('r', 'rev', '', _('print the given revision'), _('REV')),
1340 ('', 'decode', None, _('apply any matching decode filter')),
1341 ('', 'decode', None, _('apply any matching decode filter')),
1341 ] + walkopts,
1342 ] + walkopts + formatteropts,
1342 _('[OPTION]... FILE...'),
1343 _('[OPTION]... FILE...'),
1343 inferrepo=True)
1344 inferrepo=True)
1344 def cat(ui, repo, file1, *pats, **opts):
1345 def cat(ui, repo, file1, *pats, **opts):
1345 """output the current or given revision of files
1346 """output the current or given revision of files
1346
1347
1347 Print the specified files as they were at the given revision. If
1348 Print the specified files as they were at the given revision. If
1348 no revision is given, the parent of the working directory is used.
1349 no revision is given, the parent of the working directory is used.
1349
1350
1350 Output may be to a file, in which case the name of the file is
1351 Output may be to a file, in which case the name of the file is
1351 given using a format string. The formatting rules as follows:
1352 given using a format string. The formatting rules as follows:
1352
1353
1353 :``%%``: literal "%" character
1354 :``%%``: literal "%" character
1354 :``%s``: basename of file being printed
1355 :``%s``: basename of file being printed
1355 :``%d``: dirname of file being printed, or '.' if in repository root
1356 :``%d``: dirname of file being printed, or '.' if in repository root
1356 :``%p``: root-relative path name of file being printed
1357 :``%p``: root-relative path name of file being printed
1357 :``%H``: changeset hash (40 hexadecimal digits)
1358 :``%H``: changeset hash (40 hexadecimal digits)
1358 :``%R``: changeset revision number
1359 :``%R``: changeset revision number
1359 :``%h``: short-form changeset hash (12 hexadecimal digits)
1360 :``%h``: short-form changeset hash (12 hexadecimal digits)
1360 :``%r``: zero-padded changeset revision number
1361 :``%r``: zero-padded changeset revision number
1361 :``%b``: basename of the exporting repository
1362 :``%b``: basename of the exporting repository
1362
1363
1363 Returns 0 on success.
1364 Returns 0 on success.
1364 """
1365 """
1365 ctx = scmutil.revsingle(repo, opts.get('rev'))
1366 ctx = scmutil.revsingle(repo, opts.get('rev'))
1366 m = scmutil.match(ctx, (file1,) + pats, opts)
1367 m = scmutil.match(ctx, (file1,) + pats, opts)
1367 fntemplate = opts.pop('output', '')
1368 fntemplate = opts.pop('output', '')
1368 if cmdutil.isstdiofilename(fntemplate):
1369 if cmdutil.isstdiofilename(fntemplate):
1369 fntemplate = ''
1370 fntemplate = ''
1370
1371
1371 if not fntemplate:
1372 if fntemplate:
1373 fm = formatter.nullformatter(ui, 'cat')
1374 else:
1372 ui.pager('cat')
1375 ui.pager('cat')
1373 return cmdutil.cat(ui, repo, ctx, m, fntemplate, '', **opts)
1376 fm = ui.formatter('cat', opts)
1377 with fm:
1378 return cmdutil.cat(ui, repo, ctx, m, fm, fntemplate, '', **opts)
1374
1379
1375 @command('^clone',
1380 @command('^clone',
1376 [('U', 'noupdate', None, _('the clone will include an empty working '
1381 [('U', 'noupdate', None, _('the clone will include an empty working '
1377 'directory (only a repository)')),
1382 'directory (only a repository)')),
1378 ('u', 'updaterev', '', _('revision, tag, or branch to check out'),
1383 ('u', 'updaterev', '', _('revision, tag, or branch to check out'),
1379 _('REV')),
1384 _('REV')),
1380 ('r', 'rev', [], _('include the specified changeset'), _('REV')),
1385 ('r', 'rev', [], _('include the specified changeset'), _('REV')),
1381 ('b', 'branch', [], _('clone only the specified branch'), _('BRANCH')),
1386 ('b', 'branch', [], _('clone only the specified branch'), _('BRANCH')),
1382 ('', 'pull', None, _('use pull protocol to copy metadata')),
1387 ('', 'pull', None, _('use pull protocol to copy metadata')),
1383 ('', 'uncompressed', None, _('use uncompressed transfer (fast over LAN)')),
1388 ('', 'uncompressed', None, _('use uncompressed transfer (fast over LAN)')),
1384 ] + remoteopts,
1389 ] + remoteopts,
1385 _('[OPTION]... SOURCE [DEST]'),
1390 _('[OPTION]... SOURCE [DEST]'),
1386 norepo=True)
1391 norepo=True)
1387 def clone(ui, source, dest=None, **opts):
1392 def clone(ui, source, dest=None, **opts):
1388 """make a copy of an existing repository
1393 """make a copy of an existing repository
1389
1394
1390 Create a copy of an existing repository in a new directory.
1395 Create a copy of an existing repository in a new directory.
1391
1396
1392 If no destination directory name is specified, it defaults to the
1397 If no destination directory name is specified, it defaults to the
1393 basename of the source.
1398 basename of the source.
1394
1399
1395 The location of the source is added to the new repository's
1400 The location of the source is added to the new repository's
1396 ``.hg/hgrc`` file, as the default to be used for future pulls.
1401 ``.hg/hgrc`` file, as the default to be used for future pulls.
1397
1402
1398 Only local paths and ``ssh://`` URLs are supported as
1403 Only local paths and ``ssh://`` URLs are supported as
1399 destinations. For ``ssh://`` destinations, no working directory or
1404 destinations. For ``ssh://`` destinations, no working directory or
1400 ``.hg/hgrc`` will be created on the remote side.
1405 ``.hg/hgrc`` will be created on the remote side.
1401
1406
1402 If the source repository has a bookmark called '@' set, that
1407 If the source repository has a bookmark called '@' set, that
1403 revision will be checked out in the new repository by default.
1408 revision will be checked out in the new repository by default.
1404
1409
1405 To check out a particular version, use -u/--update, or
1410 To check out a particular version, use -u/--update, or
1406 -U/--noupdate to create a clone with no working directory.
1411 -U/--noupdate to create a clone with no working directory.
1407
1412
1408 To pull only a subset of changesets, specify one or more revisions
1413 To pull only a subset of changesets, specify one or more revisions
1409 identifiers with -r/--rev or branches with -b/--branch. The
1414 identifiers with -r/--rev or branches with -b/--branch. The
1410 resulting clone will contain only the specified changesets and
1415 resulting clone will contain only the specified changesets and
1411 their ancestors. These options (or 'clone src#rev dest') imply
1416 their ancestors. These options (or 'clone src#rev dest') imply
1412 --pull, even for local source repositories.
1417 --pull, even for local source repositories.
1413
1418
1414 .. note::
1419 .. note::
1415
1420
1416 Specifying a tag will include the tagged changeset but not the
1421 Specifying a tag will include the tagged changeset but not the
1417 changeset containing the tag.
1422 changeset containing the tag.
1418
1423
1419 .. container:: verbose
1424 .. container:: verbose
1420
1425
1421 For efficiency, hardlinks are used for cloning whenever the
1426 For efficiency, hardlinks are used for cloning whenever the
1422 source and destination are on the same filesystem (note this
1427 source and destination are on the same filesystem (note this
1423 applies only to the repository data, not to the working
1428 applies only to the repository data, not to the working
1424 directory). Some filesystems, such as AFS, implement hardlinking
1429 directory). Some filesystems, such as AFS, implement hardlinking
1425 incorrectly, but do not report errors. In these cases, use the
1430 incorrectly, but do not report errors. In these cases, use the
1426 --pull option to avoid hardlinking.
1431 --pull option to avoid hardlinking.
1427
1432
1428 In some cases, you can clone repositories and the working
1433 In some cases, you can clone repositories and the working
1429 directory using full hardlinks with ::
1434 directory using full hardlinks with ::
1430
1435
1431 $ cp -al REPO REPOCLONE
1436 $ cp -al REPO REPOCLONE
1432
1437
1433 This is the fastest way to clone, but it is not always safe. The
1438 This is the fastest way to clone, but it is not always safe. The
1434 operation is not atomic (making sure REPO is not modified during
1439 operation is not atomic (making sure REPO is not modified during
1435 the operation is up to you) and you have to make sure your
1440 the operation is up to you) and you have to make sure your
1436 editor breaks hardlinks (Emacs and most Linux Kernel tools do
1441 editor breaks hardlinks (Emacs and most Linux Kernel tools do
1437 so). Also, this is not compatible with certain extensions that
1442 so). Also, this is not compatible with certain extensions that
1438 place their metadata under the .hg directory, such as mq.
1443 place their metadata under the .hg directory, such as mq.
1439
1444
1440 Mercurial will update the working directory to the first applicable
1445 Mercurial will update the working directory to the first applicable
1441 revision from this list:
1446 revision from this list:
1442
1447
1443 a) null if -U or the source repository has no changesets
1448 a) null if -U or the source repository has no changesets
1444 b) if -u . and the source repository is local, the first parent of
1449 b) if -u . and the source repository is local, the first parent of
1445 the source repository's working directory
1450 the source repository's working directory
1446 c) the changeset specified with -u (if a branch name, this means the
1451 c) the changeset specified with -u (if a branch name, this means the
1447 latest head of that branch)
1452 latest head of that branch)
1448 d) the changeset specified with -r
1453 d) the changeset specified with -r
1449 e) the tipmost head specified with -b
1454 e) the tipmost head specified with -b
1450 f) the tipmost head specified with the url#branch source syntax
1455 f) the tipmost head specified with the url#branch source syntax
1451 g) the revision marked with the '@' bookmark, if present
1456 g) the revision marked with the '@' bookmark, if present
1452 h) the tipmost head of the default branch
1457 h) the tipmost head of the default branch
1453 i) tip
1458 i) tip
1454
1459
1455 When cloning from servers that support it, Mercurial may fetch
1460 When cloning from servers that support it, Mercurial may fetch
1456 pre-generated data from a server-advertised URL. When this is done,
1461 pre-generated data from a server-advertised URL. When this is done,
1457 hooks operating on incoming changesets and changegroups may fire twice,
1462 hooks operating on incoming changesets and changegroups may fire twice,
1458 once for the bundle fetched from the URL and another for any additional
1463 once for the bundle fetched from the URL and another for any additional
1459 data not fetched from this URL. In addition, if an error occurs, the
1464 data not fetched from this URL. In addition, if an error occurs, the
1460 repository may be rolled back to a partial clone. This behavior may
1465 repository may be rolled back to a partial clone. This behavior may
1461 change in future releases. See :hg:`help -e clonebundles` for more.
1466 change in future releases. See :hg:`help -e clonebundles` for more.
1462
1467
1463 Examples:
1468 Examples:
1464
1469
1465 - clone a remote repository to a new directory named hg/::
1470 - clone a remote repository to a new directory named hg/::
1466
1471
1467 hg clone https://www.mercurial-scm.org/repo/hg/
1472 hg clone https://www.mercurial-scm.org/repo/hg/
1468
1473
1469 - create a lightweight local clone::
1474 - create a lightweight local clone::
1470
1475
1471 hg clone project/ project-feature/
1476 hg clone project/ project-feature/
1472
1477
1473 - clone from an absolute path on an ssh server (note double-slash)::
1478 - clone from an absolute path on an ssh server (note double-slash)::
1474
1479
1475 hg clone ssh://user@server//home/projects/alpha/
1480 hg clone ssh://user@server//home/projects/alpha/
1476
1481
1477 - do a high-speed clone over a LAN while checking out a
1482 - do a high-speed clone over a LAN while checking out a
1478 specified version::
1483 specified version::
1479
1484
1480 hg clone --uncompressed http://server/repo -u 1.5
1485 hg clone --uncompressed http://server/repo -u 1.5
1481
1486
1482 - create a repository without changesets after a particular revision::
1487 - create a repository without changesets after a particular revision::
1483
1488
1484 hg clone -r 04e544 experimental/ good/
1489 hg clone -r 04e544 experimental/ good/
1485
1490
1486 - clone (and track) a particular named branch::
1491 - clone (and track) a particular named branch::
1487
1492
1488 hg clone https://www.mercurial-scm.org/repo/hg/#stable
1493 hg clone https://www.mercurial-scm.org/repo/hg/#stable
1489
1494
1490 See :hg:`help urls` for details on specifying URLs.
1495 See :hg:`help urls` for details on specifying URLs.
1491
1496
1492 Returns 0 on success.
1497 Returns 0 on success.
1493 """
1498 """
1494 opts = pycompat.byteskwargs(opts)
1499 opts = pycompat.byteskwargs(opts)
1495 if opts.get('noupdate') and opts.get('updaterev'):
1500 if opts.get('noupdate') and opts.get('updaterev'):
1496 raise error.Abort(_("cannot specify both --noupdate and --updaterev"))
1501 raise error.Abort(_("cannot specify both --noupdate and --updaterev"))
1497
1502
1498 r = hg.clone(ui, opts, source, dest,
1503 r = hg.clone(ui, opts, source, dest,
1499 pull=opts.get('pull'),
1504 pull=opts.get('pull'),
1500 stream=opts.get('uncompressed'),
1505 stream=opts.get('uncompressed'),
1501 rev=opts.get('rev'),
1506 rev=opts.get('rev'),
1502 update=opts.get('updaterev') or not opts.get('noupdate'),
1507 update=opts.get('updaterev') or not opts.get('noupdate'),
1503 branch=opts.get('branch'),
1508 branch=opts.get('branch'),
1504 shareopts=opts.get('shareopts'))
1509 shareopts=opts.get('shareopts'))
1505
1510
1506 return r is None
1511 return r is None
1507
1512
1508 @command('^commit|ci',
1513 @command('^commit|ci',
1509 [('A', 'addremove', None,
1514 [('A', 'addremove', None,
1510 _('mark new/missing files as added/removed before committing')),
1515 _('mark new/missing files as added/removed before committing')),
1511 ('', 'close-branch', None,
1516 ('', 'close-branch', None,
1512 _('mark a branch head as closed')),
1517 _('mark a branch head as closed')),
1513 ('', 'amend', None, _('amend the parent of the working directory')),
1518 ('', 'amend', None, _('amend the parent of the working directory')),
1514 ('s', 'secret', None, _('use the secret phase for committing')),
1519 ('s', 'secret', None, _('use the secret phase for committing')),
1515 ('e', 'edit', None, _('invoke editor on commit messages')),
1520 ('e', 'edit', None, _('invoke editor on commit messages')),
1516 ('i', 'interactive', None, _('use interactive mode')),
1521 ('i', 'interactive', None, _('use interactive mode')),
1517 ] + walkopts + commitopts + commitopts2 + subrepoopts,
1522 ] + walkopts + commitopts + commitopts2 + subrepoopts,
1518 _('[OPTION]... [FILE]...'),
1523 _('[OPTION]... [FILE]...'),
1519 inferrepo=True)
1524 inferrepo=True)
1520 def commit(ui, repo, *pats, **opts):
1525 def commit(ui, repo, *pats, **opts):
1521 """commit the specified files or all outstanding changes
1526 """commit the specified files or all outstanding changes
1522
1527
1523 Commit changes to the given files into the repository. Unlike a
1528 Commit changes to the given files into the repository. Unlike a
1524 centralized SCM, this operation is a local operation. See
1529 centralized SCM, this operation is a local operation. See
1525 :hg:`push` for a way to actively distribute your changes.
1530 :hg:`push` for a way to actively distribute your changes.
1526
1531
1527 If a list of files is omitted, all changes reported by :hg:`status`
1532 If a list of files is omitted, all changes reported by :hg:`status`
1528 will be committed.
1533 will be committed.
1529
1534
1530 If you are committing the result of a merge, do not provide any
1535 If you are committing the result of a merge, do not provide any
1531 filenames or -I/-X filters.
1536 filenames or -I/-X filters.
1532
1537
1533 If no commit message is specified, Mercurial starts your
1538 If no commit message is specified, Mercurial starts your
1534 configured editor where you can enter a message. In case your
1539 configured editor where you can enter a message. In case your
1535 commit fails, you will find a backup of your message in
1540 commit fails, you will find a backup of your message in
1536 ``.hg/last-message.txt``.
1541 ``.hg/last-message.txt``.
1537
1542
1538 The --close-branch flag can be used to mark the current branch
1543 The --close-branch flag can be used to mark the current branch
1539 head closed. When all heads of a branch are closed, the branch
1544 head closed. When all heads of a branch are closed, the branch
1540 will be considered closed and no longer listed.
1545 will be considered closed and no longer listed.
1541
1546
1542 The --amend flag can be used to amend the parent of the
1547 The --amend flag can be used to amend the parent of the
1543 working directory with a new commit that contains the changes
1548 working directory with a new commit that contains the changes
1544 in the parent in addition to those currently reported by :hg:`status`,
1549 in the parent in addition to those currently reported by :hg:`status`,
1545 if there are any. The old commit is stored in a backup bundle in
1550 if there are any. The old commit is stored in a backup bundle in
1546 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
1551 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
1547 on how to restore it).
1552 on how to restore it).
1548
1553
1549 Message, user and date are taken from the amended commit unless
1554 Message, user and date are taken from the amended commit unless
1550 specified. When a message isn't specified on the command line,
1555 specified. When a message isn't specified on the command line,
1551 the editor will open with the message of the amended commit.
1556 the editor will open with the message of the amended commit.
1552
1557
1553 It is not possible to amend public changesets (see :hg:`help phases`)
1558 It is not possible to amend public changesets (see :hg:`help phases`)
1554 or changesets that have children.
1559 or changesets that have children.
1555
1560
1556 See :hg:`help dates` for a list of formats valid for -d/--date.
1561 See :hg:`help dates` for a list of formats valid for -d/--date.
1557
1562
1558 Returns 0 on success, 1 if nothing changed.
1563 Returns 0 on success, 1 if nothing changed.
1559
1564
1560 .. container:: verbose
1565 .. container:: verbose
1561
1566
1562 Examples:
1567 Examples:
1563
1568
1564 - commit all files ending in .py::
1569 - commit all files ending in .py::
1565
1570
1566 hg commit --include "set:**.py"
1571 hg commit --include "set:**.py"
1567
1572
1568 - commit all non-binary files::
1573 - commit all non-binary files::
1569
1574
1570 hg commit --exclude "set:binary()"
1575 hg commit --exclude "set:binary()"
1571
1576
1572 - amend the current commit and set the date to now::
1577 - amend the current commit and set the date to now::
1573
1578
1574 hg commit --amend --date now
1579 hg commit --amend --date now
1575 """
1580 """
1576 wlock = lock = None
1581 wlock = lock = None
1577 try:
1582 try:
1578 wlock = repo.wlock()
1583 wlock = repo.wlock()
1579 lock = repo.lock()
1584 lock = repo.lock()
1580 return _docommit(ui, repo, *pats, **opts)
1585 return _docommit(ui, repo, *pats, **opts)
1581 finally:
1586 finally:
1582 release(lock, wlock)
1587 release(lock, wlock)
1583
1588
1584 def _docommit(ui, repo, *pats, **opts):
1589 def _docommit(ui, repo, *pats, **opts):
1585 if opts.get(r'interactive'):
1590 if opts.get(r'interactive'):
1586 opts.pop(r'interactive')
1591 opts.pop(r'interactive')
1587 ret = cmdutil.dorecord(ui, repo, commit, None, False,
1592 ret = cmdutil.dorecord(ui, repo, commit, None, False,
1588 cmdutil.recordfilter, *pats,
1593 cmdutil.recordfilter, *pats,
1589 **opts)
1594 **opts)
1590 # ret can be 0 (no changes to record) or the value returned by
1595 # ret can be 0 (no changes to record) or the value returned by
1591 # commit(), 1 if nothing changed or None on success.
1596 # commit(), 1 if nothing changed or None on success.
1592 return 1 if ret == 0 else ret
1597 return 1 if ret == 0 else ret
1593
1598
1594 opts = pycompat.byteskwargs(opts)
1599 opts = pycompat.byteskwargs(opts)
1595 if opts.get('subrepos'):
1600 if opts.get('subrepos'):
1596 if opts.get('amend'):
1601 if opts.get('amend'):
1597 raise error.Abort(_('cannot amend with --subrepos'))
1602 raise error.Abort(_('cannot amend with --subrepos'))
1598 # Let --subrepos on the command line override config setting.
1603 # Let --subrepos on the command line override config setting.
1599 ui.setconfig('ui', 'commitsubrepos', True, 'commit')
1604 ui.setconfig('ui', 'commitsubrepos', True, 'commit')
1600
1605
1601 cmdutil.checkunfinished(repo, commit=True)
1606 cmdutil.checkunfinished(repo, commit=True)
1602
1607
1603 branch = repo[None].branch()
1608 branch = repo[None].branch()
1604 bheads = repo.branchheads(branch)
1609 bheads = repo.branchheads(branch)
1605
1610
1606 extra = {}
1611 extra = {}
1607 if opts.get('close_branch'):
1612 if opts.get('close_branch'):
1608 extra['close'] = 1
1613 extra['close'] = 1
1609
1614
1610 if not bheads:
1615 if not bheads:
1611 raise error.Abort(_('can only close branch heads'))
1616 raise error.Abort(_('can only close branch heads'))
1612 elif opts.get('amend'):
1617 elif opts.get('amend'):
1613 if repo[None].parents()[0].p1().branch() != branch and \
1618 if repo[None].parents()[0].p1().branch() != branch and \
1614 repo[None].parents()[0].p2().branch() != branch:
1619 repo[None].parents()[0].p2().branch() != branch:
1615 raise error.Abort(_('can only close branch heads'))
1620 raise error.Abort(_('can only close branch heads'))
1616
1621
1617 if opts.get('amend'):
1622 if opts.get('amend'):
1618 if ui.configbool('ui', 'commitsubrepos'):
1623 if ui.configbool('ui', 'commitsubrepos'):
1619 raise error.Abort(_('cannot amend with ui.commitsubrepos enabled'))
1624 raise error.Abort(_('cannot amend with ui.commitsubrepos enabled'))
1620
1625
1621 old = repo['.']
1626 old = repo['.']
1622 if not old.mutable():
1627 if not old.mutable():
1623 raise error.Abort(_('cannot amend public changesets'))
1628 raise error.Abort(_('cannot amend public changesets'))
1624 if len(repo[None].parents()) > 1:
1629 if len(repo[None].parents()) > 1:
1625 raise error.Abort(_('cannot amend while merging'))
1630 raise error.Abort(_('cannot amend while merging'))
1626 allowunstable = obsolete.isenabled(repo, obsolete.allowunstableopt)
1631 allowunstable = obsolete.isenabled(repo, obsolete.allowunstableopt)
1627 if not allowunstable and old.children():
1632 if not allowunstable and old.children():
1628 raise error.Abort(_('cannot amend changeset with children'))
1633 raise error.Abort(_('cannot amend changeset with children'))
1629
1634
1630 # Currently histedit gets confused if an amend happens while histedit
1635 # Currently histedit gets confused if an amend happens while histedit
1631 # is in progress. Since we have a checkunfinished command, we are
1636 # is in progress. Since we have a checkunfinished command, we are
1632 # temporarily honoring it.
1637 # temporarily honoring it.
1633 #
1638 #
1634 # Note: eventually this guard will be removed. Please do not expect
1639 # Note: eventually this guard will be removed. Please do not expect
1635 # this behavior to remain.
1640 # this behavior to remain.
1636 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
1641 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
1637 cmdutil.checkunfinished(repo)
1642 cmdutil.checkunfinished(repo)
1638
1643
1639 # commitfunc is used only for temporary amend commit by cmdutil.amend
1644 # commitfunc is used only for temporary amend commit by cmdutil.amend
1640 def commitfunc(ui, repo, message, match, opts):
1645 def commitfunc(ui, repo, message, match, opts):
1641 return repo.commit(message,
1646 return repo.commit(message,
1642 opts.get('user') or old.user(),
1647 opts.get('user') or old.user(),
1643 opts.get('date') or old.date(),
1648 opts.get('date') or old.date(),
1644 match,
1649 match,
1645 extra=extra)
1650 extra=extra)
1646
1651
1647 node = cmdutil.amend(ui, repo, commitfunc, old, extra, pats, opts)
1652 node = cmdutil.amend(ui, repo, commitfunc, old, extra, pats, opts)
1648 if node == old.node():
1653 if node == old.node():
1649 ui.status(_("nothing changed\n"))
1654 ui.status(_("nothing changed\n"))
1650 return 1
1655 return 1
1651 else:
1656 else:
1652 def commitfunc(ui, repo, message, match, opts):
1657 def commitfunc(ui, repo, message, match, opts):
1653 overrides = {}
1658 overrides = {}
1654 if opts.get('secret'):
1659 if opts.get('secret'):
1655 overrides[('phases', 'new-commit')] = 'secret'
1660 overrides[('phases', 'new-commit')] = 'secret'
1656
1661
1657 baseui = repo.baseui
1662 baseui = repo.baseui
1658 with baseui.configoverride(overrides, 'commit'):
1663 with baseui.configoverride(overrides, 'commit'):
1659 with ui.configoverride(overrides, 'commit'):
1664 with ui.configoverride(overrides, 'commit'):
1660 editform = cmdutil.mergeeditform(repo[None],
1665 editform = cmdutil.mergeeditform(repo[None],
1661 'commit.normal')
1666 'commit.normal')
1662 editor = cmdutil.getcommiteditor(
1667 editor = cmdutil.getcommiteditor(
1663 editform=editform, **pycompat.strkwargs(opts))
1668 editform=editform, **pycompat.strkwargs(opts))
1664 return repo.commit(message,
1669 return repo.commit(message,
1665 opts.get('user'),
1670 opts.get('user'),
1666 opts.get('date'),
1671 opts.get('date'),
1667 match,
1672 match,
1668 editor=editor,
1673 editor=editor,
1669 extra=extra)
1674 extra=extra)
1670
1675
1671 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
1676 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
1672
1677
1673 if not node:
1678 if not node:
1674 stat = cmdutil.postcommitstatus(repo, pats, opts)
1679 stat = cmdutil.postcommitstatus(repo, pats, opts)
1675 if stat[3]:
1680 if stat[3]:
1676 ui.status(_("nothing changed (%d missing files, see "
1681 ui.status(_("nothing changed (%d missing files, see "
1677 "'hg status')\n") % len(stat[3]))
1682 "'hg status')\n") % len(stat[3]))
1678 else:
1683 else:
1679 ui.status(_("nothing changed\n"))
1684 ui.status(_("nothing changed\n"))
1680 return 1
1685 return 1
1681
1686
1682 cmdutil.commitstatus(repo, node, branch, bheads, opts)
1687 cmdutil.commitstatus(repo, node, branch, bheads, opts)
1683
1688
1684 @command('config|showconfig|debugconfig',
1689 @command('config|showconfig|debugconfig',
1685 [('u', 'untrusted', None, _('show untrusted configuration options')),
1690 [('u', 'untrusted', None, _('show untrusted configuration options')),
1686 ('e', 'edit', None, _('edit user config')),
1691 ('e', 'edit', None, _('edit user config')),
1687 ('l', 'local', None, _('edit repository config')),
1692 ('l', 'local', None, _('edit repository config')),
1688 ('g', 'global', None, _('edit global config'))] + formatteropts,
1693 ('g', 'global', None, _('edit global config'))] + formatteropts,
1689 _('[-u] [NAME]...'),
1694 _('[-u] [NAME]...'),
1690 optionalrepo=True)
1695 optionalrepo=True)
1691 def config(ui, repo, *values, **opts):
1696 def config(ui, repo, *values, **opts):
1692 """show combined config settings from all hgrc files
1697 """show combined config settings from all hgrc files
1693
1698
1694 With no arguments, print names and values of all config items.
1699 With no arguments, print names and values of all config items.
1695
1700
1696 With one argument of the form section.name, print just the value
1701 With one argument of the form section.name, print just the value
1697 of that config item.
1702 of that config item.
1698
1703
1699 With multiple arguments, print names and values of all config
1704 With multiple arguments, print names and values of all config
1700 items with matching section names.
1705 items with matching section names.
1701
1706
1702 With --edit, start an editor on the user-level config file. With
1707 With --edit, start an editor on the user-level config file. With
1703 --global, edit the system-wide config file. With --local, edit the
1708 --global, edit the system-wide config file. With --local, edit the
1704 repository-level config file.
1709 repository-level config file.
1705
1710
1706 With --debug, the source (filename and line number) is printed
1711 With --debug, the source (filename and line number) is printed
1707 for each config item.
1712 for each config item.
1708
1713
1709 See :hg:`help config` for more information about config files.
1714 See :hg:`help config` for more information about config files.
1710
1715
1711 Returns 0 on success, 1 if NAME does not exist.
1716 Returns 0 on success, 1 if NAME does not exist.
1712
1717
1713 """
1718 """
1714
1719
1715 opts = pycompat.byteskwargs(opts)
1720 opts = pycompat.byteskwargs(opts)
1716 if opts.get('edit') or opts.get('local') or opts.get('global'):
1721 if opts.get('edit') or opts.get('local') or opts.get('global'):
1717 if opts.get('local') and opts.get('global'):
1722 if opts.get('local') and opts.get('global'):
1718 raise error.Abort(_("can't use --local and --global together"))
1723 raise error.Abort(_("can't use --local and --global together"))
1719
1724
1720 if opts.get('local'):
1725 if opts.get('local'):
1721 if not repo:
1726 if not repo:
1722 raise error.Abort(_("can't use --local outside a repository"))
1727 raise error.Abort(_("can't use --local outside a repository"))
1723 paths = [repo.vfs.join('hgrc')]
1728 paths = [repo.vfs.join('hgrc')]
1724 elif opts.get('global'):
1729 elif opts.get('global'):
1725 paths = rcutil.systemrcpath()
1730 paths = rcutil.systemrcpath()
1726 else:
1731 else:
1727 paths = rcutil.userrcpath()
1732 paths = rcutil.userrcpath()
1728
1733
1729 for f in paths:
1734 for f in paths:
1730 if os.path.exists(f):
1735 if os.path.exists(f):
1731 break
1736 break
1732 else:
1737 else:
1733 if opts.get('global'):
1738 if opts.get('global'):
1734 samplehgrc = uimod.samplehgrcs['global']
1739 samplehgrc = uimod.samplehgrcs['global']
1735 elif opts.get('local'):
1740 elif opts.get('local'):
1736 samplehgrc = uimod.samplehgrcs['local']
1741 samplehgrc = uimod.samplehgrcs['local']
1737 else:
1742 else:
1738 samplehgrc = uimod.samplehgrcs['user']
1743 samplehgrc = uimod.samplehgrcs['user']
1739
1744
1740 f = paths[0]
1745 f = paths[0]
1741 fp = open(f, "w")
1746 fp = open(f, "w")
1742 fp.write(samplehgrc)
1747 fp.write(samplehgrc)
1743 fp.close()
1748 fp.close()
1744
1749
1745 editor = ui.geteditor()
1750 editor = ui.geteditor()
1746 ui.system("%s \"%s\"" % (editor, f),
1751 ui.system("%s \"%s\"" % (editor, f),
1747 onerr=error.Abort, errprefix=_("edit failed"),
1752 onerr=error.Abort, errprefix=_("edit failed"),
1748 blockedtag='config_edit')
1753 blockedtag='config_edit')
1749 return
1754 return
1750 ui.pager('config')
1755 ui.pager('config')
1751 fm = ui.formatter('config', opts)
1756 fm = ui.formatter('config', opts)
1752 for t, f in rcutil.rccomponents():
1757 for t, f in rcutil.rccomponents():
1753 if t == 'path':
1758 if t == 'path':
1754 ui.debug('read config from: %s\n' % f)
1759 ui.debug('read config from: %s\n' % f)
1755 elif t == 'items':
1760 elif t == 'items':
1756 for section, name, value, source in f:
1761 for section, name, value, source in f:
1757 ui.debug('set config by: %s\n' % source)
1762 ui.debug('set config by: %s\n' % source)
1758 else:
1763 else:
1759 raise error.ProgrammingError('unknown rctype: %s' % t)
1764 raise error.ProgrammingError('unknown rctype: %s' % t)
1760 untrusted = bool(opts.get('untrusted'))
1765 untrusted = bool(opts.get('untrusted'))
1761 if values:
1766 if values:
1762 sections = [v for v in values if '.' not in v]
1767 sections = [v for v in values if '.' not in v]
1763 items = [v for v in values if '.' in v]
1768 items = [v for v in values if '.' in v]
1764 if len(items) > 1 or items and sections:
1769 if len(items) > 1 or items and sections:
1765 raise error.Abort(_('only one config item permitted'))
1770 raise error.Abort(_('only one config item permitted'))
1766 matched = False
1771 matched = False
1767 for section, name, value in ui.walkconfig(untrusted=untrusted):
1772 for section, name, value in ui.walkconfig(untrusted=untrusted):
1768 source = ui.configsource(section, name, untrusted)
1773 source = ui.configsource(section, name, untrusted)
1769 value = pycompat.bytestr(value)
1774 value = pycompat.bytestr(value)
1770 if fm.isplain():
1775 if fm.isplain():
1771 source = source or 'none'
1776 source = source or 'none'
1772 value = value.replace('\n', '\\n')
1777 value = value.replace('\n', '\\n')
1773 entryname = section + '.' + name
1778 entryname = section + '.' + name
1774 if values:
1779 if values:
1775 for v in values:
1780 for v in values:
1776 if v == section:
1781 if v == section:
1777 fm.startitem()
1782 fm.startitem()
1778 fm.condwrite(ui.debugflag, 'source', '%s: ', source)
1783 fm.condwrite(ui.debugflag, 'source', '%s: ', source)
1779 fm.write('name value', '%s=%s\n', entryname, value)
1784 fm.write('name value', '%s=%s\n', entryname, value)
1780 matched = True
1785 matched = True
1781 elif v == entryname:
1786 elif v == entryname:
1782 fm.startitem()
1787 fm.startitem()
1783 fm.condwrite(ui.debugflag, 'source', '%s: ', source)
1788 fm.condwrite(ui.debugflag, 'source', '%s: ', source)
1784 fm.write('value', '%s\n', value)
1789 fm.write('value', '%s\n', value)
1785 fm.data(name=entryname)
1790 fm.data(name=entryname)
1786 matched = True
1791 matched = True
1787 else:
1792 else:
1788 fm.startitem()
1793 fm.startitem()
1789 fm.condwrite(ui.debugflag, 'source', '%s: ', source)
1794 fm.condwrite(ui.debugflag, 'source', '%s: ', source)
1790 fm.write('name value', '%s=%s\n', entryname, value)
1795 fm.write('name value', '%s=%s\n', entryname, value)
1791 matched = True
1796 matched = True
1792 fm.end()
1797 fm.end()
1793 if matched:
1798 if matched:
1794 return 0
1799 return 0
1795 return 1
1800 return 1
1796
1801
1797 @command('copy|cp',
1802 @command('copy|cp',
1798 [('A', 'after', None, _('record a copy that has already occurred')),
1803 [('A', 'after', None, _('record a copy that has already occurred')),
1799 ('f', 'force', None, _('forcibly copy over an existing managed file')),
1804 ('f', 'force', None, _('forcibly copy over an existing managed file')),
1800 ] + walkopts + dryrunopts,
1805 ] + walkopts + dryrunopts,
1801 _('[OPTION]... [SOURCE]... DEST'))
1806 _('[OPTION]... [SOURCE]... DEST'))
1802 def copy(ui, repo, *pats, **opts):
1807 def copy(ui, repo, *pats, **opts):
1803 """mark files as copied for the next commit
1808 """mark files as copied for the next commit
1804
1809
1805 Mark dest as having copies of source files. If dest is a
1810 Mark dest as having copies of source files. If dest is a
1806 directory, copies are put in that directory. If dest is a file,
1811 directory, copies are put in that directory. If dest is a file,
1807 the source must be a single file.
1812 the source must be a single file.
1808
1813
1809 By default, this command copies the contents of files as they
1814 By default, this command copies the contents of files as they
1810 exist in the working directory. If invoked with -A/--after, the
1815 exist in the working directory. If invoked with -A/--after, the
1811 operation is recorded, but no copying is performed.
1816 operation is recorded, but no copying is performed.
1812
1817
1813 This command takes effect with the next commit. To undo a copy
1818 This command takes effect with the next commit. To undo a copy
1814 before that, see :hg:`revert`.
1819 before that, see :hg:`revert`.
1815
1820
1816 Returns 0 on success, 1 if errors are encountered.
1821 Returns 0 on success, 1 if errors are encountered.
1817 """
1822 """
1818 opts = pycompat.byteskwargs(opts)
1823 opts = pycompat.byteskwargs(opts)
1819 with repo.wlock(False):
1824 with repo.wlock(False):
1820 return cmdutil.copy(ui, repo, pats, opts)
1825 return cmdutil.copy(ui, repo, pats, opts)
1821
1826
1822 @command('debugcommands', [], _('[COMMAND]'), norepo=True)
1827 @command('debugcommands', [], _('[COMMAND]'), norepo=True)
1823 def debugcommands(ui, cmd='', *args):
1828 def debugcommands(ui, cmd='', *args):
1824 """list all available commands and options"""
1829 """list all available commands and options"""
1825 for cmd, vals in sorted(table.iteritems()):
1830 for cmd, vals in sorted(table.iteritems()):
1826 cmd = cmd.split('|')[0].strip('^')
1831 cmd = cmd.split('|')[0].strip('^')
1827 opts = ', '.join([i[1] for i in vals[1]])
1832 opts = ', '.join([i[1] for i in vals[1]])
1828 ui.write('%s: %s\n' % (cmd, opts))
1833 ui.write('%s: %s\n' % (cmd, opts))
1829
1834
1830 @command('debugcomplete',
1835 @command('debugcomplete',
1831 [('o', 'options', None, _('show the command options'))],
1836 [('o', 'options', None, _('show the command options'))],
1832 _('[-o] CMD'),
1837 _('[-o] CMD'),
1833 norepo=True)
1838 norepo=True)
1834 def debugcomplete(ui, cmd='', **opts):
1839 def debugcomplete(ui, cmd='', **opts):
1835 """returns the completion list associated with the given command"""
1840 """returns the completion list associated with the given command"""
1836
1841
1837 if opts.get('options'):
1842 if opts.get('options'):
1838 options = []
1843 options = []
1839 otables = [globalopts]
1844 otables = [globalopts]
1840 if cmd:
1845 if cmd:
1841 aliases, entry = cmdutil.findcmd(cmd, table, False)
1846 aliases, entry = cmdutil.findcmd(cmd, table, False)
1842 otables.append(entry[1])
1847 otables.append(entry[1])
1843 for t in otables:
1848 for t in otables:
1844 for o in t:
1849 for o in t:
1845 if "(DEPRECATED)" in o[3]:
1850 if "(DEPRECATED)" in o[3]:
1846 continue
1851 continue
1847 if o[0]:
1852 if o[0]:
1848 options.append('-%s' % o[0])
1853 options.append('-%s' % o[0])
1849 options.append('--%s' % o[1])
1854 options.append('--%s' % o[1])
1850 ui.write("%s\n" % "\n".join(options))
1855 ui.write("%s\n" % "\n".join(options))
1851 return
1856 return
1852
1857
1853 cmdlist, unused_allcmds = cmdutil.findpossible(cmd, table)
1858 cmdlist, unused_allcmds = cmdutil.findpossible(cmd, table)
1854 if ui.verbose:
1859 if ui.verbose:
1855 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
1860 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
1856 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
1861 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
1857
1862
1858 @command('^diff',
1863 @command('^diff',
1859 [('r', 'rev', [], _('revision'), _('REV')),
1864 [('r', 'rev', [], _('revision'), _('REV')),
1860 ('c', 'change', '', _('change made by revision'), _('REV'))
1865 ('c', 'change', '', _('change made by revision'), _('REV'))
1861 ] + diffopts + diffopts2 + walkopts + subrepoopts,
1866 ] + diffopts + diffopts2 + walkopts + subrepoopts,
1862 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'),
1867 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'),
1863 inferrepo=True)
1868 inferrepo=True)
1864 def diff(ui, repo, *pats, **opts):
1869 def diff(ui, repo, *pats, **opts):
1865 """diff repository (or selected files)
1870 """diff repository (or selected files)
1866
1871
1867 Show differences between revisions for the specified files.
1872 Show differences between revisions for the specified files.
1868
1873
1869 Differences between files are shown using the unified diff format.
1874 Differences between files are shown using the unified diff format.
1870
1875
1871 .. note::
1876 .. note::
1872
1877
1873 :hg:`diff` may generate unexpected results for merges, as it will
1878 :hg:`diff` may generate unexpected results for merges, as it will
1874 default to comparing against the working directory's first
1879 default to comparing against the working directory's first
1875 parent changeset if no revisions are specified.
1880 parent changeset if no revisions are specified.
1876
1881
1877 When two revision arguments are given, then changes are shown
1882 When two revision arguments are given, then changes are shown
1878 between those revisions. If only one revision is specified then
1883 between those revisions. If only one revision is specified then
1879 that revision is compared to the working directory, and, when no
1884 that revision is compared to the working directory, and, when no
1880 revisions are specified, the working directory files are compared
1885 revisions are specified, the working directory files are compared
1881 to its first parent.
1886 to its first parent.
1882
1887
1883 Alternatively you can specify -c/--change with a revision to see
1888 Alternatively you can specify -c/--change with a revision to see
1884 the changes in that changeset relative to its first parent.
1889 the changes in that changeset relative to its first parent.
1885
1890
1886 Without the -a/--text option, diff will avoid generating diffs of
1891 Without the -a/--text option, diff will avoid generating diffs of
1887 files it detects as binary. With -a, diff will generate a diff
1892 files it detects as binary. With -a, diff will generate a diff
1888 anyway, probably with undesirable results.
1893 anyway, probably with undesirable results.
1889
1894
1890 Use the -g/--git option to generate diffs in the git extended diff
1895 Use the -g/--git option to generate diffs in the git extended diff
1891 format. For more information, read :hg:`help diffs`.
1896 format. For more information, read :hg:`help diffs`.
1892
1897
1893 .. container:: verbose
1898 .. container:: verbose
1894
1899
1895 Examples:
1900 Examples:
1896
1901
1897 - compare a file in the current working directory to its parent::
1902 - compare a file in the current working directory to its parent::
1898
1903
1899 hg diff foo.c
1904 hg diff foo.c
1900
1905
1901 - compare two historical versions of a directory, with rename info::
1906 - compare two historical versions of a directory, with rename info::
1902
1907
1903 hg diff --git -r 1.0:1.2 lib/
1908 hg diff --git -r 1.0:1.2 lib/
1904
1909
1905 - get change stats relative to the last change on some date::
1910 - get change stats relative to the last change on some date::
1906
1911
1907 hg diff --stat -r "date('may 2')"
1912 hg diff --stat -r "date('may 2')"
1908
1913
1909 - diff all newly-added files that contain a keyword::
1914 - diff all newly-added files that contain a keyword::
1910
1915
1911 hg diff "set:added() and grep(GNU)"
1916 hg diff "set:added() and grep(GNU)"
1912
1917
1913 - compare a revision and its parents::
1918 - compare a revision and its parents::
1914
1919
1915 hg diff -c 9353 # compare against first parent
1920 hg diff -c 9353 # compare against first parent
1916 hg diff -r 9353^:9353 # same using revset syntax
1921 hg diff -r 9353^:9353 # same using revset syntax
1917 hg diff -r 9353^2:9353 # compare against the second parent
1922 hg diff -r 9353^2:9353 # compare against the second parent
1918
1923
1919 Returns 0 on success.
1924 Returns 0 on success.
1920 """
1925 """
1921
1926
1922 opts = pycompat.byteskwargs(opts)
1927 opts = pycompat.byteskwargs(opts)
1923 revs = opts.get('rev')
1928 revs = opts.get('rev')
1924 change = opts.get('change')
1929 change = opts.get('change')
1925 stat = opts.get('stat')
1930 stat = opts.get('stat')
1926 reverse = opts.get('reverse')
1931 reverse = opts.get('reverse')
1927
1932
1928 if revs and change:
1933 if revs and change:
1929 msg = _('cannot specify --rev and --change at the same time')
1934 msg = _('cannot specify --rev and --change at the same time')
1930 raise error.Abort(msg)
1935 raise error.Abort(msg)
1931 elif change:
1936 elif change:
1932 node2 = scmutil.revsingle(repo, change, None).node()
1937 node2 = scmutil.revsingle(repo, change, None).node()
1933 node1 = repo[node2].p1().node()
1938 node1 = repo[node2].p1().node()
1934 else:
1939 else:
1935 node1, node2 = scmutil.revpair(repo, revs)
1940 node1, node2 = scmutil.revpair(repo, revs)
1936
1941
1937 if reverse:
1942 if reverse:
1938 node1, node2 = node2, node1
1943 node1, node2 = node2, node1
1939
1944
1940 diffopts = patch.diffallopts(ui, opts)
1945 diffopts = patch.diffallopts(ui, opts)
1941 m = scmutil.match(repo[node2], pats, opts)
1946 m = scmutil.match(repo[node2], pats, opts)
1942 ui.pager('diff')
1947 ui.pager('diff')
1943 cmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
1948 cmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
1944 listsubrepos=opts.get('subrepos'),
1949 listsubrepos=opts.get('subrepos'),
1945 root=opts.get('root'))
1950 root=opts.get('root'))
1946
1951
1947 @command('^export',
1952 @command('^export',
1948 [('o', 'output', '',
1953 [('o', 'output', '',
1949 _('print output to file with formatted name'), _('FORMAT')),
1954 _('print output to file with formatted name'), _('FORMAT')),
1950 ('', 'switch-parent', None, _('diff against the second parent')),
1955 ('', 'switch-parent', None, _('diff against the second parent')),
1951 ('r', 'rev', [], _('revisions to export'), _('REV')),
1956 ('r', 'rev', [], _('revisions to export'), _('REV')),
1952 ] + diffopts,
1957 ] + diffopts,
1953 _('[OPTION]... [-o OUTFILESPEC] [-r] [REV]...'))
1958 _('[OPTION]... [-o OUTFILESPEC] [-r] [REV]...'))
1954 def export(ui, repo, *changesets, **opts):
1959 def export(ui, repo, *changesets, **opts):
1955 """dump the header and diffs for one or more changesets
1960 """dump the header and diffs for one or more changesets
1956
1961
1957 Print the changeset header and diffs for one or more revisions.
1962 Print the changeset header and diffs for one or more revisions.
1958 If no revision is given, the parent of the working directory is used.
1963 If no revision is given, the parent of the working directory is used.
1959
1964
1960 The information shown in the changeset header is: author, date,
1965 The information shown in the changeset header is: author, date,
1961 branch name (if non-default), changeset hash, parent(s) and commit
1966 branch name (if non-default), changeset hash, parent(s) and commit
1962 comment.
1967 comment.
1963
1968
1964 .. note::
1969 .. note::
1965
1970
1966 :hg:`export` may generate unexpected diff output for merge
1971 :hg:`export` may generate unexpected diff output for merge
1967 changesets, as it will compare the merge changeset against its
1972 changesets, as it will compare the merge changeset against its
1968 first parent only.
1973 first parent only.
1969
1974
1970 Output may be to a file, in which case the name of the file is
1975 Output may be to a file, in which case the name of the file is
1971 given using a format string. The formatting rules are as follows:
1976 given using a format string. The formatting rules are as follows:
1972
1977
1973 :``%%``: literal "%" character
1978 :``%%``: literal "%" character
1974 :``%H``: changeset hash (40 hexadecimal digits)
1979 :``%H``: changeset hash (40 hexadecimal digits)
1975 :``%N``: number of patches being generated
1980 :``%N``: number of patches being generated
1976 :``%R``: changeset revision number
1981 :``%R``: changeset revision number
1977 :``%b``: basename of the exporting repository
1982 :``%b``: basename of the exporting repository
1978 :``%h``: short-form changeset hash (12 hexadecimal digits)
1983 :``%h``: short-form changeset hash (12 hexadecimal digits)
1979 :``%m``: first line of the commit message (only alphanumeric characters)
1984 :``%m``: first line of the commit message (only alphanumeric characters)
1980 :``%n``: zero-padded sequence number, starting at 1
1985 :``%n``: zero-padded sequence number, starting at 1
1981 :``%r``: zero-padded changeset revision number
1986 :``%r``: zero-padded changeset revision number
1982
1987
1983 Without the -a/--text option, export will avoid generating diffs
1988 Without the -a/--text option, export will avoid generating diffs
1984 of files it detects as binary. With -a, export will generate a
1989 of files it detects as binary. With -a, export will generate a
1985 diff anyway, probably with undesirable results.
1990 diff anyway, probably with undesirable results.
1986
1991
1987 Use the -g/--git option to generate diffs in the git extended diff
1992 Use the -g/--git option to generate diffs in the git extended diff
1988 format. See :hg:`help diffs` for more information.
1993 format. See :hg:`help diffs` for more information.
1989
1994
1990 With the --switch-parent option, the diff will be against the
1995 With the --switch-parent option, the diff will be against the
1991 second parent. It can be useful to review a merge.
1996 second parent. It can be useful to review a merge.
1992
1997
1993 .. container:: verbose
1998 .. container:: verbose
1994
1999
1995 Examples:
2000 Examples:
1996
2001
1997 - use export and import to transplant a bugfix to the current
2002 - use export and import to transplant a bugfix to the current
1998 branch::
2003 branch::
1999
2004
2000 hg export -r 9353 | hg import -
2005 hg export -r 9353 | hg import -
2001
2006
2002 - export all the changesets between two revisions to a file with
2007 - export all the changesets between two revisions to a file with
2003 rename information::
2008 rename information::
2004
2009
2005 hg export --git -r 123:150 > changes.txt
2010 hg export --git -r 123:150 > changes.txt
2006
2011
2007 - split outgoing changes into a series of patches with
2012 - split outgoing changes into a series of patches with
2008 descriptive names::
2013 descriptive names::
2009
2014
2010 hg export -r "outgoing()" -o "%n-%m.patch"
2015 hg export -r "outgoing()" -o "%n-%m.patch"
2011
2016
2012 Returns 0 on success.
2017 Returns 0 on success.
2013 """
2018 """
2014 opts = pycompat.byteskwargs(opts)
2019 opts = pycompat.byteskwargs(opts)
2015 changesets += tuple(opts.get('rev', []))
2020 changesets += tuple(opts.get('rev', []))
2016 if not changesets:
2021 if not changesets:
2017 changesets = ['.']
2022 changesets = ['.']
2018 revs = scmutil.revrange(repo, changesets)
2023 revs = scmutil.revrange(repo, changesets)
2019 if not revs:
2024 if not revs:
2020 raise error.Abort(_("export requires at least one changeset"))
2025 raise error.Abort(_("export requires at least one changeset"))
2021 if len(revs) > 1:
2026 if len(revs) > 1:
2022 ui.note(_('exporting patches:\n'))
2027 ui.note(_('exporting patches:\n'))
2023 else:
2028 else:
2024 ui.note(_('exporting patch:\n'))
2029 ui.note(_('exporting patch:\n'))
2025 ui.pager('export')
2030 ui.pager('export')
2026 cmdutil.export(repo, revs, fntemplate=opts.get('output'),
2031 cmdutil.export(repo, revs, fntemplate=opts.get('output'),
2027 switch_parent=opts.get('switch_parent'),
2032 switch_parent=opts.get('switch_parent'),
2028 opts=patch.diffallopts(ui, opts))
2033 opts=patch.diffallopts(ui, opts))
2029
2034
2030 @command('files',
2035 @command('files',
2031 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
2036 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
2032 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
2037 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
2033 ] + walkopts + formatteropts + subrepoopts,
2038 ] + walkopts + formatteropts + subrepoopts,
2034 _('[OPTION]... [FILE]...'))
2039 _('[OPTION]... [FILE]...'))
2035 def files(ui, repo, *pats, **opts):
2040 def files(ui, repo, *pats, **opts):
2036 """list tracked files
2041 """list tracked files
2037
2042
2038 Print files under Mercurial control in the working directory or
2043 Print files under Mercurial control in the working directory or
2039 specified revision for given files (excluding removed files).
2044 specified revision for given files (excluding removed files).
2040 Files can be specified as filenames or filesets.
2045 Files can be specified as filenames or filesets.
2041
2046
2042 If no files are given to match, this command prints the names
2047 If no files are given to match, this command prints the names
2043 of all files under Mercurial control.
2048 of all files under Mercurial control.
2044
2049
2045 .. container:: verbose
2050 .. container:: verbose
2046
2051
2047 Examples:
2052 Examples:
2048
2053
2049 - list all files under the current directory::
2054 - list all files under the current directory::
2050
2055
2051 hg files .
2056 hg files .
2052
2057
2053 - shows sizes and flags for current revision::
2058 - shows sizes and flags for current revision::
2054
2059
2055 hg files -vr .
2060 hg files -vr .
2056
2061
2057 - list all files named README::
2062 - list all files named README::
2058
2063
2059 hg files -I "**/README"
2064 hg files -I "**/README"
2060
2065
2061 - list all binary files::
2066 - list all binary files::
2062
2067
2063 hg files "set:binary()"
2068 hg files "set:binary()"
2064
2069
2065 - find files containing a regular expression::
2070 - find files containing a regular expression::
2066
2071
2067 hg files "set:grep('bob')"
2072 hg files "set:grep('bob')"
2068
2073
2069 - search tracked file contents with xargs and grep::
2074 - search tracked file contents with xargs and grep::
2070
2075
2071 hg files -0 | xargs -0 grep foo
2076 hg files -0 | xargs -0 grep foo
2072
2077
2073 See :hg:`help patterns` and :hg:`help filesets` for more information
2078 See :hg:`help patterns` and :hg:`help filesets` for more information
2074 on specifying file patterns.
2079 on specifying file patterns.
2075
2080
2076 Returns 0 if a match is found, 1 otherwise.
2081 Returns 0 if a match is found, 1 otherwise.
2077
2082
2078 """
2083 """
2079
2084
2080 opts = pycompat.byteskwargs(opts)
2085 opts = pycompat.byteskwargs(opts)
2081 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
2086 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
2082
2087
2083 end = '\n'
2088 end = '\n'
2084 if opts.get('print0'):
2089 if opts.get('print0'):
2085 end = '\0'
2090 end = '\0'
2086 fmt = '%s' + end
2091 fmt = '%s' + end
2087
2092
2088 m = scmutil.match(ctx, pats, opts)
2093 m = scmutil.match(ctx, pats, opts)
2089 ui.pager('files')
2094 ui.pager('files')
2090 with ui.formatter('files', opts) as fm:
2095 with ui.formatter('files', opts) as fm:
2091 return cmdutil.files(ui, ctx, m, fm, fmt, opts.get('subrepos'))
2096 return cmdutil.files(ui, ctx, m, fm, fmt, opts.get('subrepos'))
2092
2097
2093 @command('^forget', walkopts, _('[OPTION]... FILE...'), inferrepo=True)
2098 @command('^forget', walkopts, _('[OPTION]... FILE...'), inferrepo=True)
2094 def forget(ui, repo, *pats, **opts):
2099 def forget(ui, repo, *pats, **opts):
2095 """forget the specified files on the next commit
2100 """forget the specified files on the next commit
2096
2101
2097 Mark the specified files so they will no longer be tracked
2102 Mark the specified files so they will no longer be tracked
2098 after the next commit.
2103 after the next commit.
2099
2104
2100 This only removes files from the current branch, not from the
2105 This only removes files from the current branch, not from the
2101 entire project history, and it does not delete them from the
2106 entire project history, and it does not delete them from the
2102 working directory.
2107 working directory.
2103
2108
2104 To delete the file from the working directory, see :hg:`remove`.
2109 To delete the file from the working directory, see :hg:`remove`.
2105
2110
2106 To undo a forget before the next commit, see :hg:`add`.
2111 To undo a forget before the next commit, see :hg:`add`.
2107
2112
2108 .. container:: verbose
2113 .. container:: verbose
2109
2114
2110 Examples:
2115 Examples:
2111
2116
2112 - forget newly-added binary files::
2117 - forget newly-added binary files::
2113
2118
2114 hg forget "set:added() and binary()"
2119 hg forget "set:added() and binary()"
2115
2120
2116 - forget files that would be excluded by .hgignore::
2121 - forget files that would be excluded by .hgignore::
2117
2122
2118 hg forget "set:hgignore()"
2123 hg forget "set:hgignore()"
2119
2124
2120 Returns 0 on success.
2125 Returns 0 on success.
2121 """
2126 """
2122
2127
2123 opts = pycompat.byteskwargs(opts)
2128 opts = pycompat.byteskwargs(opts)
2124 if not pats:
2129 if not pats:
2125 raise error.Abort(_('no files specified'))
2130 raise error.Abort(_('no files specified'))
2126
2131
2127 m = scmutil.match(repo[None], pats, opts)
2132 m = scmutil.match(repo[None], pats, opts)
2128 rejected = cmdutil.forget(ui, repo, m, prefix="", explicitonly=False)[0]
2133 rejected = cmdutil.forget(ui, repo, m, prefix="", explicitonly=False)[0]
2129 return rejected and 1 or 0
2134 return rejected and 1 or 0
2130
2135
2131 @command(
2136 @command(
2132 'graft',
2137 'graft',
2133 [('r', 'rev', [], _('revisions to graft'), _('REV')),
2138 [('r', 'rev', [], _('revisions to graft'), _('REV')),
2134 ('c', 'continue', False, _('resume interrupted graft')),
2139 ('c', 'continue', False, _('resume interrupted graft')),
2135 ('e', 'edit', False, _('invoke editor on commit messages')),
2140 ('e', 'edit', False, _('invoke editor on commit messages')),
2136 ('', 'log', None, _('append graft info to log message')),
2141 ('', 'log', None, _('append graft info to log message')),
2137 ('f', 'force', False, _('force graft')),
2142 ('f', 'force', False, _('force graft')),
2138 ('D', 'currentdate', False,
2143 ('D', 'currentdate', False,
2139 _('record the current date as commit date')),
2144 _('record the current date as commit date')),
2140 ('U', 'currentuser', False,
2145 ('U', 'currentuser', False,
2141 _('record the current user as committer'), _('DATE'))]
2146 _('record the current user as committer'), _('DATE'))]
2142 + commitopts2 + mergetoolopts + dryrunopts,
2147 + commitopts2 + mergetoolopts + dryrunopts,
2143 _('[OPTION]... [-r REV]... REV...'))
2148 _('[OPTION]... [-r REV]... REV...'))
2144 def graft(ui, repo, *revs, **opts):
2149 def graft(ui, repo, *revs, **opts):
2145 '''copy changes from other branches onto the current branch
2150 '''copy changes from other branches onto the current branch
2146
2151
2147 This command uses Mercurial's merge logic to copy individual
2152 This command uses Mercurial's merge logic to copy individual
2148 changes from other branches without merging branches in the
2153 changes from other branches without merging branches in the
2149 history graph. This is sometimes known as 'backporting' or
2154 history graph. This is sometimes known as 'backporting' or
2150 'cherry-picking'. By default, graft will copy user, date, and
2155 'cherry-picking'. By default, graft will copy user, date, and
2151 description from the source changesets.
2156 description from the source changesets.
2152
2157
2153 Changesets that are ancestors of the current revision, that have
2158 Changesets that are ancestors of the current revision, that have
2154 already been grafted, or that are merges will be skipped.
2159 already been grafted, or that are merges will be skipped.
2155
2160
2156 If --log is specified, log messages will have a comment appended
2161 If --log is specified, log messages will have a comment appended
2157 of the form::
2162 of the form::
2158
2163
2159 (grafted from CHANGESETHASH)
2164 (grafted from CHANGESETHASH)
2160
2165
2161 If --force is specified, revisions will be grafted even if they
2166 If --force is specified, revisions will be grafted even if they
2162 are already ancestors of or have been grafted to the destination.
2167 are already ancestors of or have been grafted to the destination.
2163 This is useful when the revisions have since been backed out.
2168 This is useful when the revisions have since been backed out.
2164
2169
2165 If a graft merge results in conflicts, the graft process is
2170 If a graft merge results in conflicts, the graft process is
2166 interrupted so that the current merge can be manually resolved.
2171 interrupted so that the current merge can be manually resolved.
2167 Once all conflicts are addressed, the graft process can be
2172 Once all conflicts are addressed, the graft process can be
2168 continued with the -c/--continue option.
2173 continued with the -c/--continue option.
2169
2174
2170 .. note::
2175 .. note::
2171
2176
2172 The -c/--continue option does not reapply earlier options, except
2177 The -c/--continue option does not reapply earlier options, except
2173 for --force.
2178 for --force.
2174
2179
2175 .. container:: verbose
2180 .. container:: verbose
2176
2181
2177 Examples:
2182 Examples:
2178
2183
2179 - copy a single change to the stable branch and edit its description::
2184 - copy a single change to the stable branch and edit its description::
2180
2185
2181 hg update stable
2186 hg update stable
2182 hg graft --edit 9393
2187 hg graft --edit 9393
2183
2188
2184 - graft a range of changesets with one exception, updating dates::
2189 - graft a range of changesets with one exception, updating dates::
2185
2190
2186 hg graft -D "2085::2093 and not 2091"
2191 hg graft -D "2085::2093 and not 2091"
2187
2192
2188 - continue a graft after resolving conflicts::
2193 - continue a graft after resolving conflicts::
2189
2194
2190 hg graft -c
2195 hg graft -c
2191
2196
2192 - show the source of a grafted changeset::
2197 - show the source of a grafted changeset::
2193
2198
2194 hg log --debug -r .
2199 hg log --debug -r .
2195
2200
2196 - show revisions sorted by date::
2201 - show revisions sorted by date::
2197
2202
2198 hg log -r "sort(all(), date)"
2203 hg log -r "sort(all(), date)"
2199
2204
2200 See :hg:`help revisions` for more about specifying revisions.
2205 See :hg:`help revisions` for more about specifying revisions.
2201
2206
2202 Returns 0 on successful completion.
2207 Returns 0 on successful completion.
2203 '''
2208 '''
2204 with repo.wlock():
2209 with repo.wlock():
2205 return _dograft(ui, repo, *revs, **opts)
2210 return _dograft(ui, repo, *revs, **opts)
2206
2211
2207 def _dograft(ui, repo, *revs, **opts):
2212 def _dograft(ui, repo, *revs, **opts):
2208 opts = pycompat.byteskwargs(opts)
2213 opts = pycompat.byteskwargs(opts)
2209 if revs and opts.get('rev'):
2214 if revs and opts.get('rev'):
2210 ui.warn(_('warning: inconsistent use of --rev might give unexpected '
2215 ui.warn(_('warning: inconsistent use of --rev might give unexpected '
2211 'revision ordering!\n'))
2216 'revision ordering!\n'))
2212
2217
2213 revs = list(revs)
2218 revs = list(revs)
2214 revs.extend(opts.get('rev'))
2219 revs.extend(opts.get('rev'))
2215
2220
2216 if not opts.get('user') and opts.get('currentuser'):
2221 if not opts.get('user') and opts.get('currentuser'):
2217 opts['user'] = ui.username()
2222 opts['user'] = ui.username()
2218 if not opts.get('date') and opts.get('currentdate'):
2223 if not opts.get('date') and opts.get('currentdate'):
2219 opts['date'] = "%d %d" % util.makedate()
2224 opts['date'] = "%d %d" % util.makedate()
2220
2225
2221 editor = cmdutil.getcommiteditor(editform='graft',
2226 editor = cmdutil.getcommiteditor(editform='graft',
2222 **pycompat.strkwargs(opts))
2227 **pycompat.strkwargs(opts))
2223
2228
2224 cont = False
2229 cont = False
2225 if opts.get('continue'):
2230 if opts.get('continue'):
2226 cont = True
2231 cont = True
2227 if revs:
2232 if revs:
2228 raise error.Abort(_("can't specify --continue and revisions"))
2233 raise error.Abort(_("can't specify --continue and revisions"))
2229 # read in unfinished revisions
2234 # read in unfinished revisions
2230 try:
2235 try:
2231 nodes = repo.vfs.read('graftstate').splitlines()
2236 nodes = repo.vfs.read('graftstate').splitlines()
2232 revs = [repo[node].rev() for node in nodes]
2237 revs = [repo[node].rev() for node in nodes]
2233 except IOError as inst:
2238 except IOError as inst:
2234 if inst.errno != errno.ENOENT:
2239 if inst.errno != errno.ENOENT:
2235 raise
2240 raise
2236 cmdutil.wrongtooltocontinue(repo, _('graft'))
2241 cmdutil.wrongtooltocontinue(repo, _('graft'))
2237 else:
2242 else:
2238 cmdutil.checkunfinished(repo)
2243 cmdutil.checkunfinished(repo)
2239 cmdutil.bailifchanged(repo)
2244 cmdutil.bailifchanged(repo)
2240 if not revs:
2245 if not revs:
2241 raise error.Abort(_('no revisions specified'))
2246 raise error.Abort(_('no revisions specified'))
2242 revs = scmutil.revrange(repo, revs)
2247 revs = scmutil.revrange(repo, revs)
2243
2248
2244 skipped = set()
2249 skipped = set()
2245 # check for merges
2250 # check for merges
2246 for rev in repo.revs('%ld and merge()', revs):
2251 for rev in repo.revs('%ld and merge()', revs):
2247 ui.warn(_('skipping ungraftable merge revision %s\n') % rev)
2252 ui.warn(_('skipping ungraftable merge revision %s\n') % rev)
2248 skipped.add(rev)
2253 skipped.add(rev)
2249 revs = [r for r in revs if r not in skipped]
2254 revs = [r for r in revs if r not in skipped]
2250 if not revs:
2255 if not revs:
2251 return -1
2256 return -1
2252
2257
2253 # Don't check in the --continue case, in effect retaining --force across
2258 # Don't check in the --continue case, in effect retaining --force across
2254 # --continues. That's because without --force, any revisions we decided to
2259 # --continues. That's because without --force, any revisions we decided to
2255 # skip would have been filtered out here, so they wouldn't have made their
2260 # skip would have been filtered out here, so they wouldn't have made their
2256 # way to the graftstate. With --force, any revisions we would have otherwise
2261 # way to the graftstate. With --force, any revisions we would have otherwise
2257 # skipped would not have been filtered out, and if they hadn't been applied
2262 # skipped would not have been filtered out, and if they hadn't been applied
2258 # already, they'd have been in the graftstate.
2263 # already, they'd have been in the graftstate.
2259 if not (cont or opts.get('force')):
2264 if not (cont or opts.get('force')):
2260 # check for ancestors of dest branch
2265 # check for ancestors of dest branch
2261 crev = repo['.'].rev()
2266 crev = repo['.'].rev()
2262 ancestors = repo.changelog.ancestors([crev], inclusive=True)
2267 ancestors = repo.changelog.ancestors([crev], inclusive=True)
2263 # XXX make this lazy in the future
2268 # XXX make this lazy in the future
2264 # don't mutate while iterating, create a copy
2269 # don't mutate while iterating, create a copy
2265 for rev in list(revs):
2270 for rev in list(revs):
2266 if rev in ancestors:
2271 if rev in ancestors:
2267 ui.warn(_('skipping ancestor revision %d:%s\n') %
2272 ui.warn(_('skipping ancestor revision %d:%s\n') %
2268 (rev, repo[rev]))
2273 (rev, repo[rev]))
2269 # XXX remove on list is slow
2274 # XXX remove on list is slow
2270 revs.remove(rev)
2275 revs.remove(rev)
2271 if not revs:
2276 if not revs:
2272 return -1
2277 return -1
2273
2278
2274 # analyze revs for earlier grafts
2279 # analyze revs for earlier grafts
2275 ids = {}
2280 ids = {}
2276 for ctx in repo.set("%ld", revs):
2281 for ctx in repo.set("%ld", revs):
2277 ids[ctx.hex()] = ctx.rev()
2282 ids[ctx.hex()] = ctx.rev()
2278 n = ctx.extra().get('source')
2283 n = ctx.extra().get('source')
2279 if n:
2284 if n:
2280 ids[n] = ctx.rev()
2285 ids[n] = ctx.rev()
2281
2286
2282 # check ancestors for earlier grafts
2287 # check ancestors for earlier grafts
2283 ui.debug('scanning for duplicate grafts\n')
2288 ui.debug('scanning for duplicate grafts\n')
2284
2289
2285 # The only changesets we can be sure doesn't contain grafts of any
2290 # The only changesets we can be sure doesn't contain grafts of any
2286 # revs, are the ones that are common ancestors of *all* revs:
2291 # revs, are the ones that are common ancestors of *all* revs:
2287 for rev in repo.revs('only(%d,ancestor(%ld))', crev, revs):
2292 for rev in repo.revs('only(%d,ancestor(%ld))', crev, revs):
2288 ctx = repo[rev]
2293 ctx = repo[rev]
2289 n = ctx.extra().get('source')
2294 n = ctx.extra().get('source')
2290 if n in ids:
2295 if n in ids:
2291 try:
2296 try:
2292 r = repo[n].rev()
2297 r = repo[n].rev()
2293 except error.RepoLookupError:
2298 except error.RepoLookupError:
2294 r = None
2299 r = None
2295 if r in revs:
2300 if r in revs:
2296 ui.warn(_('skipping revision %d:%s '
2301 ui.warn(_('skipping revision %d:%s '
2297 '(already grafted to %d:%s)\n')
2302 '(already grafted to %d:%s)\n')
2298 % (r, repo[r], rev, ctx))
2303 % (r, repo[r], rev, ctx))
2299 revs.remove(r)
2304 revs.remove(r)
2300 elif ids[n] in revs:
2305 elif ids[n] in revs:
2301 if r is None:
2306 if r is None:
2302 ui.warn(_('skipping already grafted revision %d:%s '
2307 ui.warn(_('skipping already grafted revision %d:%s '
2303 '(%d:%s also has unknown origin %s)\n')
2308 '(%d:%s also has unknown origin %s)\n')
2304 % (ids[n], repo[ids[n]], rev, ctx, n[:12]))
2309 % (ids[n], repo[ids[n]], rev, ctx, n[:12]))
2305 else:
2310 else:
2306 ui.warn(_('skipping already grafted revision %d:%s '
2311 ui.warn(_('skipping already grafted revision %d:%s '
2307 '(%d:%s also has origin %d:%s)\n')
2312 '(%d:%s also has origin %d:%s)\n')
2308 % (ids[n], repo[ids[n]], rev, ctx, r, n[:12]))
2313 % (ids[n], repo[ids[n]], rev, ctx, r, n[:12]))
2309 revs.remove(ids[n])
2314 revs.remove(ids[n])
2310 elif ctx.hex() in ids:
2315 elif ctx.hex() in ids:
2311 r = ids[ctx.hex()]
2316 r = ids[ctx.hex()]
2312 ui.warn(_('skipping already grafted revision %d:%s '
2317 ui.warn(_('skipping already grafted revision %d:%s '
2313 '(was grafted from %d:%s)\n') %
2318 '(was grafted from %d:%s)\n') %
2314 (r, repo[r], rev, ctx))
2319 (r, repo[r], rev, ctx))
2315 revs.remove(r)
2320 revs.remove(r)
2316 if not revs:
2321 if not revs:
2317 return -1
2322 return -1
2318
2323
2319 for pos, ctx in enumerate(repo.set("%ld", revs)):
2324 for pos, ctx in enumerate(repo.set("%ld", revs)):
2320 desc = '%d:%s "%s"' % (ctx.rev(), ctx,
2325 desc = '%d:%s "%s"' % (ctx.rev(), ctx,
2321 ctx.description().split('\n', 1)[0])
2326 ctx.description().split('\n', 1)[0])
2322 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
2327 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
2323 if names:
2328 if names:
2324 desc += ' (%s)' % ' '.join(names)
2329 desc += ' (%s)' % ' '.join(names)
2325 ui.status(_('grafting %s\n') % desc)
2330 ui.status(_('grafting %s\n') % desc)
2326 if opts.get('dry_run'):
2331 if opts.get('dry_run'):
2327 continue
2332 continue
2328
2333
2329 source = ctx.extra().get('source')
2334 source = ctx.extra().get('source')
2330 extra = {}
2335 extra = {}
2331 if source:
2336 if source:
2332 extra['source'] = source
2337 extra['source'] = source
2333 extra['intermediate-source'] = ctx.hex()
2338 extra['intermediate-source'] = ctx.hex()
2334 else:
2339 else:
2335 extra['source'] = ctx.hex()
2340 extra['source'] = ctx.hex()
2336 user = ctx.user()
2341 user = ctx.user()
2337 if opts.get('user'):
2342 if opts.get('user'):
2338 user = opts['user']
2343 user = opts['user']
2339 date = ctx.date()
2344 date = ctx.date()
2340 if opts.get('date'):
2345 if opts.get('date'):
2341 date = opts['date']
2346 date = opts['date']
2342 message = ctx.description()
2347 message = ctx.description()
2343 if opts.get('log'):
2348 if opts.get('log'):
2344 message += '\n(grafted from %s)' % ctx.hex()
2349 message += '\n(grafted from %s)' % ctx.hex()
2345
2350
2346 # we don't merge the first commit when continuing
2351 # we don't merge the first commit when continuing
2347 if not cont:
2352 if not cont:
2348 # perform the graft merge with p1(rev) as 'ancestor'
2353 # perform the graft merge with p1(rev) as 'ancestor'
2349 try:
2354 try:
2350 # ui.forcemerge is an internal variable, do not document
2355 # ui.forcemerge is an internal variable, do not document
2351 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
2356 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
2352 'graft')
2357 'graft')
2353 stats = mergemod.graft(repo, ctx, ctx.p1(),
2358 stats = mergemod.graft(repo, ctx, ctx.p1(),
2354 ['local', 'graft'])
2359 ['local', 'graft'])
2355 finally:
2360 finally:
2356 repo.ui.setconfig('ui', 'forcemerge', '', 'graft')
2361 repo.ui.setconfig('ui', 'forcemerge', '', 'graft')
2357 # report any conflicts
2362 # report any conflicts
2358 if stats and stats[3] > 0:
2363 if stats and stats[3] > 0:
2359 # write out state for --continue
2364 # write out state for --continue
2360 nodelines = [repo[rev].hex() + "\n" for rev in revs[pos:]]
2365 nodelines = [repo[rev].hex() + "\n" for rev in revs[pos:]]
2361 repo.vfs.write('graftstate', ''.join(nodelines))
2366 repo.vfs.write('graftstate', ''.join(nodelines))
2362 extra = ''
2367 extra = ''
2363 if opts.get('user'):
2368 if opts.get('user'):
2364 extra += ' --user %s' % util.shellquote(opts['user'])
2369 extra += ' --user %s' % util.shellquote(opts['user'])
2365 if opts.get('date'):
2370 if opts.get('date'):
2366 extra += ' --date %s' % util.shellquote(opts['date'])
2371 extra += ' --date %s' % util.shellquote(opts['date'])
2367 if opts.get('log'):
2372 if opts.get('log'):
2368 extra += ' --log'
2373 extra += ' --log'
2369 hint=_("use 'hg resolve' and 'hg graft --continue%s'") % extra
2374 hint=_("use 'hg resolve' and 'hg graft --continue%s'") % extra
2370 raise error.Abort(
2375 raise error.Abort(
2371 _("unresolved conflicts, can't continue"),
2376 _("unresolved conflicts, can't continue"),
2372 hint=hint)
2377 hint=hint)
2373 else:
2378 else:
2374 cont = False
2379 cont = False
2375
2380
2376 # commit
2381 # commit
2377 node = repo.commit(text=message, user=user,
2382 node = repo.commit(text=message, user=user,
2378 date=date, extra=extra, editor=editor)
2383 date=date, extra=extra, editor=editor)
2379 if node is None:
2384 if node is None:
2380 ui.warn(
2385 ui.warn(
2381 _('note: graft of %d:%s created no changes to commit\n') %
2386 _('note: graft of %d:%s created no changes to commit\n') %
2382 (ctx.rev(), ctx))
2387 (ctx.rev(), ctx))
2383
2388
2384 # remove state when we complete successfully
2389 # remove state when we complete successfully
2385 if not opts.get('dry_run'):
2390 if not opts.get('dry_run'):
2386 repo.vfs.unlinkpath('graftstate', ignoremissing=True)
2391 repo.vfs.unlinkpath('graftstate', ignoremissing=True)
2387
2392
2388 return 0
2393 return 0
2389
2394
2390 @command('grep',
2395 @command('grep',
2391 [('0', 'print0', None, _('end fields with NUL')),
2396 [('0', 'print0', None, _('end fields with NUL')),
2392 ('', 'all', None, _('print all revisions that match')),
2397 ('', 'all', None, _('print all revisions that match')),
2393 ('a', 'text', None, _('treat all files as text')),
2398 ('a', 'text', None, _('treat all files as text')),
2394 ('f', 'follow', None,
2399 ('f', 'follow', None,
2395 _('follow changeset history,'
2400 _('follow changeset history,'
2396 ' or file history across copies and renames')),
2401 ' or file history across copies and renames')),
2397 ('i', 'ignore-case', None, _('ignore case when matching')),
2402 ('i', 'ignore-case', None, _('ignore case when matching')),
2398 ('l', 'files-with-matches', None,
2403 ('l', 'files-with-matches', None,
2399 _('print only filenames and revisions that match')),
2404 _('print only filenames and revisions that match')),
2400 ('n', 'line-number', None, _('print matching line numbers')),
2405 ('n', 'line-number', None, _('print matching line numbers')),
2401 ('r', 'rev', [],
2406 ('r', 'rev', [],
2402 _('only search files changed within revision range'), _('REV')),
2407 _('only search files changed within revision range'), _('REV')),
2403 ('u', 'user', None, _('list the author (long with -v)')),
2408 ('u', 'user', None, _('list the author (long with -v)')),
2404 ('d', 'date', None, _('list the date (short with -q)')),
2409 ('d', 'date', None, _('list the date (short with -q)')),
2405 ] + formatteropts + walkopts,
2410 ] + formatteropts + walkopts,
2406 _('[OPTION]... PATTERN [FILE]...'),
2411 _('[OPTION]... PATTERN [FILE]...'),
2407 inferrepo=True)
2412 inferrepo=True)
2408 def grep(ui, repo, pattern, *pats, **opts):
2413 def grep(ui, repo, pattern, *pats, **opts):
2409 """search revision history for a pattern in specified files
2414 """search revision history for a pattern in specified files
2410
2415
2411 Search revision history for a regular expression in the specified
2416 Search revision history for a regular expression in the specified
2412 files or the entire project.
2417 files or the entire project.
2413
2418
2414 By default, grep prints the most recent revision number for each
2419 By default, grep prints the most recent revision number for each
2415 file in which it finds a match. To get it to print every revision
2420 file in which it finds a match. To get it to print every revision
2416 that contains a change in match status ("-" for a match that becomes
2421 that contains a change in match status ("-" for a match that becomes
2417 a non-match, or "+" for a non-match that becomes a match), use the
2422 a non-match, or "+" for a non-match that becomes a match), use the
2418 --all flag.
2423 --all flag.
2419
2424
2420 PATTERN can be any Python (roughly Perl-compatible) regular
2425 PATTERN can be any Python (roughly Perl-compatible) regular
2421 expression.
2426 expression.
2422
2427
2423 If no FILEs are specified (and -f/--follow isn't set), all files in
2428 If no FILEs are specified (and -f/--follow isn't set), all files in
2424 the repository are searched, including those that don't exist in the
2429 the repository are searched, including those that don't exist in the
2425 current branch or have been deleted in a prior changeset.
2430 current branch or have been deleted in a prior changeset.
2426
2431
2427 Returns 0 if a match is found, 1 otherwise.
2432 Returns 0 if a match is found, 1 otherwise.
2428 """
2433 """
2429 opts = pycompat.byteskwargs(opts)
2434 opts = pycompat.byteskwargs(opts)
2430 reflags = re.M
2435 reflags = re.M
2431 if opts.get('ignore_case'):
2436 if opts.get('ignore_case'):
2432 reflags |= re.I
2437 reflags |= re.I
2433 try:
2438 try:
2434 regexp = util.re.compile(pattern, reflags)
2439 regexp = util.re.compile(pattern, reflags)
2435 except re.error as inst:
2440 except re.error as inst:
2436 ui.warn(_("grep: invalid match pattern: %s\n") % inst)
2441 ui.warn(_("grep: invalid match pattern: %s\n") % inst)
2437 return 1
2442 return 1
2438 sep, eol = ':', '\n'
2443 sep, eol = ':', '\n'
2439 if opts.get('print0'):
2444 if opts.get('print0'):
2440 sep = eol = '\0'
2445 sep = eol = '\0'
2441
2446
2442 getfile = util.lrucachefunc(repo.file)
2447 getfile = util.lrucachefunc(repo.file)
2443
2448
2444 def matchlines(body):
2449 def matchlines(body):
2445 begin = 0
2450 begin = 0
2446 linenum = 0
2451 linenum = 0
2447 while begin < len(body):
2452 while begin < len(body):
2448 match = regexp.search(body, begin)
2453 match = regexp.search(body, begin)
2449 if not match:
2454 if not match:
2450 break
2455 break
2451 mstart, mend = match.span()
2456 mstart, mend = match.span()
2452 linenum += body.count('\n', begin, mstart) + 1
2457 linenum += body.count('\n', begin, mstart) + 1
2453 lstart = body.rfind('\n', begin, mstart) + 1 or begin
2458 lstart = body.rfind('\n', begin, mstart) + 1 or begin
2454 begin = body.find('\n', mend) + 1 or len(body) + 1
2459 begin = body.find('\n', mend) + 1 or len(body) + 1
2455 lend = begin - 1
2460 lend = begin - 1
2456 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
2461 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
2457
2462
2458 class linestate(object):
2463 class linestate(object):
2459 def __init__(self, line, linenum, colstart, colend):
2464 def __init__(self, line, linenum, colstart, colend):
2460 self.line = line
2465 self.line = line
2461 self.linenum = linenum
2466 self.linenum = linenum
2462 self.colstart = colstart
2467 self.colstart = colstart
2463 self.colend = colend
2468 self.colend = colend
2464
2469
2465 def __hash__(self):
2470 def __hash__(self):
2466 return hash((self.linenum, self.line))
2471 return hash((self.linenum, self.line))
2467
2472
2468 def __eq__(self, other):
2473 def __eq__(self, other):
2469 return self.line == other.line
2474 return self.line == other.line
2470
2475
2471 def findpos(self):
2476 def findpos(self):
2472 """Iterate all (start, end) indices of matches"""
2477 """Iterate all (start, end) indices of matches"""
2473 yield self.colstart, self.colend
2478 yield self.colstart, self.colend
2474 p = self.colend
2479 p = self.colend
2475 while p < len(self.line):
2480 while p < len(self.line):
2476 m = regexp.search(self.line, p)
2481 m = regexp.search(self.line, p)
2477 if not m:
2482 if not m:
2478 break
2483 break
2479 yield m.span()
2484 yield m.span()
2480 p = m.end()
2485 p = m.end()
2481
2486
2482 matches = {}
2487 matches = {}
2483 copies = {}
2488 copies = {}
2484 def grepbody(fn, rev, body):
2489 def grepbody(fn, rev, body):
2485 matches[rev].setdefault(fn, [])
2490 matches[rev].setdefault(fn, [])
2486 m = matches[rev][fn]
2491 m = matches[rev][fn]
2487 for lnum, cstart, cend, line in matchlines(body):
2492 for lnum, cstart, cend, line in matchlines(body):
2488 s = linestate(line, lnum, cstart, cend)
2493 s = linestate(line, lnum, cstart, cend)
2489 m.append(s)
2494 m.append(s)
2490
2495
2491 def difflinestates(a, b):
2496 def difflinestates(a, b):
2492 sm = difflib.SequenceMatcher(None, a, b)
2497 sm = difflib.SequenceMatcher(None, a, b)
2493 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
2498 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
2494 if tag == 'insert':
2499 if tag == 'insert':
2495 for i in xrange(blo, bhi):
2500 for i in xrange(blo, bhi):
2496 yield ('+', b[i])
2501 yield ('+', b[i])
2497 elif tag == 'delete':
2502 elif tag == 'delete':
2498 for i in xrange(alo, ahi):
2503 for i in xrange(alo, ahi):
2499 yield ('-', a[i])
2504 yield ('-', a[i])
2500 elif tag == 'replace':
2505 elif tag == 'replace':
2501 for i in xrange(alo, ahi):
2506 for i in xrange(alo, ahi):
2502 yield ('-', a[i])
2507 yield ('-', a[i])
2503 for i in xrange(blo, bhi):
2508 for i in xrange(blo, bhi):
2504 yield ('+', b[i])
2509 yield ('+', b[i])
2505
2510
2506 def display(fm, fn, ctx, pstates, states):
2511 def display(fm, fn, ctx, pstates, states):
2507 rev = ctx.rev()
2512 rev = ctx.rev()
2508 if fm.isplain():
2513 if fm.isplain():
2509 formatuser = ui.shortuser
2514 formatuser = ui.shortuser
2510 else:
2515 else:
2511 formatuser = str
2516 formatuser = str
2512 if ui.quiet:
2517 if ui.quiet:
2513 datefmt = '%Y-%m-%d'
2518 datefmt = '%Y-%m-%d'
2514 else:
2519 else:
2515 datefmt = '%a %b %d %H:%M:%S %Y %1%2'
2520 datefmt = '%a %b %d %H:%M:%S %Y %1%2'
2516 found = False
2521 found = False
2517 @util.cachefunc
2522 @util.cachefunc
2518 def binary():
2523 def binary():
2519 flog = getfile(fn)
2524 flog = getfile(fn)
2520 return util.binary(flog.read(ctx.filenode(fn)))
2525 return util.binary(flog.read(ctx.filenode(fn)))
2521
2526
2522 fieldnamemap = {'filename': 'file', 'linenumber': 'line_number'}
2527 fieldnamemap = {'filename': 'file', 'linenumber': 'line_number'}
2523 if opts.get('all'):
2528 if opts.get('all'):
2524 iter = difflinestates(pstates, states)
2529 iter = difflinestates(pstates, states)
2525 else:
2530 else:
2526 iter = [('', l) for l in states]
2531 iter = [('', l) for l in states]
2527 for change, l in iter:
2532 for change, l in iter:
2528 fm.startitem()
2533 fm.startitem()
2529 fm.data(node=fm.hexfunc(ctx.node()))
2534 fm.data(node=fm.hexfunc(ctx.node()))
2530 cols = [
2535 cols = [
2531 ('filename', fn, True),
2536 ('filename', fn, True),
2532 ('rev', rev, True),
2537 ('rev', rev, True),
2533 ('linenumber', l.linenum, opts.get('line_number')),
2538 ('linenumber', l.linenum, opts.get('line_number')),
2534 ]
2539 ]
2535 if opts.get('all'):
2540 if opts.get('all'):
2536 cols.append(('change', change, True))
2541 cols.append(('change', change, True))
2537 cols.extend([
2542 cols.extend([
2538 ('user', formatuser(ctx.user()), opts.get('user')),
2543 ('user', formatuser(ctx.user()), opts.get('user')),
2539 ('date', fm.formatdate(ctx.date(), datefmt), opts.get('date')),
2544 ('date', fm.formatdate(ctx.date(), datefmt), opts.get('date')),
2540 ])
2545 ])
2541 lastcol = next(name for name, data, cond in reversed(cols) if cond)
2546 lastcol = next(name for name, data, cond in reversed(cols) if cond)
2542 for name, data, cond in cols:
2547 for name, data, cond in cols:
2543 field = fieldnamemap.get(name, name)
2548 field = fieldnamemap.get(name, name)
2544 fm.condwrite(cond, field, '%s', data, label='grep.%s' % name)
2549 fm.condwrite(cond, field, '%s', data, label='grep.%s' % name)
2545 if cond and name != lastcol:
2550 if cond and name != lastcol:
2546 fm.plain(sep, label='grep.sep')
2551 fm.plain(sep, label='grep.sep')
2547 if not opts.get('files_with_matches'):
2552 if not opts.get('files_with_matches'):
2548 fm.plain(sep, label='grep.sep')
2553 fm.plain(sep, label='grep.sep')
2549 if not opts.get('text') and binary():
2554 if not opts.get('text') and binary():
2550 fm.plain(_(" Binary file matches"))
2555 fm.plain(_(" Binary file matches"))
2551 else:
2556 else:
2552 displaymatches(fm.nested('texts'), l)
2557 displaymatches(fm.nested('texts'), l)
2553 fm.plain(eol)
2558 fm.plain(eol)
2554 found = True
2559 found = True
2555 if opts.get('files_with_matches'):
2560 if opts.get('files_with_matches'):
2556 break
2561 break
2557 return found
2562 return found
2558
2563
2559 def displaymatches(fm, l):
2564 def displaymatches(fm, l):
2560 p = 0
2565 p = 0
2561 for s, e in l.findpos():
2566 for s, e in l.findpos():
2562 if p < s:
2567 if p < s:
2563 fm.startitem()
2568 fm.startitem()
2564 fm.write('text', '%s', l.line[p:s])
2569 fm.write('text', '%s', l.line[p:s])
2565 fm.data(matched=False)
2570 fm.data(matched=False)
2566 fm.startitem()
2571 fm.startitem()
2567 fm.write('text', '%s', l.line[s:e], label='grep.match')
2572 fm.write('text', '%s', l.line[s:e], label='grep.match')
2568 fm.data(matched=True)
2573 fm.data(matched=True)
2569 p = e
2574 p = e
2570 if p < len(l.line):
2575 if p < len(l.line):
2571 fm.startitem()
2576 fm.startitem()
2572 fm.write('text', '%s', l.line[p:])
2577 fm.write('text', '%s', l.line[p:])
2573 fm.data(matched=False)
2578 fm.data(matched=False)
2574 fm.end()
2579 fm.end()
2575
2580
2576 skip = {}
2581 skip = {}
2577 revfiles = {}
2582 revfiles = {}
2578 matchfn = scmutil.match(repo[None], pats, opts)
2583 matchfn = scmutil.match(repo[None], pats, opts)
2579 found = False
2584 found = False
2580 follow = opts.get('follow')
2585 follow = opts.get('follow')
2581
2586
2582 def prep(ctx, fns):
2587 def prep(ctx, fns):
2583 rev = ctx.rev()
2588 rev = ctx.rev()
2584 pctx = ctx.p1()
2589 pctx = ctx.p1()
2585 parent = pctx.rev()
2590 parent = pctx.rev()
2586 matches.setdefault(rev, {})
2591 matches.setdefault(rev, {})
2587 matches.setdefault(parent, {})
2592 matches.setdefault(parent, {})
2588 files = revfiles.setdefault(rev, [])
2593 files = revfiles.setdefault(rev, [])
2589 for fn in fns:
2594 for fn in fns:
2590 flog = getfile(fn)
2595 flog = getfile(fn)
2591 try:
2596 try:
2592 fnode = ctx.filenode(fn)
2597 fnode = ctx.filenode(fn)
2593 except error.LookupError:
2598 except error.LookupError:
2594 continue
2599 continue
2595
2600
2596 copied = flog.renamed(fnode)
2601 copied = flog.renamed(fnode)
2597 copy = follow and copied and copied[0]
2602 copy = follow and copied and copied[0]
2598 if copy:
2603 if copy:
2599 copies.setdefault(rev, {})[fn] = copy
2604 copies.setdefault(rev, {})[fn] = copy
2600 if fn in skip:
2605 if fn in skip:
2601 if copy:
2606 if copy:
2602 skip[copy] = True
2607 skip[copy] = True
2603 continue
2608 continue
2604 files.append(fn)
2609 files.append(fn)
2605
2610
2606 if fn not in matches[rev]:
2611 if fn not in matches[rev]:
2607 grepbody(fn, rev, flog.read(fnode))
2612 grepbody(fn, rev, flog.read(fnode))
2608
2613
2609 pfn = copy or fn
2614 pfn = copy or fn
2610 if pfn not in matches[parent]:
2615 if pfn not in matches[parent]:
2611 try:
2616 try:
2612 fnode = pctx.filenode(pfn)
2617 fnode = pctx.filenode(pfn)
2613 grepbody(pfn, parent, flog.read(fnode))
2618 grepbody(pfn, parent, flog.read(fnode))
2614 except error.LookupError:
2619 except error.LookupError:
2615 pass
2620 pass
2616
2621
2617 ui.pager('grep')
2622 ui.pager('grep')
2618 fm = ui.formatter('grep', opts)
2623 fm = ui.formatter('grep', opts)
2619 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
2624 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
2620 rev = ctx.rev()
2625 rev = ctx.rev()
2621 parent = ctx.p1().rev()
2626 parent = ctx.p1().rev()
2622 for fn in sorted(revfiles.get(rev, [])):
2627 for fn in sorted(revfiles.get(rev, [])):
2623 states = matches[rev][fn]
2628 states = matches[rev][fn]
2624 copy = copies.get(rev, {}).get(fn)
2629 copy = copies.get(rev, {}).get(fn)
2625 if fn in skip:
2630 if fn in skip:
2626 if copy:
2631 if copy:
2627 skip[copy] = True
2632 skip[copy] = True
2628 continue
2633 continue
2629 pstates = matches.get(parent, {}).get(copy or fn, [])
2634 pstates = matches.get(parent, {}).get(copy or fn, [])
2630 if pstates or states:
2635 if pstates or states:
2631 r = display(fm, fn, ctx, pstates, states)
2636 r = display(fm, fn, ctx, pstates, states)
2632 found = found or r
2637 found = found or r
2633 if r and not opts.get('all'):
2638 if r and not opts.get('all'):
2634 skip[fn] = True
2639 skip[fn] = True
2635 if copy:
2640 if copy:
2636 skip[copy] = True
2641 skip[copy] = True
2637 del matches[rev]
2642 del matches[rev]
2638 del revfiles[rev]
2643 del revfiles[rev]
2639 fm.end()
2644 fm.end()
2640
2645
2641 return not found
2646 return not found
2642
2647
2643 @command('heads',
2648 @command('heads',
2644 [('r', 'rev', '',
2649 [('r', 'rev', '',
2645 _('show only heads which are descendants of STARTREV'), _('STARTREV')),
2650 _('show only heads which are descendants of STARTREV'), _('STARTREV')),
2646 ('t', 'topo', False, _('show topological heads only')),
2651 ('t', 'topo', False, _('show topological heads only')),
2647 ('a', 'active', False, _('show active branchheads only (DEPRECATED)')),
2652 ('a', 'active', False, _('show active branchheads only (DEPRECATED)')),
2648 ('c', 'closed', False, _('show normal and closed branch heads')),
2653 ('c', 'closed', False, _('show normal and closed branch heads')),
2649 ] + templateopts,
2654 ] + templateopts,
2650 _('[-ct] [-r STARTREV] [REV]...'))
2655 _('[-ct] [-r STARTREV] [REV]...'))
2651 def heads(ui, repo, *branchrevs, **opts):
2656 def heads(ui, repo, *branchrevs, **opts):
2652 """show branch heads
2657 """show branch heads
2653
2658
2654 With no arguments, show all open branch heads in the repository.
2659 With no arguments, show all open branch heads in the repository.
2655 Branch heads are changesets that have no descendants on the
2660 Branch heads are changesets that have no descendants on the
2656 same branch. They are where development generally takes place and
2661 same branch. They are where development generally takes place and
2657 are the usual targets for update and merge operations.
2662 are the usual targets for update and merge operations.
2658
2663
2659 If one or more REVs are given, only open branch heads on the
2664 If one or more REVs are given, only open branch heads on the
2660 branches associated with the specified changesets are shown. This
2665 branches associated with the specified changesets are shown. This
2661 means that you can use :hg:`heads .` to see the heads on the
2666 means that you can use :hg:`heads .` to see the heads on the
2662 currently checked-out branch.
2667 currently checked-out branch.
2663
2668
2664 If -c/--closed is specified, also show branch heads marked closed
2669 If -c/--closed is specified, also show branch heads marked closed
2665 (see :hg:`commit --close-branch`).
2670 (see :hg:`commit --close-branch`).
2666
2671
2667 If STARTREV is specified, only those heads that are descendants of
2672 If STARTREV is specified, only those heads that are descendants of
2668 STARTREV will be displayed.
2673 STARTREV will be displayed.
2669
2674
2670 If -t/--topo is specified, named branch mechanics will be ignored and only
2675 If -t/--topo is specified, named branch mechanics will be ignored and only
2671 topological heads (changesets with no children) will be shown.
2676 topological heads (changesets with no children) will be shown.
2672
2677
2673 Returns 0 if matching heads are found, 1 if not.
2678 Returns 0 if matching heads are found, 1 if not.
2674 """
2679 """
2675
2680
2676 opts = pycompat.byteskwargs(opts)
2681 opts = pycompat.byteskwargs(opts)
2677 start = None
2682 start = None
2678 if 'rev' in opts:
2683 if 'rev' in opts:
2679 start = scmutil.revsingle(repo, opts['rev'], None).node()
2684 start = scmutil.revsingle(repo, opts['rev'], None).node()
2680
2685
2681 if opts.get('topo'):
2686 if opts.get('topo'):
2682 heads = [repo[h] for h in repo.heads(start)]
2687 heads = [repo[h] for h in repo.heads(start)]
2683 else:
2688 else:
2684 heads = []
2689 heads = []
2685 for branch in repo.branchmap():
2690 for branch in repo.branchmap():
2686 heads += repo.branchheads(branch, start, opts.get('closed'))
2691 heads += repo.branchheads(branch, start, opts.get('closed'))
2687 heads = [repo[h] for h in heads]
2692 heads = [repo[h] for h in heads]
2688
2693
2689 if branchrevs:
2694 if branchrevs:
2690 branches = set(repo[br].branch() for br in branchrevs)
2695 branches = set(repo[br].branch() for br in branchrevs)
2691 heads = [h for h in heads if h.branch() in branches]
2696 heads = [h for h in heads if h.branch() in branches]
2692
2697
2693 if opts.get('active') and branchrevs:
2698 if opts.get('active') and branchrevs:
2694 dagheads = repo.heads(start)
2699 dagheads = repo.heads(start)
2695 heads = [h for h in heads if h.node() in dagheads]
2700 heads = [h for h in heads if h.node() in dagheads]
2696
2701
2697 if branchrevs:
2702 if branchrevs:
2698 haveheads = set(h.branch() for h in heads)
2703 haveheads = set(h.branch() for h in heads)
2699 if branches - haveheads:
2704 if branches - haveheads:
2700 headless = ', '.join(b for b in branches - haveheads)
2705 headless = ', '.join(b for b in branches - haveheads)
2701 msg = _('no open branch heads found on branches %s')
2706 msg = _('no open branch heads found on branches %s')
2702 if opts.get('rev'):
2707 if opts.get('rev'):
2703 msg += _(' (started at %s)') % opts['rev']
2708 msg += _(' (started at %s)') % opts['rev']
2704 ui.warn((msg + '\n') % headless)
2709 ui.warn((msg + '\n') % headless)
2705
2710
2706 if not heads:
2711 if not heads:
2707 return 1
2712 return 1
2708
2713
2709 ui.pager('heads')
2714 ui.pager('heads')
2710 heads = sorted(heads, key=lambda x: -x.rev())
2715 heads = sorted(heads, key=lambda x: -x.rev())
2711 displayer = cmdutil.show_changeset(ui, repo, opts)
2716 displayer = cmdutil.show_changeset(ui, repo, opts)
2712 for ctx in heads:
2717 for ctx in heads:
2713 displayer.show(ctx)
2718 displayer.show(ctx)
2714 displayer.close()
2719 displayer.close()
2715
2720
2716 @command('help',
2721 @command('help',
2717 [('e', 'extension', None, _('show only help for extensions')),
2722 [('e', 'extension', None, _('show only help for extensions')),
2718 ('c', 'command', None, _('show only help for commands')),
2723 ('c', 'command', None, _('show only help for commands')),
2719 ('k', 'keyword', None, _('show topics matching keyword')),
2724 ('k', 'keyword', None, _('show topics matching keyword')),
2720 ('s', 'system', [], _('show help for specific platform(s)')),
2725 ('s', 'system', [], _('show help for specific platform(s)')),
2721 ],
2726 ],
2722 _('[-ecks] [TOPIC]'),
2727 _('[-ecks] [TOPIC]'),
2723 norepo=True)
2728 norepo=True)
2724 def help_(ui, name=None, **opts):
2729 def help_(ui, name=None, **opts):
2725 """show help for a given topic or a help overview
2730 """show help for a given topic or a help overview
2726
2731
2727 With no arguments, print a list of commands with short help messages.
2732 With no arguments, print a list of commands with short help messages.
2728
2733
2729 Given a topic, extension, or command name, print help for that
2734 Given a topic, extension, or command name, print help for that
2730 topic.
2735 topic.
2731
2736
2732 Returns 0 if successful.
2737 Returns 0 if successful.
2733 """
2738 """
2734
2739
2735 keep = opts.get(r'system') or []
2740 keep = opts.get(r'system') or []
2736 if len(keep) == 0:
2741 if len(keep) == 0:
2737 if pycompat.sysplatform.startswith('win'):
2742 if pycompat.sysplatform.startswith('win'):
2738 keep.append('windows')
2743 keep.append('windows')
2739 elif pycompat.sysplatform == 'OpenVMS':
2744 elif pycompat.sysplatform == 'OpenVMS':
2740 keep.append('vms')
2745 keep.append('vms')
2741 elif pycompat.sysplatform == 'plan9':
2746 elif pycompat.sysplatform == 'plan9':
2742 keep.append('plan9')
2747 keep.append('plan9')
2743 else:
2748 else:
2744 keep.append('unix')
2749 keep.append('unix')
2745 keep.append(pycompat.sysplatform.lower())
2750 keep.append(pycompat.sysplatform.lower())
2746 if ui.verbose:
2751 if ui.verbose:
2747 keep.append('verbose')
2752 keep.append('verbose')
2748
2753
2749 commands = sys.modules[__name__]
2754 commands = sys.modules[__name__]
2750 formatted = help.formattedhelp(ui, commands, name, keep=keep, **opts)
2755 formatted = help.formattedhelp(ui, commands, name, keep=keep, **opts)
2751 ui.pager('help')
2756 ui.pager('help')
2752 ui.write(formatted)
2757 ui.write(formatted)
2753
2758
2754
2759
2755 @command('identify|id',
2760 @command('identify|id',
2756 [('r', 'rev', '',
2761 [('r', 'rev', '',
2757 _('identify the specified revision'), _('REV')),
2762 _('identify the specified revision'), _('REV')),
2758 ('n', 'num', None, _('show local revision number')),
2763 ('n', 'num', None, _('show local revision number')),
2759 ('i', 'id', None, _('show global revision id')),
2764 ('i', 'id', None, _('show global revision id')),
2760 ('b', 'branch', None, _('show branch')),
2765 ('b', 'branch', None, _('show branch')),
2761 ('t', 'tags', None, _('show tags')),
2766 ('t', 'tags', None, _('show tags')),
2762 ('B', 'bookmarks', None, _('show bookmarks')),
2767 ('B', 'bookmarks', None, _('show bookmarks')),
2763 ] + remoteopts,
2768 ] + remoteopts,
2764 _('[-nibtB] [-r REV] [SOURCE]'),
2769 _('[-nibtB] [-r REV] [SOURCE]'),
2765 optionalrepo=True)
2770 optionalrepo=True)
2766 def identify(ui, repo, source=None, rev=None,
2771 def identify(ui, repo, source=None, rev=None,
2767 num=None, id=None, branch=None, tags=None, bookmarks=None, **opts):
2772 num=None, id=None, branch=None, tags=None, bookmarks=None, **opts):
2768 """identify the working directory or specified revision
2773 """identify the working directory or specified revision
2769
2774
2770 Print a summary identifying the repository state at REV using one or
2775 Print a summary identifying the repository state at REV using one or
2771 two parent hash identifiers, followed by a "+" if the working
2776 two parent hash identifiers, followed by a "+" if the working
2772 directory has uncommitted changes, the branch name (if not default),
2777 directory has uncommitted changes, the branch name (if not default),
2773 a list of tags, and a list of bookmarks.
2778 a list of tags, and a list of bookmarks.
2774
2779
2775 When REV is not given, print a summary of the current state of the
2780 When REV is not given, print a summary of the current state of the
2776 repository.
2781 repository.
2777
2782
2778 Specifying a path to a repository root or Mercurial bundle will
2783 Specifying a path to a repository root or Mercurial bundle will
2779 cause lookup to operate on that repository/bundle.
2784 cause lookup to operate on that repository/bundle.
2780
2785
2781 .. container:: verbose
2786 .. container:: verbose
2782
2787
2783 Examples:
2788 Examples:
2784
2789
2785 - generate a build identifier for the working directory::
2790 - generate a build identifier for the working directory::
2786
2791
2787 hg id --id > build-id.dat
2792 hg id --id > build-id.dat
2788
2793
2789 - find the revision corresponding to a tag::
2794 - find the revision corresponding to a tag::
2790
2795
2791 hg id -n -r 1.3
2796 hg id -n -r 1.3
2792
2797
2793 - check the most recent revision of a remote repository::
2798 - check the most recent revision of a remote repository::
2794
2799
2795 hg id -r tip https://www.mercurial-scm.org/repo/hg/
2800 hg id -r tip https://www.mercurial-scm.org/repo/hg/
2796
2801
2797 See :hg:`log` for generating more information about specific revisions,
2802 See :hg:`log` for generating more information about specific revisions,
2798 including full hash identifiers.
2803 including full hash identifiers.
2799
2804
2800 Returns 0 if successful.
2805 Returns 0 if successful.
2801 """
2806 """
2802
2807
2803 opts = pycompat.byteskwargs(opts)
2808 opts = pycompat.byteskwargs(opts)
2804 if not repo and not source:
2809 if not repo and not source:
2805 raise error.Abort(_("there is no Mercurial repository here "
2810 raise error.Abort(_("there is no Mercurial repository here "
2806 "(.hg not found)"))
2811 "(.hg not found)"))
2807
2812
2808 if ui.debugflag:
2813 if ui.debugflag:
2809 hexfunc = hex
2814 hexfunc = hex
2810 else:
2815 else:
2811 hexfunc = short
2816 hexfunc = short
2812 default = not (num or id or branch or tags or bookmarks)
2817 default = not (num or id or branch or tags or bookmarks)
2813 output = []
2818 output = []
2814 revs = []
2819 revs = []
2815
2820
2816 if source:
2821 if source:
2817 source, branches = hg.parseurl(ui.expandpath(source))
2822 source, branches = hg.parseurl(ui.expandpath(source))
2818 peer = hg.peer(repo or ui, opts, source) # only pass ui when no repo
2823 peer = hg.peer(repo or ui, opts, source) # only pass ui when no repo
2819 repo = peer.local()
2824 repo = peer.local()
2820 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
2825 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
2821
2826
2822 if not repo:
2827 if not repo:
2823 if num or branch or tags:
2828 if num or branch or tags:
2824 raise error.Abort(
2829 raise error.Abort(
2825 _("can't query remote revision number, branch, or tags"))
2830 _("can't query remote revision number, branch, or tags"))
2826 if not rev and revs:
2831 if not rev and revs:
2827 rev = revs[0]
2832 rev = revs[0]
2828 if not rev:
2833 if not rev:
2829 rev = "tip"
2834 rev = "tip"
2830
2835
2831 remoterev = peer.lookup(rev)
2836 remoterev = peer.lookup(rev)
2832 if default or id:
2837 if default or id:
2833 output = [hexfunc(remoterev)]
2838 output = [hexfunc(remoterev)]
2834
2839
2835 def getbms():
2840 def getbms():
2836 bms = []
2841 bms = []
2837
2842
2838 if 'bookmarks' in peer.listkeys('namespaces'):
2843 if 'bookmarks' in peer.listkeys('namespaces'):
2839 hexremoterev = hex(remoterev)
2844 hexremoterev = hex(remoterev)
2840 bms = [bm for bm, bmr in peer.listkeys('bookmarks').iteritems()
2845 bms = [bm for bm, bmr in peer.listkeys('bookmarks').iteritems()
2841 if bmr == hexremoterev]
2846 if bmr == hexremoterev]
2842
2847
2843 return sorted(bms)
2848 return sorted(bms)
2844
2849
2845 if bookmarks:
2850 if bookmarks:
2846 output.extend(getbms())
2851 output.extend(getbms())
2847 elif default and not ui.quiet:
2852 elif default and not ui.quiet:
2848 # multiple bookmarks for a single parent separated by '/'
2853 # multiple bookmarks for a single parent separated by '/'
2849 bm = '/'.join(getbms())
2854 bm = '/'.join(getbms())
2850 if bm:
2855 if bm:
2851 output.append(bm)
2856 output.append(bm)
2852 else:
2857 else:
2853 ctx = scmutil.revsingle(repo, rev, None)
2858 ctx = scmutil.revsingle(repo, rev, None)
2854
2859
2855 if ctx.rev() is None:
2860 if ctx.rev() is None:
2856 ctx = repo[None]
2861 ctx = repo[None]
2857 parents = ctx.parents()
2862 parents = ctx.parents()
2858 taglist = []
2863 taglist = []
2859 for p in parents:
2864 for p in parents:
2860 taglist.extend(p.tags())
2865 taglist.extend(p.tags())
2861
2866
2862 changed = ""
2867 changed = ""
2863 if default or id or num:
2868 if default or id or num:
2864 if (any(repo.status())
2869 if (any(repo.status())
2865 or any(ctx.sub(s).dirty() for s in ctx.substate)):
2870 or any(ctx.sub(s).dirty() for s in ctx.substate)):
2866 changed = '+'
2871 changed = '+'
2867 if default or id:
2872 if default or id:
2868 output = ["%s%s" %
2873 output = ["%s%s" %
2869 ('+'.join([hexfunc(p.node()) for p in parents]), changed)]
2874 ('+'.join([hexfunc(p.node()) for p in parents]), changed)]
2870 if num:
2875 if num:
2871 output.append("%s%s" %
2876 output.append("%s%s" %
2872 ('+'.join([str(p.rev()) for p in parents]), changed))
2877 ('+'.join([str(p.rev()) for p in parents]), changed))
2873 else:
2878 else:
2874 if default or id:
2879 if default or id:
2875 output = [hexfunc(ctx.node())]
2880 output = [hexfunc(ctx.node())]
2876 if num:
2881 if num:
2877 output.append(str(ctx.rev()))
2882 output.append(str(ctx.rev()))
2878 taglist = ctx.tags()
2883 taglist = ctx.tags()
2879
2884
2880 if default and not ui.quiet:
2885 if default and not ui.quiet:
2881 b = ctx.branch()
2886 b = ctx.branch()
2882 if b != 'default':
2887 if b != 'default':
2883 output.append("(%s)" % b)
2888 output.append("(%s)" % b)
2884
2889
2885 # multiple tags for a single parent separated by '/'
2890 # multiple tags for a single parent separated by '/'
2886 t = '/'.join(taglist)
2891 t = '/'.join(taglist)
2887 if t:
2892 if t:
2888 output.append(t)
2893 output.append(t)
2889
2894
2890 # multiple bookmarks for a single parent separated by '/'
2895 # multiple bookmarks for a single parent separated by '/'
2891 bm = '/'.join(ctx.bookmarks())
2896 bm = '/'.join(ctx.bookmarks())
2892 if bm:
2897 if bm:
2893 output.append(bm)
2898 output.append(bm)
2894 else:
2899 else:
2895 if branch:
2900 if branch:
2896 output.append(ctx.branch())
2901 output.append(ctx.branch())
2897
2902
2898 if tags:
2903 if tags:
2899 output.extend(taglist)
2904 output.extend(taglist)
2900
2905
2901 if bookmarks:
2906 if bookmarks:
2902 output.extend(ctx.bookmarks())
2907 output.extend(ctx.bookmarks())
2903
2908
2904 ui.write("%s\n" % ' '.join(output))
2909 ui.write("%s\n" % ' '.join(output))
2905
2910
2906 @command('import|patch',
2911 @command('import|patch',
2907 [('p', 'strip', 1,
2912 [('p', 'strip', 1,
2908 _('directory strip option for patch. This has the same '
2913 _('directory strip option for patch. This has the same '
2909 'meaning as the corresponding patch option'), _('NUM')),
2914 'meaning as the corresponding patch option'), _('NUM')),
2910 ('b', 'base', '', _('base path (DEPRECATED)'), _('PATH')),
2915 ('b', 'base', '', _('base path (DEPRECATED)'), _('PATH')),
2911 ('e', 'edit', False, _('invoke editor on commit messages')),
2916 ('e', 'edit', False, _('invoke editor on commit messages')),
2912 ('f', 'force', None,
2917 ('f', 'force', None,
2913 _('skip check for outstanding uncommitted changes (DEPRECATED)')),
2918 _('skip check for outstanding uncommitted changes (DEPRECATED)')),
2914 ('', 'no-commit', None,
2919 ('', 'no-commit', None,
2915 _("don't commit, just update the working directory")),
2920 _("don't commit, just update the working directory")),
2916 ('', 'bypass', None,
2921 ('', 'bypass', None,
2917 _("apply patch without touching the working directory")),
2922 _("apply patch without touching the working directory")),
2918 ('', 'partial', None,
2923 ('', 'partial', None,
2919 _('commit even if some hunks fail')),
2924 _('commit even if some hunks fail')),
2920 ('', 'exact', None,
2925 ('', 'exact', None,
2921 _('abort if patch would apply lossily')),
2926 _('abort if patch would apply lossily')),
2922 ('', 'prefix', '',
2927 ('', 'prefix', '',
2923 _('apply patch to subdirectory'), _('DIR')),
2928 _('apply patch to subdirectory'), _('DIR')),
2924 ('', 'import-branch', None,
2929 ('', 'import-branch', None,
2925 _('use any branch information in patch (implied by --exact)'))] +
2930 _('use any branch information in patch (implied by --exact)'))] +
2926 commitopts + commitopts2 + similarityopts,
2931 commitopts + commitopts2 + similarityopts,
2927 _('[OPTION]... PATCH...'))
2932 _('[OPTION]... PATCH...'))
2928 def import_(ui, repo, patch1=None, *patches, **opts):
2933 def import_(ui, repo, patch1=None, *patches, **opts):
2929 """import an ordered set of patches
2934 """import an ordered set of patches
2930
2935
2931 Import a list of patches and commit them individually (unless
2936 Import a list of patches and commit them individually (unless
2932 --no-commit is specified).
2937 --no-commit is specified).
2933
2938
2934 To read a patch from standard input (stdin), use "-" as the patch
2939 To read a patch from standard input (stdin), use "-" as the patch
2935 name. If a URL is specified, the patch will be downloaded from
2940 name. If a URL is specified, the patch will be downloaded from
2936 there.
2941 there.
2937
2942
2938 Import first applies changes to the working directory (unless
2943 Import first applies changes to the working directory (unless
2939 --bypass is specified), import will abort if there are outstanding
2944 --bypass is specified), import will abort if there are outstanding
2940 changes.
2945 changes.
2941
2946
2942 Use --bypass to apply and commit patches directly to the
2947 Use --bypass to apply and commit patches directly to the
2943 repository, without affecting the working directory. Without
2948 repository, without affecting the working directory. Without
2944 --exact, patches will be applied on top of the working directory
2949 --exact, patches will be applied on top of the working directory
2945 parent revision.
2950 parent revision.
2946
2951
2947 You can import a patch straight from a mail message. Even patches
2952 You can import a patch straight from a mail message. Even patches
2948 as attachments work (to use the body part, it must have type
2953 as attachments work (to use the body part, it must have type
2949 text/plain or text/x-patch). From and Subject headers of email
2954 text/plain or text/x-patch). From and Subject headers of email
2950 message are used as default committer and commit message. All
2955 message are used as default committer and commit message. All
2951 text/plain body parts before first diff are added to the commit
2956 text/plain body parts before first diff are added to the commit
2952 message.
2957 message.
2953
2958
2954 If the imported patch was generated by :hg:`export`, user and
2959 If the imported patch was generated by :hg:`export`, user and
2955 description from patch override values from message headers and
2960 description from patch override values from message headers and
2956 body. Values given on command line with -m/--message and -u/--user
2961 body. Values given on command line with -m/--message and -u/--user
2957 override these.
2962 override these.
2958
2963
2959 If --exact is specified, import will set the working directory to
2964 If --exact is specified, import will set the working directory to
2960 the parent of each patch before applying it, and will abort if the
2965 the parent of each patch before applying it, and will abort if the
2961 resulting changeset has a different ID than the one recorded in
2966 resulting changeset has a different ID than the one recorded in
2962 the patch. This will guard against various ways that portable
2967 the patch. This will guard against various ways that portable
2963 patch formats and mail systems might fail to transfer Mercurial
2968 patch formats and mail systems might fail to transfer Mercurial
2964 data or metadata. See :hg:`bundle` for lossless transmission.
2969 data or metadata. See :hg:`bundle` for lossless transmission.
2965
2970
2966 Use --partial to ensure a changeset will be created from the patch
2971 Use --partial to ensure a changeset will be created from the patch
2967 even if some hunks fail to apply. Hunks that fail to apply will be
2972 even if some hunks fail to apply. Hunks that fail to apply will be
2968 written to a <target-file>.rej file. Conflicts can then be resolved
2973 written to a <target-file>.rej file. Conflicts can then be resolved
2969 by hand before :hg:`commit --amend` is run to update the created
2974 by hand before :hg:`commit --amend` is run to update the created
2970 changeset. This flag exists to let people import patches that
2975 changeset. This flag exists to let people import patches that
2971 partially apply without losing the associated metadata (author,
2976 partially apply without losing the associated metadata (author,
2972 date, description, ...).
2977 date, description, ...).
2973
2978
2974 .. note::
2979 .. note::
2975
2980
2976 When no hunks apply cleanly, :hg:`import --partial` will create
2981 When no hunks apply cleanly, :hg:`import --partial` will create
2977 an empty changeset, importing only the patch metadata.
2982 an empty changeset, importing only the patch metadata.
2978
2983
2979 With -s/--similarity, hg will attempt to discover renames and
2984 With -s/--similarity, hg will attempt to discover renames and
2980 copies in the patch in the same way as :hg:`addremove`.
2985 copies in the patch in the same way as :hg:`addremove`.
2981
2986
2982 It is possible to use external patch programs to perform the patch
2987 It is possible to use external patch programs to perform the patch
2983 by setting the ``ui.patch`` configuration option. For the default
2988 by setting the ``ui.patch`` configuration option. For the default
2984 internal tool, the fuzz can also be configured via ``patch.fuzz``.
2989 internal tool, the fuzz can also be configured via ``patch.fuzz``.
2985 See :hg:`help config` for more information about configuration
2990 See :hg:`help config` for more information about configuration
2986 files and how to use these options.
2991 files and how to use these options.
2987
2992
2988 See :hg:`help dates` for a list of formats valid for -d/--date.
2993 See :hg:`help dates` for a list of formats valid for -d/--date.
2989
2994
2990 .. container:: verbose
2995 .. container:: verbose
2991
2996
2992 Examples:
2997 Examples:
2993
2998
2994 - import a traditional patch from a website and detect renames::
2999 - import a traditional patch from a website and detect renames::
2995
3000
2996 hg import -s 80 http://example.com/bugfix.patch
3001 hg import -s 80 http://example.com/bugfix.patch
2997
3002
2998 - import a changeset from an hgweb server::
3003 - import a changeset from an hgweb server::
2999
3004
3000 hg import https://www.mercurial-scm.org/repo/hg/rev/5ca8c111e9aa
3005 hg import https://www.mercurial-scm.org/repo/hg/rev/5ca8c111e9aa
3001
3006
3002 - import all the patches in an Unix-style mbox::
3007 - import all the patches in an Unix-style mbox::
3003
3008
3004 hg import incoming-patches.mbox
3009 hg import incoming-patches.mbox
3005
3010
3006 - import patches from stdin::
3011 - import patches from stdin::
3007
3012
3008 hg import -
3013 hg import -
3009
3014
3010 - attempt to exactly restore an exported changeset (not always
3015 - attempt to exactly restore an exported changeset (not always
3011 possible)::
3016 possible)::
3012
3017
3013 hg import --exact proposed-fix.patch
3018 hg import --exact proposed-fix.patch
3014
3019
3015 - use an external tool to apply a patch which is too fuzzy for
3020 - use an external tool to apply a patch which is too fuzzy for
3016 the default internal tool.
3021 the default internal tool.
3017
3022
3018 hg import --config ui.patch="patch --merge" fuzzy.patch
3023 hg import --config ui.patch="patch --merge" fuzzy.patch
3019
3024
3020 - change the default fuzzing from 2 to a less strict 7
3025 - change the default fuzzing from 2 to a less strict 7
3021
3026
3022 hg import --config ui.fuzz=7 fuzz.patch
3027 hg import --config ui.fuzz=7 fuzz.patch
3023
3028
3024 Returns 0 on success, 1 on partial success (see --partial).
3029 Returns 0 on success, 1 on partial success (see --partial).
3025 """
3030 """
3026
3031
3027 opts = pycompat.byteskwargs(opts)
3032 opts = pycompat.byteskwargs(opts)
3028 if not patch1:
3033 if not patch1:
3029 raise error.Abort(_('need at least one patch to import'))
3034 raise error.Abort(_('need at least one patch to import'))
3030
3035
3031 patches = (patch1,) + patches
3036 patches = (patch1,) + patches
3032
3037
3033 date = opts.get('date')
3038 date = opts.get('date')
3034 if date:
3039 if date:
3035 opts['date'] = util.parsedate(date)
3040 opts['date'] = util.parsedate(date)
3036
3041
3037 exact = opts.get('exact')
3042 exact = opts.get('exact')
3038 update = not opts.get('bypass')
3043 update = not opts.get('bypass')
3039 if not update and opts.get('no_commit'):
3044 if not update and opts.get('no_commit'):
3040 raise error.Abort(_('cannot use --no-commit with --bypass'))
3045 raise error.Abort(_('cannot use --no-commit with --bypass'))
3041 try:
3046 try:
3042 sim = float(opts.get('similarity') or 0)
3047 sim = float(opts.get('similarity') or 0)
3043 except ValueError:
3048 except ValueError:
3044 raise error.Abort(_('similarity must be a number'))
3049 raise error.Abort(_('similarity must be a number'))
3045 if sim < 0 or sim > 100:
3050 if sim < 0 or sim > 100:
3046 raise error.Abort(_('similarity must be between 0 and 100'))
3051 raise error.Abort(_('similarity must be between 0 and 100'))
3047 if sim and not update:
3052 if sim and not update:
3048 raise error.Abort(_('cannot use --similarity with --bypass'))
3053 raise error.Abort(_('cannot use --similarity with --bypass'))
3049 if exact:
3054 if exact:
3050 if opts.get('edit'):
3055 if opts.get('edit'):
3051 raise error.Abort(_('cannot use --exact with --edit'))
3056 raise error.Abort(_('cannot use --exact with --edit'))
3052 if opts.get('prefix'):
3057 if opts.get('prefix'):
3053 raise error.Abort(_('cannot use --exact with --prefix'))
3058 raise error.Abort(_('cannot use --exact with --prefix'))
3054
3059
3055 base = opts["base"]
3060 base = opts["base"]
3056 wlock = dsguard = lock = tr = None
3061 wlock = dsguard = lock = tr = None
3057 msgs = []
3062 msgs = []
3058 ret = 0
3063 ret = 0
3059
3064
3060
3065
3061 try:
3066 try:
3062 wlock = repo.wlock()
3067 wlock = repo.wlock()
3063
3068
3064 if update:
3069 if update:
3065 cmdutil.checkunfinished(repo)
3070 cmdutil.checkunfinished(repo)
3066 if (exact or not opts.get('force')):
3071 if (exact or not opts.get('force')):
3067 cmdutil.bailifchanged(repo)
3072 cmdutil.bailifchanged(repo)
3068
3073
3069 if not opts.get('no_commit'):
3074 if not opts.get('no_commit'):
3070 lock = repo.lock()
3075 lock = repo.lock()
3071 tr = repo.transaction('import')
3076 tr = repo.transaction('import')
3072 else:
3077 else:
3073 dsguard = dirstateguard.dirstateguard(repo, 'import')
3078 dsguard = dirstateguard.dirstateguard(repo, 'import')
3074 parents = repo[None].parents()
3079 parents = repo[None].parents()
3075 for patchurl in patches:
3080 for patchurl in patches:
3076 if patchurl == '-':
3081 if patchurl == '-':
3077 ui.status(_('applying patch from stdin\n'))
3082 ui.status(_('applying patch from stdin\n'))
3078 patchfile = ui.fin
3083 patchfile = ui.fin
3079 patchurl = 'stdin' # for error message
3084 patchurl = 'stdin' # for error message
3080 else:
3085 else:
3081 patchurl = os.path.join(base, patchurl)
3086 patchurl = os.path.join(base, patchurl)
3082 ui.status(_('applying %s\n') % patchurl)
3087 ui.status(_('applying %s\n') % patchurl)
3083 patchfile = hg.openpath(ui, patchurl)
3088 patchfile = hg.openpath(ui, patchurl)
3084
3089
3085 haspatch = False
3090 haspatch = False
3086 for hunk in patch.split(patchfile):
3091 for hunk in patch.split(patchfile):
3087 (msg, node, rej) = cmdutil.tryimportone(ui, repo, hunk,
3092 (msg, node, rej) = cmdutil.tryimportone(ui, repo, hunk,
3088 parents, opts,
3093 parents, opts,
3089 msgs, hg.clean)
3094 msgs, hg.clean)
3090 if msg:
3095 if msg:
3091 haspatch = True
3096 haspatch = True
3092 ui.note(msg + '\n')
3097 ui.note(msg + '\n')
3093 if update or exact:
3098 if update or exact:
3094 parents = repo[None].parents()
3099 parents = repo[None].parents()
3095 else:
3100 else:
3096 parents = [repo[node]]
3101 parents = [repo[node]]
3097 if rej:
3102 if rej:
3098 ui.write_err(_("patch applied partially\n"))
3103 ui.write_err(_("patch applied partially\n"))
3099 ui.write_err(_("(fix the .rej files and run "
3104 ui.write_err(_("(fix the .rej files and run "
3100 "`hg commit --amend`)\n"))
3105 "`hg commit --amend`)\n"))
3101 ret = 1
3106 ret = 1
3102 break
3107 break
3103
3108
3104 if not haspatch:
3109 if not haspatch:
3105 raise error.Abort(_('%s: no diffs found') % patchurl)
3110 raise error.Abort(_('%s: no diffs found') % patchurl)
3106
3111
3107 if tr:
3112 if tr:
3108 tr.close()
3113 tr.close()
3109 if msgs:
3114 if msgs:
3110 repo.savecommitmessage('\n* * *\n'.join(msgs))
3115 repo.savecommitmessage('\n* * *\n'.join(msgs))
3111 if dsguard:
3116 if dsguard:
3112 dsguard.close()
3117 dsguard.close()
3113 return ret
3118 return ret
3114 finally:
3119 finally:
3115 if tr:
3120 if tr:
3116 tr.release()
3121 tr.release()
3117 release(lock, dsguard, wlock)
3122 release(lock, dsguard, wlock)
3118
3123
3119 @command('incoming|in',
3124 @command('incoming|in',
3120 [('f', 'force', None,
3125 [('f', 'force', None,
3121 _('run even if remote repository is unrelated')),
3126 _('run even if remote repository is unrelated')),
3122 ('n', 'newest-first', None, _('show newest record first')),
3127 ('n', 'newest-first', None, _('show newest record first')),
3123 ('', 'bundle', '',
3128 ('', 'bundle', '',
3124 _('file to store the bundles into'), _('FILE')),
3129 _('file to store the bundles into'), _('FILE')),
3125 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
3130 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
3126 ('B', 'bookmarks', False, _("compare bookmarks")),
3131 ('B', 'bookmarks', False, _("compare bookmarks")),
3127 ('b', 'branch', [],
3132 ('b', 'branch', [],
3128 _('a specific branch you would like to pull'), _('BRANCH')),
3133 _('a specific branch you would like to pull'), _('BRANCH')),
3129 ] + logopts + remoteopts + subrepoopts,
3134 ] + logopts + remoteopts + subrepoopts,
3130 _('[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'))
3135 _('[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'))
3131 def incoming(ui, repo, source="default", **opts):
3136 def incoming(ui, repo, source="default", **opts):
3132 """show new changesets found in source
3137 """show new changesets found in source
3133
3138
3134 Show new changesets found in the specified path/URL or the default
3139 Show new changesets found in the specified path/URL or the default
3135 pull location. These are the changesets that would have been pulled
3140 pull location. These are the changesets that would have been pulled
3136 if a pull at the time you issued this command.
3141 if a pull at the time you issued this command.
3137
3142
3138 See pull for valid source format details.
3143 See pull for valid source format details.
3139
3144
3140 .. container:: verbose
3145 .. container:: verbose
3141
3146
3142 With -B/--bookmarks, the result of bookmark comparison between
3147 With -B/--bookmarks, the result of bookmark comparison between
3143 local and remote repositories is displayed. With -v/--verbose,
3148 local and remote repositories is displayed. With -v/--verbose,
3144 status is also displayed for each bookmark like below::
3149 status is also displayed for each bookmark like below::
3145
3150
3146 BM1 01234567890a added
3151 BM1 01234567890a added
3147 BM2 1234567890ab advanced
3152 BM2 1234567890ab advanced
3148 BM3 234567890abc diverged
3153 BM3 234567890abc diverged
3149 BM4 34567890abcd changed
3154 BM4 34567890abcd changed
3150
3155
3151 The action taken locally when pulling depends on the
3156 The action taken locally when pulling depends on the
3152 status of each bookmark:
3157 status of each bookmark:
3153
3158
3154 :``added``: pull will create it
3159 :``added``: pull will create it
3155 :``advanced``: pull will update it
3160 :``advanced``: pull will update it
3156 :``diverged``: pull will create a divergent bookmark
3161 :``diverged``: pull will create a divergent bookmark
3157 :``changed``: result depends on remote changesets
3162 :``changed``: result depends on remote changesets
3158
3163
3159 From the point of view of pulling behavior, bookmark
3164 From the point of view of pulling behavior, bookmark
3160 existing only in the remote repository are treated as ``added``,
3165 existing only in the remote repository are treated as ``added``,
3161 even if it is in fact locally deleted.
3166 even if it is in fact locally deleted.
3162
3167
3163 .. container:: verbose
3168 .. container:: verbose
3164
3169
3165 For remote repository, using --bundle avoids downloading the
3170 For remote repository, using --bundle avoids downloading the
3166 changesets twice if the incoming is followed by a pull.
3171 changesets twice if the incoming is followed by a pull.
3167
3172
3168 Examples:
3173 Examples:
3169
3174
3170 - show incoming changes with patches and full description::
3175 - show incoming changes with patches and full description::
3171
3176
3172 hg incoming -vp
3177 hg incoming -vp
3173
3178
3174 - show incoming changes excluding merges, store a bundle::
3179 - show incoming changes excluding merges, store a bundle::
3175
3180
3176 hg in -vpM --bundle incoming.hg
3181 hg in -vpM --bundle incoming.hg
3177 hg pull incoming.hg
3182 hg pull incoming.hg
3178
3183
3179 - briefly list changes inside a bundle::
3184 - briefly list changes inside a bundle::
3180
3185
3181 hg in changes.hg -T "{desc|firstline}\\n"
3186 hg in changes.hg -T "{desc|firstline}\\n"
3182
3187
3183 Returns 0 if there are incoming changes, 1 otherwise.
3188 Returns 0 if there are incoming changes, 1 otherwise.
3184 """
3189 """
3185 opts = pycompat.byteskwargs(opts)
3190 opts = pycompat.byteskwargs(opts)
3186 if opts.get('graph'):
3191 if opts.get('graph'):
3187 cmdutil.checkunsupportedgraphflags([], opts)
3192 cmdutil.checkunsupportedgraphflags([], opts)
3188 def display(other, chlist, displayer):
3193 def display(other, chlist, displayer):
3189 revdag = cmdutil.graphrevs(other, chlist, opts)
3194 revdag = cmdutil.graphrevs(other, chlist, opts)
3190 cmdutil.displaygraph(ui, repo, revdag, displayer,
3195 cmdutil.displaygraph(ui, repo, revdag, displayer,
3191 graphmod.asciiedges)
3196 graphmod.asciiedges)
3192
3197
3193 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
3198 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
3194 return 0
3199 return 0
3195
3200
3196 if opts.get('bundle') and opts.get('subrepos'):
3201 if opts.get('bundle') and opts.get('subrepos'):
3197 raise error.Abort(_('cannot combine --bundle and --subrepos'))
3202 raise error.Abort(_('cannot combine --bundle and --subrepos'))
3198
3203
3199 if opts.get('bookmarks'):
3204 if opts.get('bookmarks'):
3200 source, branches = hg.parseurl(ui.expandpath(source),
3205 source, branches = hg.parseurl(ui.expandpath(source),
3201 opts.get('branch'))
3206 opts.get('branch'))
3202 other = hg.peer(repo, opts, source)
3207 other = hg.peer(repo, opts, source)
3203 if 'bookmarks' not in other.listkeys('namespaces'):
3208 if 'bookmarks' not in other.listkeys('namespaces'):
3204 ui.warn(_("remote doesn't support bookmarks\n"))
3209 ui.warn(_("remote doesn't support bookmarks\n"))
3205 return 0
3210 return 0
3206 ui.pager('incoming')
3211 ui.pager('incoming')
3207 ui.status(_('comparing with %s\n') % util.hidepassword(source))
3212 ui.status(_('comparing with %s\n') % util.hidepassword(source))
3208 return bookmarks.incoming(ui, repo, other)
3213 return bookmarks.incoming(ui, repo, other)
3209
3214
3210 repo._subtoppath = ui.expandpath(source)
3215 repo._subtoppath = ui.expandpath(source)
3211 try:
3216 try:
3212 return hg.incoming(ui, repo, source, opts)
3217 return hg.incoming(ui, repo, source, opts)
3213 finally:
3218 finally:
3214 del repo._subtoppath
3219 del repo._subtoppath
3215
3220
3216
3221
3217 @command('^init', remoteopts, _('[-e CMD] [--remotecmd CMD] [DEST]'),
3222 @command('^init', remoteopts, _('[-e CMD] [--remotecmd CMD] [DEST]'),
3218 norepo=True)
3223 norepo=True)
3219 def init(ui, dest=".", **opts):
3224 def init(ui, dest=".", **opts):
3220 """create a new repository in the given directory
3225 """create a new repository in the given directory
3221
3226
3222 Initialize a new repository in the given directory. If the given
3227 Initialize a new repository in the given directory. If the given
3223 directory does not exist, it will be created.
3228 directory does not exist, it will be created.
3224
3229
3225 If no directory is given, the current directory is used.
3230 If no directory is given, the current directory is used.
3226
3231
3227 It is possible to specify an ``ssh://`` URL as the destination.
3232 It is possible to specify an ``ssh://`` URL as the destination.
3228 See :hg:`help urls` for more information.
3233 See :hg:`help urls` for more information.
3229
3234
3230 Returns 0 on success.
3235 Returns 0 on success.
3231 """
3236 """
3232 opts = pycompat.byteskwargs(opts)
3237 opts = pycompat.byteskwargs(opts)
3233 hg.peer(ui, opts, ui.expandpath(dest), create=True)
3238 hg.peer(ui, opts, ui.expandpath(dest), create=True)
3234
3239
3235 @command('locate',
3240 @command('locate',
3236 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
3241 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
3237 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
3242 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
3238 ('f', 'fullpath', None, _('print complete paths from the filesystem root')),
3243 ('f', 'fullpath', None, _('print complete paths from the filesystem root')),
3239 ] + walkopts,
3244 ] + walkopts,
3240 _('[OPTION]... [PATTERN]...'))
3245 _('[OPTION]... [PATTERN]...'))
3241 def locate(ui, repo, *pats, **opts):
3246 def locate(ui, repo, *pats, **opts):
3242 """locate files matching specific patterns (DEPRECATED)
3247 """locate files matching specific patterns (DEPRECATED)
3243
3248
3244 Print files under Mercurial control in the working directory whose
3249 Print files under Mercurial control in the working directory whose
3245 names match the given patterns.
3250 names match the given patterns.
3246
3251
3247 By default, this command searches all directories in the working
3252 By default, this command searches all directories in the working
3248 directory. To search just the current directory and its
3253 directory. To search just the current directory and its
3249 subdirectories, use "--include .".
3254 subdirectories, use "--include .".
3250
3255
3251 If no patterns are given to match, this command prints the names
3256 If no patterns are given to match, this command prints the names
3252 of all files under Mercurial control in the working directory.
3257 of all files under Mercurial control in the working directory.
3253
3258
3254 If you want to feed the output of this command into the "xargs"
3259 If you want to feed the output of this command into the "xargs"
3255 command, use the -0 option to both this command and "xargs". This
3260 command, use the -0 option to both this command and "xargs". This
3256 will avoid the problem of "xargs" treating single filenames that
3261 will avoid the problem of "xargs" treating single filenames that
3257 contain whitespace as multiple filenames.
3262 contain whitespace as multiple filenames.
3258
3263
3259 See :hg:`help files` for a more versatile command.
3264 See :hg:`help files` for a more versatile command.
3260
3265
3261 Returns 0 if a match is found, 1 otherwise.
3266 Returns 0 if a match is found, 1 otherwise.
3262 """
3267 """
3263 opts = pycompat.byteskwargs(opts)
3268 opts = pycompat.byteskwargs(opts)
3264 if opts.get('print0'):
3269 if opts.get('print0'):
3265 end = '\0'
3270 end = '\0'
3266 else:
3271 else:
3267 end = '\n'
3272 end = '\n'
3268 rev = scmutil.revsingle(repo, opts.get('rev'), None).node()
3273 rev = scmutil.revsingle(repo, opts.get('rev'), None).node()
3269
3274
3270 ret = 1
3275 ret = 1
3271 ctx = repo[rev]
3276 ctx = repo[rev]
3272 m = scmutil.match(ctx, pats, opts, default='relglob',
3277 m = scmutil.match(ctx, pats, opts, default='relglob',
3273 badfn=lambda x, y: False)
3278 badfn=lambda x, y: False)
3274
3279
3275 ui.pager('locate')
3280 ui.pager('locate')
3276 for abs in ctx.matches(m):
3281 for abs in ctx.matches(m):
3277 if opts.get('fullpath'):
3282 if opts.get('fullpath'):
3278 ui.write(repo.wjoin(abs), end)
3283 ui.write(repo.wjoin(abs), end)
3279 else:
3284 else:
3280 ui.write(((pats and m.rel(abs)) or abs), end)
3285 ui.write(((pats and m.rel(abs)) or abs), end)
3281 ret = 0
3286 ret = 0
3282
3287
3283 return ret
3288 return ret
3284
3289
3285 @command('^log|history',
3290 @command('^log|history',
3286 [('f', 'follow', None,
3291 [('f', 'follow', None,
3287 _('follow changeset history, or file history across copies and renames')),
3292 _('follow changeset history, or file history across copies and renames')),
3288 ('', 'follow-first', None,
3293 ('', 'follow-first', None,
3289 _('only follow the first parent of merge changesets (DEPRECATED)')),
3294 _('only follow the first parent of merge changesets (DEPRECATED)')),
3290 ('d', 'date', '', _('show revisions matching date spec'), _('DATE')),
3295 ('d', 'date', '', _('show revisions matching date spec'), _('DATE')),
3291 ('C', 'copies', None, _('show copied files')),
3296 ('C', 'copies', None, _('show copied files')),
3292 ('k', 'keyword', [],
3297 ('k', 'keyword', [],
3293 _('do case-insensitive search for a given text'), _('TEXT')),
3298 _('do case-insensitive search for a given text'), _('TEXT')),
3294 ('r', 'rev', [], _('show the specified revision or revset'), _('REV')),
3299 ('r', 'rev', [], _('show the specified revision or revset'), _('REV')),
3295 ('', 'removed', None, _('include revisions where files were removed')),
3300 ('', 'removed', None, _('include revisions where files were removed')),
3296 ('m', 'only-merges', None, _('show only merges (DEPRECATED)')),
3301 ('m', 'only-merges', None, _('show only merges (DEPRECATED)')),
3297 ('u', 'user', [], _('revisions committed by user'), _('USER')),
3302 ('u', 'user', [], _('revisions committed by user'), _('USER')),
3298 ('', 'only-branch', [],
3303 ('', 'only-branch', [],
3299 _('show only changesets within the given named branch (DEPRECATED)'),
3304 _('show only changesets within the given named branch (DEPRECATED)'),
3300 _('BRANCH')),
3305 _('BRANCH')),
3301 ('b', 'branch', [],
3306 ('b', 'branch', [],
3302 _('show changesets within the given named branch'), _('BRANCH')),
3307 _('show changesets within the given named branch'), _('BRANCH')),
3303 ('P', 'prune', [],
3308 ('P', 'prune', [],
3304 _('do not display revision or any of its ancestors'), _('REV')),
3309 _('do not display revision or any of its ancestors'), _('REV')),
3305 ] + logopts + walkopts,
3310 ] + logopts + walkopts,
3306 _('[OPTION]... [FILE]'),
3311 _('[OPTION]... [FILE]'),
3307 inferrepo=True)
3312 inferrepo=True)
3308 def log(ui, repo, *pats, **opts):
3313 def log(ui, repo, *pats, **opts):
3309 """show revision history of entire repository or files
3314 """show revision history of entire repository or files
3310
3315
3311 Print the revision history of the specified files or the entire
3316 Print the revision history of the specified files or the entire
3312 project.
3317 project.
3313
3318
3314 If no revision range is specified, the default is ``tip:0`` unless
3319 If no revision range is specified, the default is ``tip:0`` unless
3315 --follow is set, in which case the working directory parent is
3320 --follow is set, in which case the working directory parent is
3316 used as the starting revision.
3321 used as the starting revision.
3317
3322
3318 File history is shown without following rename or copy history of
3323 File history is shown without following rename or copy history of
3319 files. Use -f/--follow with a filename to follow history across
3324 files. Use -f/--follow with a filename to follow history across
3320 renames and copies. --follow without a filename will only show
3325 renames and copies. --follow without a filename will only show
3321 ancestors or descendants of the starting revision.
3326 ancestors or descendants of the starting revision.
3322
3327
3323 By default this command prints revision number and changeset id,
3328 By default this command prints revision number and changeset id,
3324 tags, non-trivial parents, user, date and time, and a summary for
3329 tags, non-trivial parents, user, date and time, and a summary for
3325 each commit. When the -v/--verbose switch is used, the list of
3330 each commit. When the -v/--verbose switch is used, the list of
3326 changed files and full commit message are shown.
3331 changed files and full commit message are shown.
3327
3332
3328 With --graph the revisions are shown as an ASCII art DAG with the most
3333 With --graph the revisions are shown as an ASCII art DAG with the most
3329 recent changeset at the top.
3334 recent changeset at the top.
3330 'o' is a changeset, '@' is a working directory parent, 'x' is obsolete,
3335 'o' is a changeset, '@' is a working directory parent, 'x' is obsolete,
3331 and '+' represents a fork where the changeset from the lines below is a
3336 and '+' represents a fork where the changeset from the lines below is a
3332 parent of the 'o' merge on the same line.
3337 parent of the 'o' merge on the same line.
3333 Paths in the DAG are represented with '|', '/' and so forth. ':' in place
3338 Paths in the DAG are represented with '|', '/' and so forth. ':' in place
3334 of a '|' indicates one or more revisions in a path are omitted.
3339 of a '|' indicates one or more revisions in a path are omitted.
3335
3340
3336 .. note::
3341 .. note::
3337
3342
3338 :hg:`log --patch` may generate unexpected diff output for merge
3343 :hg:`log --patch` may generate unexpected diff output for merge
3339 changesets, as it will only compare the merge changeset against
3344 changesets, as it will only compare the merge changeset against
3340 its first parent. Also, only files different from BOTH parents
3345 its first parent. Also, only files different from BOTH parents
3341 will appear in files:.
3346 will appear in files:.
3342
3347
3343 .. note::
3348 .. note::
3344
3349
3345 For performance reasons, :hg:`log FILE` may omit duplicate changes
3350 For performance reasons, :hg:`log FILE` may omit duplicate changes
3346 made on branches and will not show removals or mode changes. To
3351 made on branches and will not show removals or mode changes. To
3347 see all such changes, use the --removed switch.
3352 see all such changes, use the --removed switch.
3348
3353
3349 .. container:: verbose
3354 .. container:: verbose
3350
3355
3351 Some examples:
3356 Some examples:
3352
3357
3353 - changesets with full descriptions and file lists::
3358 - changesets with full descriptions and file lists::
3354
3359
3355 hg log -v
3360 hg log -v
3356
3361
3357 - changesets ancestral to the working directory::
3362 - changesets ancestral to the working directory::
3358
3363
3359 hg log -f
3364 hg log -f
3360
3365
3361 - last 10 commits on the current branch::
3366 - last 10 commits on the current branch::
3362
3367
3363 hg log -l 10 -b .
3368 hg log -l 10 -b .
3364
3369
3365 - changesets showing all modifications of a file, including removals::
3370 - changesets showing all modifications of a file, including removals::
3366
3371
3367 hg log --removed file.c
3372 hg log --removed file.c
3368
3373
3369 - all changesets that touch a directory, with diffs, excluding merges::
3374 - all changesets that touch a directory, with diffs, excluding merges::
3370
3375
3371 hg log -Mp lib/
3376 hg log -Mp lib/
3372
3377
3373 - all revision numbers that match a keyword::
3378 - all revision numbers that match a keyword::
3374
3379
3375 hg log -k bug --template "{rev}\\n"
3380 hg log -k bug --template "{rev}\\n"
3376
3381
3377 - the full hash identifier of the working directory parent::
3382 - the full hash identifier of the working directory parent::
3378
3383
3379 hg log -r . --template "{node}\\n"
3384 hg log -r . --template "{node}\\n"
3380
3385
3381 - list available log templates::
3386 - list available log templates::
3382
3387
3383 hg log -T list
3388 hg log -T list
3384
3389
3385 - check if a given changeset is included in a tagged release::
3390 - check if a given changeset is included in a tagged release::
3386
3391
3387 hg log -r "a21ccf and ancestor(1.9)"
3392 hg log -r "a21ccf and ancestor(1.9)"
3388
3393
3389 - find all changesets by some user in a date range::
3394 - find all changesets by some user in a date range::
3390
3395
3391 hg log -k alice -d "may 2008 to jul 2008"
3396 hg log -k alice -d "may 2008 to jul 2008"
3392
3397
3393 - summary of all changesets after the last tag::
3398 - summary of all changesets after the last tag::
3394
3399
3395 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
3400 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
3396
3401
3397 See :hg:`help dates` for a list of formats valid for -d/--date.
3402 See :hg:`help dates` for a list of formats valid for -d/--date.
3398
3403
3399 See :hg:`help revisions` for more about specifying and ordering
3404 See :hg:`help revisions` for more about specifying and ordering
3400 revisions.
3405 revisions.
3401
3406
3402 See :hg:`help templates` for more about pre-packaged styles and
3407 See :hg:`help templates` for more about pre-packaged styles and
3403 specifying custom templates.
3408 specifying custom templates.
3404
3409
3405 Returns 0 on success.
3410 Returns 0 on success.
3406
3411
3407 """
3412 """
3408 opts = pycompat.byteskwargs(opts)
3413 opts = pycompat.byteskwargs(opts)
3409 if opts.get('follow') and opts.get('rev'):
3414 if opts.get('follow') and opts.get('rev'):
3410 opts['rev'] = [revsetlang.formatspec('reverse(::%lr)', opts.get('rev'))]
3415 opts['rev'] = [revsetlang.formatspec('reverse(::%lr)', opts.get('rev'))]
3411 del opts['follow']
3416 del opts['follow']
3412
3417
3413 if opts.get('graph'):
3418 if opts.get('graph'):
3414 return cmdutil.graphlog(ui, repo, pats, opts)
3419 return cmdutil.graphlog(ui, repo, pats, opts)
3415
3420
3416 revs, expr, filematcher = cmdutil.getlogrevs(repo, pats, opts)
3421 revs, expr, filematcher = cmdutil.getlogrevs(repo, pats, opts)
3417 limit = cmdutil.loglimit(opts)
3422 limit = cmdutil.loglimit(opts)
3418 count = 0
3423 count = 0
3419
3424
3420 getrenamed = None
3425 getrenamed = None
3421 if opts.get('copies'):
3426 if opts.get('copies'):
3422 endrev = None
3427 endrev = None
3423 if opts.get('rev'):
3428 if opts.get('rev'):
3424 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
3429 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
3425 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
3430 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
3426
3431
3427 ui.pager('log')
3432 ui.pager('log')
3428 displayer = cmdutil.show_changeset(ui, repo, opts, buffered=True)
3433 displayer = cmdutil.show_changeset(ui, repo, opts, buffered=True)
3429 for rev in revs:
3434 for rev in revs:
3430 if count == limit:
3435 if count == limit:
3431 break
3436 break
3432 ctx = repo[rev]
3437 ctx = repo[rev]
3433 copies = None
3438 copies = None
3434 if getrenamed is not None and rev:
3439 if getrenamed is not None and rev:
3435 copies = []
3440 copies = []
3436 for fn in ctx.files():
3441 for fn in ctx.files():
3437 rename = getrenamed(fn, rev)
3442 rename = getrenamed(fn, rev)
3438 if rename:
3443 if rename:
3439 copies.append((fn, rename[0]))
3444 copies.append((fn, rename[0]))
3440 if filematcher:
3445 if filematcher:
3441 revmatchfn = filematcher(ctx.rev())
3446 revmatchfn = filematcher(ctx.rev())
3442 else:
3447 else:
3443 revmatchfn = None
3448 revmatchfn = None
3444 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
3449 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
3445 if displayer.flush(ctx):
3450 if displayer.flush(ctx):
3446 count += 1
3451 count += 1
3447
3452
3448 displayer.close()
3453 displayer.close()
3449
3454
3450 @command('manifest',
3455 @command('manifest',
3451 [('r', 'rev', '', _('revision to display'), _('REV')),
3456 [('r', 'rev', '', _('revision to display'), _('REV')),
3452 ('', 'all', False, _("list files from all revisions"))]
3457 ('', 'all', False, _("list files from all revisions"))]
3453 + formatteropts,
3458 + formatteropts,
3454 _('[-r REV]'))
3459 _('[-r REV]'))
3455 def manifest(ui, repo, node=None, rev=None, **opts):
3460 def manifest(ui, repo, node=None, rev=None, **opts):
3456 """output the current or given revision of the project manifest
3461 """output the current or given revision of the project manifest
3457
3462
3458 Print a list of version controlled files for the given revision.
3463 Print a list of version controlled files for the given revision.
3459 If no revision is given, the first parent of the working directory
3464 If no revision is given, the first parent of the working directory
3460 is used, or the null revision if no revision is checked out.
3465 is used, or the null revision if no revision is checked out.
3461
3466
3462 With -v, print file permissions, symlink and executable bits.
3467 With -v, print file permissions, symlink and executable bits.
3463 With --debug, print file revision hashes.
3468 With --debug, print file revision hashes.
3464
3469
3465 If option --all is specified, the list of all files from all revisions
3470 If option --all is specified, the list of all files from all revisions
3466 is printed. This includes deleted and renamed files.
3471 is printed. This includes deleted and renamed files.
3467
3472
3468 Returns 0 on success.
3473 Returns 0 on success.
3469 """
3474 """
3470 opts = pycompat.byteskwargs(opts)
3475 opts = pycompat.byteskwargs(opts)
3471 fm = ui.formatter('manifest', opts)
3476 fm = ui.formatter('manifest', opts)
3472
3477
3473 if opts.get('all'):
3478 if opts.get('all'):
3474 if rev or node:
3479 if rev or node:
3475 raise error.Abort(_("can't specify a revision with --all"))
3480 raise error.Abort(_("can't specify a revision with --all"))
3476
3481
3477 res = []
3482 res = []
3478 prefix = "data/"
3483 prefix = "data/"
3479 suffix = ".i"
3484 suffix = ".i"
3480 plen = len(prefix)
3485 plen = len(prefix)
3481 slen = len(suffix)
3486 slen = len(suffix)
3482 with repo.lock():
3487 with repo.lock():
3483 for fn, b, size in repo.store.datafiles():
3488 for fn, b, size in repo.store.datafiles():
3484 if size != 0 and fn[-slen:] == suffix and fn[:plen] == prefix:
3489 if size != 0 and fn[-slen:] == suffix and fn[:plen] == prefix:
3485 res.append(fn[plen:-slen])
3490 res.append(fn[plen:-slen])
3486 ui.pager('manifest')
3491 ui.pager('manifest')
3487 for f in res:
3492 for f in res:
3488 fm.startitem()
3493 fm.startitem()
3489 fm.write("path", '%s\n', f)
3494 fm.write("path", '%s\n', f)
3490 fm.end()
3495 fm.end()
3491 return
3496 return
3492
3497
3493 if rev and node:
3498 if rev and node:
3494 raise error.Abort(_("please specify just one revision"))
3499 raise error.Abort(_("please specify just one revision"))
3495
3500
3496 if not node:
3501 if not node:
3497 node = rev
3502 node = rev
3498
3503
3499 char = {'l': '@', 'x': '*', '': ''}
3504 char = {'l': '@', 'x': '*', '': ''}
3500 mode = {'l': '644', 'x': '755', '': '644'}
3505 mode = {'l': '644', 'x': '755', '': '644'}
3501 ctx = scmutil.revsingle(repo, node)
3506 ctx = scmutil.revsingle(repo, node)
3502 mf = ctx.manifest()
3507 mf = ctx.manifest()
3503 ui.pager('manifest')
3508 ui.pager('manifest')
3504 for f in ctx:
3509 for f in ctx:
3505 fm.startitem()
3510 fm.startitem()
3506 fl = ctx[f].flags()
3511 fl = ctx[f].flags()
3507 fm.condwrite(ui.debugflag, 'hash', '%s ', hex(mf[f]))
3512 fm.condwrite(ui.debugflag, 'hash', '%s ', hex(mf[f]))
3508 fm.condwrite(ui.verbose, 'mode type', '%s %1s ', mode[fl], char[fl])
3513 fm.condwrite(ui.verbose, 'mode type', '%s %1s ', mode[fl], char[fl])
3509 fm.write('path', '%s\n', f)
3514 fm.write('path', '%s\n', f)
3510 fm.end()
3515 fm.end()
3511
3516
3512 @command('^merge',
3517 @command('^merge',
3513 [('f', 'force', None,
3518 [('f', 'force', None,
3514 _('force a merge including outstanding changes (DEPRECATED)')),
3519 _('force a merge including outstanding changes (DEPRECATED)')),
3515 ('r', 'rev', '', _('revision to merge'), _('REV')),
3520 ('r', 'rev', '', _('revision to merge'), _('REV')),
3516 ('P', 'preview', None,
3521 ('P', 'preview', None,
3517 _('review revisions to merge (no merge is performed)'))
3522 _('review revisions to merge (no merge is performed)'))
3518 ] + mergetoolopts,
3523 ] + mergetoolopts,
3519 _('[-P] [[-r] REV]'))
3524 _('[-P] [[-r] REV]'))
3520 def merge(ui, repo, node=None, **opts):
3525 def merge(ui, repo, node=None, **opts):
3521 """merge another revision into working directory
3526 """merge another revision into working directory
3522
3527
3523 The current working directory is updated with all changes made in
3528 The current working directory is updated with all changes made in
3524 the requested revision since the last common predecessor revision.
3529 the requested revision since the last common predecessor revision.
3525
3530
3526 Files that changed between either parent are marked as changed for
3531 Files that changed between either parent are marked as changed for
3527 the next commit and a commit must be performed before any further
3532 the next commit and a commit must be performed before any further
3528 updates to the repository are allowed. The next commit will have
3533 updates to the repository are allowed. The next commit will have
3529 two parents.
3534 two parents.
3530
3535
3531 ``--tool`` can be used to specify the merge tool used for file
3536 ``--tool`` can be used to specify the merge tool used for file
3532 merges. It overrides the HGMERGE environment variable and your
3537 merges. It overrides the HGMERGE environment variable and your
3533 configuration files. See :hg:`help merge-tools` for options.
3538 configuration files. See :hg:`help merge-tools` for options.
3534
3539
3535 If no revision is specified, the working directory's parent is a
3540 If no revision is specified, the working directory's parent is a
3536 head revision, and the current branch contains exactly one other
3541 head revision, and the current branch contains exactly one other
3537 head, the other head is merged with by default. Otherwise, an
3542 head, the other head is merged with by default. Otherwise, an
3538 explicit revision with which to merge with must be provided.
3543 explicit revision with which to merge with must be provided.
3539
3544
3540 See :hg:`help resolve` for information on handling file conflicts.
3545 See :hg:`help resolve` for information on handling file conflicts.
3541
3546
3542 To undo an uncommitted merge, use :hg:`update --clean .` which
3547 To undo an uncommitted merge, use :hg:`update --clean .` which
3543 will check out a clean copy of the original merge parent, losing
3548 will check out a clean copy of the original merge parent, losing
3544 all changes.
3549 all changes.
3545
3550
3546 Returns 0 on success, 1 if there are unresolved files.
3551 Returns 0 on success, 1 if there are unresolved files.
3547 """
3552 """
3548
3553
3549 opts = pycompat.byteskwargs(opts)
3554 opts = pycompat.byteskwargs(opts)
3550 if opts.get('rev') and node:
3555 if opts.get('rev') and node:
3551 raise error.Abort(_("please specify just one revision"))
3556 raise error.Abort(_("please specify just one revision"))
3552 if not node:
3557 if not node:
3553 node = opts.get('rev')
3558 node = opts.get('rev')
3554
3559
3555 if node:
3560 if node:
3556 node = scmutil.revsingle(repo, node).node()
3561 node = scmutil.revsingle(repo, node).node()
3557
3562
3558 if not node:
3563 if not node:
3559 node = repo[destutil.destmerge(repo)].node()
3564 node = repo[destutil.destmerge(repo)].node()
3560
3565
3561 if opts.get('preview'):
3566 if opts.get('preview'):
3562 # find nodes that are ancestors of p2 but not of p1
3567 # find nodes that are ancestors of p2 but not of p1
3563 p1 = repo.lookup('.')
3568 p1 = repo.lookup('.')
3564 p2 = repo.lookup(node)
3569 p2 = repo.lookup(node)
3565 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
3570 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
3566
3571
3567 displayer = cmdutil.show_changeset(ui, repo, opts)
3572 displayer = cmdutil.show_changeset(ui, repo, opts)
3568 for node in nodes:
3573 for node in nodes:
3569 displayer.show(repo[node])
3574 displayer.show(repo[node])
3570 displayer.close()
3575 displayer.close()
3571 return 0
3576 return 0
3572
3577
3573 try:
3578 try:
3574 # ui.forcemerge is an internal variable, do not document
3579 # ui.forcemerge is an internal variable, do not document
3575 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''), 'merge')
3580 repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''), 'merge')
3576 force = opts.get('force')
3581 force = opts.get('force')
3577 labels = ['working copy', 'merge rev']
3582 labels = ['working copy', 'merge rev']
3578 return hg.merge(repo, node, force=force, mergeforce=force,
3583 return hg.merge(repo, node, force=force, mergeforce=force,
3579 labels=labels)
3584 labels=labels)
3580 finally:
3585 finally:
3581 ui.setconfig('ui', 'forcemerge', '', 'merge')
3586 ui.setconfig('ui', 'forcemerge', '', 'merge')
3582
3587
3583 @command('outgoing|out',
3588 @command('outgoing|out',
3584 [('f', 'force', None, _('run even when the destination is unrelated')),
3589 [('f', 'force', None, _('run even when the destination is unrelated')),
3585 ('r', 'rev', [],
3590 ('r', 'rev', [],
3586 _('a changeset intended to be included in the destination'), _('REV')),
3591 _('a changeset intended to be included in the destination'), _('REV')),
3587 ('n', 'newest-first', None, _('show newest record first')),
3592 ('n', 'newest-first', None, _('show newest record first')),
3588 ('B', 'bookmarks', False, _('compare bookmarks')),
3593 ('B', 'bookmarks', False, _('compare bookmarks')),
3589 ('b', 'branch', [], _('a specific branch you would like to push'),
3594 ('b', 'branch', [], _('a specific branch you would like to push'),
3590 _('BRANCH')),
3595 _('BRANCH')),
3591 ] + logopts + remoteopts + subrepoopts,
3596 ] + logopts + remoteopts + subrepoopts,
3592 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]'))
3597 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]'))
3593 def outgoing(ui, repo, dest=None, **opts):
3598 def outgoing(ui, repo, dest=None, **opts):
3594 """show changesets not found in the destination
3599 """show changesets not found in the destination
3595
3600
3596 Show changesets not found in the specified destination repository
3601 Show changesets not found in the specified destination repository
3597 or the default push location. These are the changesets that would
3602 or the default push location. These are the changesets that would
3598 be pushed if a push was requested.
3603 be pushed if a push was requested.
3599
3604
3600 See pull for details of valid destination formats.
3605 See pull for details of valid destination formats.
3601
3606
3602 .. container:: verbose
3607 .. container:: verbose
3603
3608
3604 With -B/--bookmarks, the result of bookmark comparison between
3609 With -B/--bookmarks, the result of bookmark comparison between
3605 local and remote repositories is displayed. With -v/--verbose,
3610 local and remote repositories is displayed. With -v/--verbose,
3606 status is also displayed for each bookmark like below::
3611 status is also displayed for each bookmark like below::
3607
3612
3608 BM1 01234567890a added
3613 BM1 01234567890a added
3609 BM2 deleted
3614 BM2 deleted
3610 BM3 234567890abc advanced
3615 BM3 234567890abc advanced
3611 BM4 34567890abcd diverged
3616 BM4 34567890abcd diverged
3612 BM5 4567890abcde changed
3617 BM5 4567890abcde changed
3613
3618
3614 The action taken when pushing depends on the
3619 The action taken when pushing depends on the
3615 status of each bookmark:
3620 status of each bookmark:
3616
3621
3617 :``added``: push with ``-B`` will create it
3622 :``added``: push with ``-B`` will create it
3618 :``deleted``: push with ``-B`` will delete it
3623 :``deleted``: push with ``-B`` will delete it
3619 :``advanced``: push will update it
3624 :``advanced``: push will update it
3620 :``diverged``: push with ``-B`` will update it
3625 :``diverged``: push with ``-B`` will update it
3621 :``changed``: push with ``-B`` will update it
3626 :``changed``: push with ``-B`` will update it
3622
3627
3623 From the point of view of pushing behavior, bookmarks
3628 From the point of view of pushing behavior, bookmarks
3624 existing only in the remote repository are treated as
3629 existing only in the remote repository are treated as
3625 ``deleted``, even if it is in fact added remotely.
3630 ``deleted``, even if it is in fact added remotely.
3626
3631
3627 Returns 0 if there are outgoing changes, 1 otherwise.
3632 Returns 0 if there are outgoing changes, 1 otherwise.
3628 """
3633 """
3629 opts = pycompat.byteskwargs(opts)
3634 opts = pycompat.byteskwargs(opts)
3630 if opts.get('graph'):
3635 if opts.get('graph'):
3631 cmdutil.checkunsupportedgraphflags([], opts)
3636 cmdutil.checkunsupportedgraphflags([], opts)
3632 o, other = hg._outgoing(ui, repo, dest, opts)
3637 o, other = hg._outgoing(ui, repo, dest, opts)
3633 if not o:
3638 if not o:
3634 cmdutil.outgoinghooks(ui, repo, other, opts, o)
3639 cmdutil.outgoinghooks(ui, repo, other, opts, o)
3635 return
3640 return
3636
3641
3637 revdag = cmdutil.graphrevs(repo, o, opts)
3642 revdag = cmdutil.graphrevs(repo, o, opts)
3638 ui.pager('outgoing')
3643 ui.pager('outgoing')
3639 displayer = cmdutil.show_changeset(ui, repo, opts, buffered=True)
3644 displayer = cmdutil.show_changeset(ui, repo, opts, buffered=True)
3640 cmdutil.displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges)
3645 cmdutil.displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges)
3641 cmdutil.outgoinghooks(ui, repo, other, opts, o)
3646 cmdutil.outgoinghooks(ui, repo, other, opts, o)
3642 return 0
3647 return 0
3643
3648
3644 if opts.get('bookmarks'):
3649 if opts.get('bookmarks'):
3645 dest = ui.expandpath(dest or 'default-push', dest or 'default')
3650 dest = ui.expandpath(dest or 'default-push', dest or 'default')
3646 dest, branches = hg.parseurl(dest, opts.get('branch'))
3651 dest, branches = hg.parseurl(dest, opts.get('branch'))
3647 other = hg.peer(repo, opts, dest)
3652 other = hg.peer(repo, opts, dest)
3648 if 'bookmarks' not in other.listkeys('namespaces'):
3653 if 'bookmarks' not in other.listkeys('namespaces'):
3649 ui.warn(_("remote doesn't support bookmarks\n"))
3654 ui.warn(_("remote doesn't support bookmarks\n"))
3650 return 0
3655 return 0
3651 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
3656 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
3652 ui.pager('outgoing')
3657 ui.pager('outgoing')
3653 return bookmarks.outgoing(ui, repo, other)
3658 return bookmarks.outgoing(ui, repo, other)
3654
3659
3655 repo._subtoppath = ui.expandpath(dest or 'default-push', dest or 'default')
3660 repo._subtoppath = ui.expandpath(dest or 'default-push', dest or 'default')
3656 try:
3661 try:
3657 return hg.outgoing(ui, repo, dest, opts)
3662 return hg.outgoing(ui, repo, dest, opts)
3658 finally:
3663 finally:
3659 del repo._subtoppath
3664 del repo._subtoppath
3660
3665
3661 @command('parents',
3666 @command('parents',
3662 [('r', 'rev', '', _('show parents of the specified revision'), _('REV')),
3667 [('r', 'rev', '', _('show parents of the specified revision'), _('REV')),
3663 ] + templateopts,
3668 ] + templateopts,
3664 _('[-r REV] [FILE]'),
3669 _('[-r REV] [FILE]'),
3665 inferrepo=True)
3670 inferrepo=True)
3666 def parents(ui, repo, file_=None, **opts):
3671 def parents(ui, repo, file_=None, **opts):
3667 """show the parents of the working directory or revision (DEPRECATED)
3672 """show the parents of the working directory or revision (DEPRECATED)
3668
3673
3669 Print the working directory's parent revisions. If a revision is
3674 Print the working directory's parent revisions. If a revision is
3670 given via -r/--rev, the parent of that revision will be printed.
3675 given via -r/--rev, the parent of that revision will be printed.
3671 If a file argument is given, the revision in which the file was
3676 If a file argument is given, the revision in which the file was
3672 last changed (before the working directory revision or the
3677 last changed (before the working directory revision or the
3673 argument to --rev if given) is printed.
3678 argument to --rev if given) is printed.
3674
3679
3675 This command is equivalent to::
3680 This command is equivalent to::
3676
3681
3677 hg log -r "p1()+p2()" or
3682 hg log -r "p1()+p2()" or
3678 hg log -r "p1(REV)+p2(REV)" or
3683 hg log -r "p1(REV)+p2(REV)" or
3679 hg log -r "max(::p1() and file(FILE))+max(::p2() and file(FILE))" or
3684 hg log -r "max(::p1() and file(FILE))+max(::p2() and file(FILE))" or
3680 hg log -r "max(::p1(REV) and file(FILE))+max(::p2(REV) and file(FILE))"
3685 hg log -r "max(::p1(REV) and file(FILE))+max(::p2(REV) and file(FILE))"
3681
3686
3682 See :hg:`summary` and :hg:`help revsets` for related information.
3687 See :hg:`summary` and :hg:`help revsets` for related information.
3683
3688
3684 Returns 0 on success.
3689 Returns 0 on success.
3685 """
3690 """
3686
3691
3687 opts = pycompat.byteskwargs(opts)
3692 opts = pycompat.byteskwargs(opts)
3688 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
3693 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
3689
3694
3690 if file_:
3695 if file_:
3691 m = scmutil.match(ctx, (file_,), opts)
3696 m = scmutil.match(ctx, (file_,), opts)
3692 if m.anypats() or len(m.files()) != 1:
3697 if m.anypats() or len(m.files()) != 1:
3693 raise error.Abort(_('can only specify an explicit filename'))
3698 raise error.Abort(_('can only specify an explicit filename'))
3694 file_ = m.files()[0]
3699 file_ = m.files()[0]
3695 filenodes = []
3700 filenodes = []
3696 for cp in ctx.parents():
3701 for cp in ctx.parents():
3697 if not cp:
3702 if not cp:
3698 continue
3703 continue
3699 try:
3704 try:
3700 filenodes.append(cp.filenode(file_))
3705 filenodes.append(cp.filenode(file_))
3701 except error.LookupError:
3706 except error.LookupError:
3702 pass
3707 pass
3703 if not filenodes:
3708 if not filenodes:
3704 raise error.Abort(_("'%s' not found in manifest!") % file_)
3709 raise error.Abort(_("'%s' not found in manifest!") % file_)
3705 p = []
3710 p = []
3706 for fn in filenodes:
3711 for fn in filenodes:
3707 fctx = repo.filectx(file_, fileid=fn)
3712 fctx = repo.filectx(file_, fileid=fn)
3708 p.append(fctx.node())
3713 p.append(fctx.node())
3709 else:
3714 else:
3710 p = [cp.node() for cp in ctx.parents()]
3715 p = [cp.node() for cp in ctx.parents()]
3711
3716
3712 displayer = cmdutil.show_changeset(ui, repo, opts)
3717 displayer = cmdutil.show_changeset(ui, repo, opts)
3713 for n in p:
3718 for n in p:
3714 if n != nullid:
3719 if n != nullid:
3715 displayer.show(repo[n])
3720 displayer.show(repo[n])
3716 displayer.close()
3721 displayer.close()
3717
3722
3718 @command('paths', formatteropts, _('[NAME]'), optionalrepo=True)
3723 @command('paths', formatteropts, _('[NAME]'), optionalrepo=True)
3719 def paths(ui, repo, search=None, **opts):
3724 def paths(ui, repo, search=None, **opts):
3720 """show aliases for remote repositories
3725 """show aliases for remote repositories
3721
3726
3722 Show definition of symbolic path name NAME. If no name is given,
3727 Show definition of symbolic path name NAME. If no name is given,
3723 show definition of all available names.
3728 show definition of all available names.
3724
3729
3725 Option -q/--quiet suppresses all output when searching for NAME
3730 Option -q/--quiet suppresses all output when searching for NAME
3726 and shows only the path names when listing all definitions.
3731 and shows only the path names when listing all definitions.
3727
3732
3728 Path names are defined in the [paths] section of your
3733 Path names are defined in the [paths] section of your
3729 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
3734 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
3730 repository, ``.hg/hgrc`` is used, too.
3735 repository, ``.hg/hgrc`` is used, too.
3731
3736
3732 The path names ``default`` and ``default-push`` have a special
3737 The path names ``default`` and ``default-push`` have a special
3733 meaning. When performing a push or pull operation, they are used
3738 meaning. When performing a push or pull operation, they are used
3734 as fallbacks if no location is specified on the command-line.
3739 as fallbacks if no location is specified on the command-line.
3735 When ``default-push`` is set, it will be used for push and
3740 When ``default-push`` is set, it will be used for push and
3736 ``default`` will be used for pull; otherwise ``default`` is used
3741 ``default`` will be used for pull; otherwise ``default`` is used
3737 as the fallback for both. When cloning a repository, the clone
3742 as the fallback for both. When cloning a repository, the clone
3738 source is written as ``default`` in ``.hg/hgrc``.
3743 source is written as ``default`` in ``.hg/hgrc``.
3739
3744
3740 .. note::
3745 .. note::
3741
3746
3742 ``default`` and ``default-push`` apply to all inbound (e.g.
3747 ``default`` and ``default-push`` apply to all inbound (e.g.
3743 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email`
3748 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email`
3744 and :hg:`bundle`) operations.
3749 and :hg:`bundle`) operations.
3745
3750
3746 See :hg:`help urls` for more information.
3751 See :hg:`help urls` for more information.
3747
3752
3748 Returns 0 on success.
3753 Returns 0 on success.
3749 """
3754 """
3750
3755
3751 opts = pycompat.byteskwargs(opts)
3756 opts = pycompat.byteskwargs(opts)
3752 ui.pager('paths')
3757 ui.pager('paths')
3753 if search:
3758 if search:
3754 pathitems = [(name, path) for name, path in ui.paths.iteritems()
3759 pathitems = [(name, path) for name, path in ui.paths.iteritems()
3755 if name == search]
3760 if name == search]
3756 else:
3761 else:
3757 pathitems = sorted(ui.paths.iteritems())
3762 pathitems = sorted(ui.paths.iteritems())
3758
3763
3759 fm = ui.formatter('paths', opts)
3764 fm = ui.formatter('paths', opts)
3760 if fm.isplain():
3765 if fm.isplain():
3761 hidepassword = util.hidepassword
3766 hidepassword = util.hidepassword
3762 else:
3767 else:
3763 hidepassword = str
3768 hidepassword = str
3764 if ui.quiet:
3769 if ui.quiet:
3765 namefmt = '%s\n'
3770 namefmt = '%s\n'
3766 else:
3771 else:
3767 namefmt = '%s = '
3772 namefmt = '%s = '
3768 showsubopts = not search and not ui.quiet
3773 showsubopts = not search and not ui.quiet
3769
3774
3770 for name, path in pathitems:
3775 for name, path in pathitems:
3771 fm.startitem()
3776 fm.startitem()
3772 fm.condwrite(not search, 'name', namefmt, name)
3777 fm.condwrite(not search, 'name', namefmt, name)
3773 fm.condwrite(not ui.quiet, 'url', '%s\n', hidepassword(path.rawloc))
3778 fm.condwrite(not ui.quiet, 'url', '%s\n', hidepassword(path.rawloc))
3774 for subopt, value in sorted(path.suboptions.items()):
3779 for subopt, value in sorted(path.suboptions.items()):
3775 assert subopt not in ('name', 'url')
3780 assert subopt not in ('name', 'url')
3776 if showsubopts:
3781 if showsubopts:
3777 fm.plain('%s:%s = ' % (name, subopt))
3782 fm.plain('%s:%s = ' % (name, subopt))
3778 fm.condwrite(showsubopts, subopt, '%s\n', value)
3783 fm.condwrite(showsubopts, subopt, '%s\n', value)
3779
3784
3780 fm.end()
3785 fm.end()
3781
3786
3782 if search and not pathitems:
3787 if search and not pathitems:
3783 if not ui.quiet:
3788 if not ui.quiet:
3784 ui.warn(_("not found!\n"))
3789 ui.warn(_("not found!\n"))
3785 return 1
3790 return 1
3786 else:
3791 else:
3787 return 0
3792 return 0
3788
3793
3789 @command('phase',
3794 @command('phase',
3790 [('p', 'public', False, _('set changeset phase to public')),
3795 [('p', 'public', False, _('set changeset phase to public')),
3791 ('d', 'draft', False, _('set changeset phase to draft')),
3796 ('d', 'draft', False, _('set changeset phase to draft')),
3792 ('s', 'secret', False, _('set changeset phase to secret')),
3797 ('s', 'secret', False, _('set changeset phase to secret')),
3793 ('f', 'force', False, _('allow to move boundary backward')),
3798 ('f', 'force', False, _('allow to move boundary backward')),
3794 ('r', 'rev', [], _('target revision'), _('REV')),
3799 ('r', 'rev', [], _('target revision'), _('REV')),
3795 ],
3800 ],
3796 _('[-p|-d|-s] [-f] [-r] [REV...]'))
3801 _('[-p|-d|-s] [-f] [-r] [REV...]'))
3797 def phase(ui, repo, *revs, **opts):
3802 def phase(ui, repo, *revs, **opts):
3798 """set or show the current phase name
3803 """set or show the current phase name
3799
3804
3800 With no argument, show the phase name of the current revision(s).
3805 With no argument, show the phase name of the current revision(s).
3801
3806
3802 With one of -p/--public, -d/--draft or -s/--secret, change the
3807 With one of -p/--public, -d/--draft or -s/--secret, change the
3803 phase value of the specified revisions.
3808 phase value of the specified revisions.
3804
3809
3805 Unless -f/--force is specified, :hg:`phase` won't move changeset from a
3810 Unless -f/--force is specified, :hg:`phase` won't move changeset from a
3806 lower phase to an higher phase. Phases are ordered as follows::
3811 lower phase to an higher phase. Phases are ordered as follows::
3807
3812
3808 public < draft < secret
3813 public < draft < secret
3809
3814
3810 Returns 0 on success, 1 if some phases could not be changed.
3815 Returns 0 on success, 1 if some phases could not be changed.
3811
3816
3812 (For more information about the phases concept, see :hg:`help phases`.)
3817 (For more information about the phases concept, see :hg:`help phases`.)
3813 """
3818 """
3814 opts = pycompat.byteskwargs(opts)
3819 opts = pycompat.byteskwargs(opts)
3815 # search for a unique phase argument
3820 # search for a unique phase argument
3816 targetphase = None
3821 targetphase = None
3817 for idx, name in enumerate(phases.phasenames):
3822 for idx, name in enumerate(phases.phasenames):
3818 if opts[name]:
3823 if opts[name]:
3819 if targetphase is not None:
3824 if targetphase is not None:
3820 raise error.Abort(_('only one phase can be specified'))
3825 raise error.Abort(_('only one phase can be specified'))
3821 targetphase = idx
3826 targetphase = idx
3822
3827
3823 # look for specified revision
3828 # look for specified revision
3824 revs = list(revs)
3829 revs = list(revs)
3825 revs.extend(opts['rev'])
3830 revs.extend(opts['rev'])
3826 if not revs:
3831 if not revs:
3827 # display both parents as the second parent phase can influence
3832 # display both parents as the second parent phase can influence
3828 # the phase of a merge commit
3833 # the phase of a merge commit
3829 revs = [c.rev() for c in repo[None].parents()]
3834 revs = [c.rev() for c in repo[None].parents()]
3830
3835
3831 revs = scmutil.revrange(repo, revs)
3836 revs = scmutil.revrange(repo, revs)
3832
3837
3833 lock = None
3838 lock = None
3834 ret = 0
3839 ret = 0
3835 if targetphase is None:
3840 if targetphase is None:
3836 # display
3841 # display
3837 for r in revs:
3842 for r in revs:
3838 ctx = repo[r]
3843 ctx = repo[r]
3839 ui.write('%i: %s\n' % (ctx.rev(), ctx.phasestr()))
3844 ui.write('%i: %s\n' % (ctx.rev(), ctx.phasestr()))
3840 else:
3845 else:
3841 tr = None
3846 tr = None
3842 lock = repo.lock()
3847 lock = repo.lock()
3843 try:
3848 try:
3844 tr = repo.transaction("phase")
3849 tr = repo.transaction("phase")
3845 # set phase
3850 # set phase
3846 if not revs:
3851 if not revs:
3847 raise error.Abort(_('empty revision set'))
3852 raise error.Abort(_('empty revision set'))
3848 nodes = [repo[r].node() for r in revs]
3853 nodes = [repo[r].node() for r in revs]
3849 # moving revision from public to draft may hide them
3854 # moving revision from public to draft may hide them
3850 # We have to check result on an unfiltered repository
3855 # We have to check result on an unfiltered repository
3851 unfi = repo.unfiltered()
3856 unfi = repo.unfiltered()
3852 getphase = unfi._phasecache.phase
3857 getphase = unfi._phasecache.phase
3853 olddata = [getphase(unfi, r) for r in unfi]
3858 olddata = [getphase(unfi, r) for r in unfi]
3854 phases.advanceboundary(repo, tr, targetphase, nodes)
3859 phases.advanceboundary(repo, tr, targetphase, nodes)
3855 if opts['force']:
3860 if opts['force']:
3856 phases.retractboundary(repo, tr, targetphase, nodes)
3861 phases.retractboundary(repo, tr, targetphase, nodes)
3857 tr.close()
3862 tr.close()
3858 finally:
3863 finally:
3859 if tr is not None:
3864 if tr is not None:
3860 tr.release()
3865 tr.release()
3861 lock.release()
3866 lock.release()
3862 getphase = unfi._phasecache.phase
3867 getphase = unfi._phasecache.phase
3863 newdata = [getphase(unfi, r) for r in unfi]
3868 newdata = [getphase(unfi, r) for r in unfi]
3864 changes = sum(newdata[r] != olddata[r] for r in unfi)
3869 changes = sum(newdata[r] != olddata[r] for r in unfi)
3865 cl = unfi.changelog
3870 cl = unfi.changelog
3866 rejected = [n for n in nodes
3871 rejected = [n for n in nodes
3867 if newdata[cl.rev(n)] < targetphase]
3872 if newdata[cl.rev(n)] < targetphase]
3868 if rejected:
3873 if rejected:
3869 ui.warn(_('cannot move %i changesets to a higher '
3874 ui.warn(_('cannot move %i changesets to a higher '
3870 'phase, use --force\n') % len(rejected))
3875 'phase, use --force\n') % len(rejected))
3871 ret = 1
3876 ret = 1
3872 if changes:
3877 if changes:
3873 msg = _('phase changed for %i changesets\n') % changes
3878 msg = _('phase changed for %i changesets\n') % changes
3874 if ret:
3879 if ret:
3875 ui.status(msg)
3880 ui.status(msg)
3876 else:
3881 else:
3877 ui.note(msg)
3882 ui.note(msg)
3878 else:
3883 else:
3879 ui.warn(_('no phases changed\n'))
3884 ui.warn(_('no phases changed\n'))
3880 return ret
3885 return ret
3881
3886
3882 def postincoming(ui, repo, modheads, optupdate, checkout, brev):
3887 def postincoming(ui, repo, modheads, optupdate, checkout, brev):
3883 """Run after a changegroup has been added via pull/unbundle
3888 """Run after a changegroup has been added via pull/unbundle
3884
3889
3885 This takes arguments below:
3890 This takes arguments below:
3886
3891
3887 :modheads: change of heads by pull/unbundle
3892 :modheads: change of heads by pull/unbundle
3888 :optupdate: updating working directory is needed or not
3893 :optupdate: updating working directory is needed or not
3889 :checkout: update destination revision (or None to default destination)
3894 :checkout: update destination revision (or None to default destination)
3890 :brev: a name, which might be a bookmark to be activated after updating
3895 :brev: a name, which might be a bookmark to be activated after updating
3891 """
3896 """
3892 if modheads == 0:
3897 if modheads == 0:
3893 return
3898 return
3894 if optupdate:
3899 if optupdate:
3895 try:
3900 try:
3896 return hg.updatetotally(ui, repo, checkout, brev)
3901 return hg.updatetotally(ui, repo, checkout, brev)
3897 except error.UpdateAbort as inst:
3902 except error.UpdateAbort as inst:
3898 msg = _("not updating: %s") % str(inst)
3903 msg = _("not updating: %s") % str(inst)
3899 hint = inst.hint
3904 hint = inst.hint
3900 raise error.UpdateAbort(msg, hint=hint)
3905 raise error.UpdateAbort(msg, hint=hint)
3901 if modheads > 1:
3906 if modheads > 1:
3902 currentbranchheads = len(repo.branchheads())
3907 currentbranchheads = len(repo.branchheads())
3903 if currentbranchheads == modheads:
3908 if currentbranchheads == modheads:
3904 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
3909 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
3905 elif currentbranchheads > 1:
3910 elif currentbranchheads > 1:
3906 ui.status(_("(run 'hg heads .' to see heads, 'hg merge' to "
3911 ui.status(_("(run 'hg heads .' to see heads, 'hg merge' to "
3907 "merge)\n"))
3912 "merge)\n"))
3908 else:
3913 else:
3909 ui.status(_("(run 'hg heads' to see heads)\n"))
3914 ui.status(_("(run 'hg heads' to see heads)\n"))
3910 else:
3915 else:
3911 ui.status(_("(run 'hg update' to get a working copy)\n"))
3916 ui.status(_("(run 'hg update' to get a working copy)\n"))
3912
3917
3913 @command('^pull',
3918 @command('^pull',
3914 [('u', 'update', None,
3919 [('u', 'update', None,
3915 _('update to new branch head if changesets were pulled')),
3920 _('update to new branch head if changesets were pulled')),
3916 ('f', 'force', None, _('run even when remote repository is unrelated')),
3921 ('f', 'force', None, _('run even when remote repository is unrelated')),
3917 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
3922 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
3918 ('B', 'bookmark', [], _("bookmark to pull"), _('BOOKMARK')),
3923 ('B', 'bookmark', [], _("bookmark to pull"), _('BOOKMARK')),
3919 ('b', 'branch', [], _('a specific branch you would like to pull'),
3924 ('b', 'branch', [], _('a specific branch you would like to pull'),
3920 _('BRANCH')),
3925 _('BRANCH')),
3921 ] + remoteopts,
3926 ] + remoteopts,
3922 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'))
3927 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'))
3923 def pull(ui, repo, source="default", **opts):
3928 def pull(ui, repo, source="default", **opts):
3924 """pull changes from the specified source
3929 """pull changes from the specified source
3925
3930
3926 Pull changes from a remote repository to a local one.
3931 Pull changes from a remote repository to a local one.
3927
3932
3928 This finds all changes from the repository at the specified path
3933 This finds all changes from the repository at the specified path
3929 or URL and adds them to a local repository (the current one unless
3934 or URL and adds them to a local repository (the current one unless
3930 -R is specified). By default, this does not update the copy of the
3935 -R is specified). By default, this does not update the copy of the
3931 project in the working directory.
3936 project in the working directory.
3932
3937
3933 Use :hg:`incoming` if you want to see what would have been added
3938 Use :hg:`incoming` if you want to see what would have been added
3934 by a pull at the time you issued this command. If you then decide
3939 by a pull at the time you issued this command. If you then decide
3935 to add those changes to the repository, you should use :hg:`pull
3940 to add those changes to the repository, you should use :hg:`pull
3936 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
3941 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
3937
3942
3938 If SOURCE is omitted, the 'default' path will be used.
3943 If SOURCE is omitted, the 'default' path will be used.
3939 See :hg:`help urls` for more information.
3944 See :hg:`help urls` for more information.
3940
3945
3941 Specifying bookmark as ``.`` is equivalent to specifying the active
3946 Specifying bookmark as ``.`` is equivalent to specifying the active
3942 bookmark's name.
3947 bookmark's name.
3943
3948
3944 Returns 0 on success, 1 if an update had unresolved files.
3949 Returns 0 on success, 1 if an update had unresolved files.
3945 """
3950 """
3946
3951
3947 opts = pycompat.byteskwargs(opts)
3952 opts = pycompat.byteskwargs(opts)
3948 if ui.configbool('commands', 'update.requiredest') and opts.get('update'):
3953 if ui.configbool('commands', 'update.requiredest') and opts.get('update'):
3949 msg = _('update destination required by configuration')
3954 msg = _('update destination required by configuration')
3950 hint = _('use hg pull followed by hg update DEST')
3955 hint = _('use hg pull followed by hg update DEST')
3951 raise error.Abort(msg, hint=hint)
3956 raise error.Abort(msg, hint=hint)
3952
3957
3953 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
3958 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
3954 ui.status(_('pulling from %s\n') % util.hidepassword(source))
3959 ui.status(_('pulling from %s\n') % util.hidepassword(source))
3955 other = hg.peer(repo, opts, source)
3960 other = hg.peer(repo, opts, source)
3956 try:
3961 try:
3957 revs, checkout = hg.addbranchrevs(repo, other, branches,
3962 revs, checkout = hg.addbranchrevs(repo, other, branches,
3958 opts.get('rev'))
3963 opts.get('rev'))
3959
3964
3960
3965
3961 pullopargs = {}
3966 pullopargs = {}
3962 if opts.get('bookmark'):
3967 if opts.get('bookmark'):
3963 if not revs:
3968 if not revs:
3964 revs = []
3969 revs = []
3965 # The list of bookmark used here is not the one used to actually
3970 # The list of bookmark used here is not the one used to actually
3966 # update the bookmark name. This can result in the revision pulled
3971 # update the bookmark name. This can result in the revision pulled
3967 # not ending up with the name of the bookmark because of a race
3972 # not ending up with the name of the bookmark because of a race
3968 # condition on the server. (See issue 4689 for details)
3973 # condition on the server. (See issue 4689 for details)
3969 remotebookmarks = other.listkeys('bookmarks')
3974 remotebookmarks = other.listkeys('bookmarks')
3970 pullopargs['remotebookmarks'] = remotebookmarks
3975 pullopargs['remotebookmarks'] = remotebookmarks
3971 for b in opts['bookmark']:
3976 for b in opts['bookmark']:
3972 b = repo._bookmarks.expandname(b)
3977 b = repo._bookmarks.expandname(b)
3973 if b not in remotebookmarks:
3978 if b not in remotebookmarks:
3974 raise error.Abort(_('remote bookmark %s not found!') % b)
3979 raise error.Abort(_('remote bookmark %s not found!') % b)
3975 revs.append(remotebookmarks[b])
3980 revs.append(remotebookmarks[b])
3976
3981
3977 if revs:
3982 if revs:
3978 try:
3983 try:
3979 # When 'rev' is a bookmark name, we cannot guarantee that it
3984 # When 'rev' is a bookmark name, we cannot guarantee that it
3980 # will be updated with that name because of a race condition
3985 # will be updated with that name because of a race condition
3981 # server side. (See issue 4689 for details)
3986 # server side. (See issue 4689 for details)
3982 oldrevs = revs
3987 oldrevs = revs
3983 revs = [] # actually, nodes
3988 revs = [] # actually, nodes
3984 for r in oldrevs:
3989 for r in oldrevs:
3985 node = other.lookup(r)
3990 node = other.lookup(r)
3986 revs.append(node)
3991 revs.append(node)
3987 if r == checkout:
3992 if r == checkout:
3988 checkout = node
3993 checkout = node
3989 except error.CapabilityError:
3994 except error.CapabilityError:
3990 err = _("other repository doesn't support revision lookup, "
3995 err = _("other repository doesn't support revision lookup, "
3991 "so a rev cannot be specified.")
3996 "so a rev cannot be specified.")
3992 raise error.Abort(err)
3997 raise error.Abort(err)
3993
3998
3994 pullopargs.update(opts.get('opargs', {}))
3999 pullopargs.update(opts.get('opargs', {}))
3995 modheads = exchange.pull(repo, other, heads=revs,
4000 modheads = exchange.pull(repo, other, heads=revs,
3996 force=opts.get('force'),
4001 force=opts.get('force'),
3997 bookmarks=opts.get('bookmark', ()),
4002 bookmarks=opts.get('bookmark', ()),
3998 opargs=pullopargs).cgresult
4003 opargs=pullopargs).cgresult
3999
4004
4000 # brev is a name, which might be a bookmark to be activated at
4005 # brev is a name, which might be a bookmark to be activated at
4001 # the end of the update. In other words, it is an explicit
4006 # the end of the update. In other words, it is an explicit
4002 # destination of the update
4007 # destination of the update
4003 brev = None
4008 brev = None
4004
4009
4005 if checkout:
4010 if checkout:
4006 checkout = str(repo.changelog.rev(checkout))
4011 checkout = str(repo.changelog.rev(checkout))
4007
4012
4008 # order below depends on implementation of
4013 # order below depends on implementation of
4009 # hg.addbranchrevs(). opts['bookmark'] is ignored,
4014 # hg.addbranchrevs(). opts['bookmark'] is ignored,
4010 # because 'checkout' is determined without it.
4015 # because 'checkout' is determined without it.
4011 if opts.get('rev'):
4016 if opts.get('rev'):
4012 brev = opts['rev'][0]
4017 brev = opts['rev'][0]
4013 elif opts.get('branch'):
4018 elif opts.get('branch'):
4014 brev = opts['branch'][0]
4019 brev = opts['branch'][0]
4015 else:
4020 else:
4016 brev = branches[0]
4021 brev = branches[0]
4017 repo._subtoppath = source
4022 repo._subtoppath = source
4018 try:
4023 try:
4019 ret = postincoming(ui, repo, modheads, opts.get('update'),
4024 ret = postincoming(ui, repo, modheads, opts.get('update'),
4020 checkout, brev)
4025 checkout, brev)
4021
4026
4022 finally:
4027 finally:
4023 del repo._subtoppath
4028 del repo._subtoppath
4024
4029
4025 finally:
4030 finally:
4026 other.close()
4031 other.close()
4027 return ret
4032 return ret
4028
4033
4029 @command('^push',
4034 @command('^push',
4030 [('f', 'force', None, _('force push')),
4035 [('f', 'force', None, _('force push')),
4031 ('r', 'rev', [],
4036 ('r', 'rev', [],
4032 _('a changeset intended to be included in the destination'),
4037 _('a changeset intended to be included in the destination'),
4033 _('REV')),
4038 _('REV')),
4034 ('B', 'bookmark', [], _("bookmark to push"), _('BOOKMARK')),
4039 ('B', 'bookmark', [], _("bookmark to push"), _('BOOKMARK')),
4035 ('b', 'branch', [],
4040 ('b', 'branch', [],
4036 _('a specific branch you would like to push'), _('BRANCH')),
4041 _('a specific branch you would like to push'), _('BRANCH')),
4037 ('', 'new-branch', False, _('allow pushing a new branch')),
4042 ('', 'new-branch', False, _('allow pushing a new branch')),
4038 ] + remoteopts,
4043 ] + remoteopts,
4039 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'))
4044 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'))
4040 def push(ui, repo, dest=None, **opts):
4045 def push(ui, repo, dest=None, **opts):
4041 """push changes to the specified destination
4046 """push changes to the specified destination
4042
4047
4043 Push changesets from the local repository to the specified
4048 Push changesets from the local repository to the specified
4044 destination.
4049 destination.
4045
4050
4046 This operation is symmetrical to pull: it is identical to a pull
4051 This operation is symmetrical to pull: it is identical to a pull
4047 in the destination repository from the current one.
4052 in the destination repository from the current one.
4048
4053
4049 By default, push will not allow creation of new heads at the
4054 By default, push will not allow creation of new heads at the
4050 destination, since multiple heads would make it unclear which head
4055 destination, since multiple heads would make it unclear which head
4051 to use. In this situation, it is recommended to pull and merge
4056 to use. In this situation, it is recommended to pull and merge
4052 before pushing.
4057 before pushing.
4053
4058
4054 Use --new-branch if you want to allow push to create a new named
4059 Use --new-branch if you want to allow push to create a new named
4055 branch that is not present at the destination. This allows you to
4060 branch that is not present at the destination. This allows you to
4056 only create a new branch without forcing other changes.
4061 only create a new branch without forcing other changes.
4057
4062
4058 .. note::
4063 .. note::
4059
4064
4060 Extra care should be taken with the -f/--force option,
4065 Extra care should be taken with the -f/--force option,
4061 which will push all new heads on all branches, an action which will
4066 which will push all new heads on all branches, an action which will
4062 almost always cause confusion for collaborators.
4067 almost always cause confusion for collaborators.
4063
4068
4064 If -r/--rev is used, the specified revision and all its ancestors
4069 If -r/--rev is used, the specified revision and all its ancestors
4065 will be pushed to the remote repository.
4070 will be pushed to the remote repository.
4066
4071
4067 If -B/--bookmark is used, the specified bookmarked revision, its
4072 If -B/--bookmark is used, the specified bookmarked revision, its
4068 ancestors, and the bookmark will be pushed to the remote
4073 ancestors, and the bookmark will be pushed to the remote
4069 repository. Specifying ``.`` is equivalent to specifying the active
4074 repository. Specifying ``.`` is equivalent to specifying the active
4070 bookmark's name.
4075 bookmark's name.
4071
4076
4072 Please see :hg:`help urls` for important details about ``ssh://``
4077 Please see :hg:`help urls` for important details about ``ssh://``
4073 URLs. If DESTINATION is omitted, a default path will be used.
4078 URLs. If DESTINATION is omitted, a default path will be used.
4074
4079
4075 Returns 0 if push was successful, 1 if nothing to push.
4080 Returns 0 if push was successful, 1 if nothing to push.
4076 """
4081 """
4077
4082
4078 opts = pycompat.byteskwargs(opts)
4083 opts = pycompat.byteskwargs(opts)
4079 if opts.get('bookmark'):
4084 if opts.get('bookmark'):
4080 ui.setconfig('bookmarks', 'pushing', opts['bookmark'], 'push')
4085 ui.setconfig('bookmarks', 'pushing', opts['bookmark'], 'push')
4081 for b in opts['bookmark']:
4086 for b in opts['bookmark']:
4082 # translate -B options to -r so changesets get pushed
4087 # translate -B options to -r so changesets get pushed
4083 b = repo._bookmarks.expandname(b)
4088 b = repo._bookmarks.expandname(b)
4084 if b in repo._bookmarks:
4089 if b in repo._bookmarks:
4085 opts.setdefault('rev', []).append(b)
4090 opts.setdefault('rev', []).append(b)
4086 else:
4091 else:
4087 # if we try to push a deleted bookmark, translate it to null
4092 # if we try to push a deleted bookmark, translate it to null
4088 # this lets simultaneous -r, -b options continue working
4093 # this lets simultaneous -r, -b options continue working
4089 opts.setdefault('rev', []).append("null")
4094 opts.setdefault('rev', []).append("null")
4090
4095
4091 path = ui.paths.getpath(dest, default=('default-push', 'default'))
4096 path = ui.paths.getpath(dest, default=('default-push', 'default'))
4092 if not path:
4097 if not path:
4093 raise error.Abort(_('default repository not configured!'),
4098 raise error.Abort(_('default repository not configured!'),
4094 hint=_("see 'hg help config.paths'"))
4099 hint=_("see 'hg help config.paths'"))
4095 dest = path.pushloc or path.loc
4100 dest = path.pushloc or path.loc
4096 branches = (path.branch, opts.get('branch') or [])
4101 branches = (path.branch, opts.get('branch') or [])
4097 ui.status(_('pushing to %s\n') % util.hidepassword(dest))
4102 ui.status(_('pushing to %s\n') % util.hidepassword(dest))
4098 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
4103 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
4099 other = hg.peer(repo, opts, dest)
4104 other = hg.peer(repo, opts, dest)
4100
4105
4101 if revs:
4106 if revs:
4102 revs = [repo.lookup(r) for r in scmutil.revrange(repo, revs)]
4107 revs = [repo.lookup(r) for r in scmutil.revrange(repo, revs)]
4103 if not revs:
4108 if not revs:
4104 raise error.Abort(_("specified revisions evaluate to an empty set"),
4109 raise error.Abort(_("specified revisions evaluate to an empty set"),
4105 hint=_("use different revision arguments"))
4110 hint=_("use different revision arguments"))
4106 elif path.pushrev:
4111 elif path.pushrev:
4107 # It doesn't make any sense to specify ancestor revisions. So limit
4112 # It doesn't make any sense to specify ancestor revisions. So limit
4108 # to DAG heads to make discovery simpler.
4113 # to DAG heads to make discovery simpler.
4109 expr = revsetlang.formatspec('heads(%r)', path.pushrev)
4114 expr = revsetlang.formatspec('heads(%r)', path.pushrev)
4110 revs = scmutil.revrange(repo, [expr])
4115 revs = scmutil.revrange(repo, [expr])
4111 revs = [repo[rev].node() for rev in revs]
4116 revs = [repo[rev].node() for rev in revs]
4112 if not revs:
4117 if not revs:
4113 raise error.Abort(_('default push revset for path evaluates to an '
4118 raise error.Abort(_('default push revset for path evaluates to an '
4114 'empty set'))
4119 'empty set'))
4115
4120
4116 repo._subtoppath = dest
4121 repo._subtoppath = dest
4117 try:
4122 try:
4118 # push subrepos depth-first for coherent ordering
4123 # push subrepos depth-first for coherent ordering
4119 c = repo['']
4124 c = repo['']
4120 subs = c.substate # only repos that are committed
4125 subs = c.substate # only repos that are committed
4121 for s in sorted(subs):
4126 for s in sorted(subs):
4122 result = c.sub(s).push(opts)
4127 result = c.sub(s).push(opts)
4123 if result == 0:
4128 if result == 0:
4124 return not result
4129 return not result
4125 finally:
4130 finally:
4126 del repo._subtoppath
4131 del repo._subtoppath
4127 pushop = exchange.push(repo, other, opts.get('force'), revs=revs,
4132 pushop = exchange.push(repo, other, opts.get('force'), revs=revs,
4128 newbranch=opts.get('new_branch'),
4133 newbranch=opts.get('new_branch'),
4129 bookmarks=opts.get('bookmark', ()),
4134 bookmarks=opts.get('bookmark', ()),
4130 opargs=opts.get('opargs'))
4135 opargs=opts.get('opargs'))
4131
4136
4132 result = not pushop.cgresult
4137 result = not pushop.cgresult
4133
4138
4134 if pushop.bkresult is not None:
4139 if pushop.bkresult is not None:
4135 if pushop.bkresult == 2:
4140 if pushop.bkresult == 2:
4136 result = 2
4141 result = 2
4137 elif not result and pushop.bkresult:
4142 elif not result and pushop.bkresult:
4138 result = 2
4143 result = 2
4139
4144
4140 return result
4145 return result
4141
4146
4142 @command('recover', [])
4147 @command('recover', [])
4143 def recover(ui, repo):
4148 def recover(ui, repo):
4144 """roll back an interrupted transaction
4149 """roll back an interrupted transaction
4145
4150
4146 Recover from an interrupted commit or pull.
4151 Recover from an interrupted commit or pull.
4147
4152
4148 This command tries to fix the repository status after an
4153 This command tries to fix the repository status after an
4149 interrupted operation. It should only be necessary when Mercurial
4154 interrupted operation. It should only be necessary when Mercurial
4150 suggests it.
4155 suggests it.
4151
4156
4152 Returns 0 if successful, 1 if nothing to recover or verify fails.
4157 Returns 0 if successful, 1 if nothing to recover or verify fails.
4153 """
4158 """
4154 if repo.recover():
4159 if repo.recover():
4155 return hg.verify(repo)
4160 return hg.verify(repo)
4156 return 1
4161 return 1
4157
4162
4158 @command('^remove|rm',
4163 @command('^remove|rm',
4159 [('A', 'after', None, _('record delete for missing files')),
4164 [('A', 'after', None, _('record delete for missing files')),
4160 ('f', 'force', None,
4165 ('f', 'force', None,
4161 _('forget added files, delete modified files')),
4166 _('forget added files, delete modified files')),
4162 ] + subrepoopts + walkopts,
4167 ] + subrepoopts + walkopts,
4163 _('[OPTION]... FILE...'),
4168 _('[OPTION]... FILE...'),
4164 inferrepo=True)
4169 inferrepo=True)
4165 def remove(ui, repo, *pats, **opts):
4170 def remove(ui, repo, *pats, **opts):
4166 """remove the specified files on the next commit
4171 """remove the specified files on the next commit
4167
4172
4168 Schedule the indicated files for removal from the current branch.
4173 Schedule the indicated files for removal from the current branch.
4169
4174
4170 This command schedules the files to be removed at the next commit.
4175 This command schedules the files to be removed at the next commit.
4171 To undo a remove before that, see :hg:`revert`. To undo added
4176 To undo a remove before that, see :hg:`revert`. To undo added
4172 files, see :hg:`forget`.
4177 files, see :hg:`forget`.
4173
4178
4174 .. container:: verbose
4179 .. container:: verbose
4175
4180
4176 -A/--after can be used to remove only files that have already
4181 -A/--after can be used to remove only files that have already
4177 been deleted, -f/--force can be used to force deletion, and -Af
4182 been deleted, -f/--force can be used to force deletion, and -Af
4178 can be used to remove files from the next revision without
4183 can be used to remove files from the next revision without
4179 deleting them from the working directory.
4184 deleting them from the working directory.
4180
4185
4181 The following table details the behavior of remove for different
4186 The following table details the behavior of remove for different
4182 file states (columns) and option combinations (rows). The file
4187 file states (columns) and option combinations (rows). The file
4183 states are Added [A], Clean [C], Modified [M] and Missing [!]
4188 states are Added [A], Clean [C], Modified [M] and Missing [!]
4184 (as reported by :hg:`status`). The actions are Warn, Remove
4189 (as reported by :hg:`status`). The actions are Warn, Remove
4185 (from branch) and Delete (from disk):
4190 (from branch) and Delete (from disk):
4186
4191
4187 ========= == == == ==
4192 ========= == == == ==
4188 opt/state A C M !
4193 opt/state A C M !
4189 ========= == == == ==
4194 ========= == == == ==
4190 none W RD W R
4195 none W RD W R
4191 -f R RD RD R
4196 -f R RD RD R
4192 -A W W W R
4197 -A W W W R
4193 -Af R R R R
4198 -Af R R R R
4194 ========= == == == ==
4199 ========= == == == ==
4195
4200
4196 .. note::
4201 .. note::
4197
4202
4198 :hg:`remove` never deletes files in Added [A] state from the
4203 :hg:`remove` never deletes files in Added [A] state from the
4199 working directory, not even if ``--force`` is specified.
4204 working directory, not even if ``--force`` is specified.
4200
4205
4201 Returns 0 on success, 1 if any warnings encountered.
4206 Returns 0 on success, 1 if any warnings encountered.
4202 """
4207 """
4203
4208
4204 opts = pycompat.byteskwargs(opts)
4209 opts = pycompat.byteskwargs(opts)
4205 after, force = opts.get('after'), opts.get('force')
4210 after, force = opts.get('after'), opts.get('force')
4206 if not pats and not after:
4211 if not pats and not after:
4207 raise error.Abort(_('no files specified'))
4212 raise error.Abort(_('no files specified'))
4208
4213
4209 m = scmutil.match(repo[None], pats, opts)
4214 m = scmutil.match(repo[None], pats, opts)
4210 subrepos = opts.get('subrepos')
4215 subrepos = opts.get('subrepos')
4211 return cmdutil.remove(ui, repo, m, "", after, force, subrepos)
4216 return cmdutil.remove(ui, repo, m, "", after, force, subrepos)
4212
4217
4213 @command('rename|move|mv',
4218 @command('rename|move|mv',
4214 [('A', 'after', None, _('record a rename that has already occurred')),
4219 [('A', 'after', None, _('record a rename that has already occurred')),
4215 ('f', 'force', None, _('forcibly copy over an existing managed file')),
4220 ('f', 'force', None, _('forcibly copy over an existing managed file')),
4216 ] + walkopts + dryrunopts,
4221 ] + walkopts + dryrunopts,
4217 _('[OPTION]... SOURCE... DEST'))
4222 _('[OPTION]... SOURCE... DEST'))
4218 def rename(ui, repo, *pats, **opts):
4223 def rename(ui, repo, *pats, **opts):
4219 """rename files; equivalent of copy + remove
4224 """rename files; equivalent of copy + remove
4220
4225
4221 Mark dest as copies of sources; mark sources for deletion. If dest
4226 Mark dest as copies of sources; mark sources for deletion. If dest
4222 is a directory, copies are put in that directory. If dest is a
4227 is a directory, copies are put in that directory. If dest is a
4223 file, there can only be one source.
4228 file, there can only be one source.
4224
4229
4225 By default, this command copies the contents of files as they
4230 By default, this command copies the contents of files as they
4226 exist in the working directory. If invoked with -A/--after, the
4231 exist in the working directory. If invoked with -A/--after, the
4227 operation is recorded, but no copying is performed.
4232 operation is recorded, but no copying is performed.
4228
4233
4229 This command takes effect at the next commit. To undo a rename
4234 This command takes effect at the next commit. To undo a rename
4230 before that, see :hg:`revert`.
4235 before that, see :hg:`revert`.
4231
4236
4232 Returns 0 on success, 1 if errors are encountered.
4237 Returns 0 on success, 1 if errors are encountered.
4233 """
4238 """
4234 opts = pycompat.byteskwargs(opts)
4239 opts = pycompat.byteskwargs(opts)
4235 with repo.wlock(False):
4240 with repo.wlock(False):
4236 return cmdutil.copy(ui, repo, pats, opts, rename=True)
4241 return cmdutil.copy(ui, repo, pats, opts, rename=True)
4237
4242
4238 @command('resolve',
4243 @command('resolve',
4239 [('a', 'all', None, _('select all unresolved files')),
4244 [('a', 'all', None, _('select all unresolved files')),
4240 ('l', 'list', None, _('list state of files needing merge')),
4245 ('l', 'list', None, _('list state of files needing merge')),
4241 ('m', 'mark', None, _('mark files as resolved')),
4246 ('m', 'mark', None, _('mark files as resolved')),
4242 ('u', 'unmark', None, _('mark files as unresolved')),
4247 ('u', 'unmark', None, _('mark files as unresolved')),
4243 ('n', 'no-status', None, _('hide status prefix'))]
4248 ('n', 'no-status', None, _('hide status prefix'))]
4244 + mergetoolopts + walkopts + formatteropts,
4249 + mergetoolopts + walkopts + formatteropts,
4245 _('[OPTION]... [FILE]...'),
4250 _('[OPTION]... [FILE]...'),
4246 inferrepo=True)
4251 inferrepo=True)
4247 def resolve(ui, repo, *pats, **opts):
4252 def resolve(ui, repo, *pats, **opts):
4248 """redo merges or set/view the merge status of files
4253 """redo merges or set/view the merge status of files
4249
4254
4250 Merges with unresolved conflicts are often the result of
4255 Merges with unresolved conflicts are often the result of
4251 non-interactive merging using the ``internal:merge`` configuration
4256 non-interactive merging using the ``internal:merge`` configuration
4252 setting, or a command-line merge tool like ``diff3``. The resolve
4257 setting, or a command-line merge tool like ``diff3``. The resolve
4253 command is used to manage the files involved in a merge, after
4258 command is used to manage the files involved in a merge, after
4254 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
4259 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
4255 working directory must have two parents). See :hg:`help
4260 working directory must have two parents). See :hg:`help
4256 merge-tools` for information on configuring merge tools.
4261 merge-tools` for information on configuring merge tools.
4257
4262
4258 The resolve command can be used in the following ways:
4263 The resolve command can be used in the following ways:
4259
4264
4260 - :hg:`resolve [--tool TOOL] FILE...`: attempt to re-merge the specified
4265 - :hg:`resolve [--tool TOOL] FILE...`: attempt to re-merge the specified
4261 files, discarding any previous merge attempts. Re-merging is not
4266 files, discarding any previous merge attempts. Re-merging is not
4262 performed for files already marked as resolved. Use ``--all/-a``
4267 performed for files already marked as resolved. Use ``--all/-a``
4263 to select all unresolved files. ``--tool`` can be used to specify
4268 to select all unresolved files. ``--tool`` can be used to specify
4264 the merge tool used for the given files. It overrides the HGMERGE
4269 the merge tool used for the given files. It overrides the HGMERGE
4265 environment variable and your configuration files. Previous file
4270 environment variable and your configuration files. Previous file
4266 contents are saved with a ``.orig`` suffix.
4271 contents are saved with a ``.orig`` suffix.
4267
4272
4268 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
4273 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
4269 (e.g. after having manually fixed-up the files). The default is
4274 (e.g. after having manually fixed-up the files). The default is
4270 to mark all unresolved files.
4275 to mark all unresolved files.
4271
4276
4272 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
4277 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
4273 default is to mark all resolved files.
4278 default is to mark all resolved files.
4274
4279
4275 - :hg:`resolve -l`: list files which had or still have conflicts.
4280 - :hg:`resolve -l`: list files which had or still have conflicts.
4276 In the printed list, ``U`` = unresolved and ``R`` = resolved.
4281 In the printed list, ``U`` = unresolved and ``R`` = resolved.
4277 You can use ``set:unresolved()`` or ``set:resolved()`` to filter
4282 You can use ``set:unresolved()`` or ``set:resolved()`` to filter
4278 the list. See :hg:`help filesets` for details.
4283 the list. See :hg:`help filesets` for details.
4279
4284
4280 .. note::
4285 .. note::
4281
4286
4282 Mercurial will not let you commit files with unresolved merge
4287 Mercurial will not let you commit files with unresolved merge
4283 conflicts. You must use :hg:`resolve -m ...` before you can
4288 conflicts. You must use :hg:`resolve -m ...` before you can
4284 commit after a conflicting merge.
4289 commit after a conflicting merge.
4285
4290
4286 Returns 0 on success, 1 if any files fail a resolve attempt.
4291 Returns 0 on success, 1 if any files fail a resolve attempt.
4287 """
4292 """
4288
4293
4289 opts = pycompat.byteskwargs(opts)
4294 opts = pycompat.byteskwargs(opts)
4290 flaglist = 'all mark unmark list no_status'.split()
4295 flaglist = 'all mark unmark list no_status'.split()
4291 all, mark, unmark, show, nostatus = \
4296 all, mark, unmark, show, nostatus = \
4292 [opts.get(o) for o in flaglist]
4297 [opts.get(o) for o in flaglist]
4293
4298
4294 if (show and (mark or unmark)) or (mark and unmark):
4299 if (show and (mark or unmark)) or (mark and unmark):
4295 raise error.Abort(_("too many options specified"))
4300 raise error.Abort(_("too many options specified"))
4296 if pats and all:
4301 if pats and all:
4297 raise error.Abort(_("can't specify --all and patterns"))
4302 raise error.Abort(_("can't specify --all and patterns"))
4298 if not (all or pats or show or mark or unmark):
4303 if not (all or pats or show or mark or unmark):
4299 raise error.Abort(_('no files or directories specified'),
4304 raise error.Abort(_('no files or directories specified'),
4300 hint=('use --all to re-merge all unresolved files'))
4305 hint=('use --all to re-merge all unresolved files'))
4301
4306
4302 if show:
4307 if show:
4303 ui.pager('resolve')
4308 ui.pager('resolve')
4304 fm = ui.formatter('resolve', opts)
4309 fm = ui.formatter('resolve', opts)
4305 ms = mergemod.mergestate.read(repo)
4310 ms = mergemod.mergestate.read(repo)
4306 m = scmutil.match(repo[None], pats, opts)
4311 m = scmutil.match(repo[None], pats, opts)
4307 for f in ms:
4312 for f in ms:
4308 if not m(f):
4313 if not m(f):
4309 continue
4314 continue
4310 l = 'resolve.' + {'u': 'unresolved', 'r': 'resolved',
4315 l = 'resolve.' + {'u': 'unresolved', 'r': 'resolved',
4311 'd': 'driverresolved'}[ms[f]]
4316 'd': 'driverresolved'}[ms[f]]
4312 fm.startitem()
4317 fm.startitem()
4313 fm.condwrite(not nostatus, 'status', '%s ', ms[f].upper(), label=l)
4318 fm.condwrite(not nostatus, 'status', '%s ', ms[f].upper(), label=l)
4314 fm.write('path', '%s\n', f, label=l)
4319 fm.write('path', '%s\n', f, label=l)
4315 fm.end()
4320 fm.end()
4316 return 0
4321 return 0
4317
4322
4318 with repo.wlock():
4323 with repo.wlock():
4319 ms = mergemod.mergestate.read(repo)
4324 ms = mergemod.mergestate.read(repo)
4320
4325
4321 if not (ms.active() or repo.dirstate.p2() != nullid):
4326 if not (ms.active() or repo.dirstate.p2() != nullid):
4322 raise error.Abort(
4327 raise error.Abort(
4323 _('resolve command not applicable when not merging'))
4328 _('resolve command not applicable when not merging'))
4324
4329
4325 wctx = repo[None]
4330 wctx = repo[None]
4326
4331
4327 if ms.mergedriver and ms.mdstate() == 'u':
4332 if ms.mergedriver and ms.mdstate() == 'u':
4328 proceed = mergemod.driverpreprocess(repo, ms, wctx)
4333 proceed = mergemod.driverpreprocess(repo, ms, wctx)
4329 ms.commit()
4334 ms.commit()
4330 # allow mark and unmark to go through
4335 # allow mark and unmark to go through
4331 if not mark and not unmark and not proceed:
4336 if not mark and not unmark and not proceed:
4332 return 1
4337 return 1
4333
4338
4334 m = scmutil.match(wctx, pats, opts)
4339 m = scmutil.match(wctx, pats, opts)
4335 ret = 0
4340 ret = 0
4336 didwork = False
4341 didwork = False
4337 runconclude = False
4342 runconclude = False
4338
4343
4339 tocomplete = []
4344 tocomplete = []
4340 for f in ms:
4345 for f in ms:
4341 if not m(f):
4346 if not m(f):
4342 continue
4347 continue
4343
4348
4344 didwork = True
4349 didwork = True
4345
4350
4346 # don't let driver-resolved files be marked, and run the conclude
4351 # don't let driver-resolved files be marked, and run the conclude
4347 # step if asked to resolve
4352 # step if asked to resolve
4348 if ms[f] == "d":
4353 if ms[f] == "d":
4349 exact = m.exact(f)
4354 exact = m.exact(f)
4350 if mark:
4355 if mark:
4351 if exact:
4356 if exact:
4352 ui.warn(_('not marking %s as it is driver-resolved\n')
4357 ui.warn(_('not marking %s as it is driver-resolved\n')
4353 % f)
4358 % f)
4354 elif unmark:
4359 elif unmark:
4355 if exact:
4360 if exact:
4356 ui.warn(_('not unmarking %s as it is driver-resolved\n')
4361 ui.warn(_('not unmarking %s as it is driver-resolved\n')
4357 % f)
4362 % f)
4358 else:
4363 else:
4359 runconclude = True
4364 runconclude = True
4360 continue
4365 continue
4361
4366
4362 if mark:
4367 if mark:
4363 ms.mark(f, "r")
4368 ms.mark(f, "r")
4364 elif unmark:
4369 elif unmark:
4365 ms.mark(f, "u")
4370 ms.mark(f, "u")
4366 else:
4371 else:
4367 # backup pre-resolve (merge uses .orig for its own purposes)
4372 # backup pre-resolve (merge uses .orig for its own purposes)
4368 a = repo.wjoin(f)
4373 a = repo.wjoin(f)
4369 try:
4374 try:
4370 util.copyfile(a, a + ".resolve")
4375 util.copyfile(a, a + ".resolve")
4371 except (IOError, OSError) as inst:
4376 except (IOError, OSError) as inst:
4372 if inst.errno != errno.ENOENT:
4377 if inst.errno != errno.ENOENT:
4373 raise
4378 raise
4374
4379
4375 try:
4380 try:
4376 # preresolve file
4381 # preresolve file
4377 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
4382 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
4378 'resolve')
4383 'resolve')
4379 complete, r = ms.preresolve(f, wctx)
4384 complete, r = ms.preresolve(f, wctx)
4380 if not complete:
4385 if not complete:
4381 tocomplete.append(f)
4386 tocomplete.append(f)
4382 elif r:
4387 elif r:
4383 ret = 1
4388 ret = 1
4384 finally:
4389 finally:
4385 ui.setconfig('ui', 'forcemerge', '', 'resolve')
4390 ui.setconfig('ui', 'forcemerge', '', 'resolve')
4386 ms.commit()
4391 ms.commit()
4387
4392
4388 # replace filemerge's .orig file with our resolve file, but only
4393 # replace filemerge's .orig file with our resolve file, but only
4389 # for merges that are complete
4394 # for merges that are complete
4390 if complete:
4395 if complete:
4391 try:
4396 try:
4392 util.rename(a + ".resolve",
4397 util.rename(a + ".resolve",
4393 scmutil.origpath(ui, repo, a))
4398 scmutil.origpath(ui, repo, a))
4394 except OSError as inst:
4399 except OSError as inst:
4395 if inst.errno != errno.ENOENT:
4400 if inst.errno != errno.ENOENT:
4396 raise
4401 raise
4397
4402
4398 for f in tocomplete:
4403 for f in tocomplete:
4399 try:
4404 try:
4400 # resolve file
4405 # resolve file
4401 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
4406 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
4402 'resolve')
4407 'resolve')
4403 r = ms.resolve(f, wctx)
4408 r = ms.resolve(f, wctx)
4404 if r:
4409 if r:
4405 ret = 1
4410 ret = 1
4406 finally:
4411 finally:
4407 ui.setconfig('ui', 'forcemerge', '', 'resolve')
4412 ui.setconfig('ui', 'forcemerge', '', 'resolve')
4408 ms.commit()
4413 ms.commit()
4409
4414
4410 # replace filemerge's .orig file with our resolve file
4415 # replace filemerge's .orig file with our resolve file
4411 a = repo.wjoin(f)
4416 a = repo.wjoin(f)
4412 try:
4417 try:
4413 util.rename(a + ".resolve", scmutil.origpath(ui, repo, a))
4418 util.rename(a + ".resolve", scmutil.origpath(ui, repo, a))
4414 except OSError as inst:
4419 except OSError as inst:
4415 if inst.errno != errno.ENOENT:
4420 if inst.errno != errno.ENOENT:
4416 raise
4421 raise
4417
4422
4418 ms.commit()
4423 ms.commit()
4419 ms.recordactions()
4424 ms.recordactions()
4420
4425
4421 if not didwork and pats:
4426 if not didwork and pats:
4422 hint = None
4427 hint = None
4423 if not any([p for p in pats if p.find(':') >= 0]):
4428 if not any([p for p in pats if p.find(':') >= 0]):
4424 pats = ['path:%s' % p for p in pats]
4429 pats = ['path:%s' % p for p in pats]
4425 m = scmutil.match(wctx, pats, opts)
4430 m = scmutil.match(wctx, pats, opts)
4426 for f in ms:
4431 for f in ms:
4427 if not m(f):
4432 if not m(f):
4428 continue
4433 continue
4429 flags = ''.join(['-%s ' % o[0] for o in flaglist
4434 flags = ''.join(['-%s ' % o[0] for o in flaglist
4430 if opts.get(o)])
4435 if opts.get(o)])
4431 hint = _("(try: hg resolve %s%s)\n") % (
4436 hint = _("(try: hg resolve %s%s)\n") % (
4432 flags,
4437 flags,
4433 ' '.join(pats))
4438 ' '.join(pats))
4434 break
4439 break
4435 ui.warn(_("arguments do not match paths that need resolving\n"))
4440 ui.warn(_("arguments do not match paths that need resolving\n"))
4436 if hint:
4441 if hint:
4437 ui.warn(hint)
4442 ui.warn(hint)
4438 elif ms.mergedriver and ms.mdstate() != 's':
4443 elif ms.mergedriver and ms.mdstate() != 's':
4439 # run conclude step when either a driver-resolved file is requested
4444 # run conclude step when either a driver-resolved file is requested
4440 # or there are no driver-resolved files
4445 # or there are no driver-resolved files
4441 # we can't use 'ret' to determine whether any files are unresolved
4446 # we can't use 'ret' to determine whether any files are unresolved
4442 # because we might not have tried to resolve some
4447 # because we might not have tried to resolve some
4443 if ((runconclude or not list(ms.driverresolved()))
4448 if ((runconclude or not list(ms.driverresolved()))
4444 and not list(ms.unresolved())):
4449 and not list(ms.unresolved())):
4445 proceed = mergemod.driverconclude(repo, ms, wctx)
4450 proceed = mergemod.driverconclude(repo, ms, wctx)
4446 ms.commit()
4451 ms.commit()
4447 if not proceed:
4452 if not proceed:
4448 return 1
4453 return 1
4449
4454
4450 # Nudge users into finishing an unfinished operation
4455 # Nudge users into finishing an unfinished operation
4451 unresolvedf = list(ms.unresolved())
4456 unresolvedf = list(ms.unresolved())
4452 driverresolvedf = list(ms.driverresolved())
4457 driverresolvedf = list(ms.driverresolved())
4453 if not unresolvedf and not driverresolvedf:
4458 if not unresolvedf and not driverresolvedf:
4454 ui.status(_('(no more unresolved files)\n'))
4459 ui.status(_('(no more unresolved files)\n'))
4455 cmdutil.checkafterresolved(repo)
4460 cmdutil.checkafterresolved(repo)
4456 elif not unresolvedf:
4461 elif not unresolvedf:
4457 ui.status(_('(no more unresolved files -- '
4462 ui.status(_('(no more unresolved files -- '
4458 'run "hg resolve --all" to conclude)\n'))
4463 'run "hg resolve --all" to conclude)\n'))
4459
4464
4460 return ret
4465 return ret
4461
4466
4462 @command('revert',
4467 @command('revert',
4463 [('a', 'all', None, _('revert all changes when no arguments given')),
4468 [('a', 'all', None, _('revert all changes when no arguments given')),
4464 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
4469 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
4465 ('r', 'rev', '', _('revert to the specified revision'), _('REV')),
4470 ('r', 'rev', '', _('revert to the specified revision'), _('REV')),
4466 ('C', 'no-backup', None, _('do not save backup copies of files')),
4471 ('C', 'no-backup', None, _('do not save backup copies of files')),
4467 ('i', 'interactive', None,
4472 ('i', 'interactive', None,
4468 _('interactively select the changes (EXPERIMENTAL)')),
4473 _('interactively select the changes (EXPERIMENTAL)')),
4469 ] + walkopts + dryrunopts,
4474 ] + walkopts + dryrunopts,
4470 _('[OPTION]... [-r REV] [NAME]...'))
4475 _('[OPTION]... [-r REV] [NAME]...'))
4471 def revert(ui, repo, *pats, **opts):
4476 def revert(ui, repo, *pats, **opts):
4472 """restore files to their checkout state
4477 """restore files to their checkout state
4473
4478
4474 .. note::
4479 .. note::
4475
4480
4476 To check out earlier revisions, you should use :hg:`update REV`.
4481 To check out earlier revisions, you should use :hg:`update REV`.
4477 To cancel an uncommitted merge (and lose your changes),
4482 To cancel an uncommitted merge (and lose your changes),
4478 use :hg:`update --clean .`.
4483 use :hg:`update --clean .`.
4479
4484
4480 With no revision specified, revert the specified files or directories
4485 With no revision specified, revert the specified files or directories
4481 to the contents they had in the parent of the working directory.
4486 to the contents they had in the parent of the working directory.
4482 This restores the contents of files to an unmodified
4487 This restores the contents of files to an unmodified
4483 state and unschedules adds, removes, copies, and renames. If the
4488 state and unschedules adds, removes, copies, and renames. If the
4484 working directory has two parents, you must explicitly specify a
4489 working directory has two parents, you must explicitly specify a
4485 revision.
4490 revision.
4486
4491
4487 Using the -r/--rev or -d/--date options, revert the given files or
4492 Using the -r/--rev or -d/--date options, revert the given files or
4488 directories to their states as of a specific revision. Because
4493 directories to their states as of a specific revision. Because
4489 revert does not change the working directory parents, this will
4494 revert does not change the working directory parents, this will
4490 cause these files to appear modified. This can be helpful to "back
4495 cause these files to appear modified. This can be helpful to "back
4491 out" some or all of an earlier change. See :hg:`backout` for a
4496 out" some or all of an earlier change. See :hg:`backout` for a
4492 related method.
4497 related method.
4493
4498
4494 Modified files are saved with a .orig suffix before reverting.
4499 Modified files are saved with a .orig suffix before reverting.
4495 To disable these backups, use --no-backup. It is possible to store
4500 To disable these backups, use --no-backup. It is possible to store
4496 the backup files in a custom directory relative to the root of the
4501 the backup files in a custom directory relative to the root of the
4497 repository by setting the ``ui.origbackuppath`` configuration
4502 repository by setting the ``ui.origbackuppath`` configuration
4498 option.
4503 option.
4499
4504
4500 See :hg:`help dates` for a list of formats valid for -d/--date.
4505 See :hg:`help dates` for a list of formats valid for -d/--date.
4501
4506
4502 See :hg:`help backout` for a way to reverse the effect of an
4507 See :hg:`help backout` for a way to reverse the effect of an
4503 earlier changeset.
4508 earlier changeset.
4504
4509
4505 Returns 0 on success.
4510 Returns 0 on success.
4506 """
4511 """
4507
4512
4508 if opts.get("date"):
4513 if opts.get("date"):
4509 if opts.get("rev"):
4514 if opts.get("rev"):
4510 raise error.Abort(_("you can't specify a revision and a date"))
4515 raise error.Abort(_("you can't specify a revision and a date"))
4511 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
4516 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
4512
4517
4513 parent, p2 = repo.dirstate.parents()
4518 parent, p2 = repo.dirstate.parents()
4514 if not opts.get('rev') and p2 != nullid:
4519 if not opts.get('rev') and p2 != nullid:
4515 # revert after merge is a trap for new users (issue2915)
4520 # revert after merge is a trap for new users (issue2915)
4516 raise error.Abort(_('uncommitted merge with no revision specified'),
4521 raise error.Abort(_('uncommitted merge with no revision specified'),
4517 hint=_("use 'hg update' or see 'hg help revert'"))
4522 hint=_("use 'hg update' or see 'hg help revert'"))
4518
4523
4519 ctx = scmutil.revsingle(repo, opts.get('rev'))
4524 ctx = scmutil.revsingle(repo, opts.get('rev'))
4520
4525
4521 if (not (pats or opts.get('include') or opts.get('exclude') or
4526 if (not (pats or opts.get('include') or opts.get('exclude') or
4522 opts.get('all') or opts.get('interactive'))):
4527 opts.get('all') or opts.get('interactive'))):
4523 msg = _("no files or directories specified")
4528 msg = _("no files or directories specified")
4524 if p2 != nullid:
4529 if p2 != nullid:
4525 hint = _("uncommitted merge, use --all to discard all changes,"
4530 hint = _("uncommitted merge, use --all to discard all changes,"
4526 " or 'hg update -C .' to abort the merge")
4531 " or 'hg update -C .' to abort the merge")
4527 raise error.Abort(msg, hint=hint)
4532 raise error.Abort(msg, hint=hint)
4528 dirty = any(repo.status())
4533 dirty = any(repo.status())
4529 node = ctx.node()
4534 node = ctx.node()
4530 if node != parent:
4535 if node != parent:
4531 if dirty:
4536 if dirty:
4532 hint = _("uncommitted changes, use --all to discard all"
4537 hint = _("uncommitted changes, use --all to discard all"
4533 " changes, or 'hg update %s' to update") % ctx.rev()
4538 " changes, or 'hg update %s' to update") % ctx.rev()
4534 else:
4539 else:
4535 hint = _("use --all to revert all files,"
4540 hint = _("use --all to revert all files,"
4536 " or 'hg update %s' to update") % ctx.rev()
4541 " or 'hg update %s' to update") % ctx.rev()
4537 elif dirty:
4542 elif dirty:
4538 hint = _("uncommitted changes, use --all to discard all changes")
4543 hint = _("uncommitted changes, use --all to discard all changes")
4539 else:
4544 else:
4540 hint = _("use --all to revert all files")
4545 hint = _("use --all to revert all files")
4541 raise error.Abort(msg, hint=hint)
4546 raise error.Abort(msg, hint=hint)
4542
4547
4543 return cmdutil.revert(ui, repo, ctx, (parent, p2), *pats, **opts)
4548 return cmdutil.revert(ui, repo, ctx, (parent, p2), *pats, **opts)
4544
4549
4545 @command('rollback', dryrunopts +
4550 @command('rollback', dryrunopts +
4546 [('f', 'force', False, _('ignore safety measures'))])
4551 [('f', 'force', False, _('ignore safety measures'))])
4547 def rollback(ui, repo, **opts):
4552 def rollback(ui, repo, **opts):
4548 """roll back the last transaction (DANGEROUS) (DEPRECATED)
4553 """roll back the last transaction (DANGEROUS) (DEPRECATED)
4549
4554
4550 Please use :hg:`commit --amend` instead of rollback to correct
4555 Please use :hg:`commit --amend` instead of rollback to correct
4551 mistakes in the last commit.
4556 mistakes in the last commit.
4552
4557
4553 This command should be used with care. There is only one level of
4558 This command should be used with care. There is only one level of
4554 rollback, and there is no way to undo a rollback. It will also
4559 rollback, and there is no way to undo a rollback. It will also
4555 restore the dirstate at the time of the last transaction, losing
4560 restore the dirstate at the time of the last transaction, losing
4556 any dirstate changes since that time. This command does not alter
4561 any dirstate changes since that time. This command does not alter
4557 the working directory.
4562 the working directory.
4558
4563
4559 Transactions are used to encapsulate the effects of all commands
4564 Transactions are used to encapsulate the effects of all commands
4560 that create new changesets or propagate existing changesets into a
4565 that create new changesets or propagate existing changesets into a
4561 repository.
4566 repository.
4562
4567
4563 .. container:: verbose
4568 .. container:: verbose
4564
4569
4565 For example, the following commands are transactional, and their
4570 For example, the following commands are transactional, and their
4566 effects can be rolled back:
4571 effects can be rolled back:
4567
4572
4568 - commit
4573 - commit
4569 - import
4574 - import
4570 - pull
4575 - pull
4571 - push (with this repository as the destination)
4576 - push (with this repository as the destination)
4572 - unbundle
4577 - unbundle
4573
4578
4574 To avoid permanent data loss, rollback will refuse to rollback a
4579 To avoid permanent data loss, rollback will refuse to rollback a
4575 commit transaction if it isn't checked out. Use --force to
4580 commit transaction if it isn't checked out. Use --force to
4576 override this protection.
4581 override this protection.
4577
4582
4578 The rollback command can be entirely disabled by setting the
4583 The rollback command can be entirely disabled by setting the
4579 ``ui.rollback`` configuration setting to false. If you're here
4584 ``ui.rollback`` configuration setting to false. If you're here
4580 because you want to use rollback and it's disabled, you can
4585 because you want to use rollback and it's disabled, you can
4581 re-enable the command by setting ``ui.rollback`` to true.
4586 re-enable the command by setting ``ui.rollback`` to true.
4582
4587
4583 This command is not intended for use on public repositories. Once
4588 This command is not intended for use on public repositories. Once
4584 changes are visible for pull by other users, rolling a transaction
4589 changes are visible for pull by other users, rolling a transaction
4585 back locally is ineffective (someone else may already have pulled
4590 back locally is ineffective (someone else may already have pulled
4586 the changes). Furthermore, a race is possible with readers of the
4591 the changes). Furthermore, a race is possible with readers of the
4587 repository; for example an in-progress pull from the repository
4592 repository; for example an in-progress pull from the repository
4588 may fail if a rollback is performed.
4593 may fail if a rollback is performed.
4589
4594
4590 Returns 0 on success, 1 if no rollback data is available.
4595 Returns 0 on success, 1 if no rollback data is available.
4591 """
4596 """
4592 if not ui.configbool('ui', 'rollback', True):
4597 if not ui.configbool('ui', 'rollback', True):
4593 raise error.Abort(_('rollback is disabled because it is unsafe'),
4598 raise error.Abort(_('rollback is disabled because it is unsafe'),
4594 hint=('see `hg help -v rollback` for information'))
4599 hint=('see `hg help -v rollback` for information'))
4595 return repo.rollback(dryrun=opts.get(r'dry_run'),
4600 return repo.rollback(dryrun=opts.get(r'dry_run'),
4596 force=opts.get(r'force'))
4601 force=opts.get(r'force'))
4597
4602
4598 @command('root', [])
4603 @command('root', [])
4599 def root(ui, repo):
4604 def root(ui, repo):
4600 """print the root (top) of the current working directory
4605 """print the root (top) of the current working directory
4601
4606
4602 Print the root directory of the current repository.
4607 Print the root directory of the current repository.
4603
4608
4604 Returns 0 on success.
4609 Returns 0 on success.
4605 """
4610 """
4606 ui.write(repo.root + "\n")
4611 ui.write(repo.root + "\n")
4607
4612
4608 @command('^serve',
4613 @command('^serve',
4609 [('A', 'accesslog', '', _('name of access log file to write to'),
4614 [('A', 'accesslog', '', _('name of access log file to write to'),
4610 _('FILE')),
4615 _('FILE')),
4611 ('d', 'daemon', None, _('run server in background')),
4616 ('d', 'daemon', None, _('run server in background')),
4612 ('', 'daemon-postexec', [], _('used internally by daemon mode')),
4617 ('', 'daemon-postexec', [], _('used internally by daemon mode')),
4613 ('E', 'errorlog', '', _('name of error log file to write to'), _('FILE')),
4618 ('E', 'errorlog', '', _('name of error log file to write to'), _('FILE')),
4614 # use string type, then we can check if something was passed
4619 # use string type, then we can check if something was passed
4615 ('p', 'port', '', _('port to listen on (default: 8000)'), _('PORT')),
4620 ('p', 'port', '', _('port to listen on (default: 8000)'), _('PORT')),
4616 ('a', 'address', '', _('address to listen on (default: all interfaces)'),
4621 ('a', 'address', '', _('address to listen on (default: all interfaces)'),
4617 _('ADDR')),
4622 _('ADDR')),
4618 ('', 'prefix', '', _('prefix path to serve from (default: server root)'),
4623 ('', 'prefix', '', _('prefix path to serve from (default: server root)'),
4619 _('PREFIX')),
4624 _('PREFIX')),
4620 ('n', 'name', '',
4625 ('n', 'name', '',
4621 _('name to show in web pages (default: working directory)'), _('NAME')),
4626 _('name to show in web pages (default: working directory)'), _('NAME')),
4622 ('', 'web-conf', '',
4627 ('', 'web-conf', '',
4623 _("name of the hgweb config file (see 'hg help hgweb')"), _('FILE')),
4628 _("name of the hgweb config file (see 'hg help hgweb')"), _('FILE')),
4624 ('', 'webdir-conf', '', _('name of the hgweb config file (DEPRECATED)'),
4629 ('', 'webdir-conf', '', _('name of the hgweb config file (DEPRECATED)'),
4625 _('FILE')),
4630 _('FILE')),
4626 ('', 'pid-file', '', _('name of file to write process ID to'), _('FILE')),
4631 ('', 'pid-file', '', _('name of file to write process ID to'), _('FILE')),
4627 ('', 'stdio', None, _('for remote clients (ADVANCED)')),
4632 ('', 'stdio', None, _('for remote clients (ADVANCED)')),
4628 ('', 'cmdserver', '', _('for remote clients (ADVANCED)'), _('MODE')),
4633 ('', 'cmdserver', '', _('for remote clients (ADVANCED)'), _('MODE')),
4629 ('t', 'templates', '', _('web templates to use'), _('TEMPLATE')),
4634 ('t', 'templates', '', _('web templates to use'), _('TEMPLATE')),
4630 ('', 'style', '', _('template style to use'), _('STYLE')),
4635 ('', 'style', '', _('template style to use'), _('STYLE')),
4631 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
4636 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
4632 ('', 'certificate', '', _('SSL certificate file'), _('FILE'))]
4637 ('', 'certificate', '', _('SSL certificate file'), _('FILE'))]
4633 + subrepoopts,
4638 + subrepoopts,
4634 _('[OPTION]...'),
4639 _('[OPTION]...'),
4635 optionalrepo=True)
4640 optionalrepo=True)
4636 def serve(ui, repo, **opts):
4641 def serve(ui, repo, **opts):
4637 """start stand-alone webserver
4642 """start stand-alone webserver
4638
4643
4639 Start a local HTTP repository browser and pull server. You can use
4644 Start a local HTTP repository browser and pull server. You can use
4640 this for ad-hoc sharing and browsing of repositories. It is
4645 this for ad-hoc sharing and browsing of repositories. It is
4641 recommended to use a real web server to serve a repository for
4646 recommended to use a real web server to serve a repository for
4642 longer periods of time.
4647 longer periods of time.
4643
4648
4644 Please note that the server does not implement access control.
4649 Please note that the server does not implement access control.
4645 This means that, by default, anybody can read from the server and
4650 This means that, by default, anybody can read from the server and
4646 nobody can write to it by default. Set the ``web.allow_push``
4651 nobody can write to it by default. Set the ``web.allow_push``
4647 option to ``*`` to allow everybody to push to the server. You
4652 option to ``*`` to allow everybody to push to the server. You
4648 should use a real web server if you need to authenticate users.
4653 should use a real web server if you need to authenticate users.
4649
4654
4650 By default, the server logs accesses to stdout and errors to
4655 By default, the server logs accesses to stdout and errors to
4651 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
4656 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
4652 files.
4657 files.
4653
4658
4654 To have the server choose a free port number to listen on, specify
4659 To have the server choose a free port number to listen on, specify
4655 a port number of 0; in this case, the server will print the port
4660 a port number of 0; in this case, the server will print the port
4656 number it uses.
4661 number it uses.
4657
4662
4658 Returns 0 on success.
4663 Returns 0 on success.
4659 """
4664 """
4660
4665
4661 opts = pycompat.byteskwargs(opts)
4666 opts = pycompat.byteskwargs(opts)
4662 if opts["stdio"] and opts["cmdserver"]:
4667 if opts["stdio"] and opts["cmdserver"]:
4663 raise error.Abort(_("cannot use --stdio with --cmdserver"))
4668 raise error.Abort(_("cannot use --stdio with --cmdserver"))
4664
4669
4665 if opts["stdio"]:
4670 if opts["stdio"]:
4666 if repo is None:
4671 if repo is None:
4667 raise error.RepoError(_("there is no Mercurial repository here"
4672 raise error.RepoError(_("there is no Mercurial repository here"
4668 " (.hg not found)"))
4673 " (.hg not found)"))
4669 s = sshserver.sshserver(ui, repo)
4674 s = sshserver.sshserver(ui, repo)
4670 s.serve_forever()
4675 s.serve_forever()
4671
4676
4672 service = server.createservice(ui, repo, opts)
4677 service = server.createservice(ui, repo, opts)
4673 return server.runservice(opts, initfn=service.init, runfn=service.run)
4678 return server.runservice(opts, initfn=service.init, runfn=service.run)
4674
4679
4675 @command('^status|st',
4680 @command('^status|st',
4676 [('A', 'all', None, _('show status of all files')),
4681 [('A', 'all', None, _('show status of all files')),
4677 ('m', 'modified', None, _('show only modified files')),
4682 ('m', 'modified', None, _('show only modified files')),
4678 ('a', 'added', None, _('show only added files')),
4683 ('a', 'added', None, _('show only added files')),
4679 ('r', 'removed', None, _('show only removed files')),
4684 ('r', 'removed', None, _('show only removed files')),
4680 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
4685 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
4681 ('c', 'clean', None, _('show only files without changes')),
4686 ('c', 'clean', None, _('show only files without changes')),
4682 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
4687 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
4683 ('i', 'ignored', None, _('show only ignored files')),
4688 ('i', 'ignored', None, _('show only ignored files')),
4684 ('n', 'no-status', None, _('hide status prefix')),
4689 ('n', 'no-status', None, _('hide status prefix')),
4685 ('C', 'copies', None, _('show source of copied files')),
4690 ('C', 'copies', None, _('show source of copied files')),
4686 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
4691 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
4687 ('', 'rev', [], _('show difference from revision'), _('REV')),
4692 ('', 'rev', [], _('show difference from revision'), _('REV')),
4688 ('', 'change', '', _('list the changed files of a revision'), _('REV')),
4693 ('', 'change', '', _('list the changed files of a revision'), _('REV')),
4689 ] + walkopts + subrepoopts + formatteropts,
4694 ] + walkopts + subrepoopts + formatteropts,
4690 _('[OPTION]... [FILE]...'),
4695 _('[OPTION]... [FILE]...'),
4691 inferrepo=True)
4696 inferrepo=True)
4692 def status(ui, repo, *pats, **opts):
4697 def status(ui, repo, *pats, **opts):
4693 """show changed files in the working directory
4698 """show changed files in the working directory
4694
4699
4695 Show status of files in the repository. If names are given, only
4700 Show status of files in the repository. If names are given, only
4696 files that match are shown. Files that are clean or ignored or
4701 files that match are shown. Files that are clean or ignored or
4697 the source of a copy/move operation, are not listed unless
4702 the source of a copy/move operation, are not listed unless
4698 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
4703 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
4699 Unless options described with "show only ..." are given, the
4704 Unless options described with "show only ..." are given, the
4700 options -mardu are used.
4705 options -mardu are used.
4701
4706
4702 Option -q/--quiet hides untracked (unknown and ignored) files
4707 Option -q/--quiet hides untracked (unknown and ignored) files
4703 unless explicitly requested with -u/--unknown or -i/--ignored.
4708 unless explicitly requested with -u/--unknown or -i/--ignored.
4704
4709
4705 .. note::
4710 .. note::
4706
4711
4707 :hg:`status` may appear to disagree with diff if permissions have
4712 :hg:`status` may appear to disagree with diff if permissions have
4708 changed or a merge has occurred. The standard diff format does
4713 changed or a merge has occurred. The standard diff format does
4709 not report permission changes and diff only reports changes
4714 not report permission changes and diff only reports changes
4710 relative to one merge parent.
4715 relative to one merge parent.
4711
4716
4712 If one revision is given, it is used as the base revision.
4717 If one revision is given, it is used as the base revision.
4713 If two revisions are given, the differences between them are
4718 If two revisions are given, the differences between them are
4714 shown. The --change option can also be used as a shortcut to list
4719 shown. The --change option can also be used as a shortcut to list
4715 the changed files of a revision from its first parent.
4720 the changed files of a revision from its first parent.
4716
4721
4717 The codes used to show the status of files are::
4722 The codes used to show the status of files are::
4718
4723
4719 M = modified
4724 M = modified
4720 A = added
4725 A = added
4721 R = removed
4726 R = removed
4722 C = clean
4727 C = clean
4723 ! = missing (deleted by non-hg command, but still tracked)
4728 ! = missing (deleted by non-hg command, but still tracked)
4724 ? = not tracked
4729 ? = not tracked
4725 I = ignored
4730 I = ignored
4726 = origin of the previous file (with --copies)
4731 = origin of the previous file (with --copies)
4727
4732
4728 .. container:: verbose
4733 .. container:: verbose
4729
4734
4730 Examples:
4735 Examples:
4731
4736
4732 - show changes in the working directory relative to a
4737 - show changes in the working directory relative to a
4733 changeset::
4738 changeset::
4734
4739
4735 hg status --rev 9353
4740 hg status --rev 9353
4736
4741
4737 - show changes in the working directory relative to the
4742 - show changes in the working directory relative to the
4738 current directory (see :hg:`help patterns` for more information)::
4743 current directory (see :hg:`help patterns` for more information)::
4739
4744
4740 hg status re:
4745 hg status re:
4741
4746
4742 - show all changes including copies in an existing changeset::
4747 - show all changes including copies in an existing changeset::
4743
4748
4744 hg status --copies --change 9353
4749 hg status --copies --change 9353
4745
4750
4746 - get a NUL separated list of added files, suitable for xargs::
4751 - get a NUL separated list of added files, suitable for xargs::
4747
4752
4748 hg status -an0
4753 hg status -an0
4749
4754
4750 Returns 0 on success.
4755 Returns 0 on success.
4751 """
4756 """
4752
4757
4753 opts = pycompat.byteskwargs(opts)
4758 opts = pycompat.byteskwargs(opts)
4754 revs = opts.get('rev')
4759 revs = opts.get('rev')
4755 change = opts.get('change')
4760 change = opts.get('change')
4756
4761
4757 if revs and change:
4762 if revs and change:
4758 msg = _('cannot specify --rev and --change at the same time')
4763 msg = _('cannot specify --rev and --change at the same time')
4759 raise error.Abort(msg)
4764 raise error.Abort(msg)
4760 elif change:
4765 elif change:
4761 node2 = scmutil.revsingle(repo, change, None).node()
4766 node2 = scmutil.revsingle(repo, change, None).node()
4762 node1 = repo[node2].p1().node()
4767 node1 = repo[node2].p1().node()
4763 else:
4768 else:
4764 node1, node2 = scmutil.revpair(repo, revs)
4769 node1, node2 = scmutil.revpair(repo, revs)
4765
4770
4766 if pats or ui.configbool('commands', 'status.relative'):
4771 if pats or ui.configbool('commands', 'status.relative'):
4767 cwd = repo.getcwd()
4772 cwd = repo.getcwd()
4768 else:
4773 else:
4769 cwd = ''
4774 cwd = ''
4770
4775
4771 if opts.get('print0'):
4776 if opts.get('print0'):
4772 end = '\0'
4777 end = '\0'
4773 else:
4778 else:
4774 end = '\n'
4779 end = '\n'
4775 copy = {}
4780 copy = {}
4776 states = 'modified added removed deleted unknown ignored clean'.split()
4781 states = 'modified added removed deleted unknown ignored clean'.split()
4777 show = [k for k in states if opts.get(k)]
4782 show = [k for k in states if opts.get(k)]
4778 if opts.get('all'):
4783 if opts.get('all'):
4779 show += ui.quiet and (states[:4] + ['clean']) or states
4784 show += ui.quiet and (states[:4] + ['clean']) or states
4780 if not show:
4785 if not show:
4781 if ui.quiet:
4786 if ui.quiet:
4782 show = states[:4]
4787 show = states[:4]
4783 else:
4788 else:
4784 show = states[:5]
4789 show = states[:5]
4785
4790
4786 m = scmutil.match(repo[node2], pats, opts)
4791 m = scmutil.match(repo[node2], pats, opts)
4787 stat = repo.status(node1, node2, m,
4792 stat = repo.status(node1, node2, m,
4788 'ignored' in show, 'clean' in show, 'unknown' in show,
4793 'ignored' in show, 'clean' in show, 'unknown' in show,
4789 opts.get('subrepos'))
4794 opts.get('subrepos'))
4790 changestates = zip(states, pycompat.iterbytestr('MAR!?IC'), stat)
4795 changestates = zip(states, pycompat.iterbytestr('MAR!?IC'), stat)
4791
4796
4792 if (opts.get('all') or opts.get('copies')
4797 if (opts.get('all') or opts.get('copies')
4793 or ui.configbool('ui', 'statuscopies')) and not opts.get('no_status'):
4798 or ui.configbool('ui', 'statuscopies')) and not opts.get('no_status'):
4794 copy = copies.pathcopies(repo[node1], repo[node2], m)
4799 copy = copies.pathcopies(repo[node1], repo[node2], m)
4795
4800
4796 ui.pager('status')
4801 ui.pager('status')
4797 fm = ui.formatter('status', opts)
4802 fm = ui.formatter('status', opts)
4798 fmt = '%s' + end
4803 fmt = '%s' + end
4799 showchar = not opts.get('no_status')
4804 showchar = not opts.get('no_status')
4800
4805
4801 for state, char, files in changestates:
4806 for state, char, files in changestates:
4802 if state in show:
4807 if state in show:
4803 label = 'status.' + state
4808 label = 'status.' + state
4804 for f in files:
4809 for f in files:
4805 fm.startitem()
4810 fm.startitem()
4806 fm.condwrite(showchar, 'status', '%s ', char, label=label)
4811 fm.condwrite(showchar, 'status', '%s ', char, label=label)
4807 fm.write('path', fmt, repo.pathto(f, cwd), label=label)
4812 fm.write('path', fmt, repo.pathto(f, cwd), label=label)
4808 if f in copy:
4813 if f in copy:
4809 fm.write("copy", ' %s' + end, repo.pathto(copy[f], cwd),
4814 fm.write("copy", ' %s' + end, repo.pathto(copy[f], cwd),
4810 label='status.copied')
4815 label='status.copied')
4811 fm.end()
4816 fm.end()
4812
4817
4813 @command('^summary|sum',
4818 @command('^summary|sum',
4814 [('', 'remote', None, _('check for push and pull'))], '[--remote]')
4819 [('', 'remote', None, _('check for push and pull'))], '[--remote]')
4815 def summary(ui, repo, **opts):
4820 def summary(ui, repo, **opts):
4816 """summarize working directory state
4821 """summarize working directory state
4817
4822
4818 This generates a brief summary of the working directory state,
4823 This generates a brief summary of the working directory state,
4819 including parents, branch, commit status, phase and available updates.
4824 including parents, branch, commit status, phase and available updates.
4820
4825
4821 With the --remote option, this will check the default paths for
4826 With the --remote option, this will check the default paths for
4822 incoming and outgoing changes. This can be time-consuming.
4827 incoming and outgoing changes. This can be time-consuming.
4823
4828
4824 Returns 0 on success.
4829 Returns 0 on success.
4825 """
4830 """
4826
4831
4827 opts = pycompat.byteskwargs(opts)
4832 opts = pycompat.byteskwargs(opts)
4828 ui.pager('summary')
4833 ui.pager('summary')
4829 ctx = repo[None]
4834 ctx = repo[None]
4830 parents = ctx.parents()
4835 parents = ctx.parents()
4831 pnode = parents[0].node()
4836 pnode = parents[0].node()
4832 marks = []
4837 marks = []
4833
4838
4834 ms = None
4839 ms = None
4835 try:
4840 try:
4836 ms = mergemod.mergestate.read(repo)
4841 ms = mergemod.mergestate.read(repo)
4837 except error.UnsupportedMergeRecords as e:
4842 except error.UnsupportedMergeRecords as e:
4838 s = ' '.join(e.recordtypes)
4843 s = ' '.join(e.recordtypes)
4839 ui.warn(
4844 ui.warn(
4840 _('warning: merge state has unsupported record types: %s\n') % s)
4845 _('warning: merge state has unsupported record types: %s\n') % s)
4841 unresolved = 0
4846 unresolved = 0
4842 else:
4847 else:
4843 unresolved = [f for f in ms if ms[f] == 'u']
4848 unresolved = [f for f in ms if ms[f] == 'u']
4844
4849
4845 for p in parents:
4850 for p in parents:
4846 # label with log.changeset (instead of log.parent) since this
4851 # label with log.changeset (instead of log.parent) since this
4847 # shows a working directory parent *changeset*:
4852 # shows a working directory parent *changeset*:
4848 # i18n: column positioning for "hg summary"
4853 # i18n: column positioning for "hg summary"
4849 ui.write(_('parent: %d:%s ') % (p.rev(), p),
4854 ui.write(_('parent: %d:%s ') % (p.rev(), p),
4850 label=cmdutil._changesetlabels(p))
4855 label=cmdutil._changesetlabels(p))
4851 ui.write(' '.join(p.tags()), label='log.tag')
4856 ui.write(' '.join(p.tags()), label='log.tag')
4852 if p.bookmarks():
4857 if p.bookmarks():
4853 marks.extend(p.bookmarks())
4858 marks.extend(p.bookmarks())
4854 if p.rev() == -1:
4859 if p.rev() == -1:
4855 if not len(repo):
4860 if not len(repo):
4856 ui.write(_(' (empty repository)'))
4861 ui.write(_(' (empty repository)'))
4857 else:
4862 else:
4858 ui.write(_(' (no revision checked out)'))
4863 ui.write(_(' (no revision checked out)'))
4859 if p.obsolete():
4864 if p.obsolete():
4860 ui.write(_(' (obsolete)'))
4865 ui.write(_(' (obsolete)'))
4861 if p.troubled():
4866 if p.troubled():
4862 ui.write(' ('
4867 ui.write(' ('
4863 + ', '.join(ui.label(trouble, 'trouble.%s' % trouble)
4868 + ', '.join(ui.label(trouble, 'trouble.%s' % trouble)
4864 for trouble in p.troubles())
4869 for trouble in p.troubles())
4865 + ')')
4870 + ')')
4866 ui.write('\n')
4871 ui.write('\n')
4867 if p.description():
4872 if p.description():
4868 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
4873 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
4869 label='log.summary')
4874 label='log.summary')
4870
4875
4871 branch = ctx.branch()
4876 branch = ctx.branch()
4872 bheads = repo.branchheads(branch)
4877 bheads = repo.branchheads(branch)
4873 # i18n: column positioning for "hg summary"
4878 # i18n: column positioning for "hg summary"
4874 m = _('branch: %s\n') % branch
4879 m = _('branch: %s\n') % branch
4875 if branch != 'default':
4880 if branch != 'default':
4876 ui.write(m, label='log.branch')
4881 ui.write(m, label='log.branch')
4877 else:
4882 else:
4878 ui.status(m, label='log.branch')
4883 ui.status(m, label='log.branch')
4879
4884
4880 if marks:
4885 if marks:
4881 active = repo._activebookmark
4886 active = repo._activebookmark
4882 # i18n: column positioning for "hg summary"
4887 # i18n: column positioning for "hg summary"
4883 ui.write(_('bookmarks:'), label='log.bookmark')
4888 ui.write(_('bookmarks:'), label='log.bookmark')
4884 if active is not None:
4889 if active is not None:
4885 if active in marks:
4890 if active in marks:
4886 ui.write(' *' + active, label=activebookmarklabel)
4891 ui.write(' *' + active, label=activebookmarklabel)
4887 marks.remove(active)
4892 marks.remove(active)
4888 else:
4893 else:
4889 ui.write(' [%s]' % active, label=activebookmarklabel)
4894 ui.write(' [%s]' % active, label=activebookmarklabel)
4890 for m in marks:
4895 for m in marks:
4891 ui.write(' ' + m, label='log.bookmark')
4896 ui.write(' ' + m, label='log.bookmark')
4892 ui.write('\n', label='log.bookmark')
4897 ui.write('\n', label='log.bookmark')
4893
4898
4894 status = repo.status(unknown=True)
4899 status = repo.status(unknown=True)
4895
4900
4896 c = repo.dirstate.copies()
4901 c = repo.dirstate.copies()
4897 copied, renamed = [], []
4902 copied, renamed = [], []
4898 for d, s in c.iteritems():
4903 for d, s in c.iteritems():
4899 if s in status.removed:
4904 if s in status.removed:
4900 status.removed.remove(s)
4905 status.removed.remove(s)
4901 renamed.append(d)
4906 renamed.append(d)
4902 else:
4907 else:
4903 copied.append(d)
4908 copied.append(d)
4904 if d in status.added:
4909 if d in status.added:
4905 status.added.remove(d)
4910 status.added.remove(d)
4906
4911
4907 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
4912 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
4908
4913
4909 labels = [(ui.label(_('%d modified'), 'status.modified'), status.modified),
4914 labels = [(ui.label(_('%d modified'), 'status.modified'), status.modified),
4910 (ui.label(_('%d added'), 'status.added'), status.added),
4915 (ui.label(_('%d added'), 'status.added'), status.added),
4911 (ui.label(_('%d removed'), 'status.removed'), status.removed),
4916 (ui.label(_('%d removed'), 'status.removed'), status.removed),
4912 (ui.label(_('%d renamed'), 'status.copied'), renamed),
4917 (ui.label(_('%d renamed'), 'status.copied'), renamed),
4913 (ui.label(_('%d copied'), 'status.copied'), copied),
4918 (ui.label(_('%d copied'), 'status.copied'), copied),
4914 (ui.label(_('%d deleted'), 'status.deleted'), status.deleted),
4919 (ui.label(_('%d deleted'), 'status.deleted'), status.deleted),
4915 (ui.label(_('%d unknown'), 'status.unknown'), status.unknown),
4920 (ui.label(_('%d unknown'), 'status.unknown'), status.unknown),
4916 (ui.label(_('%d unresolved'), 'resolve.unresolved'), unresolved),
4921 (ui.label(_('%d unresolved'), 'resolve.unresolved'), unresolved),
4917 (ui.label(_('%d subrepos'), 'status.modified'), subs)]
4922 (ui.label(_('%d subrepos'), 'status.modified'), subs)]
4918 t = []
4923 t = []
4919 for l, s in labels:
4924 for l, s in labels:
4920 if s:
4925 if s:
4921 t.append(l % len(s))
4926 t.append(l % len(s))
4922
4927
4923 t = ', '.join(t)
4928 t = ', '.join(t)
4924 cleanworkdir = False
4929 cleanworkdir = False
4925
4930
4926 if repo.vfs.exists('graftstate'):
4931 if repo.vfs.exists('graftstate'):
4927 t += _(' (graft in progress)')
4932 t += _(' (graft in progress)')
4928 if repo.vfs.exists('updatestate'):
4933 if repo.vfs.exists('updatestate'):
4929 t += _(' (interrupted update)')
4934 t += _(' (interrupted update)')
4930 elif len(parents) > 1:
4935 elif len(parents) > 1:
4931 t += _(' (merge)')
4936 t += _(' (merge)')
4932 elif branch != parents[0].branch():
4937 elif branch != parents[0].branch():
4933 t += _(' (new branch)')
4938 t += _(' (new branch)')
4934 elif (parents[0].closesbranch() and
4939 elif (parents[0].closesbranch() and
4935 pnode in repo.branchheads(branch, closed=True)):
4940 pnode in repo.branchheads(branch, closed=True)):
4936 t += _(' (head closed)')
4941 t += _(' (head closed)')
4937 elif not (status.modified or status.added or status.removed or renamed or
4942 elif not (status.modified or status.added or status.removed or renamed or
4938 copied or subs):
4943 copied or subs):
4939 t += _(' (clean)')
4944 t += _(' (clean)')
4940 cleanworkdir = True
4945 cleanworkdir = True
4941 elif pnode not in bheads:
4946 elif pnode not in bheads:
4942 t += _(' (new branch head)')
4947 t += _(' (new branch head)')
4943
4948
4944 if parents:
4949 if parents:
4945 pendingphase = max(p.phase() for p in parents)
4950 pendingphase = max(p.phase() for p in parents)
4946 else:
4951 else:
4947 pendingphase = phases.public
4952 pendingphase = phases.public
4948
4953
4949 if pendingphase > phases.newcommitphase(ui):
4954 if pendingphase > phases.newcommitphase(ui):
4950 t += ' (%s)' % phases.phasenames[pendingphase]
4955 t += ' (%s)' % phases.phasenames[pendingphase]
4951
4956
4952 if cleanworkdir:
4957 if cleanworkdir:
4953 # i18n: column positioning for "hg summary"
4958 # i18n: column positioning for "hg summary"
4954 ui.status(_('commit: %s\n') % t.strip())
4959 ui.status(_('commit: %s\n') % t.strip())
4955 else:
4960 else:
4956 # i18n: column positioning for "hg summary"
4961 # i18n: column positioning for "hg summary"
4957 ui.write(_('commit: %s\n') % t.strip())
4962 ui.write(_('commit: %s\n') % t.strip())
4958
4963
4959 # all ancestors of branch heads - all ancestors of parent = new csets
4964 # all ancestors of branch heads - all ancestors of parent = new csets
4960 new = len(repo.changelog.findmissing([pctx.node() for pctx in parents],
4965 new = len(repo.changelog.findmissing([pctx.node() for pctx in parents],
4961 bheads))
4966 bheads))
4962
4967
4963 if new == 0:
4968 if new == 0:
4964 # i18n: column positioning for "hg summary"
4969 # i18n: column positioning for "hg summary"
4965 ui.status(_('update: (current)\n'))
4970 ui.status(_('update: (current)\n'))
4966 elif pnode not in bheads:
4971 elif pnode not in bheads:
4967 # i18n: column positioning for "hg summary"
4972 # i18n: column positioning for "hg summary"
4968 ui.write(_('update: %d new changesets (update)\n') % new)
4973 ui.write(_('update: %d new changesets (update)\n') % new)
4969 else:
4974 else:
4970 # i18n: column positioning for "hg summary"
4975 # i18n: column positioning for "hg summary"
4971 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
4976 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
4972 (new, len(bheads)))
4977 (new, len(bheads)))
4973
4978
4974 t = []
4979 t = []
4975 draft = len(repo.revs('draft()'))
4980 draft = len(repo.revs('draft()'))
4976 if draft:
4981 if draft:
4977 t.append(_('%d draft') % draft)
4982 t.append(_('%d draft') % draft)
4978 secret = len(repo.revs('secret()'))
4983 secret = len(repo.revs('secret()'))
4979 if secret:
4984 if secret:
4980 t.append(_('%d secret') % secret)
4985 t.append(_('%d secret') % secret)
4981
4986
4982 if draft or secret:
4987 if draft or secret:
4983 ui.status(_('phases: %s\n') % ', '.join(t))
4988 ui.status(_('phases: %s\n') % ', '.join(t))
4984
4989
4985 if obsolete.isenabled(repo, obsolete.createmarkersopt):
4990 if obsolete.isenabled(repo, obsolete.createmarkersopt):
4986 for trouble in ("unstable", "divergent", "bumped"):
4991 for trouble in ("unstable", "divergent", "bumped"):
4987 numtrouble = len(repo.revs(trouble + "()"))
4992 numtrouble = len(repo.revs(trouble + "()"))
4988 # We write all the possibilities to ease translation
4993 # We write all the possibilities to ease translation
4989 troublemsg = {
4994 troublemsg = {
4990 "unstable": _("unstable: %d changesets"),
4995 "unstable": _("unstable: %d changesets"),
4991 "divergent": _("divergent: %d changesets"),
4996 "divergent": _("divergent: %d changesets"),
4992 "bumped": _("bumped: %d changesets"),
4997 "bumped": _("bumped: %d changesets"),
4993 }
4998 }
4994 if numtrouble > 0:
4999 if numtrouble > 0:
4995 ui.status(troublemsg[trouble] % numtrouble + "\n")
5000 ui.status(troublemsg[trouble] % numtrouble + "\n")
4996
5001
4997 cmdutil.summaryhooks(ui, repo)
5002 cmdutil.summaryhooks(ui, repo)
4998
5003
4999 if opts.get('remote'):
5004 if opts.get('remote'):
5000 needsincoming, needsoutgoing = True, True
5005 needsincoming, needsoutgoing = True, True
5001 else:
5006 else:
5002 needsincoming, needsoutgoing = False, False
5007 needsincoming, needsoutgoing = False, False
5003 for i, o in cmdutil.summaryremotehooks(ui, repo, opts, None):
5008 for i, o in cmdutil.summaryremotehooks(ui, repo, opts, None):
5004 if i:
5009 if i:
5005 needsincoming = True
5010 needsincoming = True
5006 if o:
5011 if o:
5007 needsoutgoing = True
5012 needsoutgoing = True
5008 if not needsincoming and not needsoutgoing:
5013 if not needsincoming and not needsoutgoing:
5009 return
5014 return
5010
5015
5011 def getincoming():
5016 def getincoming():
5012 source, branches = hg.parseurl(ui.expandpath('default'))
5017 source, branches = hg.parseurl(ui.expandpath('default'))
5013 sbranch = branches[0]
5018 sbranch = branches[0]
5014 try:
5019 try:
5015 other = hg.peer(repo, {}, source)
5020 other = hg.peer(repo, {}, source)
5016 except error.RepoError:
5021 except error.RepoError:
5017 if opts.get('remote'):
5022 if opts.get('remote'):
5018 raise
5023 raise
5019 return source, sbranch, None, None, None
5024 return source, sbranch, None, None, None
5020 revs, checkout = hg.addbranchrevs(repo, other, branches, None)
5025 revs, checkout = hg.addbranchrevs(repo, other, branches, None)
5021 if revs:
5026 if revs:
5022 revs = [other.lookup(rev) for rev in revs]
5027 revs = [other.lookup(rev) for rev in revs]
5023 ui.debug('comparing with %s\n' % util.hidepassword(source))
5028 ui.debug('comparing with %s\n' % util.hidepassword(source))
5024 repo.ui.pushbuffer()
5029 repo.ui.pushbuffer()
5025 commoninc = discovery.findcommonincoming(repo, other, heads=revs)
5030 commoninc = discovery.findcommonincoming(repo, other, heads=revs)
5026 repo.ui.popbuffer()
5031 repo.ui.popbuffer()
5027 return source, sbranch, other, commoninc, commoninc[1]
5032 return source, sbranch, other, commoninc, commoninc[1]
5028
5033
5029 if needsincoming:
5034 if needsincoming:
5030 source, sbranch, sother, commoninc, incoming = getincoming()
5035 source, sbranch, sother, commoninc, incoming = getincoming()
5031 else:
5036 else:
5032 source = sbranch = sother = commoninc = incoming = None
5037 source = sbranch = sother = commoninc = incoming = None
5033
5038
5034 def getoutgoing():
5039 def getoutgoing():
5035 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
5040 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
5036 dbranch = branches[0]
5041 dbranch = branches[0]
5037 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
5042 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
5038 if source != dest:
5043 if source != dest:
5039 try:
5044 try:
5040 dother = hg.peer(repo, {}, dest)
5045 dother = hg.peer(repo, {}, dest)
5041 except error.RepoError:
5046 except error.RepoError:
5042 if opts.get('remote'):
5047 if opts.get('remote'):
5043 raise
5048 raise
5044 return dest, dbranch, None, None
5049 return dest, dbranch, None, None
5045 ui.debug('comparing with %s\n' % util.hidepassword(dest))
5050 ui.debug('comparing with %s\n' % util.hidepassword(dest))
5046 elif sother is None:
5051 elif sother is None:
5047 # there is no explicit destination peer, but source one is invalid
5052 # there is no explicit destination peer, but source one is invalid
5048 return dest, dbranch, None, None
5053 return dest, dbranch, None, None
5049 else:
5054 else:
5050 dother = sother
5055 dother = sother
5051 if (source != dest or (sbranch is not None and sbranch != dbranch)):
5056 if (source != dest or (sbranch is not None and sbranch != dbranch)):
5052 common = None
5057 common = None
5053 else:
5058 else:
5054 common = commoninc
5059 common = commoninc
5055 if revs:
5060 if revs:
5056 revs = [repo.lookup(rev) for rev in revs]
5061 revs = [repo.lookup(rev) for rev in revs]
5057 repo.ui.pushbuffer()
5062 repo.ui.pushbuffer()
5058 outgoing = discovery.findcommonoutgoing(repo, dother, onlyheads=revs,
5063 outgoing = discovery.findcommonoutgoing(repo, dother, onlyheads=revs,
5059 commoninc=common)
5064 commoninc=common)
5060 repo.ui.popbuffer()
5065 repo.ui.popbuffer()
5061 return dest, dbranch, dother, outgoing
5066 return dest, dbranch, dother, outgoing
5062
5067
5063 if needsoutgoing:
5068 if needsoutgoing:
5064 dest, dbranch, dother, outgoing = getoutgoing()
5069 dest, dbranch, dother, outgoing = getoutgoing()
5065 else:
5070 else:
5066 dest = dbranch = dother = outgoing = None
5071 dest = dbranch = dother = outgoing = None
5067
5072
5068 if opts.get('remote'):
5073 if opts.get('remote'):
5069 t = []
5074 t = []
5070 if incoming:
5075 if incoming:
5071 t.append(_('1 or more incoming'))
5076 t.append(_('1 or more incoming'))
5072 o = outgoing.missing
5077 o = outgoing.missing
5073 if o:
5078 if o:
5074 t.append(_('%d outgoing') % len(o))
5079 t.append(_('%d outgoing') % len(o))
5075 other = dother or sother
5080 other = dother or sother
5076 if 'bookmarks' in other.listkeys('namespaces'):
5081 if 'bookmarks' in other.listkeys('namespaces'):
5077 counts = bookmarks.summary(repo, other)
5082 counts = bookmarks.summary(repo, other)
5078 if counts[0] > 0:
5083 if counts[0] > 0:
5079 t.append(_('%d incoming bookmarks') % counts[0])
5084 t.append(_('%d incoming bookmarks') % counts[0])
5080 if counts[1] > 0:
5085 if counts[1] > 0:
5081 t.append(_('%d outgoing bookmarks') % counts[1])
5086 t.append(_('%d outgoing bookmarks') % counts[1])
5082
5087
5083 if t:
5088 if t:
5084 # i18n: column positioning for "hg summary"
5089 # i18n: column positioning for "hg summary"
5085 ui.write(_('remote: %s\n') % (', '.join(t)))
5090 ui.write(_('remote: %s\n') % (', '.join(t)))
5086 else:
5091 else:
5087 # i18n: column positioning for "hg summary"
5092 # i18n: column positioning for "hg summary"
5088 ui.status(_('remote: (synced)\n'))
5093 ui.status(_('remote: (synced)\n'))
5089
5094
5090 cmdutil.summaryremotehooks(ui, repo, opts,
5095 cmdutil.summaryremotehooks(ui, repo, opts,
5091 ((source, sbranch, sother, commoninc),
5096 ((source, sbranch, sother, commoninc),
5092 (dest, dbranch, dother, outgoing)))
5097 (dest, dbranch, dother, outgoing)))
5093
5098
5094 @command('tag',
5099 @command('tag',
5095 [('f', 'force', None, _('force tag')),
5100 [('f', 'force', None, _('force tag')),
5096 ('l', 'local', None, _('make the tag local')),
5101 ('l', 'local', None, _('make the tag local')),
5097 ('r', 'rev', '', _('revision to tag'), _('REV')),
5102 ('r', 'rev', '', _('revision to tag'), _('REV')),
5098 ('', 'remove', None, _('remove a tag')),
5103 ('', 'remove', None, _('remove a tag')),
5099 # -l/--local is already there, commitopts cannot be used
5104 # -l/--local is already there, commitopts cannot be used
5100 ('e', 'edit', None, _('invoke editor on commit messages')),
5105 ('e', 'edit', None, _('invoke editor on commit messages')),
5101 ('m', 'message', '', _('use text as commit message'), _('TEXT')),
5106 ('m', 'message', '', _('use text as commit message'), _('TEXT')),
5102 ] + commitopts2,
5107 ] + commitopts2,
5103 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'))
5108 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'))
5104 def tag(ui, repo, name1, *names, **opts):
5109 def tag(ui, repo, name1, *names, **opts):
5105 """add one or more tags for the current or given revision
5110 """add one or more tags for the current or given revision
5106
5111
5107 Name a particular revision using <name>.
5112 Name a particular revision using <name>.
5108
5113
5109 Tags are used to name particular revisions of the repository and are
5114 Tags are used to name particular revisions of the repository and are
5110 very useful to compare different revisions, to go back to significant
5115 very useful to compare different revisions, to go back to significant
5111 earlier versions or to mark branch points as releases, etc. Changing
5116 earlier versions or to mark branch points as releases, etc. Changing
5112 an existing tag is normally disallowed; use -f/--force to override.
5117 an existing tag is normally disallowed; use -f/--force to override.
5113
5118
5114 If no revision is given, the parent of the working directory is
5119 If no revision is given, the parent of the working directory is
5115 used.
5120 used.
5116
5121
5117 To facilitate version control, distribution, and merging of tags,
5122 To facilitate version control, distribution, and merging of tags,
5118 they are stored as a file named ".hgtags" which is managed similarly
5123 they are stored as a file named ".hgtags" which is managed similarly
5119 to other project files and can be hand-edited if necessary. This
5124 to other project files and can be hand-edited if necessary. This
5120 also means that tagging creates a new commit. The file
5125 also means that tagging creates a new commit. The file
5121 ".hg/localtags" is used for local tags (not shared among
5126 ".hg/localtags" is used for local tags (not shared among
5122 repositories).
5127 repositories).
5123
5128
5124 Tag commits are usually made at the head of a branch. If the parent
5129 Tag commits are usually made at the head of a branch. If the parent
5125 of the working directory is not a branch head, :hg:`tag` aborts; use
5130 of the working directory is not a branch head, :hg:`tag` aborts; use
5126 -f/--force to force the tag commit to be based on a non-head
5131 -f/--force to force the tag commit to be based on a non-head
5127 changeset.
5132 changeset.
5128
5133
5129 See :hg:`help dates` for a list of formats valid for -d/--date.
5134 See :hg:`help dates` for a list of formats valid for -d/--date.
5130
5135
5131 Since tag names have priority over branch names during revision
5136 Since tag names have priority over branch names during revision
5132 lookup, using an existing branch name as a tag name is discouraged.
5137 lookup, using an existing branch name as a tag name is discouraged.
5133
5138
5134 Returns 0 on success.
5139 Returns 0 on success.
5135 """
5140 """
5136 opts = pycompat.byteskwargs(opts)
5141 opts = pycompat.byteskwargs(opts)
5137 wlock = lock = None
5142 wlock = lock = None
5138 try:
5143 try:
5139 wlock = repo.wlock()
5144 wlock = repo.wlock()
5140 lock = repo.lock()
5145 lock = repo.lock()
5141 rev_ = "."
5146 rev_ = "."
5142 names = [t.strip() for t in (name1,) + names]
5147 names = [t.strip() for t in (name1,) + names]
5143 if len(names) != len(set(names)):
5148 if len(names) != len(set(names)):
5144 raise error.Abort(_('tag names must be unique'))
5149 raise error.Abort(_('tag names must be unique'))
5145 for n in names:
5150 for n in names:
5146 scmutil.checknewlabel(repo, n, 'tag')
5151 scmutil.checknewlabel(repo, n, 'tag')
5147 if not n:
5152 if not n:
5148 raise error.Abort(_('tag names cannot consist entirely of '
5153 raise error.Abort(_('tag names cannot consist entirely of '
5149 'whitespace'))
5154 'whitespace'))
5150 if opts.get('rev') and opts.get('remove'):
5155 if opts.get('rev') and opts.get('remove'):
5151 raise error.Abort(_("--rev and --remove are incompatible"))
5156 raise error.Abort(_("--rev and --remove are incompatible"))
5152 if opts.get('rev'):
5157 if opts.get('rev'):
5153 rev_ = opts['rev']
5158 rev_ = opts['rev']
5154 message = opts.get('message')
5159 message = opts.get('message')
5155 if opts.get('remove'):
5160 if opts.get('remove'):
5156 if opts.get('local'):
5161 if opts.get('local'):
5157 expectedtype = 'local'
5162 expectedtype = 'local'
5158 else:
5163 else:
5159 expectedtype = 'global'
5164 expectedtype = 'global'
5160
5165
5161 for n in names:
5166 for n in names:
5162 if not repo.tagtype(n):
5167 if not repo.tagtype(n):
5163 raise error.Abort(_("tag '%s' does not exist") % n)
5168 raise error.Abort(_("tag '%s' does not exist") % n)
5164 if repo.tagtype(n) != expectedtype:
5169 if repo.tagtype(n) != expectedtype:
5165 if expectedtype == 'global':
5170 if expectedtype == 'global':
5166 raise error.Abort(_("tag '%s' is not a global tag") % n)
5171 raise error.Abort(_("tag '%s' is not a global tag") % n)
5167 else:
5172 else:
5168 raise error.Abort(_("tag '%s' is not a local tag") % n)
5173 raise error.Abort(_("tag '%s' is not a local tag") % n)
5169 rev_ = 'null'
5174 rev_ = 'null'
5170 if not message:
5175 if not message:
5171 # we don't translate commit messages
5176 # we don't translate commit messages
5172 message = 'Removed tag %s' % ', '.join(names)
5177 message = 'Removed tag %s' % ', '.join(names)
5173 elif not opts.get('force'):
5178 elif not opts.get('force'):
5174 for n in names:
5179 for n in names:
5175 if n in repo.tags():
5180 if n in repo.tags():
5176 raise error.Abort(_("tag '%s' already exists "
5181 raise error.Abort(_("tag '%s' already exists "
5177 "(use -f to force)") % n)
5182 "(use -f to force)") % n)
5178 if not opts.get('local'):
5183 if not opts.get('local'):
5179 p1, p2 = repo.dirstate.parents()
5184 p1, p2 = repo.dirstate.parents()
5180 if p2 != nullid:
5185 if p2 != nullid:
5181 raise error.Abort(_('uncommitted merge'))
5186 raise error.Abort(_('uncommitted merge'))
5182 bheads = repo.branchheads()
5187 bheads = repo.branchheads()
5183 if not opts.get('force') and bheads and p1 not in bheads:
5188 if not opts.get('force') and bheads and p1 not in bheads:
5184 raise error.Abort(_('working directory is not at a branch head '
5189 raise error.Abort(_('working directory is not at a branch head '
5185 '(use -f to force)'))
5190 '(use -f to force)'))
5186 r = scmutil.revsingle(repo, rev_).node()
5191 r = scmutil.revsingle(repo, rev_).node()
5187
5192
5188 if not message:
5193 if not message:
5189 # we don't translate commit messages
5194 # we don't translate commit messages
5190 message = ('Added tag %s for changeset %s' %
5195 message = ('Added tag %s for changeset %s' %
5191 (', '.join(names), short(r)))
5196 (', '.join(names), short(r)))
5192
5197
5193 date = opts.get('date')
5198 date = opts.get('date')
5194 if date:
5199 if date:
5195 date = util.parsedate(date)
5200 date = util.parsedate(date)
5196
5201
5197 if opts.get('remove'):
5202 if opts.get('remove'):
5198 editform = 'tag.remove'
5203 editform = 'tag.remove'
5199 else:
5204 else:
5200 editform = 'tag.add'
5205 editform = 'tag.add'
5201 editor = cmdutil.getcommiteditor(editform=editform,
5206 editor = cmdutil.getcommiteditor(editform=editform,
5202 **pycompat.strkwargs(opts))
5207 **pycompat.strkwargs(opts))
5203
5208
5204 # don't allow tagging the null rev
5209 # don't allow tagging the null rev
5205 if (not opts.get('remove') and
5210 if (not opts.get('remove') and
5206 scmutil.revsingle(repo, rev_).rev() == nullrev):
5211 scmutil.revsingle(repo, rev_).rev() == nullrev):
5207 raise error.Abort(_("cannot tag null revision"))
5212 raise error.Abort(_("cannot tag null revision"))
5208
5213
5209 tagsmod.tag(repo, names, r, message, opts.get('local'),
5214 tagsmod.tag(repo, names, r, message, opts.get('local'),
5210 opts.get('user'), date, editor=editor)
5215 opts.get('user'), date, editor=editor)
5211 finally:
5216 finally:
5212 release(lock, wlock)
5217 release(lock, wlock)
5213
5218
5214 @command('tags', formatteropts, '')
5219 @command('tags', formatteropts, '')
5215 def tags(ui, repo, **opts):
5220 def tags(ui, repo, **opts):
5216 """list repository tags
5221 """list repository tags
5217
5222
5218 This lists both regular and local tags. When the -v/--verbose
5223 This lists both regular and local tags. When the -v/--verbose
5219 switch is used, a third column "local" is printed for local tags.
5224 switch is used, a third column "local" is printed for local tags.
5220 When the -q/--quiet switch is used, only the tag name is printed.
5225 When the -q/--quiet switch is used, only the tag name is printed.
5221
5226
5222 Returns 0 on success.
5227 Returns 0 on success.
5223 """
5228 """
5224
5229
5225 opts = pycompat.byteskwargs(opts)
5230 opts = pycompat.byteskwargs(opts)
5226 ui.pager('tags')
5231 ui.pager('tags')
5227 fm = ui.formatter('tags', opts)
5232 fm = ui.formatter('tags', opts)
5228 hexfunc = fm.hexfunc
5233 hexfunc = fm.hexfunc
5229 tagtype = ""
5234 tagtype = ""
5230
5235
5231 for t, n in reversed(repo.tagslist()):
5236 for t, n in reversed(repo.tagslist()):
5232 hn = hexfunc(n)
5237 hn = hexfunc(n)
5233 label = 'tags.normal'
5238 label = 'tags.normal'
5234 tagtype = ''
5239 tagtype = ''
5235 if repo.tagtype(t) == 'local':
5240 if repo.tagtype(t) == 'local':
5236 label = 'tags.local'
5241 label = 'tags.local'
5237 tagtype = 'local'
5242 tagtype = 'local'
5238
5243
5239 fm.startitem()
5244 fm.startitem()
5240 fm.write('tag', '%s', t, label=label)
5245 fm.write('tag', '%s', t, label=label)
5241 fmt = " " * (30 - encoding.colwidth(t)) + ' %5d:%s'
5246 fmt = " " * (30 - encoding.colwidth(t)) + ' %5d:%s'
5242 fm.condwrite(not ui.quiet, 'rev node', fmt,
5247 fm.condwrite(not ui.quiet, 'rev node', fmt,
5243 repo.changelog.rev(n), hn, label=label)
5248 repo.changelog.rev(n), hn, label=label)
5244 fm.condwrite(ui.verbose and tagtype, 'type', ' %s',
5249 fm.condwrite(ui.verbose and tagtype, 'type', ' %s',
5245 tagtype, label=label)
5250 tagtype, label=label)
5246 fm.plain('\n')
5251 fm.plain('\n')
5247 fm.end()
5252 fm.end()
5248
5253
5249 @command('tip',
5254 @command('tip',
5250 [('p', 'patch', None, _('show patch')),
5255 [('p', 'patch', None, _('show patch')),
5251 ('g', 'git', None, _('use git extended diff format')),
5256 ('g', 'git', None, _('use git extended diff format')),
5252 ] + templateopts,
5257 ] + templateopts,
5253 _('[-p] [-g]'))
5258 _('[-p] [-g]'))
5254 def tip(ui, repo, **opts):
5259 def tip(ui, repo, **opts):
5255 """show the tip revision (DEPRECATED)
5260 """show the tip revision (DEPRECATED)
5256
5261
5257 The tip revision (usually just called the tip) is the changeset
5262 The tip revision (usually just called the tip) is the changeset
5258 most recently added to the repository (and therefore the most
5263 most recently added to the repository (and therefore the most
5259 recently changed head).
5264 recently changed head).
5260
5265
5261 If you have just made a commit, that commit will be the tip. If
5266 If you have just made a commit, that commit will be the tip. If
5262 you have just pulled changes from another repository, the tip of
5267 you have just pulled changes from another repository, the tip of
5263 that repository becomes the current tip. The "tip" tag is special
5268 that repository becomes the current tip. The "tip" tag is special
5264 and cannot be renamed or assigned to a different changeset.
5269 and cannot be renamed or assigned to a different changeset.
5265
5270
5266 This command is deprecated, please use :hg:`heads` instead.
5271 This command is deprecated, please use :hg:`heads` instead.
5267
5272
5268 Returns 0 on success.
5273 Returns 0 on success.
5269 """
5274 """
5270 opts = pycompat.byteskwargs(opts)
5275 opts = pycompat.byteskwargs(opts)
5271 displayer = cmdutil.show_changeset(ui, repo, opts)
5276 displayer = cmdutil.show_changeset(ui, repo, opts)
5272 displayer.show(repo['tip'])
5277 displayer.show(repo['tip'])
5273 displayer.close()
5278 displayer.close()
5274
5279
5275 @command('unbundle',
5280 @command('unbundle',
5276 [('u', 'update', None,
5281 [('u', 'update', None,
5277 _('update to new branch head if changesets were unbundled'))],
5282 _('update to new branch head if changesets were unbundled'))],
5278 _('[-u] FILE...'))
5283 _('[-u] FILE...'))
5279 def unbundle(ui, repo, fname1, *fnames, **opts):
5284 def unbundle(ui, repo, fname1, *fnames, **opts):
5280 """apply one or more bundle files
5285 """apply one or more bundle files
5281
5286
5282 Apply one or more bundle files generated by :hg:`bundle`.
5287 Apply one or more bundle files generated by :hg:`bundle`.
5283
5288
5284 Returns 0 on success, 1 if an update has unresolved files.
5289 Returns 0 on success, 1 if an update has unresolved files.
5285 """
5290 """
5286 fnames = (fname1,) + fnames
5291 fnames = (fname1,) + fnames
5287
5292
5288 with repo.lock():
5293 with repo.lock():
5289 for fname in fnames:
5294 for fname in fnames:
5290 f = hg.openpath(ui, fname)
5295 f = hg.openpath(ui, fname)
5291 gen = exchange.readbundle(ui, f, fname)
5296 gen = exchange.readbundle(ui, f, fname)
5292 if isinstance(gen, bundle2.unbundle20):
5297 if isinstance(gen, bundle2.unbundle20):
5293 tr = repo.transaction('unbundle')
5298 tr = repo.transaction('unbundle')
5294 try:
5299 try:
5295 op = bundle2.applybundle(repo, gen, tr, source='unbundle',
5300 op = bundle2.applybundle(repo, gen, tr, source='unbundle',
5296 url='bundle:' + fname)
5301 url='bundle:' + fname)
5297 tr.close()
5302 tr.close()
5298 except error.BundleUnknownFeatureError as exc:
5303 except error.BundleUnknownFeatureError as exc:
5299 raise error.Abort(_('%s: unknown bundle feature, %s')
5304 raise error.Abort(_('%s: unknown bundle feature, %s')
5300 % (fname, exc),
5305 % (fname, exc),
5301 hint=_("see https://mercurial-scm.org/"
5306 hint=_("see https://mercurial-scm.org/"
5302 "wiki/BundleFeature for more "
5307 "wiki/BundleFeature for more "
5303 "information"))
5308 "information"))
5304 finally:
5309 finally:
5305 if tr:
5310 if tr:
5306 tr.release()
5311 tr.release()
5307 changes = [r.get('return', 0)
5312 changes = [r.get('return', 0)
5308 for r in op.records['changegroup']]
5313 for r in op.records['changegroup']]
5309 modheads = changegroup.combineresults(changes)
5314 modheads = changegroup.combineresults(changes)
5310 elif isinstance(gen, streamclone.streamcloneapplier):
5315 elif isinstance(gen, streamclone.streamcloneapplier):
5311 raise error.Abort(
5316 raise error.Abort(
5312 _('packed bundles cannot be applied with '
5317 _('packed bundles cannot be applied with '
5313 '"hg unbundle"'),
5318 '"hg unbundle"'),
5314 hint=_('use "hg debugapplystreamclonebundle"'))
5319 hint=_('use "hg debugapplystreamclonebundle"'))
5315 else:
5320 else:
5316 modheads = gen.apply(repo, 'unbundle', 'bundle:' + fname)
5321 modheads = gen.apply(repo, 'unbundle', 'bundle:' + fname)
5317
5322
5318 return postincoming(ui, repo, modheads, opts.get(r'update'), None, None)
5323 return postincoming(ui, repo, modheads, opts.get(r'update'), None, None)
5319
5324
5320 @command('^update|up|checkout|co',
5325 @command('^update|up|checkout|co',
5321 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
5326 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
5322 ('c', 'check', None, _('require clean working directory')),
5327 ('c', 'check', None, _('require clean working directory')),
5323 ('m', 'merge', None, _('merge uncommitted changes')),
5328 ('m', 'merge', None, _('merge uncommitted changes')),
5324 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
5329 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
5325 ('r', 'rev', '', _('revision'), _('REV'))
5330 ('r', 'rev', '', _('revision'), _('REV'))
5326 ] + mergetoolopts,
5331 ] + mergetoolopts,
5327 _('[-C|-c|-m] [-d DATE] [[-r] REV]'))
5332 _('[-C|-c|-m] [-d DATE] [[-r] REV]'))
5328 def update(ui, repo, node=None, rev=None, clean=False, date=None, check=False,
5333 def update(ui, repo, node=None, rev=None, clean=False, date=None, check=False,
5329 merge=None, tool=None):
5334 merge=None, tool=None):
5330 """update working directory (or switch revisions)
5335 """update working directory (or switch revisions)
5331
5336
5332 Update the repository's working directory to the specified
5337 Update the repository's working directory to the specified
5333 changeset. If no changeset is specified, update to the tip of the
5338 changeset. If no changeset is specified, update to the tip of the
5334 current named branch and move the active bookmark (see :hg:`help
5339 current named branch and move the active bookmark (see :hg:`help
5335 bookmarks`).
5340 bookmarks`).
5336
5341
5337 Update sets the working directory's parent revision to the specified
5342 Update sets the working directory's parent revision to the specified
5338 changeset (see :hg:`help parents`).
5343 changeset (see :hg:`help parents`).
5339
5344
5340 If the changeset is not a descendant or ancestor of the working
5345 If the changeset is not a descendant or ancestor of the working
5341 directory's parent and there are uncommitted changes, the update is
5346 directory's parent and there are uncommitted changes, the update is
5342 aborted. With the -c/--check option, the working directory is checked
5347 aborted. With the -c/--check option, the working directory is checked
5343 for uncommitted changes; if none are found, the working directory is
5348 for uncommitted changes; if none are found, the working directory is
5344 updated to the specified changeset.
5349 updated to the specified changeset.
5345
5350
5346 .. container:: verbose
5351 .. container:: verbose
5347
5352
5348 The -C/--clean, -c/--check, and -m/--merge options control what
5353 The -C/--clean, -c/--check, and -m/--merge options control what
5349 happens if the working directory contains uncommitted changes.
5354 happens if the working directory contains uncommitted changes.
5350 At most of one of them can be specified.
5355 At most of one of them can be specified.
5351
5356
5352 1. If no option is specified, and if
5357 1. If no option is specified, and if
5353 the requested changeset is an ancestor or descendant of
5358 the requested changeset is an ancestor or descendant of
5354 the working directory's parent, the uncommitted changes
5359 the working directory's parent, the uncommitted changes
5355 are merged into the requested changeset and the merged
5360 are merged into the requested changeset and the merged
5356 result is left uncommitted. If the requested changeset is
5361 result is left uncommitted. If the requested changeset is
5357 not an ancestor or descendant (that is, it is on another
5362 not an ancestor or descendant (that is, it is on another
5358 branch), the update is aborted and the uncommitted changes
5363 branch), the update is aborted and the uncommitted changes
5359 are preserved.
5364 are preserved.
5360
5365
5361 2. With the -m/--merge option, the update is allowed even if the
5366 2. With the -m/--merge option, the update is allowed even if the
5362 requested changeset is not an ancestor or descendant of
5367 requested changeset is not an ancestor or descendant of
5363 the working directory's parent.
5368 the working directory's parent.
5364
5369
5365 3. With the -c/--check option, the update is aborted and the
5370 3. With the -c/--check option, the update is aborted and the
5366 uncommitted changes are preserved.
5371 uncommitted changes are preserved.
5367
5372
5368 4. With the -C/--clean option, uncommitted changes are discarded and
5373 4. With the -C/--clean option, uncommitted changes are discarded and
5369 the working directory is updated to the requested changeset.
5374 the working directory is updated to the requested changeset.
5370
5375
5371 To cancel an uncommitted merge (and lose your changes), use
5376 To cancel an uncommitted merge (and lose your changes), use
5372 :hg:`update --clean .`.
5377 :hg:`update --clean .`.
5373
5378
5374 Use null as the changeset to remove the working directory (like
5379 Use null as the changeset to remove the working directory (like
5375 :hg:`clone -U`).
5380 :hg:`clone -U`).
5376
5381
5377 If you want to revert just one file to an older revision, use
5382 If you want to revert just one file to an older revision, use
5378 :hg:`revert [-r REV] NAME`.
5383 :hg:`revert [-r REV] NAME`.
5379
5384
5380 See :hg:`help dates` for a list of formats valid for -d/--date.
5385 See :hg:`help dates` for a list of formats valid for -d/--date.
5381
5386
5382 Returns 0 on success, 1 if there are unresolved files.
5387 Returns 0 on success, 1 if there are unresolved files.
5383 """
5388 """
5384 if rev and node:
5389 if rev and node:
5385 raise error.Abort(_("please specify just one revision"))
5390 raise error.Abort(_("please specify just one revision"))
5386
5391
5387 if ui.configbool('commands', 'update.requiredest'):
5392 if ui.configbool('commands', 'update.requiredest'):
5388 if not node and not rev and not date:
5393 if not node and not rev and not date:
5389 raise error.Abort(_('you must specify a destination'),
5394 raise error.Abort(_('you must specify a destination'),
5390 hint=_('for example: hg update ".::"'))
5395 hint=_('for example: hg update ".::"'))
5391
5396
5392 if rev is None or rev == '':
5397 if rev is None or rev == '':
5393 rev = node
5398 rev = node
5394
5399
5395 if date and rev is not None:
5400 if date and rev is not None:
5396 raise error.Abort(_("you can't specify a revision and a date"))
5401 raise error.Abort(_("you can't specify a revision and a date"))
5397
5402
5398 if len([x for x in (clean, check, merge) if x]) > 1:
5403 if len([x for x in (clean, check, merge) if x]) > 1:
5399 raise error.Abort(_("can only specify one of -C/--clean, -c/--check, "
5404 raise error.Abort(_("can only specify one of -C/--clean, -c/--check, "
5400 "or -m/merge"))
5405 "or -m/merge"))
5401
5406
5402 updatecheck = None
5407 updatecheck = None
5403 if check:
5408 if check:
5404 updatecheck = 'abort'
5409 updatecheck = 'abort'
5405 elif merge:
5410 elif merge:
5406 updatecheck = 'none'
5411 updatecheck = 'none'
5407
5412
5408 with repo.wlock():
5413 with repo.wlock():
5409 cmdutil.clearunfinished(repo)
5414 cmdutil.clearunfinished(repo)
5410
5415
5411 if date:
5416 if date:
5412 rev = cmdutil.finddate(ui, repo, date)
5417 rev = cmdutil.finddate(ui, repo, date)
5413
5418
5414 # if we defined a bookmark, we have to remember the original name
5419 # if we defined a bookmark, we have to remember the original name
5415 brev = rev
5420 brev = rev
5416 rev = scmutil.revsingle(repo, rev, rev).rev()
5421 rev = scmutil.revsingle(repo, rev, rev).rev()
5417
5422
5418 repo.ui.setconfig('ui', 'forcemerge', tool, 'update')
5423 repo.ui.setconfig('ui', 'forcemerge', tool, 'update')
5419
5424
5420 return hg.updatetotally(ui, repo, rev, brev, clean=clean,
5425 return hg.updatetotally(ui, repo, rev, brev, clean=clean,
5421 updatecheck=updatecheck)
5426 updatecheck=updatecheck)
5422
5427
5423 @command('verify', [])
5428 @command('verify', [])
5424 def verify(ui, repo):
5429 def verify(ui, repo):
5425 """verify the integrity of the repository
5430 """verify the integrity of the repository
5426
5431
5427 Verify the integrity of the current repository.
5432 Verify the integrity of the current repository.
5428
5433
5429 This will perform an extensive check of the repository's
5434 This will perform an extensive check of the repository's
5430 integrity, validating the hashes and checksums of each entry in
5435 integrity, validating the hashes and checksums of each entry in
5431 the changelog, manifest, and tracked files, as well as the
5436 the changelog, manifest, and tracked files, as well as the
5432 integrity of their crosslinks and indices.
5437 integrity of their crosslinks and indices.
5433
5438
5434 Please see https://mercurial-scm.org/wiki/RepositoryCorruption
5439 Please see https://mercurial-scm.org/wiki/RepositoryCorruption
5435 for more information about recovery from corruption of the
5440 for more information about recovery from corruption of the
5436 repository.
5441 repository.
5437
5442
5438 Returns 0 on success, 1 if errors are encountered.
5443 Returns 0 on success, 1 if errors are encountered.
5439 """
5444 """
5440 return hg.verify(repo)
5445 return hg.verify(repo)
5441
5446
5442 @command('version', [] + formatteropts, norepo=True)
5447 @command('version', [] + formatteropts, norepo=True)
5443 def version_(ui, **opts):
5448 def version_(ui, **opts):
5444 """output version and copyright information"""
5449 """output version and copyright information"""
5445 opts = pycompat.byteskwargs(opts)
5450 opts = pycompat.byteskwargs(opts)
5446 if ui.verbose:
5451 if ui.verbose:
5447 ui.pager('version')
5452 ui.pager('version')
5448 fm = ui.formatter("version", opts)
5453 fm = ui.formatter("version", opts)
5449 fm.startitem()
5454 fm.startitem()
5450 fm.write("ver", _("Mercurial Distributed SCM (version %s)\n"),
5455 fm.write("ver", _("Mercurial Distributed SCM (version %s)\n"),
5451 util.version())
5456 util.version())
5452 license = _(
5457 license = _(
5453 "(see https://mercurial-scm.org for more information)\n"
5458 "(see https://mercurial-scm.org for more information)\n"
5454 "\nCopyright (C) 2005-2017 Matt Mackall and others\n"
5459 "\nCopyright (C) 2005-2017 Matt Mackall and others\n"
5455 "This is free software; see the source for copying conditions. "
5460 "This is free software; see the source for copying conditions. "
5456 "There is NO\nwarranty; "
5461 "There is NO\nwarranty; "
5457 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
5462 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
5458 )
5463 )
5459 if not ui.quiet:
5464 if not ui.quiet:
5460 fm.plain(license)
5465 fm.plain(license)
5461
5466
5462 if ui.verbose:
5467 if ui.verbose:
5463 fm.plain(_("\nEnabled extensions:\n\n"))
5468 fm.plain(_("\nEnabled extensions:\n\n"))
5464 # format names and versions into columns
5469 # format names and versions into columns
5465 names = []
5470 names = []
5466 vers = []
5471 vers = []
5467 isinternals = []
5472 isinternals = []
5468 for name, module in extensions.extensions():
5473 for name, module in extensions.extensions():
5469 names.append(name)
5474 names.append(name)
5470 vers.append(extensions.moduleversion(module) or None)
5475 vers.append(extensions.moduleversion(module) or None)
5471 isinternals.append(extensions.ismoduleinternal(module))
5476 isinternals.append(extensions.ismoduleinternal(module))
5472 fn = fm.nested("extensions")
5477 fn = fm.nested("extensions")
5473 if names:
5478 if names:
5474 namefmt = " %%-%ds " % max(len(n) for n in names)
5479 namefmt = " %%-%ds " % max(len(n) for n in names)
5475 places = [_("external"), _("internal")]
5480 places = [_("external"), _("internal")]
5476 for n, v, p in zip(names, vers, isinternals):
5481 for n, v, p in zip(names, vers, isinternals):
5477 fn.startitem()
5482 fn.startitem()
5478 fn.condwrite(ui.verbose, "name", namefmt, n)
5483 fn.condwrite(ui.verbose, "name", namefmt, n)
5479 if ui.verbose:
5484 if ui.verbose:
5480 fn.plain("%s " % places[p])
5485 fn.plain("%s " % places[p])
5481 fn.data(bundled=p)
5486 fn.data(bundled=p)
5482 fn.condwrite(ui.verbose and v, "ver", "%s", v)
5487 fn.condwrite(ui.verbose and v, "ver", "%s", v)
5483 if ui.verbose:
5488 if ui.verbose:
5484 fn.plain("\n")
5489 fn.plain("\n")
5485 fn.end()
5490 fn.end()
5486 fm.end()
5491 fm.end()
5487
5492
5488 def loadcmdtable(ui, name, cmdtable):
5493 def loadcmdtable(ui, name, cmdtable):
5489 """Load command functions from specified cmdtable
5494 """Load command functions from specified cmdtable
5490 """
5495 """
5491 overrides = [cmd for cmd in cmdtable if cmd in table]
5496 overrides = [cmd for cmd in cmdtable if cmd in table]
5492 if overrides:
5497 if overrides:
5493 ui.warn(_("extension '%s' overrides commands: %s\n")
5498 ui.warn(_("extension '%s' overrides commands: %s\n")
5494 % (name, " ".join(overrides)))
5499 % (name, " ".join(overrides)))
5495 table.update(cmdtable)
5500 table.update(cmdtable)
@@ -1,1988 +1,1989 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, fntemplate, prefix, **opts):
541 def cat(self, match, fm, 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, fntemplate, prefix, **opts):
770 def cat(self, match, fm, 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, fntemplate, prefix,
773 return cmdutil.cat(self.ui, self._repo, ctx, match, fm, fntemplate,
774 **opts)
774 prefix, **opts)
775
775
776 @annotatesubrepoerror
776 @annotatesubrepoerror
777 def status(self, rev2, **opts):
777 def status(self, rev2, **opts):
778 try:
778 try:
779 rev1 = self._state[1]
779 rev1 = self._state[1]
780 ctx1 = self._repo[rev1]
780 ctx1 = self._repo[rev1]
781 ctx2 = self._repo[rev2]
781 ctx2 = self._repo[rev2]
782 return self._repo.status(ctx1, ctx2, **opts)
782 return self._repo.status(ctx1, ctx2, **opts)
783 except error.RepoLookupError as inst:
783 except error.RepoLookupError as inst:
784 self.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
784 self.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
785 % (inst, subrelpath(self)))
785 % (inst, subrelpath(self)))
786 return scmutil.status([], [], [], [], [], [], [])
786 return scmutil.status([], [], [], [], [], [], [])
787
787
788 @annotatesubrepoerror
788 @annotatesubrepoerror
789 def diff(self, ui, diffopts, node2, match, prefix, **opts):
789 def diff(self, ui, diffopts, node2, match, prefix, **opts):
790 try:
790 try:
791 node1 = node.bin(self._state[1])
791 node1 = node.bin(self._state[1])
792 # We currently expect node2 to come from substate and be
792 # We currently expect node2 to come from substate and be
793 # in hex format
793 # in hex format
794 if node2 is not None:
794 if node2 is not None:
795 node2 = node.bin(node2)
795 node2 = node.bin(node2)
796 cmdutil.diffordiffstat(ui, self._repo, diffopts,
796 cmdutil.diffordiffstat(ui, self._repo, diffopts,
797 node1, node2, match,
797 node1, node2, match,
798 prefix=posixpath.join(prefix, self._path),
798 prefix=posixpath.join(prefix, self._path),
799 listsubrepos=True, **opts)
799 listsubrepos=True, **opts)
800 except error.RepoLookupError as inst:
800 except error.RepoLookupError as inst:
801 self.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
801 self.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
802 % (inst, subrelpath(self)))
802 % (inst, subrelpath(self)))
803
803
804 @annotatesubrepoerror
804 @annotatesubrepoerror
805 def archive(self, archiver, prefix, match=None, decode=True):
805 def archive(self, archiver, prefix, match=None, decode=True):
806 self._get(self._state + ('hg',))
806 self._get(self._state + ('hg',))
807 total = abstractsubrepo.archive(self, archiver, prefix, match)
807 total = abstractsubrepo.archive(self, archiver, prefix, match)
808 rev = self._state[1]
808 rev = self._state[1]
809 ctx = self._repo[rev]
809 ctx = self._repo[rev]
810 for subpath in ctx.substate:
810 for subpath in ctx.substate:
811 s = subrepo(ctx, subpath, True)
811 s = subrepo(ctx, subpath, True)
812 submatch = matchmod.subdirmatcher(subpath, match)
812 submatch = matchmod.subdirmatcher(subpath, match)
813 total += s.archive(archiver, prefix + self._path + '/', submatch,
813 total += s.archive(archiver, prefix + self._path + '/', submatch,
814 decode)
814 decode)
815 return total
815 return total
816
816
817 @annotatesubrepoerror
817 @annotatesubrepoerror
818 def dirty(self, ignoreupdate=False):
818 def dirty(self, ignoreupdate=False):
819 r = self._state[1]
819 r = self._state[1]
820 if r == '' and not ignoreupdate: # no state recorded
820 if r == '' and not ignoreupdate: # no state recorded
821 return True
821 return True
822 w = self._repo[None]
822 w = self._repo[None]
823 if r != w.p1().hex() and not ignoreupdate:
823 if r != w.p1().hex() and not ignoreupdate:
824 # different version checked out
824 # different version checked out
825 return True
825 return True
826 return w.dirty() # working directory changed
826 return w.dirty() # working directory changed
827
827
828 def basestate(self):
828 def basestate(self):
829 return self._repo['.'].hex()
829 return self._repo['.'].hex()
830
830
831 def checknested(self, path):
831 def checknested(self, path):
832 return self._repo._checknested(self._repo.wjoin(path))
832 return self._repo._checknested(self._repo.wjoin(path))
833
833
834 @annotatesubrepoerror
834 @annotatesubrepoerror
835 def commit(self, text, user, date):
835 def commit(self, text, user, date):
836 # 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
837 # updated
837 # updated
838 if not self.dirty(True):
838 if not self.dirty(True):
839 return self._repo['.'].hex()
839 return self._repo['.'].hex()
840 self.ui.debug("committing subrepo %s\n" % subrelpath(self))
840 self.ui.debug("committing subrepo %s\n" % subrelpath(self))
841 n = self._repo.commit(text, user, date)
841 n = self._repo.commit(text, user, date)
842 if not n:
842 if not n:
843 return self._repo['.'].hex() # different version checked out
843 return self._repo['.'].hex() # different version checked out
844 return node.hex(n)
844 return node.hex(n)
845
845
846 @annotatesubrepoerror
846 @annotatesubrepoerror
847 def phase(self, state):
847 def phase(self, state):
848 return self._repo[state].phase()
848 return self._repo[state].phase()
849
849
850 @annotatesubrepoerror
850 @annotatesubrepoerror
851 def remove(self):
851 def remove(self):
852 # we can't fully delete the repository as it may contain
852 # we can't fully delete the repository as it may contain
853 # local-only history
853 # local-only history
854 self.ui.note(_('removing subrepo %s\n') % subrelpath(self))
854 self.ui.note(_('removing subrepo %s\n') % subrelpath(self))
855 hg.clean(self._repo, node.nullid, False)
855 hg.clean(self._repo, node.nullid, False)
856
856
857 def _get(self, state):
857 def _get(self, state):
858 source, revision, kind = state
858 source, revision, kind = state
859 if revision in self._repo.unfiltered():
859 if revision in self._repo.unfiltered():
860 return True
860 return True
861 self._repo._subsource = source
861 self._repo._subsource = source
862 srcurl = _abssource(self._repo)
862 srcurl = _abssource(self._repo)
863 other = hg.peer(self._repo, {}, srcurl)
863 other = hg.peer(self._repo, {}, srcurl)
864 if len(self._repo) == 0:
864 if len(self._repo) == 0:
865 self.ui.status(_('cloning subrepo %s from %s\n')
865 self.ui.status(_('cloning subrepo %s from %s\n')
866 % (subrelpath(self), srcurl))
866 % (subrelpath(self), srcurl))
867 parentrepo = self._repo._subparent
867 parentrepo = self._repo._subparent
868 # 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
869 self._repo.vfs.rmtree()
869 self._repo.vfs.rmtree()
870 other, cloned = hg.clone(self._repo._subparent.baseui, {},
870 other, cloned = hg.clone(self._repo._subparent.baseui, {},
871 other, self._repo.root,
871 other, self._repo.root,
872 update=False)
872 update=False)
873 self._repo = cloned.local()
873 self._repo = cloned.local()
874 self._initrepo(parentrepo, source, create=True)
874 self._initrepo(parentrepo, source, create=True)
875 self._cachestorehash(srcurl)
875 self._cachestorehash(srcurl)
876 else:
876 else:
877 self.ui.status(_('pulling subrepo %s from %s\n')
877 self.ui.status(_('pulling subrepo %s from %s\n')
878 % (subrelpath(self), srcurl))
878 % (subrelpath(self), srcurl))
879 cleansub = self.storeclean(srcurl)
879 cleansub = self.storeclean(srcurl)
880 exchange.pull(self._repo, other)
880 exchange.pull(self._repo, other)
881 if cleansub:
881 if cleansub:
882 # keep the repo clean after pull
882 # keep the repo clean after pull
883 self._cachestorehash(srcurl)
883 self._cachestorehash(srcurl)
884 return False
884 return False
885
885
886 @annotatesubrepoerror
886 @annotatesubrepoerror
887 def get(self, state, overwrite=False):
887 def get(self, state, overwrite=False):
888 inrepo = self._get(state)
888 inrepo = self._get(state)
889 source, revision, kind = state
889 source, revision, kind = state
890 repo = self._repo
890 repo = self._repo
891 repo.ui.debug("getting subrepo %s\n" % self._path)
891 repo.ui.debug("getting subrepo %s\n" % self._path)
892 if inrepo:
892 if inrepo:
893 urepo = repo.unfiltered()
893 urepo = repo.unfiltered()
894 ctx = urepo[revision]
894 ctx = urepo[revision]
895 if ctx.hidden():
895 if ctx.hidden():
896 urepo.ui.warn(
896 urepo.ui.warn(
897 _('revision %s in subrepo %s is hidden\n') \
897 _('revision %s in subrepo %s is hidden\n') \
898 % (revision[0:12], self._path))
898 % (revision[0:12], self._path))
899 repo = urepo
899 repo = urepo
900 hg.updaterepo(repo, revision, overwrite)
900 hg.updaterepo(repo, revision, overwrite)
901
901
902 @annotatesubrepoerror
902 @annotatesubrepoerror
903 def merge(self, state):
903 def merge(self, state):
904 self._get(state)
904 self._get(state)
905 cur = self._repo['.']
905 cur = self._repo['.']
906 dst = self._repo[state[1]]
906 dst = self._repo[state[1]]
907 anc = dst.ancestor(cur)
907 anc = dst.ancestor(cur)
908
908
909 def mergefunc():
909 def mergefunc():
910 if anc == cur and dst.branch() == cur.branch():
910 if anc == cur and dst.branch() == cur.branch():
911 self.ui.debug("updating subrepo %s\n" % subrelpath(self))
911 self.ui.debug("updating subrepo %s\n" % subrelpath(self))
912 hg.update(self._repo, state[1])
912 hg.update(self._repo, state[1])
913 elif anc == dst:
913 elif anc == dst:
914 self.ui.debug("skipping subrepo %s\n" % subrelpath(self))
914 self.ui.debug("skipping subrepo %s\n" % subrelpath(self))
915 else:
915 else:
916 self.ui.debug("merging subrepo %s\n" % subrelpath(self))
916 self.ui.debug("merging subrepo %s\n" % subrelpath(self))
917 hg.merge(self._repo, state[1], remind=False)
917 hg.merge(self._repo, state[1], remind=False)
918
918
919 wctx = self._repo[None]
919 wctx = self._repo[None]
920 if self.dirty():
920 if self.dirty():
921 if anc != dst:
921 if anc != dst:
922 if _updateprompt(self.ui, self, wctx.dirty(), cur, dst):
922 if _updateprompt(self.ui, self, wctx.dirty(), cur, dst):
923 mergefunc()
923 mergefunc()
924 else:
924 else:
925 mergefunc()
925 mergefunc()
926 else:
926 else:
927 mergefunc()
927 mergefunc()
928
928
929 @annotatesubrepoerror
929 @annotatesubrepoerror
930 def push(self, opts):
930 def push(self, opts):
931 force = opts.get('force')
931 force = opts.get('force')
932 newbranch = opts.get('new_branch')
932 newbranch = opts.get('new_branch')
933 ssh = opts.get('ssh')
933 ssh = opts.get('ssh')
934
934
935 # push subrepos depth-first for coherent ordering
935 # push subrepos depth-first for coherent ordering
936 c = self._repo['']
936 c = self._repo['']
937 subs = c.substate # only repos that are committed
937 subs = c.substate # only repos that are committed
938 for s in sorted(subs):
938 for s in sorted(subs):
939 if c.sub(s).push(opts) == 0:
939 if c.sub(s).push(opts) == 0:
940 return False
940 return False
941
941
942 dsturl = _abssource(self._repo, True)
942 dsturl = _abssource(self._repo, True)
943 if not force:
943 if not force:
944 if self.storeclean(dsturl):
944 if self.storeclean(dsturl):
945 self.ui.status(
945 self.ui.status(
946 _('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')
947 % (subrelpath(self), dsturl))
947 % (subrelpath(self), dsturl))
948 return None
948 return None
949 self.ui.status(_('pushing subrepo %s to %s\n') %
949 self.ui.status(_('pushing subrepo %s to %s\n') %
950 (subrelpath(self), dsturl))
950 (subrelpath(self), dsturl))
951 other = hg.peer(self._repo, {'ssh': ssh}, dsturl)
951 other = hg.peer(self._repo, {'ssh': ssh}, dsturl)
952 res = exchange.push(self._repo, other, force, newbranch=newbranch)
952 res = exchange.push(self._repo, other, force, newbranch=newbranch)
953
953
954 # the repo is now clean
954 # the repo is now clean
955 self._cachestorehash(dsturl)
955 self._cachestorehash(dsturl)
956 return res.cgresult
956 return res.cgresult
957
957
958 @annotatesubrepoerror
958 @annotatesubrepoerror
959 def outgoing(self, ui, dest, opts):
959 def outgoing(self, ui, dest, opts):
960 if 'rev' in opts or 'branch' in opts:
960 if 'rev' in opts or 'branch' in opts:
961 opts = copy.copy(opts)
961 opts = copy.copy(opts)
962 opts.pop('rev', None)
962 opts.pop('rev', None)
963 opts.pop('branch', None)
963 opts.pop('branch', None)
964 return hg.outgoing(ui, self._repo, _abssource(self._repo, True), opts)
964 return hg.outgoing(ui, self._repo, _abssource(self._repo, True), opts)
965
965
966 @annotatesubrepoerror
966 @annotatesubrepoerror
967 def incoming(self, ui, source, opts):
967 def incoming(self, ui, source, opts):
968 if 'rev' in opts or 'branch' in opts:
968 if 'rev' in opts or 'branch' in opts:
969 opts = copy.copy(opts)
969 opts = copy.copy(opts)
970 opts.pop('rev', None)
970 opts.pop('rev', None)
971 opts.pop('branch', None)
971 opts.pop('branch', None)
972 return hg.incoming(ui, self._repo, _abssource(self._repo, False), opts)
972 return hg.incoming(ui, self._repo, _abssource(self._repo, False), opts)
973
973
974 @annotatesubrepoerror
974 @annotatesubrepoerror
975 def files(self):
975 def files(self):
976 rev = self._state[1]
976 rev = self._state[1]
977 ctx = self._repo[rev]
977 ctx = self._repo[rev]
978 return ctx.manifest().keys()
978 return ctx.manifest().keys()
979
979
980 def filedata(self, name, decode):
980 def filedata(self, name, decode):
981 rev = self._state[1]
981 rev = self._state[1]
982 data = self._repo[rev][name].data()
982 data = self._repo[rev][name].data()
983 if decode:
983 if decode:
984 data = self._repo.wwritedata(name, data)
984 data = self._repo.wwritedata(name, data)
985 return data
985 return data
986
986
987 def fileflags(self, name):
987 def fileflags(self, name):
988 rev = self._state[1]
988 rev = self._state[1]
989 ctx = self._repo[rev]
989 ctx = self._repo[rev]
990 return ctx.flags(name)
990 return ctx.flags(name)
991
991
992 @annotatesubrepoerror
992 @annotatesubrepoerror
993 def printfiles(self, ui, m, fm, fmt, subrepos):
993 def printfiles(self, ui, m, fm, fmt, subrepos):
994 # 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
995 # consistency.
995 # consistency.
996 if self._ctx.rev() is None:
996 if self._ctx.rev() is None:
997 ctx = self._repo[None]
997 ctx = self._repo[None]
998 else:
998 else:
999 rev = self._state[1]
999 rev = self._state[1]
1000 ctx = self._repo[rev]
1000 ctx = self._repo[rev]
1001 return cmdutil.files(ui, ctx, m, fm, fmt, subrepos)
1001 return cmdutil.files(ui, ctx, m, fm, fmt, subrepos)
1002
1002
1003 @annotatesubrepoerror
1003 @annotatesubrepoerror
1004 def getfileset(self, expr):
1004 def getfileset(self, expr):
1005 if self._ctx.rev() is None:
1005 if self._ctx.rev() is None:
1006 ctx = self._repo[None]
1006 ctx = self._repo[None]
1007 else:
1007 else:
1008 rev = self._state[1]
1008 rev = self._state[1]
1009 ctx = self._repo[rev]
1009 ctx = self._repo[rev]
1010
1010
1011 files = ctx.getfileset(expr)
1011 files = ctx.getfileset(expr)
1012
1012
1013 for subpath in ctx.substate:
1013 for subpath in ctx.substate:
1014 sub = ctx.sub(subpath)
1014 sub = ctx.sub(subpath)
1015
1015
1016 try:
1016 try:
1017 files.extend(subpath + '/' + f for f in sub.getfileset(expr))
1017 files.extend(subpath + '/' + f for f in sub.getfileset(expr))
1018 except error.LookupError:
1018 except error.LookupError:
1019 self.ui.status(_("skipping missing subrepository: %s\n")
1019 self.ui.status(_("skipping missing subrepository: %s\n")
1020 % self.wvfs.reljoin(reporelpath(self), subpath))
1020 % self.wvfs.reljoin(reporelpath(self), subpath))
1021 return files
1021 return files
1022
1022
1023 def walk(self, match):
1023 def walk(self, match):
1024 ctx = self._repo[None]
1024 ctx = self._repo[None]
1025 return ctx.walk(match)
1025 return ctx.walk(match)
1026
1026
1027 @annotatesubrepoerror
1027 @annotatesubrepoerror
1028 def forget(self, match, prefix):
1028 def forget(self, match, prefix):
1029 return cmdutil.forget(self.ui, self._repo, match,
1029 return cmdutil.forget(self.ui, self._repo, match,
1030 self.wvfs.reljoin(prefix, self._path), True)
1030 self.wvfs.reljoin(prefix, self._path), True)
1031
1031
1032 @annotatesubrepoerror
1032 @annotatesubrepoerror
1033 def removefiles(self, matcher, prefix, after, force, subrepos, warnings):
1033 def removefiles(self, matcher, prefix, after, force, subrepos, warnings):
1034 return cmdutil.remove(self.ui, self._repo, matcher,
1034 return cmdutil.remove(self.ui, self._repo, matcher,
1035 self.wvfs.reljoin(prefix, self._path),
1035 self.wvfs.reljoin(prefix, self._path),
1036 after, force, subrepos)
1036 after, force, subrepos)
1037
1037
1038 @annotatesubrepoerror
1038 @annotatesubrepoerror
1039 def revert(self, substate, *pats, **opts):
1039 def revert(self, substate, *pats, **opts):
1040 # reverting a subrepo is a 2 step process:
1040 # reverting a subrepo is a 2 step process:
1041 # 1. if the no_backup is not set, revert all modified
1041 # 1. if the no_backup is not set, revert all modified
1042 # files inside the subrepo
1042 # files inside the subrepo
1043 # 2. update the subrepo to the revision specified in
1043 # 2. update the subrepo to the revision specified in
1044 # the corresponding substate dictionary
1044 # the corresponding substate dictionary
1045 self.ui.status(_('reverting subrepo %s\n') % substate[0])
1045 self.ui.status(_('reverting subrepo %s\n') % substate[0])
1046 if not opts.get('no_backup'):
1046 if not opts.get('no_backup'):
1047 # Revert all files on the subrepo, creating backups
1047 # Revert all files on the subrepo, creating backups
1048 # Note that this will not recursively revert subrepos
1048 # Note that this will not recursively revert subrepos
1049 # We could do it if there was a set:subrepos() predicate
1049 # We could do it if there was a set:subrepos() predicate
1050 opts = opts.copy()
1050 opts = opts.copy()
1051 opts['date'] = None
1051 opts['date'] = None
1052 opts['rev'] = substate[1]
1052 opts['rev'] = substate[1]
1053
1053
1054 self.filerevert(*pats, **opts)
1054 self.filerevert(*pats, **opts)
1055
1055
1056 # Update the repo to the revision specified in the given substate
1056 # Update the repo to the revision specified in the given substate
1057 if not opts.get('dry_run'):
1057 if not opts.get('dry_run'):
1058 self.get(substate, overwrite=True)
1058 self.get(substate, overwrite=True)
1059
1059
1060 def filerevert(self, *pats, **opts):
1060 def filerevert(self, *pats, **opts):
1061 ctx = self._repo[opts['rev']]
1061 ctx = self._repo[opts['rev']]
1062 parents = self._repo.dirstate.parents()
1062 parents = self._repo.dirstate.parents()
1063 if opts.get('all'):
1063 if opts.get('all'):
1064 pats = ['set:modified()']
1064 pats = ['set:modified()']
1065 else:
1065 else:
1066 pats = []
1066 pats = []
1067 cmdutil.revert(self.ui, self._repo, ctx, parents, *pats, **opts)
1067 cmdutil.revert(self.ui, self._repo, ctx, parents, *pats, **opts)
1068
1068
1069 def shortid(self, revid):
1069 def shortid(self, revid):
1070 return revid[:12]
1070 return revid[:12]
1071
1071
1072 def verify(self):
1072 def verify(self):
1073 try:
1073 try:
1074 rev = self._state[1]
1074 rev = self._state[1]
1075 ctx = self._repo.unfiltered()[rev]
1075 ctx = self._repo.unfiltered()[rev]
1076 if ctx.hidden():
1076 if ctx.hidden():
1077 # Since hidden revisions aren't pushed/pulled, it seems worth an
1077 # Since hidden revisions aren't pushed/pulled, it seems worth an
1078 # explicit warning.
1078 # explicit warning.
1079 ui = self._repo.ui
1079 ui = self._repo.ui
1080 ui.warn(_("subrepo '%s' is hidden in revision %s\n") %
1080 ui.warn(_("subrepo '%s' is hidden in revision %s\n") %
1081 (self._relpath, node.short(self._ctx.node())))
1081 (self._relpath, node.short(self._ctx.node())))
1082 return 0
1082 return 0
1083 except error.RepoLookupError:
1083 except error.RepoLookupError:
1084 # 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
1085 # don't treat this as an error.
1085 # don't treat this as an error.
1086 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") %
1087 (self._relpath, node.short(self._ctx.node())))
1087 (self._relpath, node.short(self._ctx.node())))
1088 return 0
1088 return 0
1089
1089
1090 @propertycache
1090 @propertycache
1091 def wvfs(self):
1091 def wvfs(self):
1092 """return own wvfs for efficiency and consistency
1092 """return own wvfs for efficiency and consistency
1093 """
1093 """
1094 return self._repo.wvfs
1094 return self._repo.wvfs
1095
1095
1096 @propertycache
1096 @propertycache
1097 def _relpath(self):
1097 def _relpath(self):
1098 """return path to this subrepository as seen from outermost repository
1098 """return path to this subrepository as seen from outermost repository
1099 """
1099 """
1100 # Keep consistent dir separators by avoiding vfs.join(self._path)
1100 # Keep consistent dir separators by avoiding vfs.join(self._path)
1101 return reporelpath(self._repo)
1101 return reporelpath(self._repo)
1102
1102
1103 class svnsubrepo(abstractsubrepo):
1103 class svnsubrepo(abstractsubrepo):
1104 def __init__(self, ctx, path, state, allowcreate):
1104 def __init__(self, ctx, path, state, allowcreate):
1105 super(svnsubrepo, self).__init__(ctx, path)
1105 super(svnsubrepo, self).__init__(ctx, path)
1106 self._state = state
1106 self._state = state
1107 self._exe = util.findexe('svn')
1107 self._exe = util.findexe('svn')
1108 if not self._exe:
1108 if not self._exe:
1109 raise error.Abort(_("'svn' executable not found for subrepo '%s'")
1109 raise error.Abort(_("'svn' executable not found for subrepo '%s'")
1110 % self._path)
1110 % self._path)
1111
1111
1112 def _svncommand(self, commands, filename='', failok=False):
1112 def _svncommand(self, commands, filename='', failok=False):
1113 cmd = [self._exe]
1113 cmd = [self._exe]
1114 extrakw = {}
1114 extrakw = {}
1115 if not self.ui.interactive():
1115 if not self.ui.interactive():
1116 # Making stdin be a pipe should prevent svn from behaving
1116 # Making stdin be a pipe should prevent svn from behaving
1117 # interactively even if we can't pass --non-interactive.
1117 # interactively even if we can't pass --non-interactive.
1118 extrakw['stdin'] = subprocess.PIPE
1118 extrakw['stdin'] = subprocess.PIPE
1119 # Starting in svn 1.5 --non-interactive is a global flag
1119 # Starting in svn 1.5 --non-interactive is a global flag
1120 # 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
1121 # we have to be intelligent about what commands take
1121 # we have to be intelligent about what commands take
1122 # --non-interactive.
1122 # --non-interactive.
1123 if commands[0] in ('update', 'checkout', 'commit'):
1123 if commands[0] in ('update', 'checkout', 'commit'):
1124 cmd.append('--non-interactive')
1124 cmd.append('--non-interactive')
1125 cmd.extend(commands)
1125 cmd.extend(commands)
1126 if filename is not None:
1126 if filename is not None:
1127 path = self.wvfs.reljoin(self._ctx.repo().origroot,
1127 path = self.wvfs.reljoin(self._ctx.repo().origroot,
1128 self._path, filename)
1128 self._path, filename)
1129 cmd.append(path)
1129 cmd.append(path)
1130 env = dict(encoding.environ)
1130 env = dict(encoding.environ)
1131 # Avoid localized output, preserve current locale for everything else.
1131 # Avoid localized output, preserve current locale for everything else.
1132 lc_all = env.get('LC_ALL')
1132 lc_all = env.get('LC_ALL')
1133 if lc_all:
1133 if lc_all:
1134 env['LANG'] = lc_all
1134 env['LANG'] = lc_all
1135 del env['LC_ALL']
1135 del env['LC_ALL']
1136 env['LC_MESSAGES'] = 'C'
1136 env['LC_MESSAGES'] = 'C'
1137 p = subprocess.Popen(cmd, bufsize=-1, close_fds=util.closefds,
1137 p = subprocess.Popen(cmd, bufsize=-1, close_fds=util.closefds,
1138 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
1138 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
1139 universal_newlines=True, env=env, **extrakw)
1139 universal_newlines=True, env=env, **extrakw)
1140 stdout, stderr = p.communicate()
1140 stdout, stderr = p.communicate()
1141 stderr = stderr.strip()
1141 stderr = stderr.strip()
1142 if not failok:
1142 if not failok:
1143 if p.returncode:
1143 if p.returncode:
1144 raise error.Abort(stderr or 'exited with code %d'
1144 raise error.Abort(stderr or 'exited with code %d'
1145 % p.returncode)
1145 % p.returncode)
1146 if stderr:
1146 if stderr:
1147 self.ui.warn(stderr + '\n')
1147 self.ui.warn(stderr + '\n')
1148 return stdout, stderr
1148 return stdout, stderr
1149
1149
1150 @propertycache
1150 @propertycache
1151 def _svnversion(self):
1151 def _svnversion(self):
1152 output, err = self._svncommand(['--version', '--quiet'], filename=None)
1152 output, err = self._svncommand(['--version', '--quiet'], filename=None)
1153 m = re.search(r'^(\d+)\.(\d+)', output)
1153 m = re.search(r'^(\d+)\.(\d+)', output)
1154 if not m:
1154 if not m:
1155 raise error.Abort(_('cannot retrieve svn tool version'))
1155 raise error.Abort(_('cannot retrieve svn tool version'))
1156 return (int(m.group(1)), int(m.group(2)))
1156 return (int(m.group(1)), int(m.group(2)))
1157
1157
1158 def _wcrevs(self):
1158 def _wcrevs(self):
1159 # Get the working directory revision as well as the last
1159 # Get the working directory revision as well as the last
1160 # commit revision so we can compare the subrepo state with
1160 # commit revision so we can compare the subrepo state with
1161 # both. We used to store the working directory one.
1161 # both. We used to store the working directory one.
1162 output, err = self._svncommand(['info', '--xml'])
1162 output, err = self._svncommand(['info', '--xml'])
1163 doc = xml.dom.minidom.parseString(output)
1163 doc = xml.dom.minidom.parseString(output)
1164 entries = doc.getElementsByTagName('entry')
1164 entries = doc.getElementsByTagName('entry')
1165 lastrev, rev = '0', '0'
1165 lastrev, rev = '0', '0'
1166 if entries:
1166 if entries:
1167 rev = str(entries[0].getAttribute('revision')) or '0'
1167 rev = str(entries[0].getAttribute('revision')) or '0'
1168 commits = entries[0].getElementsByTagName('commit')
1168 commits = entries[0].getElementsByTagName('commit')
1169 if commits:
1169 if commits:
1170 lastrev = str(commits[0].getAttribute('revision')) or '0'
1170 lastrev = str(commits[0].getAttribute('revision')) or '0'
1171 return (lastrev, rev)
1171 return (lastrev, rev)
1172
1172
1173 def _wcrev(self):
1173 def _wcrev(self):
1174 return self._wcrevs()[0]
1174 return self._wcrevs()[0]
1175
1175
1176 def _wcchanged(self):
1176 def _wcchanged(self):
1177 """Return (changes, extchanges, missing) where changes is True
1177 """Return (changes, extchanges, missing) where changes is True
1178 if the working directory was changed, extchanges is
1178 if the working directory was changed, extchanges is
1179 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
1180 is True if any change is a missing entry.
1180 is True if any change is a missing entry.
1181 """
1181 """
1182 output, err = self._svncommand(['status', '--xml'])
1182 output, err = self._svncommand(['status', '--xml'])
1183 externals, changes, missing = [], [], []
1183 externals, changes, missing = [], [], []
1184 doc = xml.dom.minidom.parseString(output)
1184 doc = xml.dom.minidom.parseString(output)
1185 for e in doc.getElementsByTagName('entry'):
1185 for e in doc.getElementsByTagName('entry'):
1186 s = e.getElementsByTagName('wc-status')
1186 s = e.getElementsByTagName('wc-status')
1187 if not s:
1187 if not s:
1188 continue
1188 continue
1189 item = s[0].getAttribute('item')
1189 item = s[0].getAttribute('item')
1190 props = s[0].getAttribute('props')
1190 props = s[0].getAttribute('props')
1191 path = e.getAttribute('path')
1191 path = e.getAttribute('path')
1192 if item == 'external':
1192 if item == 'external':
1193 externals.append(path)
1193 externals.append(path)
1194 elif item == 'missing':
1194 elif item == 'missing':
1195 missing.append(path)
1195 missing.append(path)
1196 if (item not in ('', 'normal', 'unversioned', 'external')
1196 if (item not in ('', 'normal', 'unversioned', 'external')
1197 or props not in ('', 'none', 'normal')):
1197 or props not in ('', 'none', 'normal')):
1198 changes.append(path)
1198 changes.append(path)
1199 for path in changes:
1199 for path in changes:
1200 for ext in externals:
1200 for ext in externals:
1201 if path == ext or path.startswith(ext + pycompat.ossep):
1201 if path == ext or path.startswith(ext + pycompat.ossep):
1202 return True, True, bool(missing)
1202 return True, True, bool(missing)
1203 return bool(changes), False, bool(missing)
1203 return bool(changes), False, bool(missing)
1204
1204
1205 def dirty(self, ignoreupdate=False):
1205 def dirty(self, ignoreupdate=False):
1206 if not self._wcchanged()[0]:
1206 if not self._wcchanged()[0]:
1207 if self._state[1] in self._wcrevs() or ignoreupdate:
1207 if self._state[1] in self._wcrevs() or ignoreupdate:
1208 return False
1208 return False
1209 return True
1209 return True
1210
1210
1211 def basestate(self):
1211 def basestate(self):
1212 lastrev, rev = self._wcrevs()
1212 lastrev, rev = self._wcrevs()
1213 if lastrev != rev:
1213 if lastrev != rev:
1214 # Last committed rev is not the same than rev. We would
1214 # Last committed rev is not the same than rev. We would
1215 # 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
1216 # URL exists at lastrev. Test it and fallback to rev it
1216 # URL exists at lastrev. Test it and fallback to rev it
1217 # is not there.
1217 # is not there.
1218 try:
1218 try:
1219 self._svncommand(['list', '%s@%s' % (self._state[0], lastrev)])
1219 self._svncommand(['list', '%s@%s' % (self._state[0], lastrev)])
1220 return lastrev
1220 return lastrev
1221 except error.Abort:
1221 except error.Abort:
1222 pass
1222 pass
1223 return rev
1223 return rev
1224
1224
1225 @annotatesubrepoerror
1225 @annotatesubrepoerror
1226 def commit(self, text, user, date):
1226 def commit(self, text, user, date):
1227 # 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
1228 changed, extchanged, missing = self._wcchanged()
1228 changed, extchanged, missing = self._wcchanged()
1229 if not changed:
1229 if not changed:
1230 return self.basestate()
1230 return self.basestate()
1231 if extchanged:
1231 if extchanged:
1232 # Do not try to commit externals
1232 # Do not try to commit externals
1233 raise error.Abort(_('cannot commit svn externals'))
1233 raise error.Abort(_('cannot commit svn externals'))
1234 if missing:
1234 if missing:
1235 # svn can commit with missing entries but aborting like hg
1235 # svn can commit with missing entries but aborting like hg
1236 # seems a better approach.
1236 # seems a better approach.
1237 raise error.Abort(_('cannot commit missing svn entries'))
1237 raise error.Abort(_('cannot commit missing svn entries'))
1238 commitinfo, err = self._svncommand(['commit', '-m', text])
1238 commitinfo, err = self._svncommand(['commit', '-m', text])
1239 self.ui.status(commitinfo)
1239 self.ui.status(commitinfo)
1240 newrev = re.search('Committed revision ([0-9]+).', commitinfo)
1240 newrev = re.search('Committed revision ([0-9]+).', commitinfo)
1241 if not newrev:
1241 if not newrev:
1242 if not commitinfo.strip():
1242 if not commitinfo.strip():
1243 # Sometimes, our definition of "changed" differs from
1243 # Sometimes, our definition of "changed" differs from
1244 # svn one. For instance, svn ignores missing files
1244 # svn one. For instance, svn ignores missing files
1245 # when committing. If there are only missing files, no
1245 # when committing. If there are only missing files, no
1246 # commit is made, no output and no error code.
1246 # commit is made, no output and no error code.
1247 raise error.Abort(_('failed to commit svn changes'))
1247 raise error.Abort(_('failed to commit svn changes'))
1248 raise error.Abort(commitinfo.splitlines()[-1])
1248 raise error.Abort(commitinfo.splitlines()[-1])
1249 newrev = newrev.groups()[0]
1249 newrev = newrev.groups()[0]
1250 self.ui.status(self._svncommand(['update', '-r', newrev])[0])
1250 self.ui.status(self._svncommand(['update', '-r', newrev])[0])
1251 return newrev
1251 return newrev
1252
1252
1253 @annotatesubrepoerror
1253 @annotatesubrepoerror
1254 def remove(self):
1254 def remove(self):
1255 if self.dirty():
1255 if self.dirty():
1256 self.ui.warn(_('not removing repo %s because '
1256 self.ui.warn(_('not removing repo %s because '
1257 'it has changes.\n') % self._path)
1257 'it has changes.\n') % self._path)
1258 return
1258 return
1259 self.ui.note(_('removing subrepo %s\n') % self._path)
1259 self.ui.note(_('removing subrepo %s\n') % self._path)
1260
1260
1261 self.wvfs.rmtree(forcibly=True)
1261 self.wvfs.rmtree(forcibly=True)
1262 try:
1262 try:
1263 pwvfs = self._ctx.repo().wvfs
1263 pwvfs = self._ctx.repo().wvfs
1264 pwvfs.removedirs(pwvfs.dirname(self._path))
1264 pwvfs.removedirs(pwvfs.dirname(self._path))
1265 except OSError:
1265 except OSError:
1266 pass
1266 pass
1267
1267
1268 @annotatesubrepoerror
1268 @annotatesubrepoerror
1269 def get(self, state, overwrite=False):
1269 def get(self, state, overwrite=False):
1270 if overwrite:
1270 if overwrite:
1271 self._svncommand(['revert', '--recursive'])
1271 self._svncommand(['revert', '--recursive'])
1272 args = ['checkout']
1272 args = ['checkout']
1273 if self._svnversion >= (1, 5):
1273 if self._svnversion >= (1, 5):
1274 args.append('--force')
1274 args.append('--force')
1275 # 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
1276 # update to a directory which has since been deleted and recreated.
1276 # update to a directory which has since been deleted and recreated.
1277 args.append('%s@%s' % (state[0], state[1]))
1277 args.append('%s@%s' % (state[0], state[1]))
1278 status, err = self._svncommand(args, failok=True)
1278 status, err = self._svncommand(args, failok=True)
1279 _sanitize(self.ui, self.wvfs, '.svn')
1279 _sanitize(self.ui, self.wvfs, '.svn')
1280 if not re.search('Checked out revision [0-9]+.', status):
1280 if not re.search('Checked out revision [0-9]+.', status):
1281 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
1282 and (self._wcchanged()[:2] == (False, False))):
1282 and (self._wcchanged()[:2] == (False, False))):
1283 # obstructed but clean working copy, so just blow it away.
1283 # obstructed but clean working copy, so just blow it away.
1284 self.remove()
1284 self.remove()
1285 self.get(state, overwrite=False)
1285 self.get(state, overwrite=False)
1286 return
1286 return
1287 raise error.Abort((status or err).splitlines()[-1])
1287 raise error.Abort((status or err).splitlines()[-1])
1288 self.ui.status(status)
1288 self.ui.status(status)
1289
1289
1290 @annotatesubrepoerror
1290 @annotatesubrepoerror
1291 def merge(self, state):
1291 def merge(self, state):
1292 old = self._state[1]
1292 old = self._state[1]
1293 new = state[1]
1293 new = state[1]
1294 wcrev = self._wcrev()
1294 wcrev = self._wcrev()
1295 if new != wcrev:
1295 if new != wcrev:
1296 dirty = old == wcrev or self._wcchanged()[0]
1296 dirty = old == wcrev or self._wcchanged()[0]
1297 if _updateprompt(self.ui, self, dirty, wcrev, new):
1297 if _updateprompt(self.ui, self, dirty, wcrev, new):
1298 self.get(state, False)
1298 self.get(state, False)
1299
1299
1300 def push(self, opts):
1300 def push(self, opts):
1301 # push is a no-op for SVN
1301 # push is a no-op for SVN
1302 return True
1302 return True
1303
1303
1304 @annotatesubrepoerror
1304 @annotatesubrepoerror
1305 def files(self):
1305 def files(self):
1306 output = self._svncommand(['list', '--recursive', '--xml'])[0]
1306 output = self._svncommand(['list', '--recursive', '--xml'])[0]
1307 doc = xml.dom.minidom.parseString(output)
1307 doc = xml.dom.minidom.parseString(output)
1308 paths = []
1308 paths = []
1309 for e in doc.getElementsByTagName('entry'):
1309 for e in doc.getElementsByTagName('entry'):
1310 kind = str(e.getAttribute('kind'))
1310 kind = str(e.getAttribute('kind'))
1311 if kind != 'file':
1311 if kind != 'file':
1312 continue
1312 continue
1313 name = ''.join(c.data for c
1313 name = ''.join(c.data for c
1314 in e.getElementsByTagName('name')[0].childNodes
1314 in e.getElementsByTagName('name')[0].childNodes
1315 if c.nodeType == c.TEXT_NODE)
1315 if c.nodeType == c.TEXT_NODE)
1316 paths.append(name.encode('utf-8'))
1316 paths.append(name.encode('utf-8'))
1317 return paths
1317 return paths
1318
1318
1319 def filedata(self, name, decode):
1319 def filedata(self, name, decode):
1320 return self._svncommand(['cat'], name)[0]
1320 return self._svncommand(['cat'], name)[0]
1321
1321
1322
1322
1323 class gitsubrepo(abstractsubrepo):
1323 class gitsubrepo(abstractsubrepo):
1324 def __init__(self, ctx, path, state, allowcreate):
1324 def __init__(self, ctx, path, state, allowcreate):
1325 super(gitsubrepo, self).__init__(ctx, path)
1325 super(gitsubrepo, self).__init__(ctx, path)
1326 self._state = state
1326 self._state = state
1327 self._abspath = ctx.repo().wjoin(path)
1327 self._abspath = ctx.repo().wjoin(path)
1328 self._subparent = ctx.repo()
1328 self._subparent = ctx.repo()
1329 self._ensuregit()
1329 self._ensuregit()
1330
1330
1331 def _ensuregit(self):
1331 def _ensuregit(self):
1332 try:
1332 try:
1333 self._gitexecutable = 'git'
1333 self._gitexecutable = 'git'
1334 out, err = self._gitnodir(['--version'])
1334 out, err = self._gitnodir(['--version'])
1335 except OSError as e:
1335 except OSError as e:
1336 genericerror = _("error executing git for subrepo '%s': %s")
1336 genericerror = _("error executing git for subrepo '%s': %s")
1337 notfoundhint = _("check git is installed and in your PATH")
1337 notfoundhint = _("check git is installed and in your PATH")
1338 if e.errno != errno.ENOENT:
1338 if e.errno != errno.ENOENT:
1339 raise error.Abort(genericerror % (self._path, e.strerror))
1339 raise error.Abort(genericerror % (self._path, e.strerror))
1340 elif pycompat.osname == 'nt':
1340 elif pycompat.osname == 'nt':
1341 try:
1341 try:
1342 self._gitexecutable = 'git.cmd'
1342 self._gitexecutable = 'git.cmd'
1343 out, err = self._gitnodir(['--version'])
1343 out, err = self._gitnodir(['--version'])
1344 except OSError as e2:
1344 except OSError as e2:
1345 if e2.errno == errno.ENOENT:
1345 if e2.errno == errno.ENOENT:
1346 raise error.Abort(_("couldn't find 'git' or 'git.cmd'"
1346 raise error.Abort(_("couldn't find 'git' or 'git.cmd'"
1347 " for subrepo '%s'") % self._path,
1347 " for subrepo '%s'") % self._path,
1348 hint=notfoundhint)
1348 hint=notfoundhint)
1349 else:
1349 else:
1350 raise error.Abort(genericerror % (self._path,
1350 raise error.Abort(genericerror % (self._path,
1351 e2.strerror))
1351 e2.strerror))
1352 else:
1352 else:
1353 raise error.Abort(_("couldn't find git for subrepo '%s'")
1353 raise error.Abort(_("couldn't find git for subrepo '%s'")
1354 % self._path, hint=notfoundhint)
1354 % self._path, hint=notfoundhint)
1355 versionstatus = self._checkversion(out)
1355 versionstatus = self._checkversion(out)
1356 if versionstatus == 'unknown':
1356 if versionstatus == 'unknown':
1357 self.ui.warn(_('cannot retrieve git version\n'))
1357 self.ui.warn(_('cannot retrieve git version\n'))
1358 elif versionstatus == 'abort':
1358 elif versionstatus == 'abort':
1359 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'))
1360 elif versionstatus == 'warning':
1360 elif versionstatus == 'warning':
1361 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'))
1362
1362
1363 @staticmethod
1363 @staticmethod
1364 def _gitversion(out):
1364 def _gitversion(out):
1365 m = re.search(r'^git version (\d+)\.(\d+)\.(\d+)', out)
1365 m = re.search(r'^git version (\d+)\.(\d+)\.(\d+)', out)
1366 if m:
1366 if m:
1367 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)))
1368
1368
1369 m = re.search(r'^git version (\d+)\.(\d+)', out)
1369 m = re.search(r'^git version (\d+)\.(\d+)', out)
1370 if m:
1370 if m:
1371 return (int(m.group(1)), int(m.group(2)), 0)
1371 return (int(m.group(1)), int(m.group(2)), 0)
1372
1372
1373 return -1
1373 return -1
1374
1374
1375 @staticmethod
1375 @staticmethod
1376 def _checkversion(out):
1376 def _checkversion(out):
1377 '''ensure git version is new enough
1377 '''ensure git version is new enough
1378
1378
1379 >>> _checkversion = gitsubrepo._checkversion
1379 >>> _checkversion = gitsubrepo._checkversion
1380 >>> _checkversion('git version 1.6.0')
1380 >>> _checkversion('git version 1.6.0')
1381 'ok'
1381 'ok'
1382 >>> _checkversion('git version 1.8.5')
1382 >>> _checkversion('git version 1.8.5')
1383 'ok'
1383 'ok'
1384 >>> _checkversion('git version 1.4.0')
1384 >>> _checkversion('git version 1.4.0')
1385 'abort'
1385 'abort'
1386 >>> _checkversion('git version 1.5.0')
1386 >>> _checkversion('git version 1.5.0')
1387 'warning'
1387 'warning'
1388 >>> _checkversion('git version 1.9-rc0')
1388 >>> _checkversion('git version 1.9-rc0')
1389 'ok'
1389 'ok'
1390 >>> _checkversion('git version 1.9.0.265.g81cdec2')
1390 >>> _checkversion('git version 1.9.0.265.g81cdec2')
1391 'ok'
1391 'ok'
1392 >>> _checkversion('git version 1.9.0.GIT')
1392 >>> _checkversion('git version 1.9.0.GIT')
1393 'ok'
1393 'ok'
1394 >>> _checkversion('git version 12345')
1394 >>> _checkversion('git version 12345')
1395 'unknown'
1395 'unknown'
1396 >>> _checkversion('no')
1396 >>> _checkversion('no')
1397 'unknown'
1397 'unknown'
1398 '''
1398 '''
1399 version = gitsubrepo._gitversion(out)
1399 version = gitsubrepo._gitversion(out)
1400 # 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,
1401 # 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
1402 # 1.5.0 but attempt to continue.
1402 # 1.5.0 but attempt to continue.
1403 if version == -1:
1403 if version == -1:
1404 return 'unknown'
1404 return 'unknown'
1405 if version < (1, 5, 0):
1405 if version < (1, 5, 0):
1406 return 'abort'
1406 return 'abort'
1407 elif version < (1, 6, 0):
1407 elif version < (1, 6, 0):
1408 return 'warning'
1408 return 'warning'
1409 return 'ok'
1409 return 'ok'
1410
1410
1411 def _gitcommand(self, commands, env=None, stream=False):
1411 def _gitcommand(self, commands, env=None, stream=False):
1412 return self._gitdir(commands, env=env, stream=stream)[0]
1412 return self._gitdir(commands, env=env, stream=stream)[0]
1413
1413
1414 def _gitdir(self, commands, env=None, stream=False):
1414 def _gitdir(self, commands, env=None, stream=False):
1415 return self._gitnodir(commands, env=env, stream=stream,
1415 return self._gitnodir(commands, env=env, stream=stream,
1416 cwd=self._abspath)
1416 cwd=self._abspath)
1417
1417
1418 def _gitnodir(self, commands, env=None, stream=False, cwd=None):
1418 def _gitnodir(self, commands, env=None, stream=False, cwd=None):
1419 """Calls the git command
1419 """Calls the git command
1420
1420
1421 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
1422 are not supported and very probably fail.
1422 are not supported and very probably fail.
1423 """
1423 """
1424 self.ui.debug('%s: git %s\n' % (self._relpath, ' '.join(commands)))
1424 self.ui.debug('%s: git %s\n' % (self._relpath, ' '.join(commands)))
1425 if env is None:
1425 if env is None:
1426 env = encoding.environ.copy()
1426 env = encoding.environ.copy()
1427 # disable localization for Git output (issue5176)
1427 # disable localization for Git output (issue5176)
1428 env['LC_ALL'] = 'C'
1428 env['LC_ALL'] = 'C'
1429 # fix for Git CVE-2015-7545
1429 # fix for Git CVE-2015-7545
1430 if 'GIT_ALLOW_PROTOCOL' not in env:
1430 if 'GIT_ALLOW_PROTOCOL' not in env:
1431 env['GIT_ALLOW_PROTOCOL'] = 'file:git:http:https:ssh'
1431 env['GIT_ALLOW_PROTOCOL'] = 'file:git:http:https:ssh'
1432 # unless ui.quiet is set, print git's stderr,
1432 # unless ui.quiet is set, print git's stderr,
1433 # which is mostly progress and useful info
1433 # which is mostly progress and useful info
1434 errpipe = None
1434 errpipe = None
1435 if self.ui.quiet:
1435 if self.ui.quiet:
1436 errpipe = open(os.devnull, 'w')
1436 errpipe = open(os.devnull, 'w')
1437 if self.ui._colormode and len(commands) and commands[0] == "diff":
1437 if self.ui._colormode and len(commands) and commands[0] == "diff":
1438 # insert the argument in the front,
1438 # insert the argument in the front,
1439 # the end of git diff arguments is used for paths
1439 # the end of git diff arguments is used for paths
1440 commands.insert(1, '--color')
1440 commands.insert(1, '--color')
1441 p = subprocess.Popen([self._gitexecutable] + commands, bufsize=-1,
1441 p = subprocess.Popen([self._gitexecutable] + commands, bufsize=-1,
1442 cwd=cwd, env=env, close_fds=util.closefds,
1442 cwd=cwd, env=env, close_fds=util.closefds,
1443 stdout=subprocess.PIPE, stderr=errpipe)
1443 stdout=subprocess.PIPE, stderr=errpipe)
1444 if stream:
1444 if stream:
1445 return p.stdout, None
1445 return p.stdout, None
1446
1446
1447 retdata = p.stdout.read().strip()
1447 retdata = p.stdout.read().strip()
1448 # wait for the child to exit to avoid race condition.
1448 # wait for the child to exit to avoid race condition.
1449 p.wait()
1449 p.wait()
1450
1450
1451 if p.returncode != 0 and p.returncode != 1:
1451 if p.returncode != 0 and p.returncode != 1:
1452 # there are certain error codes that are ok
1452 # there are certain error codes that are ok
1453 command = commands[0]
1453 command = commands[0]
1454 if command in ('cat-file', 'symbolic-ref'):
1454 if command in ('cat-file', 'symbolic-ref'):
1455 return retdata, p.returncode
1455 return retdata, p.returncode
1456 # for all others, abort
1456 # for all others, abort
1457 raise error.Abort(_('git %s error %d in %s') %
1457 raise error.Abort(_('git %s error %d in %s') %
1458 (command, p.returncode, self._relpath))
1458 (command, p.returncode, self._relpath))
1459
1459
1460 return retdata, p.returncode
1460 return retdata, p.returncode
1461
1461
1462 def _gitmissing(self):
1462 def _gitmissing(self):
1463 return not self.wvfs.exists('.git')
1463 return not self.wvfs.exists('.git')
1464
1464
1465 def _gitstate(self):
1465 def _gitstate(self):
1466 return self._gitcommand(['rev-parse', 'HEAD'])
1466 return self._gitcommand(['rev-parse', 'HEAD'])
1467
1467
1468 def _gitcurrentbranch(self):
1468 def _gitcurrentbranch(self):
1469 current, err = self._gitdir(['symbolic-ref', 'HEAD', '--quiet'])
1469 current, err = self._gitdir(['symbolic-ref', 'HEAD', '--quiet'])
1470 if err:
1470 if err:
1471 current = None
1471 current = None
1472 return current
1472 return current
1473
1473
1474 def _gitremote(self, remote):
1474 def _gitremote(self, remote):
1475 out = self._gitcommand(['remote', 'show', '-n', remote])
1475 out = self._gitcommand(['remote', 'show', '-n', remote])
1476 line = out.split('\n')[1]
1476 line = out.split('\n')[1]
1477 i = line.index('URL: ') + len('URL: ')
1477 i = line.index('URL: ') + len('URL: ')
1478 return line[i:]
1478 return line[i:]
1479
1479
1480 def _githavelocally(self, revision):
1480 def _githavelocally(self, revision):
1481 out, code = self._gitdir(['cat-file', '-e', revision])
1481 out, code = self._gitdir(['cat-file', '-e', revision])
1482 return code == 0
1482 return code == 0
1483
1483
1484 def _gitisancestor(self, r1, r2):
1484 def _gitisancestor(self, r1, r2):
1485 base = self._gitcommand(['merge-base', r1, r2])
1485 base = self._gitcommand(['merge-base', r1, r2])
1486 return base == r1
1486 return base == r1
1487
1487
1488 def _gitisbare(self):
1488 def _gitisbare(self):
1489 return self._gitcommand(['config', '--bool', 'core.bare']) == 'true'
1489 return self._gitcommand(['config', '--bool', 'core.bare']) == 'true'
1490
1490
1491 def _gitupdatestat(self):
1491 def _gitupdatestat(self):
1492 """This must be run before git diff-index.
1492 """This must be run before git diff-index.
1493 diff-index only looks at changes to file stat;
1493 diff-index only looks at changes to file stat;
1494 this command looks at file contents and updates the stat."""
1494 this command looks at file contents and updates the stat."""
1495 self._gitcommand(['update-index', '-q', '--refresh'])
1495 self._gitcommand(['update-index', '-q', '--refresh'])
1496
1496
1497 def _gitbranchmap(self):
1497 def _gitbranchmap(self):
1498 '''returns 2 things:
1498 '''returns 2 things:
1499 a map from git branch to revision
1499 a map from git branch to revision
1500 a map from revision to branches'''
1500 a map from revision to branches'''
1501 branch2rev = {}
1501 branch2rev = {}
1502 rev2branch = {}
1502 rev2branch = {}
1503
1503
1504 out = self._gitcommand(['for-each-ref', '--format',
1504 out = self._gitcommand(['for-each-ref', '--format',
1505 '%(objectname) %(refname)'])
1505 '%(objectname) %(refname)'])
1506 for line in out.split('\n'):
1506 for line in out.split('\n'):
1507 revision, ref = line.split(' ')
1507 revision, ref = line.split(' ')
1508 if (not ref.startswith('refs/heads/') and
1508 if (not ref.startswith('refs/heads/') and
1509 not ref.startswith('refs/remotes/')):
1509 not ref.startswith('refs/remotes/')):
1510 continue
1510 continue
1511 if ref.startswith('refs/remotes/') and ref.endswith('/HEAD'):
1511 if ref.startswith('refs/remotes/') and ref.endswith('/HEAD'):
1512 continue # ignore remote/HEAD redirects
1512 continue # ignore remote/HEAD redirects
1513 branch2rev[ref] = revision
1513 branch2rev[ref] = revision
1514 rev2branch.setdefault(revision, []).append(ref)
1514 rev2branch.setdefault(revision, []).append(ref)
1515 return branch2rev, rev2branch
1515 return branch2rev, rev2branch
1516
1516
1517 def _gittracking(self, branches):
1517 def _gittracking(self, branches):
1518 'return map of remote branch to local tracking branch'
1518 'return map of remote branch to local tracking branch'
1519 # assumes no more than one local tracking branch for each remote
1519 # assumes no more than one local tracking branch for each remote
1520 tracking = {}
1520 tracking = {}
1521 for b in branches:
1521 for b in branches:
1522 if b.startswith('refs/remotes/'):
1522 if b.startswith('refs/remotes/'):
1523 continue
1523 continue
1524 bname = b.split('/', 2)[2]
1524 bname = b.split('/', 2)[2]
1525 remote = self._gitcommand(['config', 'branch.%s.remote' % bname])
1525 remote = self._gitcommand(['config', 'branch.%s.remote' % bname])
1526 if remote:
1526 if remote:
1527 ref = self._gitcommand(['config', 'branch.%s.merge' % bname])
1527 ref = self._gitcommand(['config', 'branch.%s.merge' % bname])
1528 tracking['refs/remotes/%s/%s' %
1528 tracking['refs/remotes/%s/%s' %
1529 (remote, ref.split('/', 2)[2])] = b
1529 (remote, ref.split('/', 2)[2])] = b
1530 return tracking
1530 return tracking
1531
1531
1532 def _abssource(self, source):
1532 def _abssource(self, source):
1533 if '://' not in source:
1533 if '://' not in source:
1534 # recognize the scp syntax as an absolute source
1534 # recognize the scp syntax as an absolute source
1535 colon = source.find(':')
1535 colon = source.find(':')
1536 if colon != -1 and '/' not in source[:colon]:
1536 if colon != -1 and '/' not in source[:colon]:
1537 return source
1537 return source
1538 self._subsource = source
1538 self._subsource = source
1539 return _abssource(self)
1539 return _abssource(self)
1540
1540
1541 def _fetch(self, source, revision):
1541 def _fetch(self, source, revision):
1542 if self._gitmissing():
1542 if self._gitmissing():
1543 source = self._abssource(source)
1543 source = self._abssource(source)
1544 self.ui.status(_('cloning subrepo %s from %s\n') %
1544 self.ui.status(_('cloning subrepo %s from %s\n') %
1545 (self._relpath, source))
1545 (self._relpath, source))
1546 self._gitnodir(['clone', source, self._abspath])
1546 self._gitnodir(['clone', source, self._abspath])
1547 if self._githavelocally(revision):
1547 if self._githavelocally(revision):
1548 return
1548 return
1549 self.ui.status(_('pulling subrepo %s from %s\n') %
1549 self.ui.status(_('pulling subrepo %s from %s\n') %
1550 (self._relpath, self._gitremote('origin')))
1550 (self._relpath, self._gitremote('origin')))
1551 # try only origin: the originally cloned repo
1551 # try only origin: the originally cloned repo
1552 self._gitcommand(['fetch'])
1552 self._gitcommand(['fetch'])
1553 if not self._githavelocally(revision):
1553 if not self._githavelocally(revision):
1554 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") %
1555 (revision, self._relpath))
1555 (revision, self._relpath))
1556
1556
1557 @annotatesubrepoerror
1557 @annotatesubrepoerror
1558 def dirty(self, ignoreupdate=False):
1558 def dirty(self, ignoreupdate=False):
1559 if self._gitmissing():
1559 if self._gitmissing():
1560 return self._state[1] != ''
1560 return self._state[1] != ''
1561 if self._gitisbare():
1561 if self._gitisbare():
1562 return True
1562 return True
1563 if not ignoreupdate and self._state[1] != self._gitstate():
1563 if not ignoreupdate and self._state[1] != self._gitstate():
1564 # different version checked out
1564 # different version checked out
1565 return True
1565 return True
1566 # check for staged changes or modified files; ignore untracked files
1566 # check for staged changes or modified files; ignore untracked files
1567 self._gitupdatestat()
1567 self._gitupdatestat()
1568 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
1568 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
1569 return code == 1
1569 return code == 1
1570
1570
1571 def basestate(self):
1571 def basestate(self):
1572 return self._gitstate()
1572 return self._gitstate()
1573
1573
1574 @annotatesubrepoerror
1574 @annotatesubrepoerror
1575 def get(self, state, overwrite=False):
1575 def get(self, state, overwrite=False):
1576 source, revision, kind = state
1576 source, revision, kind = state
1577 if not revision:
1577 if not revision:
1578 self.remove()
1578 self.remove()
1579 return
1579 return
1580 self._fetch(source, revision)
1580 self._fetch(source, revision)
1581 # if the repo was set to be bare, unbare it
1581 # if the repo was set to be bare, unbare it
1582 if self._gitisbare():
1582 if self._gitisbare():
1583 self._gitcommand(['config', 'core.bare', 'false'])
1583 self._gitcommand(['config', 'core.bare', 'false'])
1584 if self._gitstate() == revision:
1584 if self._gitstate() == revision:
1585 self._gitcommand(['reset', '--hard', 'HEAD'])
1585 self._gitcommand(['reset', '--hard', 'HEAD'])
1586 return
1586 return
1587 elif self._gitstate() == revision:
1587 elif self._gitstate() == revision:
1588 if overwrite:
1588 if overwrite:
1589 # first reset the index to unmark new files for commit, because
1589 # first reset the index to unmark new files for commit, because
1590 # reset --hard will otherwise throw away files added for commit,
1590 # reset --hard will otherwise throw away files added for commit,
1591 # not just unmark them.
1591 # not just unmark them.
1592 self._gitcommand(['reset', 'HEAD'])
1592 self._gitcommand(['reset', 'HEAD'])
1593 self._gitcommand(['reset', '--hard', 'HEAD'])
1593 self._gitcommand(['reset', '--hard', 'HEAD'])
1594 return
1594 return
1595 branch2rev, rev2branch = self._gitbranchmap()
1595 branch2rev, rev2branch = self._gitbranchmap()
1596
1596
1597 def checkout(args):
1597 def checkout(args):
1598 cmd = ['checkout']
1598 cmd = ['checkout']
1599 if overwrite:
1599 if overwrite:
1600 # first reset the index to unmark new files for commit, because
1600 # first reset the index to unmark new files for commit, because
1601 # the -f option will otherwise throw away files added for
1601 # the -f option will otherwise throw away files added for
1602 # commit, not just unmark them.
1602 # commit, not just unmark them.
1603 self._gitcommand(['reset', 'HEAD'])
1603 self._gitcommand(['reset', 'HEAD'])
1604 cmd.append('-f')
1604 cmd.append('-f')
1605 self._gitcommand(cmd + args)
1605 self._gitcommand(cmd + args)
1606 _sanitize(self.ui, self.wvfs, '.git')
1606 _sanitize(self.ui, self.wvfs, '.git')
1607
1607
1608 def rawcheckout():
1608 def rawcheckout():
1609 # no branch to checkout, check it out with no branch
1609 # no branch to checkout, check it out with no branch
1610 self.ui.warn(_('checking out detached HEAD in subrepo %s\n') %
1610 self.ui.warn(_('checking out detached HEAD in subrepo %s\n') %
1611 self._relpath)
1611 self._relpath)
1612 self.ui.warn(_('check out a git branch if you intend '
1612 self.ui.warn(_('check out a git branch if you intend '
1613 'to make changes\n'))
1613 'to make changes\n'))
1614 checkout(['-q', revision])
1614 checkout(['-q', revision])
1615
1615
1616 if revision not in rev2branch:
1616 if revision not in rev2branch:
1617 rawcheckout()
1617 rawcheckout()
1618 return
1618 return
1619 branches = rev2branch[revision]
1619 branches = rev2branch[revision]
1620 firstlocalbranch = None
1620 firstlocalbranch = None
1621 for b in branches:
1621 for b in branches:
1622 if b == 'refs/heads/master':
1622 if b == 'refs/heads/master':
1623 # master trumps all other branches
1623 # master trumps all other branches
1624 checkout(['refs/heads/master'])
1624 checkout(['refs/heads/master'])
1625 return
1625 return
1626 if not firstlocalbranch and not b.startswith('refs/remotes/'):
1626 if not firstlocalbranch and not b.startswith('refs/remotes/'):
1627 firstlocalbranch = b
1627 firstlocalbranch = b
1628 if firstlocalbranch:
1628 if firstlocalbranch:
1629 checkout([firstlocalbranch])
1629 checkout([firstlocalbranch])
1630 return
1630 return
1631
1631
1632 tracking = self._gittracking(branch2rev.keys())
1632 tracking = self._gittracking(branch2rev.keys())
1633 # choose a remote branch already tracked if possible
1633 # choose a remote branch already tracked if possible
1634 remote = branches[0]
1634 remote = branches[0]
1635 if remote not in tracking:
1635 if remote not in tracking:
1636 for b in branches:
1636 for b in branches:
1637 if b in tracking:
1637 if b in tracking:
1638 remote = b
1638 remote = b
1639 break
1639 break
1640
1640
1641 if remote not in tracking:
1641 if remote not in tracking:
1642 # create a new local tracking branch
1642 # create a new local tracking branch
1643 local = remote.split('/', 3)[3]
1643 local = remote.split('/', 3)[3]
1644 checkout(['-b', local, remote])
1644 checkout(['-b', local, remote])
1645 elif self._gitisancestor(branch2rev[tracking[remote]], remote):
1645 elif self._gitisancestor(branch2rev[tracking[remote]], remote):
1646 # When updating to a tracked remote branch,
1646 # When updating to a tracked remote branch,
1647 # if the local tracking branch is downstream of it,
1647 # if the local tracking branch is downstream of it,
1648 # a normal `git pull` would have performed a "fast-forward merge"
1648 # a normal `git pull` would have performed a "fast-forward merge"
1649 # which is equivalent to updating the local branch to the remote.
1649 # which is equivalent to updating the local branch to the remote.
1650 # 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
1651 # detect this situation and perform this action lazily.
1651 # detect this situation and perform this action lazily.
1652 if tracking[remote] != self._gitcurrentbranch():
1652 if tracking[remote] != self._gitcurrentbranch():
1653 checkout([tracking[remote]])
1653 checkout([tracking[remote]])
1654 self._gitcommand(['merge', '--ff', remote])
1654 self._gitcommand(['merge', '--ff', remote])
1655 _sanitize(self.ui, self.wvfs, '.git')
1655 _sanitize(self.ui, self.wvfs, '.git')
1656 else:
1656 else:
1657 # a real merge would be required, just checkout the revision
1657 # a real merge would be required, just checkout the revision
1658 rawcheckout()
1658 rawcheckout()
1659
1659
1660 @annotatesubrepoerror
1660 @annotatesubrepoerror
1661 def commit(self, text, user, date):
1661 def commit(self, text, user, date):
1662 if self._gitmissing():
1662 if self._gitmissing():
1663 raise error.Abort(_("subrepo %s is missing") % self._relpath)
1663 raise error.Abort(_("subrepo %s is missing") % self._relpath)
1664 cmd = ['commit', '-a', '-m', text]
1664 cmd = ['commit', '-a', '-m', text]
1665 env = encoding.environ.copy()
1665 env = encoding.environ.copy()
1666 if user:
1666 if user:
1667 cmd += ['--author', user]
1667 cmd += ['--author', user]
1668 if date:
1668 if date:
1669 # git's date parser silently ignores when seconds < 1e9
1669 # git's date parser silently ignores when seconds < 1e9
1670 # convert to ISO8601
1670 # convert to ISO8601
1671 env['GIT_AUTHOR_DATE'] = util.datestr(date,
1671 env['GIT_AUTHOR_DATE'] = util.datestr(date,
1672 '%Y-%m-%dT%H:%M:%S %1%2')
1672 '%Y-%m-%dT%H:%M:%S %1%2')
1673 self._gitcommand(cmd, env=env)
1673 self._gitcommand(cmd, env=env)
1674 # make sure commit works otherwise HEAD might not exist under certain
1674 # make sure commit works otherwise HEAD might not exist under certain
1675 # circumstances
1675 # circumstances
1676 return self._gitstate()
1676 return self._gitstate()
1677
1677
1678 @annotatesubrepoerror
1678 @annotatesubrepoerror
1679 def merge(self, state):
1679 def merge(self, state):
1680 source, revision, kind = state
1680 source, revision, kind = state
1681 self._fetch(source, revision)
1681 self._fetch(source, revision)
1682 base = self._gitcommand(['merge-base', revision, self._state[1]])
1682 base = self._gitcommand(['merge-base', revision, self._state[1]])
1683 self._gitupdatestat()
1683 self._gitupdatestat()
1684 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
1684 out, code = self._gitdir(['diff-index', '--quiet', 'HEAD'])
1685
1685
1686 def mergefunc():
1686 def mergefunc():
1687 if base == revision:
1687 if base == revision:
1688 self.get(state) # fast forward merge
1688 self.get(state) # fast forward merge
1689 elif base != self._state[1]:
1689 elif base != self._state[1]:
1690 self._gitcommand(['merge', '--no-commit', revision])
1690 self._gitcommand(['merge', '--no-commit', revision])
1691 _sanitize(self.ui, self.wvfs, '.git')
1691 _sanitize(self.ui, self.wvfs, '.git')
1692
1692
1693 if self.dirty():
1693 if self.dirty():
1694 if self._gitstate() != revision:
1694 if self._gitstate() != revision:
1695 dirty = self._gitstate() == self._state[1] or code != 0
1695 dirty = self._gitstate() == self._state[1] or code != 0
1696 if _updateprompt(self.ui, self, dirty,
1696 if _updateprompt(self.ui, self, dirty,
1697 self._state[1][:7], revision[:7]):
1697 self._state[1][:7], revision[:7]):
1698 mergefunc()
1698 mergefunc()
1699 else:
1699 else:
1700 mergefunc()
1700 mergefunc()
1701
1701
1702 @annotatesubrepoerror
1702 @annotatesubrepoerror
1703 def push(self, opts):
1703 def push(self, opts):
1704 force = opts.get('force')
1704 force = opts.get('force')
1705
1705
1706 if not self._state[1]:
1706 if not self._state[1]:
1707 return True
1707 return True
1708 if self._gitmissing():
1708 if self._gitmissing():
1709 raise error.Abort(_("subrepo %s is missing") % self._relpath)
1709 raise error.Abort(_("subrepo %s is missing") % self._relpath)
1710 # if a branch in origin contains the revision, nothing to do
1710 # if a branch in origin contains the revision, nothing to do
1711 branch2rev, rev2branch = self._gitbranchmap()
1711 branch2rev, rev2branch = self._gitbranchmap()
1712 if self._state[1] in rev2branch:
1712 if self._state[1] in rev2branch:
1713 for b in rev2branch[self._state[1]]:
1713 for b in rev2branch[self._state[1]]:
1714 if b.startswith('refs/remotes/origin/'):
1714 if b.startswith('refs/remotes/origin/'):
1715 return True
1715 return True
1716 for b, revision in branch2rev.iteritems():
1716 for b, revision in branch2rev.iteritems():
1717 if b.startswith('refs/remotes/origin/'):
1717 if b.startswith('refs/remotes/origin/'):
1718 if self._gitisancestor(self._state[1], revision):
1718 if self._gitisancestor(self._state[1], revision):
1719 return True
1719 return True
1720 # otherwise, try to push the currently checked out branch
1720 # otherwise, try to push the currently checked out branch
1721 cmd = ['push']
1721 cmd = ['push']
1722 if force:
1722 if force:
1723 cmd.append('--force')
1723 cmd.append('--force')
1724
1724
1725 current = self._gitcurrentbranch()
1725 current = self._gitcurrentbranch()
1726 if current:
1726 if current:
1727 # determine if the current branch is even useful
1727 # determine if the current branch is even useful
1728 if not self._gitisancestor(self._state[1], current):
1728 if not self._gitisancestor(self._state[1], current):
1729 self.ui.warn(_('unrelated git branch checked out '
1729 self.ui.warn(_('unrelated git branch checked out '
1730 'in subrepo %s\n') % self._relpath)
1730 'in subrepo %s\n') % self._relpath)
1731 return False
1731 return False
1732 self.ui.status(_('pushing branch %s of subrepo %s\n') %
1732 self.ui.status(_('pushing branch %s of subrepo %s\n') %
1733 (current.split('/', 2)[2], self._relpath))
1733 (current.split('/', 2)[2], self._relpath))
1734 ret = self._gitdir(cmd + ['origin', current])
1734 ret = self._gitdir(cmd + ['origin', current])
1735 return ret[1] == 0
1735 return ret[1] == 0
1736 else:
1736 else:
1737 self.ui.warn(_('no branch checked out in subrepo %s\n'
1737 self.ui.warn(_('no branch checked out in subrepo %s\n'
1738 'cannot push revision %s\n') %
1738 'cannot push revision %s\n') %
1739 (self._relpath, self._state[1]))
1739 (self._relpath, self._state[1]))
1740 return False
1740 return False
1741
1741
1742 @annotatesubrepoerror
1742 @annotatesubrepoerror
1743 def add(self, ui, match, prefix, explicitonly, **opts):
1743 def add(self, ui, match, prefix, explicitonly, **opts):
1744 if self._gitmissing():
1744 if self._gitmissing():
1745 return []
1745 return []
1746
1746
1747 (modified, added, removed,
1747 (modified, added, removed,
1748 deleted, unknown, ignored, clean) = self.status(None, unknown=True,
1748 deleted, unknown, ignored, clean) = self.status(None, unknown=True,
1749 clean=True)
1749 clean=True)
1750
1750
1751 tracked = set()
1751 tracked = set()
1752 # dirstates 'amn' warn, 'r' is added again
1752 # dirstates 'amn' warn, 'r' is added again
1753 for l in (modified, added, deleted, clean):
1753 for l in (modified, added, deleted, clean):
1754 tracked.update(l)
1754 tracked.update(l)
1755
1755
1756 # Unknown files not of interest will be rejected by the matcher
1756 # Unknown files not of interest will be rejected by the matcher
1757 files = unknown
1757 files = unknown
1758 files.extend(match.files())
1758 files.extend(match.files())
1759
1759
1760 rejected = []
1760 rejected = []
1761
1761
1762 files = [f for f in sorted(set(files)) if match(f)]
1762 files = [f for f in sorted(set(files)) if match(f)]
1763 for f in files:
1763 for f in files:
1764 exact = match.exact(f)
1764 exact = match.exact(f)
1765 command = ["add"]
1765 command = ["add"]
1766 if exact:
1766 if exact:
1767 command.append("-f") #should be added, even if ignored
1767 command.append("-f") #should be added, even if ignored
1768 if ui.verbose or not exact:
1768 if ui.verbose or not exact:
1769 ui.status(_('adding %s\n') % match.rel(f))
1769 ui.status(_('adding %s\n') % match.rel(f))
1770
1770
1771 if f in tracked: # hg prints 'adding' even if already tracked
1771 if f in tracked: # hg prints 'adding' even if already tracked
1772 if exact:
1772 if exact:
1773 rejected.append(f)
1773 rejected.append(f)
1774 continue
1774 continue
1775 if not opts.get(r'dry_run'):
1775 if not opts.get(r'dry_run'):
1776 self._gitcommand(command + [f])
1776 self._gitcommand(command + [f])
1777
1777
1778 for f in rejected:
1778 for f in rejected:
1779 ui.warn(_("%s already tracked!\n") % match.abs(f))
1779 ui.warn(_("%s already tracked!\n") % match.abs(f))
1780
1780
1781 return rejected
1781 return rejected
1782
1782
1783 @annotatesubrepoerror
1783 @annotatesubrepoerror
1784 def remove(self):
1784 def remove(self):
1785 if self._gitmissing():
1785 if self._gitmissing():
1786 return
1786 return
1787 if self.dirty():
1787 if self.dirty():
1788 self.ui.warn(_('not removing repo %s because '
1788 self.ui.warn(_('not removing repo %s because '
1789 'it has changes.\n') % self._relpath)
1789 'it has changes.\n') % self._relpath)
1790 return
1790 return
1791 # we can't fully delete the repository as it may contain
1791 # we can't fully delete the repository as it may contain
1792 # local-only history
1792 # local-only history
1793 self.ui.note(_('removing subrepo %s\n') % self._relpath)
1793 self.ui.note(_('removing subrepo %s\n') % self._relpath)
1794 self._gitcommand(['config', 'core.bare', 'true'])
1794 self._gitcommand(['config', 'core.bare', 'true'])
1795 for f, kind in self.wvfs.readdir():
1795 for f, kind in self.wvfs.readdir():
1796 if f == '.git':
1796 if f == '.git':
1797 continue
1797 continue
1798 if kind == stat.S_IFDIR:
1798 if kind == stat.S_IFDIR:
1799 self.wvfs.rmtree(f)
1799 self.wvfs.rmtree(f)
1800 else:
1800 else:
1801 self.wvfs.unlink(f)
1801 self.wvfs.unlink(f)
1802
1802
1803 def archive(self, archiver, prefix, match=None, decode=True):
1803 def archive(self, archiver, prefix, match=None, decode=True):
1804 total = 0
1804 total = 0
1805 source, revision = self._state
1805 source, revision = self._state
1806 if not revision:
1806 if not revision:
1807 return total
1807 return total
1808 self._fetch(source, revision)
1808 self._fetch(source, revision)
1809
1809
1810 # Parse git's native archive command.
1810 # Parse git's native archive command.
1811 # This should be much faster than manually traversing the trees
1811 # This should be much faster than manually traversing the trees
1812 # and objects with many subprocess calls.
1812 # and objects with many subprocess calls.
1813 tarstream = self._gitcommand(['archive', revision], stream=True)
1813 tarstream = self._gitcommand(['archive', revision], stream=True)
1814 tar = tarfile.open(fileobj=tarstream, mode='r|')
1814 tar = tarfile.open(fileobj=tarstream, mode='r|')
1815 relpath = subrelpath(self)
1815 relpath = subrelpath(self)
1816 self.ui.progress(_('archiving (%s)') % relpath, 0, unit=_('files'))
1816 self.ui.progress(_('archiving (%s)') % relpath, 0, unit=_('files'))
1817 for i, info in enumerate(tar):
1817 for i, info in enumerate(tar):
1818 if info.isdir():
1818 if info.isdir():
1819 continue
1819 continue
1820 if match and not match(info.name):
1820 if match and not match(info.name):
1821 continue
1821 continue
1822 if info.issym():
1822 if info.issym():
1823 data = info.linkname
1823 data = info.linkname
1824 else:
1824 else:
1825 data = tar.extractfile(info).read()
1825 data = tar.extractfile(info).read()
1826 archiver.addfile(prefix + self._path + '/' + info.name,
1826 archiver.addfile(prefix + self._path + '/' + info.name,
1827 info.mode, info.issym(), data)
1827 info.mode, info.issym(), data)
1828 total += 1
1828 total += 1
1829 self.ui.progress(_('archiving (%s)') % relpath, i + 1,
1829 self.ui.progress(_('archiving (%s)') % relpath, i + 1,
1830 unit=_('files'))
1830 unit=_('files'))
1831 self.ui.progress(_('archiving (%s)') % relpath, None)
1831 self.ui.progress(_('archiving (%s)') % relpath, None)
1832 return total
1832 return total
1833
1833
1834
1834
1835 @annotatesubrepoerror
1835 @annotatesubrepoerror
1836 def cat(self, match, fntemplate, prefix, **opts):
1836 def cat(self, match, fm, fntemplate, prefix, **opts):
1837 rev = self._state[1]
1837 rev = self._state[1]
1838 if match.anypats():
1838 if match.anypats():
1839 return 1 #No support for include/exclude yet
1839 return 1 #No support for include/exclude yet
1840
1840
1841 if not match.files():
1841 if not match.files():
1842 return 1
1842 return 1
1843
1843
1844 # TODO: add support for non-plain formatter (see cmdutil.cat())
1844 for f in match.files():
1845 for f in match.files():
1845 output = self._gitcommand(["show", "%s:%s" % (rev, f)])
1846 output = self._gitcommand(["show", "%s:%s" % (rev, f)])
1846 fp = cmdutil.makefileobj(self._subparent, fntemplate,
1847 fp = cmdutil.makefileobj(self._subparent, fntemplate,
1847 self._ctx.node(),
1848 self._ctx.node(),
1848 pathname=self.wvfs.reljoin(prefix, f))
1849 pathname=self.wvfs.reljoin(prefix, f))
1849 fp.write(output)
1850 fp.write(output)
1850 fp.close()
1851 fp.close()
1851 return 0
1852 return 0
1852
1853
1853
1854
1854 @annotatesubrepoerror
1855 @annotatesubrepoerror
1855 def status(self, rev2, **opts):
1856 def status(self, rev2, **opts):
1856 rev1 = self._state[1]
1857 rev1 = self._state[1]
1857 if self._gitmissing() or not rev1:
1858 if self._gitmissing() or not rev1:
1858 # if the repo is missing, return no results
1859 # if the repo is missing, return no results
1859 return scmutil.status([], [], [], [], [], [], [])
1860 return scmutil.status([], [], [], [], [], [], [])
1860 modified, added, removed = [], [], []
1861 modified, added, removed = [], [], []
1861 self._gitupdatestat()
1862 self._gitupdatestat()
1862 if rev2:
1863 if rev2:
1863 command = ['diff-tree', '--no-renames', '-r', rev1, rev2]
1864 command = ['diff-tree', '--no-renames', '-r', rev1, rev2]
1864 else:
1865 else:
1865 command = ['diff-index', '--no-renames', rev1]
1866 command = ['diff-index', '--no-renames', rev1]
1866 out = self._gitcommand(command)
1867 out = self._gitcommand(command)
1867 for line in out.split('\n'):
1868 for line in out.split('\n'):
1868 tab = line.find('\t')
1869 tab = line.find('\t')
1869 if tab == -1:
1870 if tab == -1:
1870 continue
1871 continue
1871 status, f = line[tab - 1], line[tab + 1:]
1872 status, f = line[tab - 1], line[tab + 1:]
1872 if status == 'M':
1873 if status == 'M':
1873 modified.append(f)
1874 modified.append(f)
1874 elif status == 'A':
1875 elif status == 'A':
1875 added.append(f)
1876 added.append(f)
1876 elif status == 'D':
1877 elif status == 'D':
1877 removed.append(f)
1878 removed.append(f)
1878
1879
1879 deleted, unknown, ignored, clean = [], [], [], []
1880 deleted, unknown, ignored, clean = [], [], [], []
1880
1881
1881 command = ['status', '--porcelain', '-z']
1882 command = ['status', '--porcelain', '-z']
1882 if opts.get(r'unknown'):
1883 if opts.get(r'unknown'):
1883 command += ['--untracked-files=all']
1884 command += ['--untracked-files=all']
1884 if opts.get(r'ignored'):
1885 if opts.get(r'ignored'):
1885 command += ['--ignored']
1886 command += ['--ignored']
1886 out = self._gitcommand(command)
1887 out = self._gitcommand(command)
1887
1888
1888 changedfiles = set()
1889 changedfiles = set()
1889 changedfiles.update(modified)
1890 changedfiles.update(modified)
1890 changedfiles.update(added)
1891 changedfiles.update(added)
1891 changedfiles.update(removed)
1892 changedfiles.update(removed)
1892 for line in out.split('\0'):
1893 for line in out.split('\0'):
1893 if not line:
1894 if not line:
1894 continue
1895 continue
1895 st = line[0:2]
1896 st = line[0:2]
1896 #moves and copies show 2 files on one line
1897 #moves and copies show 2 files on one line
1897 if line.find('\0') >= 0:
1898 if line.find('\0') >= 0:
1898 filename1, filename2 = line[3:].split('\0')
1899 filename1, filename2 = line[3:].split('\0')
1899 else:
1900 else:
1900 filename1 = line[3:]
1901 filename1 = line[3:]
1901 filename2 = None
1902 filename2 = None
1902
1903
1903 changedfiles.add(filename1)
1904 changedfiles.add(filename1)
1904 if filename2:
1905 if filename2:
1905 changedfiles.add(filename2)
1906 changedfiles.add(filename2)
1906
1907
1907 if st == '??':
1908 if st == '??':
1908 unknown.append(filename1)
1909 unknown.append(filename1)
1909 elif st == '!!':
1910 elif st == '!!':
1910 ignored.append(filename1)
1911 ignored.append(filename1)
1911
1912
1912 if opts.get(r'clean'):
1913 if opts.get(r'clean'):
1913 out = self._gitcommand(['ls-files'])
1914 out = self._gitcommand(['ls-files'])
1914 for f in out.split('\n'):
1915 for f in out.split('\n'):
1915 if not f in changedfiles:
1916 if not f in changedfiles:
1916 clean.append(f)
1917 clean.append(f)
1917
1918
1918 return scmutil.status(modified, added, removed, deleted,
1919 return scmutil.status(modified, added, removed, deleted,
1919 unknown, ignored, clean)
1920 unknown, ignored, clean)
1920
1921
1921 @annotatesubrepoerror
1922 @annotatesubrepoerror
1922 def diff(self, ui, diffopts, node2, match, prefix, **opts):
1923 def diff(self, ui, diffopts, node2, match, prefix, **opts):
1923 node1 = self._state[1]
1924 node1 = self._state[1]
1924 cmd = ['diff', '--no-renames']
1925 cmd = ['diff', '--no-renames']
1925 if opts[r'stat']:
1926 if opts[r'stat']:
1926 cmd.append('--stat')
1927 cmd.append('--stat')
1927 else:
1928 else:
1928 # for Git, this also implies '-p'
1929 # for Git, this also implies '-p'
1929 cmd.append('-U%d' % diffopts.context)
1930 cmd.append('-U%d' % diffopts.context)
1930
1931
1931 gitprefix = self.wvfs.reljoin(prefix, self._path)
1932 gitprefix = self.wvfs.reljoin(prefix, self._path)
1932
1933
1933 if diffopts.noprefix:
1934 if diffopts.noprefix:
1934 cmd.extend(['--src-prefix=%s/' % gitprefix,
1935 cmd.extend(['--src-prefix=%s/' % gitprefix,
1935 '--dst-prefix=%s/' % gitprefix])
1936 '--dst-prefix=%s/' % gitprefix])
1936 else:
1937 else:
1937 cmd.extend(['--src-prefix=a/%s/' % gitprefix,
1938 cmd.extend(['--src-prefix=a/%s/' % gitprefix,
1938 '--dst-prefix=b/%s/' % gitprefix])
1939 '--dst-prefix=b/%s/' % gitprefix])
1939
1940
1940 if diffopts.ignorews:
1941 if diffopts.ignorews:
1941 cmd.append('--ignore-all-space')
1942 cmd.append('--ignore-all-space')
1942 if diffopts.ignorewsamount:
1943 if diffopts.ignorewsamount:
1943 cmd.append('--ignore-space-change')
1944 cmd.append('--ignore-space-change')
1944 if self._gitversion(self._gitcommand(['--version'])) >= (1, 8, 4) \
1945 if self._gitversion(self._gitcommand(['--version'])) >= (1, 8, 4) \
1945 and diffopts.ignoreblanklines:
1946 and diffopts.ignoreblanklines:
1946 cmd.append('--ignore-blank-lines')
1947 cmd.append('--ignore-blank-lines')
1947
1948
1948 cmd.append(node1)
1949 cmd.append(node1)
1949 if node2:
1950 if node2:
1950 cmd.append(node2)
1951 cmd.append(node2)
1951
1952
1952 output = ""
1953 output = ""
1953 if match.always():
1954 if match.always():
1954 output += self._gitcommand(cmd) + '\n'
1955 output += self._gitcommand(cmd) + '\n'
1955 else:
1956 else:
1956 st = self.status(node2)[:3]
1957 st = self.status(node2)[:3]
1957 files = [f for sublist in st for f in sublist]
1958 files = [f for sublist in st for f in sublist]
1958 for f in files:
1959 for f in files:
1959 if match(f):
1960 if match(f):
1960 output += self._gitcommand(cmd + ['--', f]) + '\n'
1961 output += self._gitcommand(cmd + ['--', f]) + '\n'
1961
1962
1962 if output.strip():
1963 if output.strip():
1963 ui.write(output)
1964 ui.write(output)
1964
1965
1965 @annotatesubrepoerror
1966 @annotatesubrepoerror
1966 def revert(self, substate, *pats, **opts):
1967 def revert(self, substate, *pats, **opts):
1967 self.ui.status(_('reverting subrepo %s\n') % substate[0])
1968 self.ui.status(_('reverting subrepo %s\n') % substate[0])
1968 if not opts.get(r'no_backup'):
1969 if not opts.get(r'no_backup'):
1969 status = self.status(None)
1970 status = self.status(None)
1970 names = status.modified
1971 names = status.modified
1971 for name in names:
1972 for name in names:
1972 bakname = scmutil.origpath(self.ui, self._subparent, name)
1973 bakname = scmutil.origpath(self.ui, self._subparent, name)
1973 self.ui.note(_('saving current version of %s as %s\n') %
1974 self.ui.note(_('saving current version of %s as %s\n') %
1974 (name, bakname))
1975 (name, bakname))
1975 self.wvfs.rename(name, bakname)
1976 self.wvfs.rename(name, bakname)
1976
1977
1977 if not opts.get(r'dry_run'):
1978 if not opts.get(r'dry_run'):
1978 self.get(substate, overwrite=True)
1979 self.get(substate, overwrite=True)
1979 return []
1980 return []
1980
1981
1981 def shortid(self, revid):
1982 def shortid(self, revid):
1982 return revid[:7]
1983 return revid[:7]
1983
1984
1984 types = {
1985 types = {
1985 'hg': hgsubrepo,
1986 'hg': hgsubrepo,
1986 'svn': svnsubrepo,
1987 'svn': svnsubrepo,
1987 'git': gitsubrepo,
1988 'git': gitsubrepo,
1988 }
1989 }
@@ -1,81 +1,121 b''
1 $ hg init
1 $ hg init
2 $ echo 0 > a
2 $ echo 0 > a
3 $ echo 0 > b
3 $ echo 0 > b
4 $ hg ci -A -m m
4 $ hg ci -A -m m
5 adding a
5 adding a
6 adding b
6 adding b
7 $ hg rm a
7 $ hg rm a
8 $ hg cat a
8 $ hg cat a
9 0
9 0
10 $ hg cat --decode a # more tests in test-encode
10 $ hg cat --decode a # more tests in test-encode
11 0
11 0
12 $ echo 1 > b
12 $ echo 1 > b
13 $ hg ci -m m
13 $ hg ci -m m
14 $ echo 2 > b
14 $ echo 2 > b
15 $ hg cat -r 0 a
15 $ hg cat -r 0 a
16 0
16 0
17 $ hg cat -r 0 b
17 $ hg cat -r 0 b
18 0
18 0
19 $ hg cat -r 1 a
19 $ hg cat -r 1 a
20 a: no such file in rev 7040230c159c
20 a: no such file in rev 7040230c159c
21 [1]
21 [1]
22 $ hg cat -r 1 b
22 $ hg cat -r 1 b
23 1
23 1
24
24
25 Test multiple files
25 Test multiple files
26
26
27 $ echo 3 > c
27 $ echo 3 > c
28 $ hg ci -Am addmore c
28 $ hg ci -Am addmore c
29 $ hg cat b c
29 $ hg cat b c
30 1
30 1
31 3
31 3
32 $ hg cat .
32 $ hg cat .
33 1
33 1
34 3
34 3
35 $ hg cat . c
35 $ hg cat . c
36 1
36 1
37 3
37 3
38
38
39 Test fileset
39 Test fileset
40
40
41 $ hg cat 'set:not(b) or a'
41 $ hg cat 'set:not(b) or a'
42 3
42 3
43 $ hg cat 'set:c or b'
43 $ hg cat 'set:c or b'
44 1
44 1
45 3
45 3
46
46
47 $ mkdir tmp
47 $ mkdir tmp
48 $ hg cat --output tmp/HH_%H c
48 $ hg cat --output tmp/HH_%H c
49 $ hg cat --output tmp/RR_%R c
49 $ hg cat --output tmp/RR_%R c
50 $ hg cat --output tmp/h_%h c
50 $ hg cat --output tmp/h_%h c
51 $ hg cat --output tmp/r_%r c
51 $ hg cat --output tmp/r_%r c
52 $ hg cat --output tmp/%s_s c
52 $ hg cat --output tmp/%s_s c
53 $ hg cat --output tmp/%d%%_d c
53 $ hg cat --output tmp/%d%%_d c
54 $ hg cat --output tmp/%p_p c
54 $ hg cat --output tmp/%p_p c
55 $ hg log -r . --template "{rev}: {node|short}\n"
55 $ hg log -r . --template "{rev}: {node|short}\n"
56 2: 45116003780e
56 2: 45116003780e
57 $ find tmp -type f | sort
57 $ find tmp -type f | sort
58 tmp/.%_d
58 tmp/.%_d
59 tmp/HH_45116003780e3678b333fb2c99fa7d559c8457e9
59 tmp/HH_45116003780e3678b333fb2c99fa7d559c8457e9
60 tmp/RR_2
60 tmp/RR_2
61 tmp/c_p
61 tmp/c_p
62 tmp/c_s
62 tmp/c_s
63 tmp/h_45116003780e
63 tmp/h_45116003780e
64 tmp/r_2
64 tmp/r_2
65
65
66 Test template output
67
68 $ hg --cwd tmp cat ../b ../c -T '== {path} ({abspath}) ==\n{data}'
69 == ../b (b) == (glob)
70 1
71 == ../c (c) == (glob)
72 3
73
74 $ hg cat b c -Tjson --output -
75 [
76 {
77 "abspath": "b",
78 "data": "1\n",
79 "path": "b"
80 },
81 {
82 "abspath": "c",
83 "data": "3\n",
84 "path": "c"
85 }
86 ]
87
88 $ hg cat b c -Tjson --output 'tmp/%p.json'
89 $ cat tmp/b.json
90 [
91 {
92 "abspath": "b",
93 "data": "1\n",
94 "path": "b"
95 }
96 ]
97 $ cat tmp/c.json
98 [
99 {
100 "abspath": "c",
101 "data": "3\n",
102 "path": "c"
103 }
104 ]
105
66 Test working directory
106 Test working directory
67
107
68 $ echo b-wdir > b
108 $ echo b-wdir > b
69 $ hg cat -r 'wdir()' b
109 $ hg cat -r 'wdir()' b
70 b-wdir
110 b-wdir
71
111
72 Environment variables are not visible by default
112 Environment variables are not visible by default
73
113
74 $ PATTERN='t4' hg log -r '.' -T "{ifcontains('PATTERN', envvars, 'yes', 'no')}\n"
114 $ PATTERN='t4' hg log -r '.' -T "{ifcontains('PATTERN', envvars, 'yes', 'no')}\n"
75 no
115 no
76
116
77 Environment variable visibility can be explicit
117 Environment variable visibility can be explicit
78
118
79 $ PATTERN='t4' hg log -r '.' -T "{envvars % '{key} -> {value}\n'}" \
119 $ PATTERN='t4' hg log -r '.' -T "{envvars % '{key} -> {value}\n'}" \
80 > --config "experimental.exportableenviron=PATTERN"
120 > --config "experimental.exportableenviron=PATTERN"
81 PATTERN -> t4
121 PATTERN -> t4
@@ -1,381 +1,381 b''
1 Show all commands except debug commands
1 Show all commands except debug commands
2 $ hg debugcomplete
2 $ hg debugcomplete
3 add
3 add
4 addremove
4 addremove
5 annotate
5 annotate
6 archive
6 archive
7 backout
7 backout
8 bisect
8 bisect
9 bookmarks
9 bookmarks
10 branch
10 branch
11 branches
11 branches
12 bundle
12 bundle
13 cat
13 cat
14 clone
14 clone
15 commit
15 commit
16 config
16 config
17 copy
17 copy
18 diff
18 diff
19 export
19 export
20 files
20 files
21 forget
21 forget
22 graft
22 graft
23 grep
23 grep
24 heads
24 heads
25 help
25 help
26 identify
26 identify
27 import
27 import
28 incoming
28 incoming
29 init
29 init
30 locate
30 locate
31 log
31 log
32 manifest
32 manifest
33 merge
33 merge
34 outgoing
34 outgoing
35 parents
35 parents
36 paths
36 paths
37 phase
37 phase
38 pull
38 pull
39 push
39 push
40 recover
40 recover
41 remove
41 remove
42 rename
42 rename
43 resolve
43 resolve
44 revert
44 revert
45 rollback
45 rollback
46 root
46 root
47 serve
47 serve
48 status
48 status
49 summary
49 summary
50 tag
50 tag
51 tags
51 tags
52 tip
52 tip
53 unbundle
53 unbundle
54 update
54 update
55 verify
55 verify
56 version
56 version
57
57
58 Show all commands that start with "a"
58 Show all commands that start with "a"
59 $ hg debugcomplete a
59 $ hg debugcomplete a
60 add
60 add
61 addremove
61 addremove
62 annotate
62 annotate
63 archive
63 archive
64
64
65 Do not show debug commands if there are other candidates
65 Do not show debug commands if there are other candidates
66 $ hg debugcomplete d
66 $ hg debugcomplete d
67 diff
67 diff
68
68
69 Show debug commands if there are no other candidates
69 Show debug commands if there are no other candidates
70 $ hg debugcomplete debug
70 $ hg debugcomplete debug
71 debugancestor
71 debugancestor
72 debugapplystreamclonebundle
72 debugapplystreamclonebundle
73 debugbuilddag
73 debugbuilddag
74 debugbundle
74 debugbundle
75 debugcheckstate
75 debugcheckstate
76 debugcolor
76 debugcolor
77 debugcommands
77 debugcommands
78 debugcomplete
78 debugcomplete
79 debugconfig
79 debugconfig
80 debugcreatestreamclonebundle
80 debugcreatestreamclonebundle
81 debugdag
81 debugdag
82 debugdata
82 debugdata
83 debugdate
83 debugdate
84 debugdeltachain
84 debugdeltachain
85 debugdirstate
85 debugdirstate
86 debugdiscovery
86 debugdiscovery
87 debugextensions
87 debugextensions
88 debugfileset
88 debugfileset
89 debugfsinfo
89 debugfsinfo
90 debuggetbundle
90 debuggetbundle
91 debugignore
91 debugignore
92 debugindex
92 debugindex
93 debugindexdot
93 debugindexdot
94 debuginstall
94 debuginstall
95 debugknown
95 debugknown
96 debuglabelcomplete
96 debuglabelcomplete
97 debuglocks
97 debuglocks
98 debugmergestate
98 debugmergestate
99 debugnamecomplete
99 debugnamecomplete
100 debugobsolete
100 debugobsolete
101 debugpathcomplete
101 debugpathcomplete
102 debugpickmergetool
102 debugpickmergetool
103 debugpushkey
103 debugpushkey
104 debugpvec
104 debugpvec
105 debugrebuilddirstate
105 debugrebuilddirstate
106 debugrebuildfncache
106 debugrebuildfncache
107 debugrename
107 debugrename
108 debugrevlog
108 debugrevlog
109 debugrevspec
109 debugrevspec
110 debugsetparents
110 debugsetparents
111 debugsub
111 debugsub
112 debugsuccessorssets
112 debugsuccessorssets
113 debugtemplate
113 debugtemplate
114 debugupdatecaches
114 debugupdatecaches
115 debugupgraderepo
115 debugupgraderepo
116 debugwalk
116 debugwalk
117 debugwireargs
117 debugwireargs
118
118
119 Do not show the alias of a debug command if there are other candidates
119 Do not show the alias of a debug command if there are other candidates
120 (this should hide rawcommit)
120 (this should hide rawcommit)
121 $ hg debugcomplete r
121 $ hg debugcomplete r
122 recover
122 recover
123 remove
123 remove
124 rename
124 rename
125 resolve
125 resolve
126 revert
126 revert
127 rollback
127 rollback
128 root
128 root
129 Show the alias of a debug command if there are no other candidates
129 Show the alias of a debug command if there are no other candidates
130 $ hg debugcomplete rawc
130 $ hg debugcomplete rawc
131
131
132
132
133 Show the global options
133 Show the global options
134 $ hg debugcomplete --options | sort
134 $ hg debugcomplete --options | sort
135 --color
135 --color
136 --config
136 --config
137 --cwd
137 --cwd
138 --debug
138 --debug
139 --debugger
139 --debugger
140 --encoding
140 --encoding
141 --encodingmode
141 --encodingmode
142 --help
142 --help
143 --hidden
143 --hidden
144 --noninteractive
144 --noninteractive
145 --pager
145 --pager
146 --profile
146 --profile
147 --quiet
147 --quiet
148 --repository
148 --repository
149 --time
149 --time
150 --traceback
150 --traceback
151 --verbose
151 --verbose
152 --version
152 --version
153 -R
153 -R
154 -h
154 -h
155 -q
155 -q
156 -v
156 -v
157 -y
157 -y
158
158
159 Show the options for the "serve" command
159 Show the options for the "serve" command
160 $ hg debugcomplete --options serve | sort
160 $ hg debugcomplete --options serve | sort
161 --accesslog
161 --accesslog
162 --address
162 --address
163 --certificate
163 --certificate
164 --cmdserver
164 --cmdserver
165 --color
165 --color
166 --config
166 --config
167 --cwd
167 --cwd
168 --daemon
168 --daemon
169 --daemon-postexec
169 --daemon-postexec
170 --debug
170 --debug
171 --debugger
171 --debugger
172 --encoding
172 --encoding
173 --encodingmode
173 --encodingmode
174 --errorlog
174 --errorlog
175 --help
175 --help
176 --hidden
176 --hidden
177 --ipv6
177 --ipv6
178 --name
178 --name
179 --noninteractive
179 --noninteractive
180 --pager
180 --pager
181 --pid-file
181 --pid-file
182 --port
182 --port
183 --prefix
183 --prefix
184 --profile
184 --profile
185 --quiet
185 --quiet
186 --repository
186 --repository
187 --stdio
187 --stdio
188 --style
188 --style
189 --subrepos
189 --subrepos
190 --templates
190 --templates
191 --time
191 --time
192 --traceback
192 --traceback
193 --verbose
193 --verbose
194 --version
194 --version
195 --web-conf
195 --web-conf
196 -6
196 -6
197 -A
197 -A
198 -E
198 -E
199 -R
199 -R
200 -S
200 -S
201 -a
201 -a
202 -d
202 -d
203 -h
203 -h
204 -n
204 -n
205 -p
205 -p
206 -q
206 -q
207 -t
207 -t
208 -v
208 -v
209 -y
209 -y
210
210
211 Show an error if we use --options with an ambiguous abbreviation
211 Show an error if we use --options with an ambiguous abbreviation
212 $ hg debugcomplete --options s
212 $ hg debugcomplete --options s
213 hg: command 's' is ambiguous:
213 hg: command 's' is ambiguous:
214 serve showconfig status summary
214 serve showconfig status summary
215 [255]
215 [255]
216
216
217 Show all commands + options
217 Show all commands + options
218 $ hg debugcommands
218 $ hg debugcommands
219 add: include, exclude, subrepos, dry-run
219 add: include, exclude, subrepos, dry-run
220 annotate: rev, follow, no-follow, text, user, file, date, number, changeset, line-number, skip, ignore-all-space, ignore-space-change, ignore-blank-lines, include, exclude, template
220 annotate: rev, follow, no-follow, text, user, file, date, number, changeset, line-number, skip, ignore-all-space, ignore-space-change, ignore-blank-lines, include, exclude, template
221 clone: noupdate, updaterev, rev, branch, pull, uncompressed, ssh, remotecmd, insecure
221 clone: noupdate, updaterev, rev, branch, pull, uncompressed, ssh, remotecmd, insecure
222 commit: addremove, close-branch, amend, secret, edit, interactive, include, exclude, message, logfile, date, user, subrepos
222 commit: addremove, close-branch, amend, secret, edit, interactive, include, exclude, message, logfile, date, user, subrepos
223 diff: rev, change, text, git, binary, nodates, noprefix, show-function, reverse, ignore-all-space, ignore-space-change, ignore-blank-lines, unified, stat, root, include, exclude, subrepos
223 diff: rev, change, text, git, binary, nodates, noprefix, show-function, reverse, ignore-all-space, ignore-space-change, ignore-blank-lines, unified, stat, root, include, exclude, subrepos
224 export: output, switch-parent, rev, text, git, binary, nodates
224 export: output, switch-parent, rev, text, git, binary, nodates
225 forget: include, exclude
225 forget: include, exclude
226 init: ssh, remotecmd, insecure
226 init: ssh, remotecmd, insecure
227 log: follow, follow-first, date, copies, keyword, rev, removed, only-merges, user, only-branch, branch, prune, patch, git, limit, no-merges, stat, graph, style, template, include, exclude
227 log: follow, follow-first, date, copies, keyword, rev, removed, only-merges, user, only-branch, branch, prune, patch, git, limit, no-merges, stat, graph, style, template, include, exclude
228 merge: force, rev, preview, tool
228 merge: force, rev, preview, tool
229 pull: update, force, rev, bookmark, branch, ssh, remotecmd, insecure
229 pull: update, force, rev, bookmark, branch, ssh, remotecmd, insecure
230 push: force, rev, bookmark, branch, new-branch, ssh, remotecmd, insecure
230 push: force, rev, bookmark, branch, new-branch, ssh, remotecmd, insecure
231 remove: after, force, subrepos, include, exclude
231 remove: after, force, subrepos, include, exclude
232 serve: accesslog, daemon, daemon-postexec, errorlog, port, address, prefix, name, web-conf, webdir-conf, pid-file, stdio, cmdserver, templates, style, ipv6, certificate, subrepos
232 serve: accesslog, daemon, daemon-postexec, errorlog, port, address, prefix, name, web-conf, webdir-conf, pid-file, stdio, cmdserver, templates, style, ipv6, certificate, subrepos
233 status: all, modified, added, removed, deleted, clean, unknown, ignored, no-status, copies, print0, rev, change, include, exclude, subrepos, template
233 status: all, modified, added, removed, deleted, clean, unknown, ignored, no-status, copies, print0, rev, change, include, exclude, subrepos, template
234 summary: remote
234 summary: remote
235 update: clean, check, merge, date, rev, tool
235 update: clean, check, merge, date, rev, tool
236 addremove: similarity, subrepos, include, exclude, dry-run
236 addremove: similarity, subrepos, include, exclude, dry-run
237 archive: no-decode, prefix, rev, type, subrepos, include, exclude
237 archive: no-decode, prefix, rev, type, subrepos, include, exclude
238 backout: merge, commit, no-commit, parent, rev, edit, tool, include, exclude, message, logfile, date, user
238 backout: merge, commit, no-commit, parent, rev, edit, tool, include, exclude, message, logfile, date, user
239 bisect: reset, good, bad, skip, extend, command, noupdate
239 bisect: reset, good, bad, skip, extend, command, noupdate
240 bookmarks: force, rev, delete, rename, inactive, template
240 bookmarks: force, rev, delete, rename, inactive, template
241 branch: force, clean
241 branch: force, clean
242 branches: active, closed, template
242 branches: active, closed, template
243 bundle: force, rev, branch, base, all, type, ssh, remotecmd, insecure
243 bundle: force, rev, branch, base, all, type, ssh, remotecmd, insecure
244 cat: output, rev, decode, include, exclude
244 cat: output, rev, decode, include, exclude, template
245 config: untrusted, edit, local, global, template
245 config: untrusted, edit, local, global, template
246 copy: after, force, include, exclude, dry-run
246 copy: after, force, include, exclude, dry-run
247 debugancestor:
247 debugancestor:
248 debugapplystreamclonebundle:
248 debugapplystreamclonebundle:
249 debugbuilddag: mergeable-file, overwritten-file, new-file
249 debugbuilddag: mergeable-file, overwritten-file, new-file
250 debugbundle: all, spec
250 debugbundle: all, spec
251 debugcheckstate:
251 debugcheckstate:
252 debugcolor: style
252 debugcolor: style
253 debugcommands:
253 debugcommands:
254 debugcomplete: options
254 debugcomplete: options
255 debugcreatestreamclonebundle:
255 debugcreatestreamclonebundle:
256 debugdag: tags, branches, dots, spaces
256 debugdag: tags, branches, dots, spaces
257 debugdata: changelog, manifest, dir
257 debugdata: changelog, manifest, dir
258 debugdate: extended
258 debugdate: extended
259 debugdeltachain: changelog, manifest, dir, template
259 debugdeltachain: changelog, manifest, dir, template
260 debugdirstate: nodates, datesort
260 debugdirstate: nodates, datesort
261 debugdiscovery: old, nonheads, ssh, remotecmd, insecure
261 debugdiscovery: old, nonheads, ssh, remotecmd, insecure
262 debugextensions: template
262 debugextensions: template
263 debugfileset: rev
263 debugfileset: rev
264 debugfsinfo:
264 debugfsinfo:
265 debuggetbundle: head, common, type
265 debuggetbundle: head, common, type
266 debugignore:
266 debugignore:
267 debugindex: changelog, manifest, dir, format
267 debugindex: changelog, manifest, dir, format
268 debugindexdot: changelog, manifest, dir
268 debugindexdot: changelog, manifest, dir
269 debuginstall: template
269 debuginstall: template
270 debugknown:
270 debugknown:
271 debuglabelcomplete:
271 debuglabelcomplete:
272 debuglocks: force-lock, force-wlock
272 debuglocks: force-lock, force-wlock
273 debugmergestate:
273 debugmergestate:
274 debugnamecomplete:
274 debugnamecomplete:
275 debugobsolete: flags, record-parents, rev, index, delete, date, user, template
275 debugobsolete: flags, record-parents, rev, index, delete, date, user, template
276 debugpathcomplete: full, normal, added, removed
276 debugpathcomplete: full, normal, added, removed
277 debugpickmergetool: rev, changedelete, include, exclude, tool
277 debugpickmergetool: rev, changedelete, include, exclude, tool
278 debugpushkey:
278 debugpushkey:
279 debugpvec:
279 debugpvec:
280 debugrebuilddirstate: rev, minimal
280 debugrebuilddirstate: rev, minimal
281 debugrebuildfncache:
281 debugrebuildfncache:
282 debugrename: rev
282 debugrename: rev
283 debugrevlog: changelog, manifest, dir, dump
283 debugrevlog: changelog, manifest, dir, dump
284 debugrevspec: optimize, show-stage, no-optimized, verify-optimized
284 debugrevspec: optimize, show-stage, no-optimized, verify-optimized
285 debugsetparents:
285 debugsetparents:
286 debugsub: rev
286 debugsub: rev
287 debugsuccessorssets:
287 debugsuccessorssets:
288 debugtemplate: rev, define
288 debugtemplate: rev, define
289 debugupdatecaches:
289 debugupdatecaches:
290 debugupgraderepo: optimize, run
290 debugupgraderepo: optimize, run
291 debugwalk: include, exclude
291 debugwalk: include, exclude
292 debugwireargs: three, four, five, ssh, remotecmd, insecure
292 debugwireargs: three, four, five, ssh, remotecmd, insecure
293 files: rev, print0, include, exclude, template, subrepos
293 files: rev, print0, include, exclude, template, subrepos
294 graft: rev, continue, edit, log, force, currentdate, currentuser, date, user, tool, dry-run
294 graft: rev, continue, edit, log, force, currentdate, currentuser, date, user, tool, dry-run
295 grep: print0, all, text, follow, ignore-case, files-with-matches, line-number, rev, user, date, template, include, exclude
295 grep: print0, all, text, follow, ignore-case, files-with-matches, line-number, rev, user, date, template, include, exclude
296 heads: rev, topo, active, closed, style, template
296 heads: rev, topo, active, closed, style, template
297 help: extension, command, keyword, system
297 help: extension, command, keyword, system
298 identify: rev, num, id, branch, tags, bookmarks, ssh, remotecmd, insecure
298 identify: rev, num, id, branch, tags, bookmarks, ssh, remotecmd, insecure
299 import: strip, base, edit, force, no-commit, bypass, partial, exact, prefix, import-branch, message, logfile, date, user, similarity
299 import: strip, base, edit, force, no-commit, bypass, partial, exact, prefix, import-branch, message, logfile, date, user, similarity
300 incoming: force, newest-first, bundle, rev, bookmarks, branch, patch, git, limit, no-merges, stat, graph, style, template, ssh, remotecmd, insecure, subrepos
300 incoming: force, newest-first, bundle, rev, bookmarks, branch, patch, git, limit, no-merges, stat, graph, style, template, ssh, remotecmd, insecure, subrepos
301 locate: rev, print0, fullpath, include, exclude
301 locate: rev, print0, fullpath, include, exclude
302 manifest: rev, all, template
302 manifest: rev, all, template
303 outgoing: force, rev, newest-first, bookmarks, branch, patch, git, limit, no-merges, stat, graph, style, template, ssh, remotecmd, insecure, subrepos
303 outgoing: force, rev, newest-first, bookmarks, branch, patch, git, limit, no-merges, stat, graph, style, template, ssh, remotecmd, insecure, subrepos
304 parents: rev, style, template
304 parents: rev, style, template
305 paths: template
305 paths: template
306 phase: public, draft, secret, force, rev
306 phase: public, draft, secret, force, rev
307 recover:
307 recover:
308 rename: after, force, include, exclude, dry-run
308 rename: after, force, include, exclude, dry-run
309 resolve: all, list, mark, unmark, no-status, tool, include, exclude, template
309 resolve: all, list, mark, unmark, no-status, tool, include, exclude, template
310 revert: all, date, rev, no-backup, interactive, include, exclude, dry-run
310 revert: all, date, rev, no-backup, interactive, include, exclude, dry-run
311 rollback: dry-run, force
311 rollback: dry-run, force
312 root:
312 root:
313 tag: force, local, rev, remove, edit, message, date, user
313 tag: force, local, rev, remove, edit, message, date, user
314 tags: template
314 tags: template
315 tip: patch, git, style, template
315 tip: patch, git, style, template
316 unbundle: update
316 unbundle: update
317 verify:
317 verify:
318 version: template
318 version: template
319
319
320 $ hg init a
320 $ hg init a
321 $ cd a
321 $ cd a
322 $ echo fee > fee
322 $ echo fee > fee
323 $ hg ci -q -Amfee
323 $ hg ci -q -Amfee
324 $ hg tag fee
324 $ hg tag fee
325 $ mkdir fie
325 $ mkdir fie
326 $ echo dead > fie/dead
326 $ echo dead > fie/dead
327 $ echo live > fie/live
327 $ echo live > fie/live
328 $ hg bookmark fo
328 $ hg bookmark fo
329 $ hg branch -q fie
329 $ hg branch -q fie
330 $ hg ci -q -Amfie
330 $ hg ci -q -Amfie
331 $ echo fo > fo
331 $ echo fo > fo
332 $ hg branch -qf default
332 $ hg branch -qf default
333 $ hg ci -q -Amfo
333 $ hg ci -q -Amfo
334 $ echo Fum > Fum
334 $ echo Fum > Fum
335 $ hg ci -q -AmFum
335 $ hg ci -q -AmFum
336 $ hg bookmark Fum
336 $ hg bookmark Fum
337
337
338 Test debugpathcomplete
338 Test debugpathcomplete
339
339
340 $ hg debugpathcomplete f
340 $ hg debugpathcomplete f
341 fee
341 fee
342 fie
342 fie
343 fo
343 fo
344 $ hg debugpathcomplete -f f
344 $ hg debugpathcomplete -f f
345 fee
345 fee
346 fie/dead
346 fie/dead
347 fie/live
347 fie/live
348 fo
348 fo
349
349
350 $ hg rm Fum
350 $ hg rm Fum
351 $ hg debugpathcomplete -r F
351 $ hg debugpathcomplete -r F
352 Fum
352 Fum
353
353
354 Test debugnamecomplete
354 Test debugnamecomplete
355
355
356 $ hg debugnamecomplete
356 $ hg debugnamecomplete
357 Fum
357 Fum
358 default
358 default
359 fee
359 fee
360 fie
360 fie
361 fo
361 fo
362 tip
362 tip
363 $ hg debugnamecomplete f
363 $ hg debugnamecomplete f
364 fee
364 fee
365 fie
365 fie
366 fo
366 fo
367
367
368 Test debuglabelcomplete, a deprecated name for debugnamecomplete that is still
368 Test debuglabelcomplete, a deprecated name for debugnamecomplete that is still
369 used for completions in some shells.
369 used for completions in some shells.
370
370
371 $ hg debuglabelcomplete
371 $ hg debuglabelcomplete
372 Fum
372 Fum
373 default
373 default
374 fee
374 fee
375 fie
375 fie
376 fo
376 fo
377 tip
377 tip
378 $ hg debuglabelcomplete f
378 $ hg debuglabelcomplete f
379 fee
379 fee
380 fie
380 fie
381 fo
381 fo
@@ -1,936 +1,936 b''
1 commit hooks can see env vars
1 commit hooks can see env vars
2 (and post-transaction one are run unlocked)
2 (and post-transaction one are run unlocked)
3
3
4
4
5 $ cat > $TESTTMP/txnabort.checkargs.py <<EOF
5 $ cat > $TESTTMP/txnabort.checkargs.py <<EOF
6 > def showargs(ui, repo, hooktype, **kwargs):
6 > def showargs(ui, repo, hooktype, **kwargs):
7 > ui.write('%s python hook: %s\n' % (hooktype, ','.join(sorted(kwargs))))
7 > ui.write('%s python hook: %s\n' % (hooktype, ','.join(sorted(kwargs))))
8 > EOF
8 > EOF
9
9
10 $ hg init a
10 $ hg init a
11 $ cd a
11 $ cd a
12 $ cat > .hg/hgrc <<EOF
12 $ cat > .hg/hgrc <<EOF
13 > [hooks]
13 > [hooks]
14 > commit = sh -c "HG_LOCAL= HG_TAG= printenv.py commit"
14 > commit = sh -c "HG_LOCAL= HG_TAG= printenv.py commit"
15 > commit.b = sh -c "HG_LOCAL= HG_TAG= printenv.py commit.b"
15 > commit.b = sh -c "HG_LOCAL= HG_TAG= printenv.py commit.b"
16 > precommit = sh -c "HG_LOCAL= HG_NODE= HG_TAG= printenv.py precommit"
16 > precommit = sh -c "HG_LOCAL= HG_NODE= HG_TAG= printenv.py precommit"
17 > pretxncommit = sh -c "HG_LOCAL= HG_TAG= printenv.py pretxncommit"
17 > pretxncommit = sh -c "HG_LOCAL= HG_TAG= printenv.py pretxncommit"
18 > pretxncommit.tip = hg -q tip
18 > pretxncommit.tip = hg -q tip
19 > pre-identify = sh -c "printenv.py pre-identify 1"
19 > pre-identify = sh -c "printenv.py pre-identify 1"
20 > pre-cat = sh -c "printenv.py pre-cat"
20 > pre-cat = sh -c "printenv.py pre-cat"
21 > post-cat = sh -c "printenv.py post-cat"
21 > post-cat = sh -c "printenv.py post-cat"
22 > pretxnopen = sh -c "HG_LOCAL= HG_TAG= printenv.py pretxnopen"
22 > pretxnopen = sh -c "HG_LOCAL= HG_TAG= printenv.py pretxnopen"
23 > pretxnclose = sh -c "HG_LOCAL= HG_TAG= printenv.py pretxnclose"
23 > pretxnclose = sh -c "HG_LOCAL= HG_TAG= printenv.py pretxnclose"
24 > txnclose = sh -c "HG_LOCAL= HG_TAG= printenv.py txnclose"
24 > txnclose = sh -c "HG_LOCAL= HG_TAG= printenv.py txnclose"
25 > txnabort.0 = python:$TESTTMP/txnabort.checkargs.py:showargs
25 > txnabort.0 = python:$TESTTMP/txnabort.checkargs.py:showargs
26 > txnabort.1 = sh -c "HG_LOCAL= HG_TAG= printenv.py txnabort"
26 > txnabort.1 = sh -c "HG_LOCAL= HG_TAG= printenv.py txnabort"
27 > txnclose.checklock = sh -c "hg debuglock > /dev/null"
27 > txnclose.checklock = sh -c "hg debuglock > /dev/null"
28 > EOF
28 > EOF
29 $ echo a > a
29 $ echo a > a
30 $ hg add a
30 $ hg add a
31 $ hg commit -m a
31 $ hg commit -m a
32 precommit hook: HG_HOOKNAME=precommit HG_HOOKTYPE=precommit HG_PARENT1=0000000000000000000000000000000000000000
32 precommit hook: HG_HOOKNAME=precommit HG_HOOKTYPE=precommit HG_PARENT1=0000000000000000000000000000000000000000
33 pretxnopen hook: HG_HOOKNAME=pretxnopen HG_HOOKTYPE=pretxnopen HG_TXNID=TXN:$ID$ HG_TXNNAME=commit
33 pretxnopen hook: HG_HOOKNAME=pretxnopen HG_HOOKTYPE=pretxnopen HG_TXNID=TXN:$ID$ HG_TXNNAME=commit
34 pretxncommit hook: HG_HOOKNAME=pretxncommit HG_HOOKTYPE=pretxncommit HG_NODE=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b HG_PARENT1=0000000000000000000000000000000000000000 HG_PENDING=$TESTTMP/a
34 pretxncommit hook: HG_HOOKNAME=pretxncommit HG_HOOKTYPE=pretxncommit HG_NODE=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b HG_PARENT1=0000000000000000000000000000000000000000 HG_PENDING=$TESTTMP/a
35 0:cb9a9f314b8b
35 0:cb9a9f314b8b
36 pretxnclose hook: HG_HOOKNAME=pretxnclose HG_HOOKTYPE=pretxnclose HG_PENDING=$TESTTMP/a HG_PHASES_MOVED=1 HG_TXNID=TXN:$ID$ HG_TXNNAME=commit
36 pretxnclose hook: HG_HOOKNAME=pretxnclose HG_HOOKTYPE=pretxnclose HG_PENDING=$TESTTMP/a HG_PHASES_MOVED=1 HG_TXNID=TXN:$ID$ HG_TXNNAME=commit
37 txnclose hook: HG_HOOKNAME=txnclose HG_HOOKTYPE=txnclose HG_PHASES_MOVED=1 HG_TXNID=TXN:$ID$ HG_TXNNAME=commit
37 txnclose hook: HG_HOOKNAME=txnclose HG_HOOKTYPE=txnclose HG_PHASES_MOVED=1 HG_TXNID=TXN:$ID$ HG_TXNNAME=commit
38 commit hook: HG_HOOKNAME=commit HG_HOOKTYPE=commit HG_NODE=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b HG_PARENT1=0000000000000000000000000000000000000000
38 commit hook: HG_HOOKNAME=commit HG_HOOKTYPE=commit HG_NODE=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b HG_PARENT1=0000000000000000000000000000000000000000
39 commit.b hook: HG_HOOKNAME=commit.b HG_HOOKTYPE=commit HG_NODE=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b HG_PARENT1=0000000000000000000000000000000000000000
39 commit.b hook: HG_HOOKNAME=commit.b HG_HOOKTYPE=commit HG_NODE=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b HG_PARENT1=0000000000000000000000000000000000000000
40
40
41 $ hg clone . ../b
41 $ hg clone . ../b
42 updating to branch default
42 updating to branch default
43 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
43 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
44 $ cd ../b
44 $ cd ../b
45
45
46 changegroup hooks can see env vars
46 changegroup hooks can see env vars
47
47
48 $ cat > .hg/hgrc <<EOF
48 $ cat > .hg/hgrc <<EOF
49 > [hooks]
49 > [hooks]
50 > prechangegroup = sh -c "printenv.py prechangegroup"
50 > prechangegroup = sh -c "printenv.py prechangegroup"
51 > changegroup = sh -c "printenv.py changegroup"
51 > changegroup = sh -c "printenv.py changegroup"
52 > incoming = sh -c "printenv.py incoming"
52 > incoming = sh -c "printenv.py incoming"
53 > EOF
53 > EOF
54
54
55 pretxncommit and commit hooks can see both parents of merge
55 pretxncommit and commit hooks can see both parents of merge
56
56
57 $ cd ../a
57 $ cd ../a
58 $ echo b >> a
58 $ echo b >> a
59 $ hg commit -m a1 -d "1 0"
59 $ hg commit -m a1 -d "1 0"
60 precommit hook: HG_HOOKNAME=precommit HG_HOOKTYPE=precommit HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
60 precommit hook: HG_HOOKNAME=precommit HG_HOOKTYPE=precommit HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
61 pretxnopen hook: HG_HOOKNAME=pretxnopen HG_HOOKTYPE=pretxnopen HG_TXNID=TXN:$ID$ HG_TXNNAME=commit
61 pretxnopen hook: HG_HOOKNAME=pretxnopen HG_HOOKTYPE=pretxnopen HG_TXNID=TXN:$ID$ HG_TXNNAME=commit
62 pretxncommit hook: HG_HOOKNAME=pretxncommit HG_HOOKTYPE=pretxncommit HG_NODE=ab228980c14deea8b9555d91c9581127383e40fd HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b HG_PENDING=$TESTTMP/a
62 pretxncommit hook: HG_HOOKNAME=pretxncommit HG_HOOKTYPE=pretxncommit HG_NODE=ab228980c14deea8b9555d91c9581127383e40fd HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b HG_PENDING=$TESTTMP/a
63 1:ab228980c14d
63 1:ab228980c14d
64 pretxnclose hook: HG_HOOKNAME=pretxnclose HG_HOOKTYPE=pretxnclose HG_PENDING=$TESTTMP/a HG_TXNID=TXN:$ID$ HG_TXNNAME=commit
64 pretxnclose hook: HG_HOOKNAME=pretxnclose HG_HOOKTYPE=pretxnclose HG_PENDING=$TESTTMP/a HG_TXNID=TXN:$ID$ HG_TXNNAME=commit
65 txnclose hook: HG_HOOKNAME=txnclose HG_HOOKTYPE=txnclose HG_TXNID=TXN:$ID$ HG_TXNNAME=commit
65 txnclose hook: HG_HOOKNAME=txnclose HG_HOOKTYPE=txnclose HG_TXNID=TXN:$ID$ HG_TXNNAME=commit
66 commit hook: HG_HOOKNAME=commit HG_HOOKTYPE=commit HG_NODE=ab228980c14deea8b9555d91c9581127383e40fd HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
66 commit hook: HG_HOOKNAME=commit HG_HOOKTYPE=commit HG_NODE=ab228980c14deea8b9555d91c9581127383e40fd HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
67 commit.b hook: HG_HOOKNAME=commit.b HG_HOOKTYPE=commit HG_NODE=ab228980c14deea8b9555d91c9581127383e40fd HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
67 commit.b hook: HG_HOOKNAME=commit.b HG_HOOKTYPE=commit HG_NODE=ab228980c14deea8b9555d91c9581127383e40fd HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
68 $ hg update -C 0
68 $ hg update -C 0
69 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
69 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
70 $ echo b > b
70 $ echo b > b
71 $ hg add b
71 $ hg add b
72 $ hg commit -m b -d '1 0'
72 $ hg commit -m b -d '1 0'
73 precommit hook: HG_HOOKNAME=precommit HG_HOOKTYPE=precommit HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
73 precommit hook: HG_HOOKNAME=precommit HG_HOOKTYPE=precommit HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
74 pretxnopen hook: HG_HOOKNAME=pretxnopen HG_HOOKTYPE=pretxnopen HG_TXNID=TXN:$ID$ HG_TXNNAME=commit
74 pretxnopen hook: HG_HOOKNAME=pretxnopen HG_HOOKTYPE=pretxnopen HG_TXNID=TXN:$ID$ HG_TXNNAME=commit
75 pretxncommit hook: HG_HOOKNAME=pretxncommit HG_HOOKTYPE=pretxncommit HG_NODE=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b HG_PENDING=$TESTTMP/a
75 pretxncommit hook: HG_HOOKNAME=pretxncommit HG_HOOKTYPE=pretxncommit HG_NODE=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b HG_PENDING=$TESTTMP/a
76 2:ee9deb46ab31
76 2:ee9deb46ab31
77 pretxnclose hook: HG_HOOKNAME=pretxnclose HG_HOOKTYPE=pretxnclose HG_PENDING=$TESTTMP/a HG_TXNID=TXN:$ID$ HG_TXNNAME=commit
77 pretxnclose hook: HG_HOOKNAME=pretxnclose HG_HOOKTYPE=pretxnclose HG_PENDING=$TESTTMP/a HG_TXNID=TXN:$ID$ HG_TXNNAME=commit
78 created new head
78 created new head
79 txnclose hook: HG_HOOKNAME=txnclose HG_HOOKTYPE=txnclose HG_TXNID=TXN:$ID$ HG_TXNNAME=commit
79 txnclose hook: HG_HOOKNAME=txnclose HG_HOOKTYPE=txnclose HG_TXNID=TXN:$ID$ HG_TXNNAME=commit
80 commit hook: HG_HOOKNAME=commit HG_HOOKTYPE=commit HG_NODE=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
80 commit hook: HG_HOOKNAME=commit HG_HOOKTYPE=commit HG_NODE=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
81 commit.b hook: HG_HOOKNAME=commit.b HG_HOOKTYPE=commit HG_NODE=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
81 commit.b hook: HG_HOOKNAME=commit.b HG_HOOKTYPE=commit HG_NODE=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT1=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
82 $ hg merge 1
82 $ hg merge 1
83 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
83 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
84 (branch merge, don't forget to commit)
84 (branch merge, don't forget to commit)
85 $ hg commit -m merge -d '2 0'
85 $ hg commit -m merge -d '2 0'
86 precommit hook: HG_HOOKNAME=precommit HG_HOOKTYPE=precommit HG_PARENT1=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT2=ab228980c14deea8b9555d91c9581127383e40fd
86 precommit hook: HG_HOOKNAME=precommit HG_HOOKTYPE=precommit HG_PARENT1=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT2=ab228980c14deea8b9555d91c9581127383e40fd
87 pretxnopen hook: HG_HOOKNAME=pretxnopen HG_HOOKTYPE=pretxnopen HG_TXNID=TXN:$ID$ HG_TXNNAME=commit
87 pretxnopen hook: HG_HOOKNAME=pretxnopen HG_HOOKTYPE=pretxnopen HG_TXNID=TXN:$ID$ HG_TXNNAME=commit
88 pretxncommit hook: HG_HOOKNAME=pretxncommit HG_HOOKTYPE=pretxncommit HG_NODE=07f3376c1e655977439df2a814e3cc14b27abac2 HG_PARENT1=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT2=ab228980c14deea8b9555d91c9581127383e40fd HG_PENDING=$TESTTMP/a
88 pretxncommit hook: HG_HOOKNAME=pretxncommit HG_HOOKTYPE=pretxncommit HG_NODE=07f3376c1e655977439df2a814e3cc14b27abac2 HG_PARENT1=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT2=ab228980c14deea8b9555d91c9581127383e40fd HG_PENDING=$TESTTMP/a
89 3:07f3376c1e65
89 3:07f3376c1e65
90 pretxnclose hook: HG_HOOKNAME=pretxnclose HG_HOOKTYPE=pretxnclose HG_PENDING=$TESTTMP/a HG_TXNID=TXN:$ID$ HG_TXNNAME=commit
90 pretxnclose hook: HG_HOOKNAME=pretxnclose HG_HOOKTYPE=pretxnclose HG_PENDING=$TESTTMP/a HG_TXNID=TXN:$ID$ HG_TXNNAME=commit
91 txnclose hook: HG_HOOKNAME=txnclose HG_HOOKTYPE=txnclose HG_TXNID=TXN:$ID$ HG_TXNNAME=commit
91 txnclose hook: HG_HOOKNAME=txnclose HG_HOOKTYPE=txnclose HG_TXNID=TXN:$ID$ HG_TXNNAME=commit
92 commit hook: HG_HOOKNAME=commit HG_HOOKTYPE=commit HG_NODE=07f3376c1e655977439df2a814e3cc14b27abac2 HG_PARENT1=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT2=ab228980c14deea8b9555d91c9581127383e40fd
92 commit hook: HG_HOOKNAME=commit HG_HOOKTYPE=commit HG_NODE=07f3376c1e655977439df2a814e3cc14b27abac2 HG_PARENT1=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT2=ab228980c14deea8b9555d91c9581127383e40fd
93 commit.b hook: HG_HOOKNAME=commit.b HG_HOOKTYPE=commit HG_NODE=07f3376c1e655977439df2a814e3cc14b27abac2 HG_PARENT1=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT2=ab228980c14deea8b9555d91c9581127383e40fd
93 commit.b hook: HG_HOOKNAME=commit.b HG_HOOKTYPE=commit HG_NODE=07f3376c1e655977439df2a814e3cc14b27abac2 HG_PARENT1=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_PARENT2=ab228980c14deea8b9555d91c9581127383e40fd
94
94
95 test generic hooks
95 test generic hooks
96
96
97 $ hg id
97 $ hg id
98 pre-identify hook: HG_ARGS=id HG_HOOKNAME=pre-identify HG_HOOKTYPE=pre-identify HG_OPTS={'bookmarks': None, 'branch': None, 'id': None, 'insecure': None, 'num': None, 'remotecmd': '', 'rev': '', 'ssh': '', 'tags': None} HG_PATS=[]
98 pre-identify hook: HG_ARGS=id HG_HOOKNAME=pre-identify HG_HOOKTYPE=pre-identify HG_OPTS={'bookmarks': None, 'branch': None, 'id': None, 'insecure': None, 'num': None, 'remotecmd': '', 'rev': '', 'ssh': '', 'tags': None} HG_PATS=[]
99 abort: pre-identify hook exited with status 1
99 abort: pre-identify hook exited with status 1
100 [255]
100 [255]
101 $ hg cat b
101 $ hg cat b
102 pre-cat hook: HG_ARGS=cat b HG_HOOKNAME=pre-cat HG_HOOKTYPE=pre-cat HG_OPTS={'decode': None, 'exclude': [], 'include': [], 'output': '', 'rev': ''} HG_PATS=['b']
102 pre-cat hook: HG_ARGS=cat b HG_HOOKNAME=pre-cat HG_HOOKTYPE=pre-cat HG_OPTS={'decode': None, 'exclude': [], 'include': [], 'output': '', 'rev': '', 'template': ''} HG_PATS=['b']
103 b
103 b
104 post-cat hook: HG_ARGS=cat b HG_HOOKNAME=post-cat HG_HOOKTYPE=post-cat HG_OPTS={'decode': None, 'exclude': [], 'include': [], 'output': '', 'rev': ''} HG_PATS=['b'] HG_RESULT=0
104 post-cat hook: HG_ARGS=cat b HG_HOOKNAME=post-cat HG_HOOKTYPE=post-cat HG_OPTS={'decode': None, 'exclude': [], 'include': [], 'output': '', 'rev': '', 'template': ''} HG_PATS=['b'] HG_RESULT=0
105
105
106 $ cd ../b
106 $ cd ../b
107 $ hg pull ../a
107 $ hg pull ../a
108 pulling from ../a
108 pulling from ../a
109 searching for changes
109 searching for changes
110 prechangegroup hook: HG_HOOKNAME=prechangegroup HG_HOOKTYPE=prechangegroup HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=file:$TESTTMP/a
110 prechangegroup hook: HG_HOOKNAME=prechangegroup HG_HOOKTYPE=prechangegroup HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=file:$TESTTMP/a
111 adding changesets
111 adding changesets
112 adding manifests
112 adding manifests
113 adding file changes
113 adding file changes
114 added 3 changesets with 2 changes to 2 files
114 added 3 changesets with 2 changes to 2 files
115 changegroup hook: HG_HOOKNAME=changegroup HG_HOOKTYPE=changegroup HG_NODE=ab228980c14deea8b9555d91c9581127383e40fd HG_NODE_LAST=07f3376c1e655977439df2a814e3cc14b27abac2 HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=file:$TESTTMP/a
115 changegroup hook: HG_HOOKNAME=changegroup HG_HOOKTYPE=changegroup HG_NODE=ab228980c14deea8b9555d91c9581127383e40fd HG_NODE_LAST=07f3376c1e655977439df2a814e3cc14b27abac2 HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=file:$TESTTMP/a
116 incoming hook: HG_HOOKNAME=incoming HG_HOOKTYPE=incoming HG_NODE=ab228980c14deea8b9555d91c9581127383e40fd HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=file:$TESTTMP/a
116 incoming hook: HG_HOOKNAME=incoming HG_HOOKTYPE=incoming HG_NODE=ab228980c14deea8b9555d91c9581127383e40fd HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=file:$TESTTMP/a
117 incoming hook: HG_HOOKNAME=incoming HG_HOOKTYPE=incoming HG_NODE=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=file:$TESTTMP/a
117 incoming hook: HG_HOOKNAME=incoming HG_HOOKTYPE=incoming HG_NODE=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=file:$TESTTMP/a
118 incoming hook: HG_HOOKNAME=incoming HG_HOOKTYPE=incoming HG_NODE=07f3376c1e655977439df2a814e3cc14b27abac2 HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=file:$TESTTMP/a
118 incoming hook: HG_HOOKNAME=incoming HG_HOOKTYPE=incoming HG_NODE=07f3376c1e655977439df2a814e3cc14b27abac2 HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=file:$TESTTMP/a
119 (run 'hg update' to get a working copy)
119 (run 'hg update' to get a working copy)
120
120
121 tag hooks can see env vars
121 tag hooks can see env vars
122
122
123 $ cd ../a
123 $ cd ../a
124 $ cat >> .hg/hgrc <<EOF
124 $ cat >> .hg/hgrc <<EOF
125 > pretag = sh -c "printenv.py pretag"
125 > pretag = sh -c "printenv.py pretag"
126 > tag = sh -c "HG_PARENT1= HG_PARENT2= printenv.py tag"
126 > tag = sh -c "HG_PARENT1= HG_PARENT2= printenv.py tag"
127 > EOF
127 > EOF
128 $ hg tag -d '3 0' a
128 $ hg tag -d '3 0' a
129 pretag hook: HG_HOOKNAME=pretag HG_HOOKTYPE=pretag HG_LOCAL=0 HG_NODE=07f3376c1e655977439df2a814e3cc14b27abac2 HG_TAG=a
129 pretag hook: HG_HOOKNAME=pretag HG_HOOKTYPE=pretag HG_LOCAL=0 HG_NODE=07f3376c1e655977439df2a814e3cc14b27abac2 HG_TAG=a
130 precommit hook: HG_HOOKNAME=precommit HG_HOOKTYPE=precommit HG_PARENT1=07f3376c1e655977439df2a814e3cc14b27abac2
130 precommit hook: HG_HOOKNAME=precommit HG_HOOKTYPE=precommit HG_PARENT1=07f3376c1e655977439df2a814e3cc14b27abac2
131 pretxnopen hook: HG_HOOKNAME=pretxnopen HG_HOOKTYPE=pretxnopen HG_TXNID=TXN:$ID$ HG_TXNNAME=commit
131 pretxnopen hook: HG_HOOKNAME=pretxnopen HG_HOOKTYPE=pretxnopen HG_TXNID=TXN:$ID$ HG_TXNNAME=commit
132 pretxncommit hook: HG_HOOKNAME=pretxncommit HG_HOOKTYPE=pretxncommit HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_PARENT1=07f3376c1e655977439df2a814e3cc14b27abac2 HG_PENDING=$TESTTMP/a
132 pretxncommit hook: HG_HOOKNAME=pretxncommit HG_HOOKTYPE=pretxncommit HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_PARENT1=07f3376c1e655977439df2a814e3cc14b27abac2 HG_PENDING=$TESTTMP/a
133 4:539e4b31b6dc
133 4:539e4b31b6dc
134 pretxnclose hook: HG_HOOKNAME=pretxnclose HG_HOOKTYPE=pretxnclose HG_PENDING=$TESTTMP/a HG_TXNID=TXN:$ID$ HG_TXNNAME=commit
134 pretxnclose hook: HG_HOOKNAME=pretxnclose HG_HOOKTYPE=pretxnclose HG_PENDING=$TESTTMP/a HG_TXNID=TXN:$ID$ HG_TXNNAME=commit
135 tag hook: HG_HOOKNAME=tag HG_HOOKTYPE=tag HG_LOCAL=0 HG_NODE=07f3376c1e655977439df2a814e3cc14b27abac2 HG_TAG=a
135 tag hook: HG_HOOKNAME=tag HG_HOOKTYPE=tag HG_LOCAL=0 HG_NODE=07f3376c1e655977439df2a814e3cc14b27abac2 HG_TAG=a
136 txnclose hook: HG_HOOKNAME=txnclose HG_HOOKTYPE=txnclose HG_TXNID=TXN:$ID$ HG_TXNNAME=commit
136 txnclose hook: HG_HOOKNAME=txnclose HG_HOOKTYPE=txnclose HG_TXNID=TXN:$ID$ HG_TXNNAME=commit
137 commit hook: HG_HOOKNAME=commit HG_HOOKTYPE=commit HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_PARENT1=07f3376c1e655977439df2a814e3cc14b27abac2
137 commit hook: HG_HOOKNAME=commit HG_HOOKTYPE=commit HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_PARENT1=07f3376c1e655977439df2a814e3cc14b27abac2
138 commit.b hook: HG_HOOKNAME=commit.b HG_HOOKTYPE=commit HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_PARENT1=07f3376c1e655977439df2a814e3cc14b27abac2
138 commit.b hook: HG_HOOKNAME=commit.b HG_HOOKTYPE=commit HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_PARENT1=07f3376c1e655977439df2a814e3cc14b27abac2
139 $ hg tag -l la
139 $ hg tag -l la
140 pretag hook: HG_HOOKNAME=pretag HG_HOOKTYPE=pretag HG_LOCAL=1 HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_TAG=la
140 pretag hook: HG_HOOKNAME=pretag HG_HOOKTYPE=pretag HG_LOCAL=1 HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_TAG=la
141 tag hook: HG_HOOKNAME=tag HG_HOOKTYPE=tag HG_LOCAL=1 HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_TAG=la
141 tag hook: HG_HOOKNAME=tag HG_HOOKTYPE=tag HG_LOCAL=1 HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_TAG=la
142
142
143 pretag hook can forbid tagging
143 pretag hook can forbid tagging
144
144
145 $ cat >> .hg/hgrc <<EOF
145 $ cat >> .hg/hgrc <<EOF
146 > pretag.forbid = sh -c "printenv.py pretag.forbid 1"
146 > pretag.forbid = sh -c "printenv.py pretag.forbid 1"
147 > EOF
147 > EOF
148 $ hg tag -d '4 0' fa
148 $ hg tag -d '4 0' fa
149 pretag hook: HG_HOOKNAME=pretag HG_HOOKTYPE=pretag HG_LOCAL=0 HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_TAG=fa
149 pretag hook: HG_HOOKNAME=pretag HG_HOOKTYPE=pretag HG_LOCAL=0 HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_TAG=fa
150 pretag.forbid hook: HG_HOOKNAME=pretag.forbid HG_HOOKTYPE=pretag HG_LOCAL=0 HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_TAG=fa
150 pretag.forbid hook: HG_HOOKNAME=pretag.forbid HG_HOOKTYPE=pretag HG_LOCAL=0 HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_TAG=fa
151 abort: pretag.forbid hook exited with status 1
151 abort: pretag.forbid hook exited with status 1
152 [255]
152 [255]
153 $ hg tag -l fla
153 $ hg tag -l fla
154 pretag hook: HG_HOOKNAME=pretag HG_HOOKTYPE=pretag HG_LOCAL=1 HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_TAG=fla
154 pretag hook: HG_HOOKNAME=pretag HG_HOOKTYPE=pretag HG_LOCAL=1 HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_TAG=fla
155 pretag.forbid hook: HG_HOOKNAME=pretag.forbid HG_HOOKTYPE=pretag HG_LOCAL=1 HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_TAG=fla
155 pretag.forbid hook: HG_HOOKNAME=pretag.forbid HG_HOOKTYPE=pretag HG_LOCAL=1 HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_TAG=fla
156 abort: pretag.forbid hook exited with status 1
156 abort: pretag.forbid hook exited with status 1
157 [255]
157 [255]
158
158
159 pretxncommit hook can see changeset, can roll back txn, changeset no
159 pretxncommit hook can see changeset, can roll back txn, changeset no
160 more there after
160 more there after
161
161
162 $ cat >> .hg/hgrc <<EOF
162 $ cat >> .hg/hgrc <<EOF
163 > pretxncommit.forbid0 = sh -c "hg tip -q"
163 > pretxncommit.forbid0 = sh -c "hg tip -q"
164 > pretxncommit.forbid1 = sh -c "printenv.py pretxncommit.forbid 1"
164 > pretxncommit.forbid1 = sh -c "printenv.py pretxncommit.forbid 1"
165 > EOF
165 > EOF
166 $ echo z > z
166 $ echo z > z
167 $ hg add z
167 $ hg add z
168 $ hg -q tip
168 $ hg -q tip
169 4:539e4b31b6dc
169 4:539e4b31b6dc
170 $ hg commit -m 'fail' -d '4 0'
170 $ hg commit -m 'fail' -d '4 0'
171 precommit hook: HG_HOOKNAME=precommit HG_HOOKTYPE=precommit HG_PARENT1=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10
171 precommit hook: HG_HOOKNAME=precommit HG_HOOKTYPE=precommit HG_PARENT1=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10
172 pretxnopen hook: HG_HOOKNAME=pretxnopen HG_HOOKTYPE=pretxnopen HG_TXNID=TXN:$ID$ HG_TXNNAME=commit
172 pretxnopen hook: HG_HOOKNAME=pretxnopen HG_HOOKTYPE=pretxnopen HG_TXNID=TXN:$ID$ HG_TXNNAME=commit
173 pretxncommit hook: HG_HOOKNAME=pretxncommit HG_HOOKTYPE=pretxncommit HG_NODE=6f611f8018c10e827fee6bd2bc807f937e761567 HG_PARENT1=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_PENDING=$TESTTMP/a
173 pretxncommit hook: HG_HOOKNAME=pretxncommit HG_HOOKTYPE=pretxncommit HG_NODE=6f611f8018c10e827fee6bd2bc807f937e761567 HG_PARENT1=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_PENDING=$TESTTMP/a
174 5:6f611f8018c1
174 5:6f611f8018c1
175 5:6f611f8018c1
175 5:6f611f8018c1
176 pretxncommit.forbid hook: HG_HOOKNAME=pretxncommit.forbid1 HG_HOOKTYPE=pretxncommit HG_NODE=6f611f8018c10e827fee6bd2bc807f937e761567 HG_PARENT1=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_PENDING=$TESTTMP/a
176 pretxncommit.forbid hook: HG_HOOKNAME=pretxncommit.forbid1 HG_HOOKTYPE=pretxncommit HG_NODE=6f611f8018c10e827fee6bd2bc807f937e761567 HG_PARENT1=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_PENDING=$TESTTMP/a
177 transaction abort!
177 transaction abort!
178 txnabort python hook: txnid,txnname
178 txnabort python hook: txnid,txnname
179 txnabort hook: HG_HOOKNAME=txnabort.1 HG_HOOKTYPE=txnabort HG_TXNID=TXN:$ID$ HG_TXNNAME=commit
179 txnabort hook: HG_HOOKNAME=txnabort.1 HG_HOOKTYPE=txnabort HG_TXNID=TXN:$ID$ HG_TXNNAME=commit
180 rollback completed
180 rollback completed
181 abort: pretxncommit.forbid1 hook exited with status 1
181 abort: pretxncommit.forbid1 hook exited with status 1
182 [255]
182 [255]
183 $ hg -q tip
183 $ hg -q tip
184 4:539e4b31b6dc
184 4:539e4b31b6dc
185
185
186 (Check that no 'changelog.i.a' file were left behind)
186 (Check that no 'changelog.i.a' file were left behind)
187
187
188 $ ls -1 .hg/store/
188 $ ls -1 .hg/store/
189 00changelog.i
189 00changelog.i
190 00manifest.i
190 00manifest.i
191 data
191 data
192 fncache
192 fncache
193 journal.phaseroots
193 journal.phaseroots
194 phaseroots
194 phaseroots
195 undo
195 undo
196 undo.backup.fncache
196 undo.backup.fncache
197 undo.backupfiles
197 undo.backupfiles
198 undo.phaseroots
198 undo.phaseroots
199
199
200
200
201 precommit hook can prevent commit
201 precommit hook can prevent commit
202
202
203 $ cat >> .hg/hgrc <<EOF
203 $ cat >> .hg/hgrc <<EOF
204 > precommit.forbid = sh -c "printenv.py precommit.forbid 1"
204 > precommit.forbid = sh -c "printenv.py precommit.forbid 1"
205 > EOF
205 > EOF
206 $ hg commit -m 'fail' -d '4 0'
206 $ hg commit -m 'fail' -d '4 0'
207 precommit hook: HG_HOOKNAME=precommit HG_HOOKTYPE=precommit HG_PARENT1=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10
207 precommit hook: HG_HOOKNAME=precommit HG_HOOKTYPE=precommit HG_PARENT1=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10
208 precommit.forbid hook: HG_HOOKNAME=precommit.forbid HG_HOOKTYPE=precommit HG_PARENT1=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10
208 precommit.forbid hook: HG_HOOKNAME=precommit.forbid HG_HOOKTYPE=precommit HG_PARENT1=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10
209 abort: precommit.forbid hook exited with status 1
209 abort: precommit.forbid hook exited with status 1
210 [255]
210 [255]
211 $ hg -q tip
211 $ hg -q tip
212 4:539e4b31b6dc
212 4:539e4b31b6dc
213
213
214 preupdate hook can prevent update
214 preupdate hook can prevent update
215
215
216 $ cat >> .hg/hgrc <<EOF
216 $ cat >> .hg/hgrc <<EOF
217 > preupdate = sh -c "printenv.py preupdate"
217 > preupdate = sh -c "printenv.py preupdate"
218 > EOF
218 > EOF
219 $ hg update 1
219 $ hg update 1
220 preupdate hook: HG_HOOKNAME=preupdate HG_HOOKTYPE=preupdate HG_PARENT1=ab228980c14d
220 preupdate hook: HG_HOOKNAME=preupdate HG_HOOKTYPE=preupdate HG_PARENT1=ab228980c14d
221 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
221 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
222
222
223 update hook
223 update hook
224
224
225 $ cat >> .hg/hgrc <<EOF
225 $ cat >> .hg/hgrc <<EOF
226 > update = sh -c "printenv.py update"
226 > update = sh -c "printenv.py update"
227 > EOF
227 > EOF
228 $ hg update
228 $ hg update
229 preupdate hook: HG_HOOKNAME=preupdate HG_HOOKTYPE=preupdate HG_PARENT1=539e4b31b6dc
229 preupdate hook: HG_HOOKNAME=preupdate HG_HOOKTYPE=preupdate HG_PARENT1=539e4b31b6dc
230 update hook: HG_ERROR=0 HG_HOOKNAME=update HG_HOOKTYPE=update HG_PARENT1=539e4b31b6dc
230 update hook: HG_ERROR=0 HG_HOOKNAME=update HG_HOOKTYPE=update HG_PARENT1=539e4b31b6dc
231 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
231 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
232
232
233 pushkey hook
233 pushkey hook
234
234
235 $ cat >> .hg/hgrc <<EOF
235 $ cat >> .hg/hgrc <<EOF
236 > pushkey = sh -c "printenv.py pushkey"
236 > pushkey = sh -c "printenv.py pushkey"
237 > EOF
237 > EOF
238 $ cd ../b
238 $ cd ../b
239 $ hg bookmark -r null foo
239 $ hg bookmark -r null foo
240 $ hg push -B foo ../a
240 $ hg push -B foo ../a
241 pushing to ../a
241 pushing to ../a
242 searching for changes
242 searching for changes
243 no changes found
243 no changes found
244 pretxnopen hook: HG_HOOKNAME=pretxnopen HG_HOOKTYPE=pretxnopen HG_TXNID=TXN:$ID$ HG_TXNNAME=push
244 pretxnopen hook: HG_HOOKNAME=pretxnopen HG_HOOKTYPE=pretxnopen HG_TXNID=TXN:$ID$ HG_TXNNAME=push
245 pretxnclose hook: HG_BOOKMARK_MOVED=1 HG_BUNDLE2=1 HG_HOOKNAME=pretxnclose HG_HOOKTYPE=pretxnclose HG_PENDING=$TESTTMP/a HG_SOURCE=push HG_TXNID=TXN:$ID$ HG_TXNNAME=push HG_URL=file:$TESTTMP/a
245 pretxnclose hook: HG_BOOKMARK_MOVED=1 HG_BUNDLE2=1 HG_HOOKNAME=pretxnclose HG_HOOKTYPE=pretxnclose HG_PENDING=$TESTTMP/a HG_SOURCE=push HG_TXNID=TXN:$ID$ HG_TXNNAME=push HG_URL=file:$TESTTMP/a
246 pushkey hook: HG_HOOKNAME=pushkey HG_HOOKTYPE=pushkey HG_KEY=foo HG_NAMESPACE=bookmarks HG_NEW=0000000000000000000000000000000000000000 HG_RET=1
246 pushkey hook: HG_HOOKNAME=pushkey HG_HOOKTYPE=pushkey HG_KEY=foo HG_NAMESPACE=bookmarks HG_NEW=0000000000000000000000000000000000000000 HG_RET=1
247 txnclose hook: HG_BOOKMARK_MOVED=1 HG_BUNDLE2=1 HG_HOOKNAME=txnclose HG_HOOKTYPE=txnclose HG_SOURCE=push HG_TXNID=TXN:$ID$ HG_TXNNAME=push HG_URL=file:$TESTTMP/a
247 txnclose hook: HG_BOOKMARK_MOVED=1 HG_BUNDLE2=1 HG_HOOKNAME=txnclose HG_HOOKTYPE=txnclose HG_SOURCE=push HG_TXNID=TXN:$ID$ HG_TXNNAME=push HG_URL=file:$TESTTMP/a
248 exporting bookmark foo
248 exporting bookmark foo
249 [1]
249 [1]
250 $ cd ../a
250 $ cd ../a
251
251
252 listkeys hook
252 listkeys hook
253
253
254 $ cat >> .hg/hgrc <<EOF
254 $ cat >> .hg/hgrc <<EOF
255 > listkeys = sh -c "printenv.py listkeys"
255 > listkeys = sh -c "printenv.py listkeys"
256 > EOF
256 > EOF
257 $ hg bookmark -r null bar
257 $ hg bookmark -r null bar
258 pretxnopen hook: HG_HOOKNAME=pretxnopen HG_HOOKTYPE=pretxnopen HG_TXNID=TXN:$ID$ HG_TXNNAME=bookmark
258 pretxnopen hook: HG_HOOKNAME=pretxnopen HG_HOOKTYPE=pretxnopen HG_TXNID=TXN:$ID$ HG_TXNNAME=bookmark
259 pretxnclose hook: HG_BOOKMARK_MOVED=1 HG_HOOKNAME=pretxnclose HG_HOOKTYPE=pretxnclose HG_PENDING=$TESTTMP/a HG_TXNID=TXN:$ID$ HG_TXNNAME=bookmark
259 pretxnclose hook: HG_BOOKMARK_MOVED=1 HG_HOOKNAME=pretxnclose HG_HOOKTYPE=pretxnclose HG_PENDING=$TESTTMP/a HG_TXNID=TXN:$ID$ HG_TXNNAME=bookmark
260 txnclose hook: HG_BOOKMARK_MOVED=1 HG_HOOKNAME=txnclose HG_HOOKTYPE=txnclose HG_TXNID=TXN:$ID$ HG_TXNNAME=bookmark
260 txnclose hook: HG_BOOKMARK_MOVED=1 HG_HOOKNAME=txnclose HG_HOOKTYPE=txnclose HG_TXNID=TXN:$ID$ HG_TXNNAME=bookmark
261 $ cd ../b
261 $ cd ../b
262 $ hg pull -B bar ../a
262 $ hg pull -B bar ../a
263 pulling from ../a
263 pulling from ../a
264 listkeys hook: HG_HOOKNAME=listkeys HG_HOOKTYPE=listkeys HG_NAMESPACE=bookmarks HG_VALUES={'bar': '0000000000000000000000000000000000000000', 'foo': '0000000000000000000000000000000000000000'}
264 listkeys hook: HG_HOOKNAME=listkeys HG_HOOKTYPE=listkeys HG_NAMESPACE=bookmarks HG_VALUES={'bar': '0000000000000000000000000000000000000000', 'foo': '0000000000000000000000000000000000000000'}
265 no changes found
265 no changes found
266 listkeys hook: HG_HOOKNAME=listkeys HG_HOOKTYPE=listkeys HG_NAMESPACE=phases HG_VALUES={'cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b': '1', 'publishing': 'True'}
266 listkeys hook: HG_HOOKNAME=listkeys HG_HOOKTYPE=listkeys HG_NAMESPACE=phases HG_VALUES={'cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b': '1', 'publishing': 'True'}
267 adding remote bookmark bar
267 adding remote bookmark bar
268 $ cd ../a
268 $ cd ../a
269
269
270 test that prepushkey can prevent incoming keys
270 test that prepushkey can prevent incoming keys
271
271
272 $ cat >> .hg/hgrc <<EOF
272 $ cat >> .hg/hgrc <<EOF
273 > prepushkey = sh -c "printenv.py prepushkey.forbid 1"
273 > prepushkey = sh -c "printenv.py prepushkey.forbid 1"
274 > EOF
274 > EOF
275 $ cd ../b
275 $ cd ../b
276 $ hg bookmark -r null baz
276 $ hg bookmark -r null baz
277 $ hg push -B baz ../a
277 $ hg push -B baz ../a
278 pushing to ../a
278 pushing to ../a
279 searching for changes
279 searching for changes
280 listkeys hook: HG_HOOKNAME=listkeys HG_HOOKTYPE=listkeys HG_NAMESPACE=phases HG_VALUES={'cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b': '1', 'publishing': 'True'}
280 listkeys hook: HG_HOOKNAME=listkeys HG_HOOKTYPE=listkeys HG_NAMESPACE=phases HG_VALUES={'cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b': '1', 'publishing': 'True'}
281 listkeys hook: HG_HOOKNAME=listkeys HG_HOOKTYPE=listkeys HG_NAMESPACE=bookmarks HG_VALUES={'bar': '0000000000000000000000000000000000000000', 'foo': '0000000000000000000000000000000000000000'}
281 listkeys hook: HG_HOOKNAME=listkeys HG_HOOKTYPE=listkeys HG_NAMESPACE=bookmarks HG_VALUES={'bar': '0000000000000000000000000000000000000000', 'foo': '0000000000000000000000000000000000000000'}
282 no changes found
282 no changes found
283 pretxnopen hook: HG_HOOKNAME=pretxnopen HG_HOOKTYPE=pretxnopen HG_TXNID=TXN:$ID$ HG_TXNNAME=push
283 pretxnopen hook: HG_HOOKNAME=pretxnopen HG_HOOKTYPE=pretxnopen HG_TXNID=TXN:$ID$ HG_TXNNAME=push
284 prepushkey.forbid hook: HG_BUNDLE2=1 HG_HOOKNAME=prepushkey HG_HOOKTYPE=prepushkey HG_KEY=baz HG_NAMESPACE=bookmarks HG_NEW=0000000000000000000000000000000000000000 HG_SOURCE=push HG_TXNID=TXN:$ID$ HG_URL=file:$TESTTMP/a
284 prepushkey.forbid hook: HG_BUNDLE2=1 HG_HOOKNAME=prepushkey HG_HOOKTYPE=prepushkey HG_KEY=baz HG_NAMESPACE=bookmarks HG_NEW=0000000000000000000000000000000000000000 HG_SOURCE=push HG_TXNID=TXN:$ID$ HG_URL=file:$TESTTMP/a
285 pushkey-abort: prepushkey hook exited with status 1
285 pushkey-abort: prepushkey hook exited with status 1
286 abort: exporting bookmark baz failed!
286 abort: exporting bookmark baz failed!
287 [255]
287 [255]
288 $ cd ../a
288 $ cd ../a
289
289
290 test that prelistkeys can prevent listing keys
290 test that prelistkeys can prevent listing keys
291
291
292 $ cat >> .hg/hgrc <<EOF
292 $ cat >> .hg/hgrc <<EOF
293 > prelistkeys = sh -c "printenv.py prelistkeys.forbid 1"
293 > prelistkeys = sh -c "printenv.py prelistkeys.forbid 1"
294 > EOF
294 > EOF
295 $ hg bookmark -r null quux
295 $ hg bookmark -r null quux
296 pretxnopen hook: HG_HOOKNAME=pretxnopen HG_HOOKTYPE=pretxnopen HG_TXNID=TXN:$ID$ HG_TXNNAME=bookmark
296 pretxnopen hook: HG_HOOKNAME=pretxnopen HG_HOOKTYPE=pretxnopen HG_TXNID=TXN:$ID$ HG_TXNNAME=bookmark
297 pretxnclose hook: HG_BOOKMARK_MOVED=1 HG_HOOKNAME=pretxnclose HG_HOOKTYPE=pretxnclose HG_PENDING=$TESTTMP/a HG_TXNID=TXN:$ID$ HG_TXNNAME=bookmark
297 pretxnclose hook: HG_BOOKMARK_MOVED=1 HG_HOOKNAME=pretxnclose HG_HOOKTYPE=pretxnclose HG_PENDING=$TESTTMP/a HG_TXNID=TXN:$ID$ HG_TXNNAME=bookmark
298 txnclose hook: HG_BOOKMARK_MOVED=1 HG_HOOKNAME=txnclose HG_HOOKTYPE=txnclose HG_TXNID=TXN:$ID$ HG_TXNNAME=bookmark
298 txnclose hook: HG_BOOKMARK_MOVED=1 HG_HOOKNAME=txnclose HG_HOOKTYPE=txnclose HG_TXNID=TXN:$ID$ HG_TXNNAME=bookmark
299 $ cd ../b
299 $ cd ../b
300 $ hg pull -B quux ../a
300 $ hg pull -B quux ../a
301 pulling from ../a
301 pulling from ../a
302 prelistkeys.forbid hook: HG_HOOKNAME=prelistkeys HG_HOOKTYPE=prelistkeys HG_NAMESPACE=bookmarks
302 prelistkeys.forbid hook: HG_HOOKNAME=prelistkeys HG_HOOKTYPE=prelistkeys HG_NAMESPACE=bookmarks
303 abort: prelistkeys hook exited with status 1
303 abort: prelistkeys hook exited with status 1
304 [255]
304 [255]
305 $ cd ../a
305 $ cd ../a
306 $ rm .hg/hgrc
306 $ rm .hg/hgrc
307
307
308 prechangegroup hook can prevent incoming changes
308 prechangegroup hook can prevent incoming changes
309
309
310 $ cd ../b
310 $ cd ../b
311 $ hg -q tip
311 $ hg -q tip
312 3:07f3376c1e65
312 3:07f3376c1e65
313 $ cat > .hg/hgrc <<EOF
313 $ cat > .hg/hgrc <<EOF
314 > [hooks]
314 > [hooks]
315 > prechangegroup.forbid = sh -c "printenv.py prechangegroup.forbid 1"
315 > prechangegroup.forbid = sh -c "printenv.py prechangegroup.forbid 1"
316 > EOF
316 > EOF
317 $ hg pull ../a
317 $ hg pull ../a
318 pulling from ../a
318 pulling from ../a
319 searching for changes
319 searching for changes
320 prechangegroup.forbid hook: HG_HOOKNAME=prechangegroup.forbid HG_HOOKTYPE=prechangegroup HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=file:$TESTTMP/a
320 prechangegroup.forbid hook: HG_HOOKNAME=prechangegroup.forbid HG_HOOKTYPE=prechangegroup HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=file:$TESTTMP/a
321 abort: prechangegroup.forbid hook exited with status 1
321 abort: prechangegroup.forbid hook exited with status 1
322 [255]
322 [255]
323
323
324 pretxnchangegroup hook can see incoming changes, can roll back txn,
324 pretxnchangegroup hook can see incoming changes, can roll back txn,
325 incoming changes no longer there after
325 incoming changes no longer there after
326
326
327 $ cat > .hg/hgrc <<EOF
327 $ cat > .hg/hgrc <<EOF
328 > [hooks]
328 > [hooks]
329 > pretxnchangegroup.forbid0 = hg tip -q
329 > pretxnchangegroup.forbid0 = hg tip -q
330 > pretxnchangegroup.forbid1 = sh -c "printenv.py pretxnchangegroup.forbid 1"
330 > pretxnchangegroup.forbid1 = sh -c "printenv.py pretxnchangegroup.forbid 1"
331 > EOF
331 > EOF
332 $ hg pull ../a
332 $ hg pull ../a
333 pulling from ../a
333 pulling from ../a
334 searching for changes
334 searching for changes
335 adding changesets
335 adding changesets
336 adding manifests
336 adding manifests
337 adding file changes
337 adding file changes
338 added 1 changesets with 1 changes to 1 files
338 added 1 changesets with 1 changes to 1 files
339 4:539e4b31b6dc
339 4:539e4b31b6dc
340 pretxnchangegroup.forbid hook: HG_HOOKNAME=pretxnchangegroup.forbid1 HG_HOOKTYPE=pretxnchangegroup HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_NODE_LAST=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_PENDING=$TESTTMP/b HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=file:$TESTTMP/a
340 pretxnchangegroup.forbid hook: HG_HOOKNAME=pretxnchangegroup.forbid1 HG_HOOKTYPE=pretxnchangegroup HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_NODE_LAST=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_PENDING=$TESTTMP/b HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=file:$TESTTMP/a
341 transaction abort!
341 transaction abort!
342 rollback completed
342 rollback completed
343 abort: pretxnchangegroup.forbid1 hook exited with status 1
343 abort: pretxnchangegroup.forbid1 hook exited with status 1
344 [255]
344 [255]
345 $ hg -q tip
345 $ hg -q tip
346 3:07f3376c1e65
346 3:07f3376c1e65
347
347
348 outgoing hooks can see env vars
348 outgoing hooks can see env vars
349
349
350 $ rm .hg/hgrc
350 $ rm .hg/hgrc
351 $ cat > ../a/.hg/hgrc <<EOF
351 $ cat > ../a/.hg/hgrc <<EOF
352 > [hooks]
352 > [hooks]
353 > preoutgoing = sh -c "printenv.py preoutgoing"
353 > preoutgoing = sh -c "printenv.py preoutgoing"
354 > outgoing = sh -c "printenv.py outgoing"
354 > outgoing = sh -c "printenv.py outgoing"
355 > EOF
355 > EOF
356 $ hg pull ../a
356 $ hg pull ../a
357 pulling from ../a
357 pulling from ../a
358 searching for changes
358 searching for changes
359 preoutgoing hook: HG_HOOKNAME=preoutgoing HG_HOOKTYPE=preoutgoing HG_SOURCE=pull
359 preoutgoing hook: HG_HOOKNAME=preoutgoing HG_HOOKTYPE=preoutgoing HG_SOURCE=pull
360 outgoing hook: HG_HOOKNAME=outgoing HG_HOOKTYPE=outgoing HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_SOURCE=pull
360 outgoing hook: HG_HOOKNAME=outgoing HG_HOOKTYPE=outgoing HG_NODE=539e4b31b6dc99b3cfbaa6b53cbc1c1f9a1e3a10 HG_SOURCE=pull
361 adding changesets
361 adding changesets
362 adding manifests
362 adding manifests
363 adding file changes
363 adding file changes
364 added 1 changesets with 1 changes to 1 files
364 added 1 changesets with 1 changes to 1 files
365 adding remote bookmark quux
365 adding remote bookmark quux
366 (run 'hg update' to get a working copy)
366 (run 'hg update' to get a working copy)
367 $ hg rollback
367 $ hg rollback
368 repository tip rolled back to revision 3 (undo pull)
368 repository tip rolled back to revision 3 (undo pull)
369
369
370 preoutgoing hook can prevent outgoing changes
370 preoutgoing hook can prevent outgoing changes
371
371
372 $ cat >> ../a/.hg/hgrc <<EOF
372 $ cat >> ../a/.hg/hgrc <<EOF
373 > preoutgoing.forbid = sh -c "printenv.py preoutgoing.forbid 1"
373 > preoutgoing.forbid = sh -c "printenv.py preoutgoing.forbid 1"
374 > EOF
374 > EOF
375 $ hg pull ../a
375 $ hg pull ../a
376 pulling from ../a
376 pulling from ../a
377 searching for changes
377 searching for changes
378 preoutgoing hook: HG_HOOKNAME=preoutgoing HG_HOOKTYPE=preoutgoing HG_SOURCE=pull
378 preoutgoing hook: HG_HOOKNAME=preoutgoing HG_HOOKTYPE=preoutgoing HG_SOURCE=pull
379 preoutgoing.forbid hook: HG_HOOKNAME=preoutgoing.forbid HG_HOOKTYPE=preoutgoing HG_SOURCE=pull
379 preoutgoing.forbid hook: HG_HOOKNAME=preoutgoing.forbid HG_HOOKTYPE=preoutgoing HG_SOURCE=pull
380 abort: preoutgoing.forbid hook exited with status 1
380 abort: preoutgoing.forbid hook exited with status 1
381 [255]
381 [255]
382
382
383 outgoing hooks work for local clones
383 outgoing hooks work for local clones
384
384
385 $ cd ..
385 $ cd ..
386 $ cat > a/.hg/hgrc <<EOF
386 $ cat > a/.hg/hgrc <<EOF
387 > [hooks]
387 > [hooks]
388 > preoutgoing = sh -c "printenv.py preoutgoing"
388 > preoutgoing = sh -c "printenv.py preoutgoing"
389 > outgoing = sh -c "printenv.py outgoing"
389 > outgoing = sh -c "printenv.py outgoing"
390 > EOF
390 > EOF
391 $ hg clone a c
391 $ hg clone a c
392 preoutgoing hook: HG_HOOKNAME=preoutgoing HG_HOOKTYPE=preoutgoing HG_SOURCE=clone
392 preoutgoing hook: HG_HOOKNAME=preoutgoing HG_HOOKTYPE=preoutgoing HG_SOURCE=clone
393 outgoing hook: HG_HOOKNAME=outgoing HG_HOOKTYPE=outgoing HG_NODE=0000000000000000000000000000000000000000 HG_SOURCE=clone
393 outgoing hook: HG_HOOKNAME=outgoing HG_HOOKTYPE=outgoing HG_NODE=0000000000000000000000000000000000000000 HG_SOURCE=clone
394 updating to branch default
394 updating to branch default
395 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
395 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
396 $ rm -rf c
396 $ rm -rf c
397
397
398 preoutgoing hook can prevent outgoing changes for local clones
398 preoutgoing hook can prevent outgoing changes for local clones
399
399
400 $ cat >> a/.hg/hgrc <<EOF
400 $ cat >> a/.hg/hgrc <<EOF
401 > preoutgoing.forbid = sh -c "printenv.py preoutgoing.forbid 1"
401 > preoutgoing.forbid = sh -c "printenv.py preoutgoing.forbid 1"
402 > EOF
402 > EOF
403 $ hg clone a zzz
403 $ hg clone a zzz
404 preoutgoing hook: HG_HOOKNAME=preoutgoing HG_HOOKTYPE=preoutgoing HG_SOURCE=clone
404 preoutgoing hook: HG_HOOKNAME=preoutgoing HG_HOOKTYPE=preoutgoing HG_SOURCE=clone
405 preoutgoing.forbid hook: HG_HOOKNAME=preoutgoing.forbid HG_HOOKTYPE=preoutgoing HG_SOURCE=clone
405 preoutgoing.forbid hook: HG_HOOKNAME=preoutgoing.forbid HG_HOOKTYPE=preoutgoing HG_SOURCE=clone
406 abort: preoutgoing.forbid hook exited with status 1
406 abort: preoutgoing.forbid hook exited with status 1
407 [255]
407 [255]
408
408
409 $ cd "$TESTTMP/b"
409 $ cd "$TESTTMP/b"
410
410
411 $ cat > hooktests.py <<EOF
411 $ cat > hooktests.py <<EOF
412 > from mercurial import error
412 > from mercurial import error
413 >
413 >
414 > uncallable = 0
414 > uncallable = 0
415 >
415 >
416 > def printargs(args):
416 > def printargs(args):
417 > args.pop('ui', None)
417 > args.pop('ui', None)
418 > args.pop('repo', None)
418 > args.pop('repo', None)
419 > a = list(args.items())
419 > a = list(args.items())
420 > a.sort()
420 > a.sort()
421 > print 'hook args:'
421 > print 'hook args:'
422 > for k, v in a:
422 > for k, v in a:
423 > print ' ', k, v
423 > print ' ', k, v
424 >
424 >
425 > def passhook(**args):
425 > def passhook(**args):
426 > printargs(args)
426 > printargs(args)
427 >
427 >
428 > def failhook(**args):
428 > def failhook(**args):
429 > printargs(args)
429 > printargs(args)
430 > return True
430 > return True
431 >
431 >
432 > class LocalException(Exception):
432 > class LocalException(Exception):
433 > pass
433 > pass
434 >
434 >
435 > def raisehook(**args):
435 > def raisehook(**args):
436 > raise LocalException('exception from hook')
436 > raise LocalException('exception from hook')
437 >
437 >
438 > def aborthook(**args):
438 > def aborthook(**args):
439 > raise error.Abort('raise abort from hook')
439 > raise error.Abort('raise abort from hook')
440 >
440 >
441 > def brokenhook(**args):
441 > def brokenhook(**args):
442 > return 1 + {}
442 > return 1 + {}
443 >
443 >
444 > def verbosehook(ui, **args):
444 > def verbosehook(ui, **args):
445 > ui.note('verbose output from hook\n')
445 > ui.note('verbose output from hook\n')
446 >
446 >
447 > def printtags(ui, repo, **args):
447 > def printtags(ui, repo, **args):
448 > print sorted(repo.tags())
448 > print sorted(repo.tags())
449 >
449 >
450 > class container:
450 > class container:
451 > unreachable = 1
451 > unreachable = 1
452 > EOF
452 > EOF
453
453
454 $ cat > syntaxerror.py << EOF
454 $ cat > syntaxerror.py << EOF
455 > (foo
455 > (foo
456 > EOF
456 > EOF
457
457
458 test python hooks
458 test python hooks
459
459
460 #if windows
460 #if windows
461 $ PYTHONPATH="$TESTTMP/b;$PYTHONPATH"
461 $ PYTHONPATH="$TESTTMP/b;$PYTHONPATH"
462 #else
462 #else
463 $ PYTHONPATH="$TESTTMP/b:$PYTHONPATH"
463 $ PYTHONPATH="$TESTTMP/b:$PYTHONPATH"
464 #endif
464 #endif
465 $ export PYTHONPATH
465 $ export PYTHONPATH
466
466
467 $ echo '[hooks]' > ../a/.hg/hgrc
467 $ echo '[hooks]' > ../a/.hg/hgrc
468 $ echo 'preoutgoing.broken = python:hooktests.brokenhook' >> ../a/.hg/hgrc
468 $ echo 'preoutgoing.broken = python:hooktests.brokenhook' >> ../a/.hg/hgrc
469 $ hg pull ../a 2>&1 | grep 'raised an exception'
469 $ hg pull ../a 2>&1 | grep 'raised an exception'
470 error: preoutgoing.broken hook raised an exception: unsupported operand type(s) for +: 'int' and 'dict'
470 error: preoutgoing.broken hook raised an exception: unsupported operand type(s) for +: 'int' and 'dict'
471
471
472 $ echo '[hooks]' > ../a/.hg/hgrc
472 $ echo '[hooks]' > ../a/.hg/hgrc
473 $ echo 'preoutgoing.raise = python:hooktests.raisehook' >> ../a/.hg/hgrc
473 $ echo 'preoutgoing.raise = python:hooktests.raisehook' >> ../a/.hg/hgrc
474 $ hg pull ../a 2>&1 | grep 'raised an exception'
474 $ hg pull ../a 2>&1 | grep 'raised an exception'
475 error: preoutgoing.raise hook raised an exception: exception from hook
475 error: preoutgoing.raise hook raised an exception: exception from hook
476
476
477 $ echo '[hooks]' > ../a/.hg/hgrc
477 $ echo '[hooks]' > ../a/.hg/hgrc
478 $ echo 'preoutgoing.abort = python:hooktests.aborthook' >> ../a/.hg/hgrc
478 $ echo 'preoutgoing.abort = python:hooktests.aborthook' >> ../a/.hg/hgrc
479 $ hg pull ../a
479 $ hg pull ../a
480 pulling from ../a
480 pulling from ../a
481 searching for changes
481 searching for changes
482 error: preoutgoing.abort hook failed: raise abort from hook
482 error: preoutgoing.abort hook failed: raise abort from hook
483 abort: raise abort from hook
483 abort: raise abort from hook
484 [255]
484 [255]
485
485
486 $ echo '[hooks]' > ../a/.hg/hgrc
486 $ echo '[hooks]' > ../a/.hg/hgrc
487 $ echo 'preoutgoing.fail = python:hooktests.failhook' >> ../a/.hg/hgrc
487 $ echo 'preoutgoing.fail = python:hooktests.failhook' >> ../a/.hg/hgrc
488 $ hg pull ../a
488 $ hg pull ../a
489 pulling from ../a
489 pulling from ../a
490 searching for changes
490 searching for changes
491 hook args:
491 hook args:
492 hooktype preoutgoing
492 hooktype preoutgoing
493 source pull
493 source pull
494 abort: preoutgoing.fail hook failed
494 abort: preoutgoing.fail hook failed
495 [255]
495 [255]
496
496
497 $ echo '[hooks]' > ../a/.hg/hgrc
497 $ echo '[hooks]' > ../a/.hg/hgrc
498 $ echo 'preoutgoing.uncallable = python:hooktests.uncallable' >> ../a/.hg/hgrc
498 $ echo 'preoutgoing.uncallable = python:hooktests.uncallable' >> ../a/.hg/hgrc
499 $ hg pull ../a
499 $ hg pull ../a
500 pulling from ../a
500 pulling from ../a
501 searching for changes
501 searching for changes
502 abort: preoutgoing.uncallable hook is invalid: "hooktests.uncallable" is not callable
502 abort: preoutgoing.uncallable hook is invalid: "hooktests.uncallable" is not callable
503 [255]
503 [255]
504
504
505 $ echo '[hooks]' > ../a/.hg/hgrc
505 $ echo '[hooks]' > ../a/.hg/hgrc
506 $ echo 'preoutgoing.nohook = python:hooktests.nohook' >> ../a/.hg/hgrc
506 $ echo 'preoutgoing.nohook = python:hooktests.nohook' >> ../a/.hg/hgrc
507 $ hg pull ../a
507 $ hg pull ../a
508 pulling from ../a
508 pulling from ../a
509 searching for changes
509 searching for changes
510 abort: preoutgoing.nohook hook is invalid: "hooktests.nohook" is not defined
510 abort: preoutgoing.nohook hook is invalid: "hooktests.nohook" is not defined
511 [255]
511 [255]
512
512
513 $ echo '[hooks]' > ../a/.hg/hgrc
513 $ echo '[hooks]' > ../a/.hg/hgrc
514 $ echo 'preoutgoing.nomodule = python:nomodule' >> ../a/.hg/hgrc
514 $ echo 'preoutgoing.nomodule = python:nomodule' >> ../a/.hg/hgrc
515 $ hg pull ../a
515 $ hg pull ../a
516 pulling from ../a
516 pulling from ../a
517 searching for changes
517 searching for changes
518 abort: preoutgoing.nomodule hook is invalid: "nomodule" not in a module
518 abort: preoutgoing.nomodule hook is invalid: "nomodule" not in a module
519 [255]
519 [255]
520
520
521 $ echo '[hooks]' > ../a/.hg/hgrc
521 $ echo '[hooks]' > ../a/.hg/hgrc
522 $ echo 'preoutgoing.badmodule = python:nomodule.nowhere' >> ../a/.hg/hgrc
522 $ echo 'preoutgoing.badmodule = python:nomodule.nowhere' >> ../a/.hg/hgrc
523 $ hg pull ../a
523 $ hg pull ../a
524 pulling from ../a
524 pulling from ../a
525 searching for changes
525 searching for changes
526 abort: preoutgoing.badmodule hook is invalid: import of "nomodule" failed
526 abort: preoutgoing.badmodule hook is invalid: import of "nomodule" failed
527 (run with --traceback for stack trace)
527 (run with --traceback for stack trace)
528 [255]
528 [255]
529
529
530 $ echo '[hooks]' > ../a/.hg/hgrc
530 $ echo '[hooks]' > ../a/.hg/hgrc
531 $ echo 'preoutgoing.unreachable = python:hooktests.container.unreachable' >> ../a/.hg/hgrc
531 $ echo 'preoutgoing.unreachable = python:hooktests.container.unreachable' >> ../a/.hg/hgrc
532 $ hg pull ../a
532 $ hg pull ../a
533 pulling from ../a
533 pulling from ../a
534 searching for changes
534 searching for changes
535 abort: preoutgoing.unreachable hook is invalid: import of "hooktests.container" failed
535 abort: preoutgoing.unreachable hook is invalid: import of "hooktests.container" failed
536 (run with --traceback for stack trace)
536 (run with --traceback for stack trace)
537 [255]
537 [255]
538
538
539 $ echo '[hooks]' > ../a/.hg/hgrc
539 $ echo '[hooks]' > ../a/.hg/hgrc
540 $ echo 'preoutgoing.syntaxerror = python:syntaxerror.syntaxerror' >> ../a/.hg/hgrc
540 $ echo 'preoutgoing.syntaxerror = python:syntaxerror.syntaxerror' >> ../a/.hg/hgrc
541 $ hg pull ../a
541 $ hg pull ../a
542 pulling from ../a
542 pulling from ../a
543 searching for changes
543 searching for changes
544 abort: preoutgoing.syntaxerror hook is invalid: import of "syntaxerror" failed
544 abort: preoutgoing.syntaxerror hook is invalid: import of "syntaxerror" failed
545 (run with --traceback for stack trace)
545 (run with --traceback for stack trace)
546 [255]
546 [255]
547
547
548 The second egrep is to filter out lines like ' ^', which are slightly
548 The second egrep is to filter out lines like ' ^', which are slightly
549 different between Python 2.6 and Python 2.7.
549 different between Python 2.6 and Python 2.7.
550 $ hg pull ../a --traceback 2>&1 | egrep -v '^( +File| [_a-zA-Z*(])' | egrep -v '^( )+(\^)?$'
550 $ hg pull ../a --traceback 2>&1 | egrep -v '^( +File| [_a-zA-Z*(])' | egrep -v '^( )+(\^)?$'
551 pulling from ../a
551 pulling from ../a
552 searching for changes
552 searching for changes
553 exception from first failed import attempt:
553 exception from first failed import attempt:
554 Traceback (most recent call last):
554 Traceback (most recent call last):
555 SyntaxError: * (glob)
555 SyntaxError: * (glob)
556 exception from second failed import attempt:
556 exception from second failed import attempt:
557 Traceback (most recent call last):
557 Traceback (most recent call last):
558 ImportError: No module named hgext_syntaxerror
558 ImportError: No module named hgext_syntaxerror
559 Traceback (most recent call last):
559 Traceback (most recent call last):
560 HookLoadError: preoutgoing.syntaxerror hook is invalid: import of "syntaxerror" failed
560 HookLoadError: preoutgoing.syntaxerror hook is invalid: import of "syntaxerror" failed
561 abort: preoutgoing.syntaxerror hook is invalid: import of "syntaxerror" failed
561 abort: preoutgoing.syntaxerror hook is invalid: import of "syntaxerror" failed
562
562
563 $ echo '[hooks]' > ../a/.hg/hgrc
563 $ echo '[hooks]' > ../a/.hg/hgrc
564 $ echo 'preoutgoing.pass = python:hooktests.passhook' >> ../a/.hg/hgrc
564 $ echo 'preoutgoing.pass = python:hooktests.passhook' >> ../a/.hg/hgrc
565 $ hg pull ../a
565 $ hg pull ../a
566 pulling from ../a
566 pulling from ../a
567 searching for changes
567 searching for changes
568 hook args:
568 hook args:
569 hooktype preoutgoing
569 hooktype preoutgoing
570 source pull
570 source pull
571 adding changesets
571 adding changesets
572 adding manifests
572 adding manifests
573 adding file changes
573 adding file changes
574 added 1 changesets with 1 changes to 1 files
574 added 1 changesets with 1 changes to 1 files
575 adding remote bookmark quux
575 adding remote bookmark quux
576 (run 'hg update' to get a working copy)
576 (run 'hg update' to get a working copy)
577
577
578 post- python hooks that fail to *run* don't cause an abort
578 post- python hooks that fail to *run* don't cause an abort
579 $ rm ../a/.hg/hgrc
579 $ rm ../a/.hg/hgrc
580 $ echo '[hooks]' > .hg/hgrc
580 $ echo '[hooks]' > .hg/hgrc
581 $ echo 'post-pull.broken = python:hooktests.brokenhook' >> .hg/hgrc
581 $ echo 'post-pull.broken = python:hooktests.brokenhook' >> .hg/hgrc
582 $ hg pull ../a
582 $ hg pull ../a
583 pulling from ../a
583 pulling from ../a
584 searching for changes
584 searching for changes
585 no changes found
585 no changes found
586 error: post-pull.broken hook raised an exception: unsupported operand type(s) for +: 'int' and 'dict'
586 error: post-pull.broken hook raised an exception: unsupported operand type(s) for +: 'int' and 'dict'
587 (run with --traceback for stack trace)
587 (run with --traceback for stack trace)
588
588
589 but post- python hooks that fail to *load* do
589 but post- python hooks that fail to *load* do
590 $ echo '[hooks]' > .hg/hgrc
590 $ echo '[hooks]' > .hg/hgrc
591 $ echo 'post-pull.nomodule = python:nomodule' >> .hg/hgrc
591 $ echo 'post-pull.nomodule = python:nomodule' >> .hg/hgrc
592 $ hg pull ../a
592 $ hg pull ../a
593 pulling from ../a
593 pulling from ../a
594 searching for changes
594 searching for changes
595 no changes found
595 no changes found
596 abort: post-pull.nomodule hook is invalid: "nomodule" not in a module
596 abort: post-pull.nomodule hook is invalid: "nomodule" not in a module
597 [255]
597 [255]
598
598
599 $ echo '[hooks]' > .hg/hgrc
599 $ echo '[hooks]' > .hg/hgrc
600 $ echo 'post-pull.badmodule = python:nomodule.nowhere' >> .hg/hgrc
600 $ echo 'post-pull.badmodule = python:nomodule.nowhere' >> .hg/hgrc
601 $ hg pull ../a
601 $ hg pull ../a
602 pulling from ../a
602 pulling from ../a
603 searching for changes
603 searching for changes
604 no changes found
604 no changes found
605 abort: post-pull.badmodule hook is invalid: import of "nomodule" failed
605 abort: post-pull.badmodule hook is invalid: import of "nomodule" failed
606 (run with --traceback for stack trace)
606 (run with --traceback for stack trace)
607 [255]
607 [255]
608
608
609 $ echo '[hooks]' > .hg/hgrc
609 $ echo '[hooks]' > .hg/hgrc
610 $ echo 'post-pull.nohook = python:hooktests.nohook' >> .hg/hgrc
610 $ echo 'post-pull.nohook = python:hooktests.nohook' >> .hg/hgrc
611 $ hg pull ../a
611 $ hg pull ../a
612 pulling from ../a
612 pulling from ../a
613 searching for changes
613 searching for changes
614 no changes found
614 no changes found
615 abort: post-pull.nohook hook is invalid: "hooktests.nohook" is not defined
615 abort: post-pull.nohook hook is invalid: "hooktests.nohook" is not defined
616 [255]
616 [255]
617
617
618 make sure --traceback works
618 make sure --traceback works
619
619
620 $ echo '[hooks]' > .hg/hgrc
620 $ echo '[hooks]' > .hg/hgrc
621 $ echo 'commit.abort = python:hooktests.aborthook' >> .hg/hgrc
621 $ echo 'commit.abort = python:hooktests.aborthook' >> .hg/hgrc
622
622
623 $ echo aa > a
623 $ echo aa > a
624 $ hg --traceback commit -d '0 0' -ma 2>&1 | grep '^Traceback'
624 $ hg --traceback commit -d '0 0' -ma 2>&1 | grep '^Traceback'
625 Traceback (most recent call last):
625 Traceback (most recent call last):
626
626
627 $ cd ..
627 $ cd ..
628 $ hg init c
628 $ hg init c
629 $ cd c
629 $ cd c
630
630
631 $ cat > hookext.py <<EOF
631 $ cat > hookext.py <<EOF
632 > def autohook(**args):
632 > def autohook(**args):
633 > print "Automatically installed hook"
633 > print "Automatically installed hook"
634 >
634 >
635 > def reposetup(ui, repo):
635 > def reposetup(ui, repo):
636 > repo.ui.setconfig("hooks", "commit.auto", autohook)
636 > repo.ui.setconfig("hooks", "commit.auto", autohook)
637 > EOF
637 > EOF
638 $ echo '[extensions]' >> .hg/hgrc
638 $ echo '[extensions]' >> .hg/hgrc
639 $ echo 'hookext = hookext.py' >> .hg/hgrc
639 $ echo 'hookext = hookext.py' >> .hg/hgrc
640
640
641 $ touch foo
641 $ touch foo
642 $ hg add foo
642 $ hg add foo
643 $ hg ci -d '0 0' -m 'add foo'
643 $ hg ci -d '0 0' -m 'add foo'
644 Automatically installed hook
644 Automatically installed hook
645 $ echo >> foo
645 $ echo >> foo
646 $ hg ci --debug -d '0 0' -m 'change foo'
646 $ hg ci --debug -d '0 0' -m 'change foo'
647 committing files:
647 committing files:
648 foo
648 foo
649 committing manifest
649 committing manifest
650 committing changelog
650 committing changelog
651 updating the branch cache
651 updating the branch cache
652 committed changeset 1:52998019f6252a2b893452765fcb0a47351a5708
652 committed changeset 1:52998019f6252a2b893452765fcb0a47351a5708
653 calling hook commit.auto: hgext_hookext.autohook
653 calling hook commit.auto: hgext_hookext.autohook
654 Automatically installed hook
654 Automatically installed hook
655
655
656 $ hg showconfig hooks
656 $ hg showconfig hooks
657 hooks.commit.auto=<function autohook at *> (glob)
657 hooks.commit.auto=<function autohook at *> (glob)
658
658
659 test python hook configured with python:[file]:[hook] syntax
659 test python hook configured with python:[file]:[hook] syntax
660
660
661 $ cd ..
661 $ cd ..
662 $ mkdir d
662 $ mkdir d
663 $ cd d
663 $ cd d
664 $ hg init repo
664 $ hg init repo
665 $ mkdir hooks
665 $ mkdir hooks
666
666
667 $ cd hooks
667 $ cd hooks
668 $ cat > testhooks.py <<EOF
668 $ cat > testhooks.py <<EOF
669 > def testhook(**args):
669 > def testhook(**args):
670 > print 'hook works'
670 > print 'hook works'
671 > EOF
671 > EOF
672 $ echo '[hooks]' > ../repo/.hg/hgrc
672 $ echo '[hooks]' > ../repo/.hg/hgrc
673 $ echo "pre-commit.test = python:`pwd`/testhooks.py:testhook" >> ../repo/.hg/hgrc
673 $ echo "pre-commit.test = python:`pwd`/testhooks.py:testhook" >> ../repo/.hg/hgrc
674
674
675 $ cd ../repo
675 $ cd ../repo
676 $ hg commit -d '0 0'
676 $ hg commit -d '0 0'
677 hook works
677 hook works
678 nothing changed
678 nothing changed
679 [1]
679 [1]
680
680
681 $ echo '[hooks]' > .hg/hgrc
681 $ echo '[hooks]' > .hg/hgrc
682 $ echo "update.ne = python:`pwd`/nonexistent.py:testhook" >> .hg/hgrc
682 $ echo "update.ne = python:`pwd`/nonexistent.py:testhook" >> .hg/hgrc
683 $ echo "pre-identify.npmd = python:`pwd`/:no_python_module_dir" >> .hg/hgrc
683 $ echo "pre-identify.npmd = python:`pwd`/:no_python_module_dir" >> .hg/hgrc
684
684
685 $ hg up null
685 $ hg up null
686 loading update.ne hook failed:
686 loading update.ne hook failed:
687 abort: No such file or directory: $TESTTMP/d/repo/nonexistent.py
687 abort: No such file or directory: $TESTTMP/d/repo/nonexistent.py
688 [255]
688 [255]
689
689
690 $ hg id
690 $ hg id
691 loading pre-identify.npmd hook failed:
691 loading pre-identify.npmd hook failed:
692 abort: No module named repo!
692 abort: No module named repo!
693 [255]
693 [255]
694
694
695 $ cd ../../b
695 $ cd ../../b
696
696
697 make sure --traceback works on hook import failure
697 make sure --traceback works on hook import failure
698
698
699 $ cat > importfail.py <<EOF
699 $ cat > importfail.py <<EOF
700 > import somebogusmodule
700 > import somebogusmodule
701 > # dereference something in the module to force demandimport to load it
701 > # dereference something in the module to force demandimport to load it
702 > somebogusmodule.whatever
702 > somebogusmodule.whatever
703 > EOF
703 > EOF
704
704
705 $ echo '[hooks]' > .hg/hgrc
705 $ echo '[hooks]' > .hg/hgrc
706 $ echo 'precommit.importfail = python:importfail.whatever' >> .hg/hgrc
706 $ echo 'precommit.importfail = python:importfail.whatever' >> .hg/hgrc
707
707
708 $ echo a >> a
708 $ echo a >> a
709 $ hg --traceback commit -ma 2>&1 | egrep -v '^( +File| [a-zA-Z(])'
709 $ hg --traceback commit -ma 2>&1 | egrep -v '^( +File| [a-zA-Z(])'
710 exception from first failed import attempt:
710 exception from first failed import attempt:
711 Traceback (most recent call last):
711 Traceback (most recent call last):
712 ImportError: No module named somebogusmodule
712 ImportError: No module named somebogusmodule
713 exception from second failed import attempt:
713 exception from second failed import attempt:
714 Traceback (most recent call last):
714 Traceback (most recent call last):
715 ImportError: No module named hgext_importfail
715 ImportError: No module named hgext_importfail
716 Traceback (most recent call last):
716 Traceback (most recent call last):
717 HookLoadError: precommit.importfail hook is invalid: import of "importfail" failed
717 HookLoadError: precommit.importfail hook is invalid: import of "importfail" failed
718 abort: precommit.importfail hook is invalid: import of "importfail" failed
718 abort: precommit.importfail hook is invalid: import of "importfail" failed
719
719
720 Issue1827: Hooks Update & Commit not completely post operation
720 Issue1827: Hooks Update & Commit not completely post operation
721
721
722 commit and update hooks should run after command completion. The largefiles
722 commit and update hooks should run after command completion. The largefiles
723 use demonstrates a recursive wlock, showing the hook doesn't run until the
723 use demonstrates a recursive wlock, showing the hook doesn't run until the
724 final release (and dirstate flush).
724 final release (and dirstate flush).
725
725
726 $ echo '[hooks]' > .hg/hgrc
726 $ echo '[hooks]' > .hg/hgrc
727 $ echo 'commit = hg id' >> .hg/hgrc
727 $ echo 'commit = hg id' >> .hg/hgrc
728 $ echo 'update = hg id' >> .hg/hgrc
728 $ echo 'update = hg id' >> .hg/hgrc
729 $ echo bb > a
729 $ echo bb > a
730 $ hg ci -ma
730 $ hg ci -ma
731 223eafe2750c tip
731 223eafe2750c tip
732 $ hg up 0 --config extensions.largefiles=
732 $ hg up 0 --config extensions.largefiles=
733 cb9a9f314b8b
733 cb9a9f314b8b
734 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
734 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
735
735
736 make sure --verbose (and --quiet/--debug etc.) are propagated to the local ui
736 make sure --verbose (and --quiet/--debug etc.) are propagated to the local ui
737 that is passed to pre/post hooks
737 that is passed to pre/post hooks
738
738
739 $ echo '[hooks]' > .hg/hgrc
739 $ echo '[hooks]' > .hg/hgrc
740 $ echo 'pre-identify = python:hooktests.verbosehook' >> .hg/hgrc
740 $ echo 'pre-identify = python:hooktests.verbosehook' >> .hg/hgrc
741 $ hg id
741 $ hg id
742 cb9a9f314b8b
742 cb9a9f314b8b
743 $ hg id --verbose
743 $ hg id --verbose
744 calling hook pre-identify: hooktests.verbosehook
744 calling hook pre-identify: hooktests.verbosehook
745 verbose output from hook
745 verbose output from hook
746 cb9a9f314b8b
746 cb9a9f314b8b
747
747
748 Ensure hooks can be prioritized
748 Ensure hooks can be prioritized
749
749
750 $ echo '[hooks]' > .hg/hgrc
750 $ echo '[hooks]' > .hg/hgrc
751 $ echo 'pre-identify.a = python:hooktests.verbosehook' >> .hg/hgrc
751 $ echo 'pre-identify.a = python:hooktests.verbosehook' >> .hg/hgrc
752 $ echo 'pre-identify.b = python:hooktests.verbosehook' >> .hg/hgrc
752 $ echo 'pre-identify.b = python:hooktests.verbosehook' >> .hg/hgrc
753 $ echo 'priority.pre-identify.b = 1' >> .hg/hgrc
753 $ echo 'priority.pre-identify.b = 1' >> .hg/hgrc
754 $ echo 'pre-identify.c = python:hooktests.verbosehook' >> .hg/hgrc
754 $ echo 'pre-identify.c = python:hooktests.verbosehook' >> .hg/hgrc
755 $ hg id --verbose
755 $ hg id --verbose
756 calling hook pre-identify.b: hooktests.verbosehook
756 calling hook pre-identify.b: hooktests.verbosehook
757 verbose output from hook
757 verbose output from hook
758 calling hook pre-identify.a: hooktests.verbosehook
758 calling hook pre-identify.a: hooktests.verbosehook
759 verbose output from hook
759 verbose output from hook
760 calling hook pre-identify.c: hooktests.verbosehook
760 calling hook pre-identify.c: hooktests.verbosehook
761 verbose output from hook
761 verbose output from hook
762 cb9a9f314b8b
762 cb9a9f314b8b
763
763
764 new tags must be visible in pretxncommit (issue3210)
764 new tags must be visible in pretxncommit (issue3210)
765
765
766 $ echo 'pretxncommit.printtags = python:hooktests.printtags' >> .hg/hgrc
766 $ echo 'pretxncommit.printtags = python:hooktests.printtags' >> .hg/hgrc
767 $ hg tag -f foo
767 $ hg tag -f foo
768 ['a', 'foo', 'tip']
768 ['a', 'foo', 'tip']
769
769
770 post-init hooks must not crash (issue4983)
770 post-init hooks must not crash (issue4983)
771 This also creates the `to` repo for the next test block.
771 This also creates the `to` repo for the next test block.
772
772
773 $ cd ..
773 $ cd ..
774 $ cat << EOF >> hgrc-with-post-init-hook
774 $ cat << EOF >> hgrc-with-post-init-hook
775 > [hooks]
775 > [hooks]
776 > post-init = sh -c "printenv.py post-init"
776 > post-init = sh -c "printenv.py post-init"
777 > EOF
777 > EOF
778 $ HGRCPATH=hgrc-with-post-init-hook hg init to
778 $ HGRCPATH=hgrc-with-post-init-hook hg init to
779 post-init hook: HG_ARGS=init to HG_HOOKNAME=post-init HG_HOOKTYPE=post-init HG_OPTS={'insecure': None, 'remotecmd': '', 'ssh': ''} HG_PATS=['to'] HG_RESULT=0
779 post-init hook: HG_ARGS=init to HG_HOOKNAME=post-init HG_HOOKTYPE=post-init HG_OPTS={'insecure': None, 'remotecmd': '', 'ssh': ''} HG_PATS=['to'] HG_RESULT=0
780
780
781 new commits must be visible in pretxnchangegroup (issue3428)
781 new commits must be visible in pretxnchangegroup (issue3428)
782
782
783 $ echo '[hooks]' >> to/.hg/hgrc
783 $ echo '[hooks]' >> to/.hg/hgrc
784 $ echo 'prechangegroup = hg --traceback tip' >> to/.hg/hgrc
784 $ echo 'prechangegroup = hg --traceback tip' >> to/.hg/hgrc
785 $ echo 'pretxnchangegroup = hg --traceback tip' >> to/.hg/hgrc
785 $ echo 'pretxnchangegroup = hg --traceback tip' >> to/.hg/hgrc
786 $ echo a >> to/a
786 $ echo a >> to/a
787 $ hg --cwd to ci -Ama
787 $ hg --cwd to ci -Ama
788 adding a
788 adding a
789 $ hg clone to from
789 $ hg clone to from
790 updating to branch default
790 updating to branch default
791 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
791 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
792 $ echo aa >> from/a
792 $ echo aa >> from/a
793 $ hg --cwd from ci -mb
793 $ hg --cwd from ci -mb
794 $ hg --cwd from push
794 $ hg --cwd from push
795 pushing to $TESTTMP/to (glob)
795 pushing to $TESTTMP/to (glob)
796 searching for changes
796 searching for changes
797 changeset: 0:cb9a9f314b8b
797 changeset: 0:cb9a9f314b8b
798 tag: tip
798 tag: tip
799 user: test
799 user: test
800 date: Thu Jan 01 00:00:00 1970 +0000
800 date: Thu Jan 01 00:00:00 1970 +0000
801 summary: a
801 summary: a
802
802
803 adding changesets
803 adding changesets
804 adding manifests
804 adding manifests
805 adding file changes
805 adding file changes
806 added 1 changesets with 1 changes to 1 files
806 added 1 changesets with 1 changes to 1 files
807 changeset: 1:9836a07b9b9d
807 changeset: 1:9836a07b9b9d
808 tag: tip
808 tag: tip
809 user: test
809 user: test
810 date: Thu Jan 01 00:00:00 1970 +0000
810 date: Thu Jan 01 00:00:00 1970 +0000
811 summary: b
811 summary: b
812
812
813
813
814 pretxnclose hook failure should abort the transaction
814 pretxnclose hook failure should abort the transaction
815
815
816 $ hg init txnfailure
816 $ hg init txnfailure
817 $ cd txnfailure
817 $ cd txnfailure
818 $ touch a && hg commit -Aqm a
818 $ touch a && hg commit -Aqm a
819 $ cat >> .hg/hgrc <<EOF
819 $ cat >> .hg/hgrc <<EOF
820 > [hooks]
820 > [hooks]
821 > pretxnclose.error = exit 1
821 > pretxnclose.error = exit 1
822 > EOF
822 > EOF
823 $ hg strip -r 0 --config extensions.strip=
823 $ hg strip -r 0 --config extensions.strip=
824 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
824 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
825 saved backup bundle to * (glob)
825 saved backup bundle to * (glob)
826 transaction abort!
826 transaction abort!
827 rollback completed
827 rollback completed
828 strip failed, backup bundle stored in * (glob)
828 strip failed, backup bundle stored in * (glob)
829 abort: pretxnclose.error hook exited with status 1
829 abort: pretxnclose.error hook exited with status 1
830 [255]
830 [255]
831 $ hg recover
831 $ hg recover
832 no interrupted transaction available
832 no interrupted transaction available
833 [1]
833 [1]
834 $ cd ..
834 $ cd ..
835
835
836 check whether HG_PENDING makes pending changes only in related
836 check whether HG_PENDING makes pending changes only in related
837 repositories visible to an external hook.
837 repositories visible to an external hook.
838
838
839 (emulate a transaction running concurrently by copied
839 (emulate a transaction running concurrently by copied
840 .hg/store/00changelog.i.a in subsequent test)
840 .hg/store/00changelog.i.a in subsequent test)
841
841
842 $ cat > $TESTTMP/savepending.sh <<EOF
842 $ cat > $TESTTMP/savepending.sh <<EOF
843 > cp .hg/store/00changelog.i.a .hg/store/00changelog.i.a.saved
843 > cp .hg/store/00changelog.i.a .hg/store/00changelog.i.a.saved
844 > exit 1 # to avoid adding new revision for subsequent tests
844 > exit 1 # to avoid adding new revision for subsequent tests
845 > EOF
845 > EOF
846 $ cd a
846 $ cd a
847 $ hg tip -q
847 $ hg tip -q
848 4:539e4b31b6dc
848 4:539e4b31b6dc
849 $ hg --config hooks.pretxnclose="sh $TESTTMP/savepending.sh" commit -m "invisible"
849 $ hg --config hooks.pretxnclose="sh $TESTTMP/savepending.sh" commit -m "invisible"
850 transaction abort!
850 transaction abort!
851 rollback completed
851 rollback completed
852 abort: pretxnclose hook exited with status 1
852 abort: pretxnclose hook exited with status 1
853 [255]
853 [255]
854 $ cp .hg/store/00changelog.i.a.saved .hg/store/00changelog.i.a
854 $ cp .hg/store/00changelog.i.a.saved .hg/store/00changelog.i.a
855
855
856 (check (in)visibility of new changeset while transaction running in
856 (check (in)visibility of new changeset while transaction running in
857 repo)
857 repo)
858
858
859 $ cat > $TESTTMP/checkpending.sh <<EOF
859 $ cat > $TESTTMP/checkpending.sh <<EOF
860 > echo '@a'
860 > echo '@a'
861 > hg -R "$TESTTMP/a" tip -q
861 > hg -R "$TESTTMP/a" tip -q
862 > echo '@a/nested'
862 > echo '@a/nested'
863 > hg -R "$TESTTMP/a/nested" tip -q
863 > hg -R "$TESTTMP/a/nested" tip -q
864 > exit 1 # to avoid adding new revision for subsequent tests
864 > exit 1 # to avoid adding new revision for subsequent tests
865 > EOF
865 > EOF
866 $ hg init nested
866 $ hg init nested
867 $ cd nested
867 $ cd nested
868 $ echo a > a
868 $ echo a > a
869 $ hg add a
869 $ hg add a
870 $ hg --config hooks.pretxnclose="sh $TESTTMP/checkpending.sh" commit -m '#0'
870 $ hg --config hooks.pretxnclose="sh $TESTTMP/checkpending.sh" commit -m '#0'
871 @a
871 @a
872 4:539e4b31b6dc
872 4:539e4b31b6dc
873 @a/nested
873 @a/nested
874 0:bf5e395ced2c
874 0:bf5e395ced2c
875 transaction abort!
875 transaction abort!
876 rollback completed
876 rollback completed
877 abort: pretxnclose hook exited with status 1
877 abort: pretxnclose hook exited with status 1
878 [255]
878 [255]
879
879
880 Hook from untrusted hgrc are reported as failure
880 Hook from untrusted hgrc are reported as failure
881 ================================================
881 ================================================
882
882
883 $ cat << EOF > $TESTTMP/untrusted.py
883 $ cat << EOF > $TESTTMP/untrusted.py
884 > from mercurial import scmutil, util
884 > from mercurial import scmutil, util
885 > def uisetup(ui):
885 > def uisetup(ui):
886 > class untrustedui(ui.__class__):
886 > class untrustedui(ui.__class__):
887 > def _trusted(self, fp, f):
887 > def _trusted(self, fp, f):
888 > if util.normpath(fp.name).endswith('untrusted/.hg/hgrc'):
888 > if util.normpath(fp.name).endswith('untrusted/.hg/hgrc'):
889 > return False
889 > return False
890 > return super(untrustedui, self)._trusted(fp, f)
890 > return super(untrustedui, self)._trusted(fp, f)
891 > ui.__class__ = untrustedui
891 > ui.__class__ = untrustedui
892 > EOF
892 > EOF
893 $ cat << EOF >> $HGRCPATH
893 $ cat << EOF >> $HGRCPATH
894 > [extensions]
894 > [extensions]
895 > untrusted=$TESTTMP/untrusted.py
895 > untrusted=$TESTTMP/untrusted.py
896 > EOF
896 > EOF
897 $ hg init untrusted
897 $ hg init untrusted
898 $ cd untrusted
898 $ cd untrusted
899
899
900 Non-blocking hook
900 Non-blocking hook
901 -----------------
901 -----------------
902
902
903 $ cat << EOF >> .hg/hgrc
903 $ cat << EOF >> .hg/hgrc
904 > [hooks]
904 > [hooks]
905 > txnclose.testing=echo txnclose hook called
905 > txnclose.testing=echo txnclose hook called
906 > EOF
906 > EOF
907 $ touch a && hg commit -Aqm a
907 $ touch a && hg commit -Aqm a
908 warning: untrusted hook txnclose.testing not executed
908 warning: untrusted hook txnclose.testing not executed
909 $ hg log
909 $ hg log
910 changeset: 0:3903775176ed
910 changeset: 0:3903775176ed
911 tag: tip
911 tag: tip
912 user: test
912 user: test
913 date: Thu Jan 01 00:00:00 1970 +0000
913 date: Thu Jan 01 00:00:00 1970 +0000
914 summary: a
914 summary: a
915
915
916
916
917 Non-blocking hook
917 Non-blocking hook
918 -----------------
918 -----------------
919
919
920 $ cat << EOF >> .hg/hgrc
920 $ cat << EOF >> .hg/hgrc
921 > [hooks]
921 > [hooks]
922 > pretxnclose.testing=echo pre-txnclose hook called
922 > pretxnclose.testing=echo pre-txnclose hook called
923 > EOF
923 > EOF
924 $ touch b && hg commit -Aqm a
924 $ touch b && hg commit -Aqm a
925 transaction abort!
925 transaction abort!
926 rollback completed
926 rollback completed
927 abort: untrusted hook pretxnclose.testing not executed
927 abort: untrusted hook pretxnclose.testing not executed
928 (see 'hg help config.trusted')
928 (see 'hg help config.trusted')
929 [255]
929 [255]
930 $ hg log
930 $ hg log
931 changeset: 0:3903775176ed
931 changeset: 0:3903775176ed
932 tag: tip
932 tag: tip
933 user: test
933 user: test
934 date: Thu Jan 01 00:00:00 1970 +0000
934 date: Thu Jan 01 00:00:00 1970 +0000
935 summary: a
935 summary: a
936
936
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
General Comments 0
You need to be logged in to leave comments. Login now