##// END OF EJS Templates
uncommit: add options to update to the current user or current date...
Matt Harbison -
r43173:66048f6b default
parent child Browse files
Show More
@@ -1,264 +1,267 b''
1 1 # uncommit - undo the actions of a commit
2 2 #
3 3 # Copyright 2011 Peter Arrenbrecht <peter.arrenbrecht@gmail.com>
4 4 # Logilab SA <contact@logilab.fr>
5 5 # Pierre-Yves David <pierre-yves.david@ens-lyon.org>
6 6 # Patrick Mezard <patrick@mezard.eu>
7 7 # Copyright 2016 Facebook, Inc.
8 8 #
9 9 # This software may be used and distributed according to the terms of the
10 10 # GNU General Public License version 2 or any later version.
11 11
12 12 """uncommit part or all of a local changeset (EXPERIMENTAL)
13 13
14 14 This command undoes the effect of a local commit, returning the affected
15 15 files to their uncommitted state. This means that files modified, added or
16 16 removed in the changeset will be left unchanged, and so will remain modified,
17 17 added and removed in the working directory.
18 18 """
19 19
20 20 from __future__ import absolute_import
21 21
22 22 from mercurial.i18n import _
23 23
24 24 from mercurial import (
25 25 cmdutil,
26 26 commands,
27 27 context,
28 28 copies as copiesmod,
29 29 error,
30 30 node,
31 31 obsutil,
32 32 pycompat,
33 33 registrar,
34 34 rewriteutil,
35 35 scmutil,
36 36 util,
37 37 )
38 38
39 39 cmdtable = {}
40 40 command = registrar.command(cmdtable)
41 41
42 42 configtable = {}
43 43 configitem = registrar.configitem(configtable)
44 44
45 45 configitem('experimental', 'uncommitondirtywdir',
46 46 default=False,
47 47 )
48 48 configitem('experimental', 'uncommit.keep',
49 49 default=False,
50 50 )
51 51
52 52 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
53 53 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
54 54 # be specifying the version(s) of Mercurial they are tested with, or
55 55 # leave the attribute unspecified.
56 56 testedwith = 'ships-with-hg-core'
57 57
58 58 def _commitfiltered(repo, ctx, match, keepcommit, message=None, user=None,
59 59 date=None):
60 60 """Recommit ctx with changed files not in match. Return the new
61 61 node identifier, or None if nothing changed.
62 62 """
63 63 base = ctx.p1()
64 64 # ctx
65 65 initialfiles = set(ctx.files())
66 66 exclude = set(f for f in initialfiles if match(f))
67 67
68 68 # No files matched commit, so nothing excluded
69 69 if not exclude:
70 70 return None
71 71
72 72 # return the p1 so that we don't create an obsmarker later
73 73 if not keepcommit:
74 74 return ctx.p1().node()
75 75
76 76 files = (initialfiles - exclude)
77 77 # Filter copies
78 78 copied = copiesmod.pathcopies(base, ctx)
79 79 copied = dict((dst, src) for dst, src in copied.iteritems()
80 80 if dst in files)
81 81 def filectxfn(repo, memctx, path, contentctx=ctx, redirect=()):
82 82 if path not in contentctx:
83 83 return None
84 84 fctx = contentctx[path]
85 85 mctx = context.memfilectx(repo, memctx, fctx.path(), fctx.data(),
86 86 fctx.islink(),
87 87 fctx.isexec(),
88 88 copysource=copied.get(path))
89 89 return mctx
90 90
91 91 if not files:
92 92 repo.ui.status(_("note: keeping empty commit\n"))
93 93
94 94 if message is None:
95 95 message = ctx.description()
96 96 if not user:
97 97 user = ctx.user()
98 98 if not date:
99 99 date = ctx.date()
100 100
101 101 new = context.memctx(repo,
102 102 parents=[base.node(), node.nullid],
103 103 text=message,
104 104 files=files,
105 105 filectxfn=filectxfn,
106 106 user=user,
107 107 date=date,
108 108 extra=ctx.extra())
109 109 return repo.commitctx(new)
110 110
111 111 @command('uncommit',
112 112 [('', 'keep', None, _('allow an empty commit after uncommiting')),
113 113 ('', 'allow-dirty-working-copy', False,
114 114 _('allow uncommit with outstanding changes'))
115 ] + commands.walkopts + commands.commitopts + commands.commitopts2,
115 ] + commands.walkopts + commands.commitopts + commands.commitopts2
116 + commands.commitopts3,
116 117 _('[OPTION]... [FILE]...'),
117 118 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT)
118 119 def uncommit(ui, repo, *pats, **opts):
119 120 """uncommit part or all of a local changeset
120 121
121 122 This command undoes the effect of a local commit, returning the affected
122 123 files to their uncommitted state. This means that files modified or
123 124 deleted in the changeset will be left unchanged, and so will remain
124 125 modified in the working directory.
125 126
126 127 If no files are specified, the commit will be pruned, unless --keep is
127 128 given.
128 129 """
129 130 opts = pycompat.byteskwargs(opts)
130 131
132 cmdutil.resolvecommitoptions(ui, opts)
133
131 134 with repo.wlock(), repo.lock():
132 135
133 136 m, a, r, d = repo.status()[:4]
134 137 isdirtypath = any(set(m + a + r + d) & set(pats))
135 138 allowdirtywcopy = (opts['allow_dirty_working_copy'] or
136 139 repo.ui.configbool('experimental', 'uncommitondirtywdir'))
137 140 if not allowdirtywcopy and (not pats or isdirtypath):
138 141 cmdutil.bailifchanged(repo, hint=_('requires '
139 142 '--allow-dirty-working-copy to uncommit'))
140 143 old = repo['.']
141 144 rewriteutil.precheck(repo, [old.rev()], 'uncommit')
142 145 if len(old.parents()) > 1:
143 146 raise error.Abort(_("cannot uncommit merge changeset"))
144 147
145 148 match = scmutil.match(old, pats, opts)
146 149
147 150 # Check all explicitly given files; abort if there's a problem.
148 151 if match.files():
149 152 s = old.status(old.p1(), match, listclean=True)
150 153 eligible = set(s.added) | set(s.modified) | set(s.removed)
151 154
152 155 badfiles = set(match.files()) - eligible
153 156
154 157 # Naming a parent directory of an eligible file is OK, even
155 158 # if not everything tracked in that directory can be
156 159 # uncommitted.
157 160 if badfiles:
158 161 badfiles -= {f for f in util.dirs(eligible)}
159 162
160 163 for f in sorted(badfiles):
161 164 if f in s.clean:
162 165 hint = _(b"file was not changed in working directory "
163 166 b"parent")
164 167 elif repo.wvfs.exists(f):
165 168 hint = _(b"file was untracked in working directory parent")
166 169 else:
167 170 hint = _(b"file does not exist")
168 171
169 172 raise error.Abort(_(b'cannot uncommit "%s"')
170 173 % scmutil.getuipathfn(repo)(f), hint=hint)
171 174
172 175 with repo.transaction('uncommit'):
173 176 if not (opts[b'message'] or opts[b'logfile']):
174 177 opts[b'message'] = old.description()
175 178 message = cmdutil.logmessage(ui, pycompat.byteskwargs(opts))
176 179
177 180 keepcommit = pats
178 181 if not keepcommit:
179 182 if opts.get('keep') is not None:
180 183 keepcommit = opts.get('keep')
181 184 else:
182 185 keepcommit = ui.configbool('experimental', 'uncommit.keep')
183 186 newid = _commitfiltered(repo, old, match, keepcommit,
184 187 message=message, user=opts.get(b'user'),
185 188 date=opts.get(b'date'))
186 189 if newid is None:
187 190 ui.status(_("nothing to uncommit\n"))
188 191 return 1
189 192
190 193 mapping = {}
191 194 if newid != old.p1().node():
192 195 # Move local changes on filtered changeset
193 196 mapping[old.node()] = (newid,)
194 197 else:
195 198 # Fully removed the old commit
196 199 mapping[old.node()] = ()
197 200
198 201 with repo.dirstate.parentchange():
199 202 scmutil.movedirstate(repo, repo[newid], match)
200 203
201 204 scmutil.cleanupnodes(repo, mapping, 'uncommit', fixphase=True)
202 205
203 206 def predecessormarkers(ctx):
204 207 """yields the obsolete markers marking the given changeset as a successor"""
205 208 for data in ctx.repo().obsstore.predecessors.get(ctx.node(), ()):
206 209 yield obsutil.marker(ctx.repo(), data)
207 210
208 211 @command('unamend', [], helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
209 212 helpbasic=True)
210 213 def unamend(ui, repo, **opts):
211 214 """undo the most recent amend operation on a current changeset
212 215
213 216 This command will roll back to the previous version of a changeset,
214 217 leaving working directory in state in which it was before running
215 218 `hg amend` (e.g. files modified as part of an amend will be
216 219 marked as modified `hg status`)
217 220 """
218 221
219 222 unfi = repo.unfiltered()
220 223 with repo.wlock(), repo.lock(), repo.transaction('unamend'):
221 224
222 225 # identify the commit from which to unamend
223 226 curctx = repo['.']
224 227
225 228 rewriteutil.precheck(repo, [curctx.rev()], 'unamend')
226 229
227 230 # identify the commit to which to unamend
228 231 markers = list(predecessormarkers(curctx))
229 232 if len(markers) != 1:
230 233 e = _("changeset must have one predecessor, found %i predecessors")
231 234 raise error.Abort(e % len(markers))
232 235
233 236 prednode = markers[0].prednode()
234 237 predctx = unfi[prednode]
235 238
236 239 # add an extra so that we get a new hash
237 240 # note: allowing unamend to undo an unamend is an intentional feature
238 241 extras = predctx.extra()
239 242 extras['unamend_source'] = curctx.hex()
240 243
241 244 def filectxfn(repo, ctx_, path):
242 245 try:
243 246 return predctx.filectx(path)
244 247 except KeyError:
245 248 return None
246 249
247 250 # Make a new commit same as predctx
248 251 newctx = context.memctx(repo,
249 252 parents=(predctx.p1(), predctx.p2()),
250 253 text=predctx.description(),
251 254 files=predctx.files(),
252 255 filectxfn=filectxfn,
253 256 user=predctx.user(),
254 257 date=predctx.date(),
255 258 extra=extras)
256 259 newprednode = repo.commitctx(newctx)
257 260 newpredctx = repo[newprednode]
258 261 dirstate = repo.dirstate
259 262
260 263 with dirstate.parentchange():
261 264 scmutil.movedirstate(repo, newpredctx)
262 265
263 266 mapping = {curctx.node(): (newprednode,)}
264 267 scmutil.cleanupnodes(repo, mapping, 'unamend', fixphase=True)
@@ -1,3439 +1,3455 b''
1 1 # cmdutil.py - help for command processing in mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import copy as copymod
11 11 import errno
12 12 import os
13 13 import re
14 14
15 15 from .i18n import _
16 16 from .node import (
17 17 hex,
18 18 nullid,
19 19 nullrev,
20 20 short,
21 21 )
22 22
23 23 from . import (
24 24 bookmarks,
25 25 changelog,
26 26 copies,
27 27 crecord as crecordmod,
28 28 dirstateguard,
29 29 encoding,
30 30 error,
31 31 formatter,
32 32 logcmdutil,
33 33 match as matchmod,
34 34 merge as mergemod,
35 35 mergeutil,
36 36 obsolete,
37 37 patch,
38 38 pathutil,
39 39 phases,
40 40 pycompat,
41 41 repair,
42 42 revlog,
43 43 rewriteutil,
44 44 scmutil,
45 45 smartset,
46 46 state as statemod,
47 47 subrepoutil,
48 48 templatekw,
49 49 templater,
50 50 util,
51 51 vfs as vfsmod,
52 52 )
53 53
54 54 from .utils import (
55 55 dateutil,
56 56 stringutil,
57 57 )
58 58
59 59 stringio = util.stringio
60 60
61 61 # templates of common command options
62 62
63 63 dryrunopts = [
64 64 ('n', 'dry-run', None,
65 65 _('do not perform actions, just print output')),
66 66 ]
67 67
68 68 confirmopts = [
69 69 ('', 'confirm', None,
70 70 _('ask before applying actions')),
71 71 ]
72 72
73 73 remoteopts = [
74 74 ('e', 'ssh', '',
75 75 _('specify ssh command to use'), _('CMD')),
76 76 ('', 'remotecmd', '',
77 77 _('specify hg command to run on the remote side'), _('CMD')),
78 78 ('', 'insecure', None,
79 79 _('do not verify server certificate (ignoring web.cacerts config)')),
80 80 ]
81 81
82 82 walkopts = [
83 83 ('I', 'include', [],
84 84 _('include names matching the given patterns'), _('PATTERN')),
85 85 ('X', 'exclude', [],
86 86 _('exclude names matching the given patterns'), _('PATTERN')),
87 87 ]
88 88
89 89 commitopts = [
90 90 ('m', 'message', '',
91 91 _('use text as commit message'), _('TEXT')),
92 92 ('l', 'logfile', '',
93 93 _('read commit message from file'), _('FILE')),
94 94 ]
95 95
96 96 commitopts2 = [
97 97 ('d', 'date', '',
98 98 _('record the specified date as commit date'), _('DATE')),
99 99 ('u', 'user', '',
100 100 _('record the specified user as committer'), _('USER')),
101 101 ]
102 102
103 commitopts3 = [
104 (b'D', b'current-date', None,
105 _(b'record the current date as commit date')),
106 (b'U', b'current-user', None,
107 _(b'record the current user as committer')),
108 ]
109
103 110 formatteropts = [
104 111 ('T', 'template', '',
105 112 _('display with template'), _('TEMPLATE')),
106 113 ]
107 114
108 115 templateopts = [
109 116 ('', 'style', '',
110 117 _('display using template map file (DEPRECATED)'), _('STYLE')),
111 118 ('T', 'template', '',
112 119 _('display with template'), _('TEMPLATE')),
113 120 ]
114 121
115 122 logopts = [
116 123 ('p', 'patch', None, _('show patch')),
117 124 ('g', 'git', None, _('use git extended diff format')),
118 125 ('l', 'limit', '',
119 126 _('limit number of changes displayed'), _('NUM')),
120 127 ('M', 'no-merges', None, _('do not show merges')),
121 128 ('', 'stat', None, _('output diffstat-style summary of changes')),
122 129 ('G', 'graph', None, _("show the revision DAG")),
123 130 ] + templateopts
124 131
125 132 diffopts = [
126 133 ('a', 'text', None, _('treat all files as text')),
127 134 ('g', 'git', None, _('use git extended diff format')),
128 135 ('', 'binary', None, _('generate binary diffs in git mode (default)')),
129 136 ('', 'nodates', None, _('omit dates from diff headers'))
130 137 ]
131 138
132 139 diffwsopts = [
133 140 ('w', 'ignore-all-space', None,
134 141 _('ignore white space when comparing lines')),
135 142 ('b', 'ignore-space-change', None,
136 143 _('ignore changes in the amount of white space')),
137 144 ('B', 'ignore-blank-lines', None,
138 145 _('ignore changes whose lines are all blank')),
139 146 ('Z', 'ignore-space-at-eol', None,
140 147 _('ignore changes in whitespace at EOL')),
141 148 ]
142 149
143 150 diffopts2 = [
144 151 ('', 'noprefix', None, _('omit a/ and b/ prefixes from filenames')),
145 152 ('p', 'show-function', None, _('show which function each change is in')),
146 153 ('', 'reverse', None, _('produce a diff that undoes the changes')),
147 154 ] + diffwsopts + [
148 155 ('U', 'unified', '',
149 156 _('number of lines of context to show'), _('NUM')),
150 157 ('', 'stat', None, _('output diffstat-style summary of changes')),
151 158 ('', 'root', '', _('produce diffs relative to subdirectory'), _('DIR')),
152 159 ]
153 160
154 161 mergetoolopts = [
155 162 ('t', 'tool', '', _('specify merge tool'), _('TOOL')),
156 163 ]
157 164
158 165 similarityopts = [
159 166 ('s', 'similarity', '',
160 167 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
161 168 ]
162 169
163 170 subrepoopts = [
164 171 ('S', 'subrepos', None,
165 172 _('recurse into subrepositories'))
166 173 ]
167 174
168 175 debugrevlogopts = [
169 176 ('c', 'changelog', False, _('open changelog')),
170 177 ('m', 'manifest', False, _('open manifest')),
171 178 ('', 'dir', '', _('open directory manifest')),
172 179 ]
173 180
174 181 # special string such that everything below this line will be ingored in the
175 182 # editor text
176 183 _linebelow = "^HG: ------------------------ >8 ------------------------$"
177 184
185 def resolvecommitoptions(ui, opts):
186 """modify commit options dict to handle related options
187 """
188 # N.B. this is extremely similar to setupheaderopts() in mq.py
189 if not opts.get(b'date') and opts.get(b'current_date'):
190 opts[b'date'] = b'%d %d' % dateutil.makedate()
191 if not opts.get(b'user') and opts.get(b'current_user'):
192 opts[b'user'] = ui.username()
193
178 194 def ishunk(x):
179 195 hunkclasses = (crecordmod.uihunk, patch.recordhunk)
180 196 return isinstance(x, hunkclasses)
181 197
182 198 def newandmodified(chunks, originalchunks):
183 199 newlyaddedandmodifiedfiles = set()
184 200 alsorestore = set()
185 201 for chunk in chunks:
186 202 if (ishunk(chunk) and chunk.header.isnewfile() and chunk not in
187 203 originalchunks):
188 204 newlyaddedandmodifiedfiles.add(chunk.header.filename())
189 205 alsorestore.update(set(chunk.header.files()) -
190 206 {chunk.header.filename()})
191 207 return newlyaddedandmodifiedfiles, alsorestore
192 208
193 209 def parsealiases(cmd):
194 210 return cmd.split("|")
195 211
196 212 def setupwrapcolorwrite(ui):
197 213 # wrap ui.write so diff output can be labeled/colorized
198 214 def wrapwrite(orig, *args, **kw):
199 215 label = kw.pop(r'label', '')
200 216 for chunk, l in patch.difflabel(lambda: args):
201 217 orig(chunk, label=label + l)
202 218
203 219 oldwrite = ui.write
204 220 def wrap(*args, **kwargs):
205 221 return wrapwrite(oldwrite, *args, **kwargs)
206 222 setattr(ui, 'write', wrap)
207 223 return oldwrite
208 224
209 225 def filterchunks(ui, originalhunks, usecurses, testfile, match,
210 226 operation=None):
211 227 try:
212 228 if usecurses:
213 229 if testfile:
214 230 recordfn = crecordmod.testdecorator(
215 231 testfile, crecordmod.testchunkselector)
216 232 else:
217 233 recordfn = crecordmod.chunkselector
218 234
219 235 return crecordmod.filterpatch(ui, originalhunks, recordfn,
220 236 operation)
221 237 except crecordmod.fallbackerror as e:
222 238 ui.warn('%s\n' % e.message)
223 239 ui.warn(_('falling back to text mode\n'))
224 240
225 241 return patch.filterpatch(ui, originalhunks, match, operation)
226 242
227 243 def recordfilter(ui, originalhunks, match, operation=None):
228 244 """ Prompts the user to filter the originalhunks and return a list of
229 245 selected hunks.
230 246 *operation* is used for to build ui messages to indicate the user what
231 247 kind of filtering they are doing: reverting, committing, shelving, etc.
232 248 (see patch.filterpatch).
233 249 """
234 250 usecurses = crecordmod.checkcurses(ui)
235 251 testfile = ui.config('experimental', 'crecordtest')
236 252 oldwrite = setupwrapcolorwrite(ui)
237 253 try:
238 254 newchunks, newopts = filterchunks(ui, originalhunks, usecurses,
239 255 testfile, match, operation)
240 256 finally:
241 257 ui.write = oldwrite
242 258 return newchunks, newopts
243 259
244 260 def dorecord(ui, repo, commitfunc, cmdsuggest, backupall,
245 261 filterfn, *pats, **opts):
246 262 opts = pycompat.byteskwargs(opts)
247 263 if not ui.interactive():
248 264 if cmdsuggest:
249 265 msg = _('running non-interactively, use %s instead') % cmdsuggest
250 266 else:
251 267 msg = _('running non-interactively')
252 268 raise error.Abort(msg)
253 269
254 270 # make sure username is set before going interactive
255 271 if not opts.get('user'):
256 272 ui.username() # raise exception, username not provided
257 273
258 274 def recordfunc(ui, repo, message, match, opts):
259 275 """This is generic record driver.
260 276
261 277 Its job is to interactively filter local changes, and
262 278 accordingly prepare working directory into a state in which the
263 279 job can be delegated to a non-interactive commit command such as
264 280 'commit' or 'qrefresh'.
265 281
266 282 After the actual job is done by non-interactive command, the
267 283 working directory is restored to its original state.
268 284
269 285 In the end we'll record interesting changes, and everything else
270 286 will be left in place, so the user can continue working.
271 287 """
272 288 if not opts.get('interactive-unshelve'):
273 289 checkunfinished(repo, commit=True)
274 290 wctx = repo[None]
275 291 merge = len(wctx.parents()) > 1
276 292 if merge:
277 293 raise error.Abort(_('cannot partially commit a merge '
278 294 '(use "hg commit" instead)'))
279 295
280 296 def fail(f, msg):
281 297 raise error.Abort('%s: %s' % (f, msg))
282 298
283 299 force = opts.get('force')
284 300 if not force:
285 301 vdirs = []
286 302 match = matchmod.badmatch(match, fail)
287 303 match.explicitdir = vdirs.append
288 304
289 305 status = repo.status(match=match)
290 306
291 307 overrides = {(b'ui', b'commitsubrepos'): True}
292 308
293 309 with repo.ui.configoverride(overrides, b'record'):
294 310 # subrepoutil.precommit() modifies the status
295 311 tmpstatus = scmutil.status(copymod.copy(status[0]),
296 312 copymod.copy(status[1]),
297 313 copymod.copy(status[2]),
298 314 copymod.copy(status[3]),
299 315 copymod.copy(status[4]),
300 316 copymod.copy(status[5]),
301 317 copymod.copy(status[6]))
302 318
303 319 # Force allows -X subrepo to skip the subrepo.
304 320 subs, commitsubs, newstate = subrepoutil.precommit(
305 321 repo.ui, wctx, tmpstatus, match, force=True)
306 322 for s in subs:
307 323 if s in commitsubs:
308 324 dirtyreason = wctx.sub(s).dirtyreason(True)
309 325 raise error.Abort(dirtyreason)
310 326
311 327 if not force:
312 328 repo.checkcommitpatterns(wctx, vdirs, match, status, fail)
313 329 diffopts = patch.difffeatureopts(ui, opts=opts, whitespace=True,
314 330 section='commands',
315 331 configprefix='commit.interactive.')
316 332 diffopts.nodates = True
317 333 diffopts.git = True
318 334 diffopts.showfunc = True
319 335 originaldiff = patch.diff(repo, changes=status, opts=diffopts)
320 336 originalchunks = patch.parsepatch(originaldiff)
321 337 match = scmutil.match(repo[None], pats)
322 338
323 339 # 1. filter patch, since we are intending to apply subset of it
324 340 try:
325 341 chunks, newopts = filterfn(ui, originalchunks, match)
326 342 except error.PatchError as err:
327 343 raise error.Abort(_('error parsing patch: %s') % err)
328 344 opts.update(newopts)
329 345
330 346 # We need to keep a backup of files that have been newly added and
331 347 # modified during the recording process because there is a previous
332 348 # version without the edit in the workdir. We also will need to restore
333 349 # files that were the sources of renames so that the patch application
334 350 # works.
335 351 newlyaddedandmodifiedfiles, alsorestore = newandmodified(chunks,
336 352 originalchunks)
337 353 contenders = set()
338 354 for h in chunks:
339 355 try:
340 356 contenders.update(set(h.files()))
341 357 except AttributeError:
342 358 pass
343 359
344 360 changed = status.modified + status.added + status.removed
345 361 newfiles = [f for f in changed if f in contenders]
346 362 if not newfiles:
347 363 ui.status(_('no changes to record\n'))
348 364 return 0
349 365
350 366 modified = set(status.modified)
351 367
352 368 # 2. backup changed files, so we can restore them in the end
353 369
354 370 if backupall:
355 371 tobackup = changed
356 372 else:
357 373 tobackup = [f for f in newfiles if f in modified or f in
358 374 newlyaddedandmodifiedfiles]
359 375 backups = {}
360 376 if tobackup:
361 377 backupdir = repo.vfs.join('record-backups')
362 378 try:
363 379 os.mkdir(backupdir)
364 380 except OSError as err:
365 381 if err.errno != errno.EEXIST:
366 382 raise
367 383 try:
368 384 # backup continues
369 385 for f in tobackup:
370 386 fd, tmpname = pycompat.mkstemp(prefix=f.replace('/', '_') + '.',
371 387 dir=backupdir)
372 388 os.close(fd)
373 389 ui.debug('backup %r as %r\n' % (f, tmpname))
374 390 util.copyfile(repo.wjoin(f), tmpname, copystat=True)
375 391 backups[f] = tmpname
376 392
377 393 fp = stringio()
378 394 for c in chunks:
379 395 fname = c.filename()
380 396 if fname in backups:
381 397 c.write(fp)
382 398 dopatch = fp.tell()
383 399 fp.seek(0)
384 400
385 401 # 2.5 optionally review / modify patch in text editor
386 402 if opts.get('review', False):
387 403 patchtext = (crecordmod.diffhelptext
388 404 + crecordmod.patchhelptext
389 405 + fp.read())
390 406 reviewedpatch = ui.edit(patchtext, "",
391 407 action="diff",
392 408 repopath=repo.path)
393 409 fp.truncate(0)
394 410 fp.write(reviewedpatch)
395 411 fp.seek(0)
396 412
397 413 [os.unlink(repo.wjoin(c)) for c in newlyaddedandmodifiedfiles]
398 414 # 3a. apply filtered patch to clean repo (clean)
399 415 if backups:
400 416 # Equivalent to hg.revert
401 417 m = scmutil.matchfiles(repo, set(backups.keys()) | alsorestore)
402 418 mergemod.update(repo, repo.dirstate.p1(), branchmerge=False,
403 419 force=True, matcher=m)
404 420
405 421 # 3b. (apply)
406 422 if dopatch:
407 423 try:
408 424 ui.debug('applying patch\n')
409 425 ui.debug(fp.getvalue())
410 426 patch.internalpatch(ui, repo, fp, 1, eolmode=None)
411 427 except error.PatchError as err:
412 428 raise error.Abort(pycompat.bytestr(err))
413 429 del fp
414 430
415 431 # 4. We prepared working directory according to filtered
416 432 # patch. Now is the time to delegate the job to
417 433 # commit/qrefresh or the like!
418 434
419 435 # Make all of the pathnames absolute.
420 436 newfiles = [repo.wjoin(nf) for nf in newfiles]
421 437 return commitfunc(ui, repo, *newfiles, **pycompat.strkwargs(opts))
422 438 finally:
423 439 # 5. finally restore backed-up files
424 440 try:
425 441 dirstate = repo.dirstate
426 442 for realname, tmpname in backups.iteritems():
427 443 ui.debug('restoring %r to %r\n' % (tmpname, realname))
428 444
429 445 if dirstate[realname] == 'n':
430 446 # without normallookup, restoring timestamp
431 447 # may cause partially committed files
432 448 # to be treated as unmodified
433 449 dirstate.normallookup(realname)
434 450
435 451 # copystat=True here and above are a hack to trick any
436 452 # editors that have f open that we haven't modified them.
437 453 #
438 454 # Also note that this racy as an editor could notice the
439 455 # file's mtime before we've finished writing it.
440 456 util.copyfile(tmpname, repo.wjoin(realname), copystat=True)
441 457 os.unlink(tmpname)
442 458 if tobackup:
443 459 os.rmdir(backupdir)
444 460 except OSError:
445 461 pass
446 462
447 463 def recordinwlock(ui, repo, message, match, opts):
448 464 with repo.wlock():
449 465 return recordfunc(ui, repo, message, match, opts)
450 466
451 467 return commit(ui, repo, recordinwlock, pats, opts)
452 468
453 469 class dirnode(object):
454 470 """
455 471 Represent a directory in user working copy with information required for
456 472 the purpose of tersing its status.
457 473
458 474 path is the path to the directory, without a trailing '/'
459 475
460 476 statuses is a set of statuses of all files in this directory (this includes
461 477 all the files in all the subdirectories too)
462 478
463 479 files is a list of files which are direct child of this directory
464 480
465 481 subdirs is a dictionary of sub-directory name as the key and it's own
466 482 dirnode object as the value
467 483 """
468 484
469 485 def __init__(self, dirpath):
470 486 self.path = dirpath
471 487 self.statuses = set()
472 488 self.files = []
473 489 self.subdirs = {}
474 490
475 491 def _addfileindir(self, filename, status):
476 492 """Add a file in this directory as a direct child."""
477 493 self.files.append((filename, status))
478 494
479 495 def addfile(self, filename, status):
480 496 """
481 497 Add a file to this directory or to its direct parent directory.
482 498
483 499 If the file is not direct child of this directory, we traverse to the
484 500 directory of which this file is a direct child of and add the file
485 501 there.
486 502 """
487 503
488 504 # the filename contains a path separator, it means it's not the direct
489 505 # child of this directory
490 506 if '/' in filename:
491 507 subdir, filep = filename.split('/', 1)
492 508
493 509 # does the dirnode object for subdir exists
494 510 if subdir not in self.subdirs:
495 511 subdirpath = pathutil.join(self.path, subdir)
496 512 self.subdirs[subdir] = dirnode(subdirpath)
497 513
498 514 # try adding the file in subdir
499 515 self.subdirs[subdir].addfile(filep, status)
500 516
501 517 else:
502 518 self._addfileindir(filename, status)
503 519
504 520 if status not in self.statuses:
505 521 self.statuses.add(status)
506 522
507 523 def iterfilepaths(self):
508 524 """Yield (status, path) for files directly under this directory."""
509 525 for f, st in self.files:
510 526 yield st, pathutil.join(self.path, f)
511 527
512 528 def tersewalk(self, terseargs):
513 529 """
514 530 Yield (status, path) obtained by processing the status of this
515 531 dirnode.
516 532
517 533 terseargs is the string of arguments passed by the user with `--terse`
518 534 flag.
519 535
520 536 Following are the cases which can happen:
521 537
522 538 1) All the files in the directory (including all the files in its
523 539 subdirectories) share the same status and the user has asked us to terse
524 540 that status. -> yield (status, dirpath). dirpath will end in '/'.
525 541
526 542 2) Otherwise, we do following:
527 543
528 544 a) Yield (status, filepath) for all the files which are in this
529 545 directory (only the ones in this directory, not the subdirs)
530 546
531 547 b) Recurse the function on all the subdirectories of this
532 548 directory
533 549 """
534 550
535 551 if len(self.statuses) == 1:
536 552 onlyst = self.statuses.pop()
537 553
538 554 # Making sure we terse only when the status abbreviation is
539 555 # passed as terse argument
540 556 if onlyst in terseargs:
541 557 yield onlyst, self.path + '/'
542 558 return
543 559
544 560 # add the files to status list
545 561 for st, fpath in self.iterfilepaths():
546 562 yield st, fpath
547 563
548 564 #recurse on the subdirs
549 565 for dirobj in self.subdirs.values():
550 566 for st, fpath in dirobj.tersewalk(terseargs):
551 567 yield st, fpath
552 568
553 569 def tersedir(statuslist, terseargs):
554 570 """
555 571 Terse the status if all the files in a directory shares the same status.
556 572
557 573 statuslist is scmutil.status() object which contains a list of files for
558 574 each status.
559 575 terseargs is string which is passed by the user as the argument to `--terse`
560 576 flag.
561 577
562 578 The function makes a tree of objects of dirnode class, and at each node it
563 579 stores the information required to know whether we can terse a certain
564 580 directory or not.
565 581 """
566 582 # the order matters here as that is used to produce final list
567 583 allst = ('m', 'a', 'r', 'd', 'u', 'i', 'c')
568 584
569 585 # checking the argument validity
570 586 for s in pycompat.bytestr(terseargs):
571 587 if s not in allst:
572 588 raise error.Abort(_("'%s' not recognized") % s)
573 589
574 590 # creating a dirnode object for the root of the repo
575 591 rootobj = dirnode('')
576 592 pstatus = ('modified', 'added', 'deleted', 'clean', 'unknown',
577 593 'ignored', 'removed')
578 594
579 595 tersedict = {}
580 596 for attrname in pstatus:
581 597 statuschar = attrname[0:1]
582 598 for f in getattr(statuslist, attrname):
583 599 rootobj.addfile(f, statuschar)
584 600 tersedict[statuschar] = []
585 601
586 602 # we won't be tersing the root dir, so add files in it
587 603 for st, fpath in rootobj.iterfilepaths():
588 604 tersedict[st].append(fpath)
589 605
590 606 # process each sub-directory and build tersedict
591 607 for subdir in rootobj.subdirs.values():
592 608 for st, f in subdir.tersewalk(terseargs):
593 609 tersedict[st].append(f)
594 610
595 611 tersedlist = []
596 612 for st in allst:
597 613 tersedict[st].sort()
598 614 tersedlist.append(tersedict[st])
599 615
600 616 return tersedlist
601 617
602 618 def _commentlines(raw):
603 619 '''Surround lineswith a comment char and a new line'''
604 620 lines = raw.splitlines()
605 621 commentedlines = ['# %s' % line for line in lines]
606 622 return '\n'.join(commentedlines) + '\n'
607 623
608 624 def _conflictsmsg(repo):
609 625 mergestate = mergemod.mergestate.read(repo)
610 626 if not mergestate.active():
611 627 return
612 628
613 629 m = scmutil.match(repo[None])
614 630 unresolvedlist = [f for f in mergestate.unresolved() if m(f)]
615 631 if unresolvedlist:
616 632 mergeliststr = '\n'.join(
617 633 [' %s' % util.pathto(repo.root, encoding.getcwd(), path)
618 634 for path in sorted(unresolvedlist)])
619 635 msg = _('''Unresolved merge conflicts:
620 636
621 637 %s
622 638
623 639 To mark files as resolved: hg resolve --mark FILE''') % mergeliststr
624 640 else:
625 641 msg = _('No unresolved merge conflicts.')
626 642
627 643 return _commentlines(msg)
628 644
629 645 def morestatus(repo, fm):
630 646 statetuple = statemod.getrepostate(repo)
631 647 label = 'status.morestatus'
632 648 if statetuple:
633 649 state, helpfulmsg = statetuple
634 650 statemsg = _('The repository is in an unfinished *%s* state.') % state
635 651 fm.plain('%s\n' % _commentlines(statemsg), label=label)
636 652 conmsg = _conflictsmsg(repo)
637 653 if conmsg:
638 654 fm.plain('%s\n' % conmsg, label=label)
639 655 if helpfulmsg:
640 656 fm.plain('%s\n' % _commentlines(helpfulmsg), label=label)
641 657
642 658 def findpossible(cmd, table, strict=False):
643 659 """
644 660 Return cmd -> (aliases, command table entry)
645 661 for each matching command.
646 662 Return debug commands (or their aliases) only if no normal command matches.
647 663 """
648 664 choice = {}
649 665 debugchoice = {}
650 666
651 667 if cmd in table:
652 668 # short-circuit exact matches, "log" alias beats "log|history"
653 669 keys = [cmd]
654 670 else:
655 671 keys = table.keys()
656 672
657 673 allcmds = []
658 674 for e in keys:
659 675 aliases = parsealiases(e)
660 676 allcmds.extend(aliases)
661 677 found = None
662 678 if cmd in aliases:
663 679 found = cmd
664 680 elif not strict:
665 681 for a in aliases:
666 682 if a.startswith(cmd):
667 683 found = a
668 684 break
669 685 if found is not None:
670 686 if aliases[0].startswith("debug") or found.startswith("debug"):
671 687 debugchoice[found] = (aliases, table[e])
672 688 else:
673 689 choice[found] = (aliases, table[e])
674 690
675 691 if not choice and debugchoice:
676 692 choice = debugchoice
677 693
678 694 return choice, allcmds
679 695
680 696 def findcmd(cmd, table, strict=True):
681 697 """Return (aliases, command table entry) for command string."""
682 698 choice, allcmds = findpossible(cmd, table, strict)
683 699
684 700 if cmd in choice:
685 701 return choice[cmd]
686 702
687 703 if len(choice) > 1:
688 704 clist = sorted(choice)
689 705 raise error.AmbiguousCommand(cmd, clist)
690 706
691 707 if choice:
692 708 return list(choice.values())[0]
693 709
694 710 raise error.UnknownCommand(cmd, allcmds)
695 711
696 712 def changebranch(ui, repo, revs, label):
697 713 """ Change the branch name of given revs to label """
698 714
699 715 with repo.wlock(), repo.lock(), repo.transaction('branches'):
700 716 # abort in case of uncommitted merge or dirty wdir
701 717 bailifchanged(repo)
702 718 revs = scmutil.revrange(repo, revs)
703 719 if not revs:
704 720 raise error.Abort("empty revision set")
705 721 roots = repo.revs('roots(%ld)', revs)
706 722 if len(roots) > 1:
707 723 raise error.Abort(_("cannot change branch of non-linear revisions"))
708 724 rewriteutil.precheck(repo, revs, 'change branch of')
709 725
710 726 root = repo[roots.first()]
711 727 rpb = {parent.branch() for parent in root.parents()}
712 728 if label not in rpb and label in repo.branchmap():
713 729 raise error.Abort(_("a branch of the same name already exists"))
714 730
715 731 if repo.revs('obsolete() and %ld', revs):
716 732 raise error.Abort(_("cannot change branch of a obsolete changeset"))
717 733
718 734 # make sure only topological heads
719 735 if repo.revs('heads(%ld) - head()', revs):
720 736 raise error.Abort(_("cannot change branch in middle of a stack"))
721 737
722 738 replacements = {}
723 739 # avoid import cycle mercurial.cmdutil -> mercurial.context ->
724 740 # mercurial.subrepo -> mercurial.cmdutil
725 741 from . import context
726 742 for rev in revs:
727 743 ctx = repo[rev]
728 744 oldbranch = ctx.branch()
729 745 # check if ctx has same branch
730 746 if oldbranch == label:
731 747 continue
732 748
733 749 def filectxfn(repo, newctx, path):
734 750 try:
735 751 return ctx[path]
736 752 except error.ManifestLookupError:
737 753 return None
738 754
739 755 ui.debug("changing branch of '%s' from '%s' to '%s'\n"
740 756 % (hex(ctx.node()), oldbranch, label))
741 757 extra = ctx.extra()
742 758 extra['branch_change'] = hex(ctx.node())
743 759 # While changing branch of set of linear commits, make sure that
744 760 # we base our commits on new parent rather than old parent which
745 761 # was obsoleted while changing the branch
746 762 p1 = ctx.p1().node()
747 763 p2 = ctx.p2().node()
748 764 if p1 in replacements:
749 765 p1 = replacements[p1][0]
750 766 if p2 in replacements:
751 767 p2 = replacements[p2][0]
752 768
753 769 mc = context.memctx(repo, (p1, p2),
754 770 ctx.description(),
755 771 ctx.files(),
756 772 filectxfn,
757 773 user=ctx.user(),
758 774 date=ctx.date(),
759 775 extra=extra,
760 776 branch=label)
761 777
762 778 newnode = repo.commitctx(mc)
763 779 replacements[ctx.node()] = (newnode,)
764 780 ui.debug('new node id is %s\n' % hex(newnode))
765 781
766 782 # create obsmarkers and move bookmarks
767 783 scmutil.cleanupnodes(repo, replacements, 'branch-change', fixphase=True)
768 784
769 785 # move the working copy too
770 786 wctx = repo[None]
771 787 # in-progress merge is a bit too complex for now.
772 788 if len(wctx.parents()) == 1:
773 789 newid = replacements.get(wctx.p1().node())
774 790 if newid is not None:
775 791 # avoid import cycle mercurial.cmdutil -> mercurial.hg ->
776 792 # mercurial.cmdutil
777 793 from . import hg
778 794 hg.update(repo, newid[0], quietempty=True)
779 795
780 796 ui.status(_("changed branch on %d changesets\n") % len(replacements))
781 797
782 798 def findrepo(p):
783 799 while not os.path.isdir(os.path.join(p, ".hg")):
784 800 oldp, p = p, os.path.dirname(p)
785 801 if p == oldp:
786 802 return None
787 803
788 804 return p
789 805
790 806 def bailifchanged(repo, merge=True, hint=None):
791 807 """ enforce the precondition that working directory must be clean.
792 808
793 809 'merge' can be set to false if a pending uncommitted merge should be
794 810 ignored (such as when 'update --check' runs).
795 811
796 812 'hint' is the usual hint given to Abort exception.
797 813 """
798 814
799 815 if merge and repo.dirstate.p2() != nullid:
800 816 raise error.Abort(_('outstanding uncommitted merge'), hint=hint)
801 817 modified, added, removed, deleted = repo.status()[:4]
802 818 if modified or added or removed or deleted:
803 819 raise error.Abort(_('uncommitted changes'), hint=hint)
804 820 ctx = repo[None]
805 821 for s in sorted(ctx.substate):
806 822 ctx.sub(s).bailifchanged(hint=hint)
807 823
808 824 def logmessage(ui, opts):
809 825 """ get the log message according to -m and -l option """
810 826 message = opts.get('message')
811 827 logfile = opts.get('logfile')
812 828
813 829 if message and logfile:
814 830 raise error.Abort(_('options --message and --logfile are mutually '
815 831 'exclusive'))
816 832 if not message and logfile:
817 833 try:
818 834 if isstdiofilename(logfile):
819 835 message = ui.fin.read()
820 836 else:
821 837 message = '\n'.join(util.readfile(logfile).splitlines())
822 838 except IOError as inst:
823 839 raise error.Abort(_("can't read commit message '%s': %s") %
824 840 (logfile, encoding.strtolocal(inst.strerror)))
825 841 return message
826 842
827 843 def mergeeditform(ctxorbool, baseformname):
828 844 """return appropriate editform name (referencing a committemplate)
829 845
830 846 'ctxorbool' is either a ctx to be committed, or a bool indicating whether
831 847 merging is committed.
832 848
833 849 This returns baseformname with '.merge' appended if it is a merge,
834 850 otherwise '.normal' is appended.
835 851 """
836 852 if isinstance(ctxorbool, bool):
837 853 if ctxorbool:
838 854 return baseformname + ".merge"
839 855 elif len(ctxorbool.parents()) > 1:
840 856 return baseformname + ".merge"
841 857
842 858 return baseformname + ".normal"
843 859
844 860 def getcommiteditor(edit=False, finishdesc=None, extramsg=None,
845 861 editform='', **opts):
846 862 """get appropriate commit message editor according to '--edit' option
847 863
848 864 'finishdesc' is a function to be called with edited commit message
849 865 (= 'description' of the new changeset) just after editing, but
850 866 before checking empty-ness. It should return actual text to be
851 867 stored into history. This allows to change description before
852 868 storing.
853 869
854 870 'extramsg' is a extra message to be shown in the editor instead of
855 871 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
856 872 is automatically added.
857 873
858 874 'editform' is a dot-separated list of names, to distinguish
859 875 the purpose of commit text editing.
860 876
861 877 'getcommiteditor' returns 'commitforceeditor' regardless of
862 878 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
863 879 they are specific for usage in MQ.
864 880 """
865 881 if edit or finishdesc or extramsg:
866 882 return lambda r, c, s: commitforceeditor(r, c, s,
867 883 finishdesc=finishdesc,
868 884 extramsg=extramsg,
869 885 editform=editform)
870 886 elif editform:
871 887 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
872 888 else:
873 889 return commiteditor
874 890
875 891 def _escapecommandtemplate(tmpl):
876 892 parts = []
877 893 for typ, start, end in templater.scantemplate(tmpl, raw=True):
878 894 if typ == b'string':
879 895 parts.append(stringutil.escapestr(tmpl[start:end]))
880 896 else:
881 897 parts.append(tmpl[start:end])
882 898 return b''.join(parts)
883 899
884 900 def rendercommandtemplate(ui, tmpl, props):
885 901 r"""Expand a literal template 'tmpl' in a way suitable for command line
886 902
887 903 '\' in outermost string is not taken as an escape character because it
888 904 is a directory separator on Windows.
889 905
890 906 >>> from . import ui as uimod
891 907 >>> ui = uimod.ui()
892 908 >>> rendercommandtemplate(ui, b'c:\\{path}', {b'path': b'foo'})
893 909 'c:\\foo'
894 910 >>> rendercommandtemplate(ui, b'{"c:\\{path}"}', {'path': b'foo'})
895 911 'c:{path}'
896 912 """
897 913 if not tmpl:
898 914 return tmpl
899 915 t = formatter.maketemplater(ui, _escapecommandtemplate(tmpl))
900 916 return t.renderdefault(props)
901 917
902 918 def rendertemplate(ctx, tmpl, props=None):
903 919 """Expand a literal template 'tmpl' byte-string against one changeset
904 920
905 921 Each props item must be a stringify-able value or a callable returning
906 922 such value, i.e. no bare list nor dict should be passed.
907 923 """
908 924 repo = ctx.repo()
909 925 tres = formatter.templateresources(repo.ui, repo)
910 926 t = formatter.maketemplater(repo.ui, tmpl, defaults=templatekw.keywords,
911 927 resources=tres)
912 928 mapping = {'ctx': ctx}
913 929 if props:
914 930 mapping.update(props)
915 931 return t.renderdefault(mapping)
916 932
917 933 def _buildfntemplate(pat, total=None, seqno=None, revwidth=None, pathname=None):
918 934 r"""Convert old-style filename format string to template string
919 935
920 936 >>> _buildfntemplate(b'foo-%b-%n.patch', seqno=0)
921 937 'foo-{reporoot|basename}-{seqno}.patch'
922 938 >>> _buildfntemplate(b'%R{tags % "{tag}"}%H')
923 939 '{rev}{tags % "{tag}"}{node}'
924 940
925 941 '\' in outermost strings has to be escaped because it is a directory
926 942 separator on Windows:
927 943
928 944 >>> _buildfntemplate(b'c:\\tmp\\%R\\%n.patch', seqno=0)
929 945 'c:\\\\tmp\\\\{rev}\\\\{seqno}.patch'
930 946 >>> _buildfntemplate(b'\\\\foo\\bar.patch')
931 947 '\\\\\\\\foo\\\\bar.patch'
932 948 >>> _buildfntemplate(b'\\{tags % "{tag}"}')
933 949 '\\\\{tags % "{tag}"}'
934 950
935 951 but inner strings follow the template rules (i.e. '\' is taken as an
936 952 escape character):
937 953
938 954 >>> _buildfntemplate(br'{"c:\tmp"}', seqno=0)
939 955 '{"c:\\tmp"}'
940 956 """
941 957 expander = {
942 958 b'H': b'{node}',
943 959 b'R': b'{rev}',
944 960 b'h': b'{node|short}',
945 961 b'm': br'{sub(r"[^\w]", "_", desc|firstline)}',
946 962 b'r': b'{if(revwidth, pad(rev, revwidth, "0", left=True), rev)}',
947 963 b'%': b'%',
948 964 b'b': b'{reporoot|basename}',
949 965 }
950 966 if total is not None:
951 967 expander[b'N'] = b'{total}'
952 968 if seqno is not None:
953 969 expander[b'n'] = b'{seqno}'
954 970 if total is not None and seqno is not None:
955 971 expander[b'n'] = b'{pad(seqno, total|stringify|count, "0", left=True)}'
956 972 if pathname is not None:
957 973 expander[b's'] = b'{pathname|basename}'
958 974 expander[b'd'] = b'{if(pathname|dirname, pathname|dirname, ".")}'
959 975 expander[b'p'] = b'{pathname}'
960 976
961 977 newname = []
962 978 for typ, start, end in templater.scantemplate(pat, raw=True):
963 979 if typ != b'string':
964 980 newname.append(pat[start:end])
965 981 continue
966 982 i = start
967 983 while i < end:
968 984 n = pat.find(b'%', i, end)
969 985 if n < 0:
970 986 newname.append(stringutil.escapestr(pat[i:end]))
971 987 break
972 988 newname.append(stringutil.escapestr(pat[i:n]))
973 989 if n + 2 > end:
974 990 raise error.Abort(_("incomplete format spec in output "
975 991 "filename"))
976 992 c = pat[n + 1:n + 2]
977 993 i = n + 2
978 994 try:
979 995 newname.append(expander[c])
980 996 except KeyError:
981 997 raise error.Abort(_("invalid format spec '%%%s' in output "
982 998 "filename") % c)
983 999 return ''.join(newname)
984 1000
985 1001 def makefilename(ctx, pat, **props):
986 1002 if not pat:
987 1003 return pat
988 1004 tmpl = _buildfntemplate(pat, **props)
989 1005 # BUG: alias expansion shouldn't be made against template fragments
990 1006 # rewritten from %-format strings, but we have no easy way to partially
991 1007 # disable the expansion.
992 1008 return rendertemplate(ctx, tmpl, pycompat.byteskwargs(props))
993 1009
994 1010 def isstdiofilename(pat):
995 1011 """True if the given pat looks like a filename denoting stdin/stdout"""
996 1012 return not pat or pat == '-'
997 1013
998 1014 class _unclosablefile(object):
999 1015 def __init__(self, fp):
1000 1016 self._fp = fp
1001 1017
1002 1018 def close(self):
1003 1019 pass
1004 1020
1005 1021 def __iter__(self):
1006 1022 return iter(self._fp)
1007 1023
1008 1024 def __getattr__(self, attr):
1009 1025 return getattr(self._fp, attr)
1010 1026
1011 1027 def __enter__(self):
1012 1028 return self
1013 1029
1014 1030 def __exit__(self, exc_type, exc_value, exc_tb):
1015 1031 pass
1016 1032
1017 1033 def makefileobj(ctx, pat, mode='wb', **props):
1018 1034 writable = mode not in ('r', 'rb')
1019 1035
1020 1036 if isstdiofilename(pat):
1021 1037 repo = ctx.repo()
1022 1038 if writable:
1023 1039 fp = repo.ui.fout
1024 1040 else:
1025 1041 fp = repo.ui.fin
1026 1042 return _unclosablefile(fp)
1027 1043 fn = makefilename(ctx, pat, **props)
1028 1044 return open(fn, mode)
1029 1045
1030 1046 def openstorage(repo, cmd, file_, opts, returnrevlog=False):
1031 1047 """opens the changelog, manifest, a filelog or a given revlog"""
1032 1048 cl = opts['changelog']
1033 1049 mf = opts['manifest']
1034 1050 dir = opts['dir']
1035 1051 msg = None
1036 1052 if cl and mf:
1037 1053 msg = _('cannot specify --changelog and --manifest at the same time')
1038 1054 elif cl and dir:
1039 1055 msg = _('cannot specify --changelog and --dir at the same time')
1040 1056 elif cl or mf or dir:
1041 1057 if file_:
1042 1058 msg = _('cannot specify filename with --changelog or --manifest')
1043 1059 elif not repo:
1044 1060 msg = _('cannot specify --changelog or --manifest or --dir '
1045 1061 'without a repository')
1046 1062 if msg:
1047 1063 raise error.Abort(msg)
1048 1064
1049 1065 r = None
1050 1066 if repo:
1051 1067 if cl:
1052 1068 r = repo.unfiltered().changelog
1053 1069 elif dir:
1054 1070 if 'treemanifest' not in repo.requirements:
1055 1071 raise error.Abort(_("--dir can only be used on repos with "
1056 1072 "treemanifest enabled"))
1057 1073 if not dir.endswith('/'):
1058 1074 dir = dir + '/'
1059 1075 dirlog = repo.manifestlog.getstorage(dir)
1060 1076 if len(dirlog):
1061 1077 r = dirlog
1062 1078 elif mf:
1063 1079 r = repo.manifestlog.getstorage(b'')
1064 1080 elif file_:
1065 1081 filelog = repo.file(file_)
1066 1082 if len(filelog):
1067 1083 r = filelog
1068 1084
1069 1085 # Not all storage may be revlogs. If requested, try to return an actual
1070 1086 # revlog instance.
1071 1087 if returnrevlog:
1072 1088 if isinstance(r, revlog.revlog):
1073 1089 pass
1074 1090 elif util.safehasattr(r, '_revlog'):
1075 1091 r = r._revlog
1076 1092 elif r is not None:
1077 1093 raise error.Abort(_('%r does not appear to be a revlog') % r)
1078 1094
1079 1095 if not r:
1080 1096 if not returnrevlog:
1081 1097 raise error.Abort(_('cannot give path to non-revlog'))
1082 1098
1083 1099 if not file_:
1084 1100 raise error.CommandError(cmd, _('invalid arguments'))
1085 1101 if not os.path.isfile(file_):
1086 1102 raise error.Abort(_("revlog '%s' not found") % file_)
1087 1103 r = revlog.revlog(vfsmod.vfs(encoding.getcwd(), audit=False),
1088 1104 file_[:-2] + ".i")
1089 1105 return r
1090 1106
1091 1107 def openrevlog(repo, cmd, file_, opts):
1092 1108 """Obtain a revlog backing storage of an item.
1093 1109
1094 1110 This is similar to ``openstorage()`` except it always returns a revlog.
1095 1111
1096 1112 In most cases, a caller cares about the main storage object - not the
1097 1113 revlog backing it. Therefore, this function should only be used by code
1098 1114 that needs to examine low-level revlog implementation details. e.g. debug
1099 1115 commands.
1100 1116 """
1101 1117 return openstorage(repo, cmd, file_, opts, returnrevlog=True)
1102 1118
1103 1119 def copy(ui, repo, pats, opts, rename=False):
1104 1120 # called with the repo lock held
1105 1121 #
1106 1122 # hgsep => pathname that uses "/" to separate directories
1107 1123 # ossep => pathname that uses os.sep to separate directories
1108 1124 cwd = repo.getcwd()
1109 1125 targets = {}
1110 1126 after = opts.get("after")
1111 1127 dryrun = opts.get("dry_run")
1112 1128 wctx = repo[None]
1113 1129
1114 1130 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
1115 1131 def walkpat(pat):
1116 1132 srcs = []
1117 1133 if after:
1118 1134 badstates = '?'
1119 1135 else:
1120 1136 badstates = '?r'
1121 1137 m = scmutil.match(wctx, [pat], opts, globbed=True)
1122 1138 for abs in wctx.walk(m):
1123 1139 state = repo.dirstate[abs]
1124 1140 rel = uipathfn(abs)
1125 1141 exact = m.exact(abs)
1126 1142 if state in badstates:
1127 1143 if exact and state == '?':
1128 1144 ui.warn(_('%s: not copying - file is not managed\n') % rel)
1129 1145 if exact and state == 'r':
1130 1146 ui.warn(_('%s: not copying - file has been marked for'
1131 1147 ' remove\n') % rel)
1132 1148 continue
1133 1149 # abs: hgsep
1134 1150 # rel: ossep
1135 1151 srcs.append((abs, rel, exact))
1136 1152 return srcs
1137 1153
1138 1154 # abssrc: hgsep
1139 1155 # relsrc: ossep
1140 1156 # otarget: ossep
1141 1157 def copyfile(abssrc, relsrc, otarget, exact):
1142 1158 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
1143 1159 if '/' in abstarget:
1144 1160 # We cannot normalize abstarget itself, this would prevent
1145 1161 # case only renames, like a => A.
1146 1162 abspath, absname = abstarget.rsplit('/', 1)
1147 1163 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
1148 1164 reltarget = repo.pathto(abstarget, cwd)
1149 1165 target = repo.wjoin(abstarget)
1150 1166 src = repo.wjoin(abssrc)
1151 1167 state = repo.dirstate[abstarget]
1152 1168
1153 1169 scmutil.checkportable(ui, abstarget)
1154 1170
1155 1171 # check for collisions
1156 1172 prevsrc = targets.get(abstarget)
1157 1173 if prevsrc is not None:
1158 1174 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
1159 1175 (reltarget, repo.pathto(abssrc, cwd),
1160 1176 repo.pathto(prevsrc, cwd)))
1161 1177 return True # report a failure
1162 1178
1163 1179 # check for overwrites
1164 1180 exists = os.path.lexists(target)
1165 1181 samefile = False
1166 1182 if exists and abssrc != abstarget:
1167 1183 if (repo.dirstate.normalize(abssrc) ==
1168 1184 repo.dirstate.normalize(abstarget)):
1169 1185 if not rename:
1170 1186 ui.warn(_("%s: can't copy - same file\n") % reltarget)
1171 1187 return True # report a failure
1172 1188 exists = False
1173 1189 samefile = True
1174 1190
1175 1191 if not after and exists or after and state in 'mn':
1176 1192 if not opts['force']:
1177 1193 if state in 'mn':
1178 1194 msg = _('%s: not overwriting - file already committed\n')
1179 1195 if after:
1180 1196 flags = '--after --force'
1181 1197 else:
1182 1198 flags = '--force'
1183 1199 if rename:
1184 1200 hint = _("('hg rename %s' to replace the file by "
1185 1201 'recording a rename)\n') % flags
1186 1202 else:
1187 1203 hint = _("('hg copy %s' to replace the file by "
1188 1204 'recording a copy)\n') % flags
1189 1205 else:
1190 1206 msg = _('%s: not overwriting - file exists\n')
1191 1207 if rename:
1192 1208 hint = _("('hg rename --after' to record the rename)\n")
1193 1209 else:
1194 1210 hint = _("('hg copy --after' to record the copy)\n")
1195 1211 ui.warn(msg % reltarget)
1196 1212 ui.warn(hint)
1197 1213 return True # report a failure
1198 1214
1199 1215 if after:
1200 1216 if not exists:
1201 1217 if rename:
1202 1218 ui.warn(_('%s: not recording move - %s does not exist\n') %
1203 1219 (relsrc, reltarget))
1204 1220 else:
1205 1221 ui.warn(_('%s: not recording copy - %s does not exist\n') %
1206 1222 (relsrc, reltarget))
1207 1223 return True # report a failure
1208 1224 elif not dryrun:
1209 1225 try:
1210 1226 if exists:
1211 1227 os.unlink(target)
1212 1228 targetdir = os.path.dirname(target) or '.'
1213 1229 if not os.path.isdir(targetdir):
1214 1230 os.makedirs(targetdir)
1215 1231 if samefile:
1216 1232 tmp = target + "~hgrename"
1217 1233 os.rename(src, tmp)
1218 1234 os.rename(tmp, target)
1219 1235 else:
1220 1236 # Preserve stat info on renames, not on copies; this matches
1221 1237 # Linux CLI behavior.
1222 1238 util.copyfile(src, target, copystat=rename)
1223 1239 srcexists = True
1224 1240 except IOError as inst:
1225 1241 if inst.errno == errno.ENOENT:
1226 1242 ui.warn(_('%s: deleted in working directory\n') % relsrc)
1227 1243 srcexists = False
1228 1244 else:
1229 1245 ui.warn(_('%s: cannot copy - %s\n') %
1230 1246 (relsrc, encoding.strtolocal(inst.strerror)))
1231 1247 return True # report a failure
1232 1248
1233 1249 if ui.verbose or not exact:
1234 1250 if rename:
1235 1251 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
1236 1252 else:
1237 1253 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
1238 1254
1239 1255 targets[abstarget] = abssrc
1240 1256
1241 1257 # fix up dirstate
1242 1258 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
1243 1259 dryrun=dryrun, cwd=cwd)
1244 1260 if rename and not dryrun:
1245 1261 if not after and srcexists and not samefile:
1246 1262 rmdir = repo.ui.configbool('experimental', 'removeemptydirs')
1247 1263 repo.wvfs.unlinkpath(abssrc, rmdir=rmdir)
1248 1264 wctx.forget([abssrc])
1249 1265
1250 1266 # pat: ossep
1251 1267 # dest ossep
1252 1268 # srcs: list of (hgsep, hgsep, ossep, bool)
1253 1269 # return: function that takes hgsep and returns ossep
1254 1270 def targetpathfn(pat, dest, srcs):
1255 1271 if os.path.isdir(pat):
1256 1272 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1257 1273 abspfx = util.localpath(abspfx)
1258 1274 if destdirexists:
1259 1275 striplen = len(os.path.split(abspfx)[0])
1260 1276 else:
1261 1277 striplen = len(abspfx)
1262 1278 if striplen:
1263 1279 striplen += len(pycompat.ossep)
1264 1280 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
1265 1281 elif destdirexists:
1266 1282 res = lambda p: os.path.join(dest,
1267 1283 os.path.basename(util.localpath(p)))
1268 1284 else:
1269 1285 res = lambda p: dest
1270 1286 return res
1271 1287
1272 1288 # pat: ossep
1273 1289 # dest ossep
1274 1290 # srcs: list of (hgsep, hgsep, ossep, bool)
1275 1291 # return: function that takes hgsep and returns ossep
1276 1292 def targetpathafterfn(pat, dest, srcs):
1277 1293 if matchmod.patkind(pat):
1278 1294 # a mercurial pattern
1279 1295 res = lambda p: os.path.join(dest,
1280 1296 os.path.basename(util.localpath(p)))
1281 1297 else:
1282 1298 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1283 1299 if len(abspfx) < len(srcs[0][0]):
1284 1300 # A directory. Either the target path contains the last
1285 1301 # component of the source path or it does not.
1286 1302 def evalpath(striplen):
1287 1303 score = 0
1288 1304 for s in srcs:
1289 1305 t = os.path.join(dest, util.localpath(s[0])[striplen:])
1290 1306 if os.path.lexists(t):
1291 1307 score += 1
1292 1308 return score
1293 1309
1294 1310 abspfx = util.localpath(abspfx)
1295 1311 striplen = len(abspfx)
1296 1312 if striplen:
1297 1313 striplen += len(pycompat.ossep)
1298 1314 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
1299 1315 score = evalpath(striplen)
1300 1316 striplen1 = len(os.path.split(abspfx)[0])
1301 1317 if striplen1:
1302 1318 striplen1 += len(pycompat.ossep)
1303 1319 if evalpath(striplen1) > score:
1304 1320 striplen = striplen1
1305 1321 res = lambda p: os.path.join(dest,
1306 1322 util.localpath(p)[striplen:])
1307 1323 else:
1308 1324 # a file
1309 1325 if destdirexists:
1310 1326 res = lambda p: os.path.join(dest,
1311 1327 os.path.basename(util.localpath(p)))
1312 1328 else:
1313 1329 res = lambda p: dest
1314 1330 return res
1315 1331
1316 1332 pats = scmutil.expandpats(pats)
1317 1333 if not pats:
1318 1334 raise error.Abort(_('no source or destination specified'))
1319 1335 if len(pats) == 1:
1320 1336 raise error.Abort(_('no destination specified'))
1321 1337 dest = pats.pop()
1322 1338 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
1323 1339 if not destdirexists:
1324 1340 if len(pats) > 1 or matchmod.patkind(pats[0]):
1325 1341 raise error.Abort(_('with multiple sources, destination must be an '
1326 1342 'existing directory'))
1327 1343 if util.endswithsep(dest):
1328 1344 raise error.Abort(_('destination %s is not a directory') % dest)
1329 1345
1330 1346 tfn = targetpathfn
1331 1347 if after:
1332 1348 tfn = targetpathafterfn
1333 1349 copylist = []
1334 1350 for pat in pats:
1335 1351 srcs = walkpat(pat)
1336 1352 if not srcs:
1337 1353 continue
1338 1354 copylist.append((tfn(pat, dest, srcs), srcs))
1339 1355 if not copylist:
1340 1356 raise error.Abort(_('no files to copy'))
1341 1357
1342 1358 errors = 0
1343 1359 for targetpath, srcs in copylist:
1344 1360 for abssrc, relsrc, exact in srcs:
1345 1361 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
1346 1362 errors += 1
1347 1363
1348 1364 return errors != 0
1349 1365
1350 1366 ## facility to let extension process additional data into an import patch
1351 1367 # list of identifier to be executed in order
1352 1368 extrapreimport = [] # run before commit
1353 1369 extrapostimport = [] # run after commit
1354 1370 # mapping from identifier to actual import function
1355 1371 #
1356 1372 # 'preimport' are run before the commit is made and are provided the following
1357 1373 # arguments:
1358 1374 # - repo: the localrepository instance,
1359 1375 # - patchdata: data extracted from patch header (cf m.patch.patchheadermap),
1360 1376 # - extra: the future extra dictionary of the changeset, please mutate it,
1361 1377 # - opts: the import options.
1362 1378 # XXX ideally, we would just pass an ctx ready to be computed, that would allow
1363 1379 # mutation of in memory commit and more. Feel free to rework the code to get
1364 1380 # there.
1365 1381 extrapreimportmap = {}
1366 1382 # 'postimport' are run after the commit is made and are provided the following
1367 1383 # argument:
1368 1384 # - ctx: the changectx created by import.
1369 1385 extrapostimportmap = {}
1370 1386
1371 1387 def tryimportone(ui, repo, patchdata, parents, opts, msgs, updatefunc):
1372 1388 """Utility function used by commands.import to import a single patch
1373 1389
1374 1390 This function is explicitly defined here to help the evolve extension to
1375 1391 wrap this part of the import logic.
1376 1392
1377 1393 The API is currently a bit ugly because it a simple code translation from
1378 1394 the import command. Feel free to make it better.
1379 1395
1380 1396 :patchdata: a dictionary containing parsed patch data (such as from
1381 1397 ``patch.extract()``)
1382 1398 :parents: nodes that will be parent of the created commit
1383 1399 :opts: the full dict of option passed to the import command
1384 1400 :msgs: list to save commit message to.
1385 1401 (used in case we need to save it when failing)
1386 1402 :updatefunc: a function that update a repo to a given node
1387 1403 updatefunc(<repo>, <node>)
1388 1404 """
1389 1405 # avoid cycle context -> subrepo -> cmdutil
1390 1406 from . import context
1391 1407
1392 1408 tmpname = patchdata.get('filename')
1393 1409 message = patchdata.get('message')
1394 1410 user = opts.get('user') or patchdata.get('user')
1395 1411 date = opts.get('date') or patchdata.get('date')
1396 1412 branch = patchdata.get('branch')
1397 1413 nodeid = patchdata.get('nodeid')
1398 1414 p1 = patchdata.get('p1')
1399 1415 p2 = patchdata.get('p2')
1400 1416
1401 1417 nocommit = opts.get('no_commit')
1402 1418 importbranch = opts.get('import_branch')
1403 1419 update = not opts.get('bypass')
1404 1420 strip = opts["strip"]
1405 1421 prefix = opts["prefix"]
1406 1422 sim = float(opts.get('similarity') or 0)
1407 1423
1408 1424 if not tmpname:
1409 1425 return None, None, False
1410 1426
1411 1427 rejects = False
1412 1428
1413 1429 cmdline_message = logmessage(ui, opts)
1414 1430 if cmdline_message:
1415 1431 # pickup the cmdline msg
1416 1432 message = cmdline_message
1417 1433 elif message:
1418 1434 # pickup the patch msg
1419 1435 message = message.strip()
1420 1436 else:
1421 1437 # launch the editor
1422 1438 message = None
1423 1439 ui.debug('message:\n%s\n' % (message or ''))
1424 1440
1425 1441 if len(parents) == 1:
1426 1442 parents.append(repo[nullid])
1427 1443 if opts.get('exact'):
1428 1444 if not nodeid or not p1:
1429 1445 raise error.Abort(_('not a Mercurial patch'))
1430 1446 p1 = repo[p1]
1431 1447 p2 = repo[p2 or nullid]
1432 1448 elif p2:
1433 1449 try:
1434 1450 p1 = repo[p1]
1435 1451 p2 = repo[p2]
1436 1452 # Without any options, consider p2 only if the
1437 1453 # patch is being applied on top of the recorded
1438 1454 # first parent.
1439 1455 if p1 != parents[0]:
1440 1456 p1 = parents[0]
1441 1457 p2 = repo[nullid]
1442 1458 except error.RepoError:
1443 1459 p1, p2 = parents
1444 1460 if p2.node() == nullid:
1445 1461 ui.warn(_("warning: import the patch as a normal revision\n"
1446 1462 "(use --exact to import the patch as a merge)\n"))
1447 1463 else:
1448 1464 p1, p2 = parents
1449 1465
1450 1466 n = None
1451 1467 if update:
1452 1468 if p1 != parents[0]:
1453 1469 updatefunc(repo, p1.node())
1454 1470 if p2 != parents[1]:
1455 1471 repo.setparents(p1.node(), p2.node())
1456 1472
1457 1473 if opts.get('exact') or importbranch:
1458 1474 repo.dirstate.setbranch(branch or 'default')
1459 1475
1460 1476 partial = opts.get('partial', False)
1461 1477 files = set()
1462 1478 try:
1463 1479 patch.patch(ui, repo, tmpname, strip=strip, prefix=prefix,
1464 1480 files=files, eolmode=None, similarity=sim / 100.0)
1465 1481 except error.PatchError as e:
1466 1482 if not partial:
1467 1483 raise error.Abort(pycompat.bytestr(e))
1468 1484 if partial:
1469 1485 rejects = True
1470 1486
1471 1487 files = list(files)
1472 1488 if nocommit:
1473 1489 if message:
1474 1490 msgs.append(message)
1475 1491 else:
1476 1492 if opts.get('exact') or p2:
1477 1493 # If you got here, you either use --force and know what
1478 1494 # you are doing or used --exact or a merge patch while
1479 1495 # being updated to its first parent.
1480 1496 m = None
1481 1497 else:
1482 1498 m = scmutil.matchfiles(repo, files or [])
1483 1499 editform = mergeeditform(repo[None], 'import.normal')
1484 1500 if opts.get('exact'):
1485 1501 editor = None
1486 1502 else:
1487 1503 editor = getcommiteditor(editform=editform,
1488 1504 **pycompat.strkwargs(opts))
1489 1505 extra = {}
1490 1506 for idfunc in extrapreimport:
1491 1507 extrapreimportmap[idfunc](repo, patchdata, extra, opts)
1492 1508 overrides = {}
1493 1509 if partial:
1494 1510 overrides[('ui', 'allowemptycommit')] = True
1495 1511 with repo.ui.configoverride(overrides, 'import'):
1496 1512 n = repo.commit(message, user,
1497 1513 date, match=m,
1498 1514 editor=editor, extra=extra)
1499 1515 for idfunc in extrapostimport:
1500 1516 extrapostimportmap[idfunc](repo[n])
1501 1517 else:
1502 1518 if opts.get('exact') or importbranch:
1503 1519 branch = branch or 'default'
1504 1520 else:
1505 1521 branch = p1.branch()
1506 1522 store = patch.filestore()
1507 1523 try:
1508 1524 files = set()
1509 1525 try:
1510 1526 patch.patchrepo(ui, repo, p1, store, tmpname, strip, prefix,
1511 1527 files, eolmode=None)
1512 1528 except error.PatchError as e:
1513 1529 raise error.Abort(stringutil.forcebytestr(e))
1514 1530 if opts.get('exact'):
1515 1531 editor = None
1516 1532 else:
1517 1533 editor = getcommiteditor(editform='import.bypass')
1518 1534 memctx = context.memctx(repo, (p1.node(), p2.node()),
1519 1535 message,
1520 1536 files=files,
1521 1537 filectxfn=store,
1522 1538 user=user,
1523 1539 date=date,
1524 1540 branch=branch,
1525 1541 editor=editor)
1526 1542 n = memctx.commit()
1527 1543 finally:
1528 1544 store.close()
1529 1545 if opts.get('exact') and nocommit:
1530 1546 # --exact with --no-commit is still useful in that it does merge
1531 1547 # and branch bits
1532 1548 ui.warn(_("warning: can't check exact import with --no-commit\n"))
1533 1549 elif opts.get('exact') and (not n or hex(n) != nodeid):
1534 1550 raise error.Abort(_('patch is damaged or loses information'))
1535 1551 msg = _('applied to working directory')
1536 1552 if n:
1537 1553 # i18n: refers to a short changeset id
1538 1554 msg = _('created %s') % short(n)
1539 1555 return msg, n, rejects
1540 1556
1541 1557 # facility to let extensions include additional data in an exported patch
1542 1558 # list of identifiers to be executed in order
1543 1559 extraexport = []
1544 1560 # mapping from identifier to actual export function
1545 1561 # function as to return a string to be added to the header or None
1546 1562 # it is given two arguments (sequencenumber, changectx)
1547 1563 extraexportmap = {}
1548 1564
1549 1565 def _exportsingle(repo, ctx, fm, match, switch_parent, seqno, diffopts):
1550 1566 node = scmutil.binnode(ctx)
1551 1567 parents = [p.node() for p in ctx.parents() if p]
1552 1568 branch = ctx.branch()
1553 1569 if switch_parent:
1554 1570 parents.reverse()
1555 1571
1556 1572 if parents:
1557 1573 prev = parents[0]
1558 1574 else:
1559 1575 prev = nullid
1560 1576
1561 1577 fm.context(ctx=ctx)
1562 1578 fm.plain('# HG changeset patch\n')
1563 1579 fm.write('user', '# User %s\n', ctx.user())
1564 1580 fm.plain('# Date %d %d\n' % ctx.date())
1565 1581 fm.write('date', '# %s\n', fm.formatdate(ctx.date()))
1566 1582 fm.condwrite(branch and branch != 'default',
1567 1583 'branch', '# Branch %s\n', branch)
1568 1584 fm.write('node', '# Node ID %s\n', hex(node))
1569 1585 fm.plain('# Parent %s\n' % hex(prev))
1570 1586 if len(parents) > 1:
1571 1587 fm.plain('# Parent %s\n' % hex(parents[1]))
1572 1588 fm.data(parents=fm.formatlist(pycompat.maplist(hex, parents), name='node'))
1573 1589
1574 1590 # TODO: redesign extraexportmap function to support formatter
1575 1591 for headerid in extraexport:
1576 1592 header = extraexportmap[headerid](seqno, ctx)
1577 1593 if header is not None:
1578 1594 fm.plain('# %s\n' % header)
1579 1595
1580 1596 fm.write('desc', '%s\n', ctx.description().rstrip())
1581 1597 fm.plain('\n')
1582 1598
1583 1599 if fm.isplain():
1584 1600 chunkiter = patch.diffui(repo, prev, node, match, opts=diffopts)
1585 1601 for chunk, label in chunkiter:
1586 1602 fm.plain(chunk, label=label)
1587 1603 else:
1588 1604 chunkiter = patch.diff(repo, prev, node, match, opts=diffopts)
1589 1605 # TODO: make it structured?
1590 1606 fm.data(diff=b''.join(chunkiter))
1591 1607
1592 1608 def _exportfile(repo, revs, fm, dest, switch_parent, diffopts, match):
1593 1609 """Export changesets to stdout or a single file"""
1594 1610 for seqno, rev in enumerate(revs, 1):
1595 1611 ctx = repo[rev]
1596 1612 if not dest.startswith('<'):
1597 1613 repo.ui.note("%s\n" % dest)
1598 1614 fm.startitem()
1599 1615 _exportsingle(repo, ctx, fm, match, switch_parent, seqno, diffopts)
1600 1616
1601 1617 def _exportfntemplate(repo, revs, basefm, fntemplate, switch_parent, diffopts,
1602 1618 match):
1603 1619 """Export changesets to possibly multiple files"""
1604 1620 total = len(revs)
1605 1621 revwidth = max(len(str(rev)) for rev in revs)
1606 1622 filemap = util.sortdict() # filename: [(seqno, rev), ...]
1607 1623
1608 1624 for seqno, rev in enumerate(revs, 1):
1609 1625 ctx = repo[rev]
1610 1626 dest = makefilename(ctx, fntemplate,
1611 1627 total=total, seqno=seqno, revwidth=revwidth)
1612 1628 filemap.setdefault(dest, []).append((seqno, rev))
1613 1629
1614 1630 for dest in filemap:
1615 1631 with formatter.maybereopen(basefm, dest) as fm:
1616 1632 repo.ui.note("%s\n" % dest)
1617 1633 for seqno, rev in filemap[dest]:
1618 1634 fm.startitem()
1619 1635 ctx = repo[rev]
1620 1636 _exportsingle(repo, ctx, fm, match, switch_parent, seqno,
1621 1637 diffopts)
1622 1638
1623 1639 def _prefetchchangedfiles(repo, revs, match):
1624 1640 allfiles = set()
1625 1641 for rev in revs:
1626 1642 for file in repo[rev].files():
1627 1643 if not match or match(file):
1628 1644 allfiles.add(file)
1629 1645 scmutil.prefetchfiles(repo, revs, scmutil.matchfiles(repo, allfiles))
1630 1646
1631 1647 def export(repo, revs, basefm, fntemplate='hg-%h.patch', switch_parent=False,
1632 1648 opts=None, match=None):
1633 1649 '''export changesets as hg patches
1634 1650
1635 1651 Args:
1636 1652 repo: The repository from which we're exporting revisions.
1637 1653 revs: A list of revisions to export as revision numbers.
1638 1654 basefm: A formatter to which patches should be written.
1639 1655 fntemplate: An optional string to use for generating patch file names.
1640 1656 switch_parent: If True, show diffs against second parent when not nullid.
1641 1657 Default is false, which always shows diff against p1.
1642 1658 opts: diff options to use for generating the patch.
1643 1659 match: If specified, only export changes to files matching this matcher.
1644 1660
1645 1661 Returns:
1646 1662 Nothing.
1647 1663
1648 1664 Side Effect:
1649 1665 "HG Changeset Patch" data is emitted to one of the following
1650 1666 destinations:
1651 1667 fntemplate specified: Each rev is written to a unique file named using
1652 1668 the given template.
1653 1669 Otherwise: All revs will be written to basefm.
1654 1670 '''
1655 1671 _prefetchchangedfiles(repo, revs, match)
1656 1672
1657 1673 if not fntemplate:
1658 1674 _exportfile(repo, revs, basefm, '<unnamed>', switch_parent, opts, match)
1659 1675 else:
1660 1676 _exportfntemplate(repo, revs, basefm, fntemplate, switch_parent, opts,
1661 1677 match)
1662 1678
1663 1679 def exportfile(repo, revs, fp, switch_parent=False, opts=None, match=None):
1664 1680 """Export changesets to the given file stream"""
1665 1681 _prefetchchangedfiles(repo, revs, match)
1666 1682
1667 1683 dest = getattr(fp, 'name', '<unnamed>')
1668 1684 with formatter.formatter(repo.ui, fp, 'export', {}) as fm:
1669 1685 _exportfile(repo, revs, fm, dest, switch_parent, opts, match)
1670 1686
1671 1687 def showmarker(fm, marker, index=None):
1672 1688 """utility function to display obsolescence marker in a readable way
1673 1689
1674 1690 To be used by debug function."""
1675 1691 if index is not None:
1676 1692 fm.write('index', '%i ', index)
1677 1693 fm.write('prednode', '%s ', hex(marker.prednode()))
1678 1694 succs = marker.succnodes()
1679 1695 fm.condwrite(succs, 'succnodes', '%s ',
1680 1696 fm.formatlist(map(hex, succs), name='node'))
1681 1697 fm.write('flag', '%X ', marker.flags())
1682 1698 parents = marker.parentnodes()
1683 1699 if parents is not None:
1684 1700 fm.write('parentnodes', '{%s} ',
1685 1701 fm.formatlist(map(hex, parents), name='node', sep=', '))
1686 1702 fm.write('date', '(%s) ', fm.formatdate(marker.date()))
1687 1703 meta = marker.metadata().copy()
1688 1704 meta.pop('date', None)
1689 1705 smeta = pycompat.rapply(pycompat.maybebytestr, meta)
1690 1706 fm.write('metadata', '{%s}', fm.formatdict(smeta, fmt='%r: %r', sep=', '))
1691 1707 fm.plain('\n')
1692 1708
1693 1709 def finddate(ui, repo, date):
1694 1710 """Find the tipmost changeset that matches the given date spec"""
1695 1711
1696 1712 df = dateutil.matchdate(date)
1697 1713 m = scmutil.matchall(repo)
1698 1714 results = {}
1699 1715
1700 1716 def prep(ctx, fns):
1701 1717 d = ctx.date()
1702 1718 if df(d[0]):
1703 1719 results[ctx.rev()] = d
1704 1720
1705 1721 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1706 1722 rev = ctx.rev()
1707 1723 if rev in results:
1708 1724 ui.status(_("found revision %s from %s\n") %
1709 1725 (rev, dateutil.datestr(results[rev])))
1710 1726 return '%d' % rev
1711 1727
1712 1728 raise error.Abort(_("revision matching date not found"))
1713 1729
1714 1730 def increasingwindows(windowsize=8, sizelimit=512):
1715 1731 while True:
1716 1732 yield windowsize
1717 1733 if windowsize < sizelimit:
1718 1734 windowsize *= 2
1719 1735
1720 1736 def _walkrevs(repo, opts):
1721 1737 # Default --rev value depends on --follow but --follow behavior
1722 1738 # depends on revisions resolved from --rev...
1723 1739 follow = opts.get('follow') or opts.get('follow_first')
1724 1740 if opts.get('rev'):
1725 1741 revs = scmutil.revrange(repo, opts['rev'])
1726 1742 elif follow and repo.dirstate.p1() == nullid:
1727 1743 revs = smartset.baseset()
1728 1744 elif follow:
1729 1745 revs = repo.revs('reverse(:.)')
1730 1746 else:
1731 1747 revs = smartset.spanset(repo)
1732 1748 revs.reverse()
1733 1749 return revs
1734 1750
1735 1751 class FileWalkError(Exception):
1736 1752 pass
1737 1753
1738 1754 def walkfilerevs(repo, match, follow, revs, fncache):
1739 1755 '''Walks the file history for the matched files.
1740 1756
1741 1757 Returns the changeset revs that are involved in the file history.
1742 1758
1743 1759 Throws FileWalkError if the file history can't be walked using
1744 1760 filelogs alone.
1745 1761 '''
1746 1762 wanted = set()
1747 1763 copies = []
1748 1764 minrev, maxrev = min(revs), max(revs)
1749 1765 def filerevs(filelog, last):
1750 1766 """
1751 1767 Only files, no patterns. Check the history of each file.
1752 1768
1753 1769 Examines filelog entries within minrev, maxrev linkrev range
1754 1770 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1755 1771 tuples in backwards order
1756 1772 """
1757 1773 cl_count = len(repo)
1758 1774 revs = []
1759 1775 for j in pycompat.xrange(0, last + 1):
1760 1776 linkrev = filelog.linkrev(j)
1761 1777 if linkrev < minrev:
1762 1778 continue
1763 1779 # only yield rev for which we have the changelog, it can
1764 1780 # happen while doing "hg log" during a pull or commit
1765 1781 if linkrev >= cl_count:
1766 1782 break
1767 1783
1768 1784 parentlinkrevs = []
1769 1785 for p in filelog.parentrevs(j):
1770 1786 if p != nullrev:
1771 1787 parentlinkrevs.append(filelog.linkrev(p))
1772 1788 n = filelog.node(j)
1773 1789 revs.append((linkrev, parentlinkrevs,
1774 1790 follow and filelog.renamed(n)))
1775 1791
1776 1792 return reversed(revs)
1777 1793 def iterfiles():
1778 1794 pctx = repo['.']
1779 1795 for filename in match.files():
1780 1796 if follow:
1781 1797 if filename not in pctx:
1782 1798 raise error.Abort(_('cannot follow file not in parent '
1783 1799 'revision: "%s"') % filename)
1784 1800 yield filename, pctx[filename].filenode()
1785 1801 else:
1786 1802 yield filename, None
1787 1803 for filename_node in copies:
1788 1804 yield filename_node
1789 1805
1790 1806 for file_, node in iterfiles():
1791 1807 filelog = repo.file(file_)
1792 1808 if not len(filelog):
1793 1809 if node is None:
1794 1810 # A zero count may be a directory or deleted file, so
1795 1811 # try to find matching entries on the slow path.
1796 1812 if follow:
1797 1813 raise error.Abort(
1798 1814 _('cannot follow nonexistent file: "%s"') % file_)
1799 1815 raise FileWalkError("Cannot walk via filelog")
1800 1816 else:
1801 1817 continue
1802 1818
1803 1819 if node is None:
1804 1820 last = len(filelog) - 1
1805 1821 else:
1806 1822 last = filelog.rev(node)
1807 1823
1808 1824 # keep track of all ancestors of the file
1809 1825 ancestors = {filelog.linkrev(last)}
1810 1826
1811 1827 # iterate from latest to oldest revision
1812 1828 for rev, flparentlinkrevs, copied in filerevs(filelog, last):
1813 1829 if not follow:
1814 1830 if rev > maxrev:
1815 1831 continue
1816 1832 else:
1817 1833 # Note that last might not be the first interesting
1818 1834 # rev to us:
1819 1835 # if the file has been changed after maxrev, we'll
1820 1836 # have linkrev(last) > maxrev, and we still need
1821 1837 # to explore the file graph
1822 1838 if rev not in ancestors:
1823 1839 continue
1824 1840 # XXX insert 1327 fix here
1825 1841 if flparentlinkrevs:
1826 1842 ancestors.update(flparentlinkrevs)
1827 1843
1828 1844 fncache.setdefault(rev, []).append(file_)
1829 1845 wanted.add(rev)
1830 1846 if copied:
1831 1847 copies.append(copied)
1832 1848
1833 1849 return wanted
1834 1850
1835 1851 class _followfilter(object):
1836 1852 def __init__(self, repo, onlyfirst=False):
1837 1853 self.repo = repo
1838 1854 self.startrev = nullrev
1839 1855 self.roots = set()
1840 1856 self.onlyfirst = onlyfirst
1841 1857
1842 1858 def match(self, rev):
1843 1859 def realparents(rev):
1844 1860 if self.onlyfirst:
1845 1861 return self.repo.changelog.parentrevs(rev)[0:1]
1846 1862 else:
1847 1863 return filter(lambda x: x != nullrev,
1848 1864 self.repo.changelog.parentrevs(rev))
1849 1865
1850 1866 if self.startrev == nullrev:
1851 1867 self.startrev = rev
1852 1868 return True
1853 1869
1854 1870 if rev > self.startrev:
1855 1871 # forward: all descendants
1856 1872 if not self.roots:
1857 1873 self.roots.add(self.startrev)
1858 1874 for parent in realparents(rev):
1859 1875 if parent in self.roots:
1860 1876 self.roots.add(rev)
1861 1877 return True
1862 1878 else:
1863 1879 # backwards: all parents
1864 1880 if not self.roots:
1865 1881 self.roots.update(realparents(self.startrev))
1866 1882 if rev in self.roots:
1867 1883 self.roots.remove(rev)
1868 1884 self.roots.update(realparents(rev))
1869 1885 return True
1870 1886
1871 1887 return False
1872 1888
1873 1889 def walkchangerevs(repo, match, opts, prepare):
1874 1890 '''Iterate over files and the revs in which they changed.
1875 1891
1876 1892 Callers most commonly need to iterate backwards over the history
1877 1893 in which they are interested. Doing so has awful (quadratic-looking)
1878 1894 performance, so we use iterators in a "windowed" way.
1879 1895
1880 1896 We walk a window of revisions in the desired order. Within the
1881 1897 window, we first walk forwards to gather data, then in the desired
1882 1898 order (usually backwards) to display it.
1883 1899
1884 1900 This function returns an iterator yielding contexts. Before
1885 1901 yielding each context, the iterator will first call the prepare
1886 1902 function on each context in the window in forward order.'''
1887 1903
1888 1904 allfiles = opts.get('all_files')
1889 1905 follow = opts.get('follow') or opts.get('follow_first')
1890 1906 revs = _walkrevs(repo, opts)
1891 1907 if not revs:
1892 1908 return []
1893 1909 wanted = set()
1894 1910 slowpath = match.anypats() or (not match.always() and opts.get('removed'))
1895 1911 fncache = {}
1896 1912 change = repo.__getitem__
1897 1913
1898 1914 # First step is to fill wanted, the set of revisions that we want to yield.
1899 1915 # When it does not induce extra cost, we also fill fncache for revisions in
1900 1916 # wanted: a cache of filenames that were changed (ctx.files()) and that
1901 1917 # match the file filtering conditions.
1902 1918
1903 1919 if match.always() or allfiles:
1904 1920 # No files, no patterns. Display all revs.
1905 1921 wanted = revs
1906 1922 elif not slowpath:
1907 1923 # We only have to read through the filelog to find wanted revisions
1908 1924
1909 1925 try:
1910 1926 wanted = walkfilerevs(repo, match, follow, revs, fncache)
1911 1927 except FileWalkError:
1912 1928 slowpath = True
1913 1929
1914 1930 # We decided to fall back to the slowpath because at least one
1915 1931 # of the paths was not a file. Check to see if at least one of them
1916 1932 # existed in history, otherwise simply return
1917 1933 for path in match.files():
1918 1934 if path == '.' or path in repo.store:
1919 1935 break
1920 1936 else:
1921 1937 return []
1922 1938
1923 1939 if slowpath:
1924 1940 # We have to read the changelog to match filenames against
1925 1941 # changed files
1926 1942
1927 1943 if follow:
1928 1944 raise error.Abort(_('can only follow copies/renames for explicit '
1929 1945 'filenames'))
1930 1946
1931 1947 # The slow path checks files modified in every changeset.
1932 1948 # This is really slow on large repos, so compute the set lazily.
1933 1949 class lazywantedset(object):
1934 1950 def __init__(self):
1935 1951 self.set = set()
1936 1952 self.revs = set(revs)
1937 1953
1938 1954 # No need to worry about locality here because it will be accessed
1939 1955 # in the same order as the increasing window below.
1940 1956 def __contains__(self, value):
1941 1957 if value in self.set:
1942 1958 return True
1943 1959 elif not value in self.revs:
1944 1960 return False
1945 1961 else:
1946 1962 self.revs.discard(value)
1947 1963 ctx = change(value)
1948 1964 if allfiles:
1949 1965 matches = list(ctx.manifest().walk(match))
1950 1966 else:
1951 1967 matches = [f for f in ctx.files() if match(f)]
1952 1968 if matches:
1953 1969 fncache[value] = matches
1954 1970 self.set.add(value)
1955 1971 return True
1956 1972 return False
1957 1973
1958 1974 def discard(self, value):
1959 1975 self.revs.discard(value)
1960 1976 self.set.discard(value)
1961 1977
1962 1978 wanted = lazywantedset()
1963 1979
1964 1980 # it might be worthwhile to do this in the iterator if the rev range
1965 1981 # is descending and the prune args are all within that range
1966 1982 for rev in opts.get('prune', ()):
1967 1983 rev = repo[rev].rev()
1968 1984 ff = _followfilter(repo)
1969 1985 stop = min(revs[0], revs[-1])
1970 1986 for x in pycompat.xrange(rev, stop - 1, -1):
1971 1987 if ff.match(x):
1972 1988 wanted = wanted - [x]
1973 1989
1974 1990 # Now that wanted is correctly initialized, we can iterate over the
1975 1991 # revision range, yielding only revisions in wanted.
1976 1992 def iterate():
1977 1993 if follow and match.always():
1978 1994 ff = _followfilter(repo, onlyfirst=opts.get('follow_first'))
1979 1995 def want(rev):
1980 1996 return ff.match(rev) and rev in wanted
1981 1997 else:
1982 1998 def want(rev):
1983 1999 return rev in wanted
1984 2000
1985 2001 it = iter(revs)
1986 2002 stopiteration = False
1987 2003 for windowsize in increasingwindows():
1988 2004 nrevs = []
1989 2005 for i in pycompat.xrange(windowsize):
1990 2006 rev = next(it, None)
1991 2007 if rev is None:
1992 2008 stopiteration = True
1993 2009 break
1994 2010 elif want(rev):
1995 2011 nrevs.append(rev)
1996 2012 for rev in sorted(nrevs):
1997 2013 fns = fncache.get(rev)
1998 2014 ctx = change(rev)
1999 2015 if not fns:
2000 2016 def fns_generator():
2001 2017 if allfiles:
2002 2018 fiter = iter(ctx)
2003 2019 else:
2004 2020 fiter = ctx.files()
2005 2021 for f in fiter:
2006 2022 if match(f):
2007 2023 yield f
2008 2024 fns = fns_generator()
2009 2025 prepare(ctx, fns)
2010 2026 for rev in nrevs:
2011 2027 yield change(rev)
2012 2028
2013 2029 if stopiteration:
2014 2030 break
2015 2031
2016 2032 return iterate()
2017 2033
2018 2034 def add(ui, repo, match, prefix, uipathfn, explicitonly, **opts):
2019 2035 bad = []
2020 2036
2021 2037 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2022 2038 names = []
2023 2039 wctx = repo[None]
2024 2040 cca = None
2025 2041 abort, warn = scmutil.checkportabilityalert(ui)
2026 2042 if abort or warn:
2027 2043 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
2028 2044
2029 2045 match = repo.narrowmatch(match, includeexact=True)
2030 2046 badmatch = matchmod.badmatch(match, badfn)
2031 2047 dirstate = repo.dirstate
2032 2048 # We don't want to just call wctx.walk here, since it would return a lot of
2033 2049 # clean files, which we aren't interested in and takes time.
2034 2050 for f in sorted(dirstate.walk(badmatch, subrepos=sorted(wctx.substate),
2035 2051 unknown=True, ignored=False, full=False)):
2036 2052 exact = match.exact(f)
2037 2053 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
2038 2054 if cca:
2039 2055 cca(f)
2040 2056 names.append(f)
2041 2057 if ui.verbose or not exact:
2042 2058 ui.status(_('adding %s\n') % uipathfn(f),
2043 2059 label='ui.addremove.added')
2044 2060
2045 2061 for subpath in sorted(wctx.substate):
2046 2062 sub = wctx.sub(subpath)
2047 2063 try:
2048 2064 submatch = matchmod.subdirmatcher(subpath, match)
2049 2065 subprefix = repo.wvfs.reljoin(prefix, subpath)
2050 2066 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2051 2067 if opts.get(r'subrepos'):
2052 2068 bad.extend(sub.add(ui, submatch, subprefix, subuipathfn, False,
2053 2069 **opts))
2054 2070 else:
2055 2071 bad.extend(sub.add(ui, submatch, subprefix, subuipathfn, True,
2056 2072 **opts))
2057 2073 except error.LookupError:
2058 2074 ui.status(_("skipping missing subrepository: %s\n")
2059 2075 % uipathfn(subpath))
2060 2076
2061 2077 if not opts.get(r'dry_run'):
2062 2078 rejected = wctx.add(names, prefix)
2063 2079 bad.extend(f for f in rejected if f in match.files())
2064 2080 return bad
2065 2081
2066 2082 def addwebdirpath(repo, serverpath, webconf):
2067 2083 webconf[serverpath] = repo.root
2068 2084 repo.ui.debug('adding %s = %s\n' % (serverpath, repo.root))
2069 2085
2070 2086 for r in repo.revs('filelog("path:.hgsub")'):
2071 2087 ctx = repo[r]
2072 2088 for subpath in ctx.substate:
2073 2089 ctx.sub(subpath).addwebdirpath(serverpath, webconf)
2074 2090
2075 2091 def forget(ui, repo, match, prefix, uipathfn, explicitonly, dryrun,
2076 2092 interactive):
2077 2093 if dryrun and interactive:
2078 2094 raise error.Abort(_("cannot specify both --dry-run and --interactive"))
2079 2095 bad = []
2080 2096 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2081 2097 wctx = repo[None]
2082 2098 forgot = []
2083 2099
2084 2100 s = repo.status(match=matchmod.badmatch(match, badfn), clean=True)
2085 2101 forget = sorted(s.modified + s.added + s.deleted + s.clean)
2086 2102 if explicitonly:
2087 2103 forget = [f for f in forget if match.exact(f)]
2088 2104
2089 2105 for subpath in sorted(wctx.substate):
2090 2106 sub = wctx.sub(subpath)
2091 2107 submatch = matchmod.subdirmatcher(subpath, match)
2092 2108 subprefix = repo.wvfs.reljoin(prefix, subpath)
2093 2109 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2094 2110 try:
2095 2111 subbad, subforgot = sub.forget(submatch, subprefix, subuipathfn,
2096 2112 dryrun=dryrun,
2097 2113 interactive=interactive)
2098 2114 bad.extend([subpath + '/' + f for f in subbad])
2099 2115 forgot.extend([subpath + '/' + f for f in subforgot])
2100 2116 except error.LookupError:
2101 2117 ui.status(_("skipping missing subrepository: %s\n")
2102 2118 % uipathfn(subpath))
2103 2119
2104 2120 if not explicitonly:
2105 2121 for f in match.files():
2106 2122 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2107 2123 if f not in forgot:
2108 2124 if repo.wvfs.exists(f):
2109 2125 # Don't complain if the exact case match wasn't given.
2110 2126 # But don't do this until after checking 'forgot', so
2111 2127 # that subrepo files aren't normalized, and this op is
2112 2128 # purely from data cached by the status walk above.
2113 2129 if repo.dirstate.normalize(f) in repo.dirstate:
2114 2130 continue
2115 2131 ui.warn(_('not removing %s: '
2116 2132 'file is already untracked\n')
2117 2133 % uipathfn(f))
2118 2134 bad.append(f)
2119 2135
2120 2136 if interactive:
2121 2137 responses = _('[Ynsa?]'
2122 2138 '$$ &Yes, forget this file'
2123 2139 '$$ &No, skip this file'
2124 2140 '$$ &Skip remaining files'
2125 2141 '$$ Include &all remaining files'
2126 2142 '$$ &? (display help)')
2127 2143 for filename in forget[:]:
2128 2144 r = ui.promptchoice(_('forget %s %s') %
2129 2145 (uipathfn(filename), responses))
2130 2146 if r == 4: # ?
2131 2147 while r == 4:
2132 2148 for c, t in ui.extractchoices(responses)[1]:
2133 2149 ui.write('%s - %s\n' % (c, encoding.lower(t)))
2134 2150 r = ui.promptchoice(_('forget %s %s') %
2135 2151 (uipathfn(filename), responses))
2136 2152 if r == 0: # yes
2137 2153 continue
2138 2154 elif r == 1: # no
2139 2155 forget.remove(filename)
2140 2156 elif r == 2: # Skip
2141 2157 fnindex = forget.index(filename)
2142 2158 del forget[fnindex:]
2143 2159 break
2144 2160 elif r == 3: # All
2145 2161 break
2146 2162
2147 2163 for f in forget:
2148 2164 if ui.verbose or not match.exact(f) or interactive:
2149 2165 ui.status(_('removing %s\n') % uipathfn(f),
2150 2166 label='ui.addremove.removed')
2151 2167
2152 2168 if not dryrun:
2153 2169 rejected = wctx.forget(forget, prefix)
2154 2170 bad.extend(f for f in rejected if f in match.files())
2155 2171 forgot.extend(f for f in forget if f not in rejected)
2156 2172 return bad, forgot
2157 2173
2158 2174 def files(ui, ctx, m, uipathfn, fm, fmt, subrepos):
2159 2175 ret = 1
2160 2176
2161 2177 needsfctx = ui.verbose or {'size', 'flags'} & fm.datahint()
2162 2178 for f in ctx.matches(m):
2163 2179 fm.startitem()
2164 2180 fm.context(ctx=ctx)
2165 2181 if needsfctx:
2166 2182 fc = ctx[f]
2167 2183 fm.write('size flags', '% 10d % 1s ', fc.size(), fc.flags())
2168 2184 fm.data(path=f)
2169 2185 fm.plain(fmt % uipathfn(f))
2170 2186 ret = 0
2171 2187
2172 2188 for subpath in sorted(ctx.substate):
2173 2189 submatch = matchmod.subdirmatcher(subpath, m)
2174 2190 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2175 2191 if (subrepos or m.exact(subpath) or any(submatch.files())):
2176 2192 sub = ctx.sub(subpath)
2177 2193 try:
2178 2194 recurse = m.exact(subpath) or subrepos
2179 2195 if sub.printfiles(ui, submatch, subuipathfn, fm, fmt,
2180 2196 recurse) == 0:
2181 2197 ret = 0
2182 2198 except error.LookupError:
2183 2199 ui.status(_("skipping missing subrepository: %s\n")
2184 2200 % uipathfn(subpath))
2185 2201
2186 2202 return ret
2187 2203
2188 2204 def remove(ui, repo, m, prefix, uipathfn, after, force, subrepos, dryrun,
2189 2205 warnings=None):
2190 2206 ret = 0
2191 2207 s = repo.status(match=m, clean=True)
2192 2208 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2193 2209
2194 2210 wctx = repo[None]
2195 2211
2196 2212 if warnings is None:
2197 2213 warnings = []
2198 2214 warn = True
2199 2215 else:
2200 2216 warn = False
2201 2217
2202 2218 subs = sorted(wctx.substate)
2203 2219 progress = ui.makeprogress(_('searching'), total=len(subs),
2204 2220 unit=_('subrepos'))
2205 2221 for subpath in subs:
2206 2222 submatch = matchmod.subdirmatcher(subpath, m)
2207 2223 subprefix = repo.wvfs.reljoin(prefix, subpath)
2208 2224 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2209 2225 if subrepos or m.exact(subpath) or any(submatch.files()):
2210 2226 progress.increment()
2211 2227 sub = wctx.sub(subpath)
2212 2228 try:
2213 2229 if sub.removefiles(submatch, subprefix, subuipathfn, after,
2214 2230 force, subrepos, dryrun, warnings):
2215 2231 ret = 1
2216 2232 except error.LookupError:
2217 2233 warnings.append(_("skipping missing subrepository: %s\n")
2218 2234 % uipathfn(subpath))
2219 2235 progress.complete()
2220 2236
2221 2237 # warn about failure to delete explicit files/dirs
2222 2238 deleteddirs = util.dirs(deleted)
2223 2239 files = m.files()
2224 2240 progress = ui.makeprogress(_('deleting'), total=len(files),
2225 2241 unit=_('files'))
2226 2242 for f in files:
2227 2243 def insubrepo():
2228 2244 for subpath in wctx.substate:
2229 2245 if f.startswith(subpath + '/'):
2230 2246 return True
2231 2247 return False
2232 2248
2233 2249 progress.increment()
2234 2250 isdir = f in deleteddirs or wctx.hasdir(f)
2235 2251 if (f in repo.dirstate or isdir or f == '.'
2236 2252 or insubrepo() or f in subs):
2237 2253 continue
2238 2254
2239 2255 if repo.wvfs.exists(f):
2240 2256 if repo.wvfs.isdir(f):
2241 2257 warnings.append(_('not removing %s: no tracked files\n')
2242 2258 % uipathfn(f))
2243 2259 else:
2244 2260 warnings.append(_('not removing %s: file is untracked\n')
2245 2261 % uipathfn(f))
2246 2262 # missing files will generate a warning elsewhere
2247 2263 ret = 1
2248 2264 progress.complete()
2249 2265
2250 2266 if force:
2251 2267 list = modified + deleted + clean + added
2252 2268 elif after:
2253 2269 list = deleted
2254 2270 remaining = modified + added + clean
2255 2271 progress = ui.makeprogress(_('skipping'), total=len(remaining),
2256 2272 unit=_('files'))
2257 2273 for f in remaining:
2258 2274 progress.increment()
2259 2275 if ui.verbose or (f in files):
2260 2276 warnings.append(_('not removing %s: file still exists\n')
2261 2277 % uipathfn(f))
2262 2278 ret = 1
2263 2279 progress.complete()
2264 2280 else:
2265 2281 list = deleted + clean
2266 2282 progress = ui.makeprogress(_('skipping'),
2267 2283 total=(len(modified) + len(added)),
2268 2284 unit=_('files'))
2269 2285 for f in modified:
2270 2286 progress.increment()
2271 2287 warnings.append(_('not removing %s: file is modified (use -f'
2272 2288 ' to force removal)\n') % uipathfn(f))
2273 2289 ret = 1
2274 2290 for f in added:
2275 2291 progress.increment()
2276 2292 warnings.append(_("not removing %s: file has been marked for add"
2277 2293 " (use 'hg forget' to undo add)\n") % uipathfn(f))
2278 2294 ret = 1
2279 2295 progress.complete()
2280 2296
2281 2297 list = sorted(list)
2282 2298 progress = ui.makeprogress(_('deleting'), total=len(list),
2283 2299 unit=_('files'))
2284 2300 for f in list:
2285 2301 if ui.verbose or not m.exact(f):
2286 2302 progress.increment()
2287 2303 ui.status(_('removing %s\n') % uipathfn(f),
2288 2304 label='ui.addremove.removed')
2289 2305 progress.complete()
2290 2306
2291 2307 if not dryrun:
2292 2308 with repo.wlock():
2293 2309 if not after:
2294 2310 for f in list:
2295 2311 if f in added:
2296 2312 continue # we never unlink added files on remove
2297 2313 rmdir = repo.ui.configbool('experimental',
2298 2314 'removeemptydirs')
2299 2315 repo.wvfs.unlinkpath(f, ignoremissing=True, rmdir=rmdir)
2300 2316 repo[None].forget(list)
2301 2317
2302 2318 if warn:
2303 2319 for warning in warnings:
2304 2320 ui.warn(warning)
2305 2321
2306 2322 return ret
2307 2323
2308 2324 def _catfmtneedsdata(fm):
2309 2325 return not fm.datahint() or 'data' in fm.datahint()
2310 2326
2311 2327 def _updatecatformatter(fm, ctx, matcher, path, decode):
2312 2328 """Hook for adding data to the formatter used by ``hg cat``.
2313 2329
2314 2330 Extensions (e.g., lfs) can wrap this to inject keywords/data, but must call
2315 2331 this method first."""
2316 2332
2317 2333 # data() can be expensive to fetch (e.g. lfs), so don't fetch it if it
2318 2334 # wasn't requested.
2319 2335 data = b''
2320 2336 if _catfmtneedsdata(fm):
2321 2337 data = ctx[path].data()
2322 2338 if decode:
2323 2339 data = ctx.repo().wwritedata(path, data)
2324 2340 fm.startitem()
2325 2341 fm.context(ctx=ctx)
2326 2342 fm.write('data', '%s', data)
2327 2343 fm.data(path=path)
2328 2344
2329 2345 def cat(ui, repo, ctx, matcher, basefm, fntemplate, prefix, **opts):
2330 2346 err = 1
2331 2347 opts = pycompat.byteskwargs(opts)
2332 2348
2333 2349 def write(path):
2334 2350 filename = None
2335 2351 if fntemplate:
2336 2352 filename = makefilename(ctx, fntemplate,
2337 2353 pathname=os.path.join(prefix, path))
2338 2354 # attempt to create the directory if it does not already exist
2339 2355 try:
2340 2356 os.makedirs(os.path.dirname(filename))
2341 2357 except OSError:
2342 2358 pass
2343 2359 with formatter.maybereopen(basefm, filename) as fm:
2344 2360 _updatecatformatter(fm, ctx, matcher, path, opts.get('decode'))
2345 2361
2346 2362 # Automation often uses hg cat on single files, so special case it
2347 2363 # for performance to avoid the cost of parsing the manifest.
2348 2364 if len(matcher.files()) == 1 and not matcher.anypats():
2349 2365 file = matcher.files()[0]
2350 2366 mfl = repo.manifestlog
2351 2367 mfnode = ctx.manifestnode()
2352 2368 try:
2353 2369 if mfnode and mfl[mfnode].find(file)[0]:
2354 2370 if _catfmtneedsdata(basefm):
2355 2371 scmutil.prefetchfiles(repo, [ctx.rev()], matcher)
2356 2372 write(file)
2357 2373 return 0
2358 2374 except KeyError:
2359 2375 pass
2360 2376
2361 2377 if _catfmtneedsdata(basefm):
2362 2378 scmutil.prefetchfiles(repo, [ctx.rev()], matcher)
2363 2379
2364 2380 for abs in ctx.walk(matcher):
2365 2381 write(abs)
2366 2382 err = 0
2367 2383
2368 2384 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
2369 2385 for subpath in sorted(ctx.substate):
2370 2386 sub = ctx.sub(subpath)
2371 2387 try:
2372 2388 submatch = matchmod.subdirmatcher(subpath, matcher)
2373 2389 subprefix = os.path.join(prefix, subpath)
2374 2390 if not sub.cat(submatch, basefm, fntemplate, subprefix,
2375 2391 **pycompat.strkwargs(opts)):
2376 2392 err = 0
2377 2393 except error.RepoLookupError:
2378 2394 ui.status(_("skipping missing subrepository: %s\n") %
2379 2395 uipathfn(subpath))
2380 2396
2381 2397 return err
2382 2398
2383 2399 def commit(ui, repo, commitfunc, pats, opts):
2384 2400 '''commit the specified files or all outstanding changes'''
2385 2401 date = opts.get('date')
2386 2402 if date:
2387 2403 opts['date'] = dateutil.parsedate(date)
2388 2404 message = logmessage(ui, opts)
2389 2405 matcher = scmutil.match(repo[None], pats, opts)
2390 2406
2391 2407 dsguard = None
2392 2408 # extract addremove carefully -- this function can be called from a command
2393 2409 # that doesn't support addremove
2394 2410 if opts.get('addremove'):
2395 2411 dsguard = dirstateguard.dirstateguard(repo, 'commit')
2396 2412 with dsguard or util.nullcontextmanager():
2397 2413 if dsguard:
2398 2414 relative = scmutil.anypats(pats, opts)
2399 2415 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
2400 2416 if scmutil.addremove(repo, matcher, "", uipathfn, opts) != 0:
2401 2417 raise error.Abort(
2402 2418 _("failed to mark all new/missing files as added/removed"))
2403 2419
2404 2420 return commitfunc(ui, repo, message, matcher, opts)
2405 2421
2406 2422 def samefile(f, ctx1, ctx2):
2407 2423 if f in ctx1.manifest():
2408 2424 a = ctx1.filectx(f)
2409 2425 if f in ctx2.manifest():
2410 2426 b = ctx2.filectx(f)
2411 2427 return (not a.cmp(b)
2412 2428 and a.flags() == b.flags())
2413 2429 else:
2414 2430 return False
2415 2431 else:
2416 2432 return f not in ctx2.manifest()
2417 2433
2418 2434 def amend(ui, repo, old, extra, pats, opts):
2419 2435 # avoid cycle context -> subrepo -> cmdutil
2420 2436 from . import context
2421 2437
2422 2438 # amend will reuse the existing user if not specified, but the obsolete
2423 2439 # marker creation requires that the current user's name is specified.
2424 2440 if obsolete.isenabled(repo, obsolete.createmarkersopt):
2425 2441 ui.username() # raise exception if username not set
2426 2442
2427 2443 ui.note(_('amending changeset %s\n') % old)
2428 2444 base = old.p1()
2429 2445
2430 2446 with repo.wlock(), repo.lock(), repo.transaction('amend'):
2431 2447 # Participating changesets:
2432 2448 #
2433 2449 # wctx o - workingctx that contains changes from working copy
2434 2450 # | to go into amending commit
2435 2451 # |
2436 2452 # old o - changeset to amend
2437 2453 # |
2438 2454 # base o - first parent of the changeset to amend
2439 2455 wctx = repo[None]
2440 2456
2441 2457 # Copy to avoid mutating input
2442 2458 extra = extra.copy()
2443 2459 # Update extra dict from amended commit (e.g. to preserve graft
2444 2460 # source)
2445 2461 extra.update(old.extra())
2446 2462
2447 2463 # Also update it from the from the wctx
2448 2464 extra.update(wctx.extra())
2449 2465
2450 2466 user = opts.get('user') or old.user()
2451 2467
2452 2468 datemaydiffer = False # date-only change should be ignored?
2453 2469 if opts.get('date') and opts.get('currentdate'):
2454 2470 raise error.Abort(_('--date and --currentdate are mutually '
2455 2471 'exclusive'))
2456 2472 if opts.get('date'):
2457 2473 date = dateutil.parsedate(opts.get('date'))
2458 2474 elif opts.get('currentdate'):
2459 2475 date = dateutil.makedate()
2460 2476 elif (ui.configbool('rewrite', 'update-timestamp')
2461 2477 and opts.get('currentdate') is None):
2462 2478 date = dateutil.makedate()
2463 2479 datemaydiffer = True
2464 2480 else:
2465 2481 date = old.date()
2466 2482
2467 2483 if len(old.parents()) > 1:
2468 2484 # ctx.files() isn't reliable for merges, so fall back to the
2469 2485 # slower repo.status() method
2470 2486 files = {fn for st in base.status(old)[:3] for fn in st}
2471 2487 else:
2472 2488 files = set(old.files())
2473 2489
2474 2490 # add/remove the files to the working copy if the "addremove" option
2475 2491 # was specified.
2476 2492 matcher = scmutil.match(wctx, pats, opts)
2477 2493 relative = scmutil.anypats(pats, opts)
2478 2494 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
2479 2495 if (opts.get('addremove')
2480 2496 and scmutil.addremove(repo, matcher, "", uipathfn, opts)):
2481 2497 raise error.Abort(
2482 2498 _("failed to mark all new/missing files as added/removed"))
2483 2499
2484 2500 # Check subrepos. This depends on in-place wctx._status update in
2485 2501 # subrepo.precommit(). To minimize the risk of this hack, we do
2486 2502 # nothing if .hgsub does not exist.
2487 2503 if '.hgsub' in wctx or '.hgsub' in old:
2488 2504 subs, commitsubs, newsubstate = subrepoutil.precommit(
2489 2505 ui, wctx, wctx._status, matcher)
2490 2506 # amend should abort if commitsubrepos is enabled
2491 2507 assert not commitsubs
2492 2508 if subs:
2493 2509 subrepoutil.writestate(repo, newsubstate)
2494 2510
2495 2511 ms = mergemod.mergestate.read(repo)
2496 2512 mergeutil.checkunresolved(ms)
2497 2513
2498 2514 filestoamend = set(f for f in wctx.files() if matcher(f))
2499 2515
2500 2516 changes = (len(filestoamend) > 0)
2501 2517 if changes:
2502 2518 # Recompute copies (avoid recording a -> b -> a)
2503 2519 copied = copies.pathcopies(base, wctx, matcher)
2504 2520 if old.p2:
2505 2521 copied.update(copies.pathcopies(old.p2(), wctx, matcher))
2506 2522
2507 2523 # Prune files which were reverted by the updates: if old
2508 2524 # introduced file X and the file was renamed in the working
2509 2525 # copy, then those two files are the same and
2510 2526 # we can discard X from our list of files. Likewise if X
2511 2527 # was removed, it's no longer relevant. If X is missing (aka
2512 2528 # deleted), old X must be preserved.
2513 2529 files.update(filestoamend)
2514 2530 files = [f for f in files if (f not in filestoamend
2515 2531 or not samefile(f, wctx, base))]
2516 2532
2517 2533 def filectxfn(repo, ctx_, path):
2518 2534 try:
2519 2535 # If the file being considered is not amongst the files
2520 2536 # to be amended, we should return the file context from the
2521 2537 # old changeset. This avoids issues when only some files in
2522 2538 # the working copy are being amended but there are also
2523 2539 # changes to other files from the old changeset.
2524 2540 if path not in filestoamend:
2525 2541 return old.filectx(path)
2526 2542
2527 2543 # Return None for removed files.
2528 2544 if path in wctx.removed():
2529 2545 return None
2530 2546
2531 2547 fctx = wctx[path]
2532 2548 flags = fctx.flags()
2533 2549 mctx = context.memfilectx(repo, ctx_,
2534 2550 fctx.path(), fctx.data(),
2535 2551 islink='l' in flags,
2536 2552 isexec='x' in flags,
2537 2553 copysource=copied.get(path))
2538 2554 return mctx
2539 2555 except KeyError:
2540 2556 return None
2541 2557 else:
2542 2558 ui.note(_('copying changeset %s to %s\n') % (old, base))
2543 2559
2544 2560 # Use version of files as in the old cset
2545 2561 def filectxfn(repo, ctx_, path):
2546 2562 try:
2547 2563 return old.filectx(path)
2548 2564 except KeyError:
2549 2565 return None
2550 2566
2551 2567 # See if we got a message from -m or -l, if not, open the editor with
2552 2568 # the message of the changeset to amend.
2553 2569 message = logmessage(ui, opts)
2554 2570
2555 2571 editform = mergeeditform(old, 'commit.amend')
2556 2572
2557 2573 if not message:
2558 2574 message = old.description()
2559 2575 # Default if message isn't provided and --edit is not passed is to
2560 2576 # invoke editor, but allow --no-edit. If somehow we don't have any
2561 2577 # description, let's always start the editor.
2562 2578 doedit = not message or opts.get('edit') in [True, None]
2563 2579 else:
2564 2580 # Default if message is provided is to not invoke editor, but allow
2565 2581 # --edit.
2566 2582 doedit = opts.get('edit') is True
2567 2583 editor = getcommiteditor(edit=doedit, editform=editform)
2568 2584
2569 2585 pureextra = extra.copy()
2570 2586 extra['amend_source'] = old.hex()
2571 2587
2572 2588 new = context.memctx(repo,
2573 2589 parents=[base.node(), old.p2().node()],
2574 2590 text=message,
2575 2591 files=files,
2576 2592 filectxfn=filectxfn,
2577 2593 user=user,
2578 2594 date=date,
2579 2595 extra=extra,
2580 2596 editor=editor)
2581 2597
2582 2598 newdesc = changelog.stripdesc(new.description())
2583 2599 if ((not changes)
2584 2600 and newdesc == old.description()
2585 2601 and user == old.user()
2586 2602 and (date == old.date() or datemaydiffer)
2587 2603 and pureextra == old.extra()):
2588 2604 # nothing changed. continuing here would create a new node
2589 2605 # anyway because of the amend_source noise.
2590 2606 #
2591 2607 # This not what we expect from amend.
2592 2608 return old.node()
2593 2609
2594 2610 commitphase = None
2595 2611 if opts.get('secret'):
2596 2612 commitphase = phases.secret
2597 2613 newid = repo.commitctx(new)
2598 2614
2599 2615 # Reroute the working copy parent to the new changeset
2600 2616 repo.setparents(newid, nullid)
2601 2617 mapping = {old.node(): (newid,)}
2602 2618 obsmetadata = None
2603 2619 if opts.get('note'):
2604 2620 obsmetadata = {'note': encoding.fromlocal(opts['note'])}
2605 2621 backup = ui.configbool('rewrite', 'backup-bundle')
2606 2622 scmutil.cleanupnodes(repo, mapping, 'amend', metadata=obsmetadata,
2607 2623 fixphase=True, targetphase=commitphase,
2608 2624 backup=backup)
2609 2625
2610 2626 # Fixing the dirstate because localrepo.commitctx does not update
2611 2627 # it. This is rather convenient because we did not need to update
2612 2628 # the dirstate for all the files in the new commit which commitctx
2613 2629 # could have done if it updated the dirstate. Now, we can
2614 2630 # selectively update the dirstate only for the amended files.
2615 2631 dirstate = repo.dirstate
2616 2632
2617 2633 # Update the state of the files which were added and
2618 2634 # and modified in the amend to "normal" in the dirstate.
2619 2635 normalfiles = set(wctx.modified() + wctx.added()) & filestoamend
2620 2636 for f in normalfiles:
2621 2637 dirstate.normal(f)
2622 2638
2623 2639 # Update the state of files which were removed in the amend
2624 2640 # to "removed" in the dirstate.
2625 2641 removedfiles = set(wctx.removed()) & filestoamend
2626 2642 for f in removedfiles:
2627 2643 dirstate.drop(f)
2628 2644
2629 2645 return newid
2630 2646
2631 2647 def commiteditor(repo, ctx, subs, editform=''):
2632 2648 if ctx.description():
2633 2649 return ctx.description()
2634 2650 return commitforceeditor(repo, ctx, subs, editform=editform,
2635 2651 unchangedmessagedetection=True)
2636 2652
2637 2653 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
2638 2654 editform='', unchangedmessagedetection=False):
2639 2655 if not extramsg:
2640 2656 extramsg = _("Leave message empty to abort commit.")
2641 2657
2642 2658 forms = [e for e in editform.split('.') if e]
2643 2659 forms.insert(0, 'changeset')
2644 2660 templatetext = None
2645 2661 while forms:
2646 2662 ref = '.'.join(forms)
2647 2663 if repo.ui.config('committemplate', ref):
2648 2664 templatetext = committext = buildcommittemplate(
2649 2665 repo, ctx, subs, extramsg, ref)
2650 2666 break
2651 2667 forms.pop()
2652 2668 else:
2653 2669 committext = buildcommittext(repo, ctx, subs, extramsg)
2654 2670
2655 2671 # run editor in the repository root
2656 2672 olddir = encoding.getcwd()
2657 2673 os.chdir(repo.root)
2658 2674
2659 2675 # make in-memory changes visible to external process
2660 2676 tr = repo.currenttransaction()
2661 2677 repo.dirstate.write(tr)
2662 2678 pending = tr and tr.writepending() and repo.root
2663 2679
2664 2680 editortext = repo.ui.edit(committext, ctx.user(), ctx.extra(),
2665 2681 editform=editform, pending=pending,
2666 2682 repopath=repo.path, action='commit')
2667 2683 text = editortext
2668 2684
2669 2685 # strip away anything below this special string (used for editors that want
2670 2686 # to display the diff)
2671 2687 stripbelow = re.search(_linebelow, text, flags=re.MULTILINE)
2672 2688 if stripbelow:
2673 2689 text = text[:stripbelow.start()]
2674 2690
2675 2691 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
2676 2692 os.chdir(olddir)
2677 2693
2678 2694 if finishdesc:
2679 2695 text = finishdesc(text)
2680 2696 if not text.strip():
2681 2697 raise error.Abort(_("empty commit message"))
2682 2698 if unchangedmessagedetection and editortext == templatetext:
2683 2699 raise error.Abort(_("commit message unchanged"))
2684 2700
2685 2701 return text
2686 2702
2687 2703 def buildcommittemplate(repo, ctx, subs, extramsg, ref):
2688 2704 ui = repo.ui
2689 2705 spec = formatter.templatespec(ref, None, None)
2690 2706 t = logcmdutil.changesettemplater(ui, repo, spec)
2691 2707 t.t.cache.update((k, templater.unquotestring(v))
2692 2708 for k, v in repo.ui.configitems('committemplate'))
2693 2709
2694 2710 if not extramsg:
2695 2711 extramsg = '' # ensure that extramsg is string
2696 2712
2697 2713 ui.pushbuffer()
2698 2714 t.show(ctx, extramsg=extramsg)
2699 2715 return ui.popbuffer()
2700 2716
2701 2717 def hgprefix(msg):
2702 2718 return "\n".join(["HG: %s" % a for a in msg.split("\n") if a])
2703 2719
2704 2720 def buildcommittext(repo, ctx, subs, extramsg):
2705 2721 edittext = []
2706 2722 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2707 2723 if ctx.description():
2708 2724 edittext.append(ctx.description())
2709 2725 edittext.append("")
2710 2726 edittext.append("") # Empty line between message and comments.
2711 2727 edittext.append(hgprefix(_("Enter commit message."
2712 2728 " Lines beginning with 'HG:' are removed.")))
2713 2729 edittext.append(hgprefix(extramsg))
2714 2730 edittext.append("HG: --")
2715 2731 edittext.append(hgprefix(_("user: %s") % ctx.user()))
2716 2732 if ctx.p2():
2717 2733 edittext.append(hgprefix(_("branch merge")))
2718 2734 if ctx.branch():
2719 2735 edittext.append(hgprefix(_("branch '%s'") % ctx.branch()))
2720 2736 if bookmarks.isactivewdirparent(repo):
2721 2737 edittext.append(hgprefix(_("bookmark '%s'") % repo._activebookmark))
2722 2738 edittext.extend([hgprefix(_("subrepo %s") % s) for s in subs])
2723 2739 edittext.extend([hgprefix(_("added %s") % f) for f in added])
2724 2740 edittext.extend([hgprefix(_("changed %s") % f) for f in modified])
2725 2741 edittext.extend([hgprefix(_("removed %s") % f) for f in removed])
2726 2742 if not added and not modified and not removed:
2727 2743 edittext.append(hgprefix(_("no files changed")))
2728 2744 edittext.append("")
2729 2745
2730 2746 return "\n".join(edittext)
2731 2747
2732 2748 def commitstatus(repo, node, branch, bheads=None, opts=None):
2733 2749 if opts is None:
2734 2750 opts = {}
2735 2751 ctx = repo[node]
2736 2752 parents = ctx.parents()
2737 2753
2738 2754 if (not opts.get('amend') and bheads and node not in bheads and not
2739 2755 [x for x in parents if x.node() in bheads and x.branch() == branch]):
2740 2756 repo.ui.status(_('created new head\n'))
2741 2757 # The message is not printed for initial roots. For the other
2742 2758 # changesets, it is printed in the following situations:
2743 2759 #
2744 2760 # Par column: for the 2 parents with ...
2745 2761 # N: null or no parent
2746 2762 # B: parent is on another named branch
2747 2763 # C: parent is a regular non head changeset
2748 2764 # H: parent was a branch head of the current branch
2749 2765 # Msg column: whether we print "created new head" message
2750 2766 # In the following, it is assumed that there already exists some
2751 2767 # initial branch heads of the current branch, otherwise nothing is
2752 2768 # printed anyway.
2753 2769 #
2754 2770 # Par Msg Comment
2755 2771 # N N y additional topo root
2756 2772 #
2757 2773 # B N y additional branch root
2758 2774 # C N y additional topo head
2759 2775 # H N n usual case
2760 2776 #
2761 2777 # B B y weird additional branch root
2762 2778 # C B y branch merge
2763 2779 # H B n merge with named branch
2764 2780 #
2765 2781 # C C y additional head from merge
2766 2782 # C H n merge with a head
2767 2783 #
2768 2784 # H H n head merge: head count decreases
2769 2785
2770 2786 if not opts.get('close_branch'):
2771 2787 for r in parents:
2772 2788 if r.closesbranch() and r.branch() == branch:
2773 2789 repo.ui.status(_('reopening closed branch head %d\n') % r.rev())
2774 2790
2775 2791 if repo.ui.debugflag:
2776 2792 repo.ui.write(_('committed changeset %d:%s\n') % (ctx.rev(), ctx.hex()))
2777 2793 elif repo.ui.verbose:
2778 2794 repo.ui.write(_('committed changeset %d:%s\n') % (ctx.rev(), ctx))
2779 2795
2780 2796 def postcommitstatus(repo, pats, opts):
2781 2797 return repo.status(match=scmutil.match(repo[None], pats, opts))
2782 2798
2783 2799 def revert(ui, repo, ctx, parents, *pats, **opts):
2784 2800 opts = pycompat.byteskwargs(opts)
2785 2801 parent, p2 = parents
2786 2802 node = ctx.node()
2787 2803
2788 2804 mf = ctx.manifest()
2789 2805 if node == p2:
2790 2806 parent = p2
2791 2807
2792 2808 # need all matching names in dirstate and manifest of target rev,
2793 2809 # so have to walk both. do not print errors if files exist in one
2794 2810 # but not other. in both cases, filesets should be evaluated against
2795 2811 # workingctx to get consistent result (issue4497). this means 'set:**'
2796 2812 # cannot be used to select missing files from target rev.
2797 2813
2798 2814 # `names` is a mapping for all elements in working copy and target revision
2799 2815 # The mapping is in the form:
2800 2816 # <abs path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
2801 2817 names = {}
2802 2818 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
2803 2819
2804 2820 with repo.wlock():
2805 2821 ## filling of the `names` mapping
2806 2822 # walk dirstate to fill `names`
2807 2823
2808 2824 interactive = opts.get('interactive', False)
2809 2825 wctx = repo[None]
2810 2826 m = scmutil.match(wctx, pats, opts)
2811 2827
2812 2828 # we'll need this later
2813 2829 targetsubs = sorted(s for s in wctx.substate if m(s))
2814 2830
2815 2831 if not m.always():
2816 2832 matcher = matchmod.badmatch(m, lambda x, y: False)
2817 2833 for abs in wctx.walk(matcher):
2818 2834 names[abs] = m.exact(abs)
2819 2835
2820 2836 # walk target manifest to fill `names`
2821 2837
2822 2838 def badfn(path, msg):
2823 2839 if path in names:
2824 2840 return
2825 2841 if path in ctx.substate:
2826 2842 return
2827 2843 path_ = path + '/'
2828 2844 for f in names:
2829 2845 if f.startswith(path_):
2830 2846 return
2831 2847 ui.warn("%s: %s\n" % (uipathfn(path), msg))
2832 2848
2833 2849 for abs in ctx.walk(matchmod.badmatch(m, badfn)):
2834 2850 if abs not in names:
2835 2851 names[abs] = m.exact(abs)
2836 2852
2837 2853 # Find status of all file in `names`.
2838 2854 m = scmutil.matchfiles(repo, names)
2839 2855
2840 2856 changes = repo.status(node1=node, match=m,
2841 2857 unknown=True, ignored=True, clean=True)
2842 2858 else:
2843 2859 changes = repo.status(node1=node, match=m)
2844 2860 for kind in changes:
2845 2861 for abs in kind:
2846 2862 names[abs] = m.exact(abs)
2847 2863
2848 2864 m = scmutil.matchfiles(repo, names)
2849 2865
2850 2866 modified = set(changes.modified)
2851 2867 added = set(changes.added)
2852 2868 removed = set(changes.removed)
2853 2869 _deleted = set(changes.deleted)
2854 2870 unknown = set(changes.unknown)
2855 2871 unknown.update(changes.ignored)
2856 2872 clean = set(changes.clean)
2857 2873 modadded = set()
2858 2874
2859 2875 # We need to account for the state of the file in the dirstate,
2860 2876 # even when we revert against something else than parent. This will
2861 2877 # slightly alter the behavior of revert (doing back up or not, delete
2862 2878 # or just forget etc).
2863 2879 if parent == node:
2864 2880 dsmodified = modified
2865 2881 dsadded = added
2866 2882 dsremoved = removed
2867 2883 # store all local modifications, useful later for rename detection
2868 2884 localchanges = dsmodified | dsadded
2869 2885 modified, added, removed = set(), set(), set()
2870 2886 else:
2871 2887 changes = repo.status(node1=parent, match=m)
2872 2888 dsmodified = set(changes.modified)
2873 2889 dsadded = set(changes.added)
2874 2890 dsremoved = set(changes.removed)
2875 2891 # store all local modifications, useful later for rename detection
2876 2892 localchanges = dsmodified | dsadded
2877 2893
2878 2894 # only take into account for removes between wc and target
2879 2895 clean |= dsremoved - removed
2880 2896 dsremoved &= removed
2881 2897 # distinct between dirstate remove and other
2882 2898 removed -= dsremoved
2883 2899
2884 2900 modadded = added & dsmodified
2885 2901 added -= modadded
2886 2902
2887 2903 # tell newly modified apart.
2888 2904 dsmodified &= modified
2889 2905 dsmodified |= modified & dsadded # dirstate added may need backup
2890 2906 modified -= dsmodified
2891 2907
2892 2908 # We need to wait for some post-processing to update this set
2893 2909 # before making the distinction. The dirstate will be used for
2894 2910 # that purpose.
2895 2911 dsadded = added
2896 2912
2897 2913 # in case of merge, files that are actually added can be reported as
2898 2914 # modified, we need to post process the result
2899 2915 if p2 != nullid:
2900 2916 mergeadd = set(dsmodified)
2901 2917 for path in dsmodified:
2902 2918 if path in mf:
2903 2919 mergeadd.remove(path)
2904 2920 dsadded |= mergeadd
2905 2921 dsmodified -= mergeadd
2906 2922
2907 2923 # if f is a rename, update `names` to also revert the source
2908 2924 for f in localchanges:
2909 2925 src = repo.dirstate.copied(f)
2910 2926 # XXX should we check for rename down to target node?
2911 2927 if src and src not in names and repo.dirstate[src] == 'r':
2912 2928 dsremoved.add(src)
2913 2929 names[src] = True
2914 2930
2915 2931 # determine the exact nature of the deleted changesets
2916 2932 deladded = set(_deleted)
2917 2933 for path in _deleted:
2918 2934 if path in mf:
2919 2935 deladded.remove(path)
2920 2936 deleted = _deleted - deladded
2921 2937
2922 2938 # distinguish between file to forget and the other
2923 2939 added = set()
2924 2940 for abs in dsadded:
2925 2941 if repo.dirstate[abs] != 'a':
2926 2942 added.add(abs)
2927 2943 dsadded -= added
2928 2944
2929 2945 for abs in deladded:
2930 2946 if repo.dirstate[abs] == 'a':
2931 2947 dsadded.add(abs)
2932 2948 deladded -= dsadded
2933 2949
2934 2950 # For files marked as removed, we check if an unknown file is present at
2935 2951 # the same path. If a such file exists it may need to be backed up.
2936 2952 # Making the distinction at this stage helps have simpler backup
2937 2953 # logic.
2938 2954 removunk = set()
2939 2955 for abs in removed:
2940 2956 target = repo.wjoin(abs)
2941 2957 if os.path.lexists(target):
2942 2958 removunk.add(abs)
2943 2959 removed -= removunk
2944 2960
2945 2961 dsremovunk = set()
2946 2962 for abs in dsremoved:
2947 2963 target = repo.wjoin(abs)
2948 2964 if os.path.lexists(target):
2949 2965 dsremovunk.add(abs)
2950 2966 dsremoved -= dsremovunk
2951 2967
2952 2968 # action to be actually performed by revert
2953 2969 # (<list of file>, message>) tuple
2954 2970 actions = {'revert': ([], _('reverting %s\n')),
2955 2971 'add': ([], _('adding %s\n')),
2956 2972 'remove': ([], _('removing %s\n')),
2957 2973 'drop': ([], _('removing %s\n')),
2958 2974 'forget': ([], _('forgetting %s\n')),
2959 2975 'undelete': ([], _('undeleting %s\n')),
2960 2976 'noop': (None, _('no changes needed to %s\n')),
2961 2977 'unknown': (None, _('file not managed: %s\n')),
2962 2978 }
2963 2979
2964 2980 # "constant" that convey the backup strategy.
2965 2981 # All set to `discard` if `no-backup` is set do avoid checking
2966 2982 # no_backup lower in the code.
2967 2983 # These values are ordered for comparison purposes
2968 2984 backupinteractive = 3 # do backup if interactively modified
2969 2985 backup = 2 # unconditionally do backup
2970 2986 check = 1 # check if the existing file differs from target
2971 2987 discard = 0 # never do backup
2972 2988 if opts.get('no_backup'):
2973 2989 backupinteractive = backup = check = discard
2974 2990 if interactive:
2975 2991 dsmodifiedbackup = backupinteractive
2976 2992 else:
2977 2993 dsmodifiedbackup = backup
2978 2994 tobackup = set()
2979 2995
2980 2996 backupanddel = actions['remove']
2981 2997 if not opts.get('no_backup'):
2982 2998 backupanddel = actions['drop']
2983 2999
2984 3000 disptable = (
2985 3001 # dispatch table:
2986 3002 # file state
2987 3003 # action
2988 3004 # make backup
2989 3005
2990 3006 ## Sets that results that will change file on disk
2991 3007 # Modified compared to target, no local change
2992 3008 (modified, actions['revert'], discard),
2993 3009 # Modified compared to target, but local file is deleted
2994 3010 (deleted, actions['revert'], discard),
2995 3011 # Modified compared to target, local change
2996 3012 (dsmodified, actions['revert'], dsmodifiedbackup),
2997 3013 # Added since target
2998 3014 (added, actions['remove'], discard),
2999 3015 # Added in working directory
3000 3016 (dsadded, actions['forget'], discard),
3001 3017 # Added since target, have local modification
3002 3018 (modadded, backupanddel, backup),
3003 3019 # Added since target but file is missing in working directory
3004 3020 (deladded, actions['drop'], discard),
3005 3021 # Removed since target, before working copy parent
3006 3022 (removed, actions['add'], discard),
3007 3023 # Same as `removed` but an unknown file exists at the same path
3008 3024 (removunk, actions['add'], check),
3009 3025 # Removed since targe, marked as such in working copy parent
3010 3026 (dsremoved, actions['undelete'], discard),
3011 3027 # Same as `dsremoved` but an unknown file exists at the same path
3012 3028 (dsremovunk, actions['undelete'], check),
3013 3029 ## the following sets does not result in any file changes
3014 3030 # File with no modification
3015 3031 (clean, actions['noop'], discard),
3016 3032 # Existing file, not tracked anywhere
3017 3033 (unknown, actions['unknown'], discard),
3018 3034 )
3019 3035
3020 3036 for abs, exact in sorted(names.items()):
3021 3037 # target file to be touch on disk (relative to cwd)
3022 3038 target = repo.wjoin(abs)
3023 3039 # search the entry in the dispatch table.
3024 3040 # if the file is in any of these sets, it was touched in the working
3025 3041 # directory parent and we are sure it needs to be reverted.
3026 3042 for table, (xlist, msg), dobackup in disptable:
3027 3043 if abs not in table:
3028 3044 continue
3029 3045 if xlist is not None:
3030 3046 xlist.append(abs)
3031 3047 if dobackup:
3032 3048 # If in interactive mode, don't automatically create
3033 3049 # .orig files (issue4793)
3034 3050 if dobackup == backupinteractive:
3035 3051 tobackup.add(abs)
3036 3052 elif (backup <= dobackup or wctx[abs].cmp(ctx[abs])):
3037 3053 absbakname = scmutil.backuppath(ui, repo, abs)
3038 3054 bakname = os.path.relpath(absbakname,
3039 3055 start=repo.root)
3040 3056 ui.note(_('saving current version of %s as %s\n') %
3041 3057 (uipathfn(abs), uipathfn(bakname)))
3042 3058 if not opts.get('dry_run'):
3043 3059 if interactive:
3044 3060 util.copyfile(target, absbakname)
3045 3061 else:
3046 3062 util.rename(target, absbakname)
3047 3063 if opts.get('dry_run'):
3048 3064 if ui.verbose or not exact:
3049 3065 ui.status(msg % uipathfn(abs))
3050 3066 elif exact:
3051 3067 ui.warn(msg % uipathfn(abs))
3052 3068 break
3053 3069
3054 3070 if not opts.get('dry_run'):
3055 3071 needdata = ('revert', 'add', 'undelete')
3056 3072 oplist = [actions[name][0] for name in needdata]
3057 3073 prefetch = scmutil.prefetchfiles
3058 3074 matchfiles = scmutil.matchfiles
3059 3075 prefetch(repo, [ctx.rev()],
3060 3076 matchfiles(repo,
3061 3077 [f for sublist in oplist for f in sublist]))
3062 3078 match = scmutil.match(repo[None], pats)
3063 3079 _performrevert(repo, parents, ctx, names, uipathfn, actions,
3064 3080 match, interactive, tobackup)
3065 3081
3066 3082 if targetsubs:
3067 3083 # Revert the subrepos on the revert list
3068 3084 for sub in targetsubs:
3069 3085 try:
3070 3086 wctx.sub(sub).revert(ctx.substate[sub], *pats,
3071 3087 **pycompat.strkwargs(opts))
3072 3088 except KeyError:
3073 3089 raise error.Abort("subrepository '%s' does not exist in %s!"
3074 3090 % (sub, short(ctx.node())))
3075 3091
3076 3092 def _performrevert(repo, parents, ctx, names, uipathfn, actions,
3077 3093 match, interactive=False, tobackup=None):
3078 3094 """function that actually perform all the actions computed for revert
3079 3095
3080 3096 This is an independent function to let extension to plug in and react to
3081 3097 the imminent revert.
3082 3098
3083 3099 Make sure you have the working directory locked when calling this function.
3084 3100 """
3085 3101 parent, p2 = parents
3086 3102 node = ctx.node()
3087 3103 excluded_files = []
3088 3104
3089 3105 def checkout(f):
3090 3106 fc = ctx[f]
3091 3107 repo.wwrite(f, fc.data(), fc.flags())
3092 3108
3093 3109 def doremove(f):
3094 3110 try:
3095 3111 rmdir = repo.ui.configbool('experimental', 'removeemptydirs')
3096 3112 repo.wvfs.unlinkpath(f, rmdir=rmdir)
3097 3113 except OSError:
3098 3114 pass
3099 3115 repo.dirstate.remove(f)
3100 3116
3101 3117 def prntstatusmsg(action, f):
3102 3118 exact = names[f]
3103 3119 if repo.ui.verbose or not exact:
3104 3120 repo.ui.status(actions[action][1] % uipathfn(f))
3105 3121
3106 3122 audit_path = pathutil.pathauditor(repo.root, cached=True)
3107 3123 for f in actions['forget'][0]:
3108 3124 if interactive:
3109 3125 choice = repo.ui.promptchoice(
3110 3126 _("forget added file %s (Yn)?$$ &Yes $$ &No") % uipathfn(f))
3111 3127 if choice == 0:
3112 3128 prntstatusmsg('forget', f)
3113 3129 repo.dirstate.drop(f)
3114 3130 else:
3115 3131 excluded_files.append(f)
3116 3132 else:
3117 3133 prntstatusmsg('forget', f)
3118 3134 repo.dirstate.drop(f)
3119 3135 for f in actions['remove'][0]:
3120 3136 audit_path(f)
3121 3137 if interactive:
3122 3138 choice = repo.ui.promptchoice(
3123 3139 _("remove added file %s (Yn)?$$ &Yes $$ &No") % uipathfn(f))
3124 3140 if choice == 0:
3125 3141 prntstatusmsg('remove', f)
3126 3142 doremove(f)
3127 3143 else:
3128 3144 excluded_files.append(f)
3129 3145 else:
3130 3146 prntstatusmsg('remove', f)
3131 3147 doremove(f)
3132 3148 for f in actions['drop'][0]:
3133 3149 audit_path(f)
3134 3150 prntstatusmsg('drop', f)
3135 3151 repo.dirstate.remove(f)
3136 3152
3137 3153 normal = None
3138 3154 if node == parent:
3139 3155 # We're reverting to our parent. If possible, we'd like status
3140 3156 # to report the file as clean. We have to use normallookup for
3141 3157 # merges to avoid losing information about merged/dirty files.
3142 3158 if p2 != nullid:
3143 3159 normal = repo.dirstate.normallookup
3144 3160 else:
3145 3161 normal = repo.dirstate.normal
3146 3162
3147 3163 newlyaddedandmodifiedfiles = set()
3148 3164 if interactive:
3149 3165 # Prompt the user for changes to revert
3150 3166 torevert = [f for f in actions['revert'][0] if f not in excluded_files]
3151 3167 m = scmutil.matchfiles(repo, torevert)
3152 3168 diffopts = patch.difffeatureopts(repo.ui, whitespace=True,
3153 3169 section='commands',
3154 3170 configprefix='revert.interactive.')
3155 3171 diffopts.nodates = True
3156 3172 diffopts.git = True
3157 3173 operation = 'apply'
3158 3174 if node == parent:
3159 3175 if repo.ui.configbool('experimental',
3160 3176 'revert.interactive.select-to-keep'):
3161 3177 operation = 'keep'
3162 3178 else:
3163 3179 operation = 'discard'
3164 3180
3165 3181 if operation == 'apply':
3166 3182 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
3167 3183 else:
3168 3184 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
3169 3185 originalchunks = patch.parsepatch(diff)
3170 3186
3171 3187 try:
3172 3188
3173 3189 chunks, opts = recordfilter(repo.ui, originalchunks, match,
3174 3190 operation=operation)
3175 3191 if operation == 'discard':
3176 3192 chunks = patch.reversehunks(chunks)
3177 3193
3178 3194 except error.PatchError as err:
3179 3195 raise error.Abort(_('error parsing patch: %s') % err)
3180 3196
3181 3197 # FIXME: when doing an interactive revert of a copy, there's no way of
3182 3198 # performing a partial revert of the added file, the only option is
3183 3199 # "remove added file <name> (Yn)?", so we don't need to worry about the
3184 3200 # alsorestore value. Ideally we'd be able to partially revert
3185 3201 # copied/renamed files.
3186 3202 newlyaddedandmodifiedfiles, unusedalsorestore = newandmodified(
3187 3203 chunks, originalchunks)
3188 3204 if tobackup is None:
3189 3205 tobackup = set()
3190 3206 # Apply changes
3191 3207 fp = stringio()
3192 3208 # chunks are serialized per file, but files aren't sorted
3193 3209 for f in sorted(set(c.header.filename() for c in chunks if ishunk(c))):
3194 3210 prntstatusmsg('revert', f)
3195 3211 files = set()
3196 3212 for c in chunks:
3197 3213 if ishunk(c):
3198 3214 abs = c.header.filename()
3199 3215 # Create a backup file only if this hunk should be backed up
3200 3216 if c.header.filename() in tobackup:
3201 3217 target = repo.wjoin(abs)
3202 3218 bakname = scmutil.backuppath(repo.ui, repo, abs)
3203 3219 util.copyfile(target, bakname)
3204 3220 tobackup.remove(abs)
3205 3221 if abs not in files:
3206 3222 files.add(abs)
3207 3223 if operation == 'keep':
3208 3224 checkout(abs)
3209 3225 c.write(fp)
3210 3226 dopatch = fp.tell()
3211 3227 fp.seek(0)
3212 3228 if dopatch:
3213 3229 try:
3214 3230 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3215 3231 except error.PatchError as err:
3216 3232 raise error.Abort(pycompat.bytestr(err))
3217 3233 del fp
3218 3234 else:
3219 3235 for f in actions['revert'][0]:
3220 3236 prntstatusmsg('revert', f)
3221 3237 checkout(f)
3222 3238 if normal:
3223 3239 normal(f)
3224 3240
3225 3241 for f in actions['add'][0]:
3226 3242 # Don't checkout modified files, they are already created by the diff
3227 3243 if f not in newlyaddedandmodifiedfiles:
3228 3244 prntstatusmsg('add', f)
3229 3245 checkout(f)
3230 3246 repo.dirstate.add(f)
3231 3247
3232 3248 normal = repo.dirstate.normallookup
3233 3249 if node == parent and p2 == nullid:
3234 3250 normal = repo.dirstate.normal
3235 3251 for f in actions['undelete'][0]:
3236 3252 if interactive:
3237 3253 choice = repo.ui.promptchoice(
3238 3254 _("add back removed file %s (Yn)?$$ &Yes $$ &No") % f)
3239 3255 if choice == 0:
3240 3256 prntstatusmsg('undelete', f)
3241 3257 checkout(f)
3242 3258 normal(f)
3243 3259 else:
3244 3260 excluded_files.append(f)
3245 3261 else:
3246 3262 prntstatusmsg('undelete', f)
3247 3263 checkout(f)
3248 3264 normal(f)
3249 3265
3250 3266 copied = copies.pathcopies(repo[parent], ctx)
3251 3267
3252 3268 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
3253 3269 if f in copied:
3254 3270 repo.dirstate.copy(copied[f], f)
3255 3271
3256 3272 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3257 3273 # commands.outgoing. "missing" is "missing" of the result of
3258 3274 # "findcommonoutgoing()"
3259 3275 outgoinghooks = util.hooks()
3260 3276
3261 3277 # a list of (ui, repo) functions called by commands.summary
3262 3278 summaryhooks = util.hooks()
3263 3279
3264 3280 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3265 3281 #
3266 3282 # functions should return tuple of booleans below, if 'changes' is None:
3267 3283 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3268 3284 #
3269 3285 # otherwise, 'changes' is a tuple of tuples below:
3270 3286 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3271 3287 # - (desturl, destbranch, destpeer, outgoing)
3272 3288 summaryremotehooks = util.hooks()
3273 3289
3274 3290
3275 3291 def checkunfinished(repo, commit=False, skipmerge=False):
3276 3292 '''Look for an unfinished multistep operation, like graft, and abort
3277 3293 if found. It's probably good to check this right before
3278 3294 bailifchanged().
3279 3295 '''
3280 3296 # Check for non-clearable states first, so things like rebase will take
3281 3297 # precedence over update.
3282 3298 for state in statemod._unfinishedstates:
3283 3299 if (state._clearable or (commit and state._allowcommit) or
3284 3300 state._reportonly):
3285 3301 continue
3286 3302 if state.isunfinished(repo):
3287 3303 raise error.Abort(state.msg(), hint=state.hint())
3288 3304
3289 3305 for s in statemod._unfinishedstates:
3290 3306 if (not s._clearable or (commit and s._allowcommit) or
3291 3307 (s._opname == 'merge' and skipmerge) or s._reportonly):
3292 3308 continue
3293 3309 if s.isunfinished(repo):
3294 3310 raise error.Abort(s.msg(), hint=s.hint())
3295 3311
3296 3312 def clearunfinished(repo):
3297 3313 '''Check for unfinished operations (as above), and clear the ones
3298 3314 that are clearable.
3299 3315 '''
3300 3316 for state in statemod._unfinishedstates:
3301 3317 if state._reportonly:
3302 3318 continue
3303 3319 if not state._clearable and state.isunfinished(repo):
3304 3320 raise error.Abort(state.msg(), hint=state.hint())
3305 3321
3306 3322 for s in statemod._unfinishedstates:
3307 3323 if s._opname == 'merge' or state._reportonly:
3308 3324 continue
3309 3325 if s._clearable and s.isunfinished(repo):
3310 3326 util.unlink(repo.vfs.join(s._fname))
3311 3327
3312 3328 def getunfinishedstate(repo):
3313 3329 ''' Checks for unfinished operations and returns statecheck object
3314 3330 for it'''
3315 3331 for state in statemod._unfinishedstates:
3316 3332 if state.isunfinished(repo):
3317 3333 return state
3318 3334 return None
3319 3335
3320 3336 def howtocontinue(repo):
3321 3337 '''Check for an unfinished operation and return the command to finish
3322 3338 it.
3323 3339
3324 3340 statemod._unfinishedstates list is checked for an unfinished operation
3325 3341 and the corresponding message to finish it is generated if a method to
3326 3342 continue is supported by the operation.
3327 3343
3328 3344 Returns a (msg, warning) tuple. 'msg' is a string and 'warning' is
3329 3345 a boolean.
3330 3346 '''
3331 3347 contmsg = _("continue: %s")
3332 3348 for state in statemod._unfinishedstates:
3333 3349 if not state._continueflag:
3334 3350 continue
3335 3351 if state.isunfinished(repo):
3336 3352 return contmsg % state.continuemsg(), True
3337 3353 if repo[None].dirty(missing=True, merge=False, branch=False):
3338 3354 return contmsg % _("hg commit"), False
3339 3355 return None, None
3340 3356
3341 3357 def checkafterresolved(repo):
3342 3358 '''Inform the user about the next action after completing hg resolve
3343 3359
3344 3360 If there's a an unfinished operation that supports continue flag,
3345 3361 howtocontinue will yield repo.ui.warn as the reporter.
3346 3362
3347 3363 Otherwise, it will yield repo.ui.note.
3348 3364 '''
3349 3365 msg, warning = howtocontinue(repo)
3350 3366 if msg is not None:
3351 3367 if warning:
3352 3368 repo.ui.warn("%s\n" % msg)
3353 3369 else:
3354 3370 repo.ui.note("%s\n" % msg)
3355 3371
3356 3372 def wrongtooltocontinue(repo, task):
3357 3373 '''Raise an abort suggesting how to properly continue if there is an
3358 3374 active task.
3359 3375
3360 3376 Uses howtocontinue() to find the active task.
3361 3377
3362 3378 If there's no task (repo.ui.note for 'hg commit'), it does not offer
3363 3379 a hint.
3364 3380 '''
3365 3381 after = howtocontinue(repo)
3366 3382 hint = None
3367 3383 if after[1]:
3368 3384 hint = after[0]
3369 3385 raise error.Abort(_('no %s in progress') % task, hint=hint)
3370 3386
3371 3387 def abortgraft(ui, repo, graftstate):
3372 3388 """abort the interrupted graft and rollbacks to the state before interrupted
3373 3389 graft"""
3374 3390 if not graftstate.exists():
3375 3391 raise error.Abort(_("no interrupted graft to abort"))
3376 3392 statedata = readgraftstate(repo, graftstate)
3377 3393 newnodes = statedata.get('newnodes')
3378 3394 if newnodes is None:
3379 3395 # and old graft state which does not have all the data required to abort
3380 3396 # the graft
3381 3397 raise error.Abort(_("cannot abort using an old graftstate"))
3382 3398
3383 3399 # changeset from which graft operation was started
3384 3400 if len(newnodes) > 0:
3385 3401 startctx = repo[newnodes[0]].p1()
3386 3402 else:
3387 3403 startctx = repo['.']
3388 3404 # whether to strip or not
3389 3405 cleanup = False
3390 3406 from . import hg
3391 3407 if newnodes:
3392 3408 newnodes = [repo[r].rev() for r in newnodes]
3393 3409 cleanup = True
3394 3410 # checking that none of the newnodes turned public or is public
3395 3411 immutable = [c for c in newnodes if not repo[c].mutable()]
3396 3412 if immutable:
3397 3413 repo.ui.warn(_("cannot clean up public changesets %s\n")
3398 3414 % ', '.join(bytes(repo[r]) for r in immutable),
3399 3415 hint=_("see 'hg help phases' for details"))
3400 3416 cleanup = False
3401 3417
3402 3418 # checking that no new nodes are created on top of grafted revs
3403 3419 desc = set(repo.changelog.descendants(newnodes))
3404 3420 if desc - set(newnodes):
3405 3421 repo.ui.warn(_("new changesets detected on destination "
3406 3422 "branch, can't strip\n"))
3407 3423 cleanup = False
3408 3424
3409 3425 if cleanup:
3410 3426 with repo.wlock(), repo.lock():
3411 3427 hg.updaterepo(repo, startctx.node(), overwrite=True)
3412 3428 # stripping the new nodes created
3413 3429 strippoints = [c.node() for c in repo.set("roots(%ld)",
3414 3430 newnodes)]
3415 3431 repair.strip(repo.ui, repo, strippoints, backup=False)
3416 3432
3417 3433 if not cleanup:
3418 3434 # we don't update to the startnode if we can't strip
3419 3435 startctx = repo['.']
3420 3436 hg.updaterepo(repo, startctx.node(), overwrite=True)
3421 3437
3422 3438 ui.status(_("graft aborted\n"))
3423 3439 ui.status(_("working directory is now at %s\n") % startctx.hex()[:12])
3424 3440 graftstate.delete()
3425 3441 return 0
3426 3442
3427 3443 def readgraftstate(repo, graftstate):
3428 3444 """read the graft state file and return a dict of the data stored in it"""
3429 3445 try:
3430 3446 return graftstate.read()
3431 3447 except error.CorruptedState:
3432 3448 nodes = repo.vfs.read('graftstate').splitlines()
3433 3449 return {'nodes': nodes}
3434 3450
3435 3451 def hgabortgraft(ui, repo):
3436 3452 """ abort logic for aborting graft using 'hg abort'"""
3437 3453 with repo.wlock():
3438 3454 graftstate = statemod.cmdstate(repo, 'graftstate')
3439 3455 return abortgraft(ui, repo, graftstate)
@@ -1,6450 +1,6451 b''
1 1 # commands.py - command processing for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import difflib
11 11 import errno
12 12 import os
13 13 import re
14 14 import sys
15 15
16 16 from .i18n import _
17 17 from .node import (
18 18 hex,
19 19 nullid,
20 20 nullrev,
21 21 short,
22 22 wdirhex,
23 23 wdirrev,
24 24 )
25 25 from . import (
26 26 archival,
27 27 bookmarks,
28 28 bundle2,
29 29 changegroup,
30 30 cmdutil,
31 31 copies,
32 32 debugcommands as debugcommandsmod,
33 33 destutil,
34 34 dirstateguard,
35 35 discovery,
36 36 encoding,
37 37 error,
38 38 exchange,
39 39 extensions,
40 40 filemerge,
41 41 formatter,
42 42 graphmod,
43 43 hbisect,
44 44 help,
45 45 hg,
46 46 logcmdutil,
47 47 merge as mergemod,
48 48 narrowspec,
49 49 obsolete,
50 50 obsutil,
51 51 patch,
52 52 phases,
53 53 pycompat,
54 54 rcutil,
55 55 registrar,
56 56 revsetlang,
57 57 rewriteutil,
58 58 scmutil,
59 59 server,
60 60 shelve as shelvemod,
61 61 state as statemod,
62 62 streamclone,
63 63 tags as tagsmod,
64 64 ui as uimod,
65 65 util,
66 66 verify as verifymod,
67 67 wireprotoserver,
68 68 )
69 69 from .utils import (
70 70 dateutil,
71 71 stringutil,
72 72 )
73 73
74 74 table = {}
75 75 table.update(debugcommandsmod.command._table)
76 76
77 77 command = registrar.command(table)
78 78 INTENT_READONLY = registrar.INTENT_READONLY
79 79
80 80 # common command options
81 81
82 82 globalopts = [
83 83 ('R', 'repository', '',
84 84 _('repository root directory or name of overlay bundle file'),
85 85 _('REPO')),
86 86 ('', 'cwd', '',
87 87 _('change working directory'), _('DIR')),
88 88 ('y', 'noninteractive', None,
89 89 _('do not prompt, automatically pick the first choice for all prompts')),
90 90 ('q', 'quiet', None, _('suppress output')),
91 91 ('v', 'verbose', None, _('enable additional output')),
92 92 ('', 'color', '',
93 93 # i18n: 'always', 'auto', 'never', and 'debug' are keywords
94 94 # and should not be translated
95 95 _("when to colorize (boolean, always, auto, never, or debug)"),
96 96 _('TYPE')),
97 97 ('', 'config', [],
98 98 _('set/override config option (use \'section.name=value\')'),
99 99 _('CONFIG')),
100 100 ('', 'debug', None, _('enable debugging output')),
101 101 ('', 'debugger', None, _('start debugger')),
102 102 ('', 'encoding', encoding.encoding, _('set the charset encoding'),
103 103 _('ENCODE')),
104 104 ('', 'encodingmode', encoding.encodingmode,
105 105 _('set the charset encoding mode'), _('MODE')),
106 106 ('', 'traceback', None, _('always print a traceback on exception')),
107 107 ('', 'time', None, _('time how long the command takes')),
108 108 ('', 'profile', None, _('print command execution profile')),
109 109 ('', 'version', None, _('output version information and exit')),
110 110 ('h', 'help', None, _('display help and exit')),
111 111 ('', 'hidden', False, _('consider hidden changesets')),
112 112 ('', 'pager', 'auto',
113 113 _("when to paginate (boolean, always, auto, or never)"), _('TYPE')),
114 114 ]
115 115
116 116 dryrunopts = cmdutil.dryrunopts
117 117 remoteopts = cmdutil.remoteopts
118 118 walkopts = cmdutil.walkopts
119 119 commitopts = cmdutil.commitopts
120 120 commitopts2 = cmdutil.commitopts2
121 commitopts3 = cmdutil.commitopts3
121 122 formatteropts = cmdutil.formatteropts
122 123 templateopts = cmdutil.templateopts
123 124 logopts = cmdutil.logopts
124 125 diffopts = cmdutil.diffopts
125 126 diffwsopts = cmdutil.diffwsopts
126 127 diffopts2 = cmdutil.diffopts2
127 128 mergetoolopts = cmdutil.mergetoolopts
128 129 similarityopts = cmdutil.similarityopts
129 130 subrepoopts = cmdutil.subrepoopts
130 131 debugrevlogopts = cmdutil.debugrevlogopts
131 132
132 133 # Commands start here, listed alphabetically
133 134
134 135 @command('abort',
135 136 dryrunopts, helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
136 137 helpbasic=True)
137 138 def abort(ui, repo, **opts):
138 139 """abort an unfinished operation (EXPERIMENTAL)
139 140
140 141 Aborts a multistep operation like graft, histedit, rebase, merge,
141 142 and unshelve if they are in an unfinished state.
142 143
143 144 use --dry-run/-n to dry run the command.
144 145 """
145 146 dryrun = opts.get(r'dry_run')
146 147 abortstate = cmdutil.getunfinishedstate(repo)
147 148 if not abortstate:
148 149 raise error.Abort(_('no operation in progress'))
149 150 if not abortstate.abortfunc:
150 151 raise error.Abort((_("%s in progress but does not support 'hg abort'") %
151 152 (abortstate._opname)), hint=abortstate.hint())
152 153 if dryrun:
153 154 ui.status(_('%s in progress, will be aborted\n') % (abortstate._opname))
154 155 return
155 156 return abortstate.abortfunc(ui, repo)
156 157
157 158 @command('add',
158 159 walkopts + subrepoopts + dryrunopts,
159 160 _('[OPTION]... [FILE]...'),
160 161 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
161 162 helpbasic=True, inferrepo=True)
162 163 def add(ui, repo, *pats, **opts):
163 164 """add the specified files on the next commit
164 165
165 166 Schedule files to be version controlled and added to the
166 167 repository.
167 168
168 169 The files will be added to the repository at the next commit. To
169 170 undo an add before that, see :hg:`forget`.
170 171
171 172 If no names are given, add all files to the repository (except
172 173 files matching ``.hgignore``).
173 174
174 175 .. container:: verbose
175 176
176 177 Examples:
177 178
178 179 - New (unknown) files are added
179 180 automatically by :hg:`add`::
180 181
181 182 $ ls
182 183 foo.c
183 184 $ hg status
184 185 ? foo.c
185 186 $ hg add
186 187 adding foo.c
187 188 $ hg status
188 189 A foo.c
189 190
190 191 - Specific files to be added can be specified::
191 192
192 193 $ ls
193 194 bar.c foo.c
194 195 $ hg status
195 196 ? bar.c
196 197 ? foo.c
197 198 $ hg add bar.c
198 199 $ hg status
199 200 A bar.c
200 201 ? foo.c
201 202
202 203 Returns 0 if all files are successfully added.
203 204 """
204 205
205 206 m = scmutil.match(repo[None], pats, pycompat.byteskwargs(opts))
206 207 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
207 208 rejected = cmdutil.add(ui, repo, m, "", uipathfn, False, **opts)
208 209 return rejected and 1 or 0
209 210
210 211 @command('addremove',
211 212 similarityopts + subrepoopts + walkopts + dryrunopts,
212 213 _('[OPTION]... [FILE]...'),
213 214 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
214 215 inferrepo=True)
215 216 def addremove(ui, repo, *pats, **opts):
216 217 """add all new files, delete all missing files
217 218
218 219 Add all new files and remove all missing files from the
219 220 repository.
220 221
221 222 Unless names are given, new files are ignored if they match any of
222 223 the patterns in ``.hgignore``. As with add, these changes take
223 224 effect at the next commit.
224 225
225 226 Use the -s/--similarity option to detect renamed files. This
226 227 option takes a percentage between 0 (disabled) and 100 (files must
227 228 be identical) as its parameter. With a parameter greater than 0,
228 229 this compares every removed file with every added file and records
229 230 those similar enough as renames. Detecting renamed files this way
230 231 can be expensive. After using this option, :hg:`status -C` can be
231 232 used to check which files were identified as moved or renamed. If
232 233 not specified, -s/--similarity defaults to 100 and only renames of
233 234 identical files are detected.
234 235
235 236 .. container:: verbose
236 237
237 238 Examples:
238 239
239 240 - A number of files (bar.c and foo.c) are new,
240 241 while foobar.c has been removed (without using :hg:`remove`)
241 242 from the repository::
242 243
243 244 $ ls
244 245 bar.c foo.c
245 246 $ hg status
246 247 ! foobar.c
247 248 ? bar.c
248 249 ? foo.c
249 250 $ hg addremove
250 251 adding bar.c
251 252 adding foo.c
252 253 removing foobar.c
253 254 $ hg status
254 255 A bar.c
255 256 A foo.c
256 257 R foobar.c
257 258
258 259 - A file foobar.c was moved to foo.c without using :hg:`rename`.
259 260 Afterwards, it was edited slightly::
260 261
261 262 $ ls
262 263 foo.c
263 264 $ hg status
264 265 ! foobar.c
265 266 ? foo.c
266 267 $ hg addremove --similarity 90
267 268 removing foobar.c
268 269 adding foo.c
269 270 recording removal of foobar.c as rename to foo.c (94% similar)
270 271 $ hg status -C
271 272 A foo.c
272 273 foobar.c
273 274 R foobar.c
274 275
275 276 Returns 0 if all files are successfully added.
276 277 """
277 278 opts = pycompat.byteskwargs(opts)
278 279 if not opts.get('similarity'):
279 280 opts['similarity'] = '100'
280 281 matcher = scmutil.match(repo[None], pats, opts)
281 282 relative = scmutil.anypats(pats, opts)
282 283 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
283 284 return scmutil.addremove(repo, matcher, "", uipathfn, opts)
284 285
285 286 @command('annotate|blame',
286 287 [('r', 'rev', '', _('annotate the specified revision'), _('REV')),
287 288 ('', 'follow', None,
288 289 _('follow copies/renames and list the filename (DEPRECATED)')),
289 290 ('', 'no-follow', None, _("don't follow copies and renames")),
290 291 ('a', 'text', None, _('treat all files as text')),
291 292 ('u', 'user', None, _('list the author (long with -v)')),
292 293 ('f', 'file', None, _('list the filename')),
293 294 ('d', 'date', None, _('list the date (short with -q)')),
294 295 ('n', 'number', None, _('list the revision number (default)')),
295 296 ('c', 'changeset', None, _('list the changeset')),
296 297 ('l', 'line-number', None, _('show line number at the first appearance')),
297 298 ('', 'skip', [], _('revision to not display (EXPERIMENTAL)'), _('REV')),
298 299 ] + diffwsopts + walkopts + formatteropts,
299 300 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'),
300 301 helpcategory=command.CATEGORY_FILE_CONTENTS,
301 302 helpbasic=True, inferrepo=True)
302 303 def annotate(ui, repo, *pats, **opts):
303 304 """show changeset information by line for each file
304 305
305 306 List changes in files, showing the revision id responsible for
306 307 each line.
307 308
308 309 This command is useful for discovering when a change was made and
309 310 by whom.
310 311
311 312 If you include --file, --user, or --date, the revision number is
312 313 suppressed unless you also include --number.
313 314
314 315 Without the -a/--text option, annotate will avoid processing files
315 316 it detects as binary. With -a, annotate will annotate the file
316 317 anyway, although the results will probably be neither useful
317 318 nor desirable.
318 319
319 320 .. container:: verbose
320 321
321 322 Template:
322 323
323 324 The following keywords are supported in addition to the common template
324 325 keywords and functions. See also :hg:`help templates`.
325 326
326 327 :lines: List of lines with annotation data.
327 328 :path: String. Repository-absolute path of the specified file.
328 329
329 330 And each entry of ``{lines}`` provides the following sub-keywords in
330 331 addition to ``{date}``, ``{node}``, ``{rev}``, ``{user}``, etc.
331 332
332 333 :line: String. Line content.
333 334 :lineno: Integer. Line number at that revision.
334 335 :path: String. Repository-absolute path of the file at that revision.
335 336
336 337 See :hg:`help templates.operators` for the list expansion syntax.
337 338
338 339 Returns 0 on success.
339 340 """
340 341 opts = pycompat.byteskwargs(opts)
341 342 if not pats:
342 343 raise error.Abort(_('at least one filename or pattern is required'))
343 344
344 345 if opts.get('follow'):
345 346 # --follow is deprecated and now just an alias for -f/--file
346 347 # to mimic the behavior of Mercurial before version 1.5
347 348 opts['file'] = True
348 349
349 350 if (not opts.get('user') and not opts.get('changeset')
350 351 and not opts.get('date') and not opts.get('file')):
351 352 opts['number'] = True
352 353
353 354 linenumber = opts.get('line_number') is not None
354 355 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
355 356 raise error.Abort(_('at least one of -n/-c is required for -l'))
356 357
357 358 rev = opts.get('rev')
358 359 if rev:
359 360 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
360 361 ctx = scmutil.revsingle(repo, rev)
361 362
362 363 ui.pager('annotate')
363 364 rootfm = ui.formatter('annotate', opts)
364 365 if ui.debugflag:
365 366 shorthex = pycompat.identity
366 367 else:
367 368 def shorthex(h):
368 369 return h[:12]
369 370 if ui.quiet:
370 371 datefunc = dateutil.shortdate
371 372 else:
372 373 datefunc = dateutil.datestr
373 374 if ctx.rev() is None:
374 375 if opts.get('changeset'):
375 376 # omit "+" suffix which is appended to node hex
376 377 def formatrev(rev):
377 378 if rev == wdirrev:
378 379 return '%d' % ctx.p1().rev()
379 380 else:
380 381 return '%d' % rev
381 382 else:
382 383 def formatrev(rev):
383 384 if rev == wdirrev:
384 385 return '%d+' % ctx.p1().rev()
385 386 else:
386 387 return '%d ' % rev
387 388 def formathex(h):
388 389 if h == wdirhex:
389 390 return '%s+' % shorthex(hex(ctx.p1().node()))
390 391 else:
391 392 return '%s ' % shorthex(h)
392 393 else:
393 394 formatrev = b'%d'.__mod__
394 395 formathex = shorthex
395 396
396 397 opmap = [
397 398 ('user', ' ', lambda x: x.fctx.user(), ui.shortuser),
398 399 ('rev', ' ', lambda x: scmutil.intrev(x.fctx), formatrev),
399 400 ('node', ' ', lambda x: hex(scmutil.binnode(x.fctx)), formathex),
400 401 ('date', ' ', lambda x: x.fctx.date(), util.cachefunc(datefunc)),
401 402 ('path', ' ', lambda x: x.fctx.path(), pycompat.bytestr),
402 403 ('lineno', ':', lambda x: x.lineno, pycompat.bytestr),
403 404 ]
404 405 opnamemap = {
405 406 'rev': 'number',
406 407 'node': 'changeset',
407 408 'path': 'file',
408 409 'lineno': 'line_number',
409 410 }
410 411
411 412 if rootfm.isplain():
412 413 def makefunc(get, fmt):
413 414 return lambda x: fmt(get(x))
414 415 else:
415 416 def makefunc(get, fmt):
416 417 return get
417 418 datahint = rootfm.datahint()
418 419 funcmap = [(makefunc(get, fmt), sep) for fn, sep, get, fmt in opmap
419 420 if opts.get(opnamemap.get(fn, fn)) or fn in datahint]
420 421 funcmap[0] = (funcmap[0][0], '') # no separator in front of first column
421 422 fields = ' '.join(fn for fn, sep, get, fmt in opmap
422 423 if opts.get(opnamemap.get(fn, fn)) or fn in datahint)
423 424
424 425 def bad(x, y):
425 426 raise error.Abort("%s: %s" % (x, y))
426 427
427 428 m = scmutil.match(ctx, pats, opts, badfn=bad)
428 429
429 430 follow = not opts.get('no_follow')
430 431 diffopts = patch.difffeatureopts(ui, opts, section='annotate',
431 432 whitespace=True)
432 433 skiprevs = opts.get('skip')
433 434 if skiprevs:
434 435 skiprevs = scmutil.revrange(repo, skiprevs)
435 436
436 437 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
437 438 for abs in ctx.walk(m):
438 439 fctx = ctx[abs]
439 440 rootfm.startitem()
440 441 rootfm.data(path=abs)
441 442 if not opts.get('text') and fctx.isbinary():
442 443 rootfm.plain(_("%s: binary file\n") % uipathfn(abs))
443 444 continue
444 445
445 446 fm = rootfm.nested('lines', tmpl='{rev}: {line}')
446 447 lines = fctx.annotate(follow=follow, skiprevs=skiprevs,
447 448 diffopts=diffopts)
448 449 if not lines:
449 450 fm.end()
450 451 continue
451 452 formats = []
452 453 pieces = []
453 454
454 455 for f, sep in funcmap:
455 456 l = [f(n) for n in lines]
456 457 if fm.isplain():
457 458 sizes = [encoding.colwidth(x) for x in l]
458 459 ml = max(sizes)
459 460 formats.append([sep + ' ' * (ml - w) + '%s' for w in sizes])
460 461 else:
461 462 formats.append(['%s' for x in l])
462 463 pieces.append(l)
463 464
464 465 for f, p, n in zip(zip(*formats), zip(*pieces), lines):
465 466 fm.startitem()
466 467 fm.context(fctx=n.fctx)
467 468 fm.write(fields, "".join(f), *p)
468 469 if n.skip:
469 470 fmt = "* %s"
470 471 else:
471 472 fmt = ": %s"
472 473 fm.write('line', fmt, n.text)
473 474
474 475 if not lines[-1].text.endswith('\n'):
475 476 fm.plain('\n')
476 477 fm.end()
477 478
478 479 rootfm.end()
479 480
480 481 @command('archive',
481 482 [('', 'no-decode', None, _('do not pass files through decoders')),
482 483 ('p', 'prefix', '', _('directory prefix for files in archive'),
483 484 _('PREFIX')),
484 485 ('r', 'rev', '', _('revision to distribute'), _('REV')),
485 486 ('t', 'type', '', _('type of distribution to create'), _('TYPE')),
486 487 ] + subrepoopts + walkopts,
487 488 _('[OPTION]... DEST'),
488 489 helpcategory=command.CATEGORY_IMPORT_EXPORT)
489 490 def archive(ui, repo, dest, **opts):
490 491 '''create an unversioned archive of a repository revision
491 492
492 493 By default, the revision used is the parent of the working
493 494 directory; use -r/--rev to specify a different revision.
494 495
495 496 The archive type is automatically detected based on file
496 497 extension (to override, use -t/--type).
497 498
498 499 .. container:: verbose
499 500
500 501 Examples:
501 502
502 503 - create a zip file containing the 1.0 release::
503 504
504 505 hg archive -r 1.0 project-1.0.zip
505 506
506 507 - create a tarball excluding .hg files::
507 508
508 509 hg archive project.tar.gz -X ".hg*"
509 510
510 511 Valid types are:
511 512
512 513 :``files``: a directory full of files (default)
513 514 :``tar``: tar archive, uncompressed
514 515 :``tbz2``: tar archive, compressed using bzip2
515 516 :``tgz``: tar archive, compressed using gzip
516 517 :``uzip``: zip archive, uncompressed
517 518 :``zip``: zip archive, compressed using deflate
518 519
519 520 The exact name of the destination archive or directory is given
520 521 using a format string; see :hg:`help export` for details.
521 522
522 523 Each member added to an archive file has a directory prefix
523 524 prepended. Use -p/--prefix to specify a format string for the
524 525 prefix. The default is the basename of the archive, with suffixes
525 526 removed.
526 527
527 528 Returns 0 on success.
528 529 '''
529 530
530 531 opts = pycompat.byteskwargs(opts)
531 532 rev = opts.get('rev')
532 533 if rev:
533 534 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
534 535 ctx = scmutil.revsingle(repo, rev)
535 536 if not ctx:
536 537 raise error.Abort(_('no working directory: please specify a revision'))
537 538 node = ctx.node()
538 539 dest = cmdutil.makefilename(ctx, dest)
539 540 if os.path.realpath(dest) == repo.root:
540 541 raise error.Abort(_('repository root cannot be destination'))
541 542
542 543 kind = opts.get('type') or archival.guesskind(dest) or 'files'
543 544 prefix = opts.get('prefix')
544 545
545 546 if dest == '-':
546 547 if kind == 'files':
547 548 raise error.Abort(_('cannot archive plain files to stdout'))
548 549 dest = cmdutil.makefileobj(ctx, dest)
549 550 if not prefix:
550 551 prefix = os.path.basename(repo.root) + '-%h'
551 552
552 553 prefix = cmdutil.makefilename(ctx, prefix)
553 554 match = scmutil.match(ctx, [], opts)
554 555 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
555 556 match, prefix, subrepos=opts.get('subrepos'))
556 557
557 558 @command('backout',
558 559 [('', 'merge', None, _('merge with old dirstate parent after backout')),
559 560 ('', 'commit', None,
560 561 _('commit if no conflicts were encountered (DEPRECATED)')),
561 562 ('', 'no-commit', None, _('do not commit')),
562 563 ('', 'parent', '',
563 564 _('parent to choose when backing out merge (DEPRECATED)'), _('REV')),
564 565 ('r', 'rev', '', _('revision to backout'), _('REV')),
565 566 ('e', 'edit', False, _('invoke editor on commit messages')),
566 567 ] + mergetoolopts + walkopts + commitopts + commitopts2,
567 568 _('[OPTION]... [-r] REV'),
568 569 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT)
569 570 def backout(ui, repo, node=None, rev=None, **opts):
570 571 '''reverse effect of earlier changeset
571 572
572 573 Prepare a new changeset with the effect of REV undone in the
573 574 current working directory. If no conflicts were encountered,
574 575 it will be committed immediately.
575 576
576 577 If REV is the parent of the working directory, then this new changeset
577 578 is committed automatically (unless --no-commit is specified).
578 579
579 580 .. note::
580 581
581 582 :hg:`backout` cannot be used to fix either an unwanted or
582 583 incorrect merge.
583 584
584 585 .. container:: verbose
585 586
586 587 Examples:
587 588
588 589 - Reverse the effect of the parent of the working directory.
589 590 This backout will be committed immediately::
590 591
591 592 hg backout -r .
592 593
593 594 - Reverse the effect of previous bad revision 23::
594 595
595 596 hg backout -r 23
596 597
597 598 - Reverse the effect of previous bad revision 23 and
598 599 leave changes uncommitted::
599 600
600 601 hg backout -r 23 --no-commit
601 602 hg commit -m "Backout revision 23"
602 603
603 604 By default, the pending changeset will have one parent,
604 605 maintaining a linear history. With --merge, the pending
605 606 changeset will instead have two parents: the old parent of the
606 607 working directory and a new child of REV that simply undoes REV.
607 608
608 609 Before version 1.7, the behavior without --merge was equivalent
609 610 to specifying --merge followed by :hg:`update --clean .` to
610 611 cancel the merge and leave the child of REV as a head to be
611 612 merged separately.
612 613
613 614 See :hg:`help dates` for a list of formats valid for -d/--date.
614 615
615 616 See :hg:`help revert` for a way to restore files to the state
616 617 of another revision.
617 618
618 619 Returns 0 on success, 1 if nothing to backout or there are unresolved
619 620 files.
620 621 '''
621 622 with repo.wlock(), repo.lock():
622 623 return _dobackout(ui, repo, node, rev, **opts)
623 624
624 625 def _dobackout(ui, repo, node=None, rev=None, **opts):
625 626 opts = pycompat.byteskwargs(opts)
626 627 if opts.get('commit') and opts.get('no_commit'):
627 628 raise error.Abort(_("cannot use --commit with --no-commit"))
628 629 if opts.get('merge') and opts.get('no_commit'):
629 630 raise error.Abort(_("cannot use --merge with --no-commit"))
630 631
631 632 if rev and node:
632 633 raise error.Abort(_("please specify just one revision"))
633 634
634 635 if not rev:
635 636 rev = node
636 637
637 638 if not rev:
638 639 raise error.Abort(_("please specify a revision to backout"))
639 640
640 641 date = opts.get('date')
641 642 if date:
642 643 opts['date'] = dateutil.parsedate(date)
643 644
644 645 cmdutil.checkunfinished(repo)
645 646 cmdutil.bailifchanged(repo)
646 647 node = scmutil.revsingle(repo, rev).node()
647 648
648 649 op1, op2 = repo.dirstate.parents()
649 650 if not repo.changelog.isancestor(node, op1):
650 651 raise error.Abort(_('cannot backout change that is not an ancestor'))
651 652
652 653 p1, p2 = repo.changelog.parents(node)
653 654 if p1 == nullid:
654 655 raise error.Abort(_('cannot backout a change with no parents'))
655 656 if p2 != nullid:
656 657 if not opts.get('parent'):
657 658 raise error.Abort(_('cannot backout a merge changeset'))
658 659 p = repo.lookup(opts['parent'])
659 660 if p not in (p1, p2):
660 661 raise error.Abort(_('%s is not a parent of %s') %
661 662 (short(p), short(node)))
662 663 parent = p
663 664 else:
664 665 if opts.get('parent'):
665 666 raise error.Abort(_('cannot use --parent on non-merge changeset'))
666 667 parent = p1
667 668
668 669 # the backout should appear on the same branch
669 670 branch = repo.dirstate.branch()
670 671 bheads = repo.branchheads(branch)
671 672 rctx = scmutil.revsingle(repo, hex(parent))
672 673 if not opts.get('merge') and op1 != node:
673 674 with dirstateguard.dirstateguard(repo, 'backout'):
674 675 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
675 676 with ui.configoverride(overrides, 'backout'):
676 677 stats = mergemod.update(repo, parent, branchmerge=True,
677 678 force=True, ancestor=node,
678 679 mergeancestor=False)
679 680 repo.setparents(op1, op2)
680 681 hg._showstats(repo, stats)
681 682 if stats.unresolvedcount:
682 683 repo.ui.status(_("use 'hg resolve' to retry unresolved "
683 684 "file merges\n"))
684 685 return 1
685 686 else:
686 687 hg.clean(repo, node, show_stats=False)
687 688 repo.dirstate.setbranch(branch)
688 689 cmdutil.revert(ui, repo, rctx, repo.dirstate.parents())
689 690
690 691 if opts.get('no_commit'):
691 692 msg = _("changeset %s backed out, "
692 693 "don't forget to commit.\n")
693 694 ui.status(msg % short(node))
694 695 return 0
695 696
696 697 def commitfunc(ui, repo, message, match, opts):
697 698 editform = 'backout'
698 699 e = cmdutil.getcommiteditor(editform=editform,
699 700 **pycompat.strkwargs(opts))
700 701 if not message:
701 702 # we don't translate commit messages
702 703 message = "Backed out changeset %s" % short(node)
703 704 e = cmdutil.getcommiteditor(edit=True, editform=editform)
704 705 return repo.commit(message, opts.get('user'), opts.get('date'),
705 706 match, editor=e)
706 707 newnode = cmdutil.commit(ui, repo, commitfunc, [], opts)
707 708 if not newnode:
708 709 ui.status(_("nothing changed\n"))
709 710 return 1
710 711 cmdutil.commitstatus(repo, newnode, branch, bheads)
711 712
712 713 def nice(node):
713 714 return '%d:%s' % (repo.changelog.rev(node), short(node))
714 715 ui.status(_('changeset %s backs out changeset %s\n') %
715 716 (nice(repo.changelog.tip()), nice(node)))
716 717 if opts.get('merge') and op1 != node:
717 718 hg.clean(repo, op1, show_stats=False)
718 719 ui.status(_('merging with changeset %s\n')
719 720 % nice(repo.changelog.tip()))
720 721 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
721 722 with ui.configoverride(overrides, 'backout'):
722 723 return hg.merge(repo, hex(repo.changelog.tip()))
723 724 return 0
724 725
725 726 @command('bisect',
726 727 [('r', 'reset', False, _('reset bisect state')),
727 728 ('g', 'good', False, _('mark changeset good')),
728 729 ('b', 'bad', False, _('mark changeset bad')),
729 730 ('s', 'skip', False, _('skip testing changeset')),
730 731 ('e', 'extend', False, _('extend the bisect range')),
731 732 ('c', 'command', '', _('use command to check changeset state'), _('CMD')),
732 733 ('U', 'noupdate', False, _('do not update to target'))],
733 734 _("[-gbsr] [-U] [-c CMD] [REV]"),
734 735 helpcategory=command.CATEGORY_CHANGE_NAVIGATION)
735 736 def bisect(ui, repo, rev=None, extra=None, command=None,
736 737 reset=None, good=None, bad=None, skip=None, extend=None,
737 738 noupdate=None):
738 739 """subdivision search of changesets
739 740
740 741 This command helps to find changesets which introduce problems. To
741 742 use, mark the earliest changeset you know exhibits the problem as
742 743 bad, then mark the latest changeset which is free from the problem
743 744 as good. Bisect will update your working directory to a revision
744 745 for testing (unless the -U/--noupdate option is specified). Once
745 746 you have performed tests, mark the working directory as good or
746 747 bad, and bisect will either update to another candidate changeset
747 748 or announce that it has found the bad revision.
748 749
749 750 As a shortcut, you can also use the revision argument to mark a
750 751 revision as good or bad without checking it out first.
751 752
752 753 If you supply a command, it will be used for automatic bisection.
753 754 The environment variable HG_NODE will contain the ID of the
754 755 changeset being tested. The exit status of the command will be
755 756 used to mark revisions as good or bad: status 0 means good, 125
756 757 means to skip the revision, 127 (command not found) will abort the
757 758 bisection, and any other non-zero exit status means the revision
758 759 is bad.
759 760
760 761 .. container:: verbose
761 762
762 763 Some examples:
763 764
764 765 - start a bisection with known bad revision 34, and good revision 12::
765 766
766 767 hg bisect --bad 34
767 768 hg bisect --good 12
768 769
769 770 - advance the current bisection by marking current revision as good or
770 771 bad::
771 772
772 773 hg bisect --good
773 774 hg bisect --bad
774 775
775 776 - mark the current revision, or a known revision, to be skipped (e.g. if
776 777 that revision is not usable because of another issue)::
777 778
778 779 hg bisect --skip
779 780 hg bisect --skip 23
780 781
781 782 - skip all revisions that do not touch directories ``foo`` or ``bar``::
782 783
783 784 hg bisect --skip "!( file('path:foo') & file('path:bar') )"
784 785
785 786 - forget the current bisection::
786 787
787 788 hg bisect --reset
788 789
789 790 - use 'make && make tests' to automatically find the first broken
790 791 revision::
791 792
792 793 hg bisect --reset
793 794 hg bisect --bad 34
794 795 hg bisect --good 12
795 796 hg bisect --command "make && make tests"
796 797
797 798 - see all changesets whose states are already known in the current
798 799 bisection::
799 800
800 801 hg log -r "bisect(pruned)"
801 802
802 803 - see the changeset currently being bisected (especially useful
803 804 if running with -U/--noupdate)::
804 805
805 806 hg log -r "bisect(current)"
806 807
807 808 - see all changesets that took part in the current bisection::
808 809
809 810 hg log -r "bisect(range)"
810 811
811 812 - you can even get a nice graph::
812 813
813 814 hg log --graph -r "bisect(range)"
814 815
815 816 See :hg:`help revisions.bisect` for more about the `bisect()` predicate.
816 817
817 818 Returns 0 on success.
818 819 """
819 820 # backward compatibility
820 821 if rev in "good bad reset init".split():
821 822 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
822 823 cmd, rev, extra = rev, extra, None
823 824 if cmd == "good":
824 825 good = True
825 826 elif cmd == "bad":
826 827 bad = True
827 828 else:
828 829 reset = True
829 830 elif extra:
830 831 raise error.Abort(_('incompatible arguments'))
831 832
832 833 incompatibles = {
833 834 '--bad': bad,
834 835 '--command': bool(command),
835 836 '--extend': extend,
836 837 '--good': good,
837 838 '--reset': reset,
838 839 '--skip': skip,
839 840 }
840 841
841 842 enabled = [x for x in incompatibles if incompatibles[x]]
842 843
843 844 if len(enabled) > 1:
844 845 raise error.Abort(_('%s and %s are incompatible') %
845 846 tuple(sorted(enabled)[0:2]))
846 847
847 848 if reset:
848 849 hbisect.resetstate(repo)
849 850 return
850 851
851 852 state = hbisect.load_state(repo)
852 853
853 854 # update state
854 855 if good or bad or skip:
855 856 if rev:
856 857 nodes = [repo[i].node() for i in scmutil.revrange(repo, [rev])]
857 858 else:
858 859 nodes = [repo.lookup('.')]
859 860 if good:
860 861 state['good'] += nodes
861 862 elif bad:
862 863 state['bad'] += nodes
863 864 elif skip:
864 865 state['skip'] += nodes
865 866 hbisect.save_state(repo, state)
866 867 if not (state['good'] and state['bad']):
867 868 return
868 869
869 870 def mayupdate(repo, node, show_stats=True):
870 871 """common used update sequence"""
871 872 if noupdate:
872 873 return
873 874 cmdutil.checkunfinished(repo)
874 875 cmdutil.bailifchanged(repo)
875 876 return hg.clean(repo, node, show_stats=show_stats)
876 877
877 878 displayer = logcmdutil.changesetdisplayer(ui, repo, {})
878 879
879 880 if command:
880 881 changesets = 1
881 882 if noupdate:
882 883 try:
883 884 node = state['current'][0]
884 885 except LookupError:
885 886 raise error.Abort(_('current bisect revision is unknown - '
886 887 'start a new bisect to fix'))
887 888 else:
888 889 node, p2 = repo.dirstate.parents()
889 890 if p2 != nullid:
890 891 raise error.Abort(_('current bisect revision is a merge'))
891 892 if rev:
892 893 node = repo[scmutil.revsingle(repo, rev, node)].node()
893 894 try:
894 895 while changesets:
895 896 # update state
896 897 state['current'] = [node]
897 898 hbisect.save_state(repo, state)
898 899 status = ui.system(command, environ={'HG_NODE': hex(node)},
899 900 blockedtag='bisect_check')
900 901 if status == 125:
901 902 transition = "skip"
902 903 elif status == 0:
903 904 transition = "good"
904 905 # status < 0 means process was killed
905 906 elif status == 127:
906 907 raise error.Abort(_("failed to execute %s") % command)
907 908 elif status < 0:
908 909 raise error.Abort(_("%s killed") % command)
909 910 else:
910 911 transition = "bad"
911 912 state[transition].append(node)
912 913 ctx = repo[node]
913 914 ui.status(_('changeset %d:%s: %s\n') % (ctx.rev(), ctx,
914 915 transition))
915 916 hbisect.checkstate(state)
916 917 # bisect
917 918 nodes, changesets, bgood = hbisect.bisect(repo, state)
918 919 # update to next check
919 920 node = nodes[0]
920 921 mayupdate(repo, node, show_stats=False)
921 922 finally:
922 923 state['current'] = [node]
923 924 hbisect.save_state(repo, state)
924 925 hbisect.printresult(ui, repo, state, displayer, nodes, bgood)
925 926 return
926 927
927 928 hbisect.checkstate(state)
928 929
929 930 # actually bisect
930 931 nodes, changesets, good = hbisect.bisect(repo, state)
931 932 if extend:
932 933 if not changesets:
933 934 extendnode = hbisect.extendrange(repo, state, nodes, good)
934 935 if extendnode is not None:
935 936 ui.write(_("Extending search to changeset %d:%s\n")
936 937 % (extendnode.rev(), extendnode))
937 938 state['current'] = [extendnode.node()]
938 939 hbisect.save_state(repo, state)
939 940 return mayupdate(repo, extendnode.node())
940 941 raise error.Abort(_("nothing to extend"))
941 942
942 943 if changesets == 0:
943 944 hbisect.printresult(ui, repo, state, displayer, nodes, good)
944 945 else:
945 946 assert len(nodes) == 1 # only a single node can be tested next
946 947 node = nodes[0]
947 948 # compute the approximate number of remaining tests
948 949 tests, size = 0, 2
949 950 while size <= changesets:
950 951 tests, size = tests + 1, size * 2
951 952 rev = repo.changelog.rev(node)
952 953 ui.write(_("Testing changeset %d:%s "
953 954 "(%d changesets remaining, ~%d tests)\n")
954 955 % (rev, short(node), changesets, tests))
955 956 state['current'] = [node]
956 957 hbisect.save_state(repo, state)
957 958 return mayupdate(repo, node)
958 959
959 960 @command('bookmarks|bookmark',
960 961 [('f', 'force', False, _('force')),
961 962 ('r', 'rev', '', _('revision for bookmark action'), _('REV')),
962 963 ('d', 'delete', False, _('delete a given bookmark')),
963 964 ('m', 'rename', '', _('rename a given bookmark'), _('OLD')),
964 965 ('i', 'inactive', False, _('mark a bookmark inactive')),
965 966 ('l', 'list', False, _('list existing bookmarks')),
966 967 ] + formatteropts,
967 968 _('hg bookmarks [OPTIONS]... [NAME]...'),
968 969 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION)
969 970 def bookmark(ui, repo, *names, **opts):
970 971 '''create a new bookmark or list existing bookmarks
971 972
972 973 Bookmarks are labels on changesets to help track lines of development.
973 974 Bookmarks are unversioned and can be moved, renamed and deleted.
974 975 Deleting or moving a bookmark has no effect on the associated changesets.
975 976
976 977 Creating or updating to a bookmark causes it to be marked as 'active'.
977 978 The active bookmark is indicated with a '*'.
978 979 When a commit is made, the active bookmark will advance to the new commit.
979 980 A plain :hg:`update` will also advance an active bookmark, if possible.
980 981 Updating away from a bookmark will cause it to be deactivated.
981 982
982 983 Bookmarks can be pushed and pulled between repositories (see
983 984 :hg:`help push` and :hg:`help pull`). If a shared bookmark has
984 985 diverged, a new 'divergent bookmark' of the form 'name@path' will
985 986 be created. Using :hg:`merge` will resolve the divergence.
986 987
987 988 Specifying bookmark as '.' to -m/-d/-l options is equivalent to specifying
988 989 the active bookmark's name.
989 990
990 991 A bookmark named '@' has the special property that :hg:`clone` will
991 992 check it out by default if it exists.
992 993
993 994 .. container:: verbose
994 995
995 996 Template:
996 997
997 998 The following keywords are supported in addition to the common template
998 999 keywords and functions such as ``{bookmark}``. See also
999 1000 :hg:`help templates`.
1000 1001
1001 1002 :active: Boolean. True if the bookmark is active.
1002 1003
1003 1004 Examples:
1004 1005
1005 1006 - create an active bookmark for a new line of development::
1006 1007
1007 1008 hg book new-feature
1008 1009
1009 1010 - create an inactive bookmark as a place marker::
1010 1011
1011 1012 hg book -i reviewed
1012 1013
1013 1014 - create an inactive bookmark on another changeset::
1014 1015
1015 1016 hg book -r .^ tested
1016 1017
1017 1018 - rename bookmark turkey to dinner::
1018 1019
1019 1020 hg book -m turkey dinner
1020 1021
1021 1022 - move the '@' bookmark from another branch::
1022 1023
1023 1024 hg book -f @
1024 1025
1025 1026 - print only the active bookmark name::
1026 1027
1027 1028 hg book -ql .
1028 1029 '''
1029 1030 opts = pycompat.byteskwargs(opts)
1030 1031 force = opts.get('force')
1031 1032 rev = opts.get('rev')
1032 1033 inactive = opts.get('inactive') # meaning add/rename to inactive bookmark
1033 1034
1034 1035 selactions = [k for k in ['delete', 'rename', 'list'] if opts.get(k)]
1035 1036 if len(selactions) > 1:
1036 1037 raise error.Abort(_('--%s and --%s are incompatible')
1037 1038 % tuple(selactions[:2]))
1038 1039 if selactions:
1039 1040 action = selactions[0]
1040 1041 elif names or rev:
1041 1042 action = 'add'
1042 1043 elif inactive:
1043 1044 action = 'inactive' # meaning deactivate
1044 1045 else:
1045 1046 action = 'list'
1046 1047
1047 1048 if rev and action in {'delete', 'rename', 'list'}:
1048 1049 raise error.Abort(_("--rev is incompatible with --%s") % action)
1049 1050 if inactive and action in {'delete', 'list'}:
1050 1051 raise error.Abort(_("--inactive is incompatible with --%s") % action)
1051 1052 if not names and action in {'add', 'delete'}:
1052 1053 raise error.Abort(_("bookmark name required"))
1053 1054
1054 1055 if action in {'add', 'delete', 'rename', 'inactive'}:
1055 1056 with repo.wlock(), repo.lock(), repo.transaction('bookmark') as tr:
1056 1057 if action == 'delete':
1057 1058 names = pycompat.maplist(repo._bookmarks.expandname, names)
1058 1059 bookmarks.delete(repo, tr, names)
1059 1060 elif action == 'rename':
1060 1061 if not names:
1061 1062 raise error.Abort(_("new bookmark name required"))
1062 1063 elif len(names) > 1:
1063 1064 raise error.Abort(_("only one new bookmark name allowed"))
1064 1065 oldname = repo._bookmarks.expandname(opts['rename'])
1065 1066 bookmarks.rename(repo, tr, oldname, names[0], force, inactive)
1066 1067 elif action == 'add':
1067 1068 bookmarks.addbookmarks(repo, tr, names, rev, force, inactive)
1068 1069 elif action == 'inactive':
1069 1070 if len(repo._bookmarks) == 0:
1070 1071 ui.status(_("no bookmarks set\n"))
1071 1072 elif not repo._activebookmark:
1072 1073 ui.status(_("no active bookmark\n"))
1073 1074 else:
1074 1075 bookmarks.deactivate(repo)
1075 1076 elif action == 'list':
1076 1077 names = pycompat.maplist(repo._bookmarks.expandname, names)
1077 1078 with ui.formatter('bookmarks', opts) as fm:
1078 1079 bookmarks.printbookmarks(ui, repo, fm, names)
1079 1080 else:
1080 1081 raise error.ProgrammingError('invalid action: %s' % action)
1081 1082
1082 1083 @command('branch',
1083 1084 [('f', 'force', None,
1084 1085 _('set branch name even if it shadows an existing branch')),
1085 1086 ('C', 'clean', None, _('reset branch name to parent branch name')),
1086 1087 ('r', 'rev', [], _('change branches of the given revs (EXPERIMENTAL)')),
1087 1088 ],
1088 1089 _('[-fC] [NAME]'),
1089 1090 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION)
1090 1091 def branch(ui, repo, label=None, **opts):
1091 1092 """set or show the current branch name
1092 1093
1093 1094 .. note::
1094 1095
1095 1096 Branch names are permanent and global. Use :hg:`bookmark` to create a
1096 1097 light-weight bookmark instead. See :hg:`help glossary` for more
1097 1098 information about named branches and bookmarks.
1098 1099
1099 1100 With no argument, show the current branch name. With one argument,
1100 1101 set the working directory branch name (the branch will not exist
1101 1102 in the repository until the next commit). Standard practice
1102 1103 recommends that primary development take place on the 'default'
1103 1104 branch.
1104 1105
1105 1106 Unless -f/--force is specified, branch will not let you set a
1106 1107 branch name that already exists.
1107 1108
1108 1109 Use -C/--clean to reset the working directory branch to that of
1109 1110 the parent of the working directory, negating a previous branch
1110 1111 change.
1111 1112
1112 1113 Use the command :hg:`update` to switch to an existing branch. Use
1113 1114 :hg:`commit --close-branch` to mark this branch head as closed.
1114 1115 When all heads of a branch are closed, the branch will be
1115 1116 considered closed.
1116 1117
1117 1118 Returns 0 on success.
1118 1119 """
1119 1120 opts = pycompat.byteskwargs(opts)
1120 1121 revs = opts.get('rev')
1121 1122 if label:
1122 1123 label = label.strip()
1123 1124
1124 1125 if not opts.get('clean') and not label:
1125 1126 if revs:
1126 1127 raise error.Abort(_("no branch name specified for the revisions"))
1127 1128 ui.write("%s\n" % repo.dirstate.branch())
1128 1129 return
1129 1130
1130 1131 with repo.wlock():
1131 1132 if opts.get('clean'):
1132 1133 label = repo['.'].branch()
1133 1134 repo.dirstate.setbranch(label)
1134 1135 ui.status(_('reset working directory to branch %s\n') % label)
1135 1136 elif label:
1136 1137
1137 1138 scmutil.checknewlabel(repo, label, 'branch')
1138 1139 if revs:
1139 1140 return cmdutil.changebranch(ui, repo, revs, label)
1140 1141
1141 1142 if not opts.get('force') and label in repo.branchmap():
1142 1143 if label not in [p.branch() for p in repo[None].parents()]:
1143 1144 raise error.Abort(_('a branch of the same name already'
1144 1145 ' exists'),
1145 1146 # i18n: "it" refers to an existing branch
1146 1147 hint=_("use 'hg update' to switch to it"))
1147 1148
1148 1149 repo.dirstate.setbranch(label)
1149 1150 ui.status(_('marked working directory as branch %s\n') % label)
1150 1151
1151 1152 # find any open named branches aside from default
1152 1153 for n, h, t, c in repo.branchmap().iterbranches():
1153 1154 if n != "default" and not c:
1154 1155 return 0
1155 1156 ui.status(_('(branches are permanent and global, '
1156 1157 'did you want a bookmark?)\n'))
1157 1158
1158 1159 @command('branches',
1159 1160 [('a', 'active', False,
1160 1161 _('show only branches that have unmerged heads (DEPRECATED)')),
1161 1162 ('c', 'closed', False, _('show normal and closed branches')),
1162 1163 ('r', 'rev', [], _('show branch name(s) of the given rev'))
1163 1164 ] + formatteropts,
1164 1165 _('[-c]'),
1165 1166 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1166 1167 intents={INTENT_READONLY})
1167 1168 def branches(ui, repo, active=False, closed=False, **opts):
1168 1169 """list repository named branches
1169 1170
1170 1171 List the repository's named branches, indicating which ones are
1171 1172 inactive. If -c/--closed is specified, also list branches which have
1172 1173 been marked closed (see :hg:`commit --close-branch`).
1173 1174
1174 1175 Use the command :hg:`update` to switch to an existing branch.
1175 1176
1176 1177 .. container:: verbose
1177 1178
1178 1179 Template:
1179 1180
1180 1181 The following keywords are supported in addition to the common template
1181 1182 keywords and functions such as ``{branch}``. See also
1182 1183 :hg:`help templates`.
1183 1184
1184 1185 :active: Boolean. True if the branch is active.
1185 1186 :closed: Boolean. True if the branch is closed.
1186 1187 :current: Boolean. True if it is the current branch.
1187 1188
1188 1189 Returns 0.
1189 1190 """
1190 1191
1191 1192 opts = pycompat.byteskwargs(opts)
1192 1193 revs = opts.get('rev')
1193 1194 selectedbranches = None
1194 1195 if revs:
1195 1196 revs = scmutil.revrange(repo, revs)
1196 1197 getbi = repo.revbranchcache().branchinfo
1197 1198 selectedbranches = {getbi(r)[0] for r in revs}
1198 1199
1199 1200 ui.pager('branches')
1200 1201 fm = ui.formatter('branches', opts)
1201 1202 hexfunc = fm.hexfunc
1202 1203
1203 1204 allheads = set(repo.heads())
1204 1205 branches = []
1205 1206 for tag, heads, tip, isclosed in repo.branchmap().iterbranches():
1206 1207 if selectedbranches is not None and tag not in selectedbranches:
1207 1208 continue
1208 1209 isactive = False
1209 1210 if not isclosed:
1210 1211 openheads = set(repo.branchmap().iteropen(heads))
1211 1212 isactive = bool(openheads & allheads)
1212 1213 branches.append((tag, repo[tip], isactive, not isclosed))
1213 1214 branches.sort(key=lambda i: (i[2], i[1].rev(), i[0], i[3]),
1214 1215 reverse=True)
1215 1216
1216 1217 for tag, ctx, isactive, isopen in branches:
1217 1218 if active and not isactive:
1218 1219 continue
1219 1220 if isactive:
1220 1221 label = 'branches.active'
1221 1222 notice = ''
1222 1223 elif not isopen:
1223 1224 if not closed:
1224 1225 continue
1225 1226 label = 'branches.closed'
1226 1227 notice = _(' (closed)')
1227 1228 else:
1228 1229 label = 'branches.inactive'
1229 1230 notice = _(' (inactive)')
1230 1231 current = (tag == repo.dirstate.branch())
1231 1232 if current:
1232 1233 label = 'branches.current'
1233 1234
1234 1235 fm.startitem()
1235 1236 fm.write('branch', '%s', tag, label=label)
1236 1237 rev = ctx.rev()
1237 1238 padsize = max(31 - len("%d" % rev) - encoding.colwidth(tag), 0)
1238 1239 fmt = ' ' * padsize + ' %d:%s'
1239 1240 fm.condwrite(not ui.quiet, 'rev node', fmt, rev, hexfunc(ctx.node()),
1240 1241 label='log.changeset changeset.%s' % ctx.phasestr())
1241 1242 fm.context(ctx=ctx)
1242 1243 fm.data(active=isactive, closed=not isopen, current=current)
1243 1244 if not ui.quiet:
1244 1245 fm.plain(notice)
1245 1246 fm.plain('\n')
1246 1247 fm.end()
1247 1248
1248 1249 @command('bundle',
1249 1250 [('f', 'force', None, _('run even when the destination is unrelated')),
1250 1251 ('r', 'rev', [], _('a changeset intended to be added to the destination'),
1251 1252 _('REV')),
1252 1253 ('b', 'branch', [], _('a specific branch you would like to bundle'),
1253 1254 _('BRANCH')),
1254 1255 ('', 'base', [],
1255 1256 _('a base changeset assumed to be available at the destination'),
1256 1257 _('REV')),
1257 1258 ('a', 'all', None, _('bundle all changesets in the repository')),
1258 1259 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE')),
1259 1260 ] + remoteopts,
1260 1261 _('[-f] [-t BUNDLESPEC] [-a] [-r REV]... [--base REV]... FILE [DEST]'),
1261 1262 helpcategory=command.CATEGORY_IMPORT_EXPORT)
1262 1263 def bundle(ui, repo, fname, dest=None, **opts):
1263 1264 """create a bundle file
1264 1265
1265 1266 Generate a bundle file containing data to be transferred to another
1266 1267 repository.
1267 1268
1268 1269 To create a bundle containing all changesets, use -a/--all
1269 1270 (or --base null). Otherwise, hg assumes the destination will have
1270 1271 all the nodes you specify with --base parameters. Otherwise, hg
1271 1272 will assume the repository has all the nodes in destination, or
1272 1273 default-push/default if no destination is specified, where destination
1273 1274 is the repository you provide through DEST option.
1274 1275
1275 1276 You can change bundle format with the -t/--type option. See
1276 1277 :hg:`help bundlespec` for documentation on this format. By default,
1277 1278 the most appropriate format is used and compression defaults to
1278 1279 bzip2.
1279 1280
1280 1281 The bundle file can then be transferred using conventional means
1281 1282 and applied to another repository with the unbundle or pull
1282 1283 command. This is useful when direct push and pull are not
1283 1284 available or when exporting an entire repository is undesirable.
1284 1285
1285 1286 Applying bundles preserves all changeset contents including
1286 1287 permissions, copy/rename information, and revision history.
1287 1288
1288 1289 Returns 0 on success, 1 if no changes found.
1289 1290 """
1290 1291 opts = pycompat.byteskwargs(opts)
1291 1292 revs = None
1292 1293 if 'rev' in opts:
1293 1294 revstrings = opts['rev']
1294 1295 revs = scmutil.revrange(repo, revstrings)
1295 1296 if revstrings and not revs:
1296 1297 raise error.Abort(_('no commits to bundle'))
1297 1298
1298 1299 bundletype = opts.get('type', 'bzip2').lower()
1299 1300 try:
1300 1301 bundlespec = exchange.parsebundlespec(repo, bundletype, strict=False)
1301 1302 except error.UnsupportedBundleSpecification as e:
1302 1303 raise error.Abort(pycompat.bytestr(e),
1303 1304 hint=_("see 'hg help bundlespec' for supported "
1304 1305 "values for --type"))
1305 1306 cgversion = bundlespec.contentopts["cg.version"]
1306 1307
1307 1308 # Packed bundles are a pseudo bundle format for now.
1308 1309 if cgversion == 's1':
1309 1310 raise error.Abort(_('packed bundles cannot be produced by "hg bundle"'),
1310 1311 hint=_("use 'hg debugcreatestreamclonebundle'"))
1311 1312
1312 1313 if opts.get('all'):
1313 1314 if dest:
1314 1315 raise error.Abort(_("--all is incompatible with specifying "
1315 1316 "a destination"))
1316 1317 if opts.get('base'):
1317 1318 ui.warn(_("ignoring --base because --all was specified\n"))
1318 1319 base = [nullrev]
1319 1320 else:
1320 1321 base = scmutil.revrange(repo, opts.get('base'))
1321 1322 if cgversion not in changegroup.supportedoutgoingversions(repo):
1322 1323 raise error.Abort(_("repository does not support bundle version %s") %
1323 1324 cgversion)
1324 1325
1325 1326 if base:
1326 1327 if dest:
1327 1328 raise error.Abort(_("--base is incompatible with specifying "
1328 1329 "a destination"))
1329 1330 common = [repo[rev].node() for rev in base]
1330 1331 heads = [repo[r].node() for r in revs] if revs else None
1331 1332 outgoing = discovery.outgoing(repo, common, heads)
1332 1333 else:
1333 1334 dest = ui.expandpath(dest or 'default-push', dest or 'default')
1334 1335 dest, branches = hg.parseurl(dest, opts.get('branch'))
1335 1336 other = hg.peer(repo, opts, dest)
1336 1337 revs = [repo[r].hex() for r in revs]
1337 1338 revs, checkout = hg.addbranchrevs(repo, repo, branches, revs)
1338 1339 heads = revs and pycompat.maplist(repo.lookup, revs) or revs
1339 1340 outgoing = discovery.findcommonoutgoing(repo, other,
1340 1341 onlyheads=heads,
1341 1342 force=opts.get('force'),
1342 1343 portable=True)
1343 1344
1344 1345 if not outgoing.missing:
1345 1346 scmutil.nochangesfound(ui, repo, not base and outgoing.excluded)
1346 1347 return 1
1347 1348
1348 1349 if cgversion == '01': #bundle1
1349 1350 bversion = 'HG10' + bundlespec.wirecompression
1350 1351 bcompression = None
1351 1352 elif cgversion in ('02', '03'):
1352 1353 bversion = 'HG20'
1353 1354 bcompression = bundlespec.wirecompression
1354 1355 else:
1355 1356 raise error.ProgrammingError(
1356 1357 'bundle: unexpected changegroup version %s' % cgversion)
1357 1358
1358 1359 # TODO compression options should be derived from bundlespec parsing.
1359 1360 # This is a temporary hack to allow adjusting bundle compression
1360 1361 # level without a) formalizing the bundlespec changes to declare it
1361 1362 # b) introducing a command flag.
1362 1363 compopts = {}
1363 1364 complevel = ui.configint('experimental',
1364 1365 'bundlecomplevel.' + bundlespec.compression)
1365 1366 if complevel is None:
1366 1367 complevel = ui.configint('experimental', 'bundlecomplevel')
1367 1368 if complevel is not None:
1368 1369 compopts['level'] = complevel
1369 1370
1370 1371 # Allow overriding the bundling of obsmarker in phases through
1371 1372 # configuration while we don't have a bundle version that include them
1372 1373 if repo.ui.configbool('experimental', 'evolution.bundle-obsmarker'):
1373 1374 bundlespec.contentopts['obsolescence'] = True
1374 1375 if repo.ui.configbool('experimental', 'bundle-phases'):
1375 1376 bundlespec.contentopts['phases'] = True
1376 1377
1377 1378 bundle2.writenewbundle(ui, repo, 'bundle', fname, bversion, outgoing,
1378 1379 bundlespec.contentopts, compression=bcompression,
1379 1380 compopts=compopts)
1380 1381
1381 1382 @command('cat',
1382 1383 [('o', 'output', '',
1383 1384 _('print output to file with formatted name'), _('FORMAT')),
1384 1385 ('r', 'rev', '', _('print the given revision'), _('REV')),
1385 1386 ('', 'decode', None, _('apply any matching decode filter')),
1386 1387 ] + walkopts + formatteropts,
1387 1388 _('[OPTION]... FILE...'),
1388 1389 helpcategory=command.CATEGORY_FILE_CONTENTS,
1389 1390 inferrepo=True,
1390 1391 intents={INTENT_READONLY})
1391 1392 def cat(ui, repo, file1, *pats, **opts):
1392 1393 """output the current or given revision of files
1393 1394
1394 1395 Print the specified files as they were at the given revision. If
1395 1396 no revision is given, the parent of the working directory is used.
1396 1397
1397 1398 Output may be to a file, in which case the name of the file is
1398 1399 given using a template string. See :hg:`help templates`. In addition
1399 1400 to the common template keywords, the following formatting rules are
1400 1401 supported:
1401 1402
1402 1403 :``%%``: literal "%" character
1403 1404 :``%s``: basename of file being printed
1404 1405 :``%d``: dirname of file being printed, or '.' if in repository root
1405 1406 :``%p``: root-relative path name of file being printed
1406 1407 :``%H``: changeset hash (40 hexadecimal digits)
1407 1408 :``%R``: changeset revision number
1408 1409 :``%h``: short-form changeset hash (12 hexadecimal digits)
1409 1410 :``%r``: zero-padded changeset revision number
1410 1411 :``%b``: basename of the exporting repository
1411 1412 :``\\``: literal "\\" character
1412 1413
1413 1414 .. container:: verbose
1414 1415
1415 1416 Template:
1416 1417
1417 1418 The following keywords are supported in addition to the common template
1418 1419 keywords and functions. See also :hg:`help templates`.
1419 1420
1420 1421 :data: String. File content.
1421 1422 :path: String. Repository-absolute path of the file.
1422 1423
1423 1424 Returns 0 on success.
1424 1425 """
1425 1426 opts = pycompat.byteskwargs(opts)
1426 1427 rev = opts.get('rev')
1427 1428 if rev:
1428 1429 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
1429 1430 ctx = scmutil.revsingle(repo, rev)
1430 1431 m = scmutil.match(ctx, (file1,) + pats, opts)
1431 1432 fntemplate = opts.pop('output', '')
1432 1433 if cmdutil.isstdiofilename(fntemplate):
1433 1434 fntemplate = ''
1434 1435
1435 1436 if fntemplate:
1436 1437 fm = formatter.nullformatter(ui, 'cat', opts)
1437 1438 else:
1438 1439 ui.pager('cat')
1439 1440 fm = ui.formatter('cat', opts)
1440 1441 with fm:
1441 1442 return cmdutil.cat(ui, repo, ctx, m, fm, fntemplate, '',
1442 1443 **pycompat.strkwargs(opts))
1443 1444
1444 1445 @command('clone',
1445 1446 [('U', 'noupdate', None, _('the clone will include an empty working '
1446 1447 'directory (only a repository)')),
1447 1448 ('u', 'updaterev', '', _('revision, tag, or branch to check out'),
1448 1449 _('REV')),
1449 1450 ('r', 'rev', [], _('do not clone everything, but include this changeset'
1450 1451 ' and its ancestors'), _('REV')),
1451 1452 ('b', 'branch', [], _('do not clone everything, but include this branch\'s'
1452 1453 ' changesets and their ancestors'), _('BRANCH')),
1453 1454 ('', 'pull', None, _('use pull protocol to copy metadata')),
1454 1455 ('', 'uncompressed', None,
1455 1456 _('an alias to --stream (DEPRECATED)')),
1456 1457 ('', 'stream', None,
1457 1458 _('clone with minimal data processing')),
1458 1459 ] + remoteopts,
1459 1460 _('[OPTION]... SOURCE [DEST]'),
1460 1461 helpcategory=command.CATEGORY_REPO_CREATION,
1461 1462 helpbasic=True, norepo=True)
1462 1463 def clone(ui, source, dest=None, **opts):
1463 1464 """make a copy of an existing repository
1464 1465
1465 1466 Create a copy of an existing repository in a new directory.
1466 1467
1467 1468 If no destination directory name is specified, it defaults to the
1468 1469 basename of the source.
1469 1470
1470 1471 The location of the source is added to the new repository's
1471 1472 ``.hg/hgrc`` file, as the default to be used for future pulls.
1472 1473
1473 1474 Only local paths and ``ssh://`` URLs are supported as
1474 1475 destinations. For ``ssh://`` destinations, no working directory or
1475 1476 ``.hg/hgrc`` will be created on the remote side.
1476 1477
1477 1478 If the source repository has a bookmark called '@' set, that
1478 1479 revision will be checked out in the new repository by default.
1479 1480
1480 1481 To check out a particular version, use -u/--update, or
1481 1482 -U/--noupdate to create a clone with no working directory.
1482 1483
1483 1484 To pull only a subset of changesets, specify one or more revisions
1484 1485 identifiers with -r/--rev or branches with -b/--branch. The
1485 1486 resulting clone will contain only the specified changesets and
1486 1487 their ancestors. These options (or 'clone src#rev dest') imply
1487 1488 --pull, even for local source repositories.
1488 1489
1489 1490 In normal clone mode, the remote normalizes repository data into a common
1490 1491 exchange format and the receiving end translates this data into its local
1491 1492 storage format. --stream activates a different clone mode that essentially
1492 1493 copies repository files from the remote with minimal data processing. This
1493 1494 significantly reduces the CPU cost of a clone both remotely and locally.
1494 1495 However, it often increases the transferred data size by 30-40%. This can
1495 1496 result in substantially faster clones where I/O throughput is plentiful,
1496 1497 especially for larger repositories. A side-effect of --stream clones is
1497 1498 that storage settings and requirements on the remote are applied locally:
1498 1499 a modern client may inherit legacy or inefficient storage used by the
1499 1500 remote or a legacy Mercurial client may not be able to clone from a
1500 1501 modern Mercurial remote.
1501 1502
1502 1503 .. note::
1503 1504
1504 1505 Specifying a tag will include the tagged changeset but not the
1505 1506 changeset containing the tag.
1506 1507
1507 1508 .. container:: verbose
1508 1509
1509 1510 For efficiency, hardlinks are used for cloning whenever the
1510 1511 source and destination are on the same filesystem (note this
1511 1512 applies only to the repository data, not to the working
1512 1513 directory). Some filesystems, such as AFS, implement hardlinking
1513 1514 incorrectly, but do not report errors. In these cases, use the
1514 1515 --pull option to avoid hardlinking.
1515 1516
1516 1517 Mercurial will update the working directory to the first applicable
1517 1518 revision from this list:
1518 1519
1519 1520 a) null if -U or the source repository has no changesets
1520 1521 b) if -u . and the source repository is local, the first parent of
1521 1522 the source repository's working directory
1522 1523 c) the changeset specified with -u (if a branch name, this means the
1523 1524 latest head of that branch)
1524 1525 d) the changeset specified with -r
1525 1526 e) the tipmost head specified with -b
1526 1527 f) the tipmost head specified with the url#branch source syntax
1527 1528 g) the revision marked with the '@' bookmark, if present
1528 1529 h) the tipmost head of the default branch
1529 1530 i) tip
1530 1531
1531 1532 When cloning from servers that support it, Mercurial may fetch
1532 1533 pre-generated data from a server-advertised URL or inline from the
1533 1534 same stream. When this is done, hooks operating on incoming changesets
1534 1535 and changegroups may fire more than once, once for each pre-generated
1535 1536 bundle and as well as for any additional remaining data. In addition,
1536 1537 if an error occurs, the repository may be rolled back to a partial
1537 1538 clone. This behavior may change in future releases.
1538 1539 See :hg:`help -e clonebundles` for more.
1539 1540
1540 1541 Examples:
1541 1542
1542 1543 - clone a remote repository to a new directory named hg/::
1543 1544
1544 1545 hg clone https://www.mercurial-scm.org/repo/hg/
1545 1546
1546 1547 - create a lightweight local clone::
1547 1548
1548 1549 hg clone project/ project-feature/
1549 1550
1550 1551 - clone from an absolute path on an ssh server (note double-slash)::
1551 1552
1552 1553 hg clone ssh://user@server//home/projects/alpha/
1553 1554
1554 1555 - do a streaming clone while checking out a specified version::
1555 1556
1556 1557 hg clone --stream http://server/repo -u 1.5
1557 1558
1558 1559 - create a repository without changesets after a particular revision::
1559 1560
1560 1561 hg clone -r 04e544 experimental/ good/
1561 1562
1562 1563 - clone (and track) a particular named branch::
1563 1564
1564 1565 hg clone https://www.mercurial-scm.org/repo/hg/#stable
1565 1566
1566 1567 See :hg:`help urls` for details on specifying URLs.
1567 1568
1568 1569 Returns 0 on success.
1569 1570 """
1570 1571 opts = pycompat.byteskwargs(opts)
1571 1572 if opts.get('noupdate') and opts.get('updaterev'):
1572 1573 raise error.Abort(_("cannot specify both --noupdate and --updaterev"))
1573 1574
1574 1575 # --include/--exclude can come from narrow or sparse.
1575 1576 includepats, excludepats = None, None
1576 1577
1577 1578 # hg.clone() differentiates between None and an empty set. So make sure
1578 1579 # patterns are sets if narrow is requested without patterns.
1579 1580 if opts.get('narrow'):
1580 1581 includepats = set()
1581 1582 excludepats = set()
1582 1583
1583 1584 if opts.get('include'):
1584 1585 includepats = narrowspec.parsepatterns(opts.get('include'))
1585 1586 if opts.get('exclude'):
1586 1587 excludepats = narrowspec.parsepatterns(opts.get('exclude'))
1587 1588
1588 1589 r = hg.clone(ui, opts, source, dest,
1589 1590 pull=opts.get('pull'),
1590 1591 stream=opts.get('stream') or opts.get('uncompressed'),
1591 1592 revs=opts.get('rev'),
1592 1593 update=opts.get('updaterev') or not opts.get('noupdate'),
1593 1594 branch=opts.get('branch'),
1594 1595 shareopts=opts.get('shareopts'),
1595 1596 storeincludepats=includepats,
1596 1597 storeexcludepats=excludepats,
1597 1598 depth=opts.get('depth') or None)
1598 1599
1599 1600 return r is None
1600 1601
1601 1602 @command('commit|ci',
1602 1603 [('A', 'addremove', None,
1603 1604 _('mark new/missing files as added/removed before committing')),
1604 1605 ('', 'close-branch', None,
1605 1606 _('mark a branch head as closed')),
1606 1607 ('', 'amend', None, _('amend the parent of the working directory')),
1607 1608 ('s', 'secret', None, _('use the secret phase for committing')),
1608 1609 ('e', 'edit', None, _('invoke editor on commit messages')),
1609 1610 ('', 'force-close-branch', None,
1610 1611 _('forcibly close branch from a non-head changeset (ADVANCED)')),
1611 1612 ('i', 'interactive', None, _('use interactive mode')),
1612 1613 ] + walkopts + commitopts + commitopts2 + subrepoopts,
1613 1614 _('[OPTION]... [FILE]...'),
1614 1615 helpcategory=command.CATEGORY_COMMITTING, helpbasic=True,
1615 1616 inferrepo=True)
1616 1617 def commit(ui, repo, *pats, **opts):
1617 1618 """commit the specified files or all outstanding changes
1618 1619
1619 1620 Commit changes to the given files into the repository. Unlike a
1620 1621 centralized SCM, this operation is a local operation. See
1621 1622 :hg:`push` for a way to actively distribute your changes.
1622 1623
1623 1624 If a list of files is omitted, all changes reported by :hg:`status`
1624 1625 will be committed.
1625 1626
1626 1627 If you are committing the result of a merge, do not provide any
1627 1628 filenames or -I/-X filters.
1628 1629
1629 1630 If no commit message is specified, Mercurial starts your
1630 1631 configured editor where you can enter a message. In case your
1631 1632 commit fails, you will find a backup of your message in
1632 1633 ``.hg/last-message.txt``.
1633 1634
1634 1635 The --close-branch flag can be used to mark the current branch
1635 1636 head closed. When all heads of a branch are closed, the branch
1636 1637 will be considered closed and no longer listed.
1637 1638
1638 1639 The --amend flag can be used to amend the parent of the
1639 1640 working directory with a new commit that contains the changes
1640 1641 in the parent in addition to those currently reported by :hg:`status`,
1641 1642 if there are any. The old commit is stored in a backup bundle in
1642 1643 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
1643 1644 on how to restore it).
1644 1645
1645 1646 Message, user and date are taken from the amended commit unless
1646 1647 specified. When a message isn't specified on the command line,
1647 1648 the editor will open with the message of the amended commit.
1648 1649
1649 1650 It is not possible to amend public changesets (see :hg:`help phases`)
1650 1651 or changesets that have children.
1651 1652
1652 1653 See :hg:`help dates` for a list of formats valid for -d/--date.
1653 1654
1654 1655 Returns 0 on success, 1 if nothing changed.
1655 1656
1656 1657 .. container:: verbose
1657 1658
1658 1659 Examples:
1659 1660
1660 1661 - commit all files ending in .py::
1661 1662
1662 1663 hg commit --include "set:**.py"
1663 1664
1664 1665 - commit all non-binary files::
1665 1666
1666 1667 hg commit --exclude "set:binary()"
1667 1668
1668 1669 - amend the current commit and set the date to now::
1669 1670
1670 1671 hg commit --amend --date now
1671 1672 """
1672 1673 with repo.wlock(), repo.lock():
1673 1674 return _docommit(ui, repo, *pats, **opts)
1674 1675
1675 1676 def _docommit(ui, repo, *pats, **opts):
1676 1677 if opts.get(r'interactive'):
1677 1678 opts.pop(r'interactive')
1678 1679 ret = cmdutil.dorecord(ui, repo, commit, None, False,
1679 1680 cmdutil.recordfilter, *pats,
1680 1681 **opts)
1681 1682 # ret can be 0 (no changes to record) or the value returned by
1682 1683 # commit(), 1 if nothing changed or None on success.
1683 1684 return 1 if ret == 0 else ret
1684 1685
1685 1686 opts = pycompat.byteskwargs(opts)
1686 1687 if opts.get('subrepos'):
1687 1688 if opts.get('amend'):
1688 1689 raise error.Abort(_('cannot amend with --subrepos'))
1689 1690 # Let --subrepos on the command line override config setting.
1690 1691 ui.setconfig('ui', 'commitsubrepos', True, 'commit')
1691 1692
1692 1693 cmdutil.checkunfinished(repo, commit=True)
1693 1694
1694 1695 branch = repo[None].branch()
1695 1696 bheads = repo.branchheads(branch)
1696 1697
1697 1698 extra = {}
1698 1699 if opts.get('close_branch') or opts.get('force_close_branch'):
1699 1700 extra['close'] = '1'
1700 1701
1701 1702 if repo['.'].closesbranch():
1702 1703 raise error.Abort(_('current revision is already a branch closing'
1703 1704 ' head'))
1704 1705 elif not bheads:
1705 1706 raise error.Abort(_('branch "%s" has no heads to close') % branch)
1706 1707 elif (branch == repo['.'].branch() and repo['.'].node() not in bheads
1707 1708 and not opts.get('force_close_branch')):
1708 1709 hint = _('use --force-close-branch to close branch from a non-head'
1709 1710 ' changeset')
1710 1711 raise error.Abort(_('can only close branch heads'), hint=hint)
1711 1712 elif opts.get('amend'):
1712 1713 if (repo['.'].p1().branch() != branch and
1713 1714 repo['.'].p2().branch() != branch):
1714 1715 raise error.Abort(_('can only close branch heads'))
1715 1716
1716 1717 if opts.get('amend'):
1717 1718 if ui.configbool('ui', 'commitsubrepos'):
1718 1719 raise error.Abort(_('cannot amend with ui.commitsubrepos enabled'))
1719 1720
1720 1721 old = repo['.']
1721 1722 rewriteutil.precheck(repo, [old.rev()], 'amend')
1722 1723
1723 1724 # Currently histedit gets confused if an amend happens while histedit
1724 1725 # is in progress. Since we have a checkunfinished command, we are
1725 1726 # temporarily honoring it.
1726 1727 #
1727 1728 # Note: eventually this guard will be removed. Please do not expect
1728 1729 # this behavior to remain.
1729 1730 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
1730 1731 cmdutil.checkunfinished(repo)
1731 1732
1732 1733 node = cmdutil.amend(ui, repo, old, extra, pats, opts)
1733 1734 if node == old.node():
1734 1735 ui.status(_("nothing changed\n"))
1735 1736 return 1
1736 1737 else:
1737 1738 def commitfunc(ui, repo, message, match, opts):
1738 1739 overrides = {}
1739 1740 if opts.get('secret'):
1740 1741 overrides[('phases', 'new-commit')] = 'secret'
1741 1742
1742 1743 baseui = repo.baseui
1743 1744 with baseui.configoverride(overrides, 'commit'):
1744 1745 with ui.configoverride(overrides, 'commit'):
1745 1746 editform = cmdutil.mergeeditform(repo[None],
1746 1747 'commit.normal')
1747 1748 editor = cmdutil.getcommiteditor(
1748 1749 editform=editform, **pycompat.strkwargs(opts))
1749 1750 return repo.commit(message,
1750 1751 opts.get('user'),
1751 1752 opts.get('date'),
1752 1753 match,
1753 1754 editor=editor,
1754 1755 extra=extra)
1755 1756
1756 1757 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
1757 1758
1758 1759 if not node:
1759 1760 stat = cmdutil.postcommitstatus(repo, pats, opts)
1760 1761 if stat[3]:
1761 1762 ui.status(_("nothing changed (%d missing files, see "
1762 1763 "'hg status')\n") % len(stat[3]))
1763 1764 else:
1764 1765 ui.status(_("nothing changed\n"))
1765 1766 return 1
1766 1767
1767 1768 cmdutil.commitstatus(repo, node, branch, bheads, opts)
1768 1769
1769 1770 if not ui.quiet and ui.configbool('commands', 'commit.post-status'):
1770 1771 status(ui, repo, modified=True, added=True, removed=True, deleted=True,
1771 1772 unknown=True, subrepos=opts.get('subrepos'))
1772 1773
1773 1774 @command('config|showconfig|debugconfig',
1774 1775 [('u', 'untrusted', None, _('show untrusted configuration options')),
1775 1776 ('e', 'edit', None, _('edit user config')),
1776 1777 ('l', 'local', None, _('edit repository config')),
1777 1778 ('g', 'global', None, _('edit global config'))] + formatteropts,
1778 1779 _('[-u] [NAME]...'),
1779 1780 helpcategory=command.CATEGORY_HELP,
1780 1781 optionalrepo=True,
1781 1782 intents={INTENT_READONLY})
1782 1783 def config(ui, repo, *values, **opts):
1783 1784 """show combined config settings from all hgrc files
1784 1785
1785 1786 With no arguments, print names and values of all config items.
1786 1787
1787 1788 With one argument of the form section.name, print just the value
1788 1789 of that config item.
1789 1790
1790 1791 With multiple arguments, print names and values of all config
1791 1792 items with matching section names or section.names.
1792 1793
1793 1794 With --edit, start an editor on the user-level config file. With
1794 1795 --global, edit the system-wide config file. With --local, edit the
1795 1796 repository-level config file.
1796 1797
1797 1798 With --debug, the source (filename and line number) is printed
1798 1799 for each config item.
1799 1800
1800 1801 See :hg:`help config` for more information about config files.
1801 1802
1802 1803 .. container:: verbose
1803 1804
1804 1805 Template:
1805 1806
1806 1807 The following keywords are supported. See also :hg:`help templates`.
1807 1808
1808 1809 :name: String. Config name.
1809 1810 :source: String. Filename and line number where the item is defined.
1810 1811 :value: String. Config value.
1811 1812
1812 1813 Returns 0 on success, 1 if NAME does not exist.
1813 1814
1814 1815 """
1815 1816
1816 1817 opts = pycompat.byteskwargs(opts)
1817 1818 if opts.get('edit') or opts.get('local') or opts.get('global'):
1818 1819 if opts.get('local') and opts.get('global'):
1819 1820 raise error.Abort(_("can't use --local and --global together"))
1820 1821
1821 1822 if opts.get('local'):
1822 1823 if not repo:
1823 1824 raise error.Abort(_("can't use --local outside a repository"))
1824 1825 paths = [repo.vfs.join('hgrc')]
1825 1826 elif opts.get('global'):
1826 1827 paths = rcutil.systemrcpath()
1827 1828 else:
1828 1829 paths = rcutil.userrcpath()
1829 1830
1830 1831 for f in paths:
1831 1832 if os.path.exists(f):
1832 1833 break
1833 1834 else:
1834 1835 if opts.get('global'):
1835 1836 samplehgrc = uimod.samplehgrcs['global']
1836 1837 elif opts.get('local'):
1837 1838 samplehgrc = uimod.samplehgrcs['local']
1838 1839 else:
1839 1840 samplehgrc = uimod.samplehgrcs['user']
1840 1841
1841 1842 f = paths[0]
1842 1843 fp = open(f, "wb")
1843 1844 fp.write(util.tonativeeol(samplehgrc))
1844 1845 fp.close()
1845 1846
1846 1847 editor = ui.geteditor()
1847 1848 ui.system("%s \"%s\"" % (editor, f),
1848 1849 onerr=error.Abort, errprefix=_("edit failed"),
1849 1850 blockedtag='config_edit')
1850 1851 return
1851 1852 ui.pager('config')
1852 1853 fm = ui.formatter('config', opts)
1853 1854 for t, f in rcutil.rccomponents():
1854 1855 if t == 'path':
1855 1856 ui.debug('read config from: %s\n' % f)
1856 1857 elif t == 'items':
1857 1858 for section, name, value, source in f:
1858 1859 ui.debug('set config by: %s\n' % source)
1859 1860 else:
1860 1861 raise error.ProgrammingError('unknown rctype: %s' % t)
1861 1862 untrusted = bool(opts.get('untrusted'))
1862 1863
1863 1864 selsections = selentries = []
1864 1865 if values:
1865 1866 selsections = [v for v in values if '.' not in v]
1866 1867 selentries = [v for v in values if '.' in v]
1867 1868 uniquesel = (len(selentries) == 1 and not selsections)
1868 1869 selsections = set(selsections)
1869 1870 selentries = set(selentries)
1870 1871
1871 1872 matched = False
1872 1873 for section, name, value in ui.walkconfig(untrusted=untrusted):
1873 1874 source = ui.configsource(section, name, untrusted)
1874 1875 value = pycompat.bytestr(value)
1875 1876 defaultvalue = ui.configdefault(section, name)
1876 1877 if fm.isplain():
1877 1878 source = source or 'none'
1878 1879 value = value.replace('\n', '\\n')
1879 1880 entryname = section + '.' + name
1880 1881 if values and not (section in selsections or entryname in selentries):
1881 1882 continue
1882 1883 fm.startitem()
1883 1884 fm.condwrite(ui.debugflag, 'source', '%s: ', source)
1884 1885 if uniquesel:
1885 1886 fm.data(name=entryname)
1886 1887 fm.write('value', '%s\n', value)
1887 1888 else:
1888 1889 fm.write('name value', '%s=%s\n', entryname, value)
1889 1890 fm.data(defaultvalue=defaultvalue)
1890 1891 matched = True
1891 1892 fm.end()
1892 1893 if matched:
1893 1894 return 0
1894 1895 return 1
1895 1896
1896 1897 @command('continue',
1897 1898 dryrunopts, helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
1898 1899 helpbasic=True)
1899 1900 def continuecmd(ui, repo, **opts):
1900 1901 """resumes an interrupted operation (EXPERIMENTAL)
1901 1902
1902 1903 Finishes a multistep operation like graft, histedit, rebase, merge,
1903 1904 and unshelve if they are in an interrupted state.
1904 1905
1905 1906 use --dry-run/-n to dry run the command.
1906 1907 """
1907 1908 dryrun = opts.get(r'dry_run')
1908 1909 contstate = cmdutil.getunfinishedstate(repo)
1909 1910 if not contstate:
1910 1911 raise error.Abort(_('no operation in progress'))
1911 1912 if not contstate.continuefunc:
1912 1913 raise error.Abort((_("%s in progress but does not support "
1913 1914 "'hg continue'") % (contstate._opname)),
1914 1915 hint=contstate.continuemsg())
1915 1916 if dryrun:
1916 1917 ui.status(_('%s in progress, will be resumed\n') % (contstate._opname))
1917 1918 return
1918 1919 return contstate.continuefunc(ui, repo)
1919 1920
1920 1921 @command('copy|cp',
1921 1922 [('A', 'after', None, _('record a copy that has already occurred')),
1922 1923 ('f', 'force', None, _('forcibly copy over an existing managed file')),
1923 1924 ] + walkopts + dryrunopts,
1924 1925 _('[OPTION]... SOURCE... DEST'),
1925 1926 helpcategory=command.CATEGORY_FILE_CONTENTS)
1926 1927 def copy(ui, repo, *pats, **opts):
1927 1928 """mark files as copied for the next commit
1928 1929
1929 1930 Mark dest as having copies of source files. If dest is a
1930 1931 directory, copies are put in that directory. If dest is a file,
1931 1932 the source must be a single file.
1932 1933
1933 1934 By default, this command copies the contents of files as they
1934 1935 exist in the working directory. If invoked with -A/--after, the
1935 1936 operation is recorded, but no copying is performed.
1936 1937
1937 1938 This command takes effect with the next commit. To undo a copy
1938 1939 before that, see :hg:`revert`.
1939 1940
1940 1941 Returns 0 on success, 1 if errors are encountered.
1941 1942 """
1942 1943 opts = pycompat.byteskwargs(opts)
1943 1944 with repo.wlock(False):
1944 1945 return cmdutil.copy(ui, repo, pats, opts)
1945 1946
1946 1947 @command(
1947 1948 'debugcommands', [], _('[COMMAND]'),
1948 1949 helpcategory=command.CATEGORY_HELP,
1949 1950 norepo=True)
1950 1951 def debugcommands(ui, cmd='', *args):
1951 1952 """list all available commands and options"""
1952 1953 for cmd, vals in sorted(table.iteritems()):
1953 1954 cmd = cmd.split('|')[0]
1954 1955 opts = ', '.join([i[1] for i in vals[1]])
1955 1956 ui.write('%s: %s\n' % (cmd, opts))
1956 1957
1957 1958 @command('debugcomplete',
1958 1959 [('o', 'options', None, _('show the command options'))],
1959 1960 _('[-o] CMD'),
1960 1961 helpcategory=command.CATEGORY_HELP,
1961 1962 norepo=True)
1962 1963 def debugcomplete(ui, cmd='', **opts):
1963 1964 """returns the completion list associated with the given command"""
1964 1965
1965 1966 if opts.get(r'options'):
1966 1967 options = []
1967 1968 otables = [globalopts]
1968 1969 if cmd:
1969 1970 aliases, entry = cmdutil.findcmd(cmd, table, False)
1970 1971 otables.append(entry[1])
1971 1972 for t in otables:
1972 1973 for o in t:
1973 1974 if "(DEPRECATED)" in o[3]:
1974 1975 continue
1975 1976 if o[0]:
1976 1977 options.append('-%s' % o[0])
1977 1978 options.append('--%s' % o[1])
1978 1979 ui.write("%s\n" % "\n".join(options))
1979 1980 return
1980 1981
1981 1982 cmdlist, unused_allcmds = cmdutil.findpossible(cmd, table)
1982 1983 if ui.verbose:
1983 1984 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
1984 1985 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
1985 1986
1986 1987 @command('diff',
1987 1988 [('r', 'rev', [], _('revision'), _('REV')),
1988 1989 ('c', 'change', '', _('change made by revision'), _('REV'))
1989 1990 ] + diffopts + diffopts2 + walkopts + subrepoopts,
1990 1991 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'),
1991 1992 helpcategory=command.CATEGORY_FILE_CONTENTS,
1992 1993 helpbasic=True, inferrepo=True, intents={INTENT_READONLY})
1993 1994 def diff(ui, repo, *pats, **opts):
1994 1995 """diff repository (or selected files)
1995 1996
1996 1997 Show differences between revisions for the specified files.
1997 1998
1998 1999 Differences between files are shown using the unified diff format.
1999 2000
2000 2001 .. note::
2001 2002
2002 2003 :hg:`diff` may generate unexpected results for merges, as it will
2003 2004 default to comparing against the working directory's first
2004 2005 parent changeset if no revisions are specified.
2005 2006
2006 2007 When two revision arguments are given, then changes are shown
2007 2008 between those revisions. If only one revision is specified then
2008 2009 that revision is compared to the working directory, and, when no
2009 2010 revisions are specified, the working directory files are compared
2010 2011 to its first parent.
2011 2012
2012 2013 Alternatively you can specify -c/--change with a revision to see
2013 2014 the changes in that changeset relative to its first parent.
2014 2015
2015 2016 Without the -a/--text option, diff will avoid generating diffs of
2016 2017 files it detects as binary. With -a, diff will generate a diff
2017 2018 anyway, probably with undesirable results.
2018 2019
2019 2020 Use the -g/--git option to generate diffs in the git extended diff
2020 2021 format. For more information, read :hg:`help diffs`.
2021 2022
2022 2023 .. container:: verbose
2023 2024
2024 2025 Examples:
2025 2026
2026 2027 - compare a file in the current working directory to its parent::
2027 2028
2028 2029 hg diff foo.c
2029 2030
2030 2031 - compare two historical versions of a directory, with rename info::
2031 2032
2032 2033 hg diff --git -r 1.0:1.2 lib/
2033 2034
2034 2035 - get change stats relative to the last change on some date::
2035 2036
2036 2037 hg diff --stat -r "date('may 2')"
2037 2038
2038 2039 - diff all newly-added files that contain a keyword::
2039 2040
2040 2041 hg diff "set:added() and grep(GNU)"
2041 2042
2042 2043 - compare a revision and its parents::
2043 2044
2044 2045 hg diff -c 9353 # compare against first parent
2045 2046 hg diff -r 9353^:9353 # same using revset syntax
2046 2047 hg diff -r 9353^2:9353 # compare against the second parent
2047 2048
2048 2049 Returns 0 on success.
2049 2050 """
2050 2051
2051 2052 opts = pycompat.byteskwargs(opts)
2052 2053 revs = opts.get('rev')
2053 2054 change = opts.get('change')
2054 2055 stat = opts.get('stat')
2055 2056 reverse = opts.get('reverse')
2056 2057
2057 2058 if revs and change:
2058 2059 msg = _('cannot specify --rev and --change at the same time')
2059 2060 raise error.Abort(msg)
2060 2061 elif change:
2061 2062 repo = scmutil.unhidehashlikerevs(repo, [change], 'nowarn')
2062 2063 ctx2 = scmutil.revsingle(repo, change, None)
2063 2064 ctx1 = ctx2.p1()
2064 2065 else:
2065 2066 repo = scmutil.unhidehashlikerevs(repo, revs, 'nowarn')
2066 2067 ctx1, ctx2 = scmutil.revpair(repo, revs)
2067 2068 node1, node2 = ctx1.node(), ctx2.node()
2068 2069
2069 2070 if reverse:
2070 2071 node1, node2 = node2, node1
2071 2072
2072 2073 diffopts = patch.diffallopts(ui, opts)
2073 2074 m = scmutil.match(ctx2, pats, opts)
2074 2075 m = repo.narrowmatch(m)
2075 2076 ui.pager('diff')
2076 2077 logcmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
2077 2078 listsubrepos=opts.get('subrepos'),
2078 2079 root=opts.get('root'))
2079 2080
2080 2081 @command('export',
2081 2082 [('B', 'bookmark', '',
2082 2083 _('export changes only reachable by given bookmark'), _('BOOKMARK')),
2083 2084 ('o', 'output', '',
2084 2085 _('print output to file with formatted name'), _('FORMAT')),
2085 2086 ('', 'switch-parent', None, _('diff against the second parent')),
2086 2087 ('r', 'rev', [], _('revisions to export'), _('REV')),
2087 2088 ] + diffopts + formatteropts,
2088 2089 _('[OPTION]... [-o OUTFILESPEC] [-r] [REV]...'),
2089 2090 helpcategory=command.CATEGORY_IMPORT_EXPORT,
2090 2091 helpbasic=True, intents={INTENT_READONLY})
2091 2092 def export(ui, repo, *changesets, **opts):
2092 2093 """dump the header and diffs for one or more changesets
2093 2094
2094 2095 Print the changeset header and diffs for one or more revisions.
2095 2096 If no revision is given, the parent of the working directory is used.
2096 2097
2097 2098 The information shown in the changeset header is: author, date,
2098 2099 branch name (if non-default), changeset hash, parent(s) and commit
2099 2100 comment.
2100 2101
2101 2102 .. note::
2102 2103
2103 2104 :hg:`export` may generate unexpected diff output for merge
2104 2105 changesets, as it will compare the merge changeset against its
2105 2106 first parent only.
2106 2107
2107 2108 Output may be to a file, in which case the name of the file is
2108 2109 given using a template string. See :hg:`help templates`. In addition
2109 2110 to the common template keywords, the following formatting rules are
2110 2111 supported:
2111 2112
2112 2113 :``%%``: literal "%" character
2113 2114 :``%H``: changeset hash (40 hexadecimal digits)
2114 2115 :``%N``: number of patches being generated
2115 2116 :``%R``: changeset revision number
2116 2117 :``%b``: basename of the exporting repository
2117 2118 :``%h``: short-form changeset hash (12 hexadecimal digits)
2118 2119 :``%m``: first line of the commit message (only alphanumeric characters)
2119 2120 :``%n``: zero-padded sequence number, starting at 1
2120 2121 :``%r``: zero-padded changeset revision number
2121 2122 :``\\``: literal "\\" character
2122 2123
2123 2124 Without the -a/--text option, export will avoid generating diffs
2124 2125 of files it detects as binary. With -a, export will generate a
2125 2126 diff anyway, probably with undesirable results.
2126 2127
2127 2128 With -B/--bookmark changesets reachable by the given bookmark are
2128 2129 selected.
2129 2130
2130 2131 Use the -g/--git option to generate diffs in the git extended diff
2131 2132 format. See :hg:`help diffs` for more information.
2132 2133
2133 2134 With the --switch-parent option, the diff will be against the
2134 2135 second parent. It can be useful to review a merge.
2135 2136
2136 2137 .. container:: verbose
2137 2138
2138 2139 Template:
2139 2140
2140 2141 The following keywords are supported in addition to the common template
2141 2142 keywords and functions. See also :hg:`help templates`.
2142 2143
2143 2144 :diff: String. Diff content.
2144 2145 :parents: List of strings. Parent nodes of the changeset.
2145 2146
2146 2147 Examples:
2147 2148
2148 2149 - use export and import to transplant a bugfix to the current
2149 2150 branch::
2150 2151
2151 2152 hg export -r 9353 | hg import -
2152 2153
2153 2154 - export all the changesets between two revisions to a file with
2154 2155 rename information::
2155 2156
2156 2157 hg export --git -r 123:150 > changes.txt
2157 2158
2158 2159 - split outgoing changes into a series of patches with
2159 2160 descriptive names::
2160 2161
2161 2162 hg export -r "outgoing()" -o "%n-%m.patch"
2162 2163
2163 2164 Returns 0 on success.
2164 2165 """
2165 2166 opts = pycompat.byteskwargs(opts)
2166 2167 bookmark = opts.get('bookmark')
2167 2168 changesets += tuple(opts.get('rev', []))
2168 2169
2169 2170 if bookmark and changesets:
2170 2171 raise error.Abort(_("-r and -B are mutually exclusive"))
2171 2172
2172 2173 if bookmark:
2173 2174 if bookmark not in repo._bookmarks:
2174 2175 raise error.Abort(_("bookmark '%s' not found") % bookmark)
2175 2176
2176 2177 revs = scmutil.bookmarkrevs(repo, bookmark)
2177 2178 else:
2178 2179 if not changesets:
2179 2180 changesets = ['.']
2180 2181
2181 2182 repo = scmutil.unhidehashlikerevs(repo, changesets, 'nowarn')
2182 2183 revs = scmutil.revrange(repo, changesets)
2183 2184
2184 2185 if not revs:
2185 2186 raise error.Abort(_("export requires at least one changeset"))
2186 2187 if len(revs) > 1:
2187 2188 ui.note(_('exporting patches:\n'))
2188 2189 else:
2189 2190 ui.note(_('exporting patch:\n'))
2190 2191
2191 2192 fntemplate = opts.get('output')
2192 2193 if cmdutil.isstdiofilename(fntemplate):
2193 2194 fntemplate = ''
2194 2195
2195 2196 if fntemplate:
2196 2197 fm = formatter.nullformatter(ui, 'export', opts)
2197 2198 else:
2198 2199 ui.pager('export')
2199 2200 fm = ui.formatter('export', opts)
2200 2201 with fm:
2201 2202 cmdutil.export(repo, revs, fm, fntemplate=fntemplate,
2202 2203 switch_parent=opts.get('switch_parent'),
2203 2204 opts=patch.diffallopts(ui, opts))
2204 2205
2205 2206 @command('files',
2206 2207 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
2207 2208 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
2208 2209 ] + walkopts + formatteropts + subrepoopts,
2209 2210 _('[OPTION]... [FILE]...'),
2210 2211 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2211 2212 intents={INTENT_READONLY})
2212 2213 def files(ui, repo, *pats, **opts):
2213 2214 """list tracked files
2214 2215
2215 2216 Print files under Mercurial control in the working directory or
2216 2217 specified revision for given files (excluding removed files).
2217 2218 Files can be specified as filenames or filesets.
2218 2219
2219 2220 If no files are given to match, this command prints the names
2220 2221 of all files under Mercurial control.
2221 2222
2222 2223 .. container:: verbose
2223 2224
2224 2225 Template:
2225 2226
2226 2227 The following keywords are supported in addition to the common template
2227 2228 keywords and functions. See also :hg:`help templates`.
2228 2229
2229 2230 :flags: String. Character denoting file's symlink and executable bits.
2230 2231 :path: String. Repository-absolute path of the file.
2231 2232 :size: Integer. Size of the file in bytes.
2232 2233
2233 2234 Examples:
2234 2235
2235 2236 - list all files under the current directory::
2236 2237
2237 2238 hg files .
2238 2239
2239 2240 - shows sizes and flags for current revision::
2240 2241
2241 2242 hg files -vr .
2242 2243
2243 2244 - list all files named README::
2244 2245
2245 2246 hg files -I "**/README"
2246 2247
2247 2248 - list all binary files::
2248 2249
2249 2250 hg files "set:binary()"
2250 2251
2251 2252 - find files containing a regular expression::
2252 2253
2253 2254 hg files "set:grep('bob')"
2254 2255
2255 2256 - search tracked file contents with xargs and grep::
2256 2257
2257 2258 hg files -0 | xargs -0 grep foo
2258 2259
2259 2260 See :hg:`help patterns` and :hg:`help filesets` for more information
2260 2261 on specifying file patterns.
2261 2262
2262 2263 Returns 0 if a match is found, 1 otherwise.
2263 2264
2264 2265 """
2265 2266
2266 2267 opts = pycompat.byteskwargs(opts)
2267 2268 rev = opts.get('rev')
2268 2269 if rev:
2269 2270 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
2270 2271 ctx = scmutil.revsingle(repo, rev, None)
2271 2272
2272 2273 end = '\n'
2273 2274 if opts.get('print0'):
2274 2275 end = '\0'
2275 2276 fmt = '%s' + end
2276 2277
2277 2278 m = scmutil.match(ctx, pats, opts)
2278 2279 ui.pager('files')
2279 2280 uipathfn = scmutil.getuipathfn(ctx.repo(), legacyrelativevalue=True)
2280 2281 with ui.formatter('files', opts) as fm:
2281 2282 return cmdutil.files(ui, ctx, m, uipathfn, fm, fmt,
2282 2283 opts.get('subrepos'))
2283 2284
2284 2285 @command(
2285 2286 'forget',
2286 2287 [('i', 'interactive', None, _('use interactive mode')),
2287 2288 ] + walkopts + dryrunopts,
2288 2289 _('[OPTION]... FILE...'),
2289 2290 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2290 2291 helpbasic=True, inferrepo=True)
2291 2292 def forget(ui, repo, *pats, **opts):
2292 2293 """forget the specified files on the next commit
2293 2294
2294 2295 Mark the specified files so they will no longer be tracked
2295 2296 after the next commit.
2296 2297
2297 2298 This only removes files from the current branch, not from the
2298 2299 entire project history, and it does not delete them from the
2299 2300 working directory.
2300 2301
2301 2302 To delete the file from the working directory, see :hg:`remove`.
2302 2303
2303 2304 To undo a forget before the next commit, see :hg:`add`.
2304 2305
2305 2306 .. container:: verbose
2306 2307
2307 2308 Examples:
2308 2309
2309 2310 - forget newly-added binary files::
2310 2311
2311 2312 hg forget "set:added() and binary()"
2312 2313
2313 2314 - forget files that would be excluded by .hgignore::
2314 2315
2315 2316 hg forget "set:hgignore()"
2316 2317
2317 2318 Returns 0 on success.
2318 2319 """
2319 2320
2320 2321 opts = pycompat.byteskwargs(opts)
2321 2322 if not pats:
2322 2323 raise error.Abort(_('no files specified'))
2323 2324
2324 2325 m = scmutil.match(repo[None], pats, opts)
2325 2326 dryrun, interactive = opts.get('dry_run'), opts.get('interactive')
2326 2327 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
2327 2328 rejected = cmdutil.forget(ui, repo, m, prefix="", uipathfn=uipathfn,
2328 2329 explicitonly=False, dryrun=dryrun,
2329 2330 interactive=interactive)[0]
2330 2331 return rejected and 1 or 0
2331 2332
2332 2333 @command(
2333 2334 'graft',
2334 2335 [('r', 'rev', [], _('revisions to graft'), _('REV')),
2335 2336 ('', 'base', '',
2336 2337 _('base revision when doing the graft merge (ADVANCED)'), _('REV')),
2337 2338 ('c', 'continue', False, _('resume interrupted graft')),
2338 2339 ('', 'stop', False, _('stop interrupted graft')),
2339 2340 ('', 'abort', False, _('abort interrupted graft')),
2340 2341 ('e', 'edit', False, _('invoke editor on commit messages')),
2341 2342 ('', 'log', None, _('append graft info to log message')),
2342 2343 ('', 'no-commit', None,
2343 2344 _("don't commit, just apply the changes in working directory")),
2344 2345 ('f', 'force', False, _('force graft')),
2345 2346 ('D', 'currentdate', False,
2346 2347 _('record the current date as commit date')),
2347 2348 ('U', 'currentuser', False,
2348 2349 _('record the current user as committer'))]
2349 2350 + commitopts2 + mergetoolopts + dryrunopts,
2350 2351 _('[OPTION]... [-r REV]... REV...'),
2351 2352 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT)
2352 2353 def graft(ui, repo, *revs, **opts):
2353 2354 '''copy changes from other branches onto the current branch
2354 2355
2355 2356 This command uses Mercurial's merge logic to copy individual
2356 2357 changes from other branches without merging branches in the
2357 2358 history graph. This is sometimes known as 'backporting' or
2358 2359 'cherry-picking'. By default, graft will copy user, date, and
2359 2360 description from the source changesets.
2360 2361
2361 2362 Changesets that are ancestors of the current revision, that have
2362 2363 already been grafted, or that are merges will be skipped.
2363 2364
2364 2365 If --log is specified, log messages will have a comment appended
2365 2366 of the form::
2366 2367
2367 2368 (grafted from CHANGESETHASH)
2368 2369
2369 2370 If --force is specified, revisions will be grafted even if they
2370 2371 are already ancestors of, or have been grafted to, the destination.
2371 2372 This is useful when the revisions have since been backed out.
2372 2373
2373 2374 If a graft merge results in conflicts, the graft process is
2374 2375 interrupted so that the current merge can be manually resolved.
2375 2376 Once all conflicts are addressed, the graft process can be
2376 2377 continued with the -c/--continue option.
2377 2378
2378 2379 The -c/--continue option reapplies all the earlier options.
2379 2380
2380 2381 .. container:: verbose
2381 2382
2382 2383 The --base option exposes more of how graft internally uses merge with a
2383 2384 custom base revision. --base can be used to specify another ancestor than
2384 2385 the first and only parent.
2385 2386
2386 2387 The command::
2387 2388
2388 2389 hg graft -r 345 --base 234
2389 2390
2390 2391 is thus pretty much the same as::
2391 2392
2392 2393 hg diff -r 234 -r 345 | hg import
2393 2394
2394 2395 but using merge to resolve conflicts and track moved files.
2395 2396
2396 2397 The result of a merge can thus be backported as a single commit by
2397 2398 specifying one of the merge parents as base, and thus effectively
2398 2399 grafting the changes from the other side.
2399 2400
2400 2401 It is also possible to collapse multiple changesets and clean up history
2401 2402 by specifying another ancestor as base, much like rebase --collapse
2402 2403 --keep.
2403 2404
2404 2405 The commit message can be tweaked after the fact using commit --amend .
2405 2406
2406 2407 For using non-ancestors as the base to backout changes, see the backout
2407 2408 command and the hidden --parent option.
2408 2409
2409 2410 .. container:: verbose
2410 2411
2411 2412 Examples:
2412 2413
2413 2414 - copy a single change to the stable branch and edit its description::
2414 2415
2415 2416 hg update stable
2416 2417 hg graft --edit 9393
2417 2418
2418 2419 - graft a range of changesets with one exception, updating dates::
2419 2420
2420 2421 hg graft -D "2085::2093 and not 2091"
2421 2422
2422 2423 - continue a graft after resolving conflicts::
2423 2424
2424 2425 hg graft -c
2425 2426
2426 2427 - show the source of a grafted changeset::
2427 2428
2428 2429 hg log --debug -r .
2429 2430
2430 2431 - show revisions sorted by date::
2431 2432
2432 2433 hg log -r "sort(all(), date)"
2433 2434
2434 2435 - backport the result of a merge as a single commit::
2435 2436
2436 2437 hg graft -r 123 --base 123^
2437 2438
2438 2439 - land a feature branch as one changeset::
2439 2440
2440 2441 hg up -cr default
2441 2442 hg graft -r featureX --base "ancestor('featureX', 'default')"
2442 2443
2443 2444 See :hg:`help revisions` for more about specifying revisions.
2444 2445
2445 2446 Returns 0 on successful completion.
2446 2447 '''
2447 2448 with repo.wlock():
2448 2449 return _dograft(ui, repo, *revs, **opts)
2449 2450
2450 2451 def _dograft(ui, repo, *revs, **opts):
2451 2452 opts = pycompat.byteskwargs(opts)
2452 2453 if revs and opts.get('rev'):
2453 2454 ui.warn(_('warning: inconsistent use of --rev might give unexpected '
2454 2455 'revision ordering!\n'))
2455 2456
2456 2457 revs = list(revs)
2457 2458 revs.extend(opts.get('rev'))
2458 2459 basectx = None
2459 2460 if opts.get('base'):
2460 2461 basectx = scmutil.revsingle(repo, opts['base'], None)
2461 2462 # a dict of data to be stored in state file
2462 2463 statedata = {}
2463 2464 # list of new nodes created by ongoing graft
2464 2465 statedata['newnodes'] = []
2465 2466
2466 2467 if opts.get('user') and opts.get('currentuser'):
2467 2468 raise error.Abort(_('--user and --currentuser are mutually exclusive'))
2468 2469 if opts.get('date') and opts.get('currentdate'):
2469 2470 raise error.Abort(_('--date and --currentdate are mutually exclusive'))
2470 2471 if not opts.get('user') and opts.get('currentuser'):
2471 2472 opts['user'] = ui.username()
2472 2473 if not opts.get('date') and opts.get('currentdate'):
2473 2474 opts['date'] = "%d %d" % dateutil.makedate()
2474 2475
2475 2476 editor = cmdutil.getcommiteditor(editform='graft',
2476 2477 **pycompat.strkwargs(opts))
2477 2478
2478 2479 cont = False
2479 2480 if opts.get('no_commit'):
2480 2481 if opts.get('edit'):
2481 2482 raise error.Abort(_("cannot specify --no-commit and "
2482 2483 "--edit together"))
2483 2484 if opts.get('currentuser'):
2484 2485 raise error.Abort(_("cannot specify --no-commit and "
2485 2486 "--currentuser together"))
2486 2487 if opts.get('currentdate'):
2487 2488 raise error.Abort(_("cannot specify --no-commit and "
2488 2489 "--currentdate together"))
2489 2490 if opts.get('log'):
2490 2491 raise error.Abort(_("cannot specify --no-commit and "
2491 2492 "--log together"))
2492 2493
2493 2494 graftstate = statemod.cmdstate(repo, 'graftstate')
2494 2495
2495 2496 if opts.get('stop'):
2496 2497 if opts.get('continue'):
2497 2498 raise error.Abort(_("cannot use '--continue' and "
2498 2499 "'--stop' together"))
2499 2500 if opts.get('abort'):
2500 2501 raise error.Abort(_("cannot use '--abort' and '--stop' together"))
2501 2502
2502 2503 if any((opts.get('edit'), opts.get('log'), opts.get('user'),
2503 2504 opts.get('date'), opts.get('currentdate'),
2504 2505 opts.get('currentuser'), opts.get('rev'))):
2505 2506 raise error.Abort(_("cannot specify any other flag with '--stop'"))
2506 2507 return _stopgraft(ui, repo, graftstate)
2507 2508 elif opts.get('abort'):
2508 2509 if opts.get('continue'):
2509 2510 raise error.Abort(_("cannot use '--continue' and "
2510 2511 "'--abort' together"))
2511 2512 if any((opts.get('edit'), opts.get('log'), opts.get('user'),
2512 2513 opts.get('date'), opts.get('currentdate'),
2513 2514 opts.get('currentuser'), opts.get('rev'))):
2514 2515 raise error.Abort(_("cannot specify any other flag with '--abort'"))
2515 2516
2516 2517 return cmdutil.abortgraft(ui, repo, graftstate)
2517 2518 elif opts.get('continue'):
2518 2519 cont = True
2519 2520 if revs:
2520 2521 raise error.Abort(_("can't specify --continue and revisions"))
2521 2522 # read in unfinished revisions
2522 2523 if graftstate.exists():
2523 2524 statedata = cmdutil.readgraftstate(repo, graftstate)
2524 2525 if statedata.get('date'):
2525 2526 opts['date'] = statedata['date']
2526 2527 if statedata.get('user'):
2527 2528 opts['user'] = statedata['user']
2528 2529 if statedata.get('log'):
2529 2530 opts['log'] = True
2530 2531 if statedata.get('no_commit'):
2531 2532 opts['no_commit'] = statedata.get('no_commit')
2532 2533 nodes = statedata['nodes']
2533 2534 revs = [repo[node].rev() for node in nodes]
2534 2535 else:
2535 2536 cmdutil.wrongtooltocontinue(repo, _('graft'))
2536 2537 else:
2537 2538 if not revs:
2538 2539 raise error.Abort(_('no revisions specified'))
2539 2540 cmdutil.checkunfinished(repo)
2540 2541 cmdutil.bailifchanged(repo)
2541 2542 revs = scmutil.revrange(repo, revs)
2542 2543
2543 2544 skipped = set()
2544 2545 if basectx is None:
2545 2546 # check for merges
2546 2547 for rev in repo.revs('%ld and merge()', revs):
2547 2548 ui.warn(_('skipping ungraftable merge revision %d\n') % rev)
2548 2549 skipped.add(rev)
2549 2550 revs = [r for r in revs if r not in skipped]
2550 2551 if not revs:
2551 2552 return -1
2552 2553 if basectx is not None and len(revs) != 1:
2553 2554 raise error.Abort(_('only one revision allowed with --base '))
2554 2555
2555 2556 # Don't check in the --continue case, in effect retaining --force across
2556 2557 # --continues. That's because without --force, any revisions we decided to
2557 2558 # skip would have been filtered out here, so they wouldn't have made their
2558 2559 # way to the graftstate. With --force, any revisions we would have otherwise
2559 2560 # skipped would not have been filtered out, and if they hadn't been applied
2560 2561 # already, they'd have been in the graftstate.
2561 2562 if not (cont or opts.get('force')) and basectx is None:
2562 2563 # check for ancestors of dest branch
2563 2564 crev = repo['.'].rev()
2564 2565 ancestors = repo.changelog.ancestors([crev], inclusive=True)
2565 2566 # XXX make this lazy in the future
2566 2567 # don't mutate while iterating, create a copy
2567 2568 for rev in list(revs):
2568 2569 if rev in ancestors:
2569 2570 ui.warn(_('skipping ancestor revision %d:%s\n') %
2570 2571 (rev, repo[rev]))
2571 2572 # XXX remove on list is slow
2572 2573 revs.remove(rev)
2573 2574 if not revs:
2574 2575 return -1
2575 2576
2576 2577 # analyze revs for earlier grafts
2577 2578 ids = {}
2578 2579 for ctx in repo.set("%ld", revs):
2579 2580 ids[ctx.hex()] = ctx.rev()
2580 2581 n = ctx.extra().get('source')
2581 2582 if n:
2582 2583 ids[n] = ctx.rev()
2583 2584
2584 2585 # check ancestors for earlier grafts
2585 2586 ui.debug('scanning for duplicate grafts\n')
2586 2587
2587 2588 # The only changesets we can be sure doesn't contain grafts of any
2588 2589 # revs, are the ones that are common ancestors of *all* revs:
2589 2590 for rev in repo.revs('only(%d,ancestor(%ld))', crev, revs):
2590 2591 ctx = repo[rev]
2591 2592 n = ctx.extra().get('source')
2592 2593 if n in ids:
2593 2594 try:
2594 2595 r = repo[n].rev()
2595 2596 except error.RepoLookupError:
2596 2597 r = None
2597 2598 if r in revs:
2598 2599 ui.warn(_('skipping revision %d:%s '
2599 2600 '(already grafted to %d:%s)\n')
2600 2601 % (r, repo[r], rev, ctx))
2601 2602 revs.remove(r)
2602 2603 elif ids[n] in revs:
2603 2604 if r is None:
2604 2605 ui.warn(_('skipping already grafted revision %d:%s '
2605 2606 '(%d:%s also has unknown origin %s)\n')
2606 2607 % (ids[n], repo[ids[n]], rev, ctx, n[:12]))
2607 2608 else:
2608 2609 ui.warn(_('skipping already grafted revision %d:%s '
2609 2610 '(%d:%s also has origin %d:%s)\n')
2610 2611 % (ids[n], repo[ids[n]], rev, ctx, r, n[:12]))
2611 2612 revs.remove(ids[n])
2612 2613 elif ctx.hex() in ids:
2613 2614 r = ids[ctx.hex()]
2614 2615 if r in revs:
2615 2616 ui.warn(_('skipping already grafted revision %d:%s '
2616 2617 '(was grafted from %d:%s)\n') %
2617 2618 (r, repo[r], rev, ctx))
2618 2619 revs.remove(r)
2619 2620 if not revs:
2620 2621 return -1
2621 2622
2622 2623 if opts.get('no_commit'):
2623 2624 statedata['no_commit'] = True
2624 2625 for pos, ctx in enumerate(repo.set("%ld", revs)):
2625 2626 desc = '%d:%s "%s"' % (ctx.rev(), ctx,
2626 2627 ctx.description().split('\n', 1)[0])
2627 2628 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
2628 2629 if names:
2629 2630 desc += ' (%s)' % ' '.join(names)
2630 2631 ui.status(_('grafting %s\n') % desc)
2631 2632 if opts.get('dry_run'):
2632 2633 continue
2633 2634
2634 2635 source = ctx.extra().get('source')
2635 2636 extra = {}
2636 2637 if source:
2637 2638 extra['source'] = source
2638 2639 extra['intermediate-source'] = ctx.hex()
2639 2640 else:
2640 2641 extra['source'] = ctx.hex()
2641 2642 user = ctx.user()
2642 2643 if opts.get('user'):
2643 2644 user = opts['user']
2644 2645 statedata['user'] = user
2645 2646 date = ctx.date()
2646 2647 if opts.get('date'):
2647 2648 date = opts['date']
2648 2649 statedata['date'] = date
2649 2650 message = ctx.description()
2650 2651 if opts.get('log'):
2651 2652 message += '\n(grafted from %s)' % ctx.hex()
2652 2653 statedata['log'] = True
2653 2654
2654 2655 # we don't merge the first commit when continuing
2655 2656 if not cont:
2656 2657 # perform the graft merge with p1(rev) as 'ancestor'
2657 2658 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
2658 2659 base = ctx.p1() if basectx is None else basectx
2659 2660 with ui.configoverride(overrides, 'graft'):
2660 2661 stats = mergemod.graft(repo, ctx, base, ['local', 'graft'])
2661 2662 # report any conflicts
2662 2663 if stats.unresolvedcount > 0:
2663 2664 # write out state for --continue
2664 2665 nodes = [repo[rev].hex() for rev in revs[pos:]]
2665 2666 statedata['nodes'] = nodes
2666 2667 stateversion = 1
2667 2668 graftstate.save(stateversion, statedata)
2668 2669 hint = _("use 'hg resolve' and 'hg graft --continue'")
2669 2670 raise error.Abort(
2670 2671 _("unresolved conflicts, can't continue"),
2671 2672 hint=hint)
2672 2673 else:
2673 2674 cont = False
2674 2675
2675 2676 # commit if --no-commit is false
2676 2677 if not opts.get('no_commit'):
2677 2678 node = repo.commit(text=message, user=user, date=date, extra=extra,
2678 2679 editor=editor)
2679 2680 if node is None:
2680 2681 ui.warn(
2681 2682 _('note: graft of %d:%s created no changes to commit\n') %
2682 2683 (ctx.rev(), ctx))
2683 2684 # checking that newnodes exist because old state files won't have it
2684 2685 elif statedata.get('newnodes') is not None:
2685 2686 statedata['newnodes'].append(node)
2686 2687
2687 2688 # remove state when we complete successfully
2688 2689 if not opts.get('dry_run'):
2689 2690 graftstate.delete()
2690 2691
2691 2692 return 0
2692 2693
2693 2694 def _stopgraft(ui, repo, graftstate):
2694 2695 """stop the interrupted graft"""
2695 2696 if not graftstate.exists():
2696 2697 raise error.Abort(_("no interrupted graft found"))
2697 2698 pctx = repo['.']
2698 2699 hg.updaterepo(repo, pctx.node(), overwrite=True)
2699 2700 graftstate.delete()
2700 2701 ui.status(_("stopped the interrupted graft\n"))
2701 2702 ui.status(_("working directory is now at %s\n") % pctx.hex()[:12])
2702 2703 return 0
2703 2704
2704 2705 statemod.addunfinished(
2705 2706 'graft', fname='graftstate', clearable=True, stopflag=True,
2706 2707 continueflag=True, abortfunc=cmdutil.hgabortgraft,
2707 2708 cmdhint=_("use 'hg graft --continue' or 'hg graft --stop' to stop")
2708 2709 )
2709 2710
2710 2711 @command('grep',
2711 2712 [('0', 'print0', None, _('end fields with NUL')),
2712 2713 ('', 'all', None, _('print all revisions that match (DEPRECATED) ')),
2713 2714 ('', 'diff', None, _('print all revisions when the term was introduced '
2714 2715 'or removed')),
2715 2716 ('a', 'text', None, _('treat all files as text')),
2716 2717 ('f', 'follow', None,
2717 2718 _('follow changeset history,'
2718 2719 ' or file history across copies and renames')),
2719 2720 ('i', 'ignore-case', None, _('ignore case when matching')),
2720 2721 ('l', 'files-with-matches', None,
2721 2722 _('print only filenames and revisions that match')),
2722 2723 ('n', 'line-number', None, _('print matching line numbers')),
2723 2724 ('r', 'rev', [],
2724 2725 _('only search files changed within revision range'), _('REV')),
2725 2726 ('', 'all-files', None,
2726 2727 _('include all files in the changeset while grepping (EXPERIMENTAL)')),
2727 2728 ('u', 'user', None, _('list the author (long with -v)')),
2728 2729 ('d', 'date', None, _('list the date (short with -q)')),
2729 2730 ] + formatteropts + walkopts,
2730 2731 _('[OPTION]... PATTERN [FILE]...'),
2731 2732 helpcategory=command.CATEGORY_FILE_CONTENTS,
2732 2733 inferrepo=True,
2733 2734 intents={INTENT_READONLY})
2734 2735 def grep(ui, repo, pattern, *pats, **opts):
2735 2736 """search revision history for a pattern in specified files
2736 2737
2737 2738 Search revision history for a regular expression in the specified
2738 2739 files or the entire project.
2739 2740
2740 2741 By default, grep prints the most recent revision number for each
2741 2742 file in which it finds a match. To get it to print every revision
2742 2743 that contains a change in match status ("-" for a match that becomes
2743 2744 a non-match, or "+" for a non-match that becomes a match), use the
2744 2745 --diff flag.
2745 2746
2746 2747 PATTERN can be any Python (roughly Perl-compatible) regular
2747 2748 expression.
2748 2749
2749 2750 If no FILEs are specified (and -f/--follow isn't set), all files in
2750 2751 the repository are searched, including those that don't exist in the
2751 2752 current branch or have been deleted in a prior changeset.
2752 2753
2753 2754 .. container:: verbose
2754 2755
2755 2756 Template:
2756 2757
2757 2758 The following keywords are supported in addition to the common template
2758 2759 keywords and functions. See also :hg:`help templates`.
2759 2760
2760 2761 :change: String. Character denoting insertion ``+`` or removal ``-``.
2761 2762 Available if ``--diff`` is specified.
2762 2763 :lineno: Integer. Line number of the match.
2763 2764 :path: String. Repository-absolute path of the file.
2764 2765 :texts: List of text chunks.
2765 2766
2766 2767 And each entry of ``{texts}`` provides the following sub-keywords.
2767 2768
2768 2769 :matched: Boolean. True if the chunk matches the specified pattern.
2769 2770 :text: String. Chunk content.
2770 2771
2771 2772 See :hg:`help templates.operators` for the list expansion syntax.
2772 2773
2773 2774 Returns 0 if a match is found, 1 otherwise.
2774 2775 """
2775 2776 opts = pycompat.byteskwargs(opts)
2776 2777 diff = opts.get('all') or opts.get('diff')
2777 2778 all_files = opts.get('all_files')
2778 2779 if diff and opts.get('all_files'):
2779 2780 raise error.Abort(_('--diff and --all-files are mutually exclusive'))
2780 2781 # TODO: remove "not opts.get('rev')" if --all-files -rMULTIREV gets working
2781 2782 if opts.get('all_files') is None and not opts.get('rev') and not diff:
2782 2783 # experimental config: commands.grep.all-files
2783 2784 opts['all_files'] = ui.configbool('commands', 'grep.all-files')
2784 2785 plaingrep = opts.get('all_files') and not opts.get('rev')
2785 2786 if plaingrep:
2786 2787 opts['rev'] = ['wdir()']
2787 2788
2788 2789 reflags = re.M
2789 2790 if opts.get('ignore_case'):
2790 2791 reflags |= re.I
2791 2792 try:
2792 2793 regexp = util.re.compile(pattern, reflags)
2793 2794 except re.error as inst:
2794 2795 ui.warn(_("grep: invalid match pattern: %s\n") % pycompat.bytestr(inst))
2795 2796 return 1
2796 2797 sep, eol = ':', '\n'
2797 2798 if opts.get('print0'):
2798 2799 sep = eol = '\0'
2799 2800
2800 2801 getfile = util.lrucachefunc(repo.file)
2801 2802
2802 2803 def matchlines(body):
2803 2804 begin = 0
2804 2805 linenum = 0
2805 2806 while begin < len(body):
2806 2807 match = regexp.search(body, begin)
2807 2808 if not match:
2808 2809 break
2809 2810 mstart, mend = match.span()
2810 2811 linenum += body.count('\n', begin, mstart) + 1
2811 2812 lstart = body.rfind('\n', begin, mstart) + 1 or begin
2812 2813 begin = body.find('\n', mend) + 1 or len(body) + 1
2813 2814 lend = begin - 1
2814 2815 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
2815 2816
2816 2817 class linestate(object):
2817 2818 def __init__(self, line, linenum, colstart, colend):
2818 2819 self.line = line
2819 2820 self.linenum = linenum
2820 2821 self.colstart = colstart
2821 2822 self.colend = colend
2822 2823
2823 2824 def __hash__(self):
2824 2825 return hash((self.linenum, self.line))
2825 2826
2826 2827 def __eq__(self, other):
2827 2828 return self.line == other.line
2828 2829
2829 2830 def findpos(self):
2830 2831 """Iterate all (start, end) indices of matches"""
2831 2832 yield self.colstart, self.colend
2832 2833 p = self.colend
2833 2834 while p < len(self.line):
2834 2835 m = regexp.search(self.line, p)
2835 2836 if not m:
2836 2837 break
2837 2838 yield m.span()
2838 2839 p = m.end()
2839 2840
2840 2841 matches = {}
2841 2842 copies = {}
2842 2843 def grepbody(fn, rev, body):
2843 2844 matches[rev].setdefault(fn, [])
2844 2845 m = matches[rev][fn]
2845 2846 for lnum, cstart, cend, line in matchlines(body):
2846 2847 s = linestate(line, lnum, cstart, cend)
2847 2848 m.append(s)
2848 2849
2849 2850 def difflinestates(a, b):
2850 2851 sm = difflib.SequenceMatcher(None, a, b)
2851 2852 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
2852 2853 if tag == r'insert':
2853 2854 for i in pycompat.xrange(blo, bhi):
2854 2855 yield ('+', b[i])
2855 2856 elif tag == r'delete':
2856 2857 for i in pycompat.xrange(alo, ahi):
2857 2858 yield ('-', a[i])
2858 2859 elif tag == r'replace':
2859 2860 for i in pycompat.xrange(alo, ahi):
2860 2861 yield ('-', a[i])
2861 2862 for i in pycompat.xrange(blo, bhi):
2862 2863 yield ('+', b[i])
2863 2864
2864 2865 uipathfn = scmutil.getuipathfn(repo)
2865 2866 def display(fm, fn, ctx, pstates, states):
2866 2867 rev = scmutil.intrev(ctx)
2867 2868 if fm.isplain():
2868 2869 formatuser = ui.shortuser
2869 2870 else:
2870 2871 formatuser = pycompat.bytestr
2871 2872 if ui.quiet:
2872 2873 datefmt = '%Y-%m-%d'
2873 2874 else:
2874 2875 datefmt = '%a %b %d %H:%M:%S %Y %1%2'
2875 2876 found = False
2876 2877 @util.cachefunc
2877 2878 def binary():
2878 2879 flog = getfile(fn)
2879 2880 try:
2880 2881 return stringutil.binary(flog.read(ctx.filenode(fn)))
2881 2882 except error.WdirUnsupported:
2882 2883 return ctx[fn].isbinary()
2883 2884
2884 2885 fieldnamemap = {'linenumber': 'lineno'}
2885 2886 if diff:
2886 2887 iter = difflinestates(pstates, states)
2887 2888 else:
2888 2889 iter = [('', l) for l in states]
2889 2890 for change, l in iter:
2890 2891 fm.startitem()
2891 2892 fm.context(ctx=ctx)
2892 2893 fm.data(node=fm.hexfunc(scmutil.binnode(ctx)), path=fn)
2893 2894 fm.plain(uipathfn(fn), label='grep.filename')
2894 2895
2895 2896 cols = [
2896 2897 ('rev', '%d', rev, not plaingrep, ''),
2897 2898 ('linenumber', '%d', l.linenum, opts.get('line_number'), ''),
2898 2899 ]
2899 2900 if diff:
2900 2901 cols.append(
2901 2902 ('change', '%s', change, True,
2902 2903 'grep.inserted ' if change == '+' else 'grep.deleted ')
2903 2904 )
2904 2905 cols.extend([
2905 2906 ('user', '%s', formatuser(ctx.user()), opts.get('user'), ''),
2906 2907 ('date', '%s', fm.formatdate(ctx.date(), datefmt),
2907 2908 opts.get('date'), ''),
2908 2909 ])
2909 2910 for name, fmt, data, cond, extra_label in cols:
2910 2911 if cond:
2911 2912 fm.plain(sep, label='grep.sep')
2912 2913 field = fieldnamemap.get(name, name)
2913 2914 label = extra_label + ('grep.%s' % name)
2914 2915 fm.condwrite(cond, field, fmt, data, label=label)
2915 2916 if not opts.get('files_with_matches'):
2916 2917 fm.plain(sep, label='grep.sep')
2917 2918 if not opts.get('text') and binary():
2918 2919 fm.plain(_(" Binary file matches"))
2919 2920 else:
2920 2921 displaymatches(fm.nested('texts', tmpl='{text}'), l)
2921 2922 fm.plain(eol)
2922 2923 found = True
2923 2924 if opts.get('files_with_matches'):
2924 2925 break
2925 2926 return found
2926 2927
2927 2928 def displaymatches(fm, l):
2928 2929 p = 0
2929 2930 for s, e in l.findpos():
2930 2931 if p < s:
2931 2932 fm.startitem()
2932 2933 fm.write('text', '%s', l.line[p:s])
2933 2934 fm.data(matched=False)
2934 2935 fm.startitem()
2935 2936 fm.write('text', '%s', l.line[s:e], label='grep.match')
2936 2937 fm.data(matched=True)
2937 2938 p = e
2938 2939 if p < len(l.line):
2939 2940 fm.startitem()
2940 2941 fm.write('text', '%s', l.line[p:])
2941 2942 fm.data(matched=False)
2942 2943 fm.end()
2943 2944
2944 2945 skip = set()
2945 2946 revfiles = {}
2946 2947 match = scmutil.match(repo[None], pats, opts)
2947 2948 found = False
2948 2949 follow = opts.get('follow')
2949 2950
2950 2951 getrenamed = scmutil.getrenamedfn(repo)
2951 2952 def prep(ctx, fns):
2952 2953 rev = ctx.rev()
2953 2954 pctx = ctx.p1()
2954 2955 parent = pctx.rev()
2955 2956 matches.setdefault(rev, {})
2956 2957 matches.setdefault(parent, {})
2957 2958 files = revfiles.setdefault(rev, [])
2958 2959 for fn in fns:
2959 2960 flog = getfile(fn)
2960 2961 try:
2961 2962 fnode = ctx.filenode(fn)
2962 2963 except error.LookupError:
2963 2964 continue
2964 2965
2965 2966 copy = None
2966 2967 if follow:
2967 2968 copy = getrenamed(fn, rev)
2968 2969 if copy:
2969 2970 copies.setdefault(rev, {})[fn] = copy
2970 2971 if fn in skip:
2971 2972 skip.add(copy)
2972 2973 if fn in skip:
2973 2974 continue
2974 2975 files.append(fn)
2975 2976
2976 2977 if fn not in matches[rev]:
2977 2978 try:
2978 2979 content = flog.read(fnode)
2979 2980 except error.WdirUnsupported:
2980 2981 content = ctx[fn].data()
2981 2982 grepbody(fn, rev, content)
2982 2983
2983 2984 pfn = copy or fn
2984 2985 if pfn not in matches[parent]:
2985 2986 try:
2986 2987 fnode = pctx.filenode(pfn)
2987 2988 grepbody(pfn, parent, flog.read(fnode))
2988 2989 except error.LookupError:
2989 2990 pass
2990 2991
2991 2992 ui.pager('grep')
2992 2993 fm = ui.formatter('grep', opts)
2993 2994 for ctx in cmdutil.walkchangerevs(repo, match, opts, prep):
2994 2995 rev = ctx.rev()
2995 2996 parent = ctx.p1().rev()
2996 2997 for fn in sorted(revfiles.get(rev, [])):
2997 2998 states = matches[rev][fn]
2998 2999 copy = copies.get(rev, {}).get(fn)
2999 3000 if fn in skip:
3000 3001 if copy:
3001 3002 skip.add(copy)
3002 3003 continue
3003 3004 pstates = matches.get(parent, {}).get(copy or fn, [])
3004 3005 if pstates or states:
3005 3006 r = display(fm, fn, ctx, pstates, states)
3006 3007 found = found or r
3007 3008 if r and not diff and not all_files:
3008 3009 skip.add(fn)
3009 3010 if copy:
3010 3011 skip.add(copy)
3011 3012 del revfiles[rev]
3012 3013 # We will keep the matches dict for the duration of the window
3013 3014 # clear the matches dict once the window is over
3014 3015 if not revfiles:
3015 3016 matches.clear()
3016 3017 fm.end()
3017 3018
3018 3019 return not found
3019 3020
3020 3021 @command('heads',
3021 3022 [('r', 'rev', '',
3022 3023 _('show only heads which are descendants of STARTREV'), _('STARTREV')),
3023 3024 ('t', 'topo', False, _('show topological heads only')),
3024 3025 ('a', 'active', False, _('show active branchheads only (DEPRECATED)')),
3025 3026 ('c', 'closed', False, _('show normal and closed branch heads')),
3026 3027 ] + templateopts,
3027 3028 _('[-ct] [-r STARTREV] [REV]...'),
3028 3029 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3029 3030 intents={INTENT_READONLY})
3030 3031 def heads(ui, repo, *branchrevs, **opts):
3031 3032 """show branch heads
3032 3033
3033 3034 With no arguments, show all open branch heads in the repository.
3034 3035 Branch heads are changesets that have no descendants on the
3035 3036 same branch. They are where development generally takes place and
3036 3037 are the usual targets for update and merge operations.
3037 3038
3038 3039 If one or more REVs are given, only open branch heads on the
3039 3040 branches associated with the specified changesets are shown. This
3040 3041 means that you can use :hg:`heads .` to see the heads on the
3041 3042 currently checked-out branch.
3042 3043
3043 3044 If -c/--closed is specified, also show branch heads marked closed
3044 3045 (see :hg:`commit --close-branch`).
3045 3046
3046 3047 If STARTREV is specified, only those heads that are descendants of
3047 3048 STARTREV will be displayed.
3048 3049
3049 3050 If -t/--topo is specified, named branch mechanics will be ignored and only
3050 3051 topological heads (changesets with no children) will be shown.
3051 3052
3052 3053 Returns 0 if matching heads are found, 1 if not.
3053 3054 """
3054 3055
3055 3056 opts = pycompat.byteskwargs(opts)
3056 3057 start = None
3057 3058 rev = opts.get('rev')
3058 3059 if rev:
3059 3060 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
3060 3061 start = scmutil.revsingle(repo, rev, None).node()
3061 3062
3062 3063 if opts.get('topo'):
3063 3064 heads = [repo[h] for h in repo.heads(start)]
3064 3065 else:
3065 3066 heads = []
3066 3067 for branch in repo.branchmap():
3067 3068 heads += repo.branchheads(branch, start, opts.get('closed'))
3068 3069 heads = [repo[h] for h in heads]
3069 3070
3070 3071 if branchrevs:
3071 3072 branches = set(repo[r].branch()
3072 3073 for r in scmutil.revrange(repo, branchrevs))
3073 3074 heads = [h for h in heads if h.branch() in branches]
3074 3075
3075 3076 if opts.get('active') and branchrevs:
3076 3077 dagheads = repo.heads(start)
3077 3078 heads = [h for h in heads if h.node() in dagheads]
3078 3079
3079 3080 if branchrevs:
3080 3081 haveheads = set(h.branch() for h in heads)
3081 3082 if branches - haveheads:
3082 3083 headless = ', '.join(b for b in branches - haveheads)
3083 3084 msg = _('no open branch heads found on branches %s')
3084 3085 if opts.get('rev'):
3085 3086 msg += _(' (started at %s)') % opts['rev']
3086 3087 ui.warn((msg + '\n') % headless)
3087 3088
3088 3089 if not heads:
3089 3090 return 1
3090 3091
3091 3092 ui.pager('heads')
3092 3093 heads = sorted(heads, key=lambda x: -x.rev())
3093 3094 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
3094 3095 for ctx in heads:
3095 3096 displayer.show(ctx)
3096 3097 displayer.close()
3097 3098
3098 3099 @command('help',
3099 3100 [('e', 'extension', None, _('show only help for extensions')),
3100 3101 ('c', 'command', None, _('show only help for commands')),
3101 3102 ('k', 'keyword', None, _('show topics matching keyword')),
3102 3103 ('s', 'system', [],
3103 3104 _('show help for specific platform(s)'), _('PLATFORM')),
3104 3105 ],
3105 3106 _('[-eck] [-s PLATFORM] [TOPIC]'),
3106 3107 helpcategory=command.CATEGORY_HELP,
3107 3108 norepo=True,
3108 3109 intents={INTENT_READONLY})
3109 3110 def help_(ui, name=None, **opts):
3110 3111 """show help for a given topic or a help overview
3111 3112
3112 3113 With no arguments, print a list of commands with short help messages.
3113 3114
3114 3115 Given a topic, extension, or command name, print help for that
3115 3116 topic.
3116 3117
3117 3118 Returns 0 if successful.
3118 3119 """
3119 3120
3120 3121 keep = opts.get(r'system') or []
3121 3122 if len(keep) == 0:
3122 3123 if pycompat.sysplatform.startswith('win'):
3123 3124 keep.append('windows')
3124 3125 elif pycompat.sysplatform == 'OpenVMS':
3125 3126 keep.append('vms')
3126 3127 elif pycompat.sysplatform == 'plan9':
3127 3128 keep.append('plan9')
3128 3129 else:
3129 3130 keep.append('unix')
3130 3131 keep.append(pycompat.sysplatform.lower())
3131 3132 if ui.verbose:
3132 3133 keep.append('verbose')
3133 3134
3134 3135 commands = sys.modules[__name__]
3135 3136 formatted = help.formattedhelp(ui, commands, name, keep=keep, **opts)
3136 3137 ui.pager('help')
3137 3138 ui.write(formatted)
3138 3139
3139 3140
3140 3141 @command('identify|id',
3141 3142 [('r', 'rev', '',
3142 3143 _('identify the specified revision'), _('REV')),
3143 3144 ('n', 'num', None, _('show local revision number')),
3144 3145 ('i', 'id', None, _('show global revision id')),
3145 3146 ('b', 'branch', None, _('show branch')),
3146 3147 ('t', 'tags', None, _('show tags')),
3147 3148 ('B', 'bookmarks', None, _('show bookmarks')),
3148 3149 ] + remoteopts + formatteropts,
3149 3150 _('[-nibtB] [-r REV] [SOURCE]'),
3150 3151 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3151 3152 optionalrepo=True,
3152 3153 intents={INTENT_READONLY})
3153 3154 def identify(ui, repo, source=None, rev=None,
3154 3155 num=None, id=None, branch=None, tags=None, bookmarks=None, **opts):
3155 3156 """identify the working directory or specified revision
3156 3157
3157 3158 Print a summary identifying the repository state at REV using one or
3158 3159 two parent hash identifiers, followed by a "+" if the working
3159 3160 directory has uncommitted changes, the branch name (if not default),
3160 3161 a list of tags, and a list of bookmarks.
3161 3162
3162 3163 When REV is not given, print a summary of the current state of the
3163 3164 repository including the working directory. Specify -r. to get information
3164 3165 of the working directory parent without scanning uncommitted changes.
3165 3166
3166 3167 Specifying a path to a repository root or Mercurial bundle will
3167 3168 cause lookup to operate on that repository/bundle.
3168 3169
3169 3170 .. container:: verbose
3170 3171
3171 3172 Template:
3172 3173
3173 3174 The following keywords are supported in addition to the common template
3174 3175 keywords and functions. See also :hg:`help templates`.
3175 3176
3176 3177 :dirty: String. Character ``+`` denoting if the working directory has
3177 3178 uncommitted changes.
3178 3179 :id: String. One or two nodes, optionally followed by ``+``.
3179 3180 :parents: List of strings. Parent nodes of the changeset.
3180 3181
3181 3182 Examples:
3182 3183
3183 3184 - generate a build identifier for the working directory::
3184 3185
3185 3186 hg id --id > build-id.dat
3186 3187
3187 3188 - find the revision corresponding to a tag::
3188 3189
3189 3190 hg id -n -r 1.3
3190 3191
3191 3192 - check the most recent revision of a remote repository::
3192 3193
3193 3194 hg id -r tip https://www.mercurial-scm.org/repo/hg/
3194 3195
3195 3196 See :hg:`log` for generating more information about specific revisions,
3196 3197 including full hash identifiers.
3197 3198
3198 3199 Returns 0 if successful.
3199 3200 """
3200 3201
3201 3202 opts = pycompat.byteskwargs(opts)
3202 3203 if not repo and not source:
3203 3204 raise error.Abort(_("there is no Mercurial repository here "
3204 3205 "(.hg not found)"))
3205 3206
3206 3207 default = not (num or id or branch or tags or bookmarks)
3207 3208 output = []
3208 3209 revs = []
3209 3210
3210 3211 if source:
3211 3212 source, branches = hg.parseurl(ui.expandpath(source))
3212 3213 peer = hg.peer(repo or ui, opts, source) # only pass ui when no repo
3213 3214 repo = peer.local()
3214 3215 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
3215 3216
3216 3217 fm = ui.formatter('identify', opts)
3217 3218 fm.startitem()
3218 3219
3219 3220 if not repo:
3220 3221 if num or branch or tags:
3221 3222 raise error.Abort(
3222 3223 _("can't query remote revision number, branch, or tags"))
3223 3224 if not rev and revs:
3224 3225 rev = revs[0]
3225 3226 if not rev:
3226 3227 rev = "tip"
3227 3228
3228 3229 remoterev = peer.lookup(rev)
3229 3230 hexrev = fm.hexfunc(remoterev)
3230 3231 if default or id:
3231 3232 output = [hexrev]
3232 3233 fm.data(id=hexrev)
3233 3234
3234 3235 @util.cachefunc
3235 3236 def getbms():
3236 3237 bms = []
3237 3238
3238 3239 if 'bookmarks' in peer.listkeys('namespaces'):
3239 3240 hexremoterev = hex(remoterev)
3240 3241 bms = [bm for bm, bmr in peer.listkeys('bookmarks').iteritems()
3241 3242 if bmr == hexremoterev]
3242 3243
3243 3244 return sorted(bms)
3244 3245
3245 3246 if fm.isplain():
3246 3247 if bookmarks:
3247 3248 output.extend(getbms())
3248 3249 elif default and not ui.quiet:
3249 3250 # multiple bookmarks for a single parent separated by '/'
3250 3251 bm = '/'.join(getbms())
3251 3252 if bm:
3252 3253 output.append(bm)
3253 3254 else:
3254 3255 fm.data(node=hex(remoterev))
3255 3256 if bookmarks or 'bookmarks' in fm.datahint():
3256 3257 fm.data(bookmarks=fm.formatlist(getbms(), name='bookmark'))
3257 3258 else:
3258 3259 if rev:
3259 3260 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
3260 3261 ctx = scmutil.revsingle(repo, rev, None)
3261 3262
3262 3263 if ctx.rev() is None:
3263 3264 ctx = repo[None]
3264 3265 parents = ctx.parents()
3265 3266 taglist = []
3266 3267 for p in parents:
3267 3268 taglist.extend(p.tags())
3268 3269
3269 3270 dirty = ""
3270 3271 if ctx.dirty(missing=True, merge=False, branch=False):
3271 3272 dirty = '+'
3272 3273 fm.data(dirty=dirty)
3273 3274
3274 3275 hexoutput = [fm.hexfunc(p.node()) for p in parents]
3275 3276 if default or id:
3276 3277 output = ["%s%s" % ('+'.join(hexoutput), dirty)]
3277 3278 fm.data(id="%s%s" % ('+'.join(hexoutput), dirty))
3278 3279
3279 3280 if num:
3280 3281 numoutput = ["%d" % p.rev() for p in parents]
3281 3282 output.append("%s%s" % ('+'.join(numoutput), dirty))
3282 3283
3283 3284 fm.data(parents=fm.formatlist([fm.hexfunc(p.node())
3284 3285 for p in parents], name='node'))
3285 3286 else:
3286 3287 hexoutput = fm.hexfunc(ctx.node())
3287 3288 if default or id:
3288 3289 output = [hexoutput]
3289 3290 fm.data(id=hexoutput)
3290 3291
3291 3292 if num:
3292 3293 output.append(pycompat.bytestr(ctx.rev()))
3293 3294 taglist = ctx.tags()
3294 3295
3295 3296 if default and not ui.quiet:
3296 3297 b = ctx.branch()
3297 3298 if b != 'default':
3298 3299 output.append("(%s)" % b)
3299 3300
3300 3301 # multiple tags for a single parent separated by '/'
3301 3302 t = '/'.join(taglist)
3302 3303 if t:
3303 3304 output.append(t)
3304 3305
3305 3306 # multiple bookmarks for a single parent separated by '/'
3306 3307 bm = '/'.join(ctx.bookmarks())
3307 3308 if bm:
3308 3309 output.append(bm)
3309 3310 else:
3310 3311 if branch:
3311 3312 output.append(ctx.branch())
3312 3313
3313 3314 if tags:
3314 3315 output.extend(taglist)
3315 3316
3316 3317 if bookmarks:
3317 3318 output.extend(ctx.bookmarks())
3318 3319
3319 3320 fm.data(node=ctx.hex())
3320 3321 fm.data(branch=ctx.branch())
3321 3322 fm.data(tags=fm.formatlist(taglist, name='tag', sep=':'))
3322 3323 fm.data(bookmarks=fm.formatlist(ctx.bookmarks(), name='bookmark'))
3323 3324 fm.context(ctx=ctx)
3324 3325
3325 3326 fm.plain("%s\n" % ' '.join(output))
3326 3327 fm.end()
3327 3328
3328 3329 @command('import|patch',
3329 3330 [('p', 'strip', 1,
3330 3331 _('directory strip option for patch. This has the same '
3331 3332 'meaning as the corresponding patch option'), _('NUM')),
3332 3333 ('b', 'base', '', _('base path (DEPRECATED)'), _('PATH')),
3333 3334 ('e', 'edit', False, _('invoke editor on commit messages')),
3334 3335 ('f', 'force', None,
3335 3336 _('skip check for outstanding uncommitted changes (DEPRECATED)')),
3336 3337 ('', 'no-commit', None,
3337 3338 _("don't commit, just update the working directory")),
3338 3339 ('', 'bypass', None,
3339 3340 _("apply patch without touching the working directory")),
3340 3341 ('', 'partial', None,
3341 3342 _('commit even if some hunks fail')),
3342 3343 ('', 'exact', None,
3343 3344 _('abort if patch would apply lossily')),
3344 3345 ('', 'prefix', '',
3345 3346 _('apply patch to subdirectory'), _('DIR')),
3346 3347 ('', 'import-branch', None,
3347 3348 _('use any branch information in patch (implied by --exact)'))] +
3348 3349 commitopts + commitopts2 + similarityopts,
3349 3350 _('[OPTION]... PATCH...'),
3350 3351 helpcategory=command.CATEGORY_IMPORT_EXPORT)
3351 3352 def import_(ui, repo, patch1=None, *patches, **opts):
3352 3353 """import an ordered set of patches
3353 3354
3354 3355 Import a list of patches and commit them individually (unless
3355 3356 --no-commit is specified).
3356 3357
3357 3358 To read a patch from standard input (stdin), use "-" as the patch
3358 3359 name. If a URL is specified, the patch will be downloaded from
3359 3360 there.
3360 3361
3361 3362 Import first applies changes to the working directory (unless
3362 3363 --bypass is specified), import will abort if there are outstanding
3363 3364 changes.
3364 3365
3365 3366 Use --bypass to apply and commit patches directly to the
3366 3367 repository, without affecting the working directory. Without
3367 3368 --exact, patches will be applied on top of the working directory
3368 3369 parent revision.
3369 3370
3370 3371 You can import a patch straight from a mail message. Even patches
3371 3372 as attachments work (to use the body part, it must have type
3372 3373 text/plain or text/x-patch). From and Subject headers of email
3373 3374 message are used as default committer and commit message. All
3374 3375 text/plain body parts before first diff are added to the commit
3375 3376 message.
3376 3377
3377 3378 If the imported patch was generated by :hg:`export`, user and
3378 3379 description from patch override values from message headers and
3379 3380 body. Values given on command line with -m/--message and -u/--user
3380 3381 override these.
3381 3382
3382 3383 If --exact is specified, import will set the working directory to
3383 3384 the parent of each patch before applying it, and will abort if the
3384 3385 resulting changeset has a different ID than the one recorded in
3385 3386 the patch. This will guard against various ways that portable
3386 3387 patch formats and mail systems might fail to transfer Mercurial
3387 3388 data or metadata. See :hg:`bundle` for lossless transmission.
3388 3389
3389 3390 Use --partial to ensure a changeset will be created from the patch
3390 3391 even if some hunks fail to apply. Hunks that fail to apply will be
3391 3392 written to a <target-file>.rej file. Conflicts can then be resolved
3392 3393 by hand before :hg:`commit --amend` is run to update the created
3393 3394 changeset. This flag exists to let people import patches that
3394 3395 partially apply without losing the associated metadata (author,
3395 3396 date, description, ...).
3396 3397
3397 3398 .. note::
3398 3399
3399 3400 When no hunks apply cleanly, :hg:`import --partial` will create
3400 3401 an empty changeset, importing only the patch metadata.
3401 3402
3402 3403 With -s/--similarity, hg will attempt to discover renames and
3403 3404 copies in the patch in the same way as :hg:`addremove`.
3404 3405
3405 3406 It is possible to use external patch programs to perform the patch
3406 3407 by setting the ``ui.patch`` configuration option. For the default
3407 3408 internal tool, the fuzz can also be configured via ``patch.fuzz``.
3408 3409 See :hg:`help config` for more information about configuration
3409 3410 files and how to use these options.
3410 3411
3411 3412 See :hg:`help dates` for a list of formats valid for -d/--date.
3412 3413
3413 3414 .. container:: verbose
3414 3415
3415 3416 Examples:
3416 3417
3417 3418 - import a traditional patch from a website and detect renames::
3418 3419
3419 3420 hg import -s 80 http://example.com/bugfix.patch
3420 3421
3421 3422 - import a changeset from an hgweb server::
3422 3423
3423 3424 hg import https://www.mercurial-scm.org/repo/hg/rev/5ca8c111e9aa
3424 3425
3425 3426 - import all the patches in an Unix-style mbox::
3426 3427
3427 3428 hg import incoming-patches.mbox
3428 3429
3429 3430 - import patches from stdin::
3430 3431
3431 3432 hg import -
3432 3433
3433 3434 - attempt to exactly restore an exported changeset (not always
3434 3435 possible)::
3435 3436
3436 3437 hg import --exact proposed-fix.patch
3437 3438
3438 3439 - use an external tool to apply a patch which is too fuzzy for
3439 3440 the default internal tool.
3440 3441
3441 3442 hg import --config ui.patch="patch --merge" fuzzy.patch
3442 3443
3443 3444 - change the default fuzzing from 2 to a less strict 7
3444 3445
3445 3446 hg import --config ui.fuzz=7 fuzz.patch
3446 3447
3447 3448 Returns 0 on success, 1 on partial success (see --partial).
3448 3449 """
3449 3450
3450 3451 opts = pycompat.byteskwargs(opts)
3451 3452 if not patch1:
3452 3453 raise error.Abort(_('need at least one patch to import'))
3453 3454
3454 3455 patches = (patch1,) + patches
3455 3456
3456 3457 date = opts.get('date')
3457 3458 if date:
3458 3459 opts['date'] = dateutil.parsedate(date)
3459 3460
3460 3461 exact = opts.get('exact')
3461 3462 update = not opts.get('bypass')
3462 3463 if not update and opts.get('no_commit'):
3463 3464 raise error.Abort(_('cannot use --no-commit with --bypass'))
3464 3465 try:
3465 3466 sim = float(opts.get('similarity') or 0)
3466 3467 except ValueError:
3467 3468 raise error.Abort(_('similarity must be a number'))
3468 3469 if sim < 0 or sim > 100:
3469 3470 raise error.Abort(_('similarity must be between 0 and 100'))
3470 3471 if sim and not update:
3471 3472 raise error.Abort(_('cannot use --similarity with --bypass'))
3472 3473 if exact:
3473 3474 if opts.get('edit'):
3474 3475 raise error.Abort(_('cannot use --exact with --edit'))
3475 3476 if opts.get('prefix'):
3476 3477 raise error.Abort(_('cannot use --exact with --prefix'))
3477 3478
3478 3479 base = opts["base"]
3479 3480 msgs = []
3480 3481 ret = 0
3481 3482
3482 3483 with repo.wlock():
3483 3484 if update:
3484 3485 cmdutil.checkunfinished(repo)
3485 3486 if (exact or not opts.get('force')):
3486 3487 cmdutil.bailifchanged(repo)
3487 3488
3488 3489 if not opts.get('no_commit'):
3489 3490 lock = repo.lock
3490 3491 tr = lambda: repo.transaction('import')
3491 3492 dsguard = util.nullcontextmanager
3492 3493 else:
3493 3494 lock = util.nullcontextmanager
3494 3495 tr = util.nullcontextmanager
3495 3496 dsguard = lambda: dirstateguard.dirstateguard(repo, 'import')
3496 3497 with lock(), tr(), dsguard():
3497 3498 parents = repo[None].parents()
3498 3499 for patchurl in patches:
3499 3500 if patchurl == '-':
3500 3501 ui.status(_('applying patch from stdin\n'))
3501 3502 patchfile = ui.fin
3502 3503 patchurl = 'stdin' # for error message
3503 3504 else:
3504 3505 patchurl = os.path.join(base, patchurl)
3505 3506 ui.status(_('applying %s\n') % patchurl)
3506 3507 patchfile = hg.openpath(ui, patchurl, sendaccept=False)
3507 3508
3508 3509 haspatch = False
3509 3510 for hunk in patch.split(patchfile):
3510 3511 with patch.extract(ui, hunk) as patchdata:
3511 3512 msg, node, rej = cmdutil.tryimportone(ui, repo,
3512 3513 patchdata,
3513 3514 parents, opts,
3514 3515 msgs, hg.clean)
3515 3516 if msg:
3516 3517 haspatch = True
3517 3518 ui.note(msg + '\n')
3518 3519 if update or exact:
3519 3520 parents = repo[None].parents()
3520 3521 else:
3521 3522 parents = [repo[node]]
3522 3523 if rej:
3523 3524 ui.write_err(_("patch applied partially\n"))
3524 3525 ui.write_err(_("(fix the .rej files and run "
3525 3526 "`hg commit --amend`)\n"))
3526 3527 ret = 1
3527 3528 break
3528 3529
3529 3530 if not haspatch:
3530 3531 raise error.Abort(_('%s: no diffs found') % patchurl)
3531 3532
3532 3533 if msgs:
3533 3534 repo.savecommitmessage('\n* * *\n'.join(msgs))
3534 3535 return ret
3535 3536
3536 3537 @command('incoming|in',
3537 3538 [('f', 'force', None,
3538 3539 _('run even if remote repository is unrelated')),
3539 3540 ('n', 'newest-first', None, _('show newest record first')),
3540 3541 ('', 'bundle', '',
3541 3542 _('file to store the bundles into'), _('FILE')),
3542 3543 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
3543 3544 ('B', 'bookmarks', False, _("compare bookmarks")),
3544 3545 ('b', 'branch', [],
3545 3546 _('a specific branch you would like to pull'), _('BRANCH')),
3546 3547 ] + logopts + remoteopts + subrepoopts,
3547 3548 _('[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'),
3548 3549 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT)
3549 3550 def incoming(ui, repo, source="default", **opts):
3550 3551 """show new changesets found in source
3551 3552
3552 3553 Show new changesets found in the specified path/URL or the default
3553 3554 pull location. These are the changesets that would have been pulled
3554 3555 by :hg:`pull` at the time you issued this command.
3555 3556
3556 3557 See pull for valid source format details.
3557 3558
3558 3559 .. container:: verbose
3559 3560
3560 3561 With -B/--bookmarks, the result of bookmark comparison between
3561 3562 local and remote repositories is displayed. With -v/--verbose,
3562 3563 status is also displayed for each bookmark like below::
3563 3564
3564 3565 BM1 01234567890a added
3565 3566 BM2 1234567890ab advanced
3566 3567 BM3 234567890abc diverged
3567 3568 BM4 34567890abcd changed
3568 3569
3569 3570 The action taken locally when pulling depends on the
3570 3571 status of each bookmark:
3571 3572
3572 3573 :``added``: pull will create it
3573 3574 :``advanced``: pull will update it
3574 3575 :``diverged``: pull will create a divergent bookmark
3575 3576 :``changed``: result depends on remote changesets
3576 3577
3577 3578 From the point of view of pulling behavior, bookmark
3578 3579 existing only in the remote repository are treated as ``added``,
3579 3580 even if it is in fact locally deleted.
3580 3581
3581 3582 .. container:: verbose
3582 3583
3583 3584 For remote repository, using --bundle avoids downloading the
3584 3585 changesets twice if the incoming is followed by a pull.
3585 3586
3586 3587 Examples:
3587 3588
3588 3589 - show incoming changes with patches and full description::
3589 3590
3590 3591 hg incoming -vp
3591 3592
3592 3593 - show incoming changes excluding merges, store a bundle::
3593 3594
3594 3595 hg in -vpM --bundle incoming.hg
3595 3596 hg pull incoming.hg
3596 3597
3597 3598 - briefly list changes inside a bundle::
3598 3599
3599 3600 hg in changes.hg -T "{desc|firstline}\\n"
3600 3601
3601 3602 Returns 0 if there are incoming changes, 1 otherwise.
3602 3603 """
3603 3604 opts = pycompat.byteskwargs(opts)
3604 3605 if opts.get('graph'):
3605 3606 logcmdutil.checkunsupportedgraphflags([], opts)
3606 3607 def display(other, chlist, displayer):
3607 3608 revdag = logcmdutil.graphrevs(other, chlist, opts)
3608 3609 logcmdutil.displaygraph(ui, repo, revdag, displayer,
3609 3610 graphmod.asciiedges)
3610 3611
3611 3612 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
3612 3613 return 0
3613 3614
3614 3615 if opts.get('bundle') and opts.get('subrepos'):
3615 3616 raise error.Abort(_('cannot combine --bundle and --subrepos'))
3616 3617
3617 3618 if opts.get('bookmarks'):
3618 3619 source, branches = hg.parseurl(ui.expandpath(source),
3619 3620 opts.get('branch'))
3620 3621 other = hg.peer(repo, opts, source)
3621 3622 if 'bookmarks' not in other.listkeys('namespaces'):
3622 3623 ui.warn(_("remote doesn't support bookmarks\n"))
3623 3624 return 0
3624 3625 ui.pager('incoming')
3625 3626 ui.status(_('comparing with %s\n') % util.hidepassword(source))
3626 3627 return bookmarks.incoming(ui, repo, other)
3627 3628
3628 3629 repo._subtoppath = ui.expandpath(source)
3629 3630 try:
3630 3631 return hg.incoming(ui, repo, source, opts)
3631 3632 finally:
3632 3633 del repo._subtoppath
3633 3634
3634 3635
3635 3636 @command('init', remoteopts, _('[-e CMD] [--remotecmd CMD] [DEST]'),
3636 3637 helpcategory=command.CATEGORY_REPO_CREATION,
3637 3638 helpbasic=True, norepo=True)
3638 3639 def init(ui, dest=".", **opts):
3639 3640 """create a new repository in the given directory
3640 3641
3641 3642 Initialize a new repository in the given directory. If the given
3642 3643 directory does not exist, it will be created.
3643 3644
3644 3645 If no directory is given, the current directory is used.
3645 3646
3646 3647 It is possible to specify an ``ssh://`` URL as the destination.
3647 3648 See :hg:`help urls` for more information.
3648 3649
3649 3650 Returns 0 on success.
3650 3651 """
3651 3652 opts = pycompat.byteskwargs(opts)
3652 3653 hg.peer(ui, opts, ui.expandpath(dest), create=True)
3653 3654
3654 3655 @command('locate',
3655 3656 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
3656 3657 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
3657 3658 ('f', 'fullpath', None, _('print complete paths from the filesystem root')),
3658 3659 ] + walkopts,
3659 3660 _('[OPTION]... [PATTERN]...'),
3660 3661 helpcategory=command.CATEGORY_WORKING_DIRECTORY)
3661 3662 def locate(ui, repo, *pats, **opts):
3662 3663 """locate files matching specific patterns (DEPRECATED)
3663 3664
3664 3665 Print files under Mercurial control in the working directory whose
3665 3666 names match the given patterns.
3666 3667
3667 3668 By default, this command searches all directories in the working
3668 3669 directory. To search just the current directory and its
3669 3670 subdirectories, use "--include .".
3670 3671
3671 3672 If no patterns are given to match, this command prints the names
3672 3673 of all files under Mercurial control in the working directory.
3673 3674
3674 3675 If you want to feed the output of this command into the "xargs"
3675 3676 command, use the -0 option to both this command and "xargs". This
3676 3677 will avoid the problem of "xargs" treating single filenames that
3677 3678 contain whitespace as multiple filenames.
3678 3679
3679 3680 See :hg:`help files` for a more versatile command.
3680 3681
3681 3682 Returns 0 if a match is found, 1 otherwise.
3682 3683 """
3683 3684 opts = pycompat.byteskwargs(opts)
3684 3685 if opts.get('print0'):
3685 3686 end = '\0'
3686 3687 else:
3687 3688 end = '\n'
3688 3689 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
3689 3690
3690 3691 ret = 1
3691 3692 m = scmutil.match(ctx, pats, opts, default='relglob',
3692 3693 badfn=lambda x, y: False)
3693 3694
3694 3695 ui.pager('locate')
3695 3696 if ctx.rev() is None:
3696 3697 # When run on the working copy, "locate" includes removed files, so
3697 3698 # we get the list of files from the dirstate.
3698 3699 filesgen = sorted(repo.dirstate.matches(m))
3699 3700 else:
3700 3701 filesgen = ctx.matches(m)
3701 3702 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=bool(pats))
3702 3703 for abs in filesgen:
3703 3704 if opts.get('fullpath'):
3704 3705 ui.write(repo.wjoin(abs), end)
3705 3706 else:
3706 3707 ui.write(uipathfn(abs), end)
3707 3708 ret = 0
3708 3709
3709 3710 return ret
3710 3711
3711 3712 @command('log|history',
3712 3713 [('f', 'follow', None,
3713 3714 _('follow changeset history, or file history across copies and renames')),
3714 3715 ('', 'follow-first', None,
3715 3716 _('only follow the first parent of merge changesets (DEPRECATED)')),
3716 3717 ('d', 'date', '', _('show revisions matching date spec'), _('DATE')),
3717 3718 ('C', 'copies', None, _('show copied files')),
3718 3719 ('k', 'keyword', [],
3719 3720 _('do case-insensitive search for a given text'), _('TEXT')),
3720 3721 ('r', 'rev', [], _('show the specified revision or revset'), _('REV')),
3721 3722 ('L', 'line-range', [],
3722 3723 _('follow line range of specified file (EXPERIMENTAL)'),
3723 3724 _('FILE,RANGE')),
3724 3725 ('', 'removed', None, _('include revisions where files were removed')),
3725 3726 ('m', 'only-merges', None,
3726 3727 _('show only merges (DEPRECATED) (use -r "merge()" instead)')),
3727 3728 ('u', 'user', [], _('revisions committed by user'), _('USER')),
3728 3729 ('', 'only-branch', [],
3729 3730 _('show only changesets within the given named branch (DEPRECATED)'),
3730 3731 _('BRANCH')),
3731 3732 ('b', 'branch', [],
3732 3733 _('show changesets within the given named branch'), _('BRANCH')),
3733 3734 ('P', 'prune', [],
3734 3735 _('do not display revision or any of its ancestors'), _('REV')),
3735 3736 ] + logopts + walkopts,
3736 3737 _('[OPTION]... [FILE]'),
3737 3738 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3738 3739 helpbasic=True, inferrepo=True,
3739 3740 intents={INTENT_READONLY})
3740 3741 def log(ui, repo, *pats, **opts):
3741 3742 """show revision history of entire repository or files
3742 3743
3743 3744 Print the revision history of the specified files or the entire
3744 3745 project.
3745 3746
3746 3747 If no revision range is specified, the default is ``tip:0`` unless
3747 3748 --follow is set, in which case the working directory parent is
3748 3749 used as the starting revision.
3749 3750
3750 3751 File history is shown without following rename or copy history of
3751 3752 files. Use -f/--follow with a filename to follow history across
3752 3753 renames and copies. --follow without a filename will only show
3753 3754 ancestors of the starting revision.
3754 3755
3755 3756 By default this command prints revision number and changeset id,
3756 3757 tags, non-trivial parents, user, date and time, and a summary for
3757 3758 each commit. When the -v/--verbose switch is used, the list of
3758 3759 changed files and full commit message are shown.
3759 3760
3760 3761 With --graph the revisions are shown as an ASCII art DAG with the most
3761 3762 recent changeset at the top.
3762 3763 'o' is a changeset, '@' is a working directory parent, '_' closes a branch,
3763 3764 'x' is obsolete, '*' is unstable, and '+' represents a fork where the
3764 3765 changeset from the lines below is a parent of the 'o' merge on the same
3765 3766 line.
3766 3767 Paths in the DAG are represented with '|', '/' and so forth. ':' in place
3767 3768 of a '|' indicates one or more revisions in a path are omitted.
3768 3769
3769 3770 .. container:: verbose
3770 3771
3771 3772 Use -L/--line-range FILE,M:N options to follow the history of lines
3772 3773 from M to N in FILE. With -p/--patch only diff hunks affecting
3773 3774 specified line range will be shown. This option requires --follow;
3774 3775 it can be specified multiple times. Currently, this option is not
3775 3776 compatible with --graph. This option is experimental.
3776 3777
3777 3778 .. note::
3778 3779
3779 3780 :hg:`log --patch` may generate unexpected diff output for merge
3780 3781 changesets, as it will only compare the merge changeset against
3781 3782 its first parent. Also, only files different from BOTH parents
3782 3783 will appear in files:.
3783 3784
3784 3785 .. note::
3785 3786
3786 3787 For performance reasons, :hg:`log FILE` may omit duplicate changes
3787 3788 made on branches and will not show removals or mode changes. To
3788 3789 see all such changes, use the --removed switch.
3789 3790
3790 3791 .. container:: verbose
3791 3792
3792 3793 .. note::
3793 3794
3794 3795 The history resulting from -L/--line-range options depends on diff
3795 3796 options; for instance if white-spaces are ignored, respective changes
3796 3797 with only white-spaces in specified line range will not be listed.
3797 3798
3798 3799 .. container:: verbose
3799 3800
3800 3801 Some examples:
3801 3802
3802 3803 - changesets with full descriptions and file lists::
3803 3804
3804 3805 hg log -v
3805 3806
3806 3807 - changesets ancestral to the working directory::
3807 3808
3808 3809 hg log -f
3809 3810
3810 3811 - last 10 commits on the current branch::
3811 3812
3812 3813 hg log -l 10 -b .
3813 3814
3814 3815 - changesets showing all modifications of a file, including removals::
3815 3816
3816 3817 hg log --removed file.c
3817 3818
3818 3819 - all changesets that touch a directory, with diffs, excluding merges::
3819 3820
3820 3821 hg log -Mp lib/
3821 3822
3822 3823 - all revision numbers that match a keyword::
3823 3824
3824 3825 hg log -k bug --template "{rev}\\n"
3825 3826
3826 3827 - the full hash identifier of the working directory parent::
3827 3828
3828 3829 hg log -r . --template "{node}\\n"
3829 3830
3830 3831 - list available log templates::
3831 3832
3832 3833 hg log -T list
3833 3834
3834 3835 - check if a given changeset is included in a tagged release::
3835 3836
3836 3837 hg log -r "a21ccf and ancestor(1.9)"
3837 3838
3838 3839 - find all changesets by some user in a date range::
3839 3840
3840 3841 hg log -k alice -d "may 2008 to jul 2008"
3841 3842
3842 3843 - summary of all changesets after the last tag::
3843 3844
3844 3845 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
3845 3846
3846 3847 - changesets touching lines 13 to 23 for file.c::
3847 3848
3848 3849 hg log -L file.c,13:23
3849 3850
3850 3851 - changesets touching lines 13 to 23 for file.c and lines 2 to 6 of
3851 3852 main.c with patch::
3852 3853
3853 3854 hg log -L file.c,13:23 -L main.c,2:6 -p
3854 3855
3855 3856 See :hg:`help dates` for a list of formats valid for -d/--date.
3856 3857
3857 3858 See :hg:`help revisions` for more about specifying and ordering
3858 3859 revisions.
3859 3860
3860 3861 See :hg:`help templates` for more about pre-packaged styles and
3861 3862 specifying custom templates. The default template used by the log
3862 3863 command can be customized via the ``ui.logtemplate`` configuration
3863 3864 setting.
3864 3865
3865 3866 Returns 0 on success.
3866 3867
3867 3868 """
3868 3869 opts = pycompat.byteskwargs(opts)
3869 3870 linerange = opts.get('line_range')
3870 3871
3871 3872 if linerange and not opts.get('follow'):
3872 3873 raise error.Abort(_('--line-range requires --follow'))
3873 3874
3874 3875 if linerange and pats:
3875 3876 # TODO: take pats as patterns with no line-range filter
3876 3877 raise error.Abort(
3877 3878 _('FILE arguments are not compatible with --line-range option')
3878 3879 )
3879 3880
3880 3881 repo = scmutil.unhidehashlikerevs(repo, opts.get('rev'), 'nowarn')
3881 3882 revs, differ = logcmdutil.getrevs(repo, pats, opts)
3882 3883 if linerange:
3883 3884 # TODO: should follow file history from logcmdutil._initialrevs(),
3884 3885 # then filter the result by logcmdutil._makerevset() and --limit
3885 3886 revs, differ = logcmdutil.getlinerangerevs(repo, revs, opts)
3886 3887
3887 3888 getcopies = None
3888 3889 if opts.get('copies'):
3889 3890 endrev = None
3890 3891 if revs:
3891 3892 endrev = revs.max() + 1
3892 3893 getcopies = scmutil.getcopiesfn(repo, endrev=endrev)
3893 3894
3894 3895 ui.pager('log')
3895 3896 displayer = logcmdutil.changesetdisplayer(ui, repo, opts, differ,
3896 3897 buffered=True)
3897 3898 if opts.get('graph'):
3898 3899 displayfn = logcmdutil.displaygraphrevs
3899 3900 else:
3900 3901 displayfn = logcmdutil.displayrevs
3901 3902 displayfn(ui, repo, revs, displayer, getcopies)
3902 3903
3903 3904 @command('manifest',
3904 3905 [('r', 'rev', '', _('revision to display'), _('REV')),
3905 3906 ('', 'all', False, _("list files from all revisions"))]
3906 3907 + formatteropts,
3907 3908 _('[-r REV]'),
3908 3909 helpcategory=command.CATEGORY_MAINTENANCE,
3909 3910 intents={INTENT_READONLY})
3910 3911 def manifest(ui, repo, node=None, rev=None, **opts):
3911 3912 """output the current or given revision of the project manifest
3912 3913
3913 3914 Print a list of version controlled files for the given revision.
3914 3915 If no revision is given, the first parent of the working directory
3915 3916 is used, or the null revision if no revision is checked out.
3916 3917
3917 3918 With -v, print file permissions, symlink and executable bits.
3918 3919 With --debug, print file revision hashes.
3919 3920
3920 3921 If option --all is specified, the list of all files from all revisions
3921 3922 is printed. This includes deleted and renamed files.
3922 3923
3923 3924 Returns 0 on success.
3924 3925 """
3925 3926 opts = pycompat.byteskwargs(opts)
3926 3927 fm = ui.formatter('manifest', opts)
3927 3928
3928 3929 if opts.get('all'):
3929 3930 if rev or node:
3930 3931 raise error.Abort(_("can't specify a revision with --all"))
3931 3932
3932 3933 res = set()
3933 3934 for rev in repo:
3934 3935 ctx = repo[rev]
3935 3936 res |= set(ctx.files())
3936 3937
3937 3938 ui.pager('manifest')
3938 3939 for f in sorted(res):
3939 3940 fm.startitem()
3940 3941 fm.write("path", '%s\n', f)
3941 3942 fm.end()
3942 3943 return
3943 3944
3944 3945 if rev and node:
3945 3946 raise error.Abort(_("please specify just one revision"))
3946 3947
3947 3948 if not node:
3948 3949 node = rev
3949 3950
3950 3951 char = {'l': '@', 'x': '*', '': '', 't': 'd'}
3951 3952 mode = {'l': '644', 'x': '755', '': '644', 't': '755'}
3952 3953 if node:
3953 3954 repo = scmutil.unhidehashlikerevs(repo, [node], 'nowarn')
3954 3955 ctx = scmutil.revsingle(repo, node)
3955 3956 mf = ctx.manifest()
3956 3957 ui.pager('manifest')
3957 3958 for f in ctx:
3958 3959 fm.startitem()
3959 3960 fm.context(ctx=ctx)
3960 3961 fl = ctx[f].flags()
3961 3962 fm.condwrite(ui.debugflag, 'hash', '%s ', hex(mf[f]))
3962 3963 fm.condwrite(ui.verbose, 'mode type', '%s %1s ', mode[fl], char[fl])
3963 3964 fm.write('path', '%s\n', f)
3964 3965 fm.end()
3965 3966
3966 3967 @command('merge',
3967 3968 [('f', 'force', None,
3968 3969 _('force a merge including outstanding changes (DEPRECATED)')),
3969 3970 ('r', 'rev', '', _('revision to merge'), _('REV')),
3970 3971 ('P', 'preview', None,
3971 3972 _('review revisions to merge (no merge is performed)')),
3972 3973 ('', 'abort', None, _('abort the ongoing merge')),
3973 3974 ] + mergetoolopts,
3974 3975 _('[-P] [[-r] REV]'),
3975 3976 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT, helpbasic=True)
3976 3977 def merge(ui, repo, node=None, **opts):
3977 3978 """merge another revision into working directory
3978 3979
3979 3980 The current working directory is updated with all changes made in
3980 3981 the requested revision since the last common predecessor revision.
3981 3982
3982 3983 Files that changed between either parent are marked as changed for
3983 3984 the next commit and a commit must be performed before any further
3984 3985 updates to the repository are allowed. The next commit will have
3985 3986 two parents.
3986 3987
3987 3988 ``--tool`` can be used to specify the merge tool used for file
3988 3989 merges. It overrides the HGMERGE environment variable and your
3989 3990 configuration files. See :hg:`help merge-tools` for options.
3990 3991
3991 3992 If no revision is specified, the working directory's parent is a
3992 3993 head revision, and the current branch contains exactly one other
3993 3994 head, the other head is merged with by default. Otherwise, an
3994 3995 explicit revision with which to merge must be provided.
3995 3996
3996 3997 See :hg:`help resolve` for information on handling file conflicts.
3997 3998
3998 3999 To undo an uncommitted merge, use :hg:`merge --abort` which
3999 4000 will check out a clean copy of the original merge parent, losing
4000 4001 all changes.
4001 4002
4002 4003 Returns 0 on success, 1 if there are unresolved files.
4003 4004 """
4004 4005
4005 4006 opts = pycompat.byteskwargs(opts)
4006 4007 abort = opts.get('abort')
4007 4008 if abort and repo.dirstate.p2() == nullid:
4008 4009 cmdutil.wrongtooltocontinue(repo, _('merge'))
4009 4010 if abort:
4010 4011 state = cmdutil.getunfinishedstate(repo)
4011 4012 if state and state._opname != 'merge':
4012 4013 raise error.Abort(_('cannot abort merge with %s in progress') %
4013 4014 (state._opname), hint=state.hint())
4014 4015 if node:
4015 4016 raise error.Abort(_("cannot specify a node with --abort"))
4016 4017 if opts.get('rev'):
4017 4018 raise error.Abort(_("cannot specify both --rev and --abort"))
4018 4019 if opts.get('preview'):
4019 4020 raise error.Abort(_("cannot specify --preview with --abort"))
4020 4021 if opts.get('rev') and node:
4021 4022 raise error.Abort(_("please specify just one revision"))
4022 4023 if not node:
4023 4024 node = opts.get('rev')
4024 4025
4025 4026 if node:
4026 4027 node = scmutil.revsingle(repo, node).node()
4027 4028
4028 4029 if not node and not abort:
4029 4030 node = repo[destutil.destmerge(repo)].node()
4030 4031
4031 4032 if opts.get('preview'):
4032 4033 # find nodes that are ancestors of p2 but not of p1
4033 4034 p1 = repo.lookup('.')
4034 4035 p2 = node
4035 4036 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
4036 4037
4037 4038 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
4038 4039 for node in nodes:
4039 4040 displayer.show(repo[node])
4040 4041 displayer.close()
4041 4042 return 0
4042 4043
4043 4044 # ui.forcemerge is an internal variable, do not document
4044 4045 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
4045 4046 with ui.configoverride(overrides, 'merge'):
4046 4047 force = opts.get('force')
4047 4048 labels = ['working copy', 'merge rev']
4048 4049 return hg.merge(repo, node, force=force, mergeforce=force,
4049 4050 labels=labels, abort=abort)
4050 4051
4051 4052 statemod.addunfinished(
4052 4053 'merge', fname=None, clearable=True, allowcommit=True,
4053 4054 cmdmsg=_('outstanding uncommitted merge'), abortfunc=hg.abortmerge,
4054 4055 statushint=_('To continue: hg commit\n'
4055 4056 'To abort: hg merge --abort'),
4056 4057 cmdhint=_("use 'hg commit' or 'hg merge --abort'")
4057 4058 )
4058 4059
4059 4060 @command('outgoing|out',
4060 4061 [('f', 'force', None, _('run even when the destination is unrelated')),
4061 4062 ('r', 'rev', [],
4062 4063 _('a changeset intended to be included in the destination'), _('REV')),
4063 4064 ('n', 'newest-first', None, _('show newest record first')),
4064 4065 ('B', 'bookmarks', False, _('compare bookmarks')),
4065 4066 ('b', 'branch', [], _('a specific branch you would like to push'),
4066 4067 _('BRANCH')),
4067 4068 ] + logopts + remoteopts + subrepoopts,
4068 4069 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]'),
4069 4070 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT)
4070 4071 def outgoing(ui, repo, dest=None, **opts):
4071 4072 """show changesets not found in the destination
4072 4073
4073 4074 Show changesets not found in the specified destination repository
4074 4075 or the default push location. These are the changesets that would
4075 4076 be pushed if a push was requested.
4076 4077
4077 4078 See pull for details of valid destination formats.
4078 4079
4079 4080 .. container:: verbose
4080 4081
4081 4082 With -B/--bookmarks, the result of bookmark comparison between
4082 4083 local and remote repositories is displayed. With -v/--verbose,
4083 4084 status is also displayed for each bookmark like below::
4084 4085
4085 4086 BM1 01234567890a added
4086 4087 BM2 deleted
4087 4088 BM3 234567890abc advanced
4088 4089 BM4 34567890abcd diverged
4089 4090 BM5 4567890abcde changed
4090 4091
4091 4092 The action taken when pushing depends on the
4092 4093 status of each bookmark:
4093 4094
4094 4095 :``added``: push with ``-B`` will create it
4095 4096 :``deleted``: push with ``-B`` will delete it
4096 4097 :``advanced``: push will update it
4097 4098 :``diverged``: push with ``-B`` will update it
4098 4099 :``changed``: push with ``-B`` will update it
4099 4100
4100 4101 From the point of view of pushing behavior, bookmarks
4101 4102 existing only in the remote repository are treated as
4102 4103 ``deleted``, even if it is in fact added remotely.
4103 4104
4104 4105 Returns 0 if there are outgoing changes, 1 otherwise.
4105 4106 """
4106 4107 # hg._outgoing() needs to re-resolve the path in order to handle #branch
4107 4108 # style URLs, so don't overwrite dest.
4108 4109 path = ui.paths.getpath(dest, default=('default-push', 'default'))
4109 4110 if not path:
4110 4111 raise error.Abort(_('default repository not configured!'),
4111 4112 hint=_("see 'hg help config.paths'"))
4112 4113
4113 4114 opts = pycompat.byteskwargs(opts)
4114 4115 if opts.get('graph'):
4115 4116 logcmdutil.checkunsupportedgraphflags([], opts)
4116 4117 o, other = hg._outgoing(ui, repo, dest, opts)
4117 4118 if not o:
4118 4119 cmdutil.outgoinghooks(ui, repo, other, opts, o)
4119 4120 return
4120 4121
4121 4122 revdag = logcmdutil.graphrevs(repo, o, opts)
4122 4123 ui.pager('outgoing')
4123 4124 displayer = logcmdutil.changesetdisplayer(ui, repo, opts, buffered=True)
4124 4125 logcmdutil.displaygraph(ui, repo, revdag, displayer,
4125 4126 graphmod.asciiedges)
4126 4127 cmdutil.outgoinghooks(ui, repo, other, opts, o)
4127 4128 return 0
4128 4129
4129 4130 if opts.get('bookmarks'):
4130 4131 dest = path.pushloc or path.loc
4131 4132 other = hg.peer(repo, opts, dest)
4132 4133 if 'bookmarks' not in other.listkeys('namespaces'):
4133 4134 ui.warn(_("remote doesn't support bookmarks\n"))
4134 4135 return 0
4135 4136 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
4136 4137 ui.pager('outgoing')
4137 4138 return bookmarks.outgoing(ui, repo, other)
4138 4139
4139 4140 repo._subtoppath = path.pushloc or path.loc
4140 4141 try:
4141 4142 return hg.outgoing(ui, repo, dest, opts)
4142 4143 finally:
4143 4144 del repo._subtoppath
4144 4145
4145 4146 @command('parents',
4146 4147 [('r', 'rev', '', _('show parents of the specified revision'), _('REV')),
4147 4148 ] + templateopts,
4148 4149 _('[-r REV] [FILE]'),
4149 4150 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
4150 4151 inferrepo=True)
4151 4152 def parents(ui, repo, file_=None, **opts):
4152 4153 """show the parents of the working directory or revision (DEPRECATED)
4153 4154
4154 4155 Print the working directory's parent revisions. If a revision is
4155 4156 given via -r/--rev, the parent of that revision will be printed.
4156 4157 If a file argument is given, the revision in which the file was
4157 4158 last changed (before the working directory revision or the
4158 4159 argument to --rev if given) is printed.
4159 4160
4160 4161 This command is equivalent to::
4161 4162
4162 4163 hg log -r "p1()+p2()" or
4163 4164 hg log -r "p1(REV)+p2(REV)" or
4164 4165 hg log -r "max(::p1() and file(FILE))+max(::p2() and file(FILE))" or
4165 4166 hg log -r "max(::p1(REV) and file(FILE))+max(::p2(REV) and file(FILE))"
4166 4167
4167 4168 See :hg:`summary` and :hg:`help revsets` for related information.
4168 4169
4169 4170 Returns 0 on success.
4170 4171 """
4171 4172
4172 4173 opts = pycompat.byteskwargs(opts)
4173 4174 rev = opts.get('rev')
4174 4175 if rev:
4175 4176 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
4176 4177 ctx = scmutil.revsingle(repo, rev, None)
4177 4178
4178 4179 if file_:
4179 4180 m = scmutil.match(ctx, (file_,), opts)
4180 4181 if m.anypats() or len(m.files()) != 1:
4181 4182 raise error.Abort(_('can only specify an explicit filename'))
4182 4183 file_ = m.files()[0]
4183 4184 filenodes = []
4184 4185 for cp in ctx.parents():
4185 4186 if not cp:
4186 4187 continue
4187 4188 try:
4188 4189 filenodes.append(cp.filenode(file_))
4189 4190 except error.LookupError:
4190 4191 pass
4191 4192 if not filenodes:
4192 4193 raise error.Abort(_("'%s' not found in manifest!") % file_)
4193 4194 p = []
4194 4195 for fn in filenodes:
4195 4196 fctx = repo.filectx(file_, fileid=fn)
4196 4197 p.append(fctx.node())
4197 4198 else:
4198 4199 p = [cp.node() for cp in ctx.parents()]
4199 4200
4200 4201 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
4201 4202 for n in p:
4202 4203 if n != nullid:
4203 4204 displayer.show(repo[n])
4204 4205 displayer.close()
4205 4206
4206 4207 @command('paths', formatteropts, _('[NAME]'),
4207 4208 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4208 4209 optionalrepo=True, intents={INTENT_READONLY})
4209 4210 def paths(ui, repo, search=None, **opts):
4210 4211 """show aliases for remote repositories
4211 4212
4212 4213 Show definition of symbolic path name NAME. If no name is given,
4213 4214 show definition of all available names.
4214 4215
4215 4216 Option -q/--quiet suppresses all output when searching for NAME
4216 4217 and shows only the path names when listing all definitions.
4217 4218
4218 4219 Path names are defined in the [paths] section of your
4219 4220 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
4220 4221 repository, ``.hg/hgrc`` is used, too.
4221 4222
4222 4223 The path names ``default`` and ``default-push`` have a special
4223 4224 meaning. When performing a push or pull operation, they are used
4224 4225 as fallbacks if no location is specified on the command-line.
4225 4226 When ``default-push`` is set, it will be used for push and
4226 4227 ``default`` will be used for pull; otherwise ``default`` is used
4227 4228 as the fallback for both. When cloning a repository, the clone
4228 4229 source is written as ``default`` in ``.hg/hgrc``.
4229 4230
4230 4231 .. note::
4231 4232
4232 4233 ``default`` and ``default-push`` apply to all inbound (e.g.
4233 4234 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email`
4234 4235 and :hg:`bundle`) operations.
4235 4236
4236 4237 See :hg:`help urls` for more information.
4237 4238
4238 4239 .. container:: verbose
4239 4240
4240 4241 Template:
4241 4242
4242 4243 The following keywords are supported. See also :hg:`help templates`.
4243 4244
4244 4245 :name: String. Symbolic name of the path alias.
4245 4246 :pushurl: String. URL for push operations.
4246 4247 :url: String. URL or directory path for the other operations.
4247 4248
4248 4249 Returns 0 on success.
4249 4250 """
4250 4251
4251 4252 opts = pycompat.byteskwargs(opts)
4252 4253 ui.pager('paths')
4253 4254 if search:
4254 4255 pathitems = [(name, path) for name, path in ui.paths.iteritems()
4255 4256 if name == search]
4256 4257 else:
4257 4258 pathitems = sorted(ui.paths.iteritems())
4258 4259
4259 4260 fm = ui.formatter('paths', opts)
4260 4261 if fm.isplain():
4261 4262 hidepassword = util.hidepassword
4262 4263 else:
4263 4264 hidepassword = bytes
4264 4265 if ui.quiet:
4265 4266 namefmt = '%s\n'
4266 4267 else:
4267 4268 namefmt = '%s = '
4268 4269 showsubopts = not search and not ui.quiet
4269 4270
4270 4271 for name, path in pathitems:
4271 4272 fm.startitem()
4272 4273 fm.condwrite(not search, 'name', namefmt, name)
4273 4274 fm.condwrite(not ui.quiet, 'url', '%s\n', hidepassword(path.rawloc))
4274 4275 for subopt, value in sorted(path.suboptions.items()):
4275 4276 assert subopt not in ('name', 'url')
4276 4277 if showsubopts:
4277 4278 fm.plain('%s:%s = ' % (name, subopt))
4278 4279 fm.condwrite(showsubopts, subopt, '%s\n', value)
4279 4280
4280 4281 fm.end()
4281 4282
4282 4283 if search and not pathitems:
4283 4284 if not ui.quiet:
4284 4285 ui.warn(_("not found!\n"))
4285 4286 return 1
4286 4287 else:
4287 4288 return 0
4288 4289
4289 4290 @command('phase',
4290 4291 [('p', 'public', False, _('set changeset phase to public')),
4291 4292 ('d', 'draft', False, _('set changeset phase to draft')),
4292 4293 ('s', 'secret', False, _('set changeset phase to secret')),
4293 4294 ('f', 'force', False, _('allow to move boundary backward')),
4294 4295 ('r', 'rev', [], _('target revision'), _('REV')),
4295 4296 ],
4296 4297 _('[-p|-d|-s] [-f] [-r] [REV...]'),
4297 4298 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION)
4298 4299 def phase(ui, repo, *revs, **opts):
4299 4300 """set or show the current phase name
4300 4301
4301 4302 With no argument, show the phase name of the current revision(s).
4302 4303
4303 4304 With one of -p/--public, -d/--draft or -s/--secret, change the
4304 4305 phase value of the specified revisions.
4305 4306
4306 4307 Unless -f/--force is specified, :hg:`phase` won't move changesets from a
4307 4308 lower phase to a higher phase. Phases are ordered as follows::
4308 4309
4309 4310 public < draft < secret
4310 4311
4311 4312 Returns 0 on success, 1 if some phases could not be changed.
4312 4313
4313 4314 (For more information about the phases concept, see :hg:`help phases`.)
4314 4315 """
4315 4316 opts = pycompat.byteskwargs(opts)
4316 4317 # search for a unique phase argument
4317 4318 targetphase = None
4318 4319 for idx, name in enumerate(phases.cmdphasenames):
4319 4320 if opts[name]:
4320 4321 if targetphase is not None:
4321 4322 raise error.Abort(_('only one phase can be specified'))
4322 4323 targetphase = idx
4323 4324
4324 4325 # look for specified revision
4325 4326 revs = list(revs)
4326 4327 revs.extend(opts['rev'])
4327 4328 if not revs:
4328 4329 # display both parents as the second parent phase can influence
4329 4330 # the phase of a merge commit
4330 4331 revs = [c.rev() for c in repo[None].parents()]
4331 4332
4332 4333 revs = scmutil.revrange(repo, revs)
4333 4334
4334 4335 ret = 0
4335 4336 if targetphase is None:
4336 4337 # display
4337 4338 for r in revs:
4338 4339 ctx = repo[r]
4339 4340 ui.write('%i: %s\n' % (ctx.rev(), ctx.phasestr()))
4340 4341 else:
4341 4342 with repo.lock(), repo.transaction("phase") as tr:
4342 4343 # set phase
4343 4344 if not revs:
4344 4345 raise error.Abort(_('empty revision set'))
4345 4346 nodes = [repo[r].node() for r in revs]
4346 4347 # moving revision from public to draft may hide them
4347 4348 # We have to check result on an unfiltered repository
4348 4349 unfi = repo.unfiltered()
4349 4350 getphase = unfi._phasecache.phase
4350 4351 olddata = [getphase(unfi, r) for r in unfi]
4351 4352 phases.advanceboundary(repo, tr, targetphase, nodes)
4352 4353 if opts['force']:
4353 4354 phases.retractboundary(repo, tr, targetphase, nodes)
4354 4355 getphase = unfi._phasecache.phase
4355 4356 newdata = [getphase(unfi, r) for r in unfi]
4356 4357 changes = sum(newdata[r] != olddata[r] for r in unfi)
4357 4358 cl = unfi.changelog
4358 4359 rejected = [n for n in nodes
4359 4360 if newdata[cl.rev(n)] < targetphase]
4360 4361 if rejected:
4361 4362 ui.warn(_('cannot move %i changesets to a higher '
4362 4363 'phase, use --force\n') % len(rejected))
4363 4364 ret = 1
4364 4365 if changes:
4365 4366 msg = _('phase changed for %i changesets\n') % changes
4366 4367 if ret:
4367 4368 ui.status(msg)
4368 4369 else:
4369 4370 ui.note(msg)
4370 4371 else:
4371 4372 ui.warn(_('no phases changed\n'))
4372 4373 return ret
4373 4374
4374 4375 def postincoming(ui, repo, modheads, optupdate, checkout, brev):
4375 4376 """Run after a changegroup has been added via pull/unbundle
4376 4377
4377 4378 This takes arguments below:
4378 4379
4379 4380 :modheads: change of heads by pull/unbundle
4380 4381 :optupdate: updating working directory is needed or not
4381 4382 :checkout: update destination revision (or None to default destination)
4382 4383 :brev: a name, which might be a bookmark to be activated after updating
4383 4384 """
4384 4385 if modheads == 0:
4385 4386 return
4386 4387 if optupdate:
4387 4388 try:
4388 4389 return hg.updatetotally(ui, repo, checkout, brev)
4389 4390 except error.UpdateAbort as inst:
4390 4391 msg = _("not updating: %s") % stringutil.forcebytestr(inst)
4391 4392 hint = inst.hint
4392 4393 raise error.UpdateAbort(msg, hint=hint)
4393 4394 if modheads is not None and modheads > 1:
4394 4395 currentbranchheads = len(repo.branchheads())
4395 4396 if currentbranchheads == modheads:
4396 4397 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
4397 4398 elif currentbranchheads > 1:
4398 4399 ui.status(_("(run 'hg heads .' to see heads, 'hg merge' to "
4399 4400 "merge)\n"))
4400 4401 else:
4401 4402 ui.status(_("(run 'hg heads' to see heads)\n"))
4402 4403 elif not ui.configbool('commands', 'update.requiredest'):
4403 4404 ui.status(_("(run 'hg update' to get a working copy)\n"))
4404 4405
4405 4406 @command('pull',
4406 4407 [('u', 'update', None,
4407 4408 _('update to new branch head if new descendants were pulled')),
4408 4409 ('f', 'force', None, _('run even when remote repository is unrelated')),
4409 4410 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
4410 4411 ('B', 'bookmark', [], _("bookmark to pull"), _('BOOKMARK')),
4411 4412 ('b', 'branch', [], _('a specific branch you would like to pull'),
4412 4413 _('BRANCH')),
4413 4414 ] + remoteopts,
4414 4415 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'),
4415 4416 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4416 4417 helpbasic=True)
4417 4418 def pull(ui, repo, source="default", **opts):
4418 4419 """pull changes from the specified source
4419 4420
4420 4421 Pull changes from a remote repository to a local one.
4421 4422
4422 4423 This finds all changes from the repository at the specified path
4423 4424 or URL and adds them to a local repository (the current one unless
4424 4425 -R is specified). By default, this does not update the copy of the
4425 4426 project in the working directory.
4426 4427
4427 4428 When cloning from servers that support it, Mercurial may fetch
4428 4429 pre-generated data. When this is done, hooks operating on incoming
4429 4430 changesets and changegroups may fire more than once, once for each
4430 4431 pre-generated bundle and as well as for any additional remaining
4431 4432 data. See :hg:`help -e clonebundles` for more.
4432 4433
4433 4434 Use :hg:`incoming` if you want to see what would have been added
4434 4435 by a pull at the time you issued this command. If you then decide
4435 4436 to add those changes to the repository, you should use :hg:`pull
4436 4437 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
4437 4438
4438 4439 If SOURCE is omitted, the 'default' path will be used.
4439 4440 See :hg:`help urls` for more information.
4440 4441
4441 4442 Specifying bookmark as ``.`` is equivalent to specifying the active
4442 4443 bookmark's name.
4443 4444
4444 4445 Returns 0 on success, 1 if an update had unresolved files.
4445 4446 """
4446 4447
4447 4448 opts = pycompat.byteskwargs(opts)
4448 4449 if ui.configbool('commands', 'update.requiredest') and opts.get('update'):
4449 4450 msg = _('update destination required by configuration')
4450 4451 hint = _('use hg pull followed by hg update DEST')
4451 4452 raise error.Abort(msg, hint=hint)
4452 4453
4453 4454 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
4454 4455 ui.status(_('pulling from %s\n') % util.hidepassword(source))
4455 4456 other = hg.peer(repo, opts, source)
4456 4457 try:
4457 4458 revs, checkout = hg.addbranchrevs(repo, other, branches,
4458 4459 opts.get('rev'))
4459 4460
4460 4461 pullopargs = {}
4461 4462
4462 4463 nodes = None
4463 4464 if opts.get('bookmark') or revs:
4464 4465 # The list of bookmark used here is the same used to actually update
4465 4466 # the bookmark names, to avoid the race from issue 4689 and we do
4466 4467 # all lookup and bookmark queries in one go so they see the same
4467 4468 # version of the server state (issue 4700).
4468 4469 nodes = []
4469 4470 fnodes = []
4470 4471 revs = revs or []
4471 4472 if revs and not other.capable('lookup'):
4472 4473 err = _("other repository doesn't support revision lookup, "
4473 4474 "so a rev cannot be specified.")
4474 4475 raise error.Abort(err)
4475 4476 with other.commandexecutor() as e:
4476 4477 fremotebookmarks = e.callcommand('listkeys', {
4477 4478 'namespace': 'bookmarks'
4478 4479 })
4479 4480 for r in revs:
4480 4481 fnodes.append(e.callcommand('lookup', {'key': r}))
4481 4482 remotebookmarks = fremotebookmarks.result()
4482 4483 remotebookmarks = bookmarks.unhexlifybookmarks(remotebookmarks)
4483 4484 pullopargs['remotebookmarks'] = remotebookmarks
4484 4485 for b in opts.get('bookmark', []):
4485 4486 b = repo._bookmarks.expandname(b)
4486 4487 if b not in remotebookmarks:
4487 4488 raise error.Abort(_('remote bookmark %s not found!') % b)
4488 4489 nodes.append(remotebookmarks[b])
4489 4490 for i, rev in enumerate(revs):
4490 4491 node = fnodes[i].result()
4491 4492 nodes.append(node)
4492 4493 if rev == checkout:
4493 4494 checkout = node
4494 4495
4495 4496 wlock = util.nullcontextmanager()
4496 4497 if opts.get('update'):
4497 4498 wlock = repo.wlock()
4498 4499 with wlock:
4499 4500 pullopargs.update(opts.get('opargs', {}))
4500 4501 modheads = exchange.pull(repo, other, heads=nodes,
4501 4502 force=opts.get('force'),
4502 4503 bookmarks=opts.get('bookmark', ()),
4503 4504 opargs=pullopargs).cgresult
4504 4505
4505 4506 # brev is a name, which might be a bookmark to be activated at
4506 4507 # the end of the update. In other words, it is an explicit
4507 4508 # destination of the update
4508 4509 brev = None
4509 4510
4510 4511 if checkout:
4511 4512 checkout = repo.unfiltered().changelog.rev(checkout)
4512 4513
4513 4514 # order below depends on implementation of
4514 4515 # hg.addbranchrevs(). opts['bookmark'] is ignored,
4515 4516 # because 'checkout' is determined without it.
4516 4517 if opts.get('rev'):
4517 4518 brev = opts['rev'][0]
4518 4519 elif opts.get('branch'):
4519 4520 brev = opts['branch'][0]
4520 4521 else:
4521 4522 brev = branches[0]
4522 4523 repo._subtoppath = source
4523 4524 try:
4524 4525 ret = postincoming(ui, repo, modheads, opts.get('update'),
4525 4526 checkout, brev)
4526 4527 except error.FilteredRepoLookupError as exc:
4527 4528 msg = _('cannot update to target: %s') % exc.args[0]
4528 4529 exc.args = (msg,) + exc.args[1:]
4529 4530 raise
4530 4531 finally:
4531 4532 del repo._subtoppath
4532 4533
4533 4534 finally:
4534 4535 other.close()
4535 4536 return ret
4536 4537
4537 4538 @command('push',
4538 4539 [('f', 'force', None, _('force push')),
4539 4540 ('r', 'rev', [],
4540 4541 _('a changeset intended to be included in the destination'),
4541 4542 _('REV')),
4542 4543 ('B', 'bookmark', [], _("bookmark to push"), _('BOOKMARK')),
4543 4544 ('b', 'branch', [],
4544 4545 _('a specific branch you would like to push'), _('BRANCH')),
4545 4546 ('', 'new-branch', False, _('allow pushing a new branch')),
4546 4547 ('', 'pushvars', [], _('variables that can be sent to server (ADVANCED)')),
4547 4548 ('', 'publish', False, _('push the changeset as public (EXPERIMENTAL)')),
4548 4549 ] + remoteopts,
4549 4550 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'),
4550 4551 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4551 4552 helpbasic=True)
4552 4553 def push(ui, repo, dest=None, **opts):
4553 4554 """push changes to the specified destination
4554 4555
4555 4556 Push changesets from the local repository to the specified
4556 4557 destination.
4557 4558
4558 4559 This operation is symmetrical to pull: it is identical to a pull
4559 4560 in the destination repository from the current one.
4560 4561
4561 4562 By default, push will not allow creation of new heads at the
4562 4563 destination, since multiple heads would make it unclear which head
4563 4564 to use. In this situation, it is recommended to pull and merge
4564 4565 before pushing.
4565 4566
4566 4567 Use --new-branch if you want to allow push to create a new named
4567 4568 branch that is not present at the destination. This allows you to
4568 4569 only create a new branch without forcing other changes.
4569 4570
4570 4571 .. note::
4571 4572
4572 4573 Extra care should be taken with the -f/--force option,
4573 4574 which will push all new heads on all branches, an action which will
4574 4575 almost always cause confusion for collaborators.
4575 4576
4576 4577 If -r/--rev is used, the specified revision and all its ancestors
4577 4578 will be pushed to the remote repository.
4578 4579
4579 4580 If -B/--bookmark is used, the specified bookmarked revision, its
4580 4581 ancestors, and the bookmark will be pushed to the remote
4581 4582 repository. Specifying ``.`` is equivalent to specifying the active
4582 4583 bookmark's name.
4583 4584
4584 4585 Please see :hg:`help urls` for important details about ``ssh://``
4585 4586 URLs. If DESTINATION is omitted, a default path will be used.
4586 4587
4587 4588 .. container:: verbose
4588 4589
4589 4590 The --pushvars option sends strings to the server that become
4590 4591 environment variables prepended with ``HG_USERVAR_``. For example,
4591 4592 ``--pushvars ENABLE_FEATURE=true``, provides the server side hooks with
4592 4593 ``HG_USERVAR_ENABLE_FEATURE=true`` as part of their environment.
4593 4594
4594 4595 pushvars can provide for user-overridable hooks as well as set debug
4595 4596 levels. One example is having a hook that blocks commits containing
4596 4597 conflict markers, but enables the user to override the hook if the file
4597 4598 is using conflict markers for testing purposes or the file format has
4598 4599 strings that look like conflict markers.
4599 4600
4600 4601 By default, servers will ignore `--pushvars`. To enable it add the
4601 4602 following to your configuration file::
4602 4603
4603 4604 [push]
4604 4605 pushvars.server = true
4605 4606
4606 4607 Returns 0 if push was successful, 1 if nothing to push.
4607 4608 """
4608 4609
4609 4610 opts = pycompat.byteskwargs(opts)
4610 4611 if opts.get('bookmark'):
4611 4612 ui.setconfig('bookmarks', 'pushing', opts['bookmark'], 'push')
4612 4613 for b in opts['bookmark']:
4613 4614 # translate -B options to -r so changesets get pushed
4614 4615 b = repo._bookmarks.expandname(b)
4615 4616 if b in repo._bookmarks:
4616 4617 opts.setdefault('rev', []).append(b)
4617 4618 else:
4618 4619 # if we try to push a deleted bookmark, translate it to null
4619 4620 # this lets simultaneous -r, -b options continue working
4620 4621 opts.setdefault('rev', []).append("null")
4621 4622
4622 4623 path = ui.paths.getpath(dest, default=('default-push', 'default'))
4623 4624 if not path:
4624 4625 raise error.Abort(_('default repository not configured!'),
4625 4626 hint=_("see 'hg help config.paths'"))
4626 4627 dest = path.pushloc or path.loc
4627 4628 branches = (path.branch, opts.get('branch') or [])
4628 4629 ui.status(_('pushing to %s\n') % util.hidepassword(dest))
4629 4630 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
4630 4631 other = hg.peer(repo, opts, dest)
4631 4632
4632 4633 if revs:
4633 4634 revs = [repo[r].node() for r in scmutil.revrange(repo, revs)]
4634 4635 if not revs:
4635 4636 raise error.Abort(_("specified revisions evaluate to an empty set"),
4636 4637 hint=_("use different revision arguments"))
4637 4638 elif path.pushrev:
4638 4639 # It doesn't make any sense to specify ancestor revisions. So limit
4639 4640 # to DAG heads to make discovery simpler.
4640 4641 expr = revsetlang.formatspec('heads(%r)', path.pushrev)
4641 4642 revs = scmutil.revrange(repo, [expr])
4642 4643 revs = [repo[rev].node() for rev in revs]
4643 4644 if not revs:
4644 4645 raise error.Abort(_('default push revset for path evaluates to an '
4645 4646 'empty set'))
4646 4647
4647 4648 repo._subtoppath = dest
4648 4649 try:
4649 4650 # push subrepos depth-first for coherent ordering
4650 4651 c = repo['.']
4651 4652 subs = c.substate # only repos that are committed
4652 4653 for s in sorted(subs):
4653 4654 result = c.sub(s).push(opts)
4654 4655 if result == 0:
4655 4656 return not result
4656 4657 finally:
4657 4658 del repo._subtoppath
4658 4659
4659 4660 opargs = dict(opts.get('opargs', {})) # copy opargs since we may mutate it
4660 4661 opargs.setdefault('pushvars', []).extend(opts.get('pushvars', []))
4661 4662
4662 4663 pushop = exchange.push(repo, other, opts.get('force'), revs=revs,
4663 4664 newbranch=opts.get('new_branch'),
4664 4665 bookmarks=opts.get('bookmark', ()),
4665 4666 publish=opts.get('publish'),
4666 4667 opargs=opargs)
4667 4668
4668 4669 result = not pushop.cgresult
4669 4670
4670 4671 if pushop.bkresult is not None:
4671 4672 if pushop.bkresult == 2:
4672 4673 result = 2
4673 4674 elif not result and pushop.bkresult:
4674 4675 result = 2
4675 4676
4676 4677 return result
4677 4678
4678 4679 @command('recover',
4679 4680 [('','verify', True, "run `hg verify` after succesful recover"),
4680 4681 ],
4681 4682 helpcategory=command.CATEGORY_MAINTENANCE)
4682 4683 def recover(ui, repo, **opts):
4683 4684 """roll back an interrupted transaction
4684 4685
4685 4686 Recover from an interrupted commit or pull.
4686 4687
4687 4688 This command tries to fix the repository status after an
4688 4689 interrupted operation. It should only be necessary when Mercurial
4689 4690 suggests it.
4690 4691
4691 4692 Returns 0 if successful, 1 if nothing to recover or verify fails.
4692 4693 """
4693 4694 ret = repo.recover()
4694 4695 if ret:
4695 4696 if opts[r'verify']:
4696 4697 return hg.verify(repo)
4697 4698 else:
4698 4699 msg = _("(verify step skipped, run `hg verify` to check your "
4699 4700 "repository content)\n")
4700 4701 ui.warn(msg)
4701 4702 return 0
4702 4703 return 1
4703 4704
4704 4705 @command('remove|rm',
4705 4706 [('A', 'after', None, _('record delete for missing files')),
4706 4707 ('f', 'force', None,
4707 4708 _('forget added files, delete modified files')),
4708 4709 ] + subrepoopts + walkopts + dryrunopts,
4709 4710 _('[OPTION]... FILE...'),
4710 4711 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
4711 4712 helpbasic=True, inferrepo=True)
4712 4713 def remove(ui, repo, *pats, **opts):
4713 4714 """remove the specified files on the next commit
4714 4715
4715 4716 Schedule the indicated files for removal from the current branch.
4716 4717
4717 4718 This command schedules the files to be removed at the next commit.
4718 4719 To undo a remove before that, see :hg:`revert`. To undo added
4719 4720 files, see :hg:`forget`.
4720 4721
4721 4722 .. container:: verbose
4722 4723
4723 4724 -A/--after can be used to remove only files that have already
4724 4725 been deleted, -f/--force can be used to force deletion, and -Af
4725 4726 can be used to remove files from the next revision without
4726 4727 deleting them from the working directory.
4727 4728
4728 4729 The following table details the behavior of remove for different
4729 4730 file states (columns) and option combinations (rows). The file
4730 4731 states are Added [A], Clean [C], Modified [M] and Missing [!]
4731 4732 (as reported by :hg:`status`). The actions are Warn, Remove
4732 4733 (from branch) and Delete (from disk):
4733 4734
4734 4735 ========= == == == ==
4735 4736 opt/state A C M !
4736 4737 ========= == == == ==
4737 4738 none W RD W R
4738 4739 -f R RD RD R
4739 4740 -A W W W R
4740 4741 -Af R R R R
4741 4742 ========= == == == ==
4742 4743
4743 4744 .. note::
4744 4745
4745 4746 :hg:`remove` never deletes files in Added [A] state from the
4746 4747 working directory, not even if ``--force`` is specified.
4747 4748
4748 4749 Returns 0 on success, 1 if any warnings encountered.
4749 4750 """
4750 4751
4751 4752 opts = pycompat.byteskwargs(opts)
4752 4753 after, force = opts.get('after'), opts.get('force')
4753 4754 dryrun = opts.get('dry_run')
4754 4755 if not pats and not after:
4755 4756 raise error.Abort(_('no files specified'))
4756 4757
4757 4758 m = scmutil.match(repo[None], pats, opts)
4758 4759 subrepos = opts.get('subrepos')
4759 4760 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
4760 4761 return cmdutil.remove(ui, repo, m, "", uipathfn, after, force, subrepos,
4761 4762 dryrun=dryrun)
4762 4763
4763 4764 @command('rename|move|mv',
4764 4765 [('A', 'after', None, _('record a rename that has already occurred')),
4765 4766 ('f', 'force', None, _('forcibly move over an existing managed file')),
4766 4767 ] + walkopts + dryrunopts,
4767 4768 _('[OPTION]... SOURCE... DEST'),
4768 4769 helpcategory=command.CATEGORY_WORKING_DIRECTORY)
4769 4770 def rename(ui, repo, *pats, **opts):
4770 4771 """rename files; equivalent of copy + remove
4771 4772
4772 4773 Mark dest as copies of sources; mark sources for deletion. If dest
4773 4774 is a directory, copies are put in that directory. If dest is a
4774 4775 file, there can only be one source.
4775 4776
4776 4777 By default, this command copies the contents of files as they
4777 4778 exist in the working directory. If invoked with -A/--after, the
4778 4779 operation is recorded, but no copying is performed.
4779 4780
4780 4781 This command takes effect at the next commit. To undo a rename
4781 4782 before that, see :hg:`revert`.
4782 4783
4783 4784 Returns 0 on success, 1 if errors are encountered.
4784 4785 """
4785 4786 opts = pycompat.byteskwargs(opts)
4786 4787 with repo.wlock(False):
4787 4788 return cmdutil.copy(ui, repo, pats, opts, rename=True)
4788 4789
4789 4790 @command('resolve',
4790 4791 [('a', 'all', None, _('select all unresolved files')),
4791 4792 ('l', 'list', None, _('list state of files needing merge')),
4792 4793 ('m', 'mark', None, _('mark files as resolved')),
4793 4794 ('u', 'unmark', None, _('mark files as unresolved')),
4794 4795 ('n', 'no-status', None, _('hide status prefix')),
4795 4796 ('', 're-merge', None, _('re-merge files'))]
4796 4797 + mergetoolopts + walkopts + formatteropts,
4797 4798 _('[OPTION]... [FILE]...'),
4798 4799 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
4799 4800 inferrepo=True)
4800 4801 def resolve(ui, repo, *pats, **opts):
4801 4802 """redo merges or set/view the merge status of files
4802 4803
4803 4804 Merges with unresolved conflicts are often the result of
4804 4805 non-interactive merging using the ``internal:merge`` configuration
4805 4806 setting, or a command-line merge tool like ``diff3``. The resolve
4806 4807 command is used to manage the files involved in a merge, after
4807 4808 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
4808 4809 working directory must have two parents). See :hg:`help
4809 4810 merge-tools` for information on configuring merge tools.
4810 4811
4811 4812 The resolve command can be used in the following ways:
4812 4813
4813 4814 - :hg:`resolve [--re-merge] [--tool TOOL] FILE...`: attempt to re-merge
4814 4815 the specified files, discarding any previous merge attempts. Re-merging
4815 4816 is not performed for files already marked as resolved. Use ``--all/-a``
4816 4817 to select all unresolved files. ``--tool`` can be used to specify
4817 4818 the merge tool used for the given files. It overrides the HGMERGE
4818 4819 environment variable and your configuration files. Previous file
4819 4820 contents are saved with a ``.orig`` suffix.
4820 4821
4821 4822 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
4822 4823 (e.g. after having manually fixed-up the files). The default is
4823 4824 to mark all unresolved files.
4824 4825
4825 4826 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
4826 4827 default is to mark all resolved files.
4827 4828
4828 4829 - :hg:`resolve -l`: list files which had or still have conflicts.
4829 4830 In the printed list, ``U`` = unresolved and ``R`` = resolved.
4830 4831 You can use ``set:unresolved()`` or ``set:resolved()`` to filter
4831 4832 the list. See :hg:`help filesets` for details.
4832 4833
4833 4834 .. note::
4834 4835
4835 4836 Mercurial will not let you commit files with unresolved merge
4836 4837 conflicts. You must use :hg:`resolve -m ...` before you can
4837 4838 commit after a conflicting merge.
4838 4839
4839 4840 .. container:: verbose
4840 4841
4841 4842 Template:
4842 4843
4843 4844 The following keywords are supported in addition to the common template
4844 4845 keywords and functions. See also :hg:`help templates`.
4845 4846
4846 4847 :mergestatus: String. Character denoting merge conflicts, ``U`` or ``R``.
4847 4848 :path: String. Repository-absolute path of the file.
4848 4849
4849 4850 Returns 0 on success, 1 if any files fail a resolve attempt.
4850 4851 """
4851 4852
4852 4853 opts = pycompat.byteskwargs(opts)
4853 4854 confirm = ui.configbool('commands', 'resolve.confirm')
4854 4855 flaglist = 'all mark unmark list no_status re_merge'.split()
4855 4856 all, mark, unmark, show, nostatus, remerge = [
4856 4857 opts.get(o) for o in flaglist]
4857 4858
4858 4859 actioncount = len(list(filter(None, [show, mark, unmark, remerge])))
4859 4860 if actioncount > 1:
4860 4861 raise error.Abort(_("too many actions specified"))
4861 4862 elif (actioncount == 0
4862 4863 and ui.configbool('commands', 'resolve.explicit-re-merge')):
4863 4864 hint = _('use --mark, --unmark, --list or --re-merge')
4864 4865 raise error.Abort(_('no action specified'), hint=hint)
4865 4866 if pats and all:
4866 4867 raise error.Abort(_("can't specify --all and patterns"))
4867 4868 if not (all or pats or show or mark or unmark):
4868 4869 raise error.Abort(_('no files or directories specified'),
4869 4870 hint=('use --all to re-merge all unresolved files'))
4870 4871
4871 4872 if confirm:
4872 4873 if all:
4873 4874 if ui.promptchoice(_(b're-merge all unresolved files (yn)?'
4874 4875 b'$$ &Yes $$ &No')):
4875 4876 raise error.Abort(_('user quit'))
4876 4877 if mark and not pats:
4877 4878 if ui.promptchoice(_(b'mark all unresolved files as resolved (yn)?'
4878 4879 b'$$ &Yes $$ &No')):
4879 4880 raise error.Abort(_('user quit'))
4880 4881 if unmark and not pats:
4881 4882 if ui.promptchoice(_(b'mark all resolved files as unresolved (yn)?'
4882 4883 b'$$ &Yes $$ &No')):
4883 4884 raise error.Abort(_('user quit'))
4884 4885
4885 4886 uipathfn = scmutil.getuipathfn(repo)
4886 4887
4887 4888 if show:
4888 4889 ui.pager('resolve')
4889 4890 fm = ui.formatter('resolve', opts)
4890 4891 ms = mergemod.mergestate.read(repo)
4891 4892 wctx = repo[None]
4892 4893 m = scmutil.match(wctx, pats, opts)
4893 4894
4894 4895 # Labels and keys based on merge state. Unresolved path conflicts show
4895 4896 # as 'P'. Resolved path conflicts show as 'R', the same as normal
4896 4897 # resolved conflicts.
4897 4898 mergestateinfo = {
4898 4899 mergemod.MERGE_RECORD_UNRESOLVED: ('resolve.unresolved', 'U'),
4899 4900 mergemod.MERGE_RECORD_RESOLVED: ('resolve.resolved', 'R'),
4900 4901 mergemod.MERGE_RECORD_UNRESOLVED_PATH: ('resolve.unresolved', 'P'),
4901 4902 mergemod.MERGE_RECORD_RESOLVED_PATH: ('resolve.resolved', 'R'),
4902 4903 mergemod.MERGE_RECORD_DRIVER_RESOLVED: ('resolve.driverresolved',
4903 4904 'D'),
4904 4905 }
4905 4906
4906 4907 for f in ms:
4907 4908 if not m(f):
4908 4909 continue
4909 4910
4910 4911 label, key = mergestateinfo[ms[f]]
4911 4912 fm.startitem()
4912 4913 fm.context(ctx=wctx)
4913 4914 fm.condwrite(not nostatus, 'mergestatus', '%s ', key, label=label)
4914 4915 fm.data(path=f)
4915 4916 fm.plain('%s\n' % uipathfn(f), label=label)
4916 4917 fm.end()
4917 4918 return 0
4918 4919
4919 4920 with repo.wlock():
4920 4921 ms = mergemod.mergestate.read(repo)
4921 4922
4922 4923 if not (ms.active() or repo.dirstate.p2() != nullid):
4923 4924 raise error.Abort(
4924 4925 _('resolve command not applicable when not merging'))
4925 4926
4926 4927 wctx = repo[None]
4927 4928
4928 4929 if (ms.mergedriver
4929 4930 and ms.mdstate() == mergemod.MERGE_DRIVER_STATE_UNMARKED):
4930 4931 proceed = mergemod.driverpreprocess(repo, ms, wctx)
4931 4932 ms.commit()
4932 4933 # allow mark and unmark to go through
4933 4934 if not mark and not unmark and not proceed:
4934 4935 return 1
4935 4936
4936 4937 m = scmutil.match(wctx, pats, opts)
4937 4938 ret = 0
4938 4939 didwork = False
4939 4940 runconclude = False
4940 4941
4941 4942 tocomplete = []
4942 4943 hasconflictmarkers = []
4943 4944 if mark:
4944 4945 markcheck = ui.config('commands', 'resolve.mark-check')
4945 4946 if markcheck not in ['warn', 'abort']:
4946 4947 # Treat all invalid / unrecognized values as 'none'.
4947 4948 markcheck = False
4948 4949 for f in ms:
4949 4950 if not m(f):
4950 4951 continue
4951 4952
4952 4953 didwork = True
4953 4954
4954 4955 # don't let driver-resolved files be marked, and run the conclude
4955 4956 # step if asked to resolve
4956 4957 if ms[f] == mergemod.MERGE_RECORD_DRIVER_RESOLVED:
4957 4958 exact = m.exact(f)
4958 4959 if mark:
4959 4960 if exact:
4960 4961 ui.warn(_('not marking %s as it is driver-resolved\n')
4961 4962 % uipathfn(f))
4962 4963 elif unmark:
4963 4964 if exact:
4964 4965 ui.warn(_('not unmarking %s as it is driver-resolved\n')
4965 4966 % uipathfn(f))
4966 4967 else:
4967 4968 runconclude = True
4968 4969 continue
4969 4970
4970 4971 # path conflicts must be resolved manually
4971 4972 if ms[f] in (mergemod.MERGE_RECORD_UNRESOLVED_PATH,
4972 4973 mergemod.MERGE_RECORD_RESOLVED_PATH):
4973 4974 if mark:
4974 4975 ms.mark(f, mergemod.MERGE_RECORD_RESOLVED_PATH)
4975 4976 elif unmark:
4976 4977 ms.mark(f, mergemod.MERGE_RECORD_UNRESOLVED_PATH)
4977 4978 elif ms[f] == mergemod.MERGE_RECORD_UNRESOLVED_PATH:
4978 4979 ui.warn(_('%s: path conflict must be resolved manually\n')
4979 4980 % uipathfn(f))
4980 4981 continue
4981 4982
4982 4983 if mark:
4983 4984 if markcheck:
4984 4985 fdata = repo.wvfs.tryread(f)
4985 4986 if (filemerge.hasconflictmarkers(fdata) and
4986 4987 ms[f] != mergemod.MERGE_RECORD_RESOLVED):
4987 4988 hasconflictmarkers.append(f)
4988 4989 ms.mark(f, mergemod.MERGE_RECORD_RESOLVED)
4989 4990 elif unmark:
4990 4991 ms.mark(f, mergemod.MERGE_RECORD_UNRESOLVED)
4991 4992 else:
4992 4993 # backup pre-resolve (merge uses .orig for its own purposes)
4993 4994 a = repo.wjoin(f)
4994 4995 try:
4995 4996 util.copyfile(a, a + ".resolve")
4996 4997 except (IOError, OSError) as inst:
4997 4998 if inst.errno != errno.ENOENT:
4998 4999 raise
4999 5000
5000 5001 try:
5001 5002 # preresolve file
5002 5003 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
5003 5004 with ui.configoverride(overrides, 'resolve'):
5004 5005 complete, r = ms.preresolve(f, wctx)
5005 5006 if not complete:
5006 5007 tocomplete.append(f)
5007 5008 elif r:
5008 5009 ret = 1
5009 5010 finally:
5010 5011 ms.commit()
5011 5012
5012 5013 # replace filemerge's .orig file with our resolve file, but only
5013 5014 # for merges that are complete
5014 5015 if complete:
5015 5016 try:
5016 5017 util.rename(a + ".resolve",
5017 5018 scmutil.backuppath(ui, repo, f))
5018 5019 except OSError as inst:
5019 5020 if inst.errno != errno.ENOENT:
5020 5021 raise
5021 5022
5022 5023 if hasconflictmarkers:
5023 5024 ui.warn(_('warning: the following files still have conflict '
5024 5025 'markers:\n') + ''.join(' ' + uipathfn(f) + '\n'
5025 5026 for f in hasconflictmarkers))
5026 5027 if markcheck == 'abort' and not all and not pats:
5027 5028 raise error.Abort(_('conflict markers detected'),
5028 5029 hint=_('use --all to mark anyway'))
5029 5030
5030 5031 for f in tocomplete:
5031 5032 try:
5032 5033 # resolve file
5033 5034 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
5034 5035 with ui.configoverride(overrides, 'resolve'):
5035 5036 r = ms.resolve(f, wctx)
5036 5037 if r:
5037 5038 ret = 1
5038 5039 finally:
5039 5040 ms.commit()
5040 5041
5041 5042 # replace filemerge's .orig file with our resolve file
5042 5043 a = repo.wjoin(f)
5043 5044 try:
5044 5045 util.rename(a + ".resolve", scmutil.backuppath(ui, repo, f))
5045 5046 except OSError as inst:
5046 5047 if inst.errno != errno.ENOENT:
5047 5048 raise
5048 5049
5049 5050 ms.commit()
5050 5051 ms.recordactions()
5051 5052
5052 5053 if not didwork and pats:
5053 5054 hint = None
5054 5055 if not any([p for p in pats if p.find(':') >= 0]):
5055 5056 pats = ['path:%s' % p for p in pats]
5056 5057 m = scmutil.match(wctx, pats, opts)
5057 5058 for f in ms:
5058 5059 if not m(f):
5059 5060 continue
5060 5061 def flag(o):
5061 5062 if o == 're_merge':
5062 5063 return '--re-merge '
5063 5064 return '-%s ' % o[0:1]
5064 5065 flags = ''.join([flag(o) for o in flaglist if opts.get(o)])
5065 5066 hint = _("(try: hg resolve %s%s)\n") % (
5066 5067 flags,
5067 5068 ' '.join(pats))
5068 5069 break
5069 5070 ui.warn(_("arguments do not match paths that need resolving\n"))
5070 5071 if hint:
5071 5072 ui.warn(hint)
5072 5073 elif ms.mergedriver and ms.mdstate() != 's':
5073 5074 # run conclude step when either a driver-resolved file is requested
5074 5075 # or there are no driver-resolved files
5075 5076 # we can't use 'ret' to determine whether any files are unresolved
5076 5077 # because we might not have tried to resolve some
5077 5078 if ((runconclude or not list(ms.driverresolved()))
5078 5079 and not list(ms.unresolved())):
5079 5080 proceed = mergemod.driverconclude(repo, ms, wctx)
5080 5081 ms.commit()
5081 5082 if not proceed:
5082 5083 return 1
5083 5084
5084 5085 # Nudge users into finishing an unfinished operation
5085 5086 unresolvedf = list(ms.unresolved())
5086 5087 driverresolvedf = list(ms.driverresolved())
5087 5088 if not unresolvedf and not driverresolvedf:
5088 5089 ui.status(_('(no more unresolved files)\n'))
5089 5090 cmdutil.checkafterresolved(repo)
5090 5091 elif not unresolvedf:
5091 5092 ui.status(_('(no more unresolved files -- '
5092 5093 'run "hg resolve --all" to conclude)\n'))
5093 5094
5094 5095 return ret
5095 5096
5096 5097 @command('revert',
5097 5098 [('a', 'all', None, _('revert all changes when no arguments given')),
5098 5099 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
5099 5100 ('r', 'rev', '', _('revert to the specified revision'), _('REV')),
5100 5101 ('C', 'no-backup', None, _('do not save backup copies of files')),
5101 5102 ('i', 'interactive', None, _('interactively select the changes')),
5102 5103 ] + walkopts + dryrunopts,
5103 5104 _('[OPTION]... [-r REV] [NAME]...'),
5104 5105 helpcategory=command.CATEGORY_WORKING_DIRECTORY)
5105 5106 def revert(ui, repo, *pats, **opts):
5106 5107 """restore files to their checkout state
5107 5108
5108 5109 .. note::
5109 5110
5110 5111 To check out earlier revisions, you should use :hg:`update REV`.
5111 5112 To cancel an uncommitted merge (and lose your changes),
5112 5113 use :hg:`merge --abort`.
5113 5114
5114 5115 With no revision specified, revert the specified files or directories
5115 5116 to the contents they had in the parent of the working directory.
5116 5117 This restores the contents of files to an unmodified
5117 5118 state and unschedules adds, removes, copies, and renames. If the
5118 5119 working directory has two parents, you must explicitly specify a
5119 5120 revision.
5120 5121
5121 5122 Using the -r/--rev or -d/--date options, revert the given files or
5122 5123 directories to their states as of a specific revision. Because
5123 5124 revert does not change the working directory parents, this will
5124 5125 cause these files to appear modified. This can be helpful to "back
5125 5126 out" some or all of an earlier change. See :hg:`backout` for a
5126 5127 related method.
5127 5128
5128 5129 Modified files are saved with a .orig suffix before reverting.
5129 5130 To disable these backups, use --no-backup. It is possible to store
5130 5131 the backup files in a custom directory relative to the root of the
5131 5132 repository by setting the ``ui.origbackuppath`` configuration
5132 5133 option.
5133 5134
5134 5135 See :hg:`help dates` for a list of formats valid for -d/--date.
5135 5136
5136 5137 See :hg:`help backout` for a way to reverse the effect of an
5137 5138 earlier changeset.
5138 5139
5139 5140 Returns 0 on success.
5140 5141 """
5141 5142
5142 5143 opts = pycompat.byteskwargs(opts)
5143 5144 if opts.get("date"):
5144 5145 if opts.get("rev"):
5145 5146 raise error.Abort(_("you can't specify a revision and a date"))
5146 5147 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
5147 5148
5148 5149 parent, p2 = repo.dirstate.parents()
5149 5150 if not opts.get('rev') and p2 != nullid:
5150 5151 # revert after merge is a trap for new users (issue2915)
5151 5152 raise error.Abort(_('uncommitted merge with no revision specified'),
5152 5153 hint=_("use 'hg update' or see 'hg help revert'"))
5153 5154
5154 5155 rev = opts.get('rev')
5155 5156 if rev:
5156 5157 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
5157 5158 ctx = scmutil.revsingle(repo, rev)
5158 5159
5159 5160 if (not (pats or opts.get('include') or opts.get('exclude') or
5160 5161 opts.get('all') or opts.get('interactive'))):
5161 5162 msg = _("no files or directories specified")
5162 5163 if p2 != nullid:
5163 5164 hint = _("uncommitted merge, use --all to discard all changes,"
5164 5165 " or 'hg update -C .' to abort the merge")
5165 5166 raise error.Abort(msg, hint=hint)
5166 5167 dirty = any(repo.status())
5167 5168 node = ctx.node()
5168 5169 if node != parent:
5169 5170 if dirty:
5170 5171 hint = _("uncommitted changes, use --all to discard all"
5171 5172 " changes, or 'hg update %d' to update") % ctx.rev()
5172 5173 else:
5173 5174 hint = _("use --all to revert all files,"
5174 5175 " or 'hg update %d' to update") % ctx.rev()
5175 5176 elif dirty:
5176 5177 hint = _("uncommitted changes, use --all to discard all changes")
5177 5178 else:
5178 5179 hint = _("use --all to revert all files")
5179 5180 raise error.Abort(msg, hint=hint)
5180 5181
5181 5182 return cmdutil.revert(ui, repo, ctx, (parent, p2), *pats,
5182 5183 **pycompat.strkwargs(opts))
5183 5184
5184 5185 @command(
5185 5186 'rollback',
5186 5187 dryrunopts + [('f', 'force', False, _('ignore safety measures'))],
5187 5188 helpcategory=command.CATEGORY_MAINTENANCE)
5188 5189 def rollback(ui, repo, **opts):
5189 5190 """roll back the last transaction (DANGEROUS) (DEPRECATED)
5190 5191
5191 5192 Please use :hg:`commit --amend` instead of rollback to correct
5192 5193 mistakes in the last commit.
5193 5194
5194 5195 This command should be used with care. There is only one level of
5195 5196 rollback, and there is no way to undo a rollback. It will also
5196 5197 restore the dirstate at the time of the last transaction, losing
5197 5198 any dirstate changes since that time. This command does not alter
5198 5199 the working directory.
5199 5200
5200 5201 Transactions are used to encapsulate the effects of all commands
5201 5202 that create new changesets or propagate existing changesets into a
5202 5203 repository.
5203 5204
5204 5205 .. container:: verbose
5205 5206
5206 5207 For example, the following commands are transactional, and their
5207 5208 effects can be rolled back:
5208 5209
5209 5210 - commit
5210 5211 - import
5211 5212 - pull
5212 5213 - push (with this repository as the destination)
5213 5214 - unbundle
5214 5215
5215 5216 To avoid permanent data loss, rollback will refuse to rollback a
5216 5217 commit transaction if it isn't checked out. Use --force to
5217 5218 override this protection.
5218 5219
5219 5220 The rollback command can be entirely disabled by setting the
5220 5221 ``ui.rollback`` configuration setting to false. If you're here
5221 5222 because you want to use rollback and it's disabled, you can
5222 5223 re-enable the command by setting ``ui.rollback`` to true.
5223 5224
5224 5225 This command is not intended for use on public repositories. Once
5225 5226 changes are visible for pull by other users, rolling a transaction
5226 5227 back locally is ineffective (someone else may already have pulled
5227 5228 the changes). Furthermore, a race is possible with readers of the
5228 5229 repository; for example an in-progress pull from the repository
5229 5230 may fail if a rollback is performed.
5230 5231
5231 5232 Returns 0 on success, 1 if no rollback data is available.
5232 5233 """
5233 5234 if not ui.configbool('ui', 'rollback'):
5234 5235 raise error.Abort(_('rollback is disabled because it is unsafe'),
5235 5236 hint=('see `hg help -v rollback` for information'))
5236 5237 return repo.rollback(dryrun=opts.get(r'dry_run'),
5237 5238 force=opts.get(r'force'))
5238 5239
5239 5240 @command(
5240 5241 'root', [] + formatteropts, intents={INTENT_READONLY},
5241 5242 helpcategory=command.CATEGORY_WORKING_DIRECTORY)
5242 5243 def root(ui, repo, **opts):
5243 5244 """print the root (top) of the current working directory
5244 5245
5245 5246 Print the root directory of the current repository.
5246 5247
5247 5248 .. container:: verbose
5248 5249
5249 5250 Template:
5250 5251
5251 5252 The following keywords are supported in addition to the common template
5252 5253 keywords and functions. See also :hg:`help templates`.
5253 5254
5254 5255 :hgpath: String. Path to the .hg directory.
5255 5256 :storepath: String. Path to the directory holding versioned data.
5256 5257
5257 5258 Returns 0 on success.
5258 5259 """
5259 5260 opts = pycompat.byteskwargs(opts)
5260 5261 with ui.formatter('root', opts) as fm:
5261 5262 fm.startitem()
5262 5263 fm.write('reporoot', '%s\n', repo.root)
5263 5264 fm.data(hgpath=repo.path, storepath=repo.spath)
5264 5265
5265 5266 @command('serve',
5266 5267 [('A', 'accesslog', '', _('name of access log file to write to'),
5267 5268 _('FILE')),
5268 5269 ('d', 'daemon', None, _('run server in background')),
5269 5270 ('', 'daemon-postexec', [], _('used internally by daemon mode')),
5270 5271 ('E', 'errorlog', '', _('name of error log file to write to'), _('FILE')),
5271 5272 # use string type, then we can check if something was passed
5272 5273 ('p', 'port', '', _('port to listen on (default: 8000)'), _('PORT')),
5273 5274 ('a', 'address', '', _('address to listen on (default: all interfaces)'),
5274 5275 _('ADDR')),
5275 5276 ('', 'prefix', '', _('prefix path to serve from (default: server root)'),
5276 5277 _('PREFIX')),
5277 5278 ('n', 'name', '',
5278 5279 _('name to show in web pages (default: working directory)'), _('NAME')),
5279 5280 ('', 'web-conf', '',
5280 5281 _("name of the hgweb config file (see 'hg help hgweb')"), _('FILE')),
5281 5282 ('', 'webdir-conf', '', _('name of the hgweb config file (DEPRECATED)'),
5282 5283 _('FILE')),
5283 5284 ('', 'pid-file', '', _('name of file to write process ID to'), _('FILE')),
5284 5285 ('', 'stdio', None, _('for remote clients (ADVANCED)')),
5285 5286 ('', 'cmdserver', '', _('for remote clients (ADVANCED)'), _('MODE')),
5286 5287 ('t', 'templates', '', _('web templates to use'), _('TEMPLATE')),
5287 5288 ('', 'style', '', _('template style to use'), _('STYLE')),
5288 5289 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
5289 5290 ('', 'certificate', '', _('SSL certificate file'), _('FILE')),
5290 5291 ('', 'print-url', None, _('start and print only the URL'))]
5291 5292 + subrepoopts,
5292 5293 _('[OPTION]...'),
5293 5294 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5294 5295 helpbasic=True, optionalrepo=True)
5295 5296 def serve(ui, repo, **opts):
5296 5297 """start stand-alone webserver
5297 5298
5298 5299 Start a local HTTP repository browser and pull server. You can use
5299 5300 this for ad-hoc sharing and browsing of repositories. It is
5300 5301 recommended to use a real web server to serve a repository for
5301 5302 longer periods of time.
5302 5303
5303 5304 Please note that the server does not implement access control.
5304 5305 This means that, by default, anybody can read from the server and
5305 5306 nobody can write to it by default. Set the ``web.allow-push``
5306 5307 option to ``*`` to allow everybody to push to the server. You
5307 5308 should use a real web server if you need to authenticate users.
5308 5309
5309 5310 By default, the server logs accesses to stdout and errors to
5310 5311 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
5311 5312 files.
5312 5313
5313 5314 To have the server choose a free port number to listen on, specify
5314 5315 a port number of 0; in this case, the server will print the port
5315 5316 number it uses.
5316 5317
5317 5318 Returns 0 on success.
5318 5319 """
5319 5320
5320 5321 opts = pycompat.byteskwargs(opts)
5321 5322 if opts["stdio"] and opts["cmdserver"]:
5322 5323 raise error.Abort(_("cannot use --stdio with --cmdserver"))
5323 5324 if opts["print_url"] and ui.verbose:
5324 5325 raise error.Abort(_("cannot use --print-url with --verbose"))
5325 5326
5326 5327 if opts["stdio"]:
5327 5328 if repo is None:
5328 5329 raise error.RepoError(_("there is no Mercurial repository here"
5329 5330 " (.hg not found)"))
5330 5331 s = wireprotoserver.sshserver(ui, repo)
5331 5332 s.serve_forever()
5332 5333
5333 5334 service = server.createservice(ui, repo, opts)
5334 5335 return server.runservice(opts, initfn=service.init, runfn=service.run)
5335 5336
5336 5337 @command('shelve',
5337 5338 [('A', 'addremove', None,
5338 5339 _('mark new/missing files as added/removed before shelving')),
5339 5340 ('u', 'unknown', None,
5340 5341 _('store unknown files in the shelve')),
5341 5342 ('', 'cleanup', None,
5342 5343 _('delete all shelved changes')),
5343 5344 ('', 'date', '',
5344 5345 _('shelve with the specified commit date'), _('DATE')),
5345 5346 ('d', 'delete', None,
5346 5347 _('delete the named shelved change(s)')),
5347 5348 ('e', 'edit', False,
5348 5349 _('invoke editor on commit messages')),
5349 5350 ('k', 'keep', False,
5350 5351 _('shelve, but keep changes in the working directory')),
5351 5352 ('l', 'list', None,
5352 5353 _('list current shelves')),
5353 5354 ('m', 'message', '',
5354 5355 _('use text as shelve message'), _('TEXT')),
5355 5356 ('n', 'name', '',
5356 5357 _('use the given name for the shelved commit'), _('NAME')),
5357 5358 ('p', 'patch', None,
5358 5359 _('output patches for changes (provide the names of the shelved '
5359 5360 'changes as positional arguments)')),
5360 5361 ('i', 'interactive', None,
5361 5362 _('interactive mode')),
5362 5363 ('', 'stat', None,
5363 5364 _('output diffstat-style summary of changes (provide the names of '
5364 5365 'the shelved changes as positional arguments)')
5365 5366 )] + cmdutil.walkopts,
5366 5367 _('hg shelve [OPTION]... [FILE]...'),
5367 5368 helpcategory=command.CATEGORY_WORKING_DIRECTORY)
5368 5369 def shelve(ui, repo, *pats, **opts):
5369 5370 '''save and set aside changes from the working directory
5370 5371
5371 5372 Shelving takes files that "hg status" reports as not clean, saves
5372 5373 the modifications to a bundle (a shelved change), and reverts the
5373 5374 files so that their state in the working directory becomes clean.
5374 5375
5375 5376 To restore these changes to the working directory, using "hg
5376 5377 unshelve"; this will work even if you switch to a different
5377 5378 commit.
5378 5379
5379 5380 When no files are specified, "hg shelve" saves all not-clean
5380 5381 files. If specific files or directories are named, only changes to
5381 5382 those files are shelved.
5382 5383
5383 5384 In bare shelve (when no files are specified, without interactive,
5384 5385 include and exclude option), shelving remembers information if the
5385 5386 working directory was on newly created branch, in other words working
5386 5387 directory was on different branch than its first parent. In this
5387 5388 situation unshelving restores branch information to the working directory.
5388 5389
5389 5390 Each shelved change has a name that makes it easier to find later.
5390 5391 The name of a shelved change defaults to being based on the active
5391 5392 bookmark, or if there is no active bookmark, the current named
5392 5393 branch. To specify a different name, use ``--name``.
5393 5394
5394 5395 To see a list of existing shelved changes, use the ``--list``
5395 5396 option. For each shelved change, this will print its name, age,
5396 5397 and description; use ``--patch`` or ``--stat`` for more details.
5397 5398
5398 5399 To delete specific shelved changes, use ``--delete``. To delete
5399 5400 all shelved changes, use ``--cleanup``.
5400 5401 '''
5401 5402 opts = pycompat.byteskwargs(opts)
5402 5403 allowables = [
5403 5404 ('addremove', {'create'}), # 'create' is pseudo action
5404 5405 ('unknown', {'create'}),
5405 5406 ('cleanup', {'cleanup'}),
5406 5407 # ('date', {'create'}), # ignored for passing '--date "0 0"' in tests
5407 5408 ('delete', {'delete'}),
5408 5409 ('edit', {'create'}),
5409 5410 ('keep', {'create'}),
5410 5411 ('list', {'list'}),
5411 5412 ('message', {'create'}),
5412 5413 ('name', {'create'}),
5413 5414 ('patch', {'patch', 'list'}),
5414 5415 ('stat', {'stat', 'list'}),
5415 5416 ]
5416 5417 def checkopt(opt):
5417 5418 if opts.get(opt):
5418 5419 for i, allowable in allowables:
5419 5420 if opts[i] and opt not in allowable:
5420 5421 raise error.Abort(_("options '--%s' and '--%s' may not be "
5421 5422 "used together") % (opt, i))
5422 5423 return True
5423 5424 if checkopt('cleanup'):
5424 5425 if pats:
5425 5426 raise error.Abort(_("cannot specify names when using '--cleanup'"))
5426 5427 return shelvemod.cleanupcmd(ui, repo)
5427 5428 elif checkopt('delete'):
5428 5429 return shelvemod.deletecmd(ui, repo, pats)
5429 5430 elif checkopt('list'):
5430 5431 return shelvemod.listcmd(ui, repo, pats, opts)
5431 5432 elif checkopt('patch') or checkopt('stat'):
5432 5433 return shelvemod.patchcmds(ui, repo, pats, opts)
5433 5434 else:
5434 5435 return shelvemod.createcmd(ui, repo, pats, opts)
5435 5436
5436 5437 _NOTTERSE = 'nothing'
5437 5438
5438 5439 @command('status|st',
5439 5440 [('A', 'all', None, _('show status of all files')),
5440 5441 ('m', 'modified', None, _('show only modified files')),
5441 5442 ('a', 'added', None, _('show only added files')),
5442 5443 ('r', 'removed', None, _('show only removed files')),
5443 5444 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
5444 5445 ('c', 'clean', None, _('show only files without changes')),
5445 5446 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
5446 5447 ('i', 'ignored', None, _('show only ignored files')),
5447 5448 ('n', 'no-status', None, _('hide status prefix')),
5448 5449 ('t', 'terse', _NOTTERSE, _('show the terse output (EXPERIMENTAL)')),
5449 5450 ('C', 'copies', None, _('show source of copied files')),
5450 5451 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
5451 5452 ('', 'rev', [], _('show difference from revision'), _('REV')),
5452 5453 ('', 'change', '', _('list the changed files of a revision'), _('REV')),
5453 5454 ] + walkopts + subrepoopts + formatteropts,
5454 5455 _('[OPTION]... [FILE]...'),
5455 5456 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5456 5457 helpbasic=True, inferrepo=True,
5457 5458 intents={INTENT_READONLY})
5458 5459 def status(ui, repo, *pats, **opts):
5459 5460 """show changed files in the working directory
5460 5461
5461 5462 Show status of files in the repository. If names are given, only
5462 5463 files that match are shown. Files that are clean or ignored or
5463 5464 the source of a copy/move operation, are not listed unless
5464 5465 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
5465 5466 Unless options described with "show only ..." are given, the
5466 5467 options -mardu are used.
5467 5468
5468 5469 Option -q/--quiet hides untracked (unknown and ignored) files
5469 5470 unless explicitly requested with -u/--unknown or -i/--ignored.
5470 5471
5471 5472 .. note::
5472 5473
5473 5474 :hg:`status` may appear to disagree with diff if permissions have
5474 5475 changed or a merge has occurred. The standard diff format does
5475 5476 not report permission changes and diff only reports changes
5476 5477 relative to one merge parent.
5477 5478
5478 5479 If one revision is given, it is used as the base revision.
5479 5480 If two revisions are given, the differences between them are
5480 5481 shown. The --change option can also be used as a shortcut to list
5481 5482 the changed files of a revision from its first parent.
5482 5483
5483 5484 The codes used to show the status of files are::
5484 5485
5485 5486 M = modified
5486 5487 A = added
5487 5488 R = removed
5488 5489 C = clean
5489 5490 ! = missing (deleted by non-hg command, but still tracked)
5490 5491 ? = not tracked
5491 5492 I = ignored
5492 5493 = origin of the previous file (with --copies)
5493 5494
5494 5495 .. container:: verbose
5495 5496
5496 5497 The -t/--terse option abbreviates the output by showing only the directory
5497 5498 name if all the files in it share the same status. The option takes an
5498 5499 argument indicating the statuses to abbreviate: 'm' for 'modified', 'a'
5499 5500 for 'added', 'r' for 'removed', 'd' for 'deleted', 'u' for 'unknown', 'i'
5500 5501 for 'ignored' and 'c' for clean.
5501 5502
5502 5503 It abbreviates only those statuses which are passed. Note that clean and
5503 5504 ignored files are not displayed with '--terse ic' unless the -c/--clean
5504 5505 and -i/--ignored options are also used.
5505 5506
5506 5507 The -v/--verbose option shows information when the repository is in an
5507 5508 unfinished merge, shelve, rebase state etc. You can have this behavior
5508 5509 turned on by default by enabling the ``commands.status.verbose`` option.
5509 5510
5510 5511 You can skip displaying some of these states by setting
5511 5512 ``commands.status.skipstates`` to one or more of: 'bisect', 'graft',
5512 5513 'histedit', 'merge', 'rebase', or 'unshelve'.
5513 5514
5514 5515 Template:
5515 5516
5516 5517 The following keywords are supported in addition to the common template
5517 5518 keywords and functions. See also :hg:`help templates`.
5518 5519
5519 5520 :path: String. Repository-absolute path of the file.
5520 5521 :source: String. Repository-absolute path of the file originated from.
5521 5522 Available if ``--copies`` is specified.
5522 5523 :status: String. Character denoting file's status.
5523 5524
5524 5525 Examples:
5525 5526
5526 5527 - show changes in the working directory relative to a
5527 5528 changeset::
5528 5529
5529 5530 hg status --rev 9353
5530 5531
5531 5532 - show changes in the working directory relative to the
5532 5533 current directory (see :hg:`help patterns` for more information)::
5533 5534
5534 5535 hg status re:
5535 5536
5536 5537 - show all changes including copies in an existing changeset::
5537 5538
5538 5539 hg status --copies --change 9353
5539 5540
5540 5541 - get a NUL separated list of added files, suitable for xargs::
5541 5542
5542 5543 hg status -an0
5543 5544
5544 5545 - show more information about the repository status, abbreviating
5545 5546 added, removed, modified, deleted, and untracked paths::
5546 5547
5547 5548 hg status -v -t mardu
5548 5549
5549 5550 Returns 0 on success.
5550 5551
5551 5552 """
5552 5553
5553 5554 opts = pycompat.byteskwargs(opts)
5554 5555 revs = opts.get('rev')
5555 5556 change = opts.get('change')
5556 5557 terse = opts.get('terse')
5557 5558 if terse is _NOTTERSE:
5558 5559 if revs:
5559 5560 terse = ''
5560 5561 else:
5561 5562 terse = ui.config('commands', 'status.terse')
5562 5563
5563 5564 if revs and change:
5564 5565 msg = _('cannot specify --rev and --change at the same time')
5565 5566 raise error.Abort(msg)
5566 5567 elif revs and terse:
5567 5568 msg = _('cannot use --terse with --rev')
5568 5569 raise error.Abort(msg)
5569 5570 elif change:
5570 5571 repo = scmutil.unhidehashlikerevs(repo, [change], 'nowarn')
5571 5572 ctx2 = scmutil.revsingle(repo, change, None)
5572 5573 ctx1 = ctx2.p1()
5573 5574 else:
5574 5575 repo = scmutil.unhidehashlikerevs(repo, revs, 'nowarn')
5575 5576 ctx1, ctx2 = scmutil.revpair(repo, revs)
5576 5577
5577 5578 forcerelativevalue = None
5578 5579 if ui.hasconfig('commands', 'status.relative'):
5579 5580 forcerelativevalue = ui.configbool('commands', 'status.relative')
5580 5581 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=bool(pats),
5581 5582 forcerelativevalue=forcerelativevalue)
5582 5583
5583 5584 if opts.get('print0'):
5584 5585 end = '\0'
5585 5586 else:
5586 5587 end = '\n'
5587 5588 copy = {}
5588 5589 states = 'modified added removed deleted unknown ignored clean'.split()
5589 5590 show = [k for k in states if opts.get(k)]
5590 5591 if opts.get('all'):
5591 5592 show += ui.quiet and (states[:4] + ['clean']) or states
5592 5593
5593 5594 if not show:
5594 5595 if ui.quiet:
5595 5596 show = states[:4]
5596 5597 else:
5597 5598 show = states[:5]
5598 5599
5599 5600 m = scmutil.match(ctx2, pats, opts)
5600 5601 if terse:
5601 5602 # we need to compute clean and unknown to terse
5602 5603 stat = repo.status(ctx1.node(), ctx2.node(), m,
5603 5604 'ignored' in show or 'i' in terse,
5604 5605 clean=True, unknown=True,
5605 5606 listsubrepos=opts.get('subrepos'))
5606 5607
5607 5608 stat = cmdutil.tersedir(stat, terse)
5608 5609 else:
5609 5610 stat = repo.status(ctx1.node(), ctx2.node(), m,
5610 5611 'ignored' in show, 'clean' in show,
5611 5612 'unknown' in show, opts.get('subrepos'))
5612 5613
5613 5614 changestates = zip(states, pycompat.iterbytestr('MAR!?IC'), stat)
5614 5615
5615 5616 if (opts.get('all') or opts.get('copies')
5616 5617 or ui.configbool('ui', 'statuscopies')) and not opts.get('no_status'):
5617 5618 copy = copies.pathcopies(ctx1, ctx2, m)
5618 5619
5619 5620 ui.pager('status')
5620 5621 fm = ui.formatter('status', opts)
5621 5622 fmt = '%s' + end
5622 5623 showchar = not opts.get('no_status')
5623 5624
5624 5625 for state, char, files in changestates:
5625 5626 if state in show:
5626 5627 label = 'status.' + state
5627 5628 for f in files:
5628 5629 fm.startitem()
5629 5630 fm.context(ctx=ctx2)
5630 5631 fm.data(path=f)
5631 5632 fm.condwrite(showchar, 'status', '%s ', char, label=label)
5632 5633 fm.plain(fmt % uipathfn(f), label=label)
5633 5634 if f in copy:
5634 5635 fm.data(source=copy[f])
5635 5636 fm.plain((' %s' + end) % uipathfn(copy[f]),
5636 5637 label='status.copied')
5637 5638
5638 5639 if ((ui.verbose or ui.configbool('commands', 'status.verbose'))
5639 5640 and not ui.plain()):
5640 5641 cmdutil.morestatus(repo, fm)
5641 5642 fm.end()
5642 5643
5643 5644 @command('summary|sum',
5644 5645 [('', 'remote', None, _('check for push and pull'))],
5645 5646 '[--remote]',
5646 5647 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5647 5648 helpbasic=True,
5648 5649 intents={INTENT_READONLY})
5649 5650 def summary(ui, repo, **opts):
5650 5651 """summarize working directory state
5651 5652
5652 5653 This generates a brief summary of the working directory state,
5653 5654 including parents, branch, commit status, phase and available updates.
5654 5655
5655 5656 With the --remote option, this will check the default paths for
5656 5657 incoming and outgoing changes. This can be time-consuming.
5657 5658
5658 5659 Returns 0 on success.
5659 5660 """
5660 5661
5661 5662 opts = pycompat.byteskwargs(opts)
5662 5663 ui.pager('summary')
5663 5664 ctx = repo[None]
5664 5665 parents = ctx.parents()
5665 5666 pnode = parents[0].node()
5666 5667 marks = []
5667 5668
5668 5669 try:
5669 5670 ms = mergemod.mergestate.read(repo)
5670 5671 except error.UnsupportedMergeRecords as e:
5671 5672 s = ' '.join(e.recordtypes)
5672 5673 ui.warn(
5673 5674 _('warning: merge state has unsupported record types: %s\n') % s)
5674 5675 unresolved = []
5675 5676 else:
5676 5677 unresolved = list(ms.unresolved())
5677 5678
5678 5679 for p in parents:
5679 5680 # label with log.changeset (instead of log.parent) since this
5680 5681 # shows a working directory parent *changeset*:
5681 5682 # i18n: column positioning for "hg summary"
5682 5683 ui.write(_('parent: %d:%s ') % (p.rev(), p),
5683 5684 label=logcmdutil.changesetlabels(p))
5684 5685 ui.write(' '.join(p.tags()), label='log.tag')
5685 5686 if p.bookmarks():
5686 5687 marks.extend(p.bookmarks())
5687 5688 if p.rev() == -1:
5688 5689 if not len(repo):
5689 5690 ui.write(_(' (empty repository)'))
5690 5691 else:
5691 5692 ui.write(_(' (no revision checked out)'))
5692 5693 if p.obsolete():
5693 5694 ui.write(_(' (obsolete)'))
5694 5695 if p.isunstable():
5695 5696 instabilities = (ui.label(instability, 'trouble.%s' % instability)
5696 5697 for instability in p.instabilities())
5697 5698 ui.write(' ('
5698 5699 + ', '.join(instabilities)
5699 5700 + ')')
5700 5701 ui.write('\n')
5701 5702 if p.description():
5702 5703 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
5703 5704 label='log.summary')
5704 5705
5705 5706 branch = ctx.branch()
5706 5707 bheads = repo.branchheads(branch)
5707 5708 # i18n: column positioning for "hg summary"
5708 5709 m = _('branch: %s\n') % branch
5709 5710 if branch != 'default':
5710 5711 ui.write(m, label='log.branch')
5711 5712 else:
5712 5713 ui.status(m, label='log.branch')
5713 5714
5714 5715 if marks:
5715 5716 active = repo._activebookmark
5716 5717 # i18n: column positioning for "hg summary"
5717 5718 ui.write(_('bookmarks:'), label='log.bookmark')
5718 5719 if active is not None:
5719 5720 if active in marks:
5720 5721 ui.write(' *' + active, label=bookmarks.activebookmarklabel)
5721 5722 marks.remove(active)
5722 5723 else:
5723 5724 ui.write(' [%s]' % active, label=bookmarks.activebookmarklabel)
5724 5725 for m in marks:
5725 5726 ui.write(' ' + m, label='log.bookmark')
5726 5727 ui.write('\n', label='log.bookmark')
5727 5728
5728 5729 status = repo.status(unknown=True)
5729 5730
5730 5731 c = repo.dirstate.copies()
5731 5732 copied, renamed = [], []
5732 5733 for d, s in c.iteritems():
5733 5734 if s in status.removed:
5734 5735 status.removed.remove(s)
5735 5736 renamed.append(d)
5736 5737 else:
5737 5738 copied.append(d)
5738 5739 if d in status.added:
5739 5740 status.added.remove(d)
5740 5741
5741 5742 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
5742 5743
5743 5744 labels = [(ui.label(_('%d modified'), 'status.modified'), status.modified),
5744 5745 (ui.label(_('%d added'), 'status.added'), status.added),
5745 5746 (ui.label(_('%d removed'), 'status.removed'), status.removed),
5746 5747 (ui.label(_('%d renamed'), 'status.copied'), renamed),
5747 5748 (ui.label(_('%d copied'), 'status.copied'), copied),
5748 5749 (ui.label(_('%d deleted'), 'status.deleted'), status.deleted),
5749 5750 (ui.label(_('%d unknown'), 'status.unknown'), status.unknown),
5750 5751 (ui.label(_('%d unresolved'), 'resolve.unresolved'), unresolved),
5751 5752 (ui.label(_('%d subrepos'), 'status.modified'), subs)]
5752 5753 t = []
5753 5754 for l, s in labels:
5754 5755 if s:
5755 5756 t.append(l % len(s))
5756 5757
5757 5758 t = ', '.join(t)
5758 5759 cleanworkdir = False
5759 5760
5760 5761 if repo.vfs.exists('graftstate'):
5761 5762 t += _(' (graft in progress)')
5762 5763 if repo.vfs.exists('updatestate'):
5763 5764 t += _(' (interrupted update)')
5764 5765 elif len(parents) > 1:
5765 5766 t += _(' (merge)')
5766 5767 elif branch != parents[0].branch():
5767 5768 t += _(' (new branch)')
5768 5769 elif (parents[0].closesbranch() and
5769 5770 pnode in repo.branchheads(branch, closed=True)):
5770 5771 t += _(' (head closed)')
5771 5772 elif not (status.modified or status.added or status.removed or renamed or
5772 5773 copied or subs):
5773 5774 t += _(' (clean)')
5774 5775 cleanworkdir = True
5775 5776 elif pnode not in bheads:
5776 5777 t += _(' (new branch head)')
5777 5778
5778 5779 if parents:
5779 5780 pendingphase = max(p.phase() for p in parents)
5780 5781 else:
5781 5782 pendingphase = phases.public
5782 5783
5783 5784 if pendingphase > phases.newcommitphase(ui):
5784 5785 t += ' (%s)' % phases.phasenames[pendingphase]
5785 5786
5786 5787 if cleanworkdir:
5787 5788 # i18n: column positioning for "hg summary"
5788 5789 ui.status(_('commit: %s\n') % t.strip())
5789 5790 else:
5790 5791 # i18n: column positioning for "hg summary"
5791 5792 ui.write(_('commit: %s\n') % t.strip())
5792 5793
5793 5794 # all ancestors of branch heads - all ancestors of parent = new csets
5794 5795 new = len(repo.changelog.findmissing([pctx.node() for pctx in parents],
5795 5796 bheads))
5796 5797
5797 5798 if new == 0:
5798 5799 # i18n: column positioning for "hg summary"
5799 5800 ui.status(_('update: (current)\n'))
5800 5801 elif pnode not in bheads:
5801 5802 # i18n: column positioning for "hg summary"
5802 5803 ui.write(_('update: %d new changesets (update)\n') % new)
5803 5804 else:
5804 5805 # i18n: column positioning for "hg summary"
5805 5806 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
5806 5807 (new, len(bheads)))
5807 5808
5808 5809 t = []
5809 5810 draft = len(repo.revs('draft()'))
5810 5811 if draft:
5811 5812 t.append(_('%d draft') % draft)
5812 5813 secret = len(repo.revs('secret()'))
5813 5814 if secret:
5814 5815 t.append(_('%d secret') % secret)
5815 5816
5816 5817 if draft or secret:
5817 5818 ui.status(_('phases: %s\n') % ', '.join(t))
5818 5819
5819 5820 if obsolete.isenabled(repo, obsolete.createmarkersopt):
5820 5821 for trouble in ("orphan", "contentdivergent", "phasedivergent"):
5821 5822 numtrouble = len(repo.revs(trouble + "()"))
5822 5823 # We write all the possibilities to ease translation
5823 5824 troublemsg = {
5824 5825 "orphan": _("orphan: %d changesets"),
5825 5826 "contentdivergent": _("content-divergent: %d changesets"),
5826 5827 "phasedivergent": _("phase-divergent: %d changesets"),
5827 5828 }
5828 5829 if numtrouble > 0:
5829 5830 ui.status(troublemsg[trouble] % numtrouble + "\n")
5830 5831
5831 5832 cmdutil.summaryhooks(ui, repo)
5832 5833
5833 5834 if opts.get('remote'):
5834 5835 needsincoming, needsoutgoing = True, True
5835 5836 else:
5836 5837 needsincoming, needsoutgoing = False, False
5837 5838 for i, o in cmdutil.summaryremotehooks(ui, repo, opts, None):
5838 5839 if i:
5839 5840 needsincoming = True
5840 5841 if o:
5841 5842 needsoutgoing = True
5842 5843 if not needsincoming and not needsoutgoing:
5843 5844 return
5844 5845
5845 5846 def getincoming():
5846 5847 source, branches = hg.parseurl(ui.expandpath('default'))
5847 5848 sbranch = branches[0]
5848 5849 try:
5849 5850 other = hg.peer(repo, {}, source)
5850 5851 except error.RepoError:
5851 5852 if opts.get('remote'):
5852 5853 raise
5853 5854 return source, sbranch, None, None, None
5854 5855 revs, checkout = hg.addbranchrevs(repo, other, branches, None)
5855 5856 if revs:
5856 5857 revs = [other.lookup(rev) for rev in revs]
5857 5858 ui.debug('comparing with %s\n' % util.hidepassword(source))
5858 5859 repo.ui.pushbuffer()
5859 5860 commoninc = discovery.findcommonincoming(repo, other, heads=revs)
5860 5861 repo.ui.popbuffer()
5861 5862 return source, sbranch, other, commoninc, commoninc[1]
5862 5863
5863 5864 if needsincoming:
5864 5865 source, sbranch, sother, commoninc, incoming = getincoming()
5865 5866 else:
5866 5867 source = sbranch = sother = commoninc = incoming = None
5867 5868
5868 5869 def getoutgoing():
5869 5870 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
5870 5871 dbranch = branches[0]
5871 5872 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
5872 5873 if source != dest:
5873 5874 try:
5874 5875 dother = hg.peer(repo, {}, dest)
5875 5876 except error.RepoError:
5876 5877 if opts.get('remote'):
5877 5878 raise
5878 5879 return dest, dbranch, None, None
5879 5880 ui.debug('comparing with %s\n' % util.hidepassword(dest))
5880 5881 elif sother is None:
5881 5882 # there is no explicit destination peer, but source one is invalid
5882 5883 return dest, dbranch, None, None
5883 5884 else:
5884 5885 dother = sother
5885 5886 if (source != dest or (sbranch is not None and sbranch != dbranch)):
5886 5887 common = None
5887 5888 else:
5888 5889 common = commoninc
5889 5890 if revs:
5890 5891 revs = [repo.lookup(rev) for rev in revs]
5891 5892 repo.ui.pushbuffer()
5892 5893 outgoing = discovery.findcommonoutgoing(repo, dother, onlyheads=revs,
5893 5894 commoninc=common)
5894 5895 repo.ui.popbuffer()
5895 5896 return dest, dbranch, dother, outgoing
5896 5897
5897 5898 if needsoutgoing:
5898 5899 dest, dbranch, dother, outgoing = getoutgoing()
5899 5900 else:
5900 5901 dest = dbranch = dother = outgoing = None
5901 5902
5902 5903 if opts.get('remote'):
5903 5904 t = []
5904 5905 if incoming:
5905 5906 t.append(_('1 or more incoming'))
5906 5907 o = outgoing.missing
5907 5908 if o:
5908 5909 t.append(_('%d outgoing') % len(o))
5909 5910 other = dother or sother
5910 5911 if 'bookmarks' in other.listkeys('namespaces'):
5911 5912 counts = bookmarks.summary(repo, other)
5912 5913 if counts[0] > 0:
5913 5914 t.append(_('%d incoming bookmarks') % counts[0])
5914 5915 if counts[1] > 0:
5915 5916 t.append(_('%d outgoing bookmarks') % counts[1])
5916 5917
5917 5918 if t:
5918 5919 # i18n: column positioning for "hg summary"
5919 5920 ui.write(_('remote: %s\n') % (', '.join(t)))
5920 5921 else:
5921 5922 # i18n: column positioning for "hg summary"
5922 5923 ui.status(_('remote: (synced)\n'))
5923 5924
5924 5925 cmdutil.summaryremotehooks(ui, repo, opts,
5925 5926 ((source, sbranch, sother, commoninc),
5926 5927 (dest, dbranch, dother, outgoing)))
5927 5928
5928 5929 @command('tag',
5929 5930 [('f', 'force', None, _('force tag')),
5930 5931 ('l', 'local', None, _('make the tag local')),
5931 5932 ('r', 'rev', '', _('revision to tag'), _('REV')),
5932 5933 ('', 'remove', None, _('remove a tag')),
5933 5934 # -l/--local is already there, commitopts cannot be used
5934 5935 ('e', 'edit', None, _('invoke editor on commit messages')),
5935 5936 ('m', 'message', '', _('use text as commit message'), _('TEXT')),
5936 5937 ] + commitopts2,
5937 5938 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'),
5938 5939 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION)
5939 5940 def tag(ui, repo, name1, *names, **opts):
5940 5941 """add one or more tags for the current or given revision
5941 5942
5942 5943 Name a particular revision using <name>.
5943 5944
5944 5945 Tags are used to name particular revisions of the repository and are
5945 5946 very useful to compare different revisions, to go back to significant
5946 5947 earlier versions or to mark branch points as releases, etc. Changing
5947 5948 an existing tag is normally disallowed; use -f/--force to override.
5948 5949
5949 5950 If no revision is given, the parent of the working directory is
5950 5951 used.
5951 5952
5952 5953 To facilitate version control, distribution, and merging of tags,
5953 5954 they are stored as a file named ".hgtags" which is managed similarly
5954 5955 to other project files and can be hand-edited if necessary. This
5955 5956 also means that tagging creates a new commit. The file
5956 5957 ".hg/localtags" is used for local tags (not shared among
5957 5958 repositories).
5958 5959
5959 5960 Tag commits are usually made at the head of a branch. If the parent
5960 5961 of the working directory is not a branch head, :hg:`tag` aborts; use
5961 5962 -f/--force to force the tag commit to be based on a non-head
5962 5963 changeset.
5963 5964
5964 5965 See :hg:`help dates` for a list of formats valid for -d/--date.
5965 5966
5966 5967 Since tag names have priority over branch names during revision
5967 5968 lookup, using an existing branch name as a tag name is discouraged.
5968 5969
5969 5970 Returns 0 on success.
5970 5971 """
5971 5972 opts = pycompat.byteskwargs(opts)
5972 5973 with repo.wlock(), repo.lock():
5973 5974 rev_ = "."
5974 5975 names = [t.strip() for t in (name1,) + names]
5975 5976 if len(names) != len(set(names)):
5976 5977 raise error.Abort(_('tag names must be unique'))
5977 5978 for n in names:
5978 5979 scmutil.checknewlabel(repo, n, 'tag')
5979 5980 if not n:
5980 5981 raise error.Abort(_('tag names cannot consist entirely of '
5981 5982 'whitespace'))
5982 5983 if opts.get('rev') and opts.get('remove'):
5983 5984 raise error.Abort(_("--rev and --remove are incompatible"))
5984 5985 if opts.get('rev'):
5985 5986 rev_ = opts['rev']
5986 5987 message = opts.get('message')
5987 5988 if opts.get('remove'):
5988 5989 if opts.get('local'):
5989 5990 expectedtype = 'local'
5990 5991 else:
5991 5992 expectedtype = 'global'
5992 5993
5993 5994 for n in names:
5994 5995 if repo.tagtype(n) == 'global':
5995 5996 alltags = tagsmod.findglobaltags(ui, repo)
5996 5997 if alltags[n][0] == nullid:
5997 5998 raise error.Abort(_("tag '%s' is already removed") % n)
5998 5999 if not repo.tagtype(n):
5999 6000 raise error.Abort(_("tag '%s' does not exist") % n)
6000 6001 if repo.tagtype(n) != expectedtype:
6001 6002 if expectedtype == 'global':
6002 6003 raise error.Abort(_("tag '%s' is not a global tag") % n)
6003 6004 else:
6004 6005 raise error.Abort(_("tag '%s' is not a local tag") % n)
6005 6006 rev_ = 'null'
6006 6007 if not message:
6007 6008 # we don't translate commit messages
6008 6009 message = 'Removed tag %s' % ', '.join(names)
6009 6010 elif not opts.get('force'):
6010 6011 for n in names:
6011 6012 if n in repo.tags():
6012 6013 raise error.Abort(_("tag '%s' already exists "
6013 6014 "(use -f to force)") % n)
6014 6015 if not opts.get('local'):
6015 6016 p1, p2 = repo.dirstate.parents()
6016 6017 if p2 != nullid:
6017 6018 raise error.Abort(_('uncommitted merge'))
6018 6019 bheads = repo.branchheads()
6019 6020 if not opts.get('force') and bheads and p1 not in bheads:
6020 6021 raise error.Abort(_('working directory is not at a branch head '
6021 6022 '(use -f to force)'))
6022 6023 node = scmutil.revsingle(repo, rev_).node()
6023 6024
6024 6025 if not message:
6025 6026 # we don't translate commit messages
6026 6027 message = ('Added tag %s for changeset %s' %
6027 6028 (', '.join(names), short(node)))
6028 6029
6029 6030 date = opts.get('date')
6030 6031 if date:
6031 6032 date = dateutil.parsedate(date)
6032 6033
6033 6034 if opts.get('remove'):
6034 6035 editform = 'tag.remove'
6035 6036 else:
6036 6037 editform = 'tag.add'
6037 6038 editor = cmdutil.getcommiteditor(editform=editform,
6038 6039 **pycompat.strkwargs(opts))
6039 6040
6040 6041 # don't allow tagging the null rev
6041 6042 if (not opts.get('remove') and
6042 6043 scmutil.revsingle(repo, rev_).rev() == nullrev):
6043 6044 raise error.Abort(_("cannot tag null revision"))
6044 6045
6045 6046 tagsmod.tag(repo, names, node, message, opts.get('local'),
6046 6047 opts.get('user'), date, editor=editor)
6047 6048
6048 6049 @command(
6049 6050 'tags', formatteropts, '',
6050 6051 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
6051 6052 intents={INTENT_READONLY})
6052 6053 def tags(ui, repo, **opts):
6053 6054 """list repository tags
6054 6055
6055 6056 This lists both regular and local tags. When the -v/--verbose
6056 6057 switch is used, a third column "local" is printed for local tags.
6057 6058 When the -q/--quiet switch is used, only the tag name is printed.
6058 6059
6059 6060 .. container:: verbose
6060 6061
6061 6062 Template:
6062 6063
6063 6064 The following keywords are supported in addition to the common template
6064 6065 keywords and functions such as ``{tag}``. See also
6065 6066 :hg:`help templates`.
6066 6067
6067 6068 :type: String. ``local`` for local tags.
6068 6069
6069 6070 Returns 0 on success.
6070 6071 """
6071 6072
6072 6073 opts = pycompat.byteskwargs(opts)
6073 6074 ui.pager('tags')
6074 6075 fm = ui.formatter('tags', opts)
6075 6076 hexfunc = fm.hexfunc
6076 6077
6077 6078 for t, n in reversed(repo.tagslist()):
6078 6079 hn = hexfunc(n)
6079 6080 label = 'tags.normal'
6080 6081 tagtype = ''
6081 6082 if repo.tagtype(t) == 'local':
6082 6083 label = 'tags.local'
6083 6084 tagtype = 'local'
6084 6085
6085 6086 fm.startitem()
6086 6087 fm.context(repo=repo)
6087 6088 fm.write('tag', '%s', t, label=label)
6088 6089 fmt = " " * (30 - encoding.colwidth(t)) + ' %5d:%s'
6089 6090 fm.condwrite(not ui.quiet, 'rev node', fmt,
6090 6091 repo.changelog.rev(n), hn, label=label)
6091 6092 fm.condwrite(ui.verbose and tagtype, 'type', ' %s',
6092 6093 tagtype, label=label)
6093 6094 fm.plain('\n')
6094 6095 fm.end()
6095 6096
6096 6097 @command('tip',
6097 6098 [('p', 'patch', None, _('show patch')),
6098 6099 ('g', 'git', None, _('use git extended diff format')),
6099 6100 ] + templateopts,
6100 6101 _('[-p] [-g]'),
6101 6102 helpcategory=command.CATEGORY_CHANGE_NAVIGATION)
6102 6103 def tip(ui, repo, **opts):
6103 6104 """show the tip revision (DEPRECATED)
6104 6105
6105 6106 The tip revision (usually just called the tip) is the changeset
6106 6107 most recently added to the repository (and therefore the most
6107 6108 recently changed head).
6108 6109
6109 6110 If you have just made a commit, that commit will be the tip. If
6110 6111 you have just pulled changes from another repository, the tip of
6111 6112 that repository becomes the current tip. The "tip" tag is special
6112 6113 and cannot be renamed or assigned to a different changeset.
6113 6114
6114 6115 This command is deprecated, please use :hg:`heads` instead.
6115 6116
6116 6117 Returns 0 on success.
6117 6118 """
6118 6119 opts = pycompat.byteskwargs(opts)
6119 6120 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
6120 6121 displayer.show(repo['tip'])
6121 6122 displayer.close()
6122 6123
6123 6124 @command('unbundle',
6124 6125 [('u', 'update', None,
6125 6126 _('update to new branch head if changesets were unbundled'))],
6126 6127 _('[-u] FILE...'),
6127 6128 helpcategory=command.CATEGORY_IMPORT_EXPORT)
6128 6129 def unbundle(ui, repo, fname1, *fnames, **opts):
6129 6130 """apply one or more bundle files
6130 6131
6131 6132 Apply one or more bundle files generated by :hg:`bundle`.
6132 6133
6133 6134 Returns 0 on success, 1 if an update has unresolved files.
6134 6135 """
6135 6136 fnames = (fname1,) + fnames
6136 6137
6137 6138 with repo.lock():
6138 6139 for fname in fnames:
6139 6140 f = hg.openpath(ui, fname)
6140 6141 gen = exchange.readbundle(ui, f, fname)
6141 6142 if isinstance(gen, streamclone.streamcloneapplier):
6142 6143 raise error.Abort(
6143 6144 _('packed bundles cannot be applied with '
6144 6145 '"hg unbundle"'),
6145 6146 hint=_('use "hg debugapplystreamclonebundle"'))
6146 6147 url = 'bundle:' + fname
6147 6148 try:
6148 6149 txnname = 'unbundle'
6149 6150 if not isinstance(gen, bundle2.unbundle20):
6150 6151 txnname = 'unbundle\n%s' % util.hidepassword(url)
6151 6152 with repo.transaction(txnname) as tr:
6152 6153 op = bundle2.applybundle(repo, gen, tr, source='unbundle',
6153 6154 url=url)
6154 6155 except error.BundleUnknownFeatureError as exc:
6155 6156 raise error.Abort(
6156 6157 _('%s: unknown bundle feature, %s') % (fname, exc),
6157 6158 hint=_("see https://mercurial-scm.org/"
6158 6159 "wiki/BundleFeature for more "
6159 6160 "information"))
6160 6161 modheads = bundle2.combinechangegroupresults(op)
6161 6162
6162 6163 return postincoming(ui, repo, modheads, opts.get(r'update'), None, None)
6163 6164
6164 6165 @command('unshelve',
6165 6166 [('a', 'abort', None,
6166 6167 _('abort an incomplete unshelve operation')),
6167 6168 ('c', 'continue', None,
6168 6169 _('continue an incomplete unshelve operation')),
6169 6170 ('i', 'interactive', None,
6170 6171 _('use interactive mode (EXPERIMENTAL)')),
6171 6172 ('k', 'keep', None,
6172 6173 _('keep shelve after unshelving')),
6173 6174 ('n', 'name', '',
6174 6175 _('restore shelved change with given name'), _('NAME')),
6175 6176 ('t', 'tool', '', _('specify merge tool')),
6176 6177 ('', 'date', '',
6177 6178 _('set date for temporary commits (DEPRECATED)'), _('DATE'))],
6178 6179 _('hg unshelve [OPTION]... [FILE]... [-n SHELVED]'),
6179 6180 helpcategory=command.CATEGORY_WORKING_DIRECTORY)
6180 6181 def unshelve(ui, repo, *shelved, **opts):
6181 6182 """restore a shelved change to the working directory
6182 6183
6183 6184 This command accepts an optional name of a shelved change to
6184 6185 restore. If none is given, the most recent shelved change is used.
6185 6186
6186 6187 If a shelved change is applied successfully, the bundle that
6187 6188 contains the shelved changes is moved to a backup location
6188 6189 (.hg/shelve-backup).
6189 6190
6190 6191 Since you can restore a shelved change on top of an arbitrary
6191 6192 commit, it is possible that unshelving will result in a conflict
6192 6193 between your changes and the commits you are unshelving onto. If
6193 6194 this occurs, you must resolve the conflict, then use
6194 6195 ``--continue`` to complete the unshelve operation. (The bundle
6195 6196 will not be moved until you successfully complete the unshelve.)
6196 6197
6197 6198 (Alternatively, you can use ``--abort`` to abandon an unshelve
6198 6199 that causes a conflict. This reverts the unshelved changes, and
6199 6200 leaves the bundle in place.)
6200 6201
6201 6202 If bare shelved change (when no files are specified, without interactive,
6202 6203 include and exclude option) was done on newly created branch it would
6203 6204 restore branch information to the working directory.
6204 6205
6205 6206 After a successful unshelve, the shelved changes are stored in a
6206 6207 backup directory. Only the N most recent backups are kept. N
6207 6208 defaults to 10 but can be overridden using the ``shelve.maxbackups``
6208 6209 configuration option.
6209 6210
6210 6211 .. container:: verbose
6211 6212
6212 6213 Timestamp in seconds is used to decide order of backups. More
6213 6214 than ``maxbackups`` backups are kept, if same timestamp
6214 6215 prevents from deciding exact order of them, for safety.
6215 6216
6216 6217 Selected changes can be unshelved with ``--interactive`` flag.
6217 6218 The working directory is updated with the selected changes, and
6218 6219 only the unselected changes remain shelved.
6219 6220 Note: The whole shelve is applied to working directory first before
6220 6221 running interactively. So, this will bring up all the conflicts between
6221 6222 working directory and the shelve, irrespective of which changes will be
6222 6223 unshelved.
6223 6224 """
6224 6225 with repo.wlock():
6225 6226 return shelvemod.dounshelve(ui, repo, *shelved, **opts)
6226 6227
6227 6228 statemod.addunfinished(
6228 6229 'unshelve', fname='shelvedstate', continueflag=True,
6229 6230 abortfunc=shelvemod.hgabortunshelve,
6230 6231 continuefunc=shelvemod.hgcontinueunshelve,
6231 6232 cmdmsg=_('unshelve already in progress'),
6232 6233 )
6233 6234
6234 6235 @command('update|up|checkout|co',
6235 6236 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
6236 6237 ('c', 'check', None, _('require clean working directory')),
6237 6238 ('m', 'merge', None, _('merge uncommitted changes')),
6238 6239 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
6239 6240 ('r', 'rev', '', _('revision'), _('REV'))
6240 6241 ] + mergetoolopts,
6241 6242 _('[-C|-c|-m] [-d DATE] [[-r] REV]'),
6242 6243 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6243 6244 helpbasic=True)
6244 6245 def update(ui, repo, node=None, **opts):
6245 6246 """update working directory (or switch revisions)
6246 6247
6247 6248 Update the repository's working directory to the specified
6248 6249 changeset. If no changeset is specified, update to the tip of the
6249 6250 current named branch and move the active bookmark (see :hg:`help
6250 6251 bookmarks`).
6251 6252
6252 6253 Update sets the working directory's parent revision to the specified
6253 6254 changeset (see :hg:`help parents`).
6254 6255
6255 6256 If the changeset is not a descendant or ancestor of the working
6256 6257 directory's parent and there are uncommitted changes, the update is
6257 6258 aborted. With the -c/--check option, the working directory is checked
6258 6259 for uncommitted changes; if none are found, the working directory is
6259 6260 updated to the specified changeset.
6260 6261
6261 6262 .. container:: verbose
6262 6263
6263 6264 The -C/--clean, -c/--check, and -m/--merge options control what
6264 6265 happens if the working directory contains uncommitted changes.
6265 6266 At most of one of them can be specified.
6266 6267
6267 6268 1. If no option is specified, and if
6268 6269 the requested changeset is an ancestor or descendant of
6269 6270 the working directory's parent, the uncommitted changes
6270 6271 are merged into the requested changeset and the merged
6271 6272 result is left uncommitted. If the requested changeset is
6272 6273 not an ancestor or descendant (that is, it is on another
6273 6274 branch), the update is aborted and the uncommitted changes
6274 6275 are preserved.
6275 6276
6276 6277 2. With the -m/--merge option, the update is allowed even if the
6277 6278 requested changeset is not an ancestor or descendant of
6278 6279 the working directory's parent.
6279 6280
6280 6281 3. With the -c/--check option, the update is aborted and the
6281 6282 uncommitted changes are preserved.
6282 6283
6283 6284 4. With the -C/--clean option, uncommitted changes are discarded and
6284 6285 the working directory is updated to the requested changeset.
6285 6286
6286 6287 To cancel an uncommitted merge (and lose your changes), use
6287 6288 :hg:`merge --abort`.
6288 6289
6289 6290 Use null as the changeset to remove the working directory (like
6290 6291 :hg:`clone -U`).
6291 6292
6292 6293 If you want to revert just one file to an older revision, use
6293 6294 :hg:`revert [-r REV] NAME`.
6294 6295
6295 6296 See :hg:`help dates` for a list of formats valid for -d/--date.
6296 6297
6297 6298 Returns 0 on success, 1 if there are unresolved files.
6298 6299 """
6299 6300 rev = opts.get(r'rev')
6300 6301 date = opts.get(r'date')
6301 6302 clean = opts.get(r'clean')
6302 6303 check = opts.get(r'check')
6303 6304 merge = opts.get(r'merge')
6304 6305 if rev and node:
6305 6306 raise error.Abort(_("please specify just one revision"))
6306 6307
6307 6308 if ui.configbool('commands', 'update.requiredest'):
6308 6309 if not node and not rev and not date:
6309 6310 raise error.Abort(_('you must specify a destination'),
6310 6311 hint=_('for example: hg update ".::"'))
6311 6312
6312 6313 if rev is None or rev == '':
6313 6314 rev = node
6314 6315
6315 6316 if date and rev is not None:
6316 6317 raise error.Abort(_("you can't specify a revision and a date"))
6317 6318
6318 6319 if len([x for x in (clean, check, merge) if x]) > 1:
6319 6320 raise error.Abort(_("can only specify one of -C/--clean, -c/--check, "
6320 6321 "or -m/--merge"))
6321 6322
6322 6323 updatecheck = None
6323 6324 if check:
6324 6325 updatecheck = 'abort'
6325 6326 elif merge:
6326 6327 updatecheck = 'none'
6327 6328
6328 6329 with repo.wlock():
6329 6330 cmdutil.clearunfinished(repo)
6330 6331 if date:
6331 6332 rev = cmdutil.finddate(ui, repo, date)
6332 6333
6333 6334 # if we defined a bookmark, we have to remember the original name
6334 6335 brev = rev
6335 6336 if rev:
6336 6337 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
6337 6338 ctx = scmutil.revsingle(repo, rev, default=None)
6338 6339 rev = ctx.rev()
6339 6340 hidden = ctx.hidden()
6340 6341 overrides = {('ui', 'forcemerge'): opts.get(r'tool', '')}
6341 6342 with ui.configoverride(overrides, 'update'):
6342 6343 ret = hg.updatetotally(ui, repo, rev, brev, clean=clean,
6343 6344 updatecheck=updatecheck)
6344 6345 if hidden:
6345 6346 ctxstr = ctx.hex()[:12]
6346 6347 ui.warn(_("updated to hidden changeset %s\n") % ctxstr)
6347 6348
6348 6349 if ctx.obsolete():
6349 6350 obsfatemsg = obsutil._getfilteredreason(repo, ctxstr, ctx)
6350 6351 ui.warn("(%s)\n" % obsfatemsg)
6351 6352 return ret
6352 6353
6353 6354 @command('verify',
6354 6355 [('', 'full', False, 'perform more checks (EXPERIMENTAL)')],
6355 6356 helpcategory=command.CATEGORY_MAINTENANCE)
6356 6357 def verify(ui, repo, **opts):
6357 6358 """verify the integrity of the repository
6358 6359
6359 6360 Verify the integrity of the current repository.
6360 6361
6361 6362 This will perform an extensive check of the repository's
6362 6363 integrity, validating the hashes and checksums of each entry in
6363 6364 the changelog, manifest, and tracked files, as well as the
6364 6365 integrity of their crosslinks and indices.
6365 6366
6366 6367 Please see https://mercurial-scm.org/wiki/RepositoryCorruption
6367 6368 for more information about recovery from corruption of the
6368 6369 repository.
6369 6370
6370 6371 Returns 0 on success, 1 if errors are encountered.
6371 6372 """
6372 6373 opts = pycompat.byteskwargs(opts)
6373 6374
6374 6375 level = None
6375 6376 if opts['full']:
6376 6377 level = verifymod.VERIFY_FULL
6377 6378 return hg.verify(repo, level)
6378 6379
6379 6380 @command(
6380 6381 'version', [] + formatteropts, helpcategory=command.CATEGORY_HELP,
6381 6382 norepo=True, intents={INTENT_READONLY})
6382 6383 def version_(ui, **opts):
6383 6384 """output version and copyright information
6384 6385
6385 6386 .. container:: verbose
6386 6387
6387 6388 Template:
6388 6389
6389 6390 The following keywords are supported. See also :hg:`help templates`.
6390 6391
6391 6392 :extensions: List of extensions.
6392 6393 :ver: String. Version number.
6393 6394
6394 6395 And each entry of ``{extensions}`` provides the following sub-keywords
6395 6396 in addition to ``{ver}``.
6396 6397
6397 6398 :bundled: Boolean. True if included in the release.
6398 6399 :name: String. Extension name.
6399 6400 """
6400 6401 opts = pycompat.byteskwargs(opts)
6401 6402 if ui.verbose:
6402 6403 ui.pager('version')
6403 6404 fm = ui.formatter("version", opts)
6404 6405 fm.startitem()
6405 6406 fm.write("ver", _("Mercurial Distributed SCM (version %s)\n"),
6406 6407 util.version())
6407 6408 license = _(
6408 6409 "(see https://mercurial-scm.org for more information)\n"
6409 6410 "\nCopyright (C) 2005-2019 Matt Mackall and others\n"
6410 6411 "This is free software; see the source for copying conditions. "
6411 6412 "There is NO\nwarranty; "
6412 6413 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
6413 6414 )
6414 6415 if not ui.quiet:
6415 6416 fm.plain(license)
6416 6417
6417 6418 if ui.verbose:
6418 6419 fm.plain(_("\nEnabled extensions:\n\n"))
6419 6420 # format names and versions into columns
6420 6421 names = []
6421 6422 vers = []
6422 6423 isinternals = []
6423 6424 for name, module in extensions.extensions():
6424 6425 names.append(name)
6425 6426 vers.append(extensions.moduleversion(module) or None)
6426 6427 isinternals.append(extensions.ismoduleinternal(module))
6427 6428 fn = fm.nested("extensions", tmpl='{name}\n')
6428 6429 if names:
6429 6430 namefmt = " %%-%ds " % max(len(n) for n in names)
6430 6431 places = [_("external"), _("internal")]
6431 6432 for n, v, p in zip(names, vers, isinternals):
6432 6433 fn.startitem()
6433 6434 fn.condwrite(ui.verbose, "name", namefmt, n)
6434 6435 if ui.verbose:
6435 6436 fn.plain("%s " % places[p])
6436 6437 fn.data(bundled=p)
6437 6438 fn.condwrite(ui.verbose and v, "ver", "%s", v)
6438 6439 if ui.verbose:
6439 6440 fn.plain("\n")
6440 6441 fn.end()
6441 6442 fm.end()
6442 6443
6443 6444 def loadcmdtable(ui, name, cmdtable):
6444 6445 """Load command functions from specified cmdtable
6445 6446 """
6446 6447 overrides = [cmd for cmd in cmdtable if cmd in table]
6447 6448 if overrides:
6448 6449 ui.warn(_("extension '%s' overrides commands: %s\n")
6449 6450 % (name, " ".join(overrides)))
6450 6451 table.update(cmdtable)
@@ -1,584 +1,586 b''
1 1 Test uncommit - set up the config
2 2
3 3 $ cat >> $HGRCPATH <<EOF
4 4 > [experimental]
5 5 > evolution.createmarkers=True
6 6 > evolution.allowunstable=True
7 7 > [extensions]
8 8 > uncommit =
9 9 > drawdag=$TESTDIR/drawdag.py
10 10 > EOF
11 11
12 12 Build up a repo
13 13
14 14 $ hg init repo
15 15 $ cd repo
16 16 $ hg bookmark foo
17 17
18 18 Help for uncommit
19 19
20 20 $ hg help uncommit
21 21 hg uncommit [OPTION]... [FILE]...
22 22
23 23 uncommit part or all of a local changeset
24 24
25 25 This command undoes the effect of a local commit, returning the affected
26 26 files to their uncommitted state. This means that files modified or
27 27 deleted in the changeset will be left unchanged, and so will remain
28 28 modified in the working directory.
29 29
30 30 If no files are specified, the commit will be pruned, unless --keep is
31 31 given.
32 32
33 33 (use 'hg help -e uncommit' to show help for the uncommit extension)
34 34
35 35 options ([+] can be repeated):
36 36
37 37 --keep allow an empty commit after uncommiting
38 38 --allow-dirty-working-copy allow uncommit with outstanding changes
39 39 -I --include PATTERN [+] include names matching the given patterns
40 40 -X --exclude PATTERN [+] exclude names matching the given patterns
41 41 -m --message TEXT use text as commit message
42 42 -l --logfile FILE read commit message from file
43 43 -d --date DATE record the specified date as commit date
44 44 -u --user USER record the specified user as committer
45 -D --current-date record the current date as commit date
46 -U --current-user record the current user as committer
45 47
46 48 (some details hidden, use --verbose to show complete help)
47 49
48 50 Uncommit with no commits should fail
49 51
50 52 $ hg uncommit
51 53 abort: cannot uncommit null changeset
52 54 (no changeset checked out)
53 55 [255]
54 56
55 57 Create some commits
56 58
57 59 $ touch files
58 60 $ hg add files
59 61 $ for i in a ab abc abcd abcde; do echo $i > files; echo $i > file-$i; hg add file-$i; hg commit -m "added file-$i"; done
60 62 $ ls
61 63 file-a
62 64 file-ab
63 65 file-abc
64 66 file-abcd
65 67 file-abcde
66 68 files
67 69
68 70 $ hg log -G -T '{rev}:{node} {desc}' --hidden
69 71 @ 4:6c4fd43ed714e7fcd8adbaa7b16c953c2e985b60 added file-abcde
70 72 |
71 73 o 3:6db330d65db434145c0b59d291853e9a84719b24 added file-abcd
72 74 |
73 75 o 2:abf2df566fc193b3ac34d946e63c1583e4d4732b added file-abc
74 76 |
75 77 o 1:69a232e754b08d568c4899475faf2eb44b857802 added file-ab
76 78 |
77 79 o 0:3004d2d9b50883c1538fc754a3aeb55f1b4084f6 added file-a
78 80
79 81 Simple uncommit off the top, also moves bookmark
80 82
81 83 $ hg bookmark
82 84 * foo 4:6c4fd43ed714
83 85 $ hg uncommit
84 86 $ hg status
85 87 M files
86 88 A file-abcde
87 89 $ hg bookmark
88 90 * foo 3:6db330d65db4
89 91
90 92 $ hg log -G -T '{rev}:{node} {desc}' --hidden
91 93 x 4:6c4fd43ed714e7fcd8adbaa7b16c953c2e985b60 added file-abcde
92 94 |
93 95 @ 3:6db330d65db434145c0b59d291853e9a84719b24 added file-abcd
94 96 |
95 97 o 2:abf2df566fc193b3ac34d946e63c1583e4d4732b added file-abc
96 98 |
97 99 o 1:69a232e754b08d568c4899475faf2eb44b857802 added file-ab
98 100 |
99 101 o 0:3004d2d9b50883c1538fc754a3aeb55f1b4084f6 added file-a
100 102
101 103
102 104 Recommit
103 105
104 106 $ hg commit -m 'new change abcde'
105 107 $ hg status
106 108 $ hg heads -T '{rev}:{node} {desc}'
107 109 5:0c07a3ccda771b25f1cb1edbd02e683723344ef1 new change abcde (no-eol)
108 110
109 111 Uncommit of non-existent and unchanged files aborts
110 112 $ hg uncommit nothinghere
111 113 abort: cannot uncommit "nothinghere"
112 114 (file does not exist)
113 115 [255]
114 116 $ hg status
115 117 $ hg uncommit file-abc
116 118 abort: cannot uncommit "file-abc"
117 119 (file was not changed in working directory parent)
118 120 [255]
119 121 $ hg status
120 122
121 123 Try partial uncommit, also moves bookmark
122 124
123 125 $ hg bookmark
124 126 * foo 5:0c07a3ccda77
125 127 $ hg uncommit files
126 128 $ hg status
127 129 M files
128 130 $ hg bookmark
129 131 * foo 6:3727deee06f7
130 132 $ hg heads -T '{rev}:{node} {desc}'
131 133 6:3727deee06f72f5ffa8db792ee299cf39e3e190b new change abcde (no-eol)
132 134 $ hg log -r . -p -T '{rev}:{node} {desc}'
133 135 6:3727deee06f72f5ffa8db792ee299cf39e3e190b new change abcdediff -r 6db330d65db4 -r 3727deee06f7 file-abcde
134 136 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
135 137 +++ b/file-abcde Thu Jan 01 00:00:00 1970 +0000
136 138 @@ -0,0 +1,1 @@
137 139 +abcde
138 140
139 141 $ hg log -G -T '{rev}:{node} {desc}' --hidden
140 142 @ 6:3727deee06f72f5ffa8db792ee299cf39e3e190b new change abcde
141 143 |
142 144 | x 5:0c07a3ccda771b25f1cb1edbd02e683723344ef1 new change abcde
143 145 |/
144 146 | x 4:6c4fd43ed714e7fcd8adbaa7b16c953c2e985b60 added file-abcde
145 147 |/
146 148 o 3:6db330d65db434145c0b59d291853e9a84719b24 added file-abcd
147 149 |
148 150 o 2:abf2df566fc193b3ac34d946e63c1583e4d4732b added file-abc
149 151 |
150 152 o 1:69a232e754b08d568c4899475faf2eb44b857802 added file-ab
151 153 |
152 154 o 0:3004d2d9b50883c1538fc754a3aeb55f1b4084f6 added file-a
153 155
154 156 $ hg commit -m 'update files for abcde'
155 157
156 158 Uncommit with dirty state
157 159
158 160 $ echo "foo" >> files
159 161 $ cat files
160 162 abcde
161 163 foo
162 164 $ hg status
163 165 M files
164 166 $ hg uncommit
165 167 abort: uncommitted changes
166 168 (requires --allow-dirty-working-copy to uncommit)
167 169 [255]
168 170 $ hg uncommit files
169 171 abort: uncommitted changes
170 172 (requires --allow-dirty-working-copy to uncommit)
171 173 [255]
172 174 $ cat files
173 175 abcde
174 176 foo
175 177 $ hg commit --amend -m "files abcde + foo"
176 178
177 179 Testing the 'experimental.uncommitondirtywdir' config
178 180
179 181 $ echo "bar" >> files
180 182 $ hg uncommit
181 183 abort: uncommitted changes
182 184 (requires --allow-dirty-working-copy to uncommit)
183 185 [255]
184 186 $ hg uncommit --config experimental.uncommitondirtywdir=True
185 187 $ hg commit -m "files abcde + foo"
186 188
187 189 Uncommit in the middle of a stack, does not move bookmark
188 190
189 191 $ hg checkout '.^^^'
190 192 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
191 193 (leaving bookmark foo)
192 194 $ hg log -r . -p -T '{rev}:{node} {desc}'
193 195 2:abf2df566fc193b3ac34d946e63c1583e4d4732b added file-abcdiff -r 69a232e754b0 -r abf2df566fc1 file-abc
194 196 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
195 197 +++ b/file-abc Thu Jan 01 00:00:00 1970 +0000
196 198 @@ -0,0 +1,1 @@
197 199 +abc
198 200 diff -r 69a232e754b0 -r abf2df566fc1 files
199 201 --- a/files Thu Jan 01 00:00:00 1970 +0000
200 202 +++ b/files Thu Jan 01 00:00:00 1970 +0000
201 203 @@ -1,1 +1,1 @@
202 204 -ab
203 205 +abc
204 206
205 207 $ hg bookmark
206 208 foo 9:48e5bd7cd583
207 209 $ hg uncommit
208 210 3 new orphan changesets
209 211 $ hg status
210 212 M files
211 213 A file-abc
212 214 $ hg heads -T '{rev}:{node} {desc}'
213 215 9:48e5bd7cd583eb24164ef8b89185819c84c96ed7 files abcde + foo (no-eol)
214 216 $ hg bookmark
215 217 foo 9:48e5bd7cd583
216 218 $ hg commit -m 'new abc'
217 219 created new head
218 220
219 221 Partial uncommit in the middle, does not move bookmark
220 222
221 223 $ hg checkout '.^'
222 224 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
223 225 $ hg log -r . -p -T '{rev}:{node} {desc}'
224 226 1:69a232e754b08d568c4899475faf2eb44b857802 added file-abdiff -r 3004d2d9b508 -r 69a232e754b0 file-ab
225 227 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
226 228 +++ b/file-ab Thu Jan 01 00:00:00 1970 +0000
227 229 @@ -0,0 +1,1 @@
228 230 +ab
229 231 diff -r 3004d2d9b508 -r 69a232e754b0 files
230 232 --- a/files Thu Jan 01 00:00:00 1970 +0000
231 233 +++ b/files Thu Jan 01 00:00:00 1970 +0000
232 234 @@ -1,1 +1,1 @@
233 235 -a
234 236 +ab
235 237
236 238 $ hg bookmark
237 239 foo 9:48e5bd7cd583
238 240 $ hg uncommit file-ab
239 241 1 new orphan changesets
240 242 $ hg status
241 243 A file-ab
242 244
243 245 $ hg heads -T '{rev}:{node} {desc}\n'
244 246 11:8eb87968f2edb7f27f27fe676316e179de65fff6 added file-ab
245 247 10:5dc89ca4486f8a88716c5797fa9f498d13d7c2e1 new abc
246 248 9:48e5bd7cd583eb24164ef8b89185819c84c96ed7 files abcde + foo
247 249
248 250 $ hg bookmark
249 251 foo 9:48e5bd7cd583
250 252 $ hg commit -m 'update ab'
251 253 $ hg status
252 254 $ hg heads -T '{rev}:{node} {desc}\n'
253 255 12:f21039c59242b085491bb58f591afc4ed1c04c09 update ab
254 256 10:5dc89ca4486f8a88716c5797fa9f498d13d7c2e1 new abc
255 257 9:48e5bd7cd583eb24164ef8b89185819c84c96ed7 files abcde + foo
256 258
257 259 $ hg log -G -T '{rev}:{node} {desc}' --hidden
258 260 @ 12:f21039c59242b085491bb58f591afc4ed1c04c09 update ab
259 261 |
260 262 o 11:8eb87968f2edb7f27f27fe676316e179de65fff6 added file-ab
261 263 |
262 264 | * 10:5dc89ca4486f8a88716c5797fa9f498d13d7c2e1 new abc
263 265 | |
264 266 | | * 9:48e5bd7cd583eb24164ef8b89185819c84c96ed7 files abcde + foo
265 267 | | |
266 268 | | | x 8:84beeba0ac30e19521c036e4d2dd3a5fa02586ff files abcde + foo
267 269 | | |/
268 270 | | | x 7:0977fa602c2fd7d8427ed4e7ee15ea13b84c9173 update files for abcde
269 271 | | |/
270 272 | | * 6:3727deee06f72f5ffa8db792ee299cf39e3e190b new change abcde
271 273 | | |
272 274 | | | x 5:0c07a3ccda771b25f1cb1edbd02e683723344ef1 new change abcde
273 275 | | |/
274 276 | | | x 4:6c4fd43ed714e7fcd8adbaa7b16c953c2e985b60 added file-abcde
275 277 | | |/
276 278 | | * 3:6db330d65db434145c0b59d291853e9a84719b24 added file-abcd
277 279 | | |
278 280 | | x 2:abf2df566fc193b3ac34d946e63c1583e4d4732b added file-abc
279 281 | |/
280 282 | x 1:69a232e754b08d568c4899475faf2eb44b857802 added file-ab
281 283 |/
282 284 o 0:3004d2d9b50883c1538fc754a3aeb55f1b4084f6 added file-a
283 285
284 286 Uncommit with draft parent
285 287
286 288 $ hg uncommit
287 289 $ hg phase -r .
288 290 11: draft
289 291 $ hg commit -m 'update ab again'
290 292
291 293 Phase is preserved
292 294
293 295 $ hg uncommit --keep --config phases.new-commit=secret
294 296 note: keeping empty commit
295 297 $ hg phase -r .
296 298 14: draft
297 299 $ hg commit --amend -m 'update ab again'
298 300
299 301 Uncommit with public parent
300 302
301 303 $ hg phase -p "::.^"
302 304 $ hg uncommit
303 305 $ hg phase -r .
304 306 11: public
305 307
306 308 Partial uncommit with public parent
307 309
308 310 $ echo xyz > xyz
309 311 $ hg add xyz
310 312 $ hg commit -m "update ab and add xyz"
311 313 $ hg uncommit xyz
312 314 $ hg status
313 315 A xyz
314 316 $ hg phase -r .
315 317 17: draft
316 318 $ hg phase -r ".^"
317 319 11: public
318 320
319 321 Uncommit with --keep or experimental.uncommit.keep leaves an empty changeset
320 322
321 323 $ cd $TESTTMP
322 324 $ hg init repo1
323 325 $ cd repo1
324 326 $ hg debugdrawdag <<'EOS'
325 327 > Q
326 328 > |
327 329 > P
328 330 > EOS
329 331 $ hg up Q -q
330 332 $ hg uncommit --keep
331 333 note: keeping empty commit
332 334 $ hg log -G -T '{desc} FILES: {files}'
333 335 @ Q FILES:
334 336 |
335 337 | x Q FILES: Q
336 338 |/
337 339 o P FILES: P
338 340
339 341 $ cat >> .hg/hgrc <<EOF
340 342 > [experimental]
341 343 > uncommit.keep=True
342 344 > EOF
343 345 $ hg ci --amend
344 346 $ hg uncommit
345 347 note: keeping empty commit
346 348 $ hg log -G -T '{desc} FILES: {files}'
347 349 @ Q FILES:
348 350 |
349 351 | x Q FILES: Q
350 352 |/
351 353 o P FILES: P
352 354
353 355 $ hg status
354 356 A Q
355 357 $ hg ci --amend
356 358 $ hg uncommit --no-keep
357 359 $ hg log -G -T '{desc} FILES: {files}'
358 360 x Q FILES: Q
359 361 |
360 362 @ P FILES: P
361 363
362 364 $ hg status
363 365 A Q
364 366 $ cd ..
365 367 $ rm -rf repo1
366 368
367 369 Testing uncommit while merge
368 370
369 371 $ hg init repo2
370 372 $ cd repo2
371 373
372 374 Create some history
373 375
374 376 $ touch a
375 377 $ hg add a
376 378 $ for i in 1 2 3; do echo $i > a; hg commit -m "a $i"; done
377 379 $ hg checkout 0
378 380 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
379 381 $ touch b
380 382 $ hg add b
381 383 $ for i in 1 2 3; do echo $i > b; hg commit -m "b $i"; done
382 384 created new head
383 385 $ hg log -G -T '{rev}:{node} {desc}' --hidden
384 386 @ 5:2cd56cdde163ded2fbb16ba2f918c96046ab0bf2 b 3
385 387 |
386 388 o 4:c3a0d5bb3b15834ffd2ef9ef603e93ec65cf2037 b 2
387 389 |
388 390 o 3:49bb009ca26078726b8870f1edb29fae8f7618f5 b 1
389 391 |
390 392 | o 2:990982b7384266e691f1bc08ca36177adcd1c8a9 a 3
391 393 | |
392 394 | o 1:24d38e3cf160c7b6f5ffe82179332229886a6d34 a 2
393 395 |/
394 396 o 0:ea4e33293d4d274a2ba73150733c2612231f398c a 1
395 397
396 398
397 399 Add and expect uncommit to fail on both merge working dir and merge changeset
398 400
399 401 $ hg merge 2
400 402 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
401 403 (branch merge, don't forget to commit)
402 404
403 405 $ hg uncommit
404 406 abort: outstanding uncommitted merge
405 407 (requires --allow-dirty-working-copy to uncommit)
406 408 [255]
407 409
408 410 $ hg uncommit --config experimental.uncommitondirtywdir=True
409 411 abort: cannot uncommit while merging
410 412 [255]
411 413
412 414 $ hg status
413 415 M a
414 416 $ hg commit -m 'merge a and b'
415 417
416 418 $ hg uncommit
417 419 abort: cannot uncommit merge changeset
418 420 [255]
419 421
420 422 $ hg status
421 423 $ hg log -G -T '{rev}:{node} {desc}' --hidden
422 424 @ 6:c03b9c37bc67bf504d4912061cfb527b47a63c6e merge a and b
423 425 |\
424 426 | o 5:2cd56cdde163ded2fbb16ba2f918c96046ab0bf2 b 3
425 427 | |
426 428 | o 4:c3a0d5bb3b15834ffd2ef9ef603e93ec65cf2037 b 2
427 429 | |
428 430 | o 3:49bb009ca26078726b8870f1edb29fae8f7618f5 b 1
429 431 | |
430 432 o | 2:990982b7384266e691f1bc08ca36177adcd1c8a9 a 3
431 433 | |
432 434 o | 1:24d38e3cf160c7b6f5ffe82179332229886a6d34 a 2
433 435 |/
434 436 o 0:ea4e33293d4d274a2ba73150733c2612231f398c a 1
435 437
436 438
437 439 Rename a->b, then remove b in working copy. Result should remove a.
438 440
439 441 $ hg co -q 0
440 442 $ hg mv a b
441 443 $ hg ci -qm 'move a to b'
442 444 $ hg rm b
443 445 $ hg uncommit --config experimental.uncommitondirtywdir=True
444 446 $ hg st --copies
445 447 R a
446 448 $ hg revert a
447 449
448 450 Rename a->b, then rename b->c in working copy. Result should rename a->c.
449 451
450 452 $ hg co -q 0
451 453 $ hg mv a b
452 454 $ hg ci -qm 'move a to b'
453 455 $ hg mv b c
454 456 $ hg uncommit --config experimental.uncommitondirtywdir=True
455 457 $ hg st --copies
456 458 A c
457 459 a
458 460 R a
459 461 $ hg revert a
460 462 $ hg forget c
461 463 $ rm c
462 464
463 465 Copy a->b1 and a->b2, then rename b1->c in working copy. Result should copy a->b2 and a->c.
464 466
465 467 $ hg co -q 0
466 468 $ hg cp a b1
467 469 $ hg cp a b2
468 470 $ hg ci -qm 'move a to b1 and b2'
469 471 $ hg mv b1 c
470 472 $ hg uncommit --config experimental.uncommitondirtywdir=True
471 473 $ hg st --copies
472 474 A b2
473 475 a
474 476 A c
475 477 a
476 478 $ cd ..
477 479
478 480 --allow-dirty-working-copy should also work on a dirty PATH
479 481
480 482 $ hg init issue5977
481 483 $ cd issue5977
482 484 $ echo 'super critical info!' > a
483 485 $ hg ci -Am 'add a'
484 486 adding a
485 487 $ echo 'foo' > b
486 488 $ hg add b
487 489 $ hg status
488 490 A b
489 491 $ hg unc a
490 492 note: keeping empty commit
491 493 $ cat a
492 494 super critical info!
493 495 $ hg log
494 496 changeset: 1:656ba143d384
495 497 tag: tip
496 498 parent: -1:000000000000
497 499 user: test
498 500 date: Thu Jan 01 00:00:00 1970 +0000
499 501 summary: add a
500 502
501 503 $ hg ci -Am 'add b'
502 504 $ echo 'foo bar' > b
503 505 $ hg unc b
504 506 abort: uncommitted changes
505 507 (requires --allow-dirty-working-copy to uncommit)
506 508 [255]
507 509 $ hg unc --allow-dirty-working-copy b
508 510 $ hg log
509 511 changeset: 3:30fa958635b2
510 512 tag: tip
511 513 parent: 1:656ba143d384
512 514 user: test
513 515 date: Thu Jan 01 00:00:00 1970 +0000
514 516 summary: add b
515 517
516 518 changeset: 1:656ba143d384
517 519 parent: -1:000000000000
518 520 user: test
519 521 date: Thu Jan 01 00:00:00 1970 +0000
520 522 summary: add a
521 523
522 524 Removes can be uncommitted
523 525
524 526 $ hg ci -m 'modified b'
525 527 $ hg rm b
526 528 $ hg ci -m 'remove b'
527 529 $ hg uncommit b
528 530 note: keeping empty commit
529 531 $ hg status
530 532 R b
531 533
532 534 Uncommitting a directory won't run afoul of the checks that an explicit file
533 535 can be uncommitted.
534 536
535 537 $ mkdir dir
536 538 $ echo 1 > dir/file.txt
537 539 $ hg ci -Aqm 'add file in directory'
538 540 $ hg uncommit dir -m 'uncommit with message' -u 'different user' \
539 541 > -d 'Jun 30 12:12:12 1980 +0000'
540 542 $ hg status
541 543 A dir/file.txt
542 544 $ hg log -r .
543 545 changeset: 8:b4dd26dc42e0
544 546 tag: tip
545 547 parent: 6:2278a4c24330
546 548 user: different user
547 549 date: Mon Jun 30 12:12:12 1980 +0000
548 550 summary: uncommit with message
549 551
550 552
551 553 `uncommit <dir>` and `cd <dir> && uncommit .` behave the same...
552 554
553 555 $ hg rollback -q --config ui.rollback=True
554 556 $ echo 2 > dir/file2.txt
555 557 $ hg ci -Aqm 'add file2 in directory'
556 558 $ hg uncommit dir
557 559 note: keeping empty commit
558 560 $ hg status
559 561 A dir/file2.txt
560 562
561 563 $ hg rollback -q --config ui.rollback=True
562 564 $ cd dir
563 565 $ hg uncommit .
564 566 note: keeping empty commit
565 567 $ hg status
566 568 A dir/file2.txt
567 569 $ cd ..
568 570
569 571 ... and errors out the same way when nothing can be uncommitted
570 572
571 573 $ hg rollback -q --config ui.rollback=True
572 574 $ mkdir emptydir
573 575 $ hg uncommit emptydir
574 576 abort: cannot uncommit "emptydir"
575 577 (file was untracked in working directory parent)
576 578 [255]
577 579
578 580 $ cd emptydir
579 581 $ hg uncommit .
580 582 abort: cannot uncommit "emptydir"
581 583 (file was untracked in working directory parent)
582 584 [255]
583 585 $ hg status
584 586 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now