##// END OF EJS Templates
add: pass around uipathfn and use instead of m.rel() (API)...
Martin von Zweigbergk -
r41799:f8b18583 default
parent child Browse files
Show More

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

@@ -1,1516 +1,1516 b''
1 # Copyright 2009-2010 Gregory P. Ward
1 # Copyright 2009-2010 Gregory P. Ward
2 # Copyright 2009-2010 Intelerad Medical Systems Incorporated
2 # Copyright 2009-2010 Intelerad Medical Systems Incorporated
3 # Copyright 2010-2011 Fog Creek Software
3 # Copyright 2010-2011 Fog Creek Software
4 # Copyright 2010-2011 Unity Technologies
4 # Copyright 2010-2011 Unity Technologies
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 '''Overridden Mercurial commands and functions for the largefiles extension'''
9 '''Overridden Mercurial commands and functions for the largefiles extension'''
10 from __future__ import absolute_import
10 from __future__ import absolute_import
11
11
12 import copy
12 import copy
13 import os
13 import os
14
14
15 from mercurial.i18n import _
15 from mercurial.i18n import _
16
16
17 from mercurial.hgweb import (
17 from mercurial.hgweb import (
18 webcommands,
18 webcommands,
19 )
19 )
20
20
21 from mercurial import (
21 from mercurial import (
22 archival,
22 archival,
23 cmdutil,
23 cmdutil,
24 copies as copiesmod,
24 copies as copiesmod,
25 error,
25 error,
26 exchange,
26 exchange,
27 extensions,
27 extensions,
28 exthelper,
28 exthelper,
29 filemerge,
29 filemerge,
30 hg,
30 hg,
31 logcmdutil,
31 logcmdutil,
32 match as matchmod,
32 match as matchmod,
33 merge,
33 merge,
34 pathutil,
34 pathutil,
35 pycompat,
35 pycompat,
36 scmutil,
36 scmutil,
37 smartset,
37 smartset,
38 subrepo,
38 subrepo,
39 upgrade,
39 upgrade,
40 url as urlmod,
40 url as urlmod,
41 util,
41 util,
42 )
42 )
43
43
44 from . import (
44 from . import (
45 lfcommands,
45 lfcommands,
46 lfutil,
46 lfutil,
47 storefactory,
47 storefactory,
48 )
48 )
49
49
50 eh = exthelper.exthelper()
50 eh = exthelper.exthelper()
51
51
52 # -- Utility functions: commonly/repeatedly needed functionality ---------------
52 # -- Utility functions: commonly/repeatedly needed functionality ---------------
53
53
54 def composelargefilematcher(match, manifest):
54 def composelargefilematcher(match, manifest):
55 '''create a matcher that matches only the largefiles in the original
55 '''create a matcher that matches only the largefiles in the original
56 matcher'''
56 matcher'''
57 m = copy.copy(match)
57 m = copy.copy(match)
58 lfile = lambda f: lfutil.standin(f) in manifest
58 lfile = lambda f: lfutil.standin(f) in manifest
59 m._files = [lf for lf in m._files if lfile(lf)]
59 m._files = [lf for lf in m._files if lfile(lf)]
60 m._fileset = set(m._files)
60 m._fileset = set(m._files)
61 m.always = lambda: False
61 m.always = lambda: False
62 origmatchfn = m.matchfn
62 origmatchfn = m.matchfn
63 m.matchfn = lambda f: lfile(f) and origmatchfn(f)
63 m.matchfn = lambda f: lfile(f) and origmatchfn(f)
64 return m
64 return m
65
65
66 def composenormalfilematcher(match, manifest, exclude=None):
66 def composenormalfilematcher(match, manifest, exclude=None):
67 excluded = set()
67 excluded = set()
68 if exclude is not None:
68 if exclude is not None:
69 excluded.update(exclude)
69 excluded.update(exclude)
70
70
71 m = copy.copy(match)
71 m = copy.copy(match)
72 notlfile = lambda f: not (lfutil.isstandin(f) or lfutil.standin(f) in
72 notlfile = lambda f: not (lfutil.isstandin(f) or lfutil.standin(f) in
73 manifest or f in excluded)
73 manifest or f in excluded)
74 m._files = [lf for lf in m._files if notlfile(lf)]
74 m._files = [lf for lf in m._files if notlfile(lf)]
75 m._fileset = set(m._files)
75 m._fileset = set(m._files)
76 m.always = lambda: False
76 m.always = lambda: False
77 origmatchfn = m.matchfn
77 origmatchfn = m.matchfn
78 m.matchfn = lambda f: notlfile(f) and origmatchfn(f)
78 m.matchfn = lambda f: notlfile(f) and origmatchfn(f)
79 return m
79 return m
80
80
81 def addlargefiles(ui, repo, isaddremove, matcher, **opts):
81 def addlargefiles(ui, repo, isaddremove, matcher, **opts):
82 large = opts.get(r'large')
82 large = opts.get(r'large')
83 lfsize = lfutil.getminsize(
83 lfsize = lfutil.getminsize(
84 ui, lfutil.islfilesrepo(repo), opts.get(r'lfsize'))
84 ui, lfutil.islfilesrepo(repo), opts.get(r'lfsize'))
85
85
86 lfmatcher = None
86 lfmatcher = None
87 if lfutil.islfilesrepo(repo):
87 if lfutil.islfilesrepo(repo):
88 lfpats = ui.configlist(lfutil.longname, 'patterns')
88 lfpats = ui.configlist(lfutil.longname, 'patterns')
89 if lfpats:
89 if lfpats:
90 lfmatcher = matchmod.match(repo.root, '', list(lfpats))
90 lfmatcher = matchmod.match(repo.root, '', list(lfpats))
91
91
92 lfnames = []
92 lfnames = []
93 m = matcher
93 m = matcher
94
94
95 wctx = repo[None]
95 wctx = repo[None]
96 for f in wctx.walk(matchmod.badmatch(m, lambda x, y: None)):
96 for f in wctx.walk(matchmod.badmatch(m, lambda x, y: None)):
97 exact = m.exact(f)
97 exact = m.exact(f)
98 lfile = lfutil.standin(f) in wctx
98 lfile = lfutil.standin(f) in wctx
99 nfile = f in wctx
99 nfile = f in wctx
100 exists = lfile or nfile
100 exists = lfile or nfile
101
101
102 # addremove in core gets fancy with the name, add doesn't
102 # addremove in core gets fancy with the name, add doesn't
103 if isaddremove:
103 if isaddremove:
104 name = m.uipath(f)
104 name = m.uipath(f)
105 else:
105 else:
106 name = m.rel(f)
106 name = m.rel(f)
107
107
108 # Don't warn the user when they attempt to add a normal tracked file.
108 # Don't warn the user when they attempt to add a normal tracked file.
109 # The normal add code will do that for us.
109 # The normal add code will do that for us.
110 if exact and exists:
110 if exact and exists:
111 if lfile:
111 if lfile:
112 ui.warn(_('%s already a largefile\n') % name)
112 ui.warn(_('%s already a largefile\n') % name)
113 continue
113 continue
114
114
115 if (exact or not exists) and not lfutil.isstandin(f):
115 if (exact or not exists) and not lfutil.isstandin(f):
116 # In case the file was removed previously, but not committed
116 # In case the file was removed previously, but not committed
117 # (issue3507)
117 # (issue3507)
118 if not repo.wvfs.exists(f):
118 if not repo.wvfs.exists(f):
119 continue
119 continue
120
120
121 abovemin = (lfsize and
121 abovemin = (lfsize and
122 repo.wvfs.lstat(f).st_size >= lfsize * 1024 * 1024)
122 repo.wvfs.lstat(f).st_size >= lfsize * 1024 * 1024)
123 if large or abovemin or (lfmatcher and lfmatcher(f)):
123 if large or abovemin or (lfmatcher and lfmatcher(f)):
124 lfnames.append(f)
124 lfnames.append(f)
125 if ui.verbose or not exact:
125 if ui.verbose or not exact:
126 ui.status(_('adding %s as a largefile\n') % name)
126 ui.status(_('adding %s as a largefile\n') % name)
127
127
128 bad = []
128 bad = []
129
129
130 # Need to lock, otherwise there could be a race condition between
130 # Need to lock, otherwise there could be a race condition between
131 # when standins are created and added to the repo.
131 # when standins are created and added to the repo.
132 with repo.wlock():
132 with repo.wlock():
133 if not opts.get(r'dry_run'):
133 if not opts.get(r'dry_run'):
134 standins = []
134 standins = []
135 lfdirstate = lfutil.openlfdirstate(ui, repo)
135 lfdirstate = lfutil.openlfdirstate(ui, repo)
136 for f in lfnames:
136 for f in lfnames:
137 standinname = lfutil.standin(f)
137 standinname = lfutil.standin(f)
138 lfutil.writestandin(repo, standinname, hash='',
138 lfutil.writestandin(repo, standinname, hash='',
139 executable=lfutil.getexecutable(repo.wjoin(f)))
139 executable=lfutil.getexecutable(repo.wjoin(f)))
140 standins.append(standinname)
140 standins.append(standinname)
141 if lfdirstate[f] == 'r':
141 if lfdirstate[f] == 'r':
142 lfdirstate.normallookup(f)
142 lfdirstate.normallookup(f)
143 else:
143 else:
144 lfdirstate.add(f)
144 lfdirstate.add(f)
145 lfdirstate.write()
145 lfdirstate.write()
146 bad += [lfutil.splitstandin(f)
146 bad += [lfutil.splitstandin(f)
147 for f in repo[None].add(standins)
147 for f in repo[None].add(standins)
148 if f in m.files()]
148 if f in m.files()]
149
149
150 added = [f for f in lfnames if f not in bad]
150 added = [f for f in lfnames if f not in bad]
151 return added, bad
151 return added, bad
152
152
153 def removelargefiles(ui, repo, isaddremove, matcher, dryrun, **opts):
153 def removelargefiles(ui, repo, isaddremove, matcher, dryrun, **opts):
154 after = opts.get(r'after')
154 after = opts.get(r'after')
155 m = composelargefilematcher(matcher, repo[None].manifest())
155 m = composelargefilematcher(matcher, repo[None].manifest())
156 try:
156 try:
157 repo.lfstatus = True
157 repo.lfstatus = True
158 s = repo.status(match=m, clean=not isaddremove)
158 s = repo.status(match=m, clean=not isaddremove)
159 finally:
159 finally:
160 repo.lfstatus = False
160 repo.lfstatus = False
161 manifest = repo[None].manifest()
161 manifest = repo[None].manifest()
162 modified, added, deleted, clean = [[f for f in list
162 modified, added, deleted, clean = [[f for f in list
163 if lfutil.standin(f) in manifest]
163 if lfutil.standin(f) in manifest]
164 for list in (s.modified, s.added,
164 for list in (s.modified, s.added,
165 s.deleted, s.clean)]
165 s.deleted, s.clean)]
166
166
167 def warn(files, msg):
167 def warn(files, msg):
168 for f in files:
168 for f in files:
169 ui.warn(msg % m.rel(f))
169 ui.warn(msg % m.rel(f))
170 return int(len(files) > 0)
170 return int(len(files) > 0)
171
171
172 if after:
172 if after:
173 remove = deleted
173 remove = deleted
174 result = warn(modified + added + clean,
174 result = warn(modified + added + clean,
175 _('not removing %s: file still exists\n'))
175 _('not removing %s: file still exists\n'))
176 else:
176 else:
177 remove = deleted + clean
177 remove = deleted + clean
178 result = warn(modified, _('not removing %s: file is modified (use -f'
178 result = warn(modified, _('not removing %s: file is modified (use -f'
179 ' to force removal)\n'))
179 ' to force removal)\n'))
180 result = warn(added, _('not removing %s: file has been marked for add'
180 result = warn(added, _('not removing %s: file has been marked for add'
181 ' (use forget to undo)\n')) or result
181 ' (use forget to undo)\n')) or result
182
182
183 # Need to lock because standin files are deleted then removed from the
183 # Need to lock because standin files are deleted then removed from the
184 # repository and we could race in-between.
184 # repository and we could race in-between.
185 with repo.wlock():
185 with repo.wlock():
186 lfdirstate = lfutil.openlfdirstate(ui, repo)
186 lfdirstate = lfutil.openlfdirstate(ui, repo)
187 for f in sorted(remove):
187 for f in sorted(remove):
188 if ui.verbose or not m.exact(f):
188 if ui.verbose or not m.exact(f):
189 # addremove in core gets fancy with the name, remove doesn't
189 # addremove in core gets fancy with the name, remove doesn't
190 if isaddremove:
190 if isaddremove:
191 name = m.uipath(f)
191 name = m.uipath(f)
192 else:
192 else:
193 name = m.rel(f)
193 name = m.rel(f)
194 ui.status(_('removing %s\n') % name)
194 ui.status(_('removing %s\n') % name)
195
195
196 if not dryrun:
196 if not dryrun:
197 if not after:
197 if not after:
198 repo.wvfs.unlinkpath(f, ignoremissing=True)
198 repo.wvfs.unlinkpath(f, ignoremissing=True)
199
199
200 if dryrun:
200 if dryrun:
201 return result
201 return result
202
202
203 remove = [lfutil.standin(f) for f in remove]
203 remove = [lfutil.standin(f) for f in remove]
204 # If this is being called by addremove, let the original addremove
204 # If this is being called by addremove, let the original addremove
205 # function handle this.
205 # function handle this.
206 if not isaddremove:
206 if not isaddremove:
207 for f in remove:
207 for f in remove:
208 repo.wvfs.unlinkpath(f, ignoremissing=True)
208 repo.wvfs.unlinkpath(f, ignoremissing=True)
209 repo[None].forget(remove)
209 repo[None].forget(remove)
210
210
211 for f in remove:
211 for f in remove:
212 lfutil.synclfdirstate(repo, lfdirstate, lfutil.splitstandin(f),
212 lfutil.synclfdirstate(repo, lfdirstate, lfutil.splitstandin(f),
213 False)
213 False)
214
214
215 lfdirstate.write()
215 lfdirstate.write()
216
216
217 return result
217 return result
218
218
219 # For overriding mercurial.hgweb.webcommands so that largefiles will
219 # For overriding mercurial.hgweb.webcommands so that largefiles will
220 # appear at their right place in the manifests.
220 # appear at their right place in the manifests.
221 @eh.wrapfunction(webcommands, 'decodepath')
221 @eh.wrapfunction(webcommands, 'decodepath')
222 def decodepath(orig, path):
222 def decodepath(orig, path):
223 return lfutil.splitstandin(path) or path
223 return lfutil.splitstandin(path) or path
224
224
225 # -- Wrappers: modify existing commands --------------------------------
225 # -- Wrappers: modify existing commands --------------------------------
226
226
227 @eh.wrapcommand('add',
227 @eh.wrapcommand('add',
228 opts=[('', 'large', None, _('add as largefile')),
228 opts=[('', 'large', None, _('add as largefile')),
229 ('', 'normal', None, _('add as normal file')),
229 ('', 'normal', None, _('add as normal file')),
230 ('', 'lfsize', '', _('add all files above this size (in megabytes) '
230 ('', 'lfsize', '', _('add all files above this size (in megabytes) '
231 'as largefiles (default: 10)'))])
231 'as largefiles (default: 10)'))])
232 def overrideadd(orig, ui, repo, *pats, **opts):
232 def overrideadd(orig, ui, repo, *pats, **opts):
233 if opts.get(r'normal') and opts.get(r'large'):
233 if opts.get(r'normal') and opts.get(r'large'):
234 raise error.Abort(_('--normal cannot be used with --large'))
234 raise error.Abort(_('--normal cannot be used with --large'))
235 return orig(ui, repo, *pats, **opts)
235 return orig(ui, repo, *pats, **opts)
236
236
237 @eh.wrapfunction(cmdutil, 'add')
237 @eh.wrapfunction(cmdutil, 'add')
238 def cmdutiladd(orig, ui, repo, matcher, prefix, explicitonly, **opts):
238 def cmdutiladd(orig, ui, repo, matcher, prefix, uipathfn, explicitonly, **opts):
239 # The --normal flag short circuits this override
239 # The --normal flag short circuits this override
240 if opts.get(r'normal'):
240 if opts.get(r'normal'):
241 return orig(ui, repo, matcher, prefix, explicitonly, **opts)
241 return orig(ui, repo, matcher, prefix, uipathfn, explicitonly, **opts)
242
242
243 ladded, lbad = addlargefiles(ui, repo, False, matcher, **opts)
243 ladded, lbad = addlargefiles(ui, repo, False, matcher, **opts)
244 normalmatcher = composenormalfilematcher(matcher, repo[None].manifest(),
244 normalmatcher = composenormalfilematcher(matcher, repo[None].manifest(),
245 ladded)
245 ladded)
246 bad = orig(ui, repo, normalmatcher, prefix, explicitonly, **opts)
246 bad = orig(ui, repo, normalmatcher, prefix, uipathfn, explicitonly, **opts)
247
247
248 bad.extend(f for f in lbad)
248 bad.extend(f for f in lbad)
249 return bad
249 return bad
250
250
251 @eh.wrapfunction(cmdutil, 'remove')
251 @eh.wrapfunction(cmdutil, 'remove')
252 def cmdutilremove(orig, ui, repo, matcher, prefix, after, force, subrepos,
252 def cmdutilremove(orig, ui, repo, matcher, prefix, after, force, subrepos,
253 dryrun):
253 dryrun):
254 normalmatcher = composenormalfilematcher(matcher, repo[None].manifest())
254 normalmatcher = composenormalfilematcher(matcher, repo[None].manifest())
255 result = orig(ui, repo, normalmatcher, prefix, after, force, subrepos,
255 result = orig(ui, repo, normalmatcher, prefix, after, force, subrepos,
256 dryrun)
256 dryrun)
257 return removelargefiles(ui, repo, False, matcher, dryrun, after=after,
257 return removelargefiles(ui, repo, False, matcher, dryrun, after=after,
258 force=force) or result
258 force=force) or result
259
259
260 @eh.wrapfunction(subrepo.hgsubrepo, 'status')
260 @eh.wrapfunction(subrepo.hgsubrepo, 'status')
261 def overridestatusfn(orig, repo, rev2, **opts):
261 def overridestatusfn(orig, repo, rev2, **opts):
262 try:
262 try:
263 repo._repo.lfstatus = True
263 repo._repo.lfstatus = True
264 return orig(repo, rev2, **opts)
264 return orig(repo, rev2, **opts)
265 finally:
265 finally:
266 repo._repo.lfstatus = False
266 repo._repo.lfstatus = False
267
267
268 @eh.wrapcommand('status')
268 @eh.wrapcommand('status')
269 def overridestatus(orig, ui, repo, *pats, **opts):
269 def overridestatus(orig, ui, repo, *pats, **opts):
270 try:
270 try:
271 repo.lfstatus = True
271 repo.lfstatus = True
272 return orig(ui, repo, *pats, **opts)
272 return orig(ui, repo, *pats, **opts)
273 finally:
273 finally:
274 repo.lfstatus = False
274 repo.lfstatus = False
275
275
276 @eh.wrapfunction(subrepo.hgsubrepo, 'dirty')
276 @eh.wrapfunction(subrepo.hgsubrepo, 'dirty')
277 def overridedirty(orig, repo, ignoreupdate=False, missing=False):
277 def overridedirty(orig, repo, ignoreupdate=False, missing=False):
278 try:
278 try:
279 repo._repo.lfstatus = True
279 repo._repo.lfstatus = True
280 return orig(repo, ignoreupdate=ignoreupdate, missing=missing)
280 return orig(repo, ignoreupdate=ignoreupdate, missing=missing)
281 finally:
281 finally:
282 repo._repo.lfstatus = False
282 repo._repo.lfstatus = False
283
283
284 @eh.wrapcommand('log')
284 @eh.wrapcommand('log')
285 def overridelog(orig, ui, repo, *pats, **opts):
285 def overridelog(orig, ui, repo, *pats, **opts):
286 def overridematchandpats(orig, ctx, pats=(), opts=None, globbed=False,
286 def overridematchandpats(orig, ctx, pats=(), opts=None, globbed=False,
287 default='relpath', badfn=None):
287 default='relpath', badfn=None):
288 """Matcher that merges root directory with .hglf, suitable for log.
288 """Matcher that merges root directory with .hglf, suitable for log.
289 It is still possible to match .hglf directly.
289 It is still possible to match .hglf directly.
290 For any listed files run log on the standin too.
290 For any listed files run log on the standin too.
291 matchfn tries both the given filename and with .hglf stripped.
291 matchfn tries both the given filename and with .hglf stripped.
292 """
292 """
293 if opts is None:
293 if opts is None:
294 opts = {}
294 opts = {}
295 matchandpats = orig(ctx, pats, opts, globbed, default, badfn=badfn)
295 matchandpats = orig(ctx, pats, opts, globbed, default, badfn=badfn)
296 m, p = copy.copy(matchandpats)
296 m, p = copy.copy(matchandpats)
297
297
298 if m.always():
298 if m.always():
299 # We want to match everything anyway, so there's no benefit trying
299 # We want to match everything anyway, so there's no benefit trying
300 # to add standins.
300 # to add standins.
301 return matchandpats
301 return matchandpats
302
302
303 pats = set(p)
303 pats = set(p)
304
304
305 def fixpats(pat, tostandin=lfutil.standin):
305 def fixpats(pat, tostandin=lfutil.standin):
306 if pat.startswith('set:'):
306 if pat.startswith('set:'):
307 return pat
307 return pat
308
308
309 kindpat = matchmod._patsplit(pat, None)
309 kindpat = matchmod._patsplit(pat, None)
310
310
311 if kindpat[0] is not None:
311 if kindpat[0] is not None:
312 return kindpat[0] + ':' + tostandin(kindpat[1])
312 return kindpat[0] + ':' + tostandin(kindpat[1])
313 return tostandin(kindpat[1])
313 return tostandin(kindpat[1])
314
314
315 if m._cwd:
315 if m._cwd:
316 hglf = lfutil.shortname
316 hglf = lfutil.shortname
317 back = util.pconvert(m.rel(hglf)[:-len(hglf)])
317 back = util.pconvert(m.rel(hglf)[:-len(hglf)])
318
318
319 def tostandin(f):
319 def tostandin(f):
320 # The file may already be a standin, so truncate the back
320 # The file may already be a standin, so truncate the back
321 # prefix and test before mangling it. This avoids turning
321 # prefix and test before mangling it. This avoids turning
322 # 'glob:../.hglf/foo*' into 'glob:../.hglf/../.hglf/foo*'.
322 # 'glob:../.hglf/foo*' into 'glob:../.hglf/../.hglf/foo*'.
323 if f.startswith(back) and lfutil.splitstandin(f[len(back):]):
323 if f.startswith(back) and lfutil.splitstandin(f[len(back):]):
324 return f
324 return f
325
325
326 # An absolute path is from outside the repo, so truncate the
326 # An absolute path is from outside the repo, so truncate the
327 # path to the root before building the standin. Otherwise cwd
327 # path to the root before building the standin. Otherwise cwd
328 # is somewhere in the repo, relative to root, and needs to be
328 # is somewhere in the repo, relative to root, and needs to be
329 # prepended before building the standin.
329 # prepended before building the standin.
330 if os.path.isabs(m._cwd):
330 if os.path.isabs(m._cwd):
331 f = f[len(back):]
331 f = f[len(back):]
332 else:
332 else:
333 f = m._cwd + '/' + f
333 f = m._cwd + '/' + f
334 return back + lfutil.standin(f)
334 return back + lfutil.standin(f)
335 else:
335 else:
336 def tostandin(f):
336 def tostandin(f):
337 if lfutil.isstandin(f):
337 if lfutil.isstandin(f):
338 return f
338 return f
339 return lfutil.standin(f)
339 return lfutil.standin(f)
340 pats.update(fixpats(f, tostandin) for f in p)
340 pats.update(fixpats(f, tostandin) for f in p)
341
341
342 for i in range(0, len(m._files)):
342 for i in range(0, len(m._files)):
343 # Don't add '.hglf' to m.files, since that is already covered by '.'
343 # Don't add '.hglf' to m.files, since that is already covered by '.'
344 if m._files[i] == '.':
344 if m._files[i] == '.':
345 continue
345 continue
346 standin = lfutil.standin(m._files[i])
346 standin = lfutil.standin(m._files[i])
347 # If the "standin" is a directory, append instead of replace to
347 # If the "standin" is a directory, append instead of replace to
348 # support naming a directory on the command line with only
348 # support naming a directory on the command line with only
349 # largefiles. The original directory is kept to support normal
349 # largefiles. The original directory is kept to support normal
350 # files.
350 # files.
351 if standin in ctx:
351 if standin in ctx:
352 m._files[i] = standin
352 m._files[i] = standin
353 elif m._files[i] not in ctx and repo.wvfs.isdir(standin):
353 elif m._files[i] not in ctx and repo.wvfs.isdir(standin):
354 m._files.append(standin)
354 m._files.append(standin)
355
355
356 m._fileset = set(m._files)
356 m._fileset = set(m._files)
357 m.always = lambda: False
357 m.always = lambda: False
358 origmatchfn = m.matchfn
358 origmatchfn = m.matchfn
359 def lfmatchfn(f):
359 def lfmatchfn(f):
360 lf = lfutil.splitstandin(f)
360 lf = lfutil.splitstandin(f)
361 if lf is not None and origmatchfn(lf):
361 if lf is not None and origmatchfn(lf):
362 return True
362 return True
363 r = origmatchfn(f)
363 r = origmatchfn(f)
364 return r
364 return r
365 m.matchfn = lfmatchfn
365 m.matchfn = lfmatchfn
366
366
367 ui.debug('updated patterns: %s\n' % ', '.join(sorted(pats)))
367 ui.debug('updated patterns: %s\n' % ', '.join(sorted(pats)))
368 return m, pats
368 return m, pats
369
369
370 # For hg log --patch, the match object is used in two different senses:
370 # For hg log --patch, the match object is used in two different senses:
371 # (1) to determine what revisions should be printed out, and
371 # (1) to determine what revisions should be printed out, and
372 # (2) to determine what files to print out diffs for.
372 # (2) to determine what files to print out diffs for.
373 # The magic matchandpats override should be used for case (1) but not for
373 # The magic matchandpats override should be used for case (1) but not for
374 # case (2).
374 # case (2).
375 oldmatchandpats = scmutil.matchandpats
375 oldmatchandpats = scmutil.matchandpats
376 def overridemakefilematcher(orig, repo, pats, opts, badfn=None):
376 def overridemakefilematcher(orig, repo, pats, opts, badfn=None):
377 wctx = repo[None]
377 wctx = repo[None]
378 match, pats = oldmatchandpats(wctx, pats, opts, badfn=badfn)
378 match, pats = oldmatchandpats(wctx, pats, opts, badfn=badfn)
379 return lambda ctx: match
379 return lambda ctx: match
380
380
381 wrappedmatchandpats = extensions.wrappedfunction(scmutil, 'matchandpats',
381 wrappedmatchandpats = extensions.wrappedfunction(scmutil, 'matchandpats',
382 overridematchandpats)
382 overridematchandpats)
383 wrappedmakefilematcher = extensions.wrappedfunction(
383 wrappedmakefilematcher = extensions.wrappedfunction(
384 logcmdutil, '_makenofollowfilematcher', overridemakefilematcher)
384 logcmdutil, '_makenofollowfilematcher', overridemakefilematcher)
385 with wrappedmatchandpats, wrappedmakefilematcher:
385 with wrappedmatchandpats, wrappedmakefilematcher:
386 return orig(ui, repo, *pats, **opts)
386 return orig(ui, repo, *pats, **opts)
387
387
388 @eh.wrapcommand('verify',
388 @eh.wrapcommand('verify',
389 opts=[('', 'large', None,
389 opts=[('', 'large', None,
390 _('verify that all largefiles in current revision exists')),
390 _('verify that all largefiles in current revision exists')),
391 ('', 'lfa', None,
391 ('', 'lfa', None,
392 _('verify largefiles in all revisions, not just current')),
392 _('verify largefiles in all revisions, not just current')),
393 ('', 'lfc', None,
393 ('', 'lfc', None,
394 _('verify local largefile contents, not just existence'))])
394 _('verify local largefile contents, not just existence'))])
395 def overrideverify(orig, ui, repo, *pats, **opts):
395 def overrideverify(orig, ui, repo, *pats, **opts):
396 large = opts.pop(r'large', False)
396 large = opts.pop(r'large', False)
397 all = opts.pop(r'lfa', False)
397 all = opts.pop(r'lfa', False)
398 contents = opts.pop(r'lfc', False)
398 contents = opts.pop(r'lfc', False)
399
399
400 result = orig(ui, repo, *pats, **opts)
400 result = orig(ui, repo, *pats, **opts)
401 if large or all or contents:
401 if large or all or contents:
402 result = result or lfcommands.verifylfiles(ui, repo, all, contents)
402 result = result or lfcommands.verifylfiles(ui, repo, all, contents)
403 return result
403 return result
404
404
405 @eh.wrapcommand('debugstate',
405 @eh.wrapcommand('debugstate',
406 opts=[('', 'large', None, _('display largefiles dirstate'))])
406 opts=[('', 'large', None, _('display largefiles dirstate'))])
407 def overridedebugstate(orig, ui, repo, *pats, **opts):
407 def overridedebugstate(orig, ui, repo, *pats, **opts):
408 large = opts.pop(r'large', False)
408 large = opts.pop(r'large', False)
409 if large:
409 if large:
410 class fakerepo(object):
410 class fakerepo(object):
411 dirstate = lfutil.openlfdirstate(ui, repo)
411 dirstate = lfutil.openlfdirstate(ui, repo)
412 orig(ui, fakerepo, *pats, **opts)
412 orig(ui, fakerepo, *pats, **opts)
413 else:
413 else:
414 orig(ui, repo, *pats, **opts)
414 orig(ui, repo, *pats, **opts)
415
415
416 # Before starting the manifest merge, merge.updates will call
416 # Before starting the manifest merge, merge.updates will call
417 # _checkunknownfile to check if there are any files in the merged-in
417 # _checkunknownfile to check if there are any files in the merged-in
418 # changeset that collide with unknown files in the working copy.
418 # changeset that collide with unknown files in the working copy.
419 #
419 #
420 # The largefiles are seen as unknown, so this prevents us from merging
420 # The largefiles are seen as unknown, so this prevents us from merging
421 # in a file 'foo' if we already have a largefile with the same name.
421 # in a file 'foo' if we already have a largefile with the same name.
422 #
422 #
423 # The overridden function filters the unknown files by removing any
423 # The overridden function filters the unknown files by removing any
424 # largefiles. This makes the merge proceed and we can then handle this
424 # largefiles. This makes the merge proceed and we can then handle this
425 # case further in the overridden calculateupdates function below.
425 # case further in the overridden calculateupdates function below.
426 @eh.wrapfunction(merge, '_checkunknownfile')
426 @eh.wrapfunction(merge, '_checkunknownfile')
427 def overridecheckunknownfile(origfn, repo, wctx, mctx, f, f2=None):
427 def overridecheckunknownfile(origfn, repo, wctx, mctx, f, f2=None):
428 if lfutil.standin(repo.dirstate.normalize(f)) in wctx:
428 if lfutil.standin(repo.dirstate.normalize(f)) in wctx:
429 return False
429 return False
430 return origfn(repo, wctx, mctx, f, f2)
430 return origfn(repo, wctx, mctx, f, f2)
431
431
432 # The manifest merge handles conflicts on the manifest level. We want
432 # The manifest merge handles conflicts on the manifest level. We want
433 # to handle changes in largefile-ness of files at this level too.
433 # to handle changes in largefile-ness of files at this level too.
434 #
434 #
435 # The strategy is to run the original calculateupdates and then process
435 # The strategy is to run the original calculateupdates and then process
436 # the action list it outputs. There are two cases we need to deal with:
436 # the action list it outputs. There are two cases we need to deal with:
437 #
437 #
438 # 1. Normal file in p1, largefile in p2. Here the largefile is
438 # 1. Normal file in p1, largefile in p2. Here the largefile is
439 # detected via its standin file, which will enter the working copy
439 # detected via its standin file, which will enter the working copy
440 # with a "get" action. It is not "merge" since the standin is all
440 # with a "get" action. It is not "merge" since the standin is all
441 # Mercurial is concerned with at this level -- the link to the
441 # Mercurial is concerned with at this level -- the link to the
442 # existing normal file is not relevant here.
442 # existing normal file is not relevant here.
443 #
443 #
444 # 2. Largefile in p1, normal file in p2. Here we get a "merge" action
444 # 2. Largefile in p1, normal file in p2. Here we get a "merge" action
445 # since the largefile will be present in the working copy and
445 # since the largefile will be present in the working copy and
446 # different from the normal file in p2. Mercurial therefore
446 # different from the normal file in p2. Mercurial therefore
447 # triggers a merge action.
447 # triggers a merge action.
448 #
448 #
449 # In both cases, we prompt the user and emit new actions to either
449 # In both cases, we prompt the user and emit new actions to either
450 # remove the standin (if the normal file was kept) or to remove the
450 # remove the standin (if the normal file was kept) or to remove the
451 # normal file and get the standin (if the largefile was kept). The
451 # normal file and get the standin (if the largefile was kept). The
452 # default prompt answer is to use the largefile version since it was
452 # default prompt answer is to use the largefile version since it was
453 # presumably changed on purpose.
453 # presumably changed on purpose.
454 #
454 #
455 # Finally, the merge.applyupdates function will then take care of
455 # Finally, the merge.applyupdates function will then take care of
456 # writing the files into the working copy and lfcommands.updatelfiles
456 # writing the files into the working copy and lfcommands.updatelfiles
457 # will update the largefiles.
457 # will update the largefiles.
458 @eh.wrapfunction(merge, 'calculateupdates')
458 @eh.wrapfunction(merge, 'calculateupdates')
459 def overridecalculateupdates(origfn, repo, p1, p2, pas, branchmerge, force,
459 def overridecalculateupdates(origfn, repo, p1, p2, pas, branchmerge, force,
460 acceptremote, *args, **kwargs):
460 acceptremote, *args, **kwargs):
461 overwrite = force and not branchmerge
461 overwrite = force and not branchmerge
462 actions, diverge, renamedelete = origfn(
462 actions, diverge, renamedelete = origfn(
463 repo, p1, p2, pas, branchmerge, force, acceptremote, *args, **kwargs)
463 repo, p1, p2, pas, branchmerge, force, acceptremote, *args, **kwargs)
464
464
465 if overwrite:
465 if overwrite:
466 return actions, diverge, renamedelete
466 return actions, diverge, renamedelete
467
467
468 # Convert to dictionary with filename as key and action as value.
468 # Convert to dictionary with filename as key and action as value.
469 lfiles = set()
469 lfiles = set()
470 for f in actions:
470 for f in actions:
471 splitstandin = lfutil.splitstandin(f)
471 splitstandin = lfutil.splitstandin(f)
472 if splitstandin in p1:
472 if splitstandin in p1:
473 lfiles.add(splitstandin)
473 lfiles.add(splitstandin)
474 elif lfutil.standin(f) in p1:
474 elif lfutil.standin(f) in p1:
475 lfiles.add(f)
475 lfiles.add(f)
476
476
477 for lfile in sorted(lfiles):
477 for lfile in sorted(lfiles):
478 standin = lfutil.standin(lfile)
478 standin = lfutil.standin(lfile)
479 (lm, largs, lmsg) = actions.get(lfile, (None, None, None))
479 (lm, largs, lmsg) = actions.get(lfile, (None, None, None))
480 (sm, sargs, smsg) = actions.get(standin, (None, None, None))
480 (sm, sargs, smsg) = actions.get(standin, (None, None, None))
481 if sm in ('g', 'dc') and lm != 'r':
481 if sm in ('g', 'dc') and lm != 'r':
482 if sm == 'dc':
482 if sm == 'dc':
483 f1, f2, fa, move, anc = sargs
483 f1, f2, fa, move, anc = sargs
484 sargs = (p2[f2].flags(), False)
484 sargs = (p2[f2].flags(), False)
485 # Case 1: normal file in the working copy, largefile in
485 # Case 1: normal file in the working copy, largefile in
486 # the second parent
486 # the second parent
487 usermsg = _('remote turned local normal file %s into a largefile\n'
487 usermsg = _('remote turned local normal file %s into a largefile\n'
488 'use (l)argefile or keep (n)ormal file?'
488 'use (l)argefile or keep (n)ormal file?'
489 '$$ &Largefile $$ &Normal file') % lfile
489 '$$ &Largefile $$ &Normal file') % lfile
490 if repo.ui.promptchoice(usermsg, 0) == 0: # pick remote largefile
490 if repo.ui.promptchoice(usermsg, 0) == 0: # pick remote largefile
491 actions[lfile] = ('r', None, 'replaced by standin')
491 actions[lfile] = ('r', None, 'replaced by standin')
492 actions[standin] = ('g', sargs, 'replaces standin')
492 actions[standin] = ('g', sargs, 'replaces standin')
493 else: # keep local normal file
493 else: # keep local normal file
494 actions[lfile] = ('k', None, 'replaces standin')
494 actions[lfile] = ('k', None, 'replaces standin')
495 if branchmerge:
495 if branchmerge:
496 actions[standin] = ('k', None, 'replaced by non-standin')
496 actions[standin] = ('k', None, 'replaced by non-standin')
497 else:
497 else:
498 actions[standin] = ('r', None, 'replaced by non-standin')
498 actions[standin] = ('r', None, 'replaced by non-standin')
499 elif lm in ('g', 'dc') and sm != 'r':
499 elif lm in ('g', 'dc') and sm != 'r':
500 if lm == 'dc':
500 if lm == 'dc':
501 f1, f2, fa, move, anc = largs
501 f1, f2, fa, move, anc = largs
502 largs = (p2[f2].flags(), False)
502 largs = (p2[f2].flags(), False)
503 # Case 2: largefile in the working copy, normal file in
503 # Case 2: largefile in the working copy, normal file in
504 # the second parent
504 # the second parent
505 usermsg = _('remote turned local largefile %s into a normal file\n'
505 usermsg = _('remote turned local largefile %s into a normal file\n'
506 'keep (l)argefile or use (n)ormal file?'
506 'keep (l)argefile or use (n)ormal file?'
507 '$$ &Largefile $$ &Normal file') % lfile
507 '$$ &Largefile $$ &Normal file') % lfile
508 if repo.ui.promptchoice(usermsg, 0) == 0: # keep local largefile
508 if repo.ui.promptchoice(usermsg, 0) == 0: # keep local largefile
509 if branchmerge:
509 if branchmerge:
510 # largefile can be restored from standin safely
510 # largefile can be restored from standin safely
511 actions[lfile] = ('k', None, 'replaced by standin')
511 actions[lfile] = ('k', None, 'replaced by standin')
512 actions[standin] = ('k', None, 'replaces standin')
512 actions[standin] = ('k', None, 'replaces standin')
513 else:
513 else:
514 # "lfile" should be marked as "removed" without
514 # "lfile" should be marked as "removed" without
515 # removal of itself
515 # removal of itself
516 actions[lfile] = ('lfmr', None,
516 actions[lfile] = ('lfmr', None,
517 'forget non-standin largefile')
517 'forget non-standin largefile')
518
518
519 # linear-merge should treat this largefile as 're-added'
519 # linear-merge should treat this largefile as 're-added'
520 actions[standin] = ('a', None, 'keep standin')
520 actions[standin] = ('a', None, 'keep standin')
521 else: # pick remote normal file
521 else: # pick remote normal file
522 actions[lfile] = ('g', largs, 'replaces standin')
522 actions[lfile] = ('g', largs, 'replaces standin')
523 actions[standin] = ('r', None, 'replaced by non-standin')
523 actions[standin] = ('r', None, 'replaced by non-standin')
524
524
525 return actions, diverge, renamedelete
525 return actions, diverge, renamedelete
526
526
527 @eh.wrapfunction(merge, 'recordupdates')
527 @eh.wrapfunction(merge, 'recordupdates')
528 def mergerecordupdates(orig, repo, actions, branchmerge):
528 def mergerecordupdates(orig, repo, actions, branchmerge):
529 if 'lfmr' in actions:
529 if 'lfmr' in actions:
530 lfdirstate = lfutil.openlfdirstate(repo.ui, repo)
530 lfdirstate = lfutil.openlfdirstate(repo.ui, repo)
531 for lfile, args, msg in actions['lfmr']:
531 for lfile, args, msg in actions['lfmr']:
532 # this should be executed before 'orig', to execute 'remove'
532 # this should be executed before 'orig', to execute 'remove'
533 # before all other actions
533 # before all other actions
534 repo.dirstate.remove(lfile)
534 repo.dirstate.remove(lfile)
535 # make sure lfile doesn't get synclfdirstate'd as normal
535 # make sure lfile doesn't get synclfdirstate'd as normal
536 lfdirstate.add(lfile)
536 lfdirstate.add(lfile)
537 lfdirstate.write()
537 lfdirstate.write()
538
538
539 return orig(repo, actions, branchmerge)
539 return orig(repo, actions, branchmerge)
540
540
541 # Override filemerge to prompt the user about how they wish to merge
541 # Override filemerge to prompt the user about how they wish to merge
542 # largefiles. This will handle identical edits without prompting the user.
542 # largefiles. This will handle identical edits without prompting the user.
543 @eh.wrapfunction(filemerge, '_filemerge')
543 @eh.wrapfunction(filemerge, '_filemerge')
544 def overridefilemerge(origfn, premerge, repo, wctx, mynode, orig, fcd, fco, fca,
544 def overridefilemerge(origfn, premerge, repo, wctx, mynode, orig, fcd, fco, fca,
545 labels=None):
545 labels=None):
546 if not lfutil.isstandin(orig) or fcd.isabsent() or fco.isabsent():
546 if not lfutil.isstandin(orig) or fcd.isabsent() or fco.isabsent():
547 return origfn(premerge, repo, wctx, mynode, orig, fcd, fco, fca,
547 return origfn(premerge, repo, wctx, mynode, orig, fcd, fco, fca,
548 labels=labels)
548 labels=labels)
549
549
550 ahash = lfutil.readasstandin(fca).lower()
550 ahash = lfutil.readasstandin(fca).lower()
551 dhash = lfutil.readasstandin(fcd).lower()
551 dhash = lfutil.readasstandin(fcd).lower()
552 ohash = lfutil.readasstandin(fco).lower()
552 ohash = lfutil.readasstandin(fco).lower()
553 if (ohash != ahash and
553 if (ohash != ahash and
554 ohash != dhash and
554 ohash != dhash and
555 (dhash == ahash or
555 (dhash == ahash or
556 repo.ui.promptchoice(
556 repo.ui.promptchoice(
557 _('largefile %s has a merge conflict\nancestor was %s\n'
557 _('largefile %s has a merge conflict\nancestor was %s\n'
558 'keep (l)ocal %s or\ntake (o)ther %s?'
558 'keep (l)ocal %s or\ntake (o)ther %s?'
559 '$$ &Local $$ &Other') %
559 '$$ &Local $$ &Other') %
560 (lfutil.splitstandin(orig), ahash, dhash, ohash),
560 (lfutil.splitstandin(orig), ahash, dhash, ohash),
561 0) == 1)):
561 0) == 1)):
562 repo.wwrite(fcd.path(), fco.data(), fco.flags())
562 repo.wwrite(fcd.path(), fco.data(), fco.flags())
563 return True, 0, False
563 return True, 0, False
564
564
565 @eh.wrapfunction(copiesmod, 'pathcopies')
565 @eh.wrapfunction(copiesmod, 'pathcopies')
566 def copiespathcopies(orig, ctx1, ctx2, match=None):
566 def copiespathcopies(orig, ctx1, ctx2, match=None):
567 copies = orig(ctx1, ctx2, match=match)
567 copies = orig(ctx1, ctx2, match=match)
568 updated = {}
568 updated = {}
569
569
570 for k, v in copies.iteritems():
570 for k, v in copies.iteritems():
571 updated[lfutil.splitstandin(k) or k] = lfutil.splitstandin(v) or v
571 updated[lfutil.splitstandin(k) or k] = lfutil.splitstandin(v) or v
572
572
573 return updated
573 return updated
574
574
575 # Copy first changes the matchers to match standins instead of
575 # Copy first changes the matchers to match standins instead of
576 # largefiles. Then it overrides util.copyfile in that function it
576 # largefiles. Then it overrides util.copyfile in that function it
577 # checks if the destination largefile already exists. It also keeps a
577 # checks if the destination largefile already exists. It also keeps a
578 # list of copied files so that the largefiles can be copied and the
578 # list of copied files so that the largefiles can be copied and the
579 # dirstate updated.
579 # dirstate updated.
580 @eh.wrapfunction(cmdutil, 'copy')
580 @eh.wrapfunction(cmdutil, 'copy')
581 def overridecopy(orig, ui, repo, pats, opts, rename=False):
581 def overridecopy(orig, ui, repo, pats, opts, rename=False):
582 # doesn't remove largefile on rename
582 # doesn't remove largefile on rename
583 if len(pats) < 2:
583 if len(pats) < 2:
584 # this isn't legal, let the original function deal with it
584 # this isn't legal, let the original function deal with it
585 return orig(ui, repo, pats, opts, rename)
585 return orig(ui, repo, pats, opts, rename)
586
586
587 # This could copy both lfiles and normal files in one command,
587 # This could copy both lfiles and normal files in one command,
588 # but we don't want to do that. First replace their matcher to
588 # but we don't want to do that. First replace their matcher to
589 # only match normal files and run it, then replace it to just
589 # only match normal files and run it, then replace it to just
590 # match largefiles and run it again.
590 # match largefiles and run it again.
591 nonormalfiles = False
591 nonormalfiles = False
592 nolfiles = False
592 nolfiles = False
593 manifest = repo[None].manifest()
593 manifest = repo[None].manifest()
594 def normalfilesmatchfn(orig, ctx, pats=(), opts=None, globbed=False,
594 def normalfilesmatchfn(orig, ctx, pats=(), opts=None, globbed=False,
595 default='relpath', badfn=None):
595 default='relpath', badfn=None):
596 if opts is None:
596 if opts is None:
597 opts = {}
597 opts = {}
598 match = orig(ctx, pats, opts, globbed, default, badfn=badfn)
598 match = orig(ctx, pats, opts, globbed, default, badfn=badfn)
599 return composenormalfilematcher(match, manifest)
599 return composenormalfilematcher(match, manifest)
600 with extensions.wrappedfunction(scmutil, 'match', normalfilesmatchfn):
600 with extensions.wrappedfunction(scmutil, 'match', normalfilesmatchfn):
601 try:
601 try:
602 result = orig(ui, repo, pats, opts, rename)
602 result = orig(ui, repo, pats, opts, rename)
603 except error.Abort as e:
603 except error.Abort as e:
604 if pycompat.bytestr(e) != _('no files to copy'):
604 if pycompat.bytestr(e) != _('no files to copy'):
605 raise e
605 raise e
606 else:
606 else:
607 nonormalfiles = True
607 nonormalfiles = True
608 result = 0
608 result = 0
609
609
610 # The first rename can cause our current working directory to be removed.
610 # The first rename can cause our current working directory to be removed.
611 # In that case there is nothing left to copy/rename so just quit.
611 # In that case there is nothing left to copy/rename so just quit.
612 try:
612 try:
613 repo.getcwd()
613 repo.getcwd()
614 except OSError:
614 except OSError:
615 return result
615 return result
616
616
617 def makestandin(relpath):
617 def makestandin(relpath):
618 path = pathutil.canonpath(repo.root, repo.getcwd(), relpath)
618 path = pathutil.canonpath(repo.root, repo.getcwd(), relpath)
619 return repo.wvfs.join(lfutil.standin(path))
619 return repo.wvfs.join(lfutil.standin(path))
620
620
621 fullpats = scmutil.expandpats(pats)
621 fullpats = scmutil.expandpats(pats)
622 dest = fullpats[-1]
622 dest = fullpats[-1]
623
623
624 if os.path.isdir(dest):
624 if os.path.isdir(dest):
625 if not os.path.isdir(makestandin(dest)):
625 if not os.path.isdir(makestandin(dest)):
626 os.makedirs(makestandin(dest))
626 os.makedirs(makestandin(dest))
627
627
628 try:
628 try:
629 # When we call orig below it creates the standins but we don't add
629 # When we call orig below it creates the standins but we don't add
630 # them to the dir state until later so lock during that time.
630 # them to the dir state until later so lock during that time.
631 wlock = repo.wlock()
631 wlock = repo.wlock()
632
632
633 manifest = repo[None].manifest()
633 manifest = repo[None].manifest()
634 def overridematch(orig, ctx, pats=(), opts=None, globbed=False,
634 def overridematch(orig, ctx, pats=(), opts=None, globbed=False,
635 default='relpath', badfn=None):
635 default='relpath', badfn=None):
636 if opts is None:
636 if opts is None:
637 opts = {}
637 opts = {}
638 newpats = []
638 newpats = []
639 # The patterns were previously mangled to add the standin
639 # The patterns were previously mangled to add the standin
640 # directory; we need to remove that now
640 # directory; we need to remove that now
641 for pat in pats:
641 for pat in pats:
642 if matchmod.patkind(pat) is None and lfutil.shortname in pat:
642 if matchmod.patkind(pat) is None and lfutil.shortname in pat:
643 newpats.append(pat.replace(lfutil.shortname, ''))
643 newpats.append(pat.replace(lfutil.shortname, ''))
644 else:
644 else:
645 newpats.append(pat)
645 newpats.append(pat)
646 match = orig(ctx, newpats, opts, globbed, default, badfn=badfn)
646 match = orig(ctx, newpats, opts, globbed, default, badfn=badfn)
647 m = copy.copy(match)
647 m = copy.copy(match)
648 lfile = lambda f: lfutil.standin(f) in manifest
648 lfile = lambda f: lfutil.standin(f) in manifest
649 m._files = [lfutil.standin(f) for f in m._files if lfile(f)]
649 m._files = [lfutil.standin(f) for f in m._files if lfile(f)]
650 m._fileset = set(m._files)
650 m._fileset = set(m._files)
651 origmatchfn = m.matchfn
651 origmatchfn = m.matchfn
652 def matchfn(f):
652 def matchfn(f):
653 lfile = lfutil.splitstandin(f)
653 lfile = lfutil.splitstandin(f)
654 return (lfile is not None and
654 return (lfile is not None and
655 (f in manifest) and
655 (f in manifest) and
656 origmatchfn(lfile) or
656 origmatchfn(lfile) or
657 None)
657 None)
658 m.matchfn = matchfn
658 m.matchfn = matchfn
659 return m
659 return m
660 listpats = []
660 listpats = []
661 for pat in pats:
661 for pat in pats:
662 if matchmod.patkind(pat) is not None:
662 if matchmod.patkind(pat) is not None:
663 listpats.append(pat)
663 listpats.append(pat)
664 else:
664 else:
665 listpats.append(makestandin(pat))
665 listpats.append(makestandin(pat))
666
666
667 copiedfiles = []
667 copiedfiles = []
668 def overridecopyfile(orig, src, dest, *args, **kwargs):
668 def overridecopyfile(orig, src, dest, *args, **kwargs):
669 if (lfutil.shortname in src and
669 if (lfutil.shortname in src and
670 dest.startswith(repo.wjoin(lfutil.shortname))):
670 dest.startswith(repo.wjoin(lfutil.shortname))):
671 destlfile = dest.replace(lfutil.shortname, '')
671 destlfile = dest.replace(lfutil.shortname, '')
672 if not opts['force'] and os.path.exists(destlfile):
672 if not opts['force'] and os.path.exists(destlfile):
673 raise IOError('',
673 raise IOError('',
674 _('destination largefile already exists'))
674 _('destination largefile already exists'))
675 copiedfiles.append((src, dest))
675 copiedfiles.append((src, dest))
676 orig(src, dest, *args, **kwargs)
676 orig(src, dest, *args, **kwargs)
677 with extensions.wrappedfunction(util, 'copyfile', overridecopyfile), \
677 with extensions.wrappedfunction(util, 'copyfile', overridecopyfile), \
678 extensions.wrappedfunction(scmutil, 'match', overridematch):
678 extensions.wrappedfunction(scmutil, 'match', overridematch):
679 result += orig(ui, repo, listpats, opts, rename)
679 result += orig(ui, repo, listpats, opts, rename)
680
680
681 lfdirstate = lfutil.openlfdirstate(ui, repo)
681 lfdirstate = lfutil.openlfdirstate(ui, repo)
682 for (src, dest) in copiedfiles:
682 for (src, dest) in copiedfiles:
683 if (lfutil.shortname in src and
683 if (lfutil.shortname in src and
684 dest.startswith(repo.wjoin(lfutil.shortname))):
684 dest.startswith(repo.wjoin(lfutil.shortname))):
685 srclfile = src.replace(repo.wjoin(lfutil.standin('')), '')
685 srclfile = src.replace(repo.wjoin(lfutil.standin('')), '')
686 destlfile = dest.replace(repo.wjoin(lfutil.standin('')), '')
686 destlfile = dest.replace(repo.wjoin(lfutil.standin('')), '')
687 destlfiledir = repo.wvfs.dirname(repo.wjoin(destlfile)) or '.'
687 destlfiledir = repo.wvfs.dirname(repo.wjoin(destlfile)) or '.'
688 if not os.path.isdir(destlfiledir):
688 if not os.path.isdir(destlfiledir):
689 os.makedirs(destlfiledir)
689 os.makedirs(destlfiledir)
690 if rename:
690 if rename:
691 os.rename(repo.wjoin(srclfile), repo.wjoin(destlfile))
691 os.rename(repo.wjoin(srclfile), repo.wjoin(destlfile))
692
692
693 # The file is gone, but this deletes any empty parent
693 # The file is gone, but this deletes any empty parent
694 # directories as a side-effect.
694 # directories as a side-effect.
695 repo.wvfs.unlinkpath(srclfile, ignoremissing=True)
695 repo.wvfs.unlinkpath(srclfile, ignoremissing=True)
696 lfdirstate.remove(srclfile)
696 lfdirstate.remove(srclfile)
697 else:
697 else:
698 util.copyfile(repo.wjoin(srclfile),
698 util.copyfile(repo.wjoin(srclfile),
699 repo.wjoin(destlfile))
699 repo.wjoin(destlfile))
700
700
701 lfdirstate.add(destlfile)
701 lfdirstate.add(destlfile)
702 lfdirstate.write()
702 lfdirstate.write()
703 except error.Abort as e:
703 except error.Abort as e:
704 if pycompat.bytestr(e) != _('no files to copy'):
704 if pycompat.bytestr(e) != _('no files to copy'):
705 raise e
705 raise e
706 else:
706 else:
707 nolfiles = True
707 nolfiles = True
708 finally:
708 finally:
709 wlock.release()
709 wlock.release()
710
710
711 if nolfiles and nonormalfiles:
711 if nolfiles and nonormalfiles:
712 raise error.Abort(_('no files to copy'))
712 raise error.Abort(_('no files to copy'))
713
713
714 return result
714 return result
715
715
716 # When the user calls revert, we have to be careful to not revert any
716 # When the user calls revert, we have to be careful to not revert any
717 # changes to other largefiles accidentally. This means we have to keep
717 # changes to other largefiles accidentally. This means we have to keep
718 # track of the largefiles that are being reverted so we only pull down
718 # track of the largefiles that are being reverted so we only pull down
719 # the necessary largefiles.
719 # the necessary largefiles.
720 #
720 #
721 # Standins are only updated (to match the hash of largefiles) before
721 # Standins are only updated (to match the hash of largefiles) before
722 # commits. Update the standins then run the original revert, changing
722 # commits. Update the standins then run the original revert, changing
723 # the matcher to hit standins instead of largefiles. Based on the
723 # the matcher to hit standins instead of largefiles. Based on the
724 # resulting standins update the largefiles.
724 # resulting standins update the largefiles.
725 @eh.wrapfunction(cmdutil, 'revert')
725 @eh.wrapfunction(cmdutil, 'revert')
726 def overriderevert(orig, ui, repo, ctx, parents, *pats, **opts):
726 def overriderevert(orig, ui, repo, ctx, parents, *pats, **opts):
727 # Because we put the standins in a bad state (by updating them)
727 # Because we put the standins in a bad state (by updating them)
728 # and then return them to a correct state we need to lock to
728 # and then return them to a correct state we need to lock to
729 # prevent others from changing them in their incorrect state.
729 # prevent others from changing them in their incorrect state.
730 with repo.wlock():
730 with repo.wlock():
731 lfdirstate = lfutil.openlfdirstate(ui, repo)
731 lfdirstate = lfutil.openlfdirstate(ui, repo)
732 s = lfutil.lfdirstatestatus(lfdirstate, repo)
732 s = lfutil.lfdirstatestatus(lfdirstate, repo)
733 lfdirstate.write()
733 lfdirstate.write()
734 for lfile in s.modified:
734 for lfile in s.modified:
735 lfutil.updatestandin(repo, lfile, lfutil.standin(lfile))
735 lfutil.updatestandin(repo, lfile, lfutil.standin(lfile))
736 for lfile in s.deleted:
736 for lfile in s.deleted:
737 fstandin = lfutil.standin(lfile)
737 fstandin = lfutil.standin(lfile)
738 if (repo.wvfs.exists(fstandin)):
738 if (repo.wvfs.exists(fstandin)):
739 repo.wvfs.unlink(fstandin)
739 repo.wvfs.unlink(fstandin)
740
740
741 oldstandins = lfutil.getstandinsstate(repo)
741 oldstandins = lfutil.getstandinsstate(repo)
742
742
743 def overridematch(orig, mctx, pats=(), opts=None, globbed=False,
743 def overridematch(orig, mctx, pats=(), opts=None, globbed=False,
744 default='relpath', badfn=None):
744 default='relpath', badfn=None):
745 if opts is None:
745 if opts is None:
746 opts = {}
746 opts = {}
747 match = orig(mctx, pats, opts, globbed, default, badfn=badfn)
747 match = orig(mctx, pats, opts, globbed, default, badfn=badfn)
748 m = copy.copy(match)
748 m = copy.copy(match)
749
749
750 # revert supports recursing into subrepos, and though largefiles
750 # revert supports recursing into subrepos, and though largefiles
751 # currently doesn't work correctly in that case, this match is
751 # currently doesn't work correctly in that case, this match is
752 # called, so the lfdirstate above may not be the correct one for
752 # called, so the lfdirstate above may not be the correct one for
753 # this invocation of match.
753 # this invocation of match.
754 lfdirstate = lfutil.openlfdirstate(mctx.repo().ui, mctx.repo(),
754 lfdirstate = lfutil.openlfdirstate(mctx.repo().ui, mctx.repo(),
755 False)
755 False)
756
756
757 wctx = repo[None]
757 wctx = repo[None]
758 matchfiles = []
758 matchfiles = []
759 for f in m._files:
759 for f in m._files:
760 standin = lfutil.standin(f)
760 standin = lfutil.standin(f)
761 if standin in ctx or standin in mctx:
761 if standin in ctx or standin in mctx:
762 matchfiles.append(standin)
762 matchfiles.append(standin)
763 elif standin in wctx or lfdirstate[f] == 'r':
763 elif standin in wctx or lfdirstate[f] == 'r':
764 continue
764 continue
765 else:
765 else:
766 matchfiles.append(f)
766 matchfiles.append(f)
767 m._files = matchfiles
767 m._files = matchfiles
768 m._fileset = set(m._files)
768 m._fileset = set(m._files)
769 origmatchfn = m.matchfn
769 origmatchfn = m.matchfn
770 def matchfn(f):
770 def matchfn(f):
771 lfile = lfutil.splitstandin(f)
771 lfile = lfutil.splitstandin(f)
772 if lfile is not None:
772 if lfile is not None:
773 return (origmatchfn(lfile) and
773 return (origmatchfn(lfile) and
774 (f in ctx or f in mctx))
774 (f in ctx or f in mctx))
775 return origmatchfn(f)
775 return origmatchfn(f)
776 m.matchfn = matchfn
776 m.matchfn = matchfn
777 return m
777 return m
778 with extensions.wrappedfunction(scmutil, 'match', overridematch):
778 with extensions.wrappedfunction(scmutil, 'match', overridematch):
779 orig(ui, repo, ctx, parents, *pats, **opts)
779 orig(ui, repo, ctx, parents, *pats, **opts)
780
780
781 newstandins = lfutil.getstandinsstate(repo)
781 newstandins = lfutil.getstandinsstate(repo)
782 filelist = lfutil.getlfilestoupdate(oldstandins, newstandins)
782 filelist = lfutil.getlfilestoupdate(oldstandins, newstandins)
783 # lfdirstate should be 'normallookup'-ed for updated files,
783 # lfdirstate should be 'normallookup'-ed for updated files,
784 # because reverting doesn't touch dirstate for 'normal' files
784 # because reverting doesn't touch dirstate for 'normal' files
785 # when target revision is explicitly specified: in such case,
785 # when target revision is explicitly specified: in such case,
786 # 'n' and valid timestamp in dirstate doesn't ensure 'clean'
786 # 'n' and valid timestamp in dirstate doesn't ensure 'clean'
787 # of target (standin) file.
787 # of target (standin) file.
788 lfcommands.updatelfiles(ui, repo, filelist, printmessage=False,
788 lfcommands.updatelfiles(ui, repo, filelist, printmessage=False,
789 normallookup=True)
789 normallookup=True)
790
790
791 # after pulling changesets, we need to take some extra care to get
791 # after pulling changesets, we need to take some extra care to get
792 # largefiles updated remotely
792 # largefiles updated remotely
793 @eh.wrapcommand('pull',
793 @eh.wrapcommand('pull',
794 opts=[('', 'all-largefiles', None,
794 opts=[('', 'all-largefiles', None,
795 _('download all pulled versions of largefiles (DEPRECATED)')),
795 _('download all pulled versions of largefiles (DEPRECATED)')),
796 ('', 'lfrev', [],
796 ('', 'lfrev', [],
797 _('download largefiles for these revisions'), _('REV'))])
797 _('download largefiles for these revisions'), _('REV'))])
798 def overridepull(orig, ui, repo, source=None, **opts):
798 def overridepull(orig, ui, repo, source=None, **opts):
799 revsprepull = len(repo)
799 revsprepull = len(repo)
800 if not source:
800 if not source:
801 source = 'default'
801 source = 'default'
802 repo.lfpullsource = source
802 repo.lfpullsource = source
803 result = orig(ui, repo, source, **opts)
803 result = orig(ui, repo, source, **opts)
804 revspostpull = len(repo)
804 revspostpull = len(repo)
805 lfrevs = opts.get(r'lfrev', [])
805 lfrevs = opts.get(r'lfrev', [])
806 if opts.get(r'all_largefiles'):
806 if opts.get(r'all_largefiles'):
807 lfrevs.append('pulled()')
807 lfrevs.append('pulled()')
808 if lfrevs and revspostpull > revsprepull:
808 if lfrevs and revspostpull > revsprepull:
809 numcached = 0
809 numcached = 0
810 repo.firstpulled = revsprepull # for pulled() revset expression
810 repo.firstpulled = revsprepull # for pulled() revset expression
811 try:
811 try:
812 for rev in scmutil.revrange(repo, lfrevs):
812 for rev in scmutil.revrange(repo, lfrevs):
813 ui.note(_('pulling largefiles for revision %d\n') % rev)
813 ui.note(_('pulling largefiles for revision %d\n') % rev)
814 (cached, missing) = lfcommands.cachelfiles(ui, repo, rev)
814 (cached, missing) = lfcommands.cachelfiles(ui, repo, rev)
815 numcached += len(cached)
815 numcached += len(cached)
816 finally:
816 finally:
817 del repo.firstpulled
817 del repo.firstpulled
818 ui.status(_("%d largefiles cached\n") % numcached)
818 ui.status(_("%d largefiles cached\n") % numcached)
819 return result
819 return result
820
820
821 @eh.wrapcommand('push',
821 @eh.wrapcommand('push',
822 opts=[('', 'lfrev', [],
822 opts=[('', 'lfrev', [],
823 _('upload largefiles for these revisions'), _('REV'))])
823 _('upload largefiles for these revisions'), _('REV'))])
824 def overridepush(orig, ui, repo, *args, **kwargs):
824 def overridepush(orig, ui, repo, *args, **kwargs):
825 """Override push command and store --lfrev parameters in opargs"""
825 """Override push command and store --lfrev parameters in opargs"""
826 lfrevs = kwargs.pop(r'lfrev', None)
826 lfrevs = kwargs.pop(r'lfrev', None)
827 if lfrevs:
827 if lfrevs:
828 opargs = kwargs.setdefault(r'opargs', {})
828 opargs = kwargs.setdefault(r'opargs', {})
829 opargs['lfrevs'] = scmutil.revrange(repo, lfrevs)
829 opargs['lfrevs'] = scmutil.revrange(repo, lfrevs)
830 return orig(ui, repo, *args, **kwargs)
830 return orig(ui, repo, *args, **kwargs)
831
831
832 @eh.wrapfunction(exchange, 'pushoperation')
832 @eh.wrapfunction(exchange, 'pushoperation')
833 def exchangepushoperation(orig, *args, **kwargs):
833 def exchangepushoperation(orig, *args, **kwargs):
834 """Override pushoperation constructor and store lfrevs parameter"""
834 """Override pushoperation constructor and store lfrevs parameter"""
835 lfrevs = kwargs.pop(r'lfrevs', None)
835 lfrevs = kwargs.pop(r'lfrevs', None)
836 pushop = orig(*args, **kwargs)
836 pushop = orig(*args, **kwargs)
837 pushop.lfrevs = lfrevs
837 pushop.lfrevs = lfrevs
838 return pushop
838 return pushop
839
839
840 @eh.revsetpredicate('pulled()')
840 @eh.revsetpredicate('pulled()')
841 def pulledrevsetsymbol(repo, subset, x):
841 def pulledrevsetsymbol(repo, subset, x):
842 """Changesets that just has been pulled.
842 """Changesets that just has been pulled.
843
843
844 Only available with largefiles from pull --lfrev expressions.
844 Only available with largefiles from pull --lfrev expressions.
845
845
846 .. container:: verbose
846 .. container:: verbose
847
847
848 Some examples:
848 Some examples:
849
849
850 - pull largefiles for all new changesets::
850 - pull largefiles for all new changesets::
851
851
852 hg pull -lfrev "pulled()"
852 hg pull -lfrev "pulled()"
853
853
854 - pull largefiles for all new branch heads::
854 - pull largefiles for all new branch heads::
855
855
856 hg pull -lfrev "head(pulled()) and not closed()"
856 hg pull -lfrev "head(pulled()) and not closed()"
857
857
858 """
858 """
859
859
860 try:
860 try:
861 firstpulled = repo.firstpulled
861 firstpulled = repo.firstpulled
862 except AttributeError:
862 except AttributeError:
863 raise error.Abort(_("pulled() only available in --lfrev"))
863 raise error.Abort(_("pulled() only available in --lfrev"))
864 return smartset.baseset([r for r in subset if r >= firstpulled])
864 return smartset.baseset([r for r in subset if r >= firstpulled])
865
865
866 @eh.wrapcommand('clone',
866 @eh.wrapcommand('clone',
867 opts=[('', 'all-largefiles', None,
867 opts=[('', 'all-largefiles', None,
868 _('download all versions of all largefiles'))])
868 _('download all versions of all largefiles'))])
869 def overrideclone(orig, ui, source, dest=None, **opts):
869 def overrideclone(orig, ui, source, dest=None, **opts):
870 d = dest
870 d = dest
871 if d is None:
871 if d is None:
872 d = hg.defaultdest(source)
872 d = hg.defaultdest(source)
873 if opts.get(r'all_largefiles') and not hg.islocal(d):
873 if opts.get(r'all_largefiles') and not hg.islocal(d):
874 raise error.Abort(_(
874 raise error.Abort(_(
875 '--all-largefiles is incompatible with non-local destination %s') %
875 '--all-largefiles is incompatible with non-local destination %s') %
876 d)
876 d)
877
877
878 return orig(ui, source, dest, **opts)
878 return orig(ui, source, dest, **opts)
879
879
880 @eh.wrapfunction(hg, 'clone')
880 @eh.wrapfunction(hg, 'clone')
881 def hgclone(orig, ui, opts, *args, **kwargs):
881 def hgclone(orig, ui, opts, *args, **kwargs):
882 result = orig(ui, opts, *args, **kwargs)
882 result = orig(ui, opts, *args, **kwargs)
883
883
884 if result is not None:
884 if result is not None:
885 sourcerepo, destrepo = result
885 sourcerepo, destrepo = result
886 repo = destrepo.local()
886 repo = destrepo.local()
887
887
888 # When cloning to a remote repo (like through SSH), no repo is available
888 # When cloning to a remote repo (like through SSH), no repo is available
889 # from the peer. Therefore the largefiles can't be downloaded and the
889 # from the peer. Therefore the largefiles can't be downloaded and the
890 # hgrc can't be updated.
890 # hgrc can't be updated.
891 if not repo:
891 if not repo:
892 return result
892 return result
893
893
894 # Caching is implicitly limited to 'rev' option, since the dest repo was
894 # Caching is implicitly limited to 'rev' option, since the dest repo was
895 # truncated at that point. The user may expect a download count with
895 # truncated at that point. The user may expect a download count with
896 # this option, so attempt whether or not this is a largefile repo.
896 # this option, so attempt whether or not this is a largefile repo.
897 if opts.get('all_largefiles'):
897 if opts.get('all_largefiles'):
898 success, missing = lfcommands.downloadlfiles(ui, repo, None)
898 success, missing = lfcommands.downloadlfiles(ui, repo, None)
899
899
900 if missing != 0:
900 if missing != 0:
901 return None
901 return None
902
902
903 return result
903 return result
904
904
905 @eh.wrapcommand('rebase', extension='rebase')
905 @eh.wrapcommand('rebase', extension='rebase')
906 def overriderebase(orig, ui, repo, **opts):
906 def overriderebase(orig, ui, repo, **opts):
907 if not util.safehasattr(repo, '_largefilesenabled'):
907 if not util.safehasattr(repo, '_largefilesenabled'):
908 return orig(ui, repo, **opts)
908 return orig(ui, repo, **opts)
909
909
910 resuming = opts.get(r'continue')
910 resuming = opts.get(r'continue')
911 repo._lfcommithooks.append(lfutil.automatedcommithook(resuming))
911 repo._lfcommithooks.append(lfutil.automatedcommithook(resuming))
912 repo._lfstatuswriters.append(lambda *msg, **opts: None)
912 repo._lfstatuswriters.append(lambda *msg, **opts: None)
913 try:
913 try:
914 return orig(ui, repo, **opts)
914 return orig(ui, repo, **opts)
915 finally:
915 finally:
916 repo._lfstatuswriters.pop()
916 repo._lfstatuswriters.pop()
917 repo._lfcommithooks.pop()
917 repo._lfcommithooks.pop()
918
918
919 @eh.wrapcommand('archive')
919 @eh.wrapcommand('archive')
920 def overridearchivecmd(orig, ui, repo, dest, **opts):
920 def overridearchivecmd(orig, ui, repo, dest, **opts):
921 repo.unfiltered().lfstatus = True
921 repo.unfiltered().lfstatus = True
922
922
923 try:
923 try:
924 return orig(ui, repo.unfiltered(), dest, **opts)
924 return orig(ui, repo.unfiltered(), dest, **opts)
925 finally:
925 finally:
926 repo.unfiltered().lfstatus = False
926 repo.unfiltered().lfstatus = False
927
927
928 @eh.wrapfunction(webcommands, 'archive')
928 @eh.wrapfunction(webcommands, 'archive')
929 def hgwebarchive(orig, web):
929 def hgwebarchive(orig, web):
930 web.repo.lfstatus = True
930 web.repo.lfstatus = True
931
931
932 try:
932 try:
933 return orig(web)
933 return orig(web)
934 finally:
934 finally:
935 web.repo.lfstatus = False
935 web.repo.lfstatus = False
936
936
937 @eh.wrapfunction(archival, 'archive')
937 @eh.wrapfunction(archival, 'archive')
938 def overridearchive(orig, repo, dest, node, kind, decode=True, match=None,
938 def overridearchive(orig, repo, dest, node, kind, decode=True, match=None,
939 prefix='', mtime=None, subrepos=None):
939 prefix='', mtime=None, subrepos=None):
940 # For some reason setting repo.lfstatus in hgwebarchive only changes the
940 # For some reason setting repo.lfstatus in hgwebarchive only changes the
941 # unfiltered repo's attr, so check that as well.
941 # unfiltered repo's attr, so check that as well.
942 if not repo.lfstatus and not repo.unfiltered().lfstatus:
942 if not repo.lfstatus and not repo.unfiltered().lfstatus:
943 return orig(repo, dest, node, kind, decode, match, prefix, mtime,
943 return orig(repo, dest, node, kind, decode, match, prefix, mtime,
944 subrepos)
944 subrepos)
945
945
946 # No need to lock because we are only reading history and
946 # No need to lock because we are only reading history and
947 # largefile caches, neither of which are modified.
947 # largefile caches, neither of which are modified.
948 if node is not None:
948 if node is not None:
949 lfcommands.cachelfiles(repo.ui, repo, node)
949 lfcommands.cachelfiles(repo.ui, repo, node)
950
950
951 if kind not in archival.archivers:
951 if kind not in archival.archivers:
952 raise error.Abort(_("unknown archive type '%s'") % kind)
952 raise error.Abort(_("unknown archive type '%s'") % kind)
953
953
954 ctx = repo[node]
954 ctx = repo[node]
955
955
956 if kind == 'files':
956 if kind == 'files':
957 if prefix:
957 if prefix:
958 raise error.Abort(
958 raise error.Abort(
959 _('cannot give prefix when archiving to files'))
959 _('cannot give prefix when archiving to files'))
960 else:
960 else:
961 prefix = archival.tidyprefix(dest, kind, prefix)
961 prefix = archival.tidyprefix(dest, kind, prefix)
962
962
963 def write(name, mode, islink, getdata):
963 def write(name, mode, islink, getdata):
964 if match and not match(name):
964 if match and not match(name):
965 return
965 return
966 data = getdata()
966 data = getdata()
967 if decode:
967 if decode:
968 data = repo.wwritedata(name, data)
968 data = repo.wwritedata(name, data)
969 archiver.addfile(prefix + name, mode, islink, data)
969 archiver.addfile(prefix + name, mode, islink, data)
970
970
971 archiver = archival.archivers[kind](dest, mtime or ctx.date()[0])
971 archiver = archival.archivers[kind](dest, mtime or ctx.date()[0])
972
972
973 if repo.ui.configbool("ui", "archivemeta"):
973 if repo.ui.configbool("ui", "archivemeta"):
974 write('.hg_archival.txt', 0o644, False,
974 write('.hg_archival.txt', 0o644, False,
975 lambda: archival.buildmetadata(ctx))
975 lambda: archival.buildmetadata(ctx))
976
976
977 for f in ctx:
977 for f in ctx:
978 ff = ctx.flags(f)
978 ff = ctx.flags(f)
979 getdata = ctx[f].data
979 getdata = ctx[f].data
980 lfile = lfutil.splitstandin(f)
980 lfile = lfutil.splitstandin(f)
981 if lfile is not None:
981 if lfile is not None:
982 if node is not None:
982 if node is not None:
983 path = lfutil.findfile(repo, getdata().strip())
983 path = lfutil.findfile(repo, getdata().strip())
984
984
985 if path is None:
985 if path is None:
986 raise error.Abort(
986 raise error.Abort(
987 _('largefile %s not found in repo store or system cache')
987 _('largefile %s not found in repo store or system cache')
988 % lfile)
988 % lfile)
989 else:
989 else:
990 path = lfile
990 path = lfile
991
991
992 f = lfile
992 f = lfile
993
993
994 getdata = lambda: util.readfile(path)
994 getdata = lambda: util.readfile(path)
995 write(f, 'x' in ff and 0o755 or 0o644, 'l' in ff, getdata)
995 write(f, 'x' in ff and 0o755 or 0o644, 'l' in ff, getdata)
996
996
997 if subrepos:
997 if subrepos:
998 for subpath in sorted(ctx.substate):
998 for subpath in sorted(ctx.substate):
999 sub = ctx.workingsub(subpath)
999 sub = ctx.workingsub(subpath)
1000 submatch = matchmod.subdirmatcher(subpath, match)
1000 submatch = matchmod.subdirmatcher(subpath, match)
1001 subprefix = prefix + subpath + '/'
1001 subprefix = prefix + subpath + '/'
1002 sub._repo.lfstatus = True
1002 sub._repo.lfstatus = True
1003 sub.archive(archiver, subprefix, submatch)
1003 sub.archive(archiver, subprefix, submatch)
1004
1004
1005 archiver.done()
1005 archiver.done()
1006
1006
1007 @eh.wrapfunction(subrepo.hgsubrepo, 'archive')
1007 @eh.wrapfunction(subrepo.hgsubrepo, 'archive')
1008 def hgsubrepoarchive(orig, repo, archiver, prefix, match=None, decode=True):
1008 def hgsubrepoarchive(orig, repo, archiver, prefix, match=None, decode=True):
1009 lfenabled = util.safehasattr(repo._repo, '_largefilesenabled')
1009 lfenabled = util.safehasattr(repo._repo, '_largefilesenabled')
1010 if not lfenabled or not repo._repo.lfstatus:
1010 if not lfenabled or not repo._repo.lfstatus:
1011 return orig(repo, archiver, prefix, match, decode)
1011 return orig(repo, archiver, prefix, match, decode)
1012
1012
1013 repo._get(repo._state + ('hg',))
1013 repo._get(repo._state + ('hg',))
1014 rev = repo._state[1]
1014 rev = repo._state[1]
1015 ctx = repo._repo[rev]
1015 ctx = repo._repo[rev]
1016
1016
1017 if ctx.node() is not None:
1017 if ctx.node() is not None:
1018 lfcommands.cachelfiles(repo.ui, repo._repo, ctx.node())
1018 lfcommands.cachelfiles(repo.ui, repo._repo, ctx.node())
1019
1019
1020 def write(name, mode, islink, getdata):
1020 def write(name, mode, islink, getdata):
1021 # At this point, the standin has been replaced with the largefile name,
1021 # At this point, the standin has been replaced with the largefile name,
1022 # so the normal matcher works here without the lfutil variants.
1022 # so the normal matcher works here without the lfutil variants.
1023 if match and not match(f):
1023 if match and not match(f):
1024 return
1024 return
1025 data = getdata()
1025 data = getdata()
1026 if decode:
1026 if decode:
1027 data = repo._repo.wwritedata(name, data)
1027 data = repo._repo.wwritedata(name, data)
1028
1028
1029 archiver.addfile(prefix + name, mode, islink, data)
1029 archiver.addfile(prefix + name, mode, islink, data)
1030
1030
1031 for f in ctx:
1031 for f in ctx:
1032 ff = ctx.flags(f)
1032 ff = ctx.flags(f)
1033 getdata = ctx[f].data
1033 getdata = ctx[f].data
1034 lfile = lfutil.splitstandin(f)
1034 lfile = lfutil.splitstandin(f)
1035 if lfile is not None:
1035 if lfile is not None:
1036 if ctx.node() is not None:
1036 if ctx.node() is not None:
1037 path = lfutil.findfile(repo._repo, getdata().strip())
1037 path = lfutil.findfile(repo._repo, getdata().strip())
1038
1038
1039 if path is None:
1039 if path is None:
1040 raise error.Abort(
1040 raise error.Abort(
1041 _('largefile %s not found in repo store or system cache')
1041 _('largefile %s not found in repo store or system cache')
1042 % lfile)
1042 % lfile)
1043 else:
1043 else:
1044 path = lfile
1044 path = lfile
1045
1045
1046 f = lfile
1046 f = lfile
1047
1047
1048 getdata = lambda: util.readfile(os.path.join(prefix, path))
1048 getdata = lambda: util.readfile(os.path.join(prefix, path))
1049
1049
1050 write(f, 'x' in ff and 0o755 or 0o644, 'l' in ff, getdata)
1050 write(f, 'x' in ff and 0o755 or 0o644, 'l' in ff, getdata)
1051
1051
1052 for subpath in sorted(ctx.substate):
1052 for subpath in sorted(ctx.substate):
1053 sub = ctx.workingsub(subpath)
1053 sub = ctx.workingsub(subpath)
1054 submatch = matchmod.subdirmatcher(subpath, match)
1054 submatch = matchmod.subdirmatcher(subpath, match)
1055 subprefix = prefix + subpath + '/'
1055 subprefix = prefix + subpath + '/'
1056 sub._repo.lfstatus = True
1056 sub._repo.lfstatus = True
1057 sub.archive(archiver, subprefix, submatch, decode)
1057 sub.archive(archiver, subprefix, submatch, decode)
1058
1058
1059 # If a largefile is modified, the change is not reflected in its
1059 # If a largefile is modified, the change is not reflected in its
1060 # standin until a commit. cmdutil.bailifchanged() raises an exception
1060 # standin until a commit. cmdutil.bailifchanged() raises an exception
1061 # if the repo has uncommitted changes. Wrap it to also check if
1061 # if the repo has uncommitted changes. Wrap it to also check if
1062 # largefiles were changed. This is used by bisect, backout and fetch.
1062 # largefiles were changed. This is used by bisect, backout and fetch.
1063 @eh.wrapfunction(cmdutil, 'bailifchanged')
1063 @eh.wrapfunction(cmdutil, 'bailifchanged')
1064 def overridebailifchanged(orig, repo, *args, **kwargs):
1064 def overridebailifchanged(orig, repo, *args, **kwargs):
1065 orig(repo, *args, **kwargs)
1065 orig(repo, *args, **kwargs)
1066 repo.lfstatus = True
1066 repo.lfstatus = True
1067 s = repo.status()
1067 s = repo.status()
1068 repo.lfstatus = False
1068 repo.lfstatus = False
1069 if s.modified or s.added or s.removed or s.deleted:
1069 if s.modified or s.added or s.removed or s.deleted:
1070 raise error.Abort(_('uncommitted changes'))
1070 raise error.Abort(_('uncommitted changes'))
1071
1071
1072 @eh.wrapfunction(cmdutil, 'postcommitstatus')
1072 @eh.wrapfunction(cmdutil, 'postcommitstatus')
1073 def postcommitstatus(orig, repo, *args, **kwargs):
1073 def postcommitstatus(orig, repo, *args, **kwargs):
1074 repo.lfstatus = True
1074 repo.lfstatus = True
1075 try:
1075 try:
1076 return orig(repo, *args, **kwargs)
1076 return orig(repo, *args, **kwargs)
1077 finally:
1077 finally:
1078 repo.lfstatus = False
1078 repo.lfstatus = False
1079
1079
1080 @eh.wrapfunction(cmdutil, 'forget')
1080 @eh.wrapfunction(cmdutil, 'forget')
1081 def cmdutilforget(orig, ui, repo, match, prefix, explicitonly, dryrun,
1081 def cmdutilforget(orig, ui, repo, match, prefix, explicitonly, dryrun,
1082 interactive):
1082 interactive):
1083 normalmatcher = composenormalfilematcher(match, repo[None].manifest())
1083 normalmatcher = composenormalfilematcher(match, repo[None].manifest())
1084 bad, forgot = orig(ui, repo, normalmatcher, prefix, explicitonly, dryrun,
1084 bad, forgot = orig(ui, repo, normalmatcher, prefix, explicitonly, dryrun,
1085 interactive)
1085 interactive)
1086 m = composelargefilematcher(match, repo[None].manifest())
1086 m = composelargefilematcher(match, repo[None].manifest())
1087
1087
1088 try:
1088 try:
1089 repo.lfstatus = True
1089 repo.lfstatus = True
1090 s = repo.status(match=m, clean=True)
1090 s = repo.status(match=m, clean=True)
1091 finally:
1091 finally:
1092 repo.lfstatus = False
1092 repo.lfstatus = False
1093 manifest = repo[None].manifest()
1093 manifest = repo[None].manifest()
1094 forget = sorted(s.modified + s.added + s.deleted + s.clean)
1094 forget = sorted(s.modified + s.added + s.deleted + s.clean)
1095 forget = [f for f in forget if lfutil.standin(f) in manifest]
1095 forget = [f for f in forget if lfutil.standin(f) in manifest]
1096
1096
1097 for f in forget:
1097 for f in forget:
1098 fstandin = lfutil.standin(f)
1098 fstandin = lfutil.standin(f)
1099 if fstandin not in repo.dirstate and not repo.wvfs.isdir(fstandin):
1099 if fstandin not in repo.dirstate and not repo.wvfs.isdir(fstandin):
1100 ui.warn(_('not removing %s: file is already untracked\n')
1100 ui.warn(_('not removing %s: file is already untracked\n')
1101 % m.rel(f))
1101 % m.rel(f))
1102 bad.append(f)
1102 bad.append(f)
1103
1103
1104 for f in forget:
1104 for f in forget:
1105 if ui.verbose or not m.exact(f):
1105 if ui.verbose or not m.exact(f):
1106 ui.status(_('removing %s\n') % m.rel(f))
1106 ui.status(_('removing %s\n') % m.rel(f))
1107
1107
1108 # Need to lock because standin files are deleted then removed from the
1108 # Need to lock because standin files are deleted then removed from the
1109 # repository and we could race in-between.
1109 # repository and we could race in-between.
1110 with repo.wlock():
1110 with repo.wlock():
1111 lfdirstate = lfutil.openlfdirstate(ui, repo)
1111 lfdirstate = lfutil.openlfdirstate(ui, repo)
1112 for f in forget:
1112 for f in forget:
1113 if lfdirstate[f] == 'a':
1113 if lfdirstate[f] == 'a':
1114 lfdirstate.drop(f)
1114 lfdirstate.drop(f)
1115 else:
1115 else:
1116 lfdirstate.remove(f)
1116 lfdirstate.remove(f)
1117 lfdirstate.write()
1117 lfdirstate.write()
1118 standins = [lfutil.standin(f) for f in forget]
1118 standins = [lfutil.standin(f) for f in forget]
1119 for f in standins:
1119 for f in standins:
1120 repo.wvfs.unlinkpath(f, ignoremissing=True)
1120 repo.wvfs.unlinkpath(f, ignoremissing=True)
1121 rejected = repo[None].forget(standins)
1121 rejected = repo[None].forget(standins)
1122
1122
1123 bad.extend(f for f in rejected if f in m.files())
1123 bad.extend(f for f in rejected if f in m.files())
1124 forgot.extend(f for f in forget if f not in rejected)
1124 forgot.extend(f for f in forget if f not in rejected)
1125 return bad, forgot
1125 return bad, forgot
1126
1126
1127 def _getoutgoings(repo, other, missing, addfunc):
1127 def _getoutgoings(repo, other, missing, addfunc):
1128 """get pairs of filename and largefile hash in outgoing revisions
1128 """get pairs of filename and largefile hash in outgoing revisions
1129 in 'missing'.
1129 in 'missing'.
1130
1130
1131 largefiles already existing on 'other' repository are ignored.
1131 largefiles already existing on 'other' repository are ignored.
1132
1132
1133 'addfunc' is invoked with each unique pairs of filename and
1133 'addfunc' is invoked with each unique pairs of filename and
1134 largefile hash value.
1134 largefile hash value.
1135 """
1135 """
1136 knowns = set()
1136 knowns = set()
1137 lfhashes = set()
1137 lfhashes = set()
1138 def dedup(fn, lfhash):
1138 def dedup(fn, lfhash):
1139 k = (fn, lfhash)
1139 k = (fn, lfhash)
1140 if k not in knowns:
1140 if k not in knowns:
1141 knowns.add(k)
1141 knowns.add(k)
1142 lfhashes.add(lfhash)
1142 lfhashes.add(lfhash)
1143 lfutil.getlfilestoupload(repo, missing, dedup)
1143 lfutil.getlfilestoupload(repo, missing, dedup)
1144 if lfhashes:
1144 if lfhashes:
1145 lfexists = storefactory.openstore(repo, other).exists(lfhashes)
1145 lfexists = storefactory.openstore(repo, other).exists(lfhashes)
1146 for fn, lfhash in knowns:
1146 for fn, lfhash in knowns:
1147 if not lfexists[lfhash]: # lfhash doesn't exist on "other"
1147 if not lfexists[lfhash]: # lfhash doesn't exist on "other"
1148 addfunc(fn, lfhash)
1148 addfunc(fn, lfhash)
1149
1149
1150 def outgoinghook(ui, repo, other, opts, missing):
1150 def outgoinghook(ui, repo, other, opts, missing):
1151 if opts.pop('large', None):
1151 if opts.pop('large', None):
1152 lfhashes = set()
1152 lfhashes = set()
1153 if ui.debugflag:
1153 if ui.debugflag:
1154 toupload = {}
1154 toupload = {}
1155 def addfunc(fn, lfhash):
1155 def addfunc(fn, lfhash):
1156 if fn not in toupload:
1156 if fn not in toupload:
1157 toupload[fn] = []
1157 toupload[fn] = []
1158 toupload[fn].append(lfhash)
1158 toupload[fn].append(lfhash)
1159 lfhashes.add(lfhash)
1159 lfhashes.add(lfhash)
1160 def showhashes(fn):
1160 def showhashes(fn):
1161 for lfhash in sorted(toupload[fn]):
1161 for lfhash in sorted(toupload[fn]):
1162 ui.debug(' %s\n' % (lfhash))
1162 ui.debug(' %s\n' % (lfhash))
1163 else:
1163 else:
1164 toupload = set()
1164 toupload = set()
1165 def addfunc(fn, lfhash):
1165 def addfunc(fn, lfhash):
1166 toupload.add(fn)
1166 toupload.add(fn)
1167 lfhashes.add(lfhash)
1167 lfhashes.add(lfhash)
1168 def showhashes(fn):
1168 def showhashes(fn):
1169 pass
1169 pass
1170 _getoutgoings(repo, other, missing, addfunc)
1170 _getoutgoings(repo, other, missing, addfunc)
1171
1171
1172 if not toupload:
1172 if not toupload:
1173 ui.status(_('largefiles: no files to upload\n'))
1173 ui.status(_('largefiles: no files to upload\n'))
1174 else:
1174 else:
1175 ui.status(_('largefiles to upload (%d entities):\n')
1175 ui.status(_('largefiles to upload (%d entities):\n')
1176 % (len(lfhashes)))
1176 % (len(lfhashes)))
1177 for file in sorted(toupload):
1177 for file in sorted(toupload):
1178 ui.status(lfutil.splitstandin(file) + '\n')
1178 ui.status(lfutil.splitstandin(file) + '\n')
1179 showhashes(file)
1179 showhashes(file)
1180 ui.status('\n')
1180 ui.status('\n')
1181
1181
1182 @eh.wrapcommand('outgoing',
1182 @eh.wrapcommand('outgoing',
1183 opts=[('', 'large', None, _('display outgoing largefiles'))])
1183 opts=[('', 'large', None, _('display outgoing largefiles'))])
1184 def _outgoingcmd(orig, *args, **kwargs):
1184 def _outgoingcmd(orig, *args, **kwargs):
1185 # Nothing to do here other than add the extra help option- the hook above
1185 # Nothing to do here other than add the extra help option- the hook above
1186 # processes it.
1186 # processes it.
1187 return orig(*args, **kwargs)
1187 return orig(*args, **kwargs)
1188
1188
1189 def summaryremotehook(ui, repo, opts, changes):
1189 def summaryremotehook(ui, repo, opts, changes):
1190 largeopt = opts.get('large', False)
1190 largeopt = opts.get('large', False)
1191 if changes is None:
1191 if changes is None:
1192 if largeopt:
1192 if largeopt:
1193 return (False, True) # only outgoing check is needed
1193 return (False, True) # only outgoing check is needed
1194 else:
1194 else:
1195 return (False, False)
1195 return (False, False)
1196 elif largeopt:
1196 elif largeopt:
1197 url, branch, peer, outgoing = changes[1]
1197 url, branch, peer, outgoing = changes[1]
1198 if peer is None:
1198 if peer is None:
1199 # i18n: column positioning for "hg summary"
1199 # i18n: column positioning for "hg summary"
1200 ui.status(_('largefiles: (no remote repo)\n'))
1200 ui.status(_('largefiles: (no remote repo)\n'))
1201 return
1201 return
1202
1202
1203 toupload = set()
1203 toupload = set()
1204 lfhashes = set()
1204 lfhashes = set()
1205 def addfunc(fn, lfhash):
1205 def addfunc(fn, lfhash):
1206 toupload.add(fn)
1206 toupload.add(fn)
1207 lfhashes.add(lfhash)
1207 lfhashes.add(lfhash)
1208 _getoutgoings(repo, peer, outgoing.missing, addfunc)
1208 _getoutgoings(repo, peer, outgoing.missing, addfunc)
1209
1209
1210 if not toupload:
1210 if not toupload:
1211 # i18n: column positioning for "hg summary"
1211 # i18n: column positioning for "hg summary"
1212 ui.status(_('largefiles: (no files to upload)\n'))
1212 ui.status(_('largefiles: (no files to upload)\n'))
1213 else:
1213 else:
1214 # i18n: column positioning for "hg summary"
1214 # i18n: column positioning for "hg summary"
1215 ui.status(_('largefiles: %d entities for %d files to upload\n')
1215 ui.status(_('largefiles: %d entities for %d files to upload\n')
1216 % (len(lfhashes), len(toupload)))
1216 % (len(lfhashes), len(toupload)))
1217
1217
1218 @eh.wrapcommand('summary',
1218 @eh.wrapcommand('summary',
1219 opts=[('', 'large', None, _('display outgoing largefiles'))])
1219 opts=[('', 'large', None, _('display outgoing largefiles'))])
1220 def overridesummary(orig, ui, repo, *pats, **opts):
1220 def overridesummary(orig, ui, repo, *pats, **opts):
1221 try:
1221 try:
1222 repo.lfstatus = True
1222 repo.lfstatus = True
1223 orig(ui, repo, *pats, **opts)
1223 orig(ui, repo, *pats, **opts)
1224 finally:
1224 finally:
1225 repo.lfstatus = False
1225 repo.lfstatus = False
1226
1226
1227 @eh.wrapfunction(scmutil, 'addremove')
1227 @eh.wrapfunction(scmutil, 'addremove')
1228 def scmutiladdremove(orig, repo, matcher, prefix, opts=None):
1228 def scmutiladdremove(orig, repo, matcher, prefix, opts=None):
1229 if opts is None:
1229 if opts is None:
1230 opts = {}
1230 opts = {}
1231 if not lfutil.islfilesrepo(repo):
1231 if not lfutil.islfilesrepo(repo):
1232 return orig(repo, matcher, prefix, opts)
1232 return orig(repo, matcher, prefix, opts)
1233 # Get the list of missing largefiles so we can remove them
1233 # Get the list of missing largefiles so we can remove them
1234 lfdirstate = lfutil.openlfdirstate(repo.ui, repo)
1234 lfdirstate = lfutil.openlfdirstate(repo.ui, repo)
1235 unsure, s = lfdirstate.status(matchmod.always(repo.root, repo.getcwd()),
1235 unsure, s = lfdirstate.status(matchmod.always(repo.root, repo.getcwd()),
1236 subrepos=[], ignored=False, clean=False,
1236 subrepos=[], ignored=False, clean=False,
1237 unknown=False)
1237 unknown=False)
1238
1238
1239 # Call into the normal remove code, but the removing of the standin, we want
1239 # Call into the normal remove code, but the removing of the standin, we want
1240 # to have handled by original addremove. Monkey patching here makes sure
1240 # to have handled by original addremove. Monkey patching here makes sure
1241 # we don't remove the standin in the largefiles code, preventing a very
1241 # we don't remove the standin in the largefiles code, preventing a very
1242 # confused state later.
1242 # confused state later.
1243 if s.deleted:
1243 if s.deleted:
1244 m = copy.copy(matcher)
1244 m = copy.copy(matcher)
1245
1245
1246 # The m._files and m._map attributes are not changed to the deleted list
1246 # The m._files and m._map attributes are not changed to the deleted list
1247 # because that affects the m.exact() test, which in turn governs whether
1247 # because that affects the m.exact() test, which in turn governs whether
1248 # or not the file name is printed, and how. Simply limit the original
1248 # or not the file name is printed, and how. Simply limit the original
1249 # matches to those in the deleted status list.
1249 # matches to those in the deleted status list.
1250 matchfn = m.matchfn
1250 matchfn = m.matchfn
1251 m.matchfn = lambda f: f in s.deleted and matchfn(f)
1251 m.matchfn = lambda f: f in s.deleted and matchfn(f)
1252
1252
1253 removelargefiles(repo.ui, repo, True, m, opts.get('dry_run'),
1253 removelargefiles(repo.ui, repo, True, m, opts.get('dry_run'),
1254 **pycompat.strkwargs(opts))
1254 **pycompat.strkwargs(opts))
1255 # Call into the normal add code, and any files that *should* be added as
1255 # Call into the normal add code, and any files that *should* be added as
1256 # largefiles will be
1256 # largefiles will be
1257 added, bad = addlargefiles(repo.ui, repo, True, matcher,
1257 added, bad = addlargefiles(repo.ui, repo, True, matcher,
1258 **pycompat.strkwargs(opts))
1258 **pycompat.strkwargs(opts))
1259 # Now that we've handled largefiles, hand off to the original addremove
1259 # Now that we've handled largefiles, hand off to the original addremove
1260 # function to take care of the rest. Make sure it doesn't do anything with
1260 # function to take care of the rest. Make sure it doesn't do anything with
1261 # largefiles by passing a matcher that will ignore them.
1261 # largefiles by passing a matcher that will ignore them.
1262 matcher = composenormalfilematcher(matcher, repo[None].manifest(), added)
1262 matcher = composenormalfilematcher(matcher, repo[None].manifest(), added)
1263 return orig(repo, matcher, prefix, opts)
1263 return orig(repo, matcher, prefix, opts)
1264
1264
1265 # Calling purge with --all will cause the largefiles to be deleted.
1265 # Calling purge with --all will cause the largefiles to be deleted.
1266 # Override repo.status to prevent this from happening.
1266 # Override repo.status to prevent this from happening.
1267 @eh.wrapcommand('purge', extension='purge')
1267 @eh.wrapcommand('purge', extension='purge')
1268 def overridepurge(orig, ui, repo, *dirs, **opts):
1268 def overridepurge(orig, ui, repo, *dirs, **opts):
1269 # XXX Monkey patching a repoview will not work. The assigned attribute will
1269 # XXX Monkey patching a repoview will not work. The assigned attribute will
1270 # be set on the unfiltered repo, but we will only lookup attributes in the
1270 # be set on the unfiltered repo, but we will only lookup attributes in the
1271 # unfiltered repo if the lookup in the repoview object itself fails. As the
1271 # unfiltered repo if the lookup in the repoview object itself fails. As the
1272 # monkey patched method exists on the repoview class the lookup will not
1272 # monkey patched method exists on the repoview class the lookup will not
1273 # fail. As a result, the original version will shadow the monkey patched
1273 # fail. As a result, the original version will shadow the monkey patched
1274 # one, defeating the monkey patch.
1274 # one, defeating the monkey patch.
1275 #
1275 #
1276 # As a work around we use an unfiltered repo here. We should do something
1276 # As a work around we use an unfiltered repo here. We should do something
1277 # cleaner instead.
1277 # cleaner instead.
1278 repo = repo.unfiltered()
1278 repo = repo.unfiltered()
1279 oldstatus = repo.status
1279 oldstatus = repo.status
1280 def overridestatus(node1='.', node2=None, match=None, ignored=False,
1280 def overridestatus(node1='.', node2=None, match=None, ignored=False,
1281 clean=False, unknown=False, listsubrepos=False):
1281 clean=False, unknown=False, listsubrepos=False):
1282 r = oldstatus(node1, node2, match, ignored, clean, unknown,
1282 r = oldstatus(node1, node2, match, ignored, clean, unknown,
1283 listsubrepos)
1283 listsubrepos)
1284 lfdirstate = lfutil.openlfdirstate(ui, repo)
1284 lfdirstate = lfutil.openlfdirstate(ui, repo)
1285 unknown = [f for f in r.unknown if lfdirstate[f] == '?']
1285 unknown = [f for f in r.unknown if lfdirstate[f] == '?']
1286 ignored = [f for f in r.ignored if lfdirstate[f] == '?']
1286 ignored = [f for f in r.ignored if lfdirstate[f] == '?']
1287 return scmutil.status(r.modified, r.added, r.removed, r.deleted,
1287 return scmutil.status(r.modified, r.added, r.removed, r.deleted,
1288 unknown, ignored, r.clean)
1288 unknown, ignored, r.clean)
1289 repo.status = overridestatus
1289 repo.status = overridestatus
1290 orig(ui, repo, *dirs, **opts)
1290 orig(ui, repo, *dirs, **opts)
1291 repo.status = oldstatus
1291 repo.status = oldstatus
1292
1292
1293 @eh.wrapcommand('rollback')
1293 @eh.wrapcommand('rollback')
1294 def overriderollback(orig, ui, repo, **opts):
1294 def overriderollback(orig, ui, repo, **opts):
1295 with repo.wlock():
1295 with repo.wlock():
1296 before = repo.dirstate.parents()
1296 before = repo.dirstate.parents()
1297 orphans = set(f for f in repo.dirstate
1297 orphans = set(f for f in repo.dirstate
1298 if lfutil.isstandin(f) and repo.dirstate[f] != 'r')
1298 if lfutil.isstandin(f) and repo.dirstate[f] != 'r')
1299 result = orig(ui, repo, **opts)
1299 result = orig(ui, repo, **opts)
1300 after = repo.dirstate.parents()
1300 after = repo.dirstate.parents()
1301 if before == after:
1301 if before == after:
1302 return result # no need to restore standins
1302 return result # no need to restore standins
1303
1303
1304 pctx = repo['.']
1304 pctx = repo['.']
1305 for f in repo.dirstate:
1305 for f in repo.dirstate:
1306 if lfutil.isstandin(f):
1306 if lfutil.isstandin(f):
1307 orphans.discard(f)
1307 orphans.discard(f)
1308 if repo.dirstate[f] == 'r':
1308 if repo.dirstate[f] == 'r':
1309 repo.wvfs.unlinkpath(f, ignoremissing=True)
1309 repo.wvfs.unlinkpath(f, ignoremissing=True)
1310 elif f in pctx:
1310 elif f in pctx:
1311 fctx = pctx[f]
1311 fctx = pctx[f]
1312 repo.wwrite(f, fctx.data(), fctx.flags())
1312 repo.wwrite(f, fctx.data(), fctx.flags())
1313 else:
1313 else:
1314 # content of standin is not so important in 'a',
1314 # content of standin is not so important in 'a',
1315 # 'm' or 'n' (coming from the 2nd parent) cases
1315 # 'm' or 'n' (coming from the 2nd parent) cases
1316 lfutil.writestandin(repo, f, '', False)
1316 lfutil.writestandin(repo, f, '', False)
1317 for standin in orphans:
1317 for standin in orphans:
1318 repo.wvfs.unlinkpath(standin, ignoremissing=True)
1318 repo.wvfs.unlinkpath(standin, ignoremissing=True)
1319
1319
1320 lfdirstate = lfutil.openlfdirstate(ui, repo)
1320 lfdirstate = lfutil.openlfdirstate(ui, repo)
1321 orphans = set(lfdirstate)
1321 orphans = set(lfdirstate)
1322 lfiles = lfutil.listlfiles(repo)
1322 lfiles = lfutil.listlfiles(repo)
1323 for file in lfiles:
1323 for file in lfiles:
1324 lfutil.synclfdirstate(repo, lfdirstate, file, True)
1324 lfutil.synclfdirstate(repo, lfdirstate, file, True)
1325 orphans.discard(file)
1325 orphans.discard(file)
1326 for lfile in orphans:
1326 for lfile in orphans:
1327 lfdirstate.drop(lfile)
1327 lfdirstate.drop(lfile)
1328 lfdirstate.write()
1328 lfdirstate.write()
1329 return result
1329 return result
1330
1330
1331 @eh.wrapcommand('transplant', extension='transplant')
1331 @eh.wrapcommand('transplant', extension='transplant')
1332 def overridetransplant(orig, ui, repo, *revs, **opts):
1332 def overridetransplant(orig, ui, repo, *revs, **opts):
1333 resuming = opts.get(r'continue')
1333 resuming = opts.get(r'continue')
1334 repo._lfcommithooks.append(lfutil.automatedcommithook(resuming))
1334 repo._lfcommithooks.append(lfutil.automatedcommithook(resuming))
1335 repo._lfstatuswriters.append(lambda *msg, **opts: None)
1335 repo._lfstatuswriters.append(lambda *msg, **opts: None)
1336 try:
1336 try:
1337 result = orig(ui, repo, *revs, **opts)
1337 result = orig(ui, repo, *revs, **opts)
1338 finally:
1338 finally:
1339 repo._lfstatuswriters.pop()
1339 repo._lfstatuswriters.pop()
1340 repo._lfcommithooks.pop()
1340 repo._lfcommithooks.pop()
1341 return result
1341 return result
1342
1342
1343 @eh.wrapcommand('cat')
1343 @eh.wrapcommand('cat')
1344 def overridecat(orig, ui, repo, file1, *pats, **opts):
1344 def overridecat(orig, ui, repo, file1, *pats, **opts):
1345 opts = pycompat.byteskwargs(opts)
1345 opts = pycompat.byteskwargs(opts)
1346 ctx = scmutil.revsingle(repo, opts.get('rev'))
1346 ctx = scmutil.revsingle(repo, opts.get('rev'))
1347 err = 1
1347 err = 1
1348 notbad = set()
1348 notbad = set()
1349 m = scmutil.match(ctx, (file1,) + pats, opts)
1349 m = scmutil.match(ctx, (file1,) + pats, opts)
1350 origmatchfn = m.matchfn
1350 origmatchfn = m.matchfn
1351 def lfmatchfn(f):
1351 def lfmatchfn(f):
1352 if origmatchfn(f):
1352 if origmatchfn(f):
1353 return True
1353 return True
1354 lf = lfutil.splitstandin(f)
1354 lf = lfutil.splitstandin(f)
1355 if lf is None:
1355 if lf is None:
1356 return False
1356 return False
1357 notbad.add(lf)
1357 notbad.add(lf)
1358 return origmatchfn(lf)
1358 return origmatchfn(lf)
1359 m.matchfn = lfmatchfn
1359 m.matchfn = lfmatchfn
1360 origbadfn = m.bad
1360 origbadfn = m.bad
1361 def lfbadfn(f, msg):
1361 def lfbadfn(f, msg):
1362 if not f in notbad:
1362 if not f in notbad:
1363 origbadfn(f, msg)
1363 origbadfn(f, msg)
1364 m.bad = lfbadfn
1364 m.bad = lfbadfn
1365
1365
1366 origvisitdirfn = m.visitdir
1366 origvisitdirfn = m.visitdir
1367 def lfvisitdirfn(dir):
1367 def lfvisitdirfn(dir):
1368 if dir == lfutil.shortname:
1368 if dir == lfutil.shortname:
1369 return True
1369 return True
1370 ret = origvisitdirfn(dir)
1370 ret = origvisitdirfn(dir)
1371 if ret:
1371 if ret:
1372 return ret
1372 return ret
1373 lf = lfutil.splitstandin(dir)
1373 lf = lfutil.splitstandin(dir)
1374 if lf is None:
1374 if lf is None:
1375 return False
1375 return False
1376 return origvisitdirfn(lf)
1376 return origvisitdirfn(lf)
1377 m.visitdir = lfvisitdirfn
1377 m.visitdir = lfvisitdirfn
1378
1378
1379 for f in ctx.walk(m):
1379 for f in ctx.walk(m):
1380 with cmdutil.makefileobj(ctx, opts.get('output'), pathname=f) as fp:
1380 with cmdutil.makefileobj(ctx, opts.get('output'), pathname=f) as fp:
1381 lf = lfutil.splitstandin(f)
1381 lf = lfutil.splitstandin(f)
1382 if lf is None or origmatchfn(f):
1382 if lf is None or origmatchfn(f):
1383 # duplicating unreachable code from commands.cat
1383 # duplicating unreachable code from commands.cat
1384 data = ctx[f].data()
1384 data = ctx[f].data()
1385 if opts.get('decode'):
1385 if opts.get('decode'):
1386 data = repo.wwritedata(f, data)
1386 data = repo.wwritedata(f, data)
1387 fp.write(data)
1387 fp.write(data)
1388 else:
1388 else:
1389 hash = lfutil.readasstandin(ctx[f])
1389 hash = lfutil.readasstandin(ctx[f])
1390 if not lfutil.inusercache(repo.ui, hash):
1390 if not lfutil.inusercache(repo.ui, hash):
1391 store = storefactory.openstore(repo)
1391 store = storefactory.openstore(repo)
1392 success, missing = store.get([(lf, hash)])
1392 success, missing = store.get([(lf, hash)])
1393 if len(success) != 1:
1393 if len(success) != 1:
1394 raise error.Abort(
1394 raise error.Abort(
1395 _('largefile %s is not in cache and could not be '
1395 _('largefile %s is not in cache and could not be '
1396 'downloaded') % lf)
1396 'downloaded') % lf)
1397 path = lfutil.usercachepath(repo.ui, hash)
1397 path = lfutil.usercachepath(repo.ui, hash)
1398 with open(path, "rb") as fpin:
1398 with open(path, "rb") as fpin:
1399 for chunk in util.filechunkiter(fpin):
1399 for chunk in util.filechunkiter(fpin):
1400 fp.write(chunk)
1400 fp.write(chunk)
1401 err = 0
1401 err = 0
1402 return err
1402 return err
1403
1403
1404 @eh.wrapfunction(merge, 'update')
1404 @eh.wrapfunction(merge, 'update')
1405 def mergeupdate(orig, repo, node, branchmerge, force,
1405 def mergeupdate(orig, repo, node, branchmerge, force,
1406 *args, **kwargs):
1406 *args, **kwargs):
1407 matcher = kwargs.get(r'matcher', None)
1407 matcher = kwargs.get(r'matcher', None)
1408 # note if this is a partial update
1408 # note if this is a partial update
1409 partial = matcher and not matcher.always()
1409 partial = matcher and not matcher.always()
1410 with repo.wlock():
1410 with repo.wlock():
1411 # branch | | |
1411 # branch | | |
1412 # merge | force | partial | action
1412 # merge | force | partial | action
1413 # -------+-------+---------+--------------
1413 # -------+-------+---------+--------------
1414 # x | x | x | linear-merge
1414 # x | x | x | linear-merge
1415 # o | x | x | branch-merge
1415 # o | x | x | branch-merge
1416 # x | o | x | overwrite (as clean update)
1416 # x | o | x | overwrite (as clean update)
1417 # o | o | x | force-branch-merge (*1)
1417 # o | o | x | force-branch-merge (*1)
1418 # x | x | o | (*)
1418 # x | x | o | (*)
1419 # o | x | o | (*)
1419 # o | x | o | (*)
1420 # x | o | o | overwrite (as revert)
1420 # x | o | o | overwrite (as revert)
1421 # o | o | o | (*)
1421 # o | o | o | (*)
1422 #
1422 #
1423 # (*) don't care
1423 # (*) don't care
1424 # (*1) deprecated, but used internally (e.g: "rebase --collapse")
1424 # (*1) deprecated, but used internally (e.g: "rebase --collapse")
1425
1425
1426 lfdirstate = lfutil.openlfdirstate(repo.ui, repo)
1426 lfdirstate = lfutil.openlfdirstate(repo.ui, repo)
1427 unsure, s = lfdirstate.status(matchmod.always(repo.root,
1427 unsure, s = lfdirstate.status(matchmod.always(repo.root,
1428 repo.getcwd()),
1428 repo.getcwd()),
1429 subrepos=[], ignored=False,
1429 subrepos=[], ignored=False,
1430 clean=True, unknown=False)
1430 clean=True, unknown=False)
1431 oldclean = set(s.clean)
1431 oldclean = set(s.clean)
1432 pctx = repo['.']
1432 pctx = repo['.']
1433 dctx = repo[node]
1433 dctx = repo[node]
1434 for lfile in unsure + s.modified:
1434 for lfile in unsure + s.modified:
1435 lfileabs = repo.wvfs.join(lfile)
1435 lfileabs = repo.wvfs.join(lfile)
1436 if not repo.wvfs.exists(lfileabs):
1436 if not repo.wvfs.exists(lfileabs):
1437 continue
1437 continue
1438 lfhash = lfutil.hashfile(lfileabs)
1438 lfhash = lfutil.hashfile(lfileabs)
1439 standin = lfutil.standin(lfile)
1439 standin = lfutil.standin(lfile)
1440 lfutil.writestandin(repo, standin, lfhash,
1440 lfutil.writestandin(repo, standin, lfhash,
1441 lfutil.getexecutable(lfileabs))
1441 lfutil.getexecutable(lfileabs))
1442 if (standin in pctx and
1442 if (standin in pctx and
1443 lfhash == lfutil.readasstandin(pctx[standin])):
1443 lfhash == lfutil.readasstandin(pctx[standin])):
1444 oldclean.add(lfile)
1444 oldclean.add(lfile)
1445 for lfile in s.added:
1445 for lfile in s.added:
1446 fstandin = lfutil.standin(lfile)
1446 fstandin = lfutil.standin(lfile)
1447 if fstandin not in dctx:
1447 if fstandin not in dctx:
1448 # in this case, content of standin file is meaningless
1448 # in this case, content of standin file is meaningless
1449 # (in dctx, lfile is unknown, or normal file)
1449 # (in dctx, lfile is unknown, or normal file)
1450 continue
1450 continue
1451 lfutil.updatestandin(repo, lfile, fstandin)
1451 lfutil.updatestandin(repo, lfile, fstandin)
1452 # mark all clean largefiles as dirty, just in case the update gets
1452 # mark all clean largefiles as dirty, just in case the update gets
1453 # interrupted before largefiles and lfdirstate are synchronized
1453 # interrupted before largefiles and lfdirstate are synchronized
1454 for lfile in oldclean:
1454 for lfile in oldclean:
1455 lfdirstate.normallookup(lfile)
1455 lfdirstate.normallookup(lfile)
1456 lfdirstate.write()
1456 lfdirstate.write()
1457
1457
1458 oldstandins = lfutil.getstandinsstate(repo)
1458 oldstandins = lfutil.getstandinsstate(repo)
1459 # Make sure the merge runs on disk, not in-memory. largefiles is not a
1459 # Make sure the merge runs on disk, not in-memory. largefiles is not a
1460 # good candidate for in-memory merge (large files, custom dirstate,
1460 # good candidate for in-memory merge (large files, custom dirstate,
1461 # matcher usage).
1461 # matcher usage).
1462 kwargs[r'wc'] = repo[None]
1462 kwargs[r'wc'] = repo[None]
1463 result = orig(repo, node, branchmerge, force, *args, **kwargs)
1463 result = orig(repo, node, branchmerge, force, *args, **kwargs)
1464
1464
1465 newstandins = lfutil.getstandinsstate(repo)
1465 newstandins = lfutil.getstandinsstate(repo)
1466 filelist = lfutil.getlfilestoupdate(oldstandins, newstandins)
1466 filelist = lfutil.getlfilestoupdate(oldstandins, newstandins)
1467
1467
1468 # to avoid leaving all largefiles as dirty and thus rehash them, mark
1468 # to avoid leaving all largefiles as dirty and thus rehash them, mark
1469 # all the ones that didn't change as clean
1469 # all the ones that didn't change as clean
1470 for lfile in oldclean.difference(filelist):
1470 for lfile in oldclean.difference(filelist):
1471 lfdirstate.normal(lfile)
1471 lfdirstate.normal(lfile)
1472 lfdirstate.write()
1472 lfdirstate.write()
1473
1473
1474 if branchmerge or force or partial:
1474 if branchmerge or force or partial:
1475 filelist.extend(s.deleted + s.removed)
1475 filelist.extend(s.deleted + s.removed)
1476
1476
1477 lfcommands.updatelfiles(repo.ui, repo, filelist=filelist,
1477 lfcommands.updatelfiles(repo.ui, repo, filelist=filelist,
1478 normallookup=partial)
1478 normallookup=partial)
1479
1479
1480 return result
1480 return result
1481
1481
1482 @eh.wrapfunction(scmutil, 'marktouched')
1482 @eh.wrapfunction(scmutil, 'marktouched')
1483 def scmutilmarktouched(orig, repo, files, *args, **kwargs):
1483 def scmutilmarktouched(orig, repo, files, *args, **kwargs):
1484 result = orig(repo, files, *args, **kwargs)
1484 result = orig(repo, files, *args, **kwargs)
1485
1485
1486 filelist = []
1486 filelist = []
1487 for f in files:
1487 for f in files:
1488 lf = lfutil.splitstandin(f)
1488 lf = lfutil.splitstandin(f)
1489 if lf is not None:
1489 if lf is not None:
1490 filelist.append(lf)
1490 filelist.append(lf)
1491 if filelist:
1491 if filelist:
1492 lfcommands.updatelfiles(repo.ui, repo, filelist=filelist,
1492 lfcommands.updatelfiles(repo.ui, repo, filelist=filelist,
1493 printmessage=False, normallookup=True)
1493 printmessage=False, normallookup=True)
1494
1494
1495 return result
1495 return result
1496
1496
1497 @eh.wrapfunction(upgrade, 'preservedrequirements')
1497 @eh.wrapfunction(upgrade, 'preservedrequirements')
1498 @eh.wrapfunction(upgrade, 'supporteddestrequirements')
1498 @eh.wrapfunction(upgrade, 'supporteddestrequirements')
1499 def upgraderequirements(orig, repo):
1499 def upgraderequirements(orig, repo):
1500 reqs = orig(repo)
1500 reqs = orig(repo)
1501 if 'largefiles' in repo.requirements:
1501 if 'largefiles' in repo.requirements:
1502 reqs.add('largefiles')
1502 reqs.add('largefiles')
1503 return reqs
1503 return reqs
1504
1504
1505 _lfscheme = 'largefile://'
1505 _lfscheme = 'largefile://'
1506
1506
1507 @eh.wrapfunction(urlmod, 'open')
1507 @eh.wrapfunction(urlmod, 'open')
1508 def openlargefile(orig, ui, url_, data=None):
1508 def openlargefile(orig, ui, url_, data=None):
1509 if url_.startswith(_lfscheme):
1509 if url_.startswith(_lfscheme):
1510 if data:
1510 if data:
1511 msg = "cannot use data on a 'largefile://' url"
1511 msg = "cannot use data on a 'largefile://' url"
1512 raise error.ProgrammingError(msg)
1512 raise error.ProgrammingError(msg)
1513 lfid = url_[len(_lfscheme):]
1513 lfid = url_[len(_lfscheme):]
1514 return storefactory.getlfile(ui, lfid)
1514 return storefactory.getlfile(ui, lfid)
1515 else:
1515 else:
1516 return orig(ui, url_, data=data)
1516 return orig(ui, url_, data=data)
@@ -1,3333 +1,3336 b''
1 # cmdutil.py - help for command processing in mercurial
1 # cmdutil.py - help for command processing in mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import errno
10 import errno
11 import os
11 import os
12 import re
12 import re
13
13
14 from .i18n import _
14 from .i18n import _
15 from .node import (
15 from .node import (
16 hex,
16 hex,
17 nullid,
17 nullid,
18 nullrev,
18 nullrev,
19 short,
19 short,
20 )
20 )
21
21
22 from . import (
22 from . import (
23 bookmarks,
23 bookmarks,
24 changelog,
24 changelog,
25 copies,
25 copies,
26 crecord as crecordmod,
26 crecord as crecordmod,
27 dirstateguard,
27 dirstateguard,
28 encoding,
28 encoding,
29 error,
29 error,
30 formatter,
30 formatter,
31 logcmdutil,
31 logcmdutil,
32 match as matchmod,
32 match as matchmod,
33 merge as mergemod,
33 merge as mergemod,
34 mergeutil,
34 mergeutil,
35 obsolete,
35 obsolete,
36 patch,
36 patch,
37 pathutil,
37 pathutil,
38 phases,
38 phases,
39 pycompat,
39 pycompat,
40 revlog,
40 revlog,
41 rewriteutil,
41 rewriteutil,
42 scmutil,
42 scmutil,
43 smartset,
43 smartset,
44 subrepoutil,
44 subrepoutil,
45 templatekw,
45 templatekw,
46 templater,
46 templater,
47 util,
47 util,
48 vfs as vfsmod,
48 vfs as vfsmod,
49 )
49 )
50
50
51 from .utils import (
51 from .utils import (
52 dateutil,
52 dateutil,
53 stringutil,
53 stringutil,
54 )
54 )
55
55
56 stringio = util.stringio
56 stringio = util.stringio
57
57
58 # templates of common command options
58 # templates of common command options
59
59
60 dryrunopts = [
60 dryrunopts = [
61 ('n', 'dry-run', None,
61 ('n', 'dry-run', None,
62 _('do not perform actions, just print output')),
62 _('do not perform actions, just print output')),
63 ]
63 ]
64
64
65 confirmopts = [
65 confirmopts = [
66 ('', 'confirm', None,
66 ('', 'confirm', None,
67 _('ask before applying actions')),
67 _('ask before applying actions')),
68 ]
68 ]
69
69
70 remoteopts = [
70 remoteopts = [
71 ('e', 'ssh', '',
71 ('e', 'ssh', '',
72 _('specify ssh command to use'), _('CMD')),
72 _('specify ssh command to use'), _('CMD')),
73 ('', 'remotecmd', '',
73 ('', 'remotecmd', '',
74 _('specify hg command to run on the remote side'), _('CMD')),
74 _('specify hg command to run on the remote side'), _('CMD')),
75 ('', 'insecure', None,
75 ('', 'insecure', None,
76 _('do not verify server certificate (ignoring web.cacerts config)')),
76 _('do not verify server certificate (ignoring web.cacerts config)')),
77 ]
77 ]
78
78
79 walkopts = [
79 walkopts = [
80 ('I', 'include', [],
80 ('I', 'include', [],
81 _('include names matching the given patterns'), _('PATTERN')),
81 _('include names matching the given patterns'), _('PATTERN')),
82 ('X', 'exclude', [],
82 ('X', 'exclude', [],
83 _('exclude names matching the given patterns'), _('PATTERN')),
83 _('exclude names matching the given patterns'), _('PATTERN')),
84 ]
84 ]
85
85
86 commitopts = [
86 commitopts = [
87 ('m', 'message', '',
87 ('m', 'message', '',
88 _('use text as commit message'), _('TEXT')),
88 _('use text as commit message'), _('TEXT')),
89 ('l', 'logfile', '',
89 ('l', 'logfile', '',
90 _('read commit message from file'), _('FILE')),
90 _('read commit message from file'), _('FILE')),
91 ]
91 ]
92
92
93 commitopts2 = [
93 commitopts2 = [
94 ('d', 'date', '',
94 ('d', 'date', '',
95 _('record the specified date as commit date'), _('DATE')),
95 _('record the specified date as commit date'), _('DATE')),
96 ('u', 'user', '',
96 ('u', 'user', '',
97 _('record the specified user as committer'), _('USER')),
97 _('record the specified user as committer'), _('USER')),
98 ]
98 ]
99
99
100 formatteropts = [
100 formatteropts = [
101 ('T', 'template', '',
101 ('T', 'template', '',
102 _('display with template'), _('TEMPLATE')),
102 _('display with template'), _('TEMPLATE')),
103 ]
103 ]
104
104
105 templateopts = [
105 templateopts = [
106 ('', 'style', '',
106 ('', 'style', '',
107 _('display using template map file (DEPRECATED)'), _('STYLE')),
107 _('display using template map file (DEPRECATED)'), _('STYLE')),
108 ('T', 'template', '',
108 ('T', 'template', '',
109 _('display with template'), _('TEMPLATE')),
109 _('display with template'), _('TEMPLATE')),
110 ]
110 ]
111
111
112 logopts = [
112 logopts = [
113 ('p', 'patch', None, _('show patch')),
113 ('p', 'patch', None, _('show patch')),
114 ('g', 'git', None, _('use git extended diff format')),
114 ('g', 'git', None, _('use git extended diff format')),
115 ('l', 'limit', '',
115 ('l', 'limit', '',
116 _('limit number of changes displayed'), _('NUM')),
116 _('limit number of changes displayed'), _('NUM')),
117 ('M', 'no-merges', None, _('do not show merges')),
117 ('M', 'no-merges', None, _('do not show merges')),
118 ('', 'stat', None, _('output diffstat-style summary of changes')),
118 ('', 'stat', None, _('output diffstat-style summary of changes')),
119 ('G', 'graph', None, _("show the revision DAG")),
119 ('G', 'graph', None, _("show the revision DAG")),
120 ] + templateopts
120 ] + templateopts
121
121
122 diffopts = [
122 diffopts = [
123 ('a', 'text', None, _('treat all files as text')),
123 ('a', 'text', None, _('treat all files as text')),
124 ('g', 'git', None, _('use git extended diff format')),
124 ('g', 'git', None, _('use git extended diff format')),
125 ('', 'binary', None, _('generate binary diffs in git mode (default)')),
125 ('', 'binary', None, _('generate binary diffs in git mode (default)')),
126 ('', 'nodates', None, _('omit dates from diff headers'))
126 ('', 'nodates', None, _('omit dates from diff headers'))
127 ]
127 ]
128
128
129 diffwsopts = [
129 diffwsopts = [
130 ('w', 'ignore-all-space', None,
130 ('w', 'ignore-all-space', None,
131 _('ignore white space when comparing lines')),
131 _('ignore white space when comparing lines')),
132 ('b', 'ignore-space-change', None,
132 ('b', 'ignore-space-change', None,
133 _('ignore changes in the amount of white space')),
133 _('ignore changes in the amount of white space')),
134 ('B', 'ignore-blank-lines', None,
134 ('B', 'ignore-blank-lines', None,
135 _('ignore changes whose lines are all blank')),
135 _('ignore changes whose lines are all blank')),
136 ('Z', 'ignore-space-at-eol', None,
136 ('Z', 'ignore-space-at-eol', None,
137 _('ignore changes in whitespace at EOL')),
137 _('ignore changes in whitespace at EOL')),
138 ]
138 ]
139
139
140 diffopts2 = [
140 diffopts2 = [
141 ('', 'noprefix', None, _('omit a/ and b/ prefixes from filenames')),
141 ('', 'noprefix', None, _('omit a/ and b/ prefixes from filenames')),
142 ('p', 'show-function', None, _('show which function each change is in')),
142 ('p', 'show-function', None, _('show which function each change is in')),
143 ('', 'reverse', None, _('produce a diff that undoes the changes')),
143 ('', 'reverse', None, _('produce a diff that undoes the changes')),
144 ] + diffwsopts + [
144 ] + diffwsopts + [
145 ('U', 'unified', '',
145 ('U', 'unified', '',
146 _('number of lines of context to show'), _('NUM')),
146 _('number of lines of context to show'), _('NUM')),
147 ('', 'stat', None, _('output diffstat-style summary of changes')),
147 ('', 'stat', None, _('output diffstat-style summary of changes')),
148 ('', 'root', '', _('produce diffs relative to subdirectory'), _('DIR')),
148 ('', 'root', '', _('produce diffs relative to subdirectory'), _('DIR')),
149 ]
149 ]
150
150
151 mergetoolopts = [
151 mergetoolopts = [
152 ('t', 'tool', '', _('specify merge tool'), _('TOOL')),
152 ('t', 'tool', '', _('specify merge tool'), _('TOOL')),
153 ]
153 ]
154
154
155 similarityopts = [
155 similarityopts = [
156 ('s', 'similarity', '',
156 ('s', 'similarity', '',
157 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
157 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
158 ]
158 ]
159
159
160 subrepoopts = [
160 subrepoopts = [
161 ('S', 'subrepos', None,
161 ('S', 'subrepos', None,
162 _('recurse into subrepositories'))
162 _('recurse into subrepositories'))
163 ]
163 ]
164
164
165 debugrevlogopts = [
165 debugrevlogopts = [
166 ('c', 'changelog', False, _('open changelog')),
166 ('c', 'changelog', False, _('open changelog')),
167 ('m', 'manifest', False, _('open manifest')),
167 ('m', 'manifest', False, _('open manifest')),
168 ('', 'dir', '', _('open directory manifest')),
168 ('', 'dir', '', _('open directory manifest')),
169 ]
169 ]
170
170
171 # special string such that everything below this line will be ingored in the
171 # special string such that everything below this line will be ingored in the
172 # editor text
172 # editor text
173 _linebelow = "^HG: ------------------------ >8 ------------------------$"
173 _linebelow = "^HG: ------------------------ >8 ------------------------$"
174
174
175 def ishunk(x):
175 def ishunk(x):
176 hunkclasses = (crecordmod.uihunk, patch.recordhunk)
176 hunkclasses = (crecordmod.uihunk, patch.recordhunk)
177 return isinstance(x, hunkclasses)
177 return isinstance(x, hunkclasses)
178
178
179 def newandmodified(chunks, originalchunks):
179 def newandmodified(chunks, originalchunks):
180 newlyaddedandmodifiedfiles = set()
180 newlyaddedandmodifiedfiles = set()
181 for chunk in chunks:
181 for chunk in chunks:
182 if ishunk(chunk) and chunk.header.isnewfile() and chunk not in \
182 if ishunk(chunk) and chunk.header.isnewfile() and chunk not in \
183 originalchunks:
183 originalchunks:
184 newlyaddedandmodifiedfiles.add(chunk.header.filename())
184 newlyaddedandmodifiedfiles.add(chunk.header.filename())
185 return newlyaddedandmodifiedfiles
185 return newlyaddedandmodifiedfiles
186
186
187 def parsealiases(cmd):
187 def parsealiases(cmd):
188 return cmd.split("|")
188 return cmd.split("|")
189
189
190 def setupwrapcolorwrite(ui):
190 def setupwrapcolorwrite(ui):
191 # wrap ui.write so diff output can be labeled/colorized
191 # wrap ui.write so diff output can be labeled/colorized
192 def wrapwrite(orig, *args, **kw):
192 def wrapwrite(orig, *args, **kw):
193 label = kw.pop(r'label', '')
193 label = kw.pop(r'label', '')
194 for chunk, l in patch.difflabel(lambda: args):
194 for chunk, l in patch.difflabel(lambda: args):
195 orig(chunk, label=label + l)
195 orig(chunk, label=label + l)
196
196
197 oldwrite = ui.write
197 oldwrite = ui.write
198 def wrap(*args, **kwargs):
198 def wrap(*args, **kwargs):
199 return wrapwrite(oldwrite, *args, **kwargs)
199 return wrapwrite(oldwrite, *args, **kwargs)
200 setattr(ui, 'write', wrap)
200 setattr(ui, 'write', wrap)
201 return oldwrite
201 return oldwrite
202
202
203 def filterchunks(ui, originalhunks, usecurses, testfile, operation=None):
203 def filterchunks(ui, originalhunks, usecurses, testfile, operation=None):
204 try:
204 try:
205 if usecurses:
205 if usecurses:
206 if testfile:
206 if testfile:
207 recordfn = crecordmod.testdecorator(
207 recordfn = crecordmod.testdecorator(
208 testfile, crecordmod.testchunkselector)
208 testfile, crecordmod.testchunkselector)
209 else:
209 else:
210 recordfn = crecordmod.chunkselector
210 recordfn = crecordmod.chunkselector
211
211
212 return crecordmod.filterpatch(ui, originalhunks, recordfn,
212 return crecordmod.filterpatch(ui, originalhunks, recordfn,
213 operation)
213 operation)
214 except crecordmod.fallbackerror as e:
214 except crecordmod.fallbackerror as e:
215 ui.warn('%s\n' % e.message)
215 ui.warn('%s\n' % e.message)
216 ui.warn(_('falling back to text mode\n'))
216 ui.warn(_('falling back to text mode\n'))
217
217
218 return patch.filterpatch(ui, originalhunks, operation)
218 return patch.filterpatch(ui, originalhunks, operation)
219
219
220 def recordfilter(ui, originalhunks, operation=None):
220 def recordfilter(ui, originalhunks, operation=None):
221 """ Prompts the user to filter the originalhunks and return a list of
221 """ Prompts the user to filter the originalhunks and return a list of
222 selected hunks.
222 selected hunks.
223 *operation* is used for to build ui messages to indicate the user what
223 *operation* is used for to build ui messages to indicate the user what
224 kind of filtering they are doing: reverting, committing, shelving, etc.
224 kind of filtering they are doing: reverting, committing, shelving, etc.
225 (see patch.filterpatch).
225 (see patch.filterpatch).
226 """
226 """
227 usecurses = crecordmod.checkcurses(ui)
227 usecurses = crecordmod.checkcurses(ui)
228 testfile = ui.config('experimental', 'crecordtest')
228 testfile = ui.config('experimental', 'crecordtest')
229 oldwrite = setupwrapcolorwrite(ui)
229 oldwrite = setupwrapcolorwrite(ui)
230 try:
230 try:
231 newchunks, newopts = filterchunks(ui, originalhunks, usecurses,
231 newchunks, newopts = filterchunks(ui, originalhunks, usecurses,
232 testfile, operation)
232 testfile, operation)
233 finally:
233 finally:
234 ui.write = oldwrite
234 ui.write = oldwrite
235 return newchunks, newopts
235 return newchunks, newopts
236
236
237 def dorecord(ui, repo, commitfunc, cmdsuggest, backupall,
237 def dorecord(ui, repo, commitfunc, cmdsuggest, backupall,
238 filterfn, *pats, **opts):
238 filterfn, *pats, **opts):
239 opts = pycompat.byteskwargs(opts)
239 opts = pycompat.byteskwargs(opts)
240 if not ui.interactive():
240 if not ui.interactive():
241 if cmdsuggest:
241 if cmdsuggest:
242 msg = _('running non-interactively, use %s instead') % cmdsuggest
242 msg = _('running non-interactively, use %s instead') % cmdsuggest
243 else:
243 else:
244 msg = _('running non-interactively')
244 msg = _('running non-interactively')
245 raise error.Abort(msg)
245 raise error.Abort(msg)
246
246
247 # make sure username is set before going interactive
247 # make sure username is set before going interactive
248 if not opts.get('user'):
248 if not opts.get('user'):
249 ui.username() # raise exception, username not provided
249 ui.username() # raise exception, username not provided
250
250
251 def recordfunc(ui, repo, message, match, opts):
251 def recordfunc(ui, repo, message, match, opts):
252 """This is generic record driver.
252 """This is generic record driver.
253
253
254 Its job is to interactively filter local changes, and
254 Its job is to interactively filter local changes, and
255 accordingly prepare working directory into a state in which the
255 accordingly prepare working directory into a state in which the
256 job can be delegated to a non-interactive commit command such as
256 job can be delegated to a non-interactive commit command such as
257 'commit' or 'qrefresh'.
257 'commit' or 'qrefresh'.
258
258
259 After the actual job is done by non-interactive command, the
259 After the actual job is done by non-interactive command, the
260 working directory is restored to its original state.
260 working directory is restored to its original state.
261
261
262 In the end we'll record interesting changes, and everything else
262 In the end we'll record interesting changes, and everything else
263 will be left in place, so the user can continue working.
263 will be left in place, so the user can continue working.
264 """
264 """
265
265
266 checkunfinished(repo, commit=True)
266 checkunfinished(repo, commit=True)
267 wctx = repo[None]
267 wctx = repo[None]
268 merge = len(wctx.parents()) > 1
268 merge = len(wctx.parents()) > 1
269 if merge:
269 if merge:
270 raise error.Abort(_('cannot partially commit a merge '
270 raise error.Abort(_('cannot partially commit a merge '
271 '(use "hg commit" instead)'))
271 '(use "hg commit" instead)'))
272
272
273 def fail(f, msg):
273 def fail(f, msg):
274 raise error.Abort('%s: %s' % (f, msg))
274 raise error.Abort('%s: %s' % (f, msg))
275
275
276 force = opts.get('force')
276 force = opts.get('force')
277 if not force:
277 if not force:
278 vdirs = []
278 vdirs = []
279 match.explicitdir = vdirs.append
279 match.explicitdir = vdirs.append
280 match.bad = fail
280 match.bad = fail
281
281
282 status = repo.status(match=match)
282 status = repo.status(match=match)
283 if not force:
283 if not force:
284 repo.checkcommitpatterns(wctx, vdirs, match, status, fail)
284 repo.checkcommitpatterns(wctx, vdirs, match, status, fail)
285 diffopts = patch.difffeatureopts(ui, opts=opts, whitespace=True,
285 diffopts = patch.difffeatureopts(ui, opts=opts, whitespace=True,
286 section='commands',
286 section='commands',
287 configprefix='commit.interactive.')
287 configprefix='commit.interactive.')
288 diffopts.nodates = True
288 diffopts.nodates = True
289 diffopts.git = True
289 diffopts.git = True
290 diffopts.showfunc = True
290 diffopts.showfunc = True
291 originaldiff = patch.diff(repo, changes=status, opts=diffopts)
291 originaldiff = patch.diff(repo, changes=status, opts=diffopts)
292 originalchunks = patch.parsepatch(originaldiff)
292 originalchunks = patch.parsepatch(originaldiff)
293
293
294 # 1. filter patch, since we are intending to apply subset of it
294 # 1. filter patch, since we are intending to apply subset of it
295 try:
295 try:
296 chunks, newopts = filterfn(ui, originalchunks)
296 chunks, newopts = filterfn(ui, originalchunks)
297 except error.PatchError as err:
297 except error.PatchError as err:
298 raise error.Abort(_('error parsing patch: %s') % err)
298 raise error.Abort(_('error parsing patch: %s') % err)
299 opts.update(newopts)
299 opts.update(newopts)
300
300
301 # We need to keep a backup of files that have been newly added and
301 # We need to keep a backup of files that have been newly added and
302 # modified during the recording process because there is a previous
302 # modified during the recording process because there is a previous
303 # version without the edit in the workdir
303 # version without the edit in the workdir
304 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
304 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
305 contenders = set()
305 contenders = set()
306 for h in chunks:
306 for h in chunks:
307 try:
307 try:
308 contenders.update(set(h.files()))
308 contenders.update(set(h.files()))
309 except AttributeError:
309 except AttributeError:
310 pass
310 pass
311
311
312 changed = status.modified + status.added + status.removed
312 changed = status.modified + status.added + status.removed
313 newfiles = [f for f in changed if f in contenders]
313 newfiles = [f for f in changed if f in contenders]
314 if not newfiles:
314 if not newfiles:
315 ui.status(_('no changes to record\n'))
315 ui.status(_('no changes to record\n'))
316 return 0
316 return 0
317
317
318 modified = set(status.modified)
318 modified = set(status.modified)
319
319
320 # 2. backup changed files, so we can restore them in the end
320 # 2. backup changed files, so we can restore them in the end
321
321
322 if backupall:
322 if backupall:
323 tobackup = changed
323 tobackup = changed
324 else:
324 else:
325 tobackup = [f for f in newfiles if f in modified or f in \
325 tobackup = [f for f in newfiles if f in modified or f in \
326 newlyaddedandmodifiedfiles]
326 newlyaddedandmodifiedfiles]
327 backups = {}
327 backups = {}
328 if tobackup:
328 if tobackup:
329 backupdir = repo.vfs.join('record-backups')
329 backupdir = repo.vfs.join('record-backups')
330 try:
330 try:
331 os.mkdir(backupdir)
331 os.mkdir(backupdir)
332 except OSError as err:
332 except OSError as err:
333 if err.errno != errno.EEXIST:
333 if err.errno != errno.EEXIST:
334 raise
334 raise
335 try:
335 try:
336 # backup continues
336 # backup continues
337 for f in tobackup:
337 for f in tobackup:
338 fd, tmpname = pycompat.mkstemp(prefix=f.replace('/', '_') + '.',
338 fd, tmpname = pycompat.mkstemp(prefix=f.replace('/', '_') + '.',
339 dir=backupdir)
339 dir=backupdir)
340 os.close(fd)
340 os.close(fd)
341 ui.debug('backup %r as %r\n' % (f, tmpname))
341 ui.debug('backup %r as %r\n' % (f, tmpname))
342 util.copyfile(repo.wjoin(f), tmpname, copystat=True)
342 util.copyfile(repo.wjoin(f), tmpname, copystat=True)
343 backups[f] = tmpname
343 backups[f] = tmpname
344
344
345 fp = stringio()
345 fp = stringio()
346 for c in chunks:
346 for c in chunks:
347 fname = c.filename()
347 fname = c.filename()
348 if fname in backups:
348 if fname in backups:
349 c.write(fp)
349 c.write(fp)
350 dopatch = fp.tell()
350 dopatch = fp.tell()
351 fp.seek(0)
351 fp.seek(0)
352
352
353 # 2.5 optionally review / modify patch in text editor
353 # 2.5 optionally review / modify patch in text editor
354 if opts.get('review', False):
354 if opts.get('review', False):
355 patchtext = (crecordmod.diffhelptext
355 patchtext = (crecordmod.diffhelptext
356 + crecordmod.patchhelptext
356 + crecordmod.patchhelptext
357 + fp.read())
357 + fp.read())
358 reviewedpatch = ui.edit(patchtext, "",
358 reviewedpatch = ui.edit(patchtext, "",
359 action="diff",
359 action="diff",
360 repopath=repo.path)
360 repopath=repo.path)
361 fp.truncate(0)
361 fp.truncate(0)
362 fp.write(reviewedpatch)
362 fp.write(reviewedpatch)
363 fp.seek(0)
363 fp.seek(0)
364
364
365 [os.unlink(repo.wjoin(c)) for c in newlyaddedandmodifiedfiles]
365 [os.unlink(repo.wjoin(c)) for c in newlyaddedandmodifiedfiles]
366 # 3a. apply filtered patch to clean repo (clean)
366 # 3a. apply filtered patch to clean repo (clean)
367 if backups:
367 if backups:
368 # Equivalent to hg.revert
368 # Equivalent to hg.revert
369 m = scmutil.matchfiles(repo, backups.keys())
369 m = scmutil.matchfiles(repo, backups.keys())
370 mergemod.update(repo, repo.dirstate.p1(), branchmerge=False,
370 mergemod.update(repo, repo.dirstate.p1(), branchmerge=False,
371 force=True, matcher=m)
371 force=True, matcher=m)
372
372
373 # 3b. (apply)
373 # 3b. (apply)
374 if dopatch:
374 if dopatch:
375 try:
375 try:
376 ui.debug('applying patch\n')
376 ui.debug('applying patch\n')
377 ui.debug(fp.getvalue())
377 ui.debug(fp.getvalue())
378 patch.internalpatch(ui, repo, fp, 1, eolmode=None)
378 patch.internalpatch(ui, repo, fp, 1, eolmode=None)
379 except error.PatchError as err:
379 except error.PatchError as err:
380 raise error.Abort(pycompat.bytestr(err))
380 raise error.Abort(pycompat.bytestr(err))
381 del fp
381 del fp
382
382
383 # 4. We prepared working directory according to filtered
383 # 4. We prepared working directory according to filtered
384 # patch. Now is the time to delegate the job to
384 # patch. Now is the time to delegate the job to
385 # commit/qrefresh or the like!
385 # commit/qrefresh or the like!
386
386
387 # Make all of the pathnames absolute.
387 # Make all of the pathnames absolute.
388 newfiles = [repo.wjoin(nf) for nf in newfiles]
388 newfiles = [repo.wjoin(nf) for nf in newfiles]
389 return commitfunc(ui, repo, *newfiles, **pycompat.strkwargs(opts))
389 return commitfunc(ui, repo, *newfiles, **pycompat.strkwargs(opts))
390 finally:
390 finally:
391 # 5. finally restore backed-up files
391 # 5. finally restore backed-up files
392 try:
392 try:
393 dirstate = repo.dirstate
393 dirstate = repo.dirstate
394 for realname, tmpname in backups.iteritems():
394 for realname, tmpname in backups.iteritems():
395 ui.debug('restoring %r to %r\n' % (tmpname, realname))
395 ui.debug('restoring %r to %r\n' % (tmpname, realname))
396
396
397 if dirstate[realname] == 'n':
397 if dirstate[realname] == 'n':
398 # without normallookup, restoring timestamp
398 # without normallookup, restoring timestamp
399 # may cause partially committed files
399 # may cause partially committed files
400 # to be treated as unmodified
400 # to be treated as unmodified
401 dirstate.normallookup(realname)
401 dirstate.normallookup(realname)
402
402
403 # copystat=True here and above are a hack to trick any
403 # copystat=True here and above are a hack to trick any
404 # editors that have f open that we haven't modified them.
404 # editors that have f open that we haven't modified them.
405 #
405 #
406 # Also note that this racy as an editor could notice the
406 # Also note that this racy as an editor could notice the
407 # file's mtime before we've finished writing it.
407 # file's mtime before we've finished writing it.
408 util.copyfile(tmpname, repo.wjoin(realname), copystat=True)
408 util.copyfile(tmpname, repo.wjoin(realname), copystat=True)
409 os.unlink(tmpname)
409 os.unlink(tmpname)
410 if tobackup:
410 if tobackup:
411 os.rmdir(backupdir)
411 os.rmdir(backupdir)
412 except OSError:
412 except OSError:
413 pass
413 pass
414
414
415 def recordinwlock(ui, repo, message, match, opts):
415 def recordinwlock(ui, repo, message, match, opts):
416 with repo.wlock():
416 with repo.wlock():
417 return recordfunc(ui, repo, message, match, opts)
417 return recordfunc(ui, repo, message, match, opts)
418
418
419 return commit(ui, repo, recordinwlock, pats, opts)
419 return commit(ui, repo, recordinwlock, pats, opts)
420
420
421 class dirnode(object):
421 class dirnode(object):
422 """
422 """
423 Represent a directory in user working copy with information required for
423 Represent a directory in user working copy with information required for
424 the purpose of tersing its status.
424 the purpose of tersing its status.
425
425
426 path is the path to the directory, without a trailing '/'
426 path is the path to the directory, without a trailing '/'
427
427
428 statuses is a set of statuses of all files in this directory (this includes
428 statuses is a set of statuses of all files in this directory (this includes
429 all the files in all the subdirectories too)
429 all the files in all the subdirectories too)
430
430
431 files is a list of files which are direct child of this directory
431 files is a list of files which are direct child of this directory
432
432
433 subdirs is a dictionary of sub-directory name as the key and it's own
433 subdirs is a dictionary of sub-directory name as the key and it's own
434 dirnode object as the value
434 dirnode object as the value
435 """
435 """
436
436
437 def __init__(self, dirpath):
437 def __init__(self, dirpath):
438 self.path = dirpath
438 self.path = dirpath
439 self.statuses = set([])
439 self.statuses = set([])
440 self.files = []
440 self.files = []
441 self.subdirs = {}
441 self.subdirs = {}
442
442
443 def _addfileindir(self, filename, status):
443 def _addfileindir(self, filename, status):
444 """Add a file in this directory as a direct child."""
444 """Add a file in this directory as a direct child."""
445 self.files.append((filename, status))
445 self.files.append((filename, status))
446
446
447 def addfile(self, filename, status):
447 def addfile(self, filename, status):
448 """
448 """
449 Add a file to this directory or to its direct parent directory.
449 Add a file to this directory or to its direct parent directory.
450
450
451 If the file is not direct child of this directory, we traverse to the
451 If the file is not direct child of this directory, we traverse to the
452 directory of which this file is a direct child of and add the file
452 directory of which this file is a direct child of and add the file
453 there.
453 there.
454 """
454 """
455
455
456 # the filename contains a path separator, it means it's not the direct
456 # the filename contains a path separator, it means it's not the direct
457 # child of this directory
457 # child of this directory
458 if '/' in filename:
458 if '/' in filename:
459 subdir, filep = filename.split('/', 1)
459 subdir, filep = filename.split('/', 1)
460
460
461 # does the dirnode object for subdir exists
461 # does the dirnode object for subdir exists
462 if subdir not in self.subdirs:
462 if subdir not in self.subdirs:
463 subdirpath = pathutil.join(self.path, subdir)
463 subdirpath = pathutil.join(self.path, subdir)
464 self.subdirs[subdir] = dirnode(subdirpath)
464 self.subdirs[subdir] = dirnode(subdirpath)
465
465
466 # try adding the file in subdir
466 # try adding the file in subdir
467 self.subdirs[subdir].addfile(filep, status)
467 self.subdirs[subdir].addfile(filep, status)
468
468
469 else:
469 else:
470 self._addfileindir(filename, status)
470 self._addfileindir(filename, status)
471
471
472 if status not in self.statuses:
472 if status not in self.statuses:
473 self.statuses.add(status)
473 self.statuses.add(status)
474
474
475 def iterfilepaths(self):
475 def iterfilepaths(self):
476 """Yield (status, path) for files directly under this directory."""
476 """Yield (status, path) for files directly under this directory."""
477 for f, st in self.files:
477 for f, st in self.files:
478 yield st, pathutil.join(self.path, f)
478 yield st, pathutil.join(self.path, f)
479
479
480 def tersewalk(self, terseargs):
480 def tersewalk(self, terseargs):
481 """
481 """
482 Yield (status, path) obtained by processing the status of this
482 Yield (status, path) obtained by processing the status of this
483 dirnode.
483 dirnode.
484
484
485 terseargs is the string of arguments passed by the user with `--terse`
485 terseargs is the string of arguments passed by the user with `--terse`
486 flag.
486 flag.
487
487
488 Following are the cases which can happen:
488 Following are the cases which can happen:
489
489
490 1) All the files in the directory (including all the files in its
490 1) All the files in the directory (including all the files in its
491 subdirectories) share the same status and the user has asked us to terse
491 subdirectories) share the same status and the user has asked us to terse
492 that status. -> yield (status, dirpath). dirpath will end in '/'.
492 that status. -> yield (status, dirpath). dirpath will end in '/'.
493
493
494 2) Otherwise, we do following:
494 2) Otherwise, we do following:
495
495
496 a) Yield (status, filepath) for all the files which are in this
496 a) Yield (status, filepath) for all the files which are in this
497 directory (only the ones in this directory, not the subdirs)
497 directory (only the ones in this directory, not the subdirs)
498
498
499 b) Recurse the function on all the subdirectories of this
499 b) Recurse the function on all the subdirectories of this
500 directory
500 directory
501 """
501 """
502
502
503 if len(self.statuses) == 1:
503 if len(self.statuses) == 1:
504 onlyst = self.statuses.pop()
504 onlyst = self.statuses.pop()
505
505
506 # Making sure we terse only when the status abbreviation is
506 # Making sure we terse only when the status abbreviation is
507 # passed as terse argument
507 # passed as terse argument
508 if onlyst in terseargs:
508 if onlyst in terseargs:
509 yield onlyst, self.path + '/'
509 yield onlyst, self.path + '/'
510 return
510 return
511
511
512 # add the files to status list
512 # add the files to status list
513 for st, fpath in self.iterfilepaths():
513 for st, fpath in self.iterfilepaths():
514 yield st, fpath
514 yield st, fpath
515
515
516 #recurse on the subdirs
516 #recurse on the subdirs
517 for dirobj in self.subdirs.values():
517 for dirobj in self.subdirs.values():
518 for st, fpath in dirobj.tersewalk(terseargs):
518 for st, fpath in dirobj.tersewalk(terseargs):
519 yield st, fpath
519 yield st, fpath
520
520
521 def tersedir(statuslist, terseargs):
521 def tersedir(statuslist, terseargs):
522 """
522 """
523 Terse the status if all the files in a directory shares the same status.
523 Terse the status if all the files in a directory shares the same status.
524
524
525 statuslist is scmutil.status() object which contains a list of files for
525 statuslist is scmutil.status() object which contains a list of files for
526 each status.
526 each status.
527 terseargs is string which is passed by the user as the argument to `--terse`
527 terseargs is string which is passed by the user as the argument to `--terse`
528 flag.
528 flag.
529
529
530 The function makes a tree of objects of dirnode class, and at each node it
530 The function makes a tree of objects of dirnode class, and at each node it
531 stores the information required to know whether we can terse a certain
531 stores the information required to know whether we can terse a certain
532 directory or not.
532 directory or not.
533 """
533 """
534 # the order matters here as that is used to produce final list
534 # the order matters here as that is used to produce final list
535 allst = ('m', 'a', 'r', 'd', 'u', 'i', 'c')
535 allst = ('m', 'a', 'r', 'd', 'u', 'i', 'c')
536
536
537 # checking the argument validity
537 # checking the argument validity
538 for s in pycompat.bytestr(terseargs):
538 for s in pycompat.bytestr(terseargs):
539 if s not in allst:
539 if s not in allst:
540 raise error.Abort(_("'%s' not recognized") % s)
540 raise error.Abort(_("'%s' not recognized") % s)
541
541
542 # creating a dirnode object for the root of the repo
542 # creating a dirnode object for the root of the repo
543 rootobj = dirnode('')
543 rootobj = dirnode('')
544 pstatus = ('modified', 'added', 'deleted', 'clean', 'unknown',
544 pstatus = ('modified', 'added', 'deleted', 'clean', 'unknown',
545 'ignored', 'removed')
545 'ignored', 'removed')
546
546
547 tersedict = {}
547 tersedict = {}
548 for attrname in pstatus:
548 for attrname in pstatus:
549 statuschar = attrname[0:1]
549 statuschar = attrname[0:1]
550 for f in getattr(statuslist, attrname):
550 for f in getattr(statuslist, attrname):
551 rootobj.addfile(f, statuschar)
551 rootobj.addfile(f, statuschar)
552 tersedict[statuschar] = []
552 tersedict[statuschar] = []
553
553
554 # we won't be tersing the root dir, so add files in it
554 # we won't be tersing the root dir, so add files in it
555 for st, fpath in rootobj.iterfilepaths():
555 for st, fpath in rootobj.iterfilepaths():
556 tersedict[st].append(fpath)
556 tersedict[st].append(fpath)
557
557
558 # process each sub-directory and build tersedict
558 # process each sub-directory and build tersedict
559 for subdir in rootobj.subdirs.values():
559 for subdir in rootobj.subdirs.values():
560 for st, f in subdir.tersewalk(terseargs):
560 for st, f in subdir.tersewalk(terseargs):
561 tersedict[st].append(f)
561 tersedict[st].append(f)
562
562
563 tersedlist = []
563 tersedlist = []
564 for st in allst:
564 for st in allst:
565 tersedict[st].sort()
565 tersedict[st].sort()
566 tersedlist.append(tersedict[st])
566 tersedlist.append(tersedict[st])
567
567
568 return tersedlist
568 return tersedlist
569
569
570 def _commentlines(raw):
570 def _commentlines(raw):
571 '''Surround lineswith a comment char and a new line'''
571 '''Surround lineswith a comment char and a new line'''
572 lines = raw.splitlines()
572 lines = raw.splitlines()
573 commentedlines = ['# %s' % line for line in lines]
573 commentedlines = ['# %s' % line for line in lines]
574 return '\n'.join(commentedlines) + '\n'
574 return '\n'.join(commentedlines) + '\n'
575
575
576 def _conflictsmsg(repo):
576 def _conflictsmsg(repo):
577 mergestate = mergemod.mergestate.read(repo)
577 mergestate = mergemod.mergestate.read(repo)
578 if not mergestate.active():
578 if not mergestate.active():
579 return
579 return
580
580
581 m = scmutil.match(repo[None])
581 m = scmutil.match(repo[None])
582 unresolvedlist = [f for f in mergestate.unresolved() if m(f)]
582 unresolvedlist = [f for f in mergestate.unresolved() if m(f)]
583 if unresolvedlist:
583 if unresolvedlist:
584 mergeliststr = '\n'.join(
584 mergeliststr = '\n'.join(
585 [' %s' % util.pathto(repo.root, encoding.getcwd(), path)
585 [' %s' % util.pathto(repo.root, encoding.getcwd(), path)
586 for path in sorted(unresolvedlist)])
586 for path in sorted(unresolvedlist)])
587 msg = _('''Unresolved merge conflicts:
587 msg = _('''Unresolved merge conflicts:
588
588
589 %s
589 %s
590
590
591 To mark files as resolved: hg resolve --mark FILE''') % mergeliststr
591 To mark files as resolved: hg resolve --mark FILE''') % mergeliststr
592 else:
592 else:
593 msg = _('No unresolved merge conflicts.')
593 msg = _('No unresolved merge conflicts.')
594
594
595 return _commentlines(msg)
595 return _commentlines(msg)
596
596
597 def _helpmessage(continuecmd, abortcmd):
597 def _helpmessage(continuecmd, abortcmd):
598 msg = _('To continue: %s\n'
598 msg = _('To continue: %s\n'
599 'To abort: %s') % (continuecmd, abortcmd)
599 'To abort: %s') % (continuecmd, abortcmd)
600 return _commentlines(msg)
600 return _commentlines(msg)
601
601
602 def _rebasemsg():
602 def _rebasemsg():
603 return _helpmessage('hg rebase --continue', 'hg rebase --abort')
603 return _helpmessage('hg rebase --continue', 'hg rebase --abort')
604
604
605 def _histeditmsg():
605 def _histeditmsg():
606 return _helpmessage('hg histedit --continue', 'hg histedit --abort')
606 return _helpmessage('hg histedit --continue', 'hg histedit --abort')
607
607
608 def _unshelvemsg():
608 def _unshelvemsg():
609 return _helpmessage('hg unshelve --continue', 'hg unshelve --abort')
609 return _helpmessage('hg unshelve --continue', 'hg unshelve --abort')
610
610
611 def _graftmsg():
611 def _graftmsg():
612 return _helpmessage('hg graft --continue', 'hg graft --abort')
612 return _helpmessage('hg graft --continue', 'hg graft --abort')
613
613
614 def _mergemsg():
614 def _mergemsg():
615 return _helpmessage('hg commit', 'hg merge --abort')
615 return _helpmessage('hg commit', 'hg merge --abort')
616
616
617 def _bisectmsg():
617 def _bisectmsg():
618 msg = _('To mark the changeset good: hg bisect --good\n'
618 msg = _('To mark the changeset good: hg bisect --good\n'
619 'To mark the changeset bad: hg bisect --bad\n'
619 'To mark the changeset bad: hg bisect --bad\n'
620 'To abort: hg bisect --reset\n')
620 'To abort: hg bisect --reset\n')
621 return _commentlines(msg)
621 return _commentlines(msg)
622
622
623 def fileexistspredicate(filename):
623 def fileexistspredicate(filename):
624 return lambda repo: repo.vfs.exists(filename)
624 return lambda repo: repo.vfs.exists(filename)
625
625
626 def _mergepredicate(repo):
626 def _mergepredicate(repo):
627 return len(repo[None].parents()) > 1
627 return len(repo[None].parents()) > 1
628
628
629 STATES = (
629 STATES = (
630 # (state, predicate to detect states, helpful message function)
630 # (state, predicate to detect states, helpful message function)
631 ('histedit', fileexistspredicate('histedit-state'), _histeditmsg),
631 ('histedit', fileexistspredicate('histedit-state'), _histeditmsg),
632 ('bisect', fileexistspredicate('bisect.state'), _bisectmsg),
632 ('bisect', fileexistspredicate('bisect.state'), _bisectmsg),
633 ('graft', fileexistspredicate('graftstate'), _graftmsg),
633 ('graft', fileexistspredicate('graftstate'), _graftmsg),
634 ('unshelve', fileexistspredicate('shelvedstate'), _unshelvemsg),
634 ('unshelve', fileexistspredicate('shelvedstate'), _unshelvemsg),
635 ('rebase', fileexistspredicate('rebasestate'), _rebasemsg),
635 ('rebase', fileexistspredicate('rebasestate'), _rebasemsg),
636 # The merge state is part of a list that will be iterated over.
636 # The merge state is part of a list that will be iterated over.
637 # They need to be last because some of the other unfinished states may also
637 # They need to be last because some of the other unfinished states may also
638 # be in a merge or update state (eg. rebase, histedit, graft, etc).
638 # be in a merge or update state (eg. rebase, histedit, graft, etc).
639 # We want those to have priority.
639 # We want those to have priority.
640 ('merge', _mergepredicate, _mergemsg),
640 ('merge', _mergepredicate, _mergemsg),
641 )
641 )
642
642
643 def _getrepostate(repo):
643 def _getrepostate(repo):
644 # experimental config: commands.status.skipstates
644 # experimental config: commands.status.skipstates
645 skip = set(repo.ui.configlist('commands', 'status.skipstates'))
645 skip = set(repo.ui.configlist('commands', 'status.skipstates'))
646 for state, statedetectionpredicate, msgfn in STATES:
646 for state, statedetectionpredicate, msgfn in STATES:
647 if state in skip:
647 if state in skip:
648 continue
648 continue
649 if statedetectionpredicate(repo):
649 if statedetectionpredicate(repo):
650 return (state, statedetectionpredicate, msgfn)
650 return (state, statedetectionpredicate, msgfn)
651
651
652 def morestatus(repo, fm):
652 def morestatus(repo, fm):
653 statetuple = _getrepostate(repo)
653 statetuple = _getrepostate(repo)
654 label = 'status.morestatus'
654 label = 'status.morestatus'
655 if statetuple:
655 if statetuple:
656 state, statedetectionpredicate, helpfulmsg = statetuple
656 state, statedetectionpredicate, helpfulmsg = statetuple
657 statemsg = _('The repository is in an unfinished *%s* state.') % state
657 statemsg = _('The repository is in an unfinished *%s* state.') % state
658 fm.plain('%s\n' % _commentlines(statemsg), label=label)
658 fm.plain('%s\n' % _commentlines(statemsg), label=label)
659 conmsg = _conflictsmsg(repo)
659 conmsg = _conflictsmsg(repo)
660 if conmsg:
660 if conmsg:
661 fm.plain('%s\n' % conmsg, label=label)
661 fm.plain('%s\n' % conmsg, label=label)
662 if helpfulmsg:
662 if helpfulmsg:
663 helpmsg = helpfulmsg()
663 helpmsg = helpfulmsg()
664 fm.plain('%s\n' % helpmsg, label=label)
664 fm.plain('%s\n' % helpmsg, label=label)
665
665
666 def findpossible(cmd, table, strict=False):
666 def findpossible(cmd, table, strict=False):
667 """
667 """
668 Return cmd -> (aliases, command table entry)
668 Return cmd -> (aliases, command table entry)
669 for each matching command.
669 for each matching command.
670 Return debug commands (or their aliases) only if no normal command matches.
670 Return debug commands (or their aliases) only if no normal command matches.
671 """
671 """
672 choice = {}
672 choice = {}
673 debugchoice = {}
673 debugchoice = {}
674
674
675 if cmd in table:
675 if cmd in table:
676 # short-circuit exact matches, "log" alias beats "log|history"
676 # short-circuit exact matches, "log" alias beats "log|history"
677 keys = [cmd]
677 keys = [cmd]
678 else:
678 else:
679 keys = table.keys()
679 keys = table.keys()
680
680
681 allcmds = []
681 allcmds = []
682 for e in keys:
682 for e in keys:
683 aliases = parsealiases(e)
683 aliases = parsealiases(e)
684 allcmds.extend(aliases)
684 allcmds.extend(aliases)
685 found = None
685 found = None
686 if cmd in aliases:
686 if cmd in aliases:
687 found = cmd
687 found = cmd
688 elif not strict:
688 elif not strict:
689 for a in aliases:
689 for a in aliases:
690 if a.startswith(cmd):
690 if a.startswith(cmd):
691 found = a
691 found = a
692 break
692 break
693 if found is not None:
693 if found is not None:
694 if aliases[0].startswith("debug") or found.startswith("debug"):
694 if aliases[0].startswith("debug") or found.startswith("debug"):
695 debugchoice[found] = (aliases, table[e])
695 debugchoice[found] = (aliases, table[e])
696 else:
696 else:
697 choice[found] = (aliases, table[e])
697 choice[found] = (aliases, table[e])
698
698
699 if not choice and debugchoice:
699 if not choice and debugchoice:
700 choice = debugchoice
700 choice = debugchoice
701
701
702 return choice, allcmds
702 return choice, allcmds
703
703
704 def findcmd(cmd, table, strict=True):
704 def findcmd(cmd, table, strict=True):
705 """Return (aliases, command table entry) for command string."""
705 """Return (aliases, command table entry) for command string."""
706 choice, allcmds = findpossible(cmd, table, strict)
706 choice, allcmds = findpossible(cmd, table, strict)
707
707
708 if cmd in choice:
708 if cmd in choice:
709 return choice[cmd]
709 return choice[cmd]
710
710
711 if len(choice) > 1:
711 if len(choice) > 1:
712 clist = sorted(choice)
712 clist = sorted(choice)
713 raise error.AmbiguousCommand(cmd, clist)
713 raise error.AmbiguousCommand(cmd, clist)
714
714
715 if choice:
715 if choice:
716 return list(choice.values())[0]
716 return list(choice.values())[0]
717
717
718 raise error.UnknownCommand(cmd, allcmds)
718 raise error.UnknownCommand(cmd, allcmds)
719
719
720 def changebranch(ui, repo, revs, label):
720 def changebranch(ui, repo, revs, label):
721 """ Change the branch name of given revs to label """
721 """ Change the branch name of given revs to label """
722
722
723 with repo.wlock(), repo.lock(), repo.transaction('branches'):
723 with repo.wlock(), repo.lock(), repo.transaction('branches'):
724 # abort in case of uncommitted merge or dirty wdir
724 # abort in case of uncommitted merge or dirty wdir
725 bailifchanged(repo)
725 bailifchanged(repo)
726 revs = scmutil.revrange(repo, revs)
726 revs = scmutil.revrange(repo, revs)
727 if not revs:
727 if not revs:
728 raise error.Abort("empty revision set")
728 raise error.Abort("empty revision set")
729 roots = repo.revs('roots(%ld)', revs)
729 roots = repo.revs('roots(%ld)', revs)
730 if len(roots) > 1:
730 if len(roots) > 1:
731 raise error.Abort(_("cannot change branch of non-linear revisions"))
731 raise error.Abort(_("cannot change branch of non-linear revisions"))
732 rewriteutil.precheck(repo, revs, 'change branch of')
732 rewriteutil.precheck(repo, revs, 'change branch of')
733
733
734 root = repo[roots.first()]
734 root = repo[roots.first()]
735 rpb = {parent.branch() for parent in root.parents()}
735 rpb = {parent.branch() for parent in root.parents()}
736 if label not in rpb and label in repo.branchmap():
736 if label not in rpb and label in repo.branchmap():
737 raise error.Abort(_("a branch of the same name already exists"))
737 raise error.Abort(_("a branch of the same name already exists"))
738
738
739 if repo.revs('obsolete() and %ld', revs):
739 if repo.revs('obsolete() and %ld', revs):
740 raise error.Abort(_("cannot change branch of a obsolete changeset"))
740 raise error.Abort(_("cannot change branch of a obsolete changeset"))
741
741
742 # make sure only topological heads
742 # make sure only topological heads
743 if repo.revs('heads(%ld) - head()', revs):
743 if repo.revs('heads(%ld) - head()', revs):
744 raise error.Abort(_("cannot change branch in middle of a stack"))
744 raise error.Abort(_("cannot change branch in middle of a stack"))
745
745
746 replacements = {}
746 replacements = {}
747 # avoid import cycle mercurial.cmdutil -> mercurial.context ->
747 # avoid import cycle mercurial.cmdutil -> mercurial.context ->
748 # mercurial.subrepo -> mercurial.cmdutil
748 # mercurial.subrepo -> mercurial.cmdutil
749 from . import context
749 from . import context
750 for rev in revs:
750 for rev in revs:
751 ctx = repo[rev]
751 ctx = repo[rev]
752 oldbranch = ctx.branch()
752 oldbranch = ctx.branch()
753 # check if ctx has same branch
753 # check if ctx has same branch
754 if oldbranch == label:
754 if oldbranch == label:
755 continue
755 continue
756
756
757 def filectxfn(repo, newctx, path):
757 def filectxfn(repo, newctx, path):
758 try:
758 try:
759 return ctx[path]
759 return ctx[path]
760 except error.ManifestLookupError:
760 except error.ManifestLookupError:
761 return None
761 return None
762
762
763 ui.debug("changing branch of '%s' from '%s' to '%s'\n"
763 ui.debug("changing branch of '%s' from '%s' to '%s'\n"
764 % (hex(ctx.node()), oldbranch, label))
764 % (hex(ctx.node()), oldbranch, label))
765 extra = ctx.extra()
765 extra = ctx.extra()
766 extra['branch_change'] = hex(ctx.node())
766 extra['branch_change'] = hex(ctx.node())
767 # While changing branch of set of linear commits, make sure that
767 # While changing branch of set of linear commits, make sure that
768 # we base our commits on new parent rather than old parent which
768 # we base our commits on new parent rather than old parent which
769 # was obsoleted while changing the branch
769 # was obsoleted while changing the branch
770 p1 = ctx.p1().node()
770 p1 = ctx.p1().node()
771 p2 = ctx.p2().node()
771 p2 = ctx.p2().node()
772 if p1 in replacements:
772 if p1 in replacements:
773 p1 = replacements[p1][0]
773 p1 = replacements[p1][0]
774 if p2 in replacements:
774 if p2 in replacements:
775 p2 = replacements[p2][0]
775 p2 = replacements[p2][0]
776
776
777 mc = context.memctx(repo, (p1, p2),
777 mc = context.memctx(repo, (p1, p2),
778 ctx.description(),
778 ctx.description(),
779 ctx.files(),
779 ctx.files(),
780 filectxfn,
780 filectxfn,
781 user=ctx.user(),
781 user=ctx.user(),
782 date=ctx.date(),
782 date=ctx.date(),
783 extra=extra,
783 extra=extra,
784 branch=label)
784 branch=label)
785
785
786 newnode = repo.commitctx(mc)
786 newnode = repo.commitctx(mc)
787 replacements[ctx.node()] = (newnode,)
787 replacements[ctx.node()] = (newnode,)
788 ui.debug('new node id is %s\n' % hex(newnode))
788 ui.debug('new node id is %s\n' % hex(newnode))
789
789
790 # create obsmarkers and move bookmarks
790 # create obsmarkers and move bookmarks
791 scmutil.cleanupnodes(repo, replacements, 'branch-change', fixphase=True)
791 scmutil.cleanupnodes(repo, replacements, 'branch-change', fixphase=True)
792
792
793 # move the working copy too
793 # move the working copy too
794 wctx = repo[None]
794 wctx = repo[None]
795 # in-progress merge is a bit too complex for now.
795 # in-progress merge is a bit too complex for now.
796 if len(wctx.parents()) == 1:
796 if len(wctx.parents()) == 1:
797 newid = replacements.get(wctx.p1().node())
797 newid = replacements.get(wctx.p1().node())
798 if newid is not None:
798 if newid is not None:
799 # avoid import cycle mercurial.cmdutil -> mercurial.hg ->
799 # avoid import cycle mercurial.cmdutil -> mercurial.hg ->
800 # mercurial.cmdutil
800 # mercurial.cmdutil
801 from . import hg
801 from . import hg
802 hg.update(repo, newid[0], quietempty=True)
802 hg.update(repo, newid[0], quietempty=True)
803
803
804 ui.status(_("changed branch on %d changesets\n") % len(replacements))
804 ui.status(_("changed branch on %d changesets\n") % len(replacements))
805
805
806 def findrepo(p):
806 def findrepo(p):
807 while not os.path.isdir(os.path.join(p, ".hg")):
807 while not os.path.isdir(os.path.join(p, ".hg")):
808 oldp, p = p, os.path.dirname(p)
808 oldp, p = p, os.path.dirname(p)
809 if p == oldp:
809 if p == oldp:
810 return None
810 return None
811
811
812 return p
812 return p
813
813
814 def bailifchanged(repo, merge=True, hint=None):
814 def bailifchanged(repo, merge=True, hint=None):
815 """ enforce the precondition that working directory must be clean.
815 """ enforce the precondition that working directory must be clean.
816
816
817 'merge' can be set to false if a pending uncommitted merge should be
817 'merge' can be set to false if a pending uncommitted merge should be
818 ignored (such as when 'update --check' runs).
818 ignored (such as when 'update --check' runs).
819
819
820 'hint' is the usual hint given to Abort exception.
820 'hint' is the usual hint given to Abort exception.
821 """
821 """
822
822
823 if merge and repo.dirstate.p2() != nullid:
823 if merge and repo.dirstate.p2() != nullid:
824 raise error.Abort(_('outstanding uncommitted merge'), hint=hint)
824 raise error.Abort(_('outstanding uncommitted merge'), hint=hint)
825 modified, added, removed, deleted = repo.status()[:4]
825 modified, added, removed, deleted = repo.status()[:4]
826 if modified or added or removed or deleted:
826 if modified or added or removed or deleted:
827 raise error.Abort(_('uncommitted changes'), hint=hint)
827 raise error.Abort(_('uncommitted changes'), hint=hint)
828 ctx = repo[None]
828 ctx = repo[None]
829 for s in sorted(ctx.substate):
829 for s in sorted(ctx.substate):
830 ctx.sub(s).bailifchanged(hint=hint)
830 ctx.sub(s).bailifchanged(hint=hint)
831
831
832 def logmessage(ui, opts):
832 def logmessage(ui, opts):
833 """ get the log message according to -m and -l option """
833 """ get the log message according to -m and -l option """
834 message = opts.get('message')
834 message = opts.get('message')
835 logfile = opts.get('logfile')
835 logfile = opts.get('logfile')
836
836
837 if message and logfile:
837 if message and logfile:
838 raise error.Abort(_('options --message and --logfile are mutually '
838 raise error.Abort(_('options --message and --logfile are mutually '
839 'exclusive'))
839 'exclusive'))
840 if not message and logfile:
840 if not message and logfile:
841 try:
841 try:
842 if isstdiofilename(logfile):
842 if isstdiofilename(logfile):
843 message = ui.fin.read()
843 message = ui.fin.read()
844 else:
844 else:
845 message = '\n'.join(util.readfile(logfile).splitlines())
845 message = '\n'.join(util.readfile(logfile).splitlines())
846 except IOError as inst:
846 except IOError as inst:
847 raise error.Abort(_("can't read commit message '%s': %s") %
847 raise error.Abort(_("can't read commit message '%s': %s") %
848 (logfile, encoding.strtolocal(inst.strerror)))
848 (logfile, encoding.strtolocal(inst.strerror)))
849 return message
849 return message
850
850
851 def mergeeditform(ctxorbool, baseformname):
851 def mergeeditform(ctxorbool, baseformname):
852 """return appropriate editform name (referencing a committemplate)
852 """return appropriate editform name (referencing a committemplate)
853
853
854 'ctxorbool' is either a ctx to be committed, or a bool indicating whether
854 'ctxorbool' is either a ctx to be committed, or a bool indicating whether
855 merging is committed.
855 merging is committed.
856
856
857 This returns baseformname with '.merge' appended if it is a merge,
857 This returns baseformname with '.merge' appended if it is a merge,
858 otherwise '.normal' is appended.
858 otherwise '.normal' is appended.
859 """
859 """
860 if isinstance(ctxorbool, bool):
860 if isinstance(ctxorbool, bool):
861 if ctxorbool:
861 if ctxorbool:
862 return baseformname + ".merge"
862 return baseformname + ".merge"
863 elif len(ctxorbool.parents()) > 1:
863 elif len(ctxorbool.parents()) > 1:
864 return baseformname + ".merge"
864 return baseformname + ".merge"
865
865
866 return baseformname + ".normal"
866 return baseformname + ".normal"
867
867
868 def getcommiteditor(edit=False, finishdesc=None, extramsg=None,
868 def getcommiteditor(edit=False, finishdesc=None, extramsg=None,
869 editform='', **opts):
869 editform='', **opts):
870 """get appropriate commit message editor according to '--edit' option
870 """get appropriate commit message editor according to '--edit' option
871
871
872 'finishdesc' is a function to be called with edited commit message
872 'finishdesc' is a function to be called with edited commit message
873 (= 'description' of the new changeset) just after editing, but
873 (= 'description' of the new changeset) just after editing, but
874 before checking empty-ness. It should return actual text to be
874 before checking empty-ness. It should return actual text to be
875 stored into history. This allows to change description before
875 stored into history. This allows to change description before
876 storing.
876 storing.
877
877
878 'extramsg' is a extra message to be shown in the editor instead of
878 'extramsg' is a extra message to be shown in the editor instead of
879 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
879 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
880 is automatically added.
880 is automatically added.
881
881
882 'editform' is a dot-separated list of names, to distinguish
882 'editform' is a dot-separated list of names, to distinguish
883 the purpose of commit text editing.
883 the purpose of commit text editing.
884
884
885 'getcommiteditor' returns 'commitforceeditor' regardless of
885 'getcommiteditor' returns 'commitforceeditor' regardless of
886 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
886 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
887 they are specific for usage in MQ.
887 they are specific for usage in MQ.
888 """
888 """
889 if edit or finishdesc or extramsg:
889 if edit or finishdesc or extramsg:
890 return lambda r, c, s: commitforceeditor(r, c, s,
890 return lambda r, c, s: commitforceeditor(r, c, s,
891 finishdesc=finishdesc,
891 finishdesc=finishdesc,
892 extramsg=extramsg,
892 extramsg=extramsg,
893 editform=editform)
893 editform=editform)
894 elif editform:
894 elif editform:
895 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
895 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
896 else:
896 else:
897 return commiteditor
897 return commiteditor
898
898
899 def _escapecommandtemplate(tmpl):
899 def _escapecommandtemplate(tmpl):
900 parts = []
900 parts = []
901 for typ, start, end in templater.scantemplate(tmpl, raw=True):
901 for typ, start, end in templater.scantemplate(tmpl, raw=True):
902 if typ == b'string':
902 if typ == b'string':
903 parts.append(stringutil.escapestr(tmpl[start:end]))
903 parts.append(stringutil.escapestr(tmpl[start:end]))
904 else:
904 else:
905 parts.append(tmpl[start:end])
905 parts.append(tmpl[start:end])
906 return b''.join(parts)
906 return b''.join(parts)
907
907
908 def rendercommandtemplate(ui, tmpl, props):
908 def rendercommandtemplate(ui, tmpl, props):
909 r"""Expand a literal template 'tmpl' in a way suitable for command line
909 r"""Expand a literal template 'tmpl' in a way suitable for command line
910
910
911 '\' in outermost string is not taken as an escape character because it
911 '\' in outermost string is not taken as an escape character because it
912 is a directory separator on Windows.
912 is a directory separator on Windows.
913
913
914 >>> from . import ui as uimod
914 >>> from . import ui as uimod
915 >>> ui = uimod.ui()
915 >>> ui = uimod.ui()
916 >>> rendercommandtemplate(ui, b'c:\\{path}', {b'path': b'foo'})
916 >>> rendercommandtemplate(ui, b'c:\\{path}', {b'path': b'foo'})
917 'c:\\foo'
917 'c:\\foo'
918 >>> rendercommandtemplate(ui, b'{"c:\\{path}"}', {'path': b'foo'})
918 >>> rendercommandtemplate(ui, b'{"c:\\{path}"}', {'path': b'foo'})
919 'c:{path}'
919 'c:{path}'
920 """
920 """
921 if not tmpl:
921 if not tmpl:
922 return tmpl
922 return tmpl
923 t = formatter.maketemplater(ui, _escapecommandtemplate(tmpl))
923 t = formatter.maketemplater(ui, _escapecommandtemplate(tmpl))
924 return t.renderdefault(props)
924 return t.renderdefault(props)
925
925
926 def rendertemplate(ctx, tmpl, props=None):
926 def rendertemplate(ctx, tmpl, props=None):
927 """Expand a literal template 'tmpl' byte-string against one changeset
927 """Expand a literal template 'tmpl' byte-string against one changeset
928
928
929 Each props item must be a stringify-able value or a callable returning
929 Each props item must be a stringify-able value or a callable returning
930 such value, i.e. no bare list nor dict should be passed.
930 such value, i.e. no bare list nor dict should be passed.
931 """
931 """
932 repo = ctx.repo()
932 repo = ctx.repo()
933 tres = formatter.templateresources(repo.ui, repo)
933 tres = formatter.templateresources(repo.ui, repo)
934 t = formatter.maketemplater(repo.ui, tmpl, defaults=templatekw.keywords,
934 t = formatter.maketemplater(repo.ui, tmpl, defaults=templatekw.keywords,
935 resources=tres)
935 resources=tres)
936 mapping = {'ctx': ctx}
936 mapping = {'ctx': ctx}
937 if props:
937 if props:
938 mapping.update(props)
938 mapping.update(props)
939 return t.renderdefault(mapping)
939 return t.renderdefault(mapping)
940
940
941 def _buildfntemplate(pat, total=None, seqno=None, revwidth=None, pathname=None):
941 def _buildfntemplate(pat, total=None, seqno=None, revwidth=None, pathname=None):
942 r"""Convert old-style filename format string to template string
942 r"""Convert old-style filename format string to template string
943
943
944 >>> _buildfntemplate(b'foo-%b-%n.patch', seqno=0)
944 >>> _buildfntemplate(b'foo-%b-%n.patch', seqno=0)
945 'foo-{reporoot|basename}-{seqno}.patch'
945 'foo-{reporoot|basename}-{seqno}.patch'
946 >>> _buildfntemplate(b'%R{tags % "{tag}"}%H')
946 >>> _buildfntemplate(b'%R{tags % "{tag}"}%H')
947 '{rev}{tags % "{tag}"}{node}'
947 '{rev}{tags % "{tag}"}{node}'
948
948
949 '\' in outermost strings has to be escaped because it is a directory
949 '\' in outermost strings has to be escaped because it is a directory
950 separator on Windows:
950 separator on Windows:
951
951
952 >>> _buildfntemplate(b'c:\\tmp\\%R\\%n.patch', seqno=0)
952 >>> _buildfntemplate(b'c:\\tmp\\%R\\%n.patch', seqno=0)
953 'c:\\\\tmp\\\\{rev}\\\\{seqno}.patch'
953 'c:\\\\tmp\\\\{rev}\\\\{seqno}.patch'
954 >>> _buildfntemplate(b'\\\\foo\\bar.patch')
954 >>> _buildfntemplate(b'\\\\foo\\bar.patch')
955 '\\\\\\\\foo\\\\bar.patch'
955 '\\\\\\\\foo\\\\bar.patch'
956 >>> _buildfntemplate(b'\\{tags % "{tag}"}')
956 >>> _buildfntemplate(b'\\{tags % "{tag}"}')
957 '\\\\{tags % "{tag}"}'
957 '\\\\{tags % "{tag}"}'
958
958
959 but inner strings follow the template rules (i.e. '\' is taken as an
959 but inner strings follow the template rules (i.e. '\' is taken as an
960 escape character):
960 escape character):
961
961
962 >>> _buildfntemplate(br'{"c:\tmp"}', seqno=0)
962 >>> _buildfntemplate(br'{"c:\tmp"}', seqno=0)
963 '{"c:\\tmp"}'
963 '{"c:\\tmp"}'
964 """
964 """
965 expander = {
965 expander = {
966 b'H': b'{node}',
966 b'H': b'{node}',
967 b'R': b'{rev}',
967 b'R': b'{rev}',
968 b'h': b'{node|short}',
968 b'h': b'{node|short}',
969 b'm': br'{sub(r"[^\w]", "_", desc|firstline)}',
969 b'm': br'{sub(r"[^\w]", "_", desc|firstline)}',
970 b'r': b'{if(revwidth, pad(rev, revwidth, "0", left=True), rev)}',
970 b'r': b'{if(revwidth, pad(rev, revwidth, "0", left=True), rev)}',
971 b'%': b'%',
971 b'%': b'%',
972 b'b': b'{reporoot|basename}',
972 b'b': b'{reporoot|basename}',
973 }
973 }
974 if total is not None:
974 if total is not None:
975 expander[b'N'] = b'{total}'
975 expander[b'N'] = b'{total}'
976 if seqno is not None:
976 if seqno is not None:
977 expander[b'n'] = b'{seqno}'
977 expander[b'n'] = b'{seqno}'
978 if total is not None and seqno is not None:
978 if total is not None and seqno is not None:
979 expander[b'n'] = b'{pad(seqno, total|stringify|count, "0", left=True)}'
979 expander[b'n'] = b'{pad(seqno, total|stringify|count, "0", left=True)}'
980 if pathname is not None:
980 if pathname is not None:
981 expander[b's'] = b'{pathname|basename}'
981 expander[b's'] = b'{pathname|basename}'
982 expander[b'd'] = b'{if(pathname|dirname, pathname|dirname, ".")}'
982 expander[b'd'] = b'{if(pathname|dirname, pathname|dirname, ".")}'
983 expander[b'p'] = b'{pathname}'
983 expander[b'p'] = b'{pathname}'
984
984
985 newname = []
985 newname = []
986 for typ, start, end in templater.scantemplate(pat, raw=True):
986 for typ, start, end in templater.scantemplate(pat, raw=True):
987 if typ != b'string':
987 if typ != b'string':
988 newname.append(pat[start:end])
988 newname.append(pat[start:end])
989 continue
989 continue
990 i = start
990 i = start
991 while i < end:
991 while i < end:
992 n = pat.find(b'%', i, end)
992 n = pat.find(b'%', i, end)
993 if n < 0:
993 if n < 0:
994 newname.append(stringutil.escapestr(pat[i:end]))
994 newname.append(stringutil.escapestr(pat[i:end]))
995 break
995 break
996 newname.append(stringutil.escapestr(pat[i:n]))
996 newname.append(stringutil.escapestr(pat[i:n]))
997 if n + 2 > end:
997 if n + 2 > end:
998 raise error.Abort(_("incomplete format spec in output "
998 raise error.Abort(_("incomplete format spec in output "
999 "filename"))
999 "filename"))
1000 c = pat[n + 1:n + 2]
1000 c = pat[n + 1:n + 2]
1001 i = n + 2
1001 i = n + 2
1002 try:
1002 try:
1003 newname.append(expander[c])
1003 newname.append(expander[c])
1004 except KeyError:
1004 except KeyError:
1005 raise error.Abort(_("invalid format spec '%%%s' in output "
1005 raise error.Abort(_("invalid format spec '%%%s' in output "
1006 "filename") % c)
1006 "filename") % c)
1007 return ''.join(newname)
1007 return ''.join(newname)
1008
1008
1009 def makefilename(ctx, pat, **props):
1009 def makefilename(ctx, pat, **props):
1010 if not pat:
1010 if not pat:
1011 return pat
1011 return pat
1012 tmpl = _buildfntemplate(pat, **props)
1012 tmpl = _buildfntemplate(pat, **props)
1013 # BUG: alias expansion shouldn't be made against template fragments
1013 # BUG: alias expansion shouldn't be made against template fragments
1014 # rewritten from %-format strings, but we have no easy way to partially
1014 # rewritten from %-format strings, but we have no easy way to partially
1015 # disable the expansion.
1015 # disable the expansion.
1016 return rendertemplate(ctx, tmpl, pycompat.byteskwargs(props))
1016 return rendertemplate(ctx, tmpl, pycompat.byteskwargs(props))
1017
1017
1018 def isstdiofilename(pat):
1018 def isstdiofilename(pat):
1019 """True if the given pat looks like a filename denoting stdin/stdout"""
1019 """True if the given pat looks like a filename denoting stdin/stdout"""
1020 return not pat or pat == '-'
1020 return not pat or pat == '-'
1021
1021
1022 class _unclosablefile(object):
1022 class _unclosablefile(object):
1023 def __init__(self, fp):
1023 def __init__(self, fp):
1024 self._fp = fp
1024 self._fp = fp
1025
1025
1026 def close(self):
1026 def close(self):
1027 pass
1027 pass
1028
1028
1029 def __iter__(self):
1029 def __iter__(self):
1030 return iter(self._fp)
1030 return iter(self._fp)
1031
1031
1032 def __getattr__(self, attr):
1032 def __getattr__(self, attr):
1033 return getattr(self._fp, attr)
1033 return getattr(self._fp, attr)
1034
1034
1035 def __enter__(self):
1035 def __enter__(self):
1036 return self
1036 return self
1037
1037
1038 def __exit__(self, exc_type, exc_value, exc_tb):
1038 def __exit__(self, exc_type, exc_value, exc_tb):
1039 pass
1039 pass
1040
1040
1041 def makefileobj(ctx, pat, mode='wb', **props):
1041 def makefileobj(ctx, pat, mode='wb', **props):
1042 writable = mode not in ('r', 'rb')
1042 writable = mode not in ('r', 'rb')
1043
1043
1044 if isstdiofilename(pat):
1044 if isstdiofilename(pat):
1045 repo = ctx.repo()
1045 repo = ctx.repo()
1046 if writable:
1046 if writable:
1047 fp = repo.ui.fout
1047 fp = repo.ui.fout
1048 else:
1048 else:
1049 fp = repo.ui.fin
1049 fp = repo.ui.fin
1050 return _unclosablefile(fp)
1050 return _unclosablefile(fp)
1051 fn = makefilename(ctx, pat, **props)
1051 fn = makefilename(ctx, pat, **props)
1052 return open(fn, mode)
1052 return open(fn, mode)
1053
1053
1054 def openstorage(repo, cmd, file_, opts, returnrevlog=False):
1054 def openstorage(repo, cmd, file_, opts, returnrevlog=False):
1055 """opens the changelog, manifest, a filelog or a given revlog"""
1055 """opens the changelog, manifest, a filelog or a given revlog"""
1056 cl = opts['changelog']
1056 cl = opts['changelog']
1057 mf = opts['manifest']
1057 mf = opts['manifest']
1058 dir = opts['dir']
1058 dir = opts['dir']
1059 msg = None
1059 msg = None
1060 if cl and mf:
1060 if cl and mf:
1061 msg = _('cannot specify --changelog and --manifest at the same time')
1061 msg = _('cannot specify --changelog and --manifest at the same time')
1062 elif cl and dir:
1062 elif cl and dir:
1063 msg = _('cannot specify --changelog and --dir at the same time')
1063 msg = _('cannot specify --changelog and --dir at the same time')
1064 elif cl or mf or dir:
1064 elif cl or mf or dir:
1065 if file_:
1065 if file_:
1066 msg = _('cannot specify filename with --changelog or --manifest')
1066 msg = _('cannot specify filename with --changelog or --manifest')
1067 elif not repo:
1067 elif not repo:
1068 msg = _('cannot specify --changelog or --manifest or --dir '
1068 msg = _('cannot specify --changelog or --manifest or --dir '
1069 'without a repository')
1069 'without a repository')
1070 if msg:
1070 if msg:
1071 raise error.Abort(msg)
1071 raise error.Abort(msg)
1072
1072
1073 r = None
1073 r = None
1074 if repo:
1074 if repo:
1075 if cl:
1075 if cl:
1076 r = repo.unfiltered().changelog
1076 r = repo.unfiltered().changelog
1077 elif dir:
1077 elif dir:
1078 if 'treemanifest' not in repo.requirements:
1078 if 'treemanifest' not in repo.requirements:
1079 raise error.Abort(_("--dir can only be used on repos with "
1079 raise error.Abort(_("--dir can only be used on repos with "
1080 "treemanifest enabled"))
1080 "treemanifest enabled"))
1081 if not dir.endswith('/'):
1081 if not dir.endswith('/'):
1082 dir = dir + '/'
1082 dir = dir + '/'
1083 dirlog = repo.manifestlog.getstorage(dir)
1083 dirlog = repo.manifestlog.getstorage(dir)
1084 if len(dirlog):
1084 if len(dirlog):
1085 r = dirlog
1085 r = dirlog
1086 elif mf:
1086 elif mf:
1087 r = repo.manifestlog.getstorage(b'')
1087 r = repo.manifestlog.getstorage(b'')
1088 elif file_:
1088 elif file_:
1089 filelog = repo.file(file_)
1089 filelog = repo.file(file_)
1090 if len(filelog):
1090 if len(filelog):
1091 r = filelog
1091 r = filelog
1092
1092
1093 # Not all storage may be revlogs. If requested, try to return an actual
1093 # Not all storage may be revlogs. If requested, try to return an actual
1094 # revlog instance.
1094 # revlog instance.
1095 if returnrevlog:
1095 if returnrevlog:
1096 if isinstance(r, revlog.revlog):
1096 if isinstance(r, revlog.revlog):
1097 pass
1097 pass
1098 elif util.safehasattr(r, '_revlog'):
1098 elif util.safehasattr(r, '_revlog'):
1099 r = r._revlog
1099 r = r._revlog
1100 elif r is not None:
1100 elif r is not None:
1101 raise error.Abort(_('%r does not appear to be a revlog') % r)
1101 raise error.Abort(_('%r does not appear to be a revlog') % r)
1102
1102
1103 if not r:
1103 if not r:
1104 if not returnrevlog:
1104 if not returnrevlog:
1105 raise error.Abort(_('cannot give path to non-revlog'))
1105 raise error.Abort(_('cannot give path to non-revlog'))
1106
1106
1107 if not file_:
1107 if not file_:
1108 raise error.CommandError(cmd, _('invalid arguments'))
1108 raise error.CommandError(cmd, _('invalid arguments'))
1109 if not os.path.isfile(file_):
1109 if not os.path.isfile(file_):
1110 raise error.Abort(_("revlog '%s' not found") % file_)
1110 raise error.Abort(_("revlog '%s' not found") % file_)
1111 r = revlog.revlog(vfsmod.vfs(encoding.getcwd(), audit=False),
1111 r = revlog.revlog(vfsmod.vfs(encoding.getcwd(), audit=False),
1112 file_[:-2] + ".i")
1112 file_[:-2] + ".i")
1113 return r
1113 return r
1114
1114
1115 def openrevlog(repo, cmd, file_, opts):
1115 def openrevlog(repo, cmd, file_, opts):
1116 """Obtain a revlog backing storage of an item.
1116 """Obtain a revlog backing storage of an item.
1117
1117
1118 This is similar to ``openstorage()`` except it always returns a revlog.
1118 This is similar to ``openstorage()`` except it always returns a revlog.
1119
1119
1120 In most cases, a caller cares about the main storage object - not the
1120 In most cases, a caller cares about the main storage object - not the
1121 revlog backing it. Therefore, this function should only be used by code
1121 revlog backing it. Therefore, this function should only be used by code
1122 that needs to examine low-level revlog implementation details. e.g. debug
1122 that needs to examine low-level revlog implementation details. e.g. debug
1123 commands.
1123 commands.
1124 """
1124 """
1125 return openstorage(repo, cmd, file_, opts, returnrevlog=True)
1125 return openstorage(repo, cmd, file_, opts, returnrevlog=True)
1126
1126
1127 def copy(ui, repo, pats, opts, rename=False):
1127 def copy(ui, repo, pats, opts, rename=False):
1128 # called with the repo lock held
1128 # called with the repo lock held
1129 #
1129 #
1130 # hgsep => pathname that uses "/" to separate directories
1130 # hgsep => pathname that uses "/" to separate directories
1131 # ossep => pathname that uses os.sep to separate directories
1131 # ossep => pathname that uses os.sep to separate directories
1132 cwd = repo.getcwd()
1132 cwd = repo.getcwd()
1133 targets = {}
1133 targets = {}
1134 after = opts.get("after")
1134 after = opts.get("after")
1135 dryrun = opts.get("dry_run")
1135 dryrun = opts.get("dry_run")
1136 wctx = repo[None]
1136 wctx = repo[None]
1137
1137
1138 def walkpat(pat):
1138 def walkpat(pat):
1139 srcs = []
1139 srcs = []
1140 if after:
1140 if after:
1141 badstates = '?'
1141 badstates = '?'
1142 else:
1142 else:
1143 badstates = '?r'
1143 badstates = '?r'
1144 m = scmutil.match(wctx, [pat], opts, globbed=True)
1144 m = scmutil.match(wctx, [pat], opts, globbed=True)
1145 for abs in wctx.walk(m):
1145 for abs in wctx.walk(m):
1146 state = repo.dirstate[abs]
1146 state = repo.dirstate[abs]
1147 rel = m.rel(abs)
1147 rel = m.rel(abs)
1148 exact = m.exact(abs)
1148 exact = m.exact(abs)
1149 if state in badstates:
1149 if state in badstates:
1150 if exact and state == '?':
1150 if exact and state == '?':
1151 ui.warn(_('%s: not copying - file is not managed\n') % rel)
1151 ui.warn(_('%s: not copying - file is not managed\n') % rel)
1152 if exact and state == 'r':
1152 if exact and state == 'r':
1153 ui.warn(_('%s: not copying - file has been marked for'
1153 ui.warn(_('%s: not copying - file has been marked for'
1154 ' remove\n') % rel)
1154 ' remove\n') % rel)
1155 continue
1155 continue
1156 # abs: hgsep
1156 # abs: hgsep
1157 # rel: ossep
1157 # rel: ossep
1158 srcs.append((abs, rel, exact))
1158 srcs.append((abs, rel, exact))
1159 return srcs
1159 return srcs
1160
1160
1161 # abssrc: hgsep
1161 # abssrc: hgsep
1162 # relsrc: ossep
1162 # relsrc: ossep
1163 # otarget: ossep
1163 # otarget: ossep
1164 def copyfile(abssrc, relsrc, otarget, exact):
1164 def copyfile(abssrc, relsrc, otarget, exact):
1165 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
1165 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
1166 if '/' in abstarget:
1166 if '/' in abstarget:
1167 # We cannot normalize abstarget itself, this would prevent
1167 # We cannot normalize abstarget itself, this would prevent
1168 # case only renames, like a => A.
1168 # case only renames, like a => A.
1169 abspath, absname = abstarget.rsplit('/', 1)
1169 abspath, absname = abstarget.rsplit('/', 1)
1170 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
1170 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
1171 reltarget = repo.pathto(abstarget, cwd)
1171 reltarget = repo.pathto(abstarget, cwd)
1172 target = repo.wjoin(abstarget)
1172 target = repo.wjoin(abstarget)
1173 src = repo.wjoin(abssrc)
1173 src = repo.wjoin(abssrc)
1174 state = repo.dirstate[abstarget]
1174 state = repo.dirstate[abstarget]
1175
1175
1176 scmutil.checkportable(ui, abstarget)
1176 scmutil.checkportable(ui, abstarget)
1177
1177
1178 # check for collisions
1178 # check for collisions
1179 prevsrc = targets.get(abstarget)
1179 prevsrc = targets.get(abstarget)
1180 if prevsrc is not None:
1180 if prevsrc is not None:
1181 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
1181 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
1182 (reltarget, repo.pathto(abssrc, cwd),
1182 (reltarget, repo.pathto(abssrc, cwd),
1183 repo.pathto(prevsrc, cwd)))
1183 repo.pathto(prevsrc, cwd)))
1184 return True # report a failure
1184 return True # report a failure
1185
1185
1186 # check for overwrites
1186 # check for overwrites
1187 exists = os.path.lexists(target)
1187 exists = os.path.lexists(target)
1188 samefile = False
1188 samefile = False
1189 if exists and abssrc != abstarget:
1189 if exists and abssrc != abstarget:
1190 if (repo.dirstate.normalize(abssrc) ==
1190 if (repo.dirstate.normalize(abssrc) ==
1191 repo.dirstate.normalize(abstarget)):
1191 repo.dirstate.normalize(abstarget)):
1192 if not rename:
1192 if not rename:
1193 ui.warn(_("%s: can't copy - same file\n") % reltarget)
1193 ui.warn(_("%s: can't copy - same file\n") % reltarget)
1194 return True # report a failure
1194 return True # report a failure
1195 exists = False
1195 exists = False
1196 samefile = True
1196 samefile = True
1197
1197
1198 if not after and exists or after and state in 'mn':
1198 if not after and exists or after and state in 'mn':
1199 if not opts['force']:
1199 if not opts['force']:
1200 if state in 'mn':
1200 if state in 'mn':
1201 msg = _('%s: not overwriting - file already committed\n')
1201 msg = _('%s: not overwriting - file already committed\n')
1202 if after:
1202 if after:
1203 flags = '--after --force'
1203 flags = '--after --force'
1204 else:
1204 else:
1205 flags = '--force'
1205 flags = '--force'
1206 if rename:
1206 if rename:
1207 hint = _("('hg rename %s' to replace the file by "
1207 hint = _("('hg rename %s' to replace the file by "
1208 'recording a rename)\n') % flags
1208 'recording a rename)\n') % flags
1209 else:
1209 else:
1210 hint = _("('hg copy %s' to replace the file by "
1210 hint = _("('hg copy %s' to replace the file by "
1211 'recording a copy)\n') % flags
1211 'recording a copy)\n') % flags
1212 else:
1212 else:
1213 msg = _('%s: not overwriting - file exists\n')
1213 msg = _('%s: not overwriting - file exists\n')
1214 if rename:
1214 if rename:
1215 hint = _("('hg rename --after' to record the rename)\n")
1215 hint = _("('hg rename --after' to record the rename)\n")
1216 else:
1216 else:
1217 hint = _("('hg copy --after' to record the copy)\n")
1217 hint = _("('hg copy --after' to record the copy)\n")
1218 ui.warn(msg % reltarget)
1218 ui.warn(msg % reltarget)
1219 ui.warn(hint)
1219 ui.warn(hint)
1220 return True # report a failure
1220 return True # report a failure
1221
1221
1222 if after:
1222 if after:
1223 if not exists:
1223 if not exists:
1224 if rename:
1224 if rename:
1225 ui.warn(_('%s: not recording move - %s does not exist\n') %
1225 ui.warn(_('%s: not recording move - %s does not exist\n') %
1226 (relsrc, reltarget))
1226 (relsrc, reltarget))
1227 else:
1227 else:
1228 ui.warn(_('%s: not recording copy - %s does not exist\n') %
1228 ui.warn(_('%s: not recording copy - %s does not exist\n') %
1229 (relsrc, reltarget))
1229 (relsrc, reltarget))
1230 return True # report a failure
1230 return True # report a failure
1231 elif not dryrun:
1231 elif not dryrun:
1232 try:
1232 try:
1233 if exists:
1233 if exists:
1234 os.unlink(target)
1234 os.unlink(target)
1235 targetdir = os.path.dirname(target) or '.'
1235 targetdir = os.path.dirname(target) or '.'
1236 if not os.path.isdir(targetdir):
1236 if not os.path.isdir(targetdir):
1237 os.makedirs(targetdir)
1237 os.makedirs(targetdir)
1238 if samefile:
1238 if samefile:
1239 tmp = target + "~hgrename"
1239 tmp = target + "~hgrename"
1240 os.rename(src, tmp)
1240 os.rename(src, tmp)
1241 os.rename(tmp, target)
1241 os.rename(tmp, target)
1242 else:
1242 else:
1243 # Preserve stat info on renames, not on copies; this matches
1243 # Preserve stat info on renames, not on copies; this matches
1244 # Linux CLI behavior.
1244 # Linux CLI behavior.
1245 util.copyfile(src, target, copystat=rename)
1245 util.copyfile(src, target, copystat=rename)
1246 srcexists = True
1246 srcexists = True
1247 except IOError as inst:
1247 except IOError as inst:
1248 if inst.errno == errno.ENOENT:
1248 if inst.errno == errno.ENOENT:
1249 ui.warn(_('%s: deleted in working directory\n') % relsrc)
1249 ui.warn(_('%s: deleted in working directory\n') % relsrc)
1250 srcexists = False
1250 srcexists = False
1251 else:
1251 else:
1252 ui.warn(_('%s: cannot copy - %s\n') %
1252 ui.warn(_('%s: cannot copy - %s\n') %
1253 (relsrc, encoding.strtolocal(inst.strerror)))
1253 (relsrc, encoding.strtolocal(inst.strerror)))
1254 return True # report a failure
1254 return True # report a failure
1255
1255
1256 if ui.verbose or not exact:
1256 if ui.verbose or not exact:
1257 if rename:
1257 if rename:
1258 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
1258 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
1259 else:
1259 else:
1260 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
1260 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
1261
1261
1262 targets[abstarget] = abssrc
1262 targets[abstarget] = abssrc
1263
1263
1264 # fix up dirstate
1264 # fix up dirstate
1265 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
1265 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
1266 dryrun=dryrun, cwd=cwd)
1266 dryrun=dryrun, cwd=cwd)
1267 if rename and not dryrun:
1267 if rename and not dryrun:
1268 if not after and srcexists and not samefile:
1268 if not after and srcexists and not samefile:
1269 rmdir = repo.ui.configbool('experimental', 'removeemptydirs')
1269 rmdir = repo.ui.configbool('experimental', 'removeemptydirs')
1270 repo.wvfs.unlinkpath(abssrc, rmdir=rmdir)
1270 repo.wvfs.unlinkpath(abssrc, rmdir=rmdir)
1271 wctx.forget([abssrc])
1271 wctx.forget([abssrc])
1272
1272
1273 # pat: ossep
1273 # pat: ossep
1274 # dest ossep
1274 # dest ossep
1275 # srcs: list of (hgsep, hgsep, ossep, bool)
1275 # srcs: list of (hgsep, hgsep, ossep, bool)
1276 # return: function that takes hgsep and returns ossep
1276 # return: function that takes hgsep and returns ossep
1277 def targetpathfn(pat, dest, srcs):
1277 def targetpathfn(pat, dest, srcs):
1278 if os.path.isdir(pat):
1278 if os.path.isdir(pat):
1279 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1279 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1280 abspfx = util.localpath(abspfx)
1280 abspfx = util.localpath(abspfx)
1281 if destdirexists:
1281 if destdirexists:
1282 striplen = len(os.path.split(abspfx)[0])
1282 striplen = len(os.path.split(abspfx)[0])
1283 else:
1283 else:
1284 striplen = len(abspfx)
1284 striplen = len(abspfx)
1285 if striplen:
1285 if striplen:
1286 striplen += len(pycompat.ossep)
1286 striplen += len(pycompat.ossep)
1287 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
1287 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
1288 elif destdirexists:
1288 elif destdirexists:
1289 res = lambda p: os.path.join(dest,
1289 res = lambda p: os.path.join(dest,
1290 os.path.basename(util.localpath(p)))
1290 os.path.basename(util.localpath(p)))
1291 else:
1291 else:
1292 res = lambda p: dest
1292 res = lambda p: dest
1293 return res
1293 return res
1294
1294
1295 # pat: ossep
1295 # pat: ossep
1296 # dest ossep
1296 # dest ossep
1297 # srcs: list of (hgsep, hgsep, ossep, bool)
1297 # srcs: list of (hgsep, hgsep, ossep, bool)
1298 # return: function that takes hgsep and returns ossep
1298 # return: function that takes hgsep and returns ossep
1299 def targetpathafterfn(pat, dest, srcs):
1299 def targetpathafterfn(pat, dest, srcs):
1300 if matchmod.patkind(pat):
1300 if matchmod.patkind(pat):
1301 # a mercurial pattern
1301 # a mercurial pattern
1302 res = lambda p: os.path.join(dest,
1302 res = lambda p: os.path.join(dest,
1303 os.path.basename(util.localpath(p)))
1303 os.path.basename(util.localpath(p)))
1304 else:
1304 else:
1305 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1305 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1306 if len(abspfx) < len(srcs[0][0]):
1306 if len(abspfx) < len(srcs[0][0]):
1307 # A directory. Either the target path contains the last
1307 # A directory. Either the target path contains the last
1308 # component of the source path or it does not.
1308 # component of the source path or it does not.
1309 def evalpath(striplen):
1309 def evalpath(striplen):
1310 score = 0
1310 score = 0
1311 for s in srcs:
1311 for s in srcs:
1312 t = os.path.join(dest, util.localpath(s[0])[striplen:])
1312 t = os.path.join(dest, util.localpath(s[0])[striplen:])
1313 if os.path.lexists(t):
1313 if os.path.lexists(t):
1314 score += 1
1314 score += 1
1315 return score
1315 return score
1316
1316
1317 abspfx = util.localpath(abspfx)
1317 abspfx = util.localpath(abspfx)
1318 striplen = len(abspfx)
1318 striplen = len(abspfx)
1319 if striplen:
1319 if striplen:
1320 striplen += len(pycompat.ossep)
1320 striplen += len(pycompat.ossep)
1321 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
1321 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
1322 score = evalpath(striplen)
1322 score = evalpath(striplen)
1323 striplen1 = len(os.path.split(abspfx)[0])
1323 striplen1 = len(os.path.split(abspfx)[0])
1324 if striplen1:
1324 if striplen1:
1325 striplen1 += len(pycompat.ossep)
1325 striplen1 += len(pycompat.ossep)
1326 if evalpath(striplen1) > score:
1326 if evalpath(striplen1) > score:
1327 striplen = striplen1
1327 striplen = striplen1
1328 res = lambda p: os.path.join(dest,
1328 res = lambda p: os.path.join(dest,
1329 util.localpath(p)[striplen:])
1329 util.localpath(p)[striplen:])
1330 else:
1330 else:
1331 # a file
1331 # a file
1332 if destdirexists:
1332 if destdirexists:
1333 res = lambda p: os.path.join(dest,
1333 res = lambda p: os.path.join(dest,
1334 os.path.basename(util.localpath(p)))
1334 os.path.basename(util.localpath(p)))
1335 else:
1335 else:
1336 res = lambda p: dest
1336 res = lambda p: dest
1337 return res
1337 return res
1338
1338
1339 pats = scmutil.expandpats(pats)
1339 pats = scmutil.expandpats(pats)
1340 if not pats:
1340 if not pats:
1341 raise error.Abort(_('no source or destination specified'))
1341 raise error.Abort(_('no source or destination specified'))
1342 if len(pats) == 1:
1342 if len(pats) == 1:
1343 raise error.Abort(_('no destination specified'))
1343 raise error.Abort(_('no destination specified'))
1344 dest = pats.pop()
1344 dest = pats.pop()
1345 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
1345 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
1346 if not destdirexists:
1346 if not destdirexists:
1347 if len(pats) > 1 or matchmod.patkind(pats[0]):
1347 if len(pats) > 1 or matchmod.patkind(pats[0]):
1348 raise error.Abort(_('with multiple sources, destination must be an '
1348 raise error.Abort(_('with multiple sources, destination must be an '
1349 'existing directory'))
1349 'existing directory'))
1350 if util.endswithsep(dest):
1350 if util.endswithsep(dest):
1351 raise error.Abort(_('destination %s is not a directory') % dest)
1351 raise error.Abort(_('destination %s is not a directory') % dest)
1352
1352
1353 tfn = targetpathfn
1353 tfn = targetpathfn
1354 if after:
1354 if after:
1355 tfn = targetpathafterfn
1355 tfn = targetpathafterfn
1356 copylist = []
1356 copylist = []
1357 for pat in pats:
1357 for pat in pats:
1358 srcs = walkpat(pat)
1358 srcs = walkpat(pat)
1359 if not srcs:
1359 if not srcs:
1360 continue
1360 continue
1361 copylist.append((tfn(pat, dest, srcs), srcs))
1361 copylist.append((tfn(pat, dest, srcs), srcs))
1362 if not copylist:
1362 if not copylist:
1363 raise error.Abort(_('no files to copy'))
1363 raise error.Abort(_('no files to copy'))
1364
1364
1365 errors = 0
1365 errors = 0
1366 for targetpath, srcs in copylist:
1366 for targetpath, srcs in copylist:
1367 for abssrc, relsrc, exact in srcs:
1367 for abssrc, relsrc, exact in srcs:
1368 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
1368 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
1369 errors += 1
1369 errors += 1
1370
1370
1371 return errors != 0
1371 return errors != 0
1372
1372
1373 ## facility to let extension process additional data into an import patch
1373 ## facility to let extension process additional data into an import patch
1374 # list of identifier to be executed in order
1374 # list of identifier to be executed in order
1375 extrapreimport = [] # run before commit
1375 extrapreimport = [] # run before commit
1376 extrapostimport = [] # run after commit
1376 extrapostimport = [] # run after commit
1377 # mapping from identifier to actual import function
1377 # mapping from identifier to actual import function
1378 #
1378 #
1379 # 'preimport' are run before the commit is made and are provided the following
1379 # 'preimport' are run before the commit is made and are provided the following
1380 # arguments:
1380 # arguments:
1381 # - repo: the localrepository instance,
1381 # - repo: the localrepository instance,
1382 # - patchdata: data extracted from patch header (cf m.patch.patchheadermap),
1382 # - patchdata: data extracted from patch header (cf m.patch.patchheadermap),
1383 # - extra: the future extra dictionary of the changeset, please mutate it,
1383 # - extra: the future extra dictionary of the changeset, please mutate it,
1384 # - opts: the import options.
1384 # - opts: the import options.
1385 # XXX ideally, we would just pass an ctx ready to be computed, that would allow
1385 # XXX ideally, we would just pass an ctx ready to be computed, that would allow
1386 # mutation of in memory commit and more. Feel free to rework the code to get
1386 # mutation of in memory commit and more. Feel free to rework the code to get
1387 # there.
1387 # there.
1388 extrapreimportmap = {}
1388 extrapreimportmap = {}
1389 # 'postimport' are run after the commit is made and are provided the following
1389 # 'postimport' are run after the commit is made and are provided the following
1390 # argument:
1390 # argument:
1391 # - ctx: the changectx created by import.
1391 # - ctx: the changectx created by import.
1392 extrapostimportmap = {}
1392 extrapostimportmap = {}
1393
1393
1394 def tryimportone(ui, repo, patchdata, parents, opts, msgs, updatefunc):
1394 def tryimportone(ui, repo, patchdata, parents, opts, msgs, updatefunc):
1395 """Utility function used by commands.import to import a single patch
1395 """Utility function used by commands.import to import a single patch
1396
1396
1397 This function is explicitly defined here to help the evolve extension to
1397 This function is explicitly defined here to help the evolve extension to
1398 wrap this part of the import logic.
1398 wrap this part of the import logic.
1399
1399
1400 The API is currently a bit ugly because it a simple code translation from
1400 The API is currently a bit ugly because it a simple code translation from
1401 the import command. Feel free to make it better.
1401 the import command. Feel free to make it better.
1402
1402
1403 :patchdata: a dictionary containing parsed patch data (such as from
1403 :patchdata: a dictionary containing parsed patch data (such as from
1404 ``patch.extract()``)
1404 ``patch.extract()``)
1405 :parents: nodes that will be parent of the created commit
1405 :parents: nodes that will be parent of the created commit
1406 :opts: the full dict of option passed to the import command
1406 :opts: the full dict of option passed to the import command
1407 :msgs: list to save commit message to.
1407 :msgs: list to save commit message to.
1408 (used in case we need to save it when failing)
1408 (used in case we need to save it when failing)
1409 :updatefunc: a function that update a repo to a given node
1409 :updatefunc: a function that update a repo to a given node
1410 updatefunc(<repo>, <node>)
1410 updatefunc(<repo>, <node>)
1411 """
1411 """
1412 # avoid cycle context -> subrepo -> cmdutil
1412 # avoid cycle context -> subrepo -> cmdutil
1413 from . import context
1413 from . import context
1414
1414
1415 tmpname = patchdata.get('filename')
1415 tmpname = patchdata.get('filename')
1416 message = patchdata.get('message')
1416 message = patchdata.get('message')
1417 user = opts.get('user') or patchdata.get('user')
1417 user = opts.get('user') or patchdata.get('user')
1418 date = opts.get('date') or patchdata.get('date')
1418 date = opts.get('date') or patchdata.get('date')
1419 branch = patchdata.get('branch')
1419 branch = patchdata.get('branch')
1420 nodeid = patchdata.get('nodeid')
1420 nodeid = patchdata.get('nodeid')
1421 p1 = patchdata.get('p1')
1421 p1 = patchdata.get('p1')
1422 p2 = patchdata.get('p2')
1422 p2 = patchdata.get('p2')
1423
1423
1424 nocommit = opts.get('no_commit')
1424 nocommit = opts.get('no_commit')
1425 importbranch = opts.get('import_branch')
1425 importbranch = opts.get('import_branch')
1426 update = not opts.get('bypass')
1426 update = not opts.get('bypass')
1427 strip = opts["strip"]
1427 strip = opts["strip"]
1428 prefix = opts["prefix"]
1428 prefix = opts["prefix"]
1429 sim = float(opts.get('similarity') or 0)
1429 sim = float(opts.get('similarity') or 0)
1430
1430
1431 if not tmpname:
1431 if not tmpname:
1432 return None, None, False
1432 return None, None, False
1433
1433
1434 rejects = False
1434 rejects = False
1435
1435
1436 cmdline_message = logmessage(ui, opts)
1436 cmdline_message = logmessage(ui, opts)
1437 if cmdline_message:
1437 if cmdline_message:
1438 # pickup the cmdline msg
1438 # pickup the cmdline msg
1439 message = cmdline_message
1439 message = cmdline_message
1440 elif message:
1440 elif message:
1441 # pickup the patch msg
1441 # pickup the patch msg
1442 message = message.strip()
1442 message = message.strip()
1443 else:
1443 else:
1444 # launch the editor
1444 # launch the editor
1445 message = None
1445 message = None
1446 ui.debug('message:\n%s\n' % (message or ''))
1446 ui.debug('message:\n%s\n' % (message or ''))
1447
1447
1448 if len(parents) == 1:
1448 if len(parents) == 1:
1449 parents.append(repo[nullid])
1449 parents.append(repo[nullid])
1450 if opts.get('exact'):
1450 if opts.get('exact'):
1451 if not nodeid or not p1:
1451 if not nodeid or not p1:
1452 raise error.Abort(_('not a Mercurial patch'))
1452 raise error.Abort(_('not a Mercurial patch'))
1453 p1 = repo[p1]
1453 p1 = repo[p1]
1454 p2 = repo[p2 or nullid]
1454 p2 = repo[p2 or nullid]
1455 elif p2:
1455 elif p2:
1456 try:
1456 try:
1457 p1 = repo[p1]
1457 p1 = repo[p1]
1458 p2 = repo[p2]
1458 p2 = repo[p2]
1459 # Without any options, consider p2 only if the
1459 # Without any options, consider p2 only if the
1460 # patch is being applied on top of the recorded
1460 # patch is being applied on top of the recorded
1461 # first parent.
1461 # first parent.
1462 if p1 != parents[0]:
1462 if p1 != parents[0]:
1463 p1 = parents[0]
1463 p1 = parents[0]
1464 p2 = repo[nullid]
1464 p2 = repo[nullid]
1465 except error.RepoError:
1465 except error.RepoError:
1466 p1, p2 = parents
1466 p1, p2 = parents
1467 if p2.node() == nullid:
1467 if p2.node() == nullid:
1468 ui.warn(_("warning: import the patch as a normal revision\n"
1468 ui.warn(_("warning: import the patch as a normal revision\n"
1469 "(use --exact to import the patch as a merge)\n"))
1469 "(use --exact to import the patch as a merge)\n"))
1470 else:
1470 else:
1471 p1, p2 = parents
1471 p1, p2 = parents
1472
1472
1473 n = None
1473 n = None
1474 if update:
1474 if update:
1475 if p1 != parents[0]:
1475 if p1 != parents[0]:
1476 updatefunc(repo, p1.node())
1476 updatefunc(repo, p1.node())
1477 if p2 != parents[1]:
1477 if p2 != parents[1]:
1478 repo.setparents(p1.node(), p2.node())
1478 repo.setparents(p1.node(), p2.node())
1479
1479
1480 if opts.get('exact') or importbranch:
1480 if opts.get('exact') or importbranch:
1481 repo.dirstate.setbranch(branch or 'default')
1481 repo.dirstate.setbranch(branch or 'default')
1482
1482
1483 partial = opts.get('partial', False)
1483 partial = opts.get('partial', False)
1484 files = set()
1484 files = set()
1485 try:
1485 try:
1486 patch.patch(ui, repo, tmpname, strip=strip, prefix=prefix,
1486 patch.patch(ui, repo, tmpname, strip=strip, prefix=prefix,
1487 files=files, eolmode=None, similarity=sim / 100.0)
1487 files=files, eolmode=None, similarity=sim / 100.0)
1488 except error.PatchError as e:
1488 except error.PatchError as e:
1489 if not partial:
1489 if not partial:
1490 raise error.Abort(pycompat.bytestr(e))
1490 raise error.Abort(pycompat.bytestr(e))
1491 if partial:
1491 if partial:
1492 rejects = True
1492 rejects = True
1493
1493
1494 files = list(files)
1494 files = list(files)
1495 if nocommit:
1495 if nocommit:
1496 if message:
1496 if message:
1497 msgs.append(message)
1497 msgs.append(message)
1498 else:
1498 else:
1499 if opts.get('exact') or p2:
1499 if opts.get('exact') or p2:
1500 # If you got here, you either use --force and know what
1500 # If you got here, you either use --force and know what
1501 # you are doing or used --exact or a merge patch while
1501 # you are doing or used --exact or a merge patch while
1502 # being updated to its first parent.
1502 # being updated to its first parent.
1503 m = None
1503 m = None
1504 else:
1504 else:
1505 m = scmutil.matchfiles(repo, files or [])
1505 m = scmutil.matchfiles(repo, files or [])
1506 editform = mergeeditform(repo[None], 'import.normal')
1506 editform = mergeeditform(repo[None], 'import.normal')
1507 if opts.get('exact'):
1507 if opts.get('exact'):
1508 editor = None
1508 editor = None
1509 else:
1509 else:
1510 editor = getcommiteditor(editform=editform,
1510 editor = getcommiteditor(editform=editform,
1511 **pycompat.strkwargs(opts))
1511 **pycompat.strkwargs(opts))
1512 extra = {}
1512 extra = {}
1513 for idfunc in extrapreimport:
1513 for idfunc in extrapreimport:
1514 extrapreimportmap[idfunc](repo, patchdata, extra, opts)
1514 extrapreimportmap[idfunc](repo, patchdata, extra, opts)
1515 overrides = {}
1515 overrides = {}
1516 if partial:
1516 if partial:
1517 overrides[('ui', 'allowemptycommit')] = True
1517 overrides[('ui', 'allowemptycommit')] = True
1518 with repo.ui.configoverride(overrides, 'import'):
1518 with repo.ui.configoverride(overrides, 'import'):
1519 n = repo.commit(message, user,
1519 n = repo.commit(message, user,
1520 date, match=m,
1520 date, match=m,
1521 editor=editor, extra=extra)
1521 editor=editor, extra=extra)
1522 for idfunc in extrapostimport:
1522 for idfunc in extrapostimport:
1523 extrapostimportmap[idfunc](repo[n])
1523 extrapostimportmap[idfunc](repo[n])
1524 else:
1524 else:
1525 if opts.get('exact') or importbranch:
1525 if opts.get('exact') or importbranch:
1526 branch = branch or 'default'
1526 branch = branch or 'default'
1527 else:
1527 else:
1528 branch = p1.branch()
1528 branch = p1.branch()
1529 store = patch.filestore()
1529 store = patch.filestore()
1530 try:
1530 try:
1531 files = set()
1531 files = set()
1532 try:
1532 try:
1533 patch.patchrepo(ui, repo, p1, store, tmpname, strip, prefix,
1533 patch.patchrepo(ui, repo, p1, store, tmpname, strip, prefix,
1534 files, eolmode=None)
1534 files, eolmode=None)
1535 except error.PatchError as e:
1535 except error.PatchError as e:
1536 raise error.Abort(stringutil.forcebytestr(e))
1536 raise error.Abort(stringutil.forcebytestr(e))
1537 if opts.get('exact'):
1537 if opts.get('exact'):
1538 editor = None
1538 editor = None
1539 else:
1539 else:
1540 editor = getcommiteditor(editform='import.bypass')
1540 editor = getcommiteditor(editform='import.bypass')
1541 memctx = context.memctx(repo, (p1.node(), p2.node()),
1541 memctx = context.memctx(repo, (p1.node(), p2.node()),
1542 message,
1542 message,
1543 files=files,
1543 files=files,
1544 filectxfn=store,
1544 filectxfn=store,
1545 user=user,
1545 user=user,
1546 date=date,
1546 date=date,
1547 branch=branch,
1547 branch=branch,
1548 editor=editor)
1548 editor=editor)
1549 n = memctx.commit()
1549 n = memctx.commit()
1550 finally:
1550 finally:
1551 store.close()
1551 store.close()
1552 if opts.get('exact') and nocommit:
1552 if opts.get('exact') and nocommit:
1553 # --exact with --no-commit is still useful in that it does merge
1553 # --exact with --no-commit is still useful in that it does merge
1554 # and branch bits
1554 # and branch bits
1555 ui.warn(_("warning: can't check exact import with --no-commit\n"))
1555 ui.warn(_("warning: can't check exact import with --no-commit\n"))
1556 elif opts.get('exact') and (not n or hex(n) != nodeid):
1556 elif opts.get('exact') and (not n or hex(n) != nodeid):
1557 raise error.Abort(_('patch is damaged or loses information'))
1557 raise error.Abort(_('patch is damaged or loses information'))
1558 msg = _('applied to working directory')
1558 msg = _('applied to working directory')
1559 if n:
1559 if n:
1560 # i18n: refers to a short changeset id
1560 # i18n: refers to a short changeset id
1561 msg = _('created %s') % short(n)
1561 msg = _('created %s') % short(n)
1562 return msg, n, rejects
1562 return msg, n, rejects
1563
1563
1564 # facility to let extensions include additional data in an exported patch
1564 # facility to let extensions include additional data in an exported patch
1565 # list of identifiers to be executed in order
1565 # list of identifiers to be executed in order
1566 extraexport = []
1566 extraexport = []
1567 # mapping from identifier to actual export function
1567 # mapping from identifier to actual export function
1568 # function as to return a string to be added to the header or None
1568 # function as to return a string to be added to the header or None
1569 # it is given two arguments (sequencenumber, changectx)
1569 # it is given two arguments (sequencenumber, changectx)
1570 extraexportmap = {}
1570 extraexportmap = {}
1571
1571
1572 def _exportsingle(repo, ctx, fm, match, switch_parent, seqno, diffopts):
1572 def _exportsingle(repo, ctx, fm, match, switch_parent, seqno, diffopts):
1573 node = scmutil.binnode(ctx)
1573 node = scmutil.binnode(ctx)
1574 parents = [p.node() for p in ctx.parents() if p]
1574 parents = [p.node() for p in ctx.parents() if p]
1575 branch = ctx.branch()
1575 branch = ctx.branch()
1576 if switch_parent:
1576 if switch_parent:
1577 parents.reverse()
1577 parents.reverse()
1578
1578
1579 if parents:
1579 if parents:
1580 prev = parents[0]
1580 prev = parents[0]
1581 else:
1581 else:
1582 prev = nullid
1582 prev = nullid
1583
1583
1584 fm.context(ctx=ctx)
1584 fm.context(ctx=ctx)
1585 fm.plain('# HG changeset patch\n')
1585 fm.plain('# HG changeset patch\n')
1586 fm.write('user', '# User %s\n', ctx.user())
1586 fm.write('user', '# User %s\n', ctx.user())
1587 fm.plain('# Date %d %d\n' % ctx.date())
1587 fm.plain('# Date %d %d\n' % ctx.date())
1588 fm.write('date', '# %s\n', fm.formatdate(ctx.date()))
1588 fm.write('date', '# %s\n', fm.formatdate(ctx.date()))
1589 fm.condwrite(branch and branch != 'default',
1589 fm.condwrite(branch and branch != 'default',
1590 'branch', '# Branch %s\n', branch)
1590 'branch', '# Branch %s\n', branch)
1591 fm.write('node', '# Node ID %s\n', hex(node))
1591 fm.write('node', '# Node ID %s\n', hex(node))
1592 fm.plain('# Parent %s\n' % hex(prev))
1592 fm.plain('# Parent %s\n' % hex(prev))
1593 if len(parents) > 1:
1593 if len(parents) > 1:
1594 fm.plain('# Parent %s\n' % hex(parents[1]))
1594 fm.plain('# Parent %s\n' % hex(parents[1]))
1595 fm.data(parents=fm.formatlist(pycompat.maplist(hex, parents), name='node'))
1595 fm.data(parents=fm.formatlist(pycompat.maplist(hex, parents), name='node'))
1596
1596
1597 # TODO: redesign extraexportmap function to support formatter
1597 # TODO: redesign extraexportmap function to support formatter
1598 for headerid in extraexport:
1598 for headerid in extraexport:
1599 header = extraexportmap[headerid](seqno, ctx)
1599 header = extraexportmap[headerid](seqno, ctx)
1600 if header is not None:
1600 if header is not None:
1601 fm.plain('# %s\n' % header)
1601 fm.plain('# %s\n' % header)
1602
1602
1603 fm.write('desc', '%s\n', ctx.description().rstrip())
1603 fm.write('desc', '%s\n', ctx.description().rstrip())
1604 fm.plain('\n')
1604 fm.plain('\n')
1605
1605
1606 if fm.isplain():
1606 if fm.isplain():
1607 chunkiter = patch.diffui(repo, prev, node, match, opts=diffopts)
1607 chunkiter = patch.diffui(repo, prev, node, match, opts=diffopts)
1608 for chunk, label in chunkiter:
1608 for chunk, label in chunkiter:
1609 fm.plain(chunk, label=label)
1609 fm.plain(chunk, label=label)
1610 else:
1610 else:
1611 chunkiter = patch.diff(repo, prev, node, match, opts=diffopts)
1611 chunkiter = patch.diff(repo, prev, node, match, opts=diffopts)
1612 # TODO: make it structured?
1612 # TODO: make it structured?
1613 fm.data(diff=b''.join(chunkiter))
1613 fm.data(diff=b''.join(chunkiter))
1614
1614
1615 def _exportfile(repo, revs, fm, dest, switch_parent, diffopts, match):
1615 def _exportfile(repo, revs, fm, dest, switch_parent, diffopts, match):
1616 """Export changesets to stdout or a single file"""
1616 """Export changesets to stdout or a single file"""
1617 for seqno, rev in enumerate(revs, 1):
1617 for seqno, rev in enumerate(revs, 1):
1618 ctx = repo[rev]
1618 ctx = repo[rev]
1619 if not dest.startswith('<'):
1619 if not dest.startswith('<'):
1620 repo.ui.note("%s\n" % dest)
1620 repo.ui.note("%s\n" % dest)
1621 fm.startitem()
1621 fm.startitem()
1622 _exportsingle(repo, ctx, fm, match, switch_parent, seqno, diffopts)
1622 _exportsingle(repo, ctx, fm, match, switch_parent, seqno, diffopts)
1623
1623
1624 def _exportfntemplate(repo, revs, basefm, fntemplate, switch_parent, diffopts,
1624 def _exportfntemplate(repo, revs, basefm, fntemplate, switch_parent, diffopts,
1625 match):
1625 match):
1626 """Export changesets to possibly multiple files"""
1626 """Export changesets to possibly multiple files"""
1627 total = len(revs)
1627 total = len(revs)
1628 revwidth = max(len(str(rev)) for rev in revs)
1628 revwidth = max(len(str(rev)) for rev in revs)
1629 filemap = util.sortdict() # filename: [(seqno, rev), ...]
1629 filemap = util.sortdict() # filename: [(seqno, rev), ...]
1630
1630
1631 for seqno, rev in enumerate(revs, 1):
1631 for seqno, rev in enumerate(revs, 1):
1632 ctx = repo[rev]
1632 ctx = repo[rev]
1633 dest = makefilename(ctx, fntemplate,
1633 dest = makefilename(ctx, fntemplate,
1634 total=total, seqno=seqno, revwidth=revwidth)
1634 total=total, seqno=seqno, revwidth=revwidth)
1635 filemap.setdefault(dest, []).append((seqno, rev))
1635 filemap.setdefault(dest, []).append((seqno, rev))
1636
1636
1637 for dest in filemap:
1637 for dest in filemap:
1638 with formatter.maybereopen(basefm, dest) as fm:
1638 with formatter.maybereopen(basefm, dest) as fm:
1639 repo.ui.note("%s\n" % dest)
1639 repo.ui.note("%s\n" % dest)
1640 for seqno, rev in filemap[dest]:
1640 for seqno, rev in filemap[dest]:
1641 fm.startitem()
1641 fm.startitem()
1642 ctx = repo[rev]
1642 ctx = repo[rev]
1643 _exportsingle(repo, ctx, fm, match, switch_parent, seqno,
1643 _exportsingle(repo, ctx, fm, match, switch_parent, seqno,
1644 diffopts)
1644 diffopts)
1645
1645
1646 def export(repo, revs, basefm, fntemplate='hg-%h.patch', switch_parent=False,
1646 def export(repo, revs, basefm, fntemplate='hg-%h.patch', switch_parent=False,
1647 opts=None, match=None):
1647 opts=None, match=None):
1648 '''export changesets as hg patches
1648 '''export changesets as hg patches
1649
1649
1650 Args:
1650 Args:
1651 repo: The repository from which we're exporting revisions.
1651 repo: The repository from which we're exporting revisions.
1652 revs: A list of revisions to export as revision numbers.
1652 revs: A list of revisions to export as revision numbers.
1653 basefm: A formatter to which patches should be written.
1653 basefm: A formatter to which patches should be written.
1654 fntemplate: An optional string to use for generating patch file names.
1654 fntemplate: An optional string to use for generating patch file names.
1655 switch_parent: If True, show diffs against second parent when not nullid.
1655 switch_parent: If True, show diffs against second parent when not nullid.
1656 Default is false, which always shows diff against p1.
1656 Default is false, which always shows diff against p1.
1657 opts: diff options to use for generating the patch.
1657 opts: diff options to use for generating the patch.
1658 match: If specified, only export changes to files matching this matcher.
1658 match: If specified, only export changes to files matching this matcher.
1659
1659
1660 Returns:
1660 Returns:
1661 Nothing.
1661 Nothing.
1662
1662
1663 Side Effect:
1663 Side Effect:
1664 "HG Changeset Patch" data is emitted to one of the following
1664 "HG Changeset Patch" data is emitted to one of the following
1665 destinations:
1665 destinations:
1666 fntemplate specified: Each rev is written to a unique file named using
1666 fntemplate specified: Each rev is written to a unique file named using
1667 the given template.
1667 the given template.
1668 Otherwise: All revs will be written to basefm.
1668 Otherwise: All revs will be written to basefm.
1669 '''
1669 '''
1670 scmutil.prefetchfiles(repo, revs, match)
1670 scmutil.prefetchfiles(repo, revs, match)
1671
1671
1672 if not fntemplate:
1672 if not fntemplate:
1673 _exportfile(repo, revs, basefm, '<unnamed>', switch_parent, opts, match)
1673 _exportfile(repo, revs, basefm, '<unnamed>', switch_parent, opts, match)
1674 else:
1674 else:
1675 _exportfntemplate(repo, revs, basefm, fntemplate, switch_parent, opts,
1675 _exportfntemplate(repo, revs, basefm, fntemplate, switch_parent, opts,
1676 match)
1676 match)
1677
1677
1678 def exportfile(repo, revs, fp, switch_parent=False, opts=None, match=None):
1678 def exportfile(repo, revs, fp, switch_parent=False, opts=None, match=None):
1679 """Export changesets to the given file stream"""
1679 """Export changesets to the given file stream"""
1680 scmutil.prefetchfiles(repo, revs, match)
1680 scmutil.prefetchfiles(repo, revs, match)
1681
1681
1682 dest = getattr(fp, 'name', '<unnamed>')
1682 dest = getattr(fp, 'name', '<unnamed>')
1683 with formatter.formatter(repo.ui, fp, 'export', {}) as fm:
1683 with formatter.formatter(repo.ui, fp, 'export', {}) as fm:
1684 _exportfile(repo, revs, fm, dest, switch_parent, opts, match)
1684 _exportfile(repo, revs, fm, dest, switch_parent, opts, match)
1685
1685
1686 def showmarker(fm, marker, index=None):
1686 def showmarker(fm, marker, index=None):
1687 """utility function to display obsolescence marker in a readable way
1687 """utility function to display obsolescence marker in a readable way
1688
1688
1689 To be used by debug function."""
1689 To be used by debug function."""
1690 if index is not None:
1690 if index is not None:
1691 fm.write('index', '%i ', index)
1691 fm.write('index', '%i ', index)
1692 fm.write('prednode', '%s ', hex(marker.prednode()))
1692 fm.write('prednode', '%s ', hex(marker.prednode()))
1693 succs = marker.succnodes()
1693 succs = marker.succnodes()
1694 fm.condwrite(succs, 'succnodes', '%s ',
1694 fm.condwrite(succs, 'succnodes', '%s ',
1695 fm.formatlist(map(hex, succs), name='node'))
1695 fm.formatlist(map(hex, succs), name='node'))
1696 fm.write('flag', '%X ', marker.flags())
1696 fm.write('flag', '%X ', marker.flags())
1697 parents = marker.parentnodes()
1697 parents = marker.parentnodes()
1698 if parents is not None:
1698 if parents is not None:
1699 fm.write('parentnodes', '{%s} ',
1699 fm.write('parentnodes', '{%s} ',
1700 fm.formatlist(map(hex, parents), name='node', sep=', '))
1700 fm.formatlist(map(hex, parents), name='node', sep=', '))
1701 fm.write('date', '(%s) ', fm.formatdate(marker.date()))
1701 fm.write('date', '(%s) ', fm.formatdate(marker.date()))
1702 meta = marker.metadata().copy()
1702 meta = marker.metadata().copy()
1703 meta.pop('date', None)
1703 meta.pop('date', None)
1704 smeta = pycompat.rapply(pycompat.maybebytestr, meta)
1704 smeta = pycompat.rapply(pycompat.maybebytestr, meta)
1705 fm.write('metadata', '{%s}', fm.formatdict(smeta, fmt='%r: %r', sep=', '))
1705 fm.write('metadata', '{%s}', fm.formatdict(smeta, fmt='%r: %r', sep=', '))
1706 fm.plain('\n')
1706 fm.plain('\n')
1707
1707
1708 def finddate(ui, repo, date):
1708 def finddate(ui, repo, date):
1709 """Find the tipmost changeset that matches the given date spec"""
1709 """Find the tipmost changeset that matches the given date spec"""
1710
1710
1711 df = dateutil.matchdate(date)
1711 df = dateutil.matchdate(date)
1712 m = scmutil.matchall(repo)
1712 m = scmutil.matchall(repo)
1713 results = {}
1713 results = {}
1714
1714
1715 def prep(ctx, fns):
1715 def prep(ctx, fns):
1716 d = ctx.date()
1716 d = ctx.date()
1717 if df(d[0]):
1717 if df(d[0]):
1718 results[ctx.rev()] = d
1718 results[ctx.rev()] = d
1719
1719
1720 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1720 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1721 rev = ctx.rev()
1721 rev = ctx.rev()
1722 if rev in results:
1722 if rev in results:
1723 ui.status(_("found revision %s from %s\n") %
1723 ui.status(_("found revision %s from %s\n") %
1724 (rev, dateutil.datestr(results[rev])))
1724 (rev, dateutil.datestr(results[rev])))
1725 return '%d' % rev
1725 return '%d' % rev
1726
1726
1727 raise error.Abort(_("revision matching date not found"))
1727 raise error.Abort(_("revision matching date not found"))
1728
1728
1729 def increasingwindows(windowsize=8, sizelimit=512):
1729 def increasingwindows(windowsize=8, sizelimit=512):
1730 while True:
1730 while True:
1731 yield windowsize
1731 yield windowsize
1732 if windowsize < sizelimit:
1732 if windowsize < sizelimit:
1733 windowsize *= 2
1733 windowsize *= 2
1734
1734
1735 def _walkrevs(repo, opts):
1735 def _walkrevs(repo, opts):
1736 # Default --rev value depends on --follow but --follow behavior
1736 # Default --rev value depends on --follow but --follow behavior
1737 # depends on revisions resolved from --rev...
1737 # depends on revisions resolved from --rev...
1738 follow = opts.get('follow') or opts.get('follow_first')
1738 follow = opts.get('follow') or opts.get('follow_first')
1739 if opts.get('rev'):
1739 if opts.get('rev'):
1740 revs = scmutil.revrange(repo, opts['rev'])
1740 revs = scmutil.revrange(repo, opts['rev'])
1741 elif follow and repo.dirstate.p1() == nullid:
1741 elif follow and repo.dirstate.p1() == nullid:
1742 revs = smartset.baseset()
1742 revs = smartset.baseset()
1743 elif follow:
1743 elif follow:
1744 revs = repo.revs('reverse(:.)')
1744 revs = repo.revs('reverse(:.)')
1745 else:
1745 else:
1746 revs = smartset.spanset(repo)
1746 revs = smartset.spanset(repo)
1747 revs.reverse()
1747 revs.reverse()
1748 return revs
1748 return revs
1749
1749
1750 class FileWalkError(Exception):
1750 class FileWalkError(Exception):
1751 pass
1751 pass
1752
1752
1753 def walkfilerevs(repo, match, follow, revs, fncache):
1753 def walkfilerevs(repo, match, follow, revs, fncache):
1754 '''Walks the file history for the matched files.
1754 '''Walks the file history for the matched files.
1755
1755
1756 Returns the changeset revs that are involved in the file history.
1756 Returns the changeset revs that are involved in the file history.
1757
1757
1758 Throws FileWalkError if the file history can't be walked using
1758 Throws FileWalkError if the file history can't be walked using
1759 filelogs alone.
1759 filelogs alone.
1760 '''
1760 '''
1761 wanted = set()
1761 wanted = set()
1762 copies = []
1762 copies = []
1763 minrev, maxrev = min(revs), max(revs)
1763 minrev, maxrev = min(revs), max(revs)
1764 def filerevgen(filelog, last):
1764 def filerevgen(filelog, last):
1765 """
1765 """
1766 Only files, no patterns. Check the history of each file.
1766 Only files, no patterns. Check the history of each file.
1767
1767
1768 Examines filelog entries within minrev, maxrev linkrev range
1768 Examines filelog entries within minrev, maxrev linkrev range
1769 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1769 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1770 tuples in backwards order
1770 tuples in backwards order
1771 """
1771 """
1772 cl_count = len(repo)
1772 cl_count = len(repo)
1773 revs = []
1773 revs = []
1774 for j in pycompat.xrange(0, last + 1):
1774 for j in pycompat.xrange(0, last + 1):
1775 linkrev = filelog.linkrev(j)
1775 linkrev = filelog.linkrev(j)
1776 if linkrev < minrev:
1776 if linkrev < minrev:
1777 continue
1777 continue
1778 # only yield rev for which we have the changelog, it can
1778 # only yield rev for which we have the changelog, it can
1779 # happen while doing "hg log" during a pull or commit
1779 # happen while doing "hg log" during a pull or commit
1780 if linkrev >= cl_count:
1780 if linkrev >= cl_count:
1781 break
1781 break
1782
1782
1783 parentlinkrevs = []
1783 parentlinkrevs = []
1784 for p in filelog.parentrevs(j):
1784 for p in filelog.parentrevs(j):
1785 if p != nullrev:
1785 if p != nullrev:
1786 parentlinkrevs.append(filelog.linkrev(p))
1786 parentlinkrevs.append(filelog.linkrev(p))
1787 n = filelog.node(j)
1787 n = filelog.node(j)
1788 revs.append((linkrev, parentlinkrevs,
1788 revs.append((linkrev, parentlinkrevs,
1789 follow and filelog.renamed(n)))
1789 follow and filelog.renamed(n)))
1790
1790
1791 return reversed(revs)
1791 return reversed(revs)
1792 def iterfiles():
1792 def iterfiles():
1793 pctx = repo['.']
1793 pctx = repo['.']
1794 for filename in match.files():
1794 for filename in match.files():
1795 if follow:
1795 if follow:
1796 if filename not in pctx:
1796 if filename not in pctx:
1797 raise error.Abort(_('cannot follow file not in parent '
1797 raise error.Abort(_('cannot follow file not in parent '
1798 'revision: "%s"') % filename)
1798 'revision: "%s"') % filename)
1799 yield filename, pctx[filename].filenode()
1799 yield filename, pctx[filename].filenode()
1800 else:
1800 else:
1801 yield filename, None
1801 yield filename, None
1802 for filename_node in copies:
1802 for filename_node in copies:
1803 yield filename_node
1803 yield filename_node
1804
1804
1805 for file_, node in iterfiles():
1805 for file_, node in iterfiles():
1806 filelog = repo.file(file_)
1806 filelog = repo.file(file_)
1807 if not len(filelog):
1807 if not len(filelog):
1808 if node is None:
1808 if node is None:
1809 # A zero count may be a directory or deleted file, so
1809 # A zero count may be a directory or deleted file, so
1810 # try to find matching entries on the slow path.
1810 # try to find matching entries on the slow path.
1811 if follow:
1811 if follow:
1812 raise error.Abort(
1812 raise error.Abort(
1813 _('cannot follow nonexistent file: "%s"') % file_)
1813 _('cannot follow nonexistent file: "%s"') % file_)
1814 raise FileWalkError("Cannot walk via filelog")
1814 raise FileWalkError("Cannot walk via filelog")
1815 else:
1815 else:
1816 continue
1816 continue
1817
1817
1818 if node is None:
1818 if node is None:
1819 last = len(filelog) - 1
1819 last = len(filelog) - 1
1820 else:
1820 else:
1821 last = filelog.rev(node)
1821 last = filelog.rev(node)
1822
1822
1823 # keep track of all ancestors of the file
1823 # keep track of all ancestors of the file
1824 ancestors = {filelog.linkrev(last)}
1824 ancestors = {filelog.linkrev(last)}
1825
1825
1826 # iterate from latest to oldest revision
1826 # iterate from latest to oldest revision
1827 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1827 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1828 if not follow:
1828 if not follow:
1829 if rev > maxrev:
1829 if rev > maxrev:
1830 continue
1830 continue
1831 else:
1831 else:
1832 # Note that last might not be the first interesting
1832 # Note that last might not be the first interesting
1833 # rev to us:
1833 # rev to us:
1834 # if the file has been changed after maxrev, we'll
1834 # if the file has been changed after maxrev, we'll
1835 # have linkrev(last) > maxrev, and we still need
1835 # have linkrev(last) > maxrev, and we still need
1836 # to explore the file graph
1836 # to explore the file graph
1837 if rev not in ancestors:
1837 if rev not in ancestors:
1838 continue
1838 continue
1839 # XXX insert 1327 fix here
1839 # XXX insert 1327 fix here
1840 if flparentlinkrevs:
1840 if flparentlinkrevs:
1841 ancestors.update(flparentlinkrevs)
1841 ancestors.update(flparentlinkrevs)
1842
1842
1843 fncache.setdefault(rev, []).append(file_)
1843 fncache.setdefault(rev, []).append(file_)
1844 wanted.add(rev)
1844 wanted.add(rev)
1845 if copied:
1845 if copied:
1846 copies.append(copied)
1846 copies.append(copied)
1847
1847
1848 return wanted
1848 return wanted
1849
1849
1850 class _followfilter(object):
1850 class _followfilter(object):
1851 def __init__(self, repo, onlyfirst=False):
1851 def __init__(self, repo, onlyfirst=False):
1852 self.repo = repo
1852 self.repo = repo
1853 self.startrev = nullrev
1853 self.startrev = nullrev
1854 self.roots = set()
1854 self.roots = set()
1855 self.onlyfirst = onlyfirst
1855 self.onlyfirst = onlyfirst
1856
1856
1857 def match(self, rev):
1857 def match(self, rev):
1858 def realparents(rev):
1858 def realparents(rev):
1859 if self.onlyfirst:
1859 if self.onlyfirst:
1860 return self.repo.changelog.parentrevs(rev)[0:1]
1860 return self.repo.changelog.parentrevs(rev)[0:1]
1861 else:
1861 else:
1862 return filter(lambda x: x != nullrev,
1862 return filter(lambda x: x != nullrev,
1863 self.repo.changelog.parentrevs(rev))
1863 self.repo.changelog.parentrevs(rev))
1864
1864
1865 if self.startrev == nullrev:
1865 if self.startrev == nullrev:
1866 self.startrev = rev
1866 self.startrev = rev
1867 return True
1867 return True
1868
1868
1869 if rev > self.startrev:
1869 if rev > self.startrev:
1870 # forward: all descendants
1870 # forward: all descendants
1871 if not self.roots:
1871 if not self.roots:
1872 self.roots.add(self.startrev)
1872 self.roots.add(self.startrev)
1873 for parent in realparents(rev):
1873 for parent in realparents(rev):
1874 if parent in self.roots:
1874 if parent in self.roots:
1875 self.roots.add(rev)
1875 self.roots.add(rev)
1876 return True
1876 return True
1877 else:
1877 else:
1878 # backwards: all parents
1878 # backwards: all parents
1879 if not self.roots:
1879 if not self.roots:
1880 self.roots.update(realparents(self.startrev))
1880 self.roots.update(realparents(self.startrev))
1881 if rev in self.roots:
1881 if rev in self.roots:
1882 self.roots.remove(rev)
1882 self.roots.remove(rev)
1883 self.roots.update(realparents(rev))
1883 self.roots.update(realparents(rev))
1884 return True
1884 return True
1885
1885
1886 return False
1886 return False
1887
1887
1888 def walkchangerevs(repo, match, opts, prepare):
1888 def walkchangerevs(repo, match, opts, prepare):
1889 '''Iterate over files and the revs in which they changed.
1889 '''Iterate over files and the revs in which they changed.
1890
1890
1891 Callers most commonly need to iterate backwards over the history
1891 Callers most commonly need to iterate backwards over the history
1892 in which they are interested. Doing so has awful (quadratic-looking)
1892 in which they are interested. Doing so has awful (quadratic-looking)
1893 performance, so we use iterators in a "windowed" way.
1893 performance, so we use iterators in a "windowed" way.
1894
1894
1895 We walk a window of revisions in the desired order. Within the
1895 We walk a window of revisions in the desired order. Within the
1896 window, we first walk forwards to gather data, then in the desired
1896 window, we first walk forwards to gather data, then in the desired
1897 order (usually backwards) to display it.
1897 order (usually backwards) to display it.
1898
1898
1899 This function returns an iterator yielding contexts. Before
1899 This function returns an iterator yielding contexts. Before
1900 yielding each context, the iterator will first call the prepare
1900 yielding each context, the iterator will first call the prepare
1901 function on each context in the window in forward order.'''
1901 function on each context in the window in forward order.'''
1902
1902
1903 allfiles = opts.get('all_files')
1903 allfiles = opts.get('all_files')
1904 follow = opts.get('follow') or opts.get('follow_first')
1904 follow = opts.get('follow') or opts.get('follow_first')
1905 revs = _walkrevs(repo, opts)
1905 revs = _walkrevs(repo, opts)
1906 if not revs:
1906 if not revs:
1907 return []
1907 return []
1908 wanted = set()
1908 wanted = set()
1909 slowpath = match.anypats() or (not match.always() and opts.get('removed'))
1909 slowpath = match.anypats() or (not match.always() and opts.get('removed'))
1910 fncache = {}
1910 fncache = {}
1911 change = repo.__getitem__
1911 change = repo.__getitem__
1912
1912
1913 # First step is to fill wanted, the set of revisions that we want to yield.
1913 # First step is to fill wanted, the set of revisions that we want to yield.
1914 # When it does not induce extra cost, we also fill fncache for revisions in
1914 # When it does not induce extra cost, we also fill fncache for revisions in
1915 # wanted: a cache of filenames that were changed (ctx.files()) and that
1915 # wanted: a cache of filenames that were changed (ctx.files()) and that
1916 # match the file filtering conditions.
1916 # match the file filtering conditions.
1917
1917
1918 if match.always() or allfiles:
1918 if match.always() or allfiles:
1919 # No files, no patterns. Display all revs.
1919 # No files, no patterns. Display all revs.
1920 wanted = revs
1920 wanted = revs
1921 elif not slowpath:
1921 elif not slowpath:
1922 # We only have to read through the filelog to find wanted revisions
1922 # We only have to read through the filelog to find wanted revisions
1923
1923
1924 try:
1924 try:
1925 wanted = walkfilerevs(repo, match, follow, revs, fncache)
1925 wanted = walkfilerevs(repo, match, follow, revs, fncache)
1926 except FileWalkError:
1926 except FileWalkError:
1927 slowpath = True
1927 slowpath = True
1928
1928
1929 # We decided to fall back to the slowpath because at least one
1929 # We decided to fall back to the slowpath because at least one
1930 # of the paths was not a file. Check to see if at least one of them
1930 # of the paths was not a file. Check to see if at least one of them
1931 # existed in history, otherwise simply return
1931 # existed in history, otherwise simply return
1932 for path in match.files():
1932 for path in match.files():
1933 if path == '.' or path in repo.store:
1933 if path == '.' or path in repo.store:
1934 break
1934 break
1935 else:
1935 else:
1936 return []
1936 return []
1937
1937
1938 if slowpath:
1938 if slowpath:
1939 # We have to read the changelog to match filenames against
1939 # We have to read the changelog to match filenames against
1940 # changed files
1940 # changed files
1941
1941
1942 if follow:
1942 if follow:
1943 raise error.Abort(_('can only follow copies/renames for explicit '
1943 raise error.Abort(_('can only follow copies/renames for explicit '
1944 'filenames'))
1944 'filenames'))
1945
1945
1946 # The slow path checks files modified in every changeset.
1946 # The slow path checks files modified in every changeset.
1947 # This is really slow on large repos, so compute the set lazily.
1947 # This is really slow on large repos, so compute the set lazily.
1948 class lazywantedset(object):
1948 class lazywantedset(object):
1949 def __init__(self):
1949 def __init__(self):
1950 self.set = set()
1950 self.set = set()
1951 self.revs = set(revs)
1951 self.revs = set(revs)
1952
1952
1953 # No need to worry about locality here because it will be accessed
1953 # No need to worry about locality here because it will be accessed
1954 # in the same order as the increasing window below.
1954 # in the same order as the increasing window below.
1955 def __contains__(self, value):
1955 def __contains__(self, value):
1956 if value in self.set:
1956 if value in self.set:
1957 return True
1957 return True
1958 elif not value in self.revs:
1958 elif not value in self.revs:
1959 return False
1959 return False
1960 else:
1960 else:
1961 self.revs.discard(value)
1961 self.revs.discard(value)
1962 ctx = change(value)
1962 ctx = change(value)
1963 matches = [f for f in ctx.files() if match(f)]
1963 matches = [f for f in ctx.files() if match(f)]
1964 if matches:
1964 if matches:
1965 fncache[value] = matches
1965 fncache[value] = matches
1966 self.set.add(value)
1966 self.set.add(value)
1967 return True
1967 return True
1968 return False
1968 return False
1969
1969
1970 def discard(self, value):
1970 def discard(self, value):
1971 self.revs.discard(value)
1971 self.revs.discard(value)
1972 self.set.discard(value)
1972 self.set.discard(value)
1973
1973
1974 wanted = lazywantedset()
1974 wanted = lazywantedset()
1975
1975
1976 # it might be worthwhile to do this in the iterator if the rev range
1976 # it might be worthwhile to do this in the iterator if the rev range
1977 # is descending and the prune args are all within that range
1977 # is descending and the prune args are all within that range
1978 for rev in opts.get('prune', ()):
1978 for rev in opts.get('prune', ()):
1979 rev = repo[rev].rev()
1979 rev = repo[rev].rev()
1980 ff = _followfilter(repo)
1980 ff = _followfilter(repo)
1981 stop = min(revs[0], revs[-1])
1981 stop = min(revs[0], revs[-1])
1982 for x in pycompat.xrange(rev, stop - 1, -1):
1982 for x in pycompat.xrange(rev, stop - 1, -1):
1983 if ff.match(x):
1983 if ff.match(x):
1984 wanted = wanted - [x]
1984 wanted = wanted - [x]
1985
1985
1986 # Now that wanted is correctly initialized, we can iterate over the
1986 # Now that wanted is correctly initialized, we can iterate over the
1987 # revision range, yielding only revisions in wanted.
1987 # revision range, yielding only revisions in wanted.
1988 def iterate():
1988 def iterate():
1989 if follow and match.always():
1989 if follow and match.always():
1990 ff = _followfilter(repo, onlyfirst=opts.get('follow_first'))
1990 ff = _followfilter(repo, onlyfirst=opts.get('follow_first'))
1991 def want(rev):
1991 def want(rev):
1992 return ff.match(rev) and rev in wanted
1992 return ff.match(rev) and rev in wanted
1993 else:
1993 else:
1994 def want(rev):
1994 def want(rev):
1995 return rev in wanted
1995 return rev in wanted
1996
1996
1997 it = iter(revs)
1997 it = iter(revs)
1998 stopiteration = False
1998 stopiteration = False
1999 for windowsize in increasingwindows():
1999 for windowsize in increasingwindows():
2000 nrevs = []
2000 nrevs = []
2001 for i in pycompat.xrange(windowsize):
2001 for i in pycompat.xrange(windowsize):
2002 rev = next(it, None)
2002 rev = next(it, None)
2003 if rev is None:
2003 if rev is None:
2004 stopiteration = True
2004 stopiteration = True
2005 break
2005 break
2006 elif want(rev):
2006 elif want(rev):
2007 nrevs.append(rev)
2007 nrevs.append(rev)
2008 for rev in sorted(nrevs):
2008 for rev in sorted(nrevs):
2009 fns = fncache.get(rev)
2009 fns = fncache.get(rev)
2010 ctx = change(rev)
2010 ctx = change(rev)
2011 if not fns:
2011 if not fns:
2012 def fns_generator():
2012 def fns_generator():
2013 if allfiles:
2013 if allfiles:
2014 fiter = iter(ctx)
2014 fiter = iter(ctx)
2015 else:
2015 else:
2016 fiter = ctx.files()
2016 fiter = ctx.files()
2017 for f in fiter:
2017 for f in fiter:
2018 if match(f):
2018 if match(f):
2019 yield f
2019 yield f
2020 fns = fns_generator()
2020 fns = fns_generator()
2021 prepare(ctx, fns)
2021 prepare(ctx, fns)
2022 for rev in nrevs:
2022 for rev in nrevs:
2023 yield change(rev)
2023 yield change(rev)
2024
2024
2025 if stopiteration:
2025 if stopiteration:
2026 break
2026 break
2027
2027
2028 return iterate()
2028 return iterate()
2029
2029
2030 def add(ui, repo, match, prefix, explicitonly, **opts):
2030 def add(ui, repo, match, prefix, uipathfn, explicitonly, **opts):
2031 bad = []
2031 bad = []
2032
2032
2033 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2033 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2034 names = []
2034 names = []
2035 wctx = repo[None]
2035 wctx = repo[None]
2036 cca = None
2036 cca = None
2037 abort, warn = scmutil.checkportabilityalert(ui)
2037 abort, warn = scmutil.checkportabilityalert(ui)
2038 if abort or warn:
2038 if abort or warn:
2039 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
2039 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
2040
2040
2041 match = repo.narrowmatch(match, includeexact=True)
2041 match = repo.narrowmatch(match, includeexact=True)
2042 badmatch = matchmod.badmatch(match, badfn)
2042 badmatch = matchmod.badmatch(match, badfn)
2043 dirstate = repo.dirstate
2043 dirstate = repo.dirstate
2044 # We don't want to just call wctx.walk here, since it would return a lot of
2044 # We don't want to just call wctx.walk here, since it would return a lot of
2045 # clean files, which we aren't interested in and takes time.
2045 # clean files, which we aren't interested in and takes time.
2046 for f in sorted(dirstate.walk(badmatch, subrepos=sorted(wctx.substate),
2046 for f in sorted(dirstate.walk(badmatch, subrepos=sorted(wctx.substate),
2047 unknown=True, ignored=False, full=False)):
2047 unknown=True, ignored=False, full=False)):
2048 exact = match.exact(f)
2048 exact = match.exact(f)
2049 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
2049 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
2050 if cca:
2050 if cca:
2051 cca(f)
2051 cca(f)
2052 names.append(f)
2052 names.append(f)
2053 if ui.verbose or not exact:
2053 if ui.verbose or not exact:
2054 ui.status(_('adding %s\n') % match.rel(f),
2054 ui.status(_('adding %s\n') % uipathfn(f),
2055 label='ui.addremove.added')
2055 label='ui.addremove.added')
2056
2056
2057 for subpath in sorted(wctx.substate):
2057 for subpath in sorted(wctx.substate):
2058 sub = wctx.sub(subpath)
2058 sub = wctx.sub(subpath)
2059 try:
2059 try:
2060 submatch = matchmod.subdirmatcher(subpath, match)
2060 submatch = matchmod.subdirmatcher(subpath, match)
2061 subprefix = repo.wvfs.reljoin(prefix, subpath)
2061 subprefix = repo.wvfs.reljoin(prefix, subpath)
2062 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2062 if opts.get(r'subrepos'):
2063 if opts.get(r'subrepos'):
2063 bad.extend(sub.add(ui, submatch, subprefix, False, **opts))
2064 bad.extend(sub.add(ui, submatch, subprefix, subuipathfn, False,
2065 **opts))
2064 else:
2066 else:
2065 bad.extend(sub.add(ui, submatch, subprefix, True, **opts))
2067 bad.extend(sub.add(ui, submatch, subprefix, subuipathfn, True,
2068 **opts))
2066 except error.LookupError:
2069 except error.LookupError:
2067 ui.status(_("skipping missing subrepository: %s\n")
2070 ui.status(_("skipping missing subrepository: %s\n")
2068 % match.rel(subpath))
2071 % uipathfn(subpath))
2069
2072
2070 if not opts.get(r'dry_run'):
2073 if not opts.get(r'dry_run'):
2071 rejected = wctx.add(names, prefix)
2074 rejected = wctx.add(names, prefix)
2072 bad.extend(f for f in rejected if f in match.files())
2075 bad.extend(f for f in rejected if f in match.files())
2073 return bad
2076 return bad
2074
2077
2075 def addwebdirpath(repo, serverpath, webconf):
2078 def addwebdirpath(repo, serverpath, webconf):
2076 webconf[serverpath] = repo.root
2079 webconf[serverpath] = repo.root
2077 repo.ui.debug('adding %s = %s\n' % (serverpath, repo.root))
2080 repo.ui.debug('adding %s = %s\n' % (serverpath, repo.root))
2078
2081
2079 for r in repo.revs('filelog("path:.hgsub")'):
2082 for r in repo.revs('filelog("path:.hgsub")'):
2080 ctx = repo[r]
2083 ctx = repo[r]
2081 for subpath in ctx.substate:
2084 for subpath in ctx.substate:
2082 ctx.sub(subpath).addwebdirpath(serverpath, webconf)
2085 ctx.sub(subpath).addwebdirpath(serverpath, webconf)
2083
2086
2084 def forget(ui, repo, match, prefix, explicitonly, dryrun, interactive):
2087 def forget(ui, repo, match, prefix, explicitonly, dryrun, interactive):
2085 if dryrun and interactive:
2088 if dryrun and interactive:
2086 raise error.Abort(_("cannot specify both --dry-run and --interactive"))
2089 raise error.Abort(_("cannot specify both --dry-run and --interactive"))
2087 bad = []
2090 bad = []
2088 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2091 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2089 wctx = repo[None]
2092 wctx = repo[None]
2090 forgot = []
2093 forgot = []
2091
2094
2092 s = repo.status(match=matchmod.badmatch(match, badfn), clean=True)
2095 s = repo.status(match=matchmod.badmatch(match, badfn), clean=True)
2093 forget = sorted(s.modified + s.added + s.deleted + s.clean)
2096 forget = sorted(s.modified + s.added + s.deleted + s.clean)
2094 if explicitonly:
2097 if explicitonly:
2095 forget = [f for f in forget if match.exact(f)]
2098 forget = [f for f in forget if match.exact(f)]
2096
2099
2097 for subpath in sorted(wctx.substate):
2100 for subpath in sorted(wctx.substate):
2098 sub = wctx.sub(subpath)
2101 sub = wctx.sub(subpath)
2099 submatch = matchmod.subdirmatcher(subpath, match)
2102 submatch = matchmod.subdirmatcher(subpath, match)
2100 subprefix = repo.wvfs.reljoin(prefix, subpath)
2103 subprefix = repo.wvfs.reljoin(prefix, subpath)
2101 try:
2104 try:
2102 subbad, subforgot = sub.forget(submatch, subprefix, dryrun=dryrun,
2105 subbad, subforgot = sub.forget(submatch, subprefix, dryrun=dryrun,
2103 interactive=interactive)
2106 interactive=interactive)
2104 bad.extend([subpath + '/' + f for f in subbad])
2107 bad.extend([subpath + '/' + f for f in subbad])
2105 forgot.extend([subpath + '/' + f for f in subforgot])
2108 forgot.extend([subpath + '/' + f for f in subforgot])
2106 except error.LookupError:
2109 except error.LookupError:
2107 ui.status(_("skipping missing subrepository: %s\n")
2110 ui.status(_("skipping missing subrepository: %s\n")
2108 % match.rel(subpath))
2111 % match.rel(subpath))
2109
2112
2110 if not explicitonly:
2113 if not explicitonly:
2111 for f in match.files():
2114 for f in match.files():
2112 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2115 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2113 if f not in forgot:
2116 if f not in forgot:
2114 if repo.wvfs.exists(f):
2117 if repo.wvfs.exists(f):
2115 # Don't complain if the exact case match wasn't given.
2118 # Don't complain if the exact case match wasn't given.
2116 # But don't do this until after checking 'forgot', so
2119 # But don't do this until after checking 'forgot', so
2117 # that subrepo files aren't normalized, and this op is
2120 # that subrepo files aren't normalized, and this op is
2118 # purely from data cached by the status walk above.
2121 # purely from data cached by the status walk above.
2119 if repo.dirstate.normalize(f) in repo.dirstate:
2122 if repo.dirstate.normalize(f) in repo.dirstate:
2120 continue
2123 continue
2121 ui.warn(_('not removing %s: '
2124 ui.warn(_('not removing %s: '
2122 'file is already untracked\n')
2125 'file is already untracked\n')
2123 % match.rel(f))
2126 % match.rel(f))
2124 bad.append(f)
2127 bad.append(f)
2125
2128
2126 if interactive:
2129 if interactive:
2127 responses = _('[Ynsa?]'
2130 responses = _('[Ynsa?]'
2128 '$$ &Yes, forget this file'
2131 '$$ &Yes, forget this file'
2129 '$$ &No, skip this file'
2132 '$$ &No, skip this file'
2130 '$$ &Skip remaining files'
2133 '$$ &Skip remaining files'
2131 '$$ Include &all remaining files'
2134 '$$ Include &all remaining files'
2132 '$$ &? (display help)')
2135 '$$ &? (display help)')
2133 for filename in forget[:]:
2136 for filename in forget[:]:
2134 r = ui.promptchoice(_('forget %s %s') % (filename, responses))
2137 r = ui.promptchoice(_('forget %s %s') % (filename, responses))
2135 if r == 4: # ?
2138 if r == 4: # ?
2136 while r == 4:
2139 while r == 4:
2137 for c, t in ui.extractchoices(responses)[1]:
2140 for c, t in ui.extractchoices(responses)[1]:
2138 ui.write('%s - %s\n' % (c, encoding.lower(t)))
2141 ui.write('%s - %s\n' % (c, encoding.lower(t)))
2139 r = ui.promptchoice(_('forget %s %s') % (filename,
2142 r = ui.promptchoice(_('forget %s %s') % (filename,
2140 responses))
2143 responses))
2141 if r == 0: # yes
2144 if r == 0: # yes
2142 continue
2145 continue
2143 elif r == 1: # no
2146 elif r == 1: # no
2144 forget.remove(filename)
2147 forget.remove(filename)
2145 elif r == 2: # Skip
2148 elif r == 2: # Skip
2146 fnindex = forget.index(filename)
2149 fnindex = forget.index(filename)
2147 del forget[fnindex:]
2150 del forget[fnindex:]
2148 break
2151 break
2149 elif r == 3: # All
2152 elif r == 3: # All
2150 break
2153 break
2151
2154
2152 for f in forget:
2155 for f in forget:
2153 if ui.verbose or not match.exact(f) or interactive:
2156 if ui.verbose or not match.exact(f) or interactive:
2154 ui.status(_('removing %s\n') % match.rel(f),
2157 ui.status(_('removing %s\n') % match.rel(f),
2155 label='ui.addremove.removed')
2158 label='ui.addremove.removed')
2156
2159
2157 if not dryrun:
2160 if not dryrun:
2158 rejected = wctx.forget(forget, prefix)
2161 rejected = wctx.forget(forget, prefix)
2159 bad.extend(f for f in rejected if f in match.files())
2162 bad.extend(f for f in rejected if f in match.files())
2160 forgot.extend(f for f in forget if f not in rejected)
2163 forgot.extend(f for f in forget if f not in rejected)
2161 return bad, forgot
2164 return bad, forgot
2162
2165
2163 def files(ui, ctx, m, fm, fmt, subrepos):
2166 def files(ui, ctx, m, fm, fmt, subrepos):
2164 ret = 1
2167 ret = 1
2165
2168
2166 needsfctx = ui.verbose or {'size', 'flags'} & fm.datahint()
2169 needsfctx = ui.verbose or {'size', 'flags'} & fm.datahint()
2167 uipathfn = scmutil.getuipathfn(ctx.repo(), legacyrelativevalue=True)
2170 uipathfn = scmutil.getuipathfn(ctx.repo(), legacyrelativevalue=True)
2168 for f in ctx.matches(m):
2171 for f in ctx.matches(m):
2169 fm.startitem()
2172 fm.startitem()
2170 fm.context(ctx=ctx)
2173 fm.context(ctx=ctx)
2171 if needsfctx:
2174 if needsfctx:
2172 fc = ctx[f]
2175 fc = ctx[f]
2173 fm.write('size flags', '% 10d % 1s ', fc.size(), fc.flags())
2176 fm.write('size flags', '% 10d % 1s ', fc.size(), fc.flags())
2174 fm.data(path=f)
2177 fm.data(path=f)
2175 fm.plain(fmt % uipathfn(f))
2178 fm.plain(fmt % uipathfn(f))
2176 ret = 0
2179 ret = 0
2177
2180
2178 for subpath in sorted(ctx.substate):
2181 for subpath in sorted(ctx.substate):
2179 submatch = matchmod.subdirmatcher(subpath, m)
2182 submatch = matchmod.subdirmatcher(subpath, m)
2180 if (subrepos or m.exact(subpath) or any(submatch.files())):
2183 if (subrepos or m.exact(subpath) or any(submatch.files())):
2181 sub = ctx.sub(subpath)
2184 sub = ctx.sub(subpath)
2182 try:
2185 try:
2183 recurse = m.exact(subpath) or subrepos
2186 recurse = m.exact(subpath) or subrepos
2184 if sub.printfiles(ui, submatch, fm, fmt, recurse) == 0:
2187 if sub.printfiles(ui, submatch, fm, fmt, recurse) == 0:
2185 ret = 0
2188 ret = 0
2186 except error.LookupError:
2189 except error.LookupError:
2187 ui.status(_("skipping missing subrepository: %s\n")
2190 ui.status(_("skipping missing subrepository: %s\n")
2188 % m.rel(subpath))
2191 % m.rel(subpath))
2189
2192
2190 return ret
2193 return ret
2191
2194
2192 def remove(ui, repo, m, prefix, after, force, subrepos, dryrun, warnings=None):
2195 def remove(ui, repo, m, prefix, after, force, subrepos, dryrun, warnings=None):
2193 ret = 0
2196 ret = 0
2194 s = repo.status(match=m, clean=True)
2197 s = repo.status(match=m, clean=True)
2195 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2198 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2196
2199
2197 wctx = repo[None]
2200 wctx = repo[None]
2198
2201
2199 if warnings is None:
2202 if warnings is None:
2200 warnings = []
2203 warnings = []
2201 warn = True
2204 warn = True
2202 else:
2205 else:
2203 warn = False
2206 warn = False
2204
2207
2205 subs = sorted(wctx.substate)
2208 subs = sorted(wctx.substate)
2206 progress = ui.makeprogress(_('searching'), total=len(subs),
2209 progress = ui.makeprogress(_('searching'), total=len(subs),
2207 unit=_('subrepos'))
2210 unit=_('subrepos'))
2208 for subpath in subs:
2211 for subpath in subs:
2209 submatch = matchmod.subdirmatcher(subpath, m)
2212 submatch = matchmod.subdirmatcher(subpath, m)
2210 subprefix = repo.wvfs.reljoin(prefix, subpath)
2213 subprefix = repo.wvfs.reljoin(prefix, subpath)
2211 if subrepos or m.exact(subpath) or any(submatch.files()):
2214 if subrepos or m.exact(subpath) or any(submatch.files()):
2212 progress.increment()
2215 progress.increment()
2213 sub = wctx.sub(subpath)
2216 sub = wctx.sub(subpath)
2214 try:
2217 try:
2215 if sub.removefiles(submatch, subprefix, after, force, subrepos,
2218 if sub.removefiles(submatch, subprefix, after, force, subrepos,
2216 dryrun, warnings):
2219 dryrun, warnings):
2217 ret = 1
2220 ret = 1
2218 except error.LookupError:
2221 except error.LookupError:
2219 warnings.append(_("skipping missing subrepository: %s\n")
2222 warnings.append(_("skipping missing subrepository: %s\n")
2220 % m.rel(subpath))
2223 % m.rel(subpath))
2221 progress.complete()
2224 progress.complete()
2222
2225
2223 # warn about failure to delete explicit files/dirs
2226 # warn about failure to delete explicit files/dirs
2224 deleteddirs = util.dirs(deleted)
2227 deleteddirs = util.dirs(deleted)
2225 files = m.files()
2228 files = m.files()
2226 progress = ui.makeprogress(_('deleting'), total=len(files),
2229 progress = ui.makeprogress(_('deleting'), total=len(files),
2227 unit=_('files'))
2230 unit=_('files'))
2228 for f in files:
2231 for f in files:
2229 def insubrepo():
2232 def insubrepo():
2230 for subpath in wctx.substate:
2233 for subpath in wctx.substate:
2231 if f.startswith(subpath + '/'):
2234 if f.startswith(subpath + '/'):
2232 return True
2235 return True
2233 return False
2236 return False
2234
2237
2235 progress.increment()
2238 progress.increment()
2236 isdir = f in deleteddirs or wctx.hasdir(f)
2239 isdir = f in deleteddirs or wctx.hasdir(f)
2237 if (f in repo.dirstate or isdir or f == '.'
2240 if (f in repo.dirstate or isdir or f == '.'
2238 or insubrepo() or f in subs):
2241 or insubrepo() or f in subs):
2239 continue
2242 continue
2240
2243
2241 if repo.wvfs.exists(f):
2244 if repo.wvfs.exists(f):
2242 if repo.wvfs.isdir(f):
2245 if repo.wvfs.isdir(f):
2243 warnings.append(_('not removing %s: no tracked files\n')
2246 warnings.append(_('not removing %s: no tracked files\n')
2244 % m.rel(f))
2247 % m.rel(f))
2245 else:
2248 else:
2246 warnings.append(_('not removing %s: file is untracked\n')
2249 warnings.append(_('not removing %s: file is untracked\n')
2247 % m.rel(f))
2250 % m.rel(f))
2248 # missing files will generate a warning elsewhere
2251 # missing files will generate a warning elsewhere
2249 ret = 1
2252 ret = 1
2250 progress.complete()
2253 progress.complete()
2251
2254
2252 if force:
2255 if force:
2253 list = modified + deleted + clean + added
2256 list = modified + deleted + clean + added
2254 elif after:
2257 elif after:
2255 list = deleted
2258 list = deleted
2256 remaining = modified + added + clean
2259 remaining = modified + added + clean
2257 progress = ui.makeprogress(_('skipping'), total=len(remaining),
2260 progress = ui.makeprogress(_('skipping'), total=len(remaining),
2258 unit=_('files'))
2261 unit=_('files'))
2259 for f in remaining:
2262 for f in remaining:
2260 progress.increment()
2263 progress.increment()
2261 if ui.verbose or (f in files):
2264 if ui.verbose or (f in files):
2262 warnings.append(_('not removing %s: file still exists\n')
2265 warnings.append(_('not removing %s: file still exists\n')
2263 % m.rel(f))
2266 % m.rel(f))
2264 ret = 1
2267 ret = 1
2265 progress.complete()
2268 progress.complete()
2266 else:
2269 else:
2267 list = deleted + clean
2270 list = deleted + clean
2268 progress = ui.makeprogress(_('skipping'),
2271 progress = ui.makeprogress(_('skipping'),
2269 total=(len(modified) + len(added)),
2272 total=(len(modified) + len(added)),
2270 unit=_('files'))
2273 unit=_('files'))
2271 for f in modified:
2274 for f in modified:
2272 progress.increment()
2275 progress.increment()
2273 warnings.append(_('not removing %s: file is modified (use -f'
2276 warnings.append(_('not removing %s: file is modified (use -f'
2274 ' to force removal)\n') % m.rel(f))
2277 ' to force removal)\n') % m.rel(f))
2275 ret = 1
2278 ret = 1
2276 for f in added:
2279 for f in added:
2277 progress.increment()
2280 progress.increment()
2278 warnings.append(_("not removing %s: file has been marked for add"
2281 warnings.append(_("not removing %s: file has been marked for add"
2279 " (use 'hg forget' to undo add)\n") % m.rel(f))
2282 " (use 'hg forget' to undo add)\n") % m.rel(f))
2280 ret = 1
2283 ret = 1
2281 progress.complete()
2284 progress.complete()
2282
2285
2283 list = sorted(list)
2286 list = sorted(list)
2284 progress = ui.makeprogress(_('deleting'), total=len(list),
2287 progress = ui.makeprogress(_('deleting'), total=len(list),
2285 unit=_('files'))
2288 unit=_('files'))
2286 for f in list:
2289 for f in list:
2287 if ui.verbose or not m.exact(f):
2290 if ui.verbose or not m.exact(f):
2288 progress.increment()
2291 progress.increment()
2289 ui.status(_('removing %s\n') % m.rel(f),
2292 ui.status(_('removing %s\n') % m.rel(f),
2290 label='ui.addremove.removed')
2293 label='ui.addremove.removed')
2291 progress.complete()
2294 progress.complete()
2292
2295
2293 if not dryrun:
2296 if not dryrun:
2294 with repo.wlock():
2297 with repo.wlock():
2295 if not after:
2298 if not after:
2296 for f in list:
2299 for f in list:
2297 if f in added:
2300 if f in added:
2298 continue # we never unlink added files on remove
2301 continue # we never unlink added files on remove
2299 rmdir = repo.ui.configbool('experimental',
2302 rmdir = repo.ui.configbool('experimental',
2300 'removeemptydirs')
2303 'removeemptydirs')
2301 repo.wvfs.unlinkpath(f, ignoremissing=True, rmdir=rmdir)
2304 repo.wvfs.unlinkpath(f, ignoremissing=True, rmdir=rmdir)
2302 repo[None].forget(list)
2305 repo[None].forget(list)
2303
2306
2304 if warn:
2307 if warn:
2305 for warning in warnings:
2308 for warning in warnings:
2306 ui.warn(warning)
2309 ui.warn(warning)
2307
2310
2308 return ret
2311 return ret
2309
2312
2310 def _updatecatformatter(fm, ctx, matcher, path, decode):
2313 def _updatecatformatter(fm, ctx, matcher, path, decode):
2311 """Hook for adding data to the formatter used by ``hg cat``.
2314 """Hook for adding data to the formatter used by ``hg cat``.
2312
2315
2313 Extensions (e.g., lfs) can wrap this to inject keywords/data, but must call
2316 Extensions (e.g., lfs) can wrap this to inject keywords/data, but must call
2314 this method first."""
2317 this method first."""
2315 data = ctx[path].data()
2318 data = ctx[path].data()
2316 if decode:
2319 if decode:
2317 data = ctx.repo().wwritedata(path, data)
2320 data = ctx.repo().wwritedata(path, data)
2318 fm.startitem()
2321 fm.startitem()
2319 fm.context(ctx=ctx)
2322 fm.context(ctx=ctx)
2320 fm.write('data', '%s', data)
2323 fm.write('data', '%s', data)
2321 fm.data(path=path)
2324 fm.data(path=path)
2322
2325
2323 def cat(ui, repo, ctx, matcher, basefm, fntemplate, prefix, **opts):
2326 def cat(ui, repo, ctx, matcher, basefm, fntemplate, prefix, **opts):
2324 err = 1
2327 err = 1
2325 opts = pycompat.byteskwargs(opts)
2328 opts = pycompat.byteskwargs(opts)
2326
2329
2327 def write(path):
2330 def write(path):
2328 filename = None
2331 filename = None
2329 if fntemplate:
2332 if fntemplate:
2330 filename = makefilename(ctx, fntemplate,
2333 filename = makefilename(ctx, fntemplate,
2331 pathname=os.path.join(prefix, path))
2334 pathname=os.path.join(prefix, path))
2332 # attempt to create the directory if it does not already exist
2335 # attempt to create the directory if it does not already exist
2333 try:
2336 try:
2334 os.makedirs(os.path.dirname(filename))
2337 os.makedirs(os.path.dirname(filename))
2335 except OSError:
2338 except OSError:
2336 pass
2339 pass
2337 with formatter.maybereopen(basefm, filename) as fm:
2340 with formatter.maybereopen(basefm, filename) as fm:
2338 _updatecatformatter(fm, ctx, matcher, path, opts.get('decode'))
2341 _updatecatformatter(fm, ctx, matcher, path, opts.get('decode'))
2339
2342
2340 # Automation often uses hg cat on single files, so special case it
2343 # Automation often uses hg cat on single files, so special case it
2341 # for performance to avoid the cost of parsing the manifest.
2344 # for performance to avoid the cost of parsing the manifest.
2342 if len(matcher.files()) == 1 and not matcher.anypats():
2345 if len(matcher.files()) == 1 and not matcher.anypats():
2343 file = matcher.files()[0]
2346 file = matcher.files()[0]
2344 mfl = repo.manifestlog
2347 mfl = repo.manifestlog
2345 mfnode = ctx.manifestnode()
2348 mfnode = ctx.manifestnode()
2346 try:
2349 try:
2347 if mfnode and mfl[mfnode].find(file)[0]:
2350 if mfnode and mfl[mfnode].find(file)[0]:
2348 scmutil.prefetchfiles(repo, [ctx.rev()], matcher)
2351 scmutil.prefetchfiles(repo, [ctx.rev()], matcher)
2349 write(file)
2352 write(file)
2350 return 0
2353 return 0
2351 except KeyError:
2354 except KeyError:
2352 pass
2355 pass
2353
2356
2354 scmutil.prefetchfiles(repo, [ctx.rev()], matcher)
2357 scmutil.prefetchfiles(repo, [ctx.rev()], matcher)
2355
2358
2356 for abs in ctx.walk(matcher):
2359 for abs in ctx.walk(matcher):
2357 write(abs)
2360 write(abs)
2358 err = 0
2361 err = 0
2359
2362
2360 for subpath in sorted(ctx.substate):
2363 for subpath in sorted(ctx.substate):
2361 sub = ctx.sub(subpath)
2364 sub = ctx.sub(subpath)
2362 try:
2365 try:
2363 submatch = matchmod.subdirmatcher(subpath, matcher)
2366 submatch = matchmod.subdirmatcher(subpath, matcher)
2364 subprefix = os.path.join(prefix, subpath)
2367 subprefix = os.path.join(prefix, subpath)
2365 if not sub.cat(submatch, basefm, fntemplate, subprefix,
2368 if not sub.cat(submatch, basefm, fntemplate, subprefix,
2366 **pycompat.strkwargs(opts)):
2369 **pycompat.strkwargs(opts)):
2367 err = 0
2370 err = 0
2368 except error.RepoLookupError:
2371 except error.RepoLookupError:
2369 ui.status(_("skipping missing subrepository: %s\n") %
2372 ui.status(_("skipping missing subrepository: %s\n") %
2370 matcher.rel(subpath))
2373 matcher.rel(subpath))
2371
2374
2372 return err
2375 return err
2373
2376
2374 def commit(ui, repo, commitfunc, pats, opts):
2377 def commit(ui, repo, commitfunc, pats, opts):
2375 '''commit the specified files or all outstanding changes'''
2378 '''commit the specified files or all outstanding changes'''
2376 date = opts.get('date')
2379 date = opts.get('date')
2377 if date:
2380 if date:
2378 opts['date'] = dateutil.parsedate(date)
2381 opts['date'] = dateutil.parsedate(date)
2379 message = logmessage(ui, opts)
2382 message = logmessage(ui, opts)
2380 matcher = scmutil.match(repo[None], pats, opts)
2383 matcher = scmutil.match(repo[None], pats, opts)
2381
2384
2382 dsguard = None
2385 dsguard = None
2383 # extract addremove carefully -- this function can be called from a command
2386 # extract addremove carefully -- this function can be called from a command
2384 # that doesn't support addremove
2387 # that doesn't support addremove
2385 if opts.get('addremove'):
2388 if opts.get('addremove'):
2386 dsguard = dirstateguard.dirstateguard(repo, 'commit')
2389 dsguard = dirstateguard.dirstateguard(repo, 'commit')
2387 with dsguard or util.nullcontextmanager():
2390 with dsguard or util.nullcontextmanager():
2388 if dsguard:
2391 if dsguard:
2389 if scmutil.addremove(repo, matcher, "", opts) != 0:
2392 if scmutil.addremove(repo, matcher, "", opts) != 0:
2390 raise error.Abort(
2393 raise error.Abort(
2391 _("failed to mark all new/missing files as added/removed"))
2394 _("failed to mark all new/missing files as added/removed"))
2392
2395
2393 return commitfunc(ui, repo, message, matcher, opts)
2396 return commitfunc(ui, repo, message, matcher, opts)
2394
2397
2395 def samefile(f, ctx1, ctx2):
2398 def samefile(f, ctx1, ctx2):
2396 if f in ctx1.manifest():
2399 if f in ctx1.manifest():
2397 a = ctx1.filectx(f)
2400 a = ctx1.filectx(f)
2398 if f in ctx2.manifest():
2401 if f in ctx2.manifest():
2399 b = ctx2.filectx(f)
2402 b = ctx2.filectx(f)
2400 return (not a.cmp(b)
2403 return (not a.cmp(b)
2401 and a.flags() == b.flags())
2404 and a.flags() == b.flags())
2402 else:
2405 else:
2403 return False
2406 return False
2404 else:
2407 else:
2405 return f not in ctx2.manifest()
2408 return f not in ctx2.manifest()
2406
2409
2407 def amend(ui, repo, old, extra, pats, opts):
2410 def amend(ui, repo, old, extra, pats, opts):
2408 # avoid cycle context -> subrepo -> cmdutil
2411 # avoid cycle context -> subrepo -> cmdutil
2409 from . import context
2412 from . import context
2410
2413
2411 # amend will reuse the existing user if not specified, but the obsolete
2414 # amend will reuse the existing user if not specified, but the obsolete
2412 # marker creation requires that the current user's name is specified.
2415 # marker creation requires that the current user's name is specified.
2413 if obsolete.isenabled(repo, obsolete.createmarkersopt):
2416 if obsolete.isenabled(repo, obsolete.createmarkersopt):
2414 ui.username() # raise exception if username not set
2417 ui.username() # raise exception if username not set
2415
2418
2416 ui.note(_('amending changeset %s\n') % old)
2419 ui.note(_('amending changeset %s\n') % old)
2417 base = old.p1()
2420 base = old.p1()
2418
2421
2419 with repo.wlock(), repo.lock(), repo.transaction('amend'):
2422 with repo.wlock(), repo.lock(), repo.transaction('amend'):
2420 # Participating changesets:
2423 # Participating changesets:
2421 #
2424 #
2422 # wctx o - workingctx that contains changes from working copy
2425 # wctx o - workingctx that contains changes from working copy
2423 # | to go into amending commit
2426 # | to go into amending commit
2424 # |
2427 # |
2425 # old o - changeset to amend
2428 # old o - changeset to amend
2426 # |
2429 # |
2427 # base o - first parent of the changeset to amend
2430 # base o - first parent of the changeset to amend
2428 wctx = repo[None]
2431 wctx = repo[None]
2429
2432
2430 # Copy to avoid mutating input
2433 # Copy to avoid mutating input
2431 extra = extra.copy()
2434 extra = extra.copy()
2432 # Update extra dict from amended commit (e.g. to preserve graft
2435 # Update extra dict from amended commit (e.g. to preserve graft
2433 # source)
2436 # source)
2434 extra.update(old.extra())
2437 extra.update(old.extra())
2435
2438
2436 # Also update it from the from the wctx
2439 # Also update it from the from the wctx
2437 extra.update(wctx.extra())
2440 extra.update(wctx.extra())
2438
2441
2439 user = opts.get('user') or old.user()
2442 user = opts.get('user') or old.user()
2440
2443
2441 datemaydiffer = False # date-only change should be ignored?
2444 datemaydiffer = False # date-only change should be ignored?
2442 if opts.get('date') and opts.get('currentdate'):
2445 if opts.get('date') and opts.get('currentdate'):
2443 raise error.Abort(_('--date and --currentdate are mutually '
2446 raise error.Abort(_('--date and --currentdate are mutually '
2444 'exclusive'))
2447 'exclusive'))
2445 if opts.get('date'):
2448 if opts.get('date'):
2446 date = dateutil.parsedate(opts.get('date'))
2449 date = dateutil.parsedate(opts.get('date'))
2447 elif opts.get('currentdate'):
2450 elif opts.get('currentdate'):
2448 date = dateutil.makedate()
2451 date = dateutil.makedate()
2449 elif (ui.configbool('rewrite', 'update-timestamp')
2452 elif (ui.configbool('rewrite', 'update-timestamp')
2450 and opts.get('currentdate') is None):
2453 and opts.get('currentdate') is None):
2451 date = dateutil.makedate()
2454 date = dateutil.makedate()
2452 datemaydiffer = True
2455 datemaydiffer = True
2453 else:
2456 else:
2454 date = old.date()
2457 date = old.date()
2455
2458
2456 if len(old.parents()) > 1:
2459 if len(old.parents()) > 1:
2457 # ctx.files() isn't reliable for merges, so fall back to the
2460 # ctx.files() isn't reliable for merges, so fall back to the
2458 # slower repo.status() method
2461 # slower repo.status() method
2459 files = set([fn for st in base.status(old)[:3]
2462 files = set([fn for st in base.status(old)[:3]
2460 for fn in st])
2463 for fn in st])
2461 else:
2464 else:
2462 files = set(old.files())
2465 files = set(old.files())
2463
2466
2464 # add/remove the files to the working copy if the "addremove" option
2467 # add/remove the files to the working copy if the "addremove" option
2465 # was specified.
2468 # was specified.
2466 matcher = scmutil.match(wctx, pats, opts)
2469 matcher = scmutil.match(wctx, pats, opts)
2467 if (opts.get('addremove')
2470 if (opts.get('addremove')
2468 and scmutil.addremove(repo, matcher, "", opts)):
2471 and scmutil.addremove(repo, matcher, "", opts)):
2469 raise error.Abort(
2472 raise error.Abort(
2470 _("failed to mark all new/missing files as added/removed"))
2473 _("failed to mark all new/missing files as added/removed"))
2471
2474
2472 # Check subrepos. This depends on in-place wctx._status update in
2475 # Check subrepos. This depends on in-place wctx._status update in
2473 # subrepo.precommit(). To minimize the risk of this hack, we do
2476 # subrepo.precommit(). To minimize the risk of this hack, we do
2474 # nothing if .hgsub does not exist.
2477 # nothing if .hgsub does not exist.
2475 if '.hgsub' in wctx or '.hgsub' in old:
2478 if '.hgsub' in wctx or '.hgsub' in old:
2476 subs, commitsubs, newsubstate = subrepoutil.precommit(
2479 subs, commitsubs, newsubstate = subrepoutil.precommit(
2477 ui, wctx, wctx._status, matcher)
2480 ui, wctx, wctx._status, matcher)
2478 # amend should abort if commitsubrepos is enabled
2481 # amend should abort if commitsubrepos is enabled
2479 assert not commitsubs
2482 assert not commitsubs
2480 if subs:
2483 if subs:
2481 subrepoutil.writestate(repo, newsubstate)
2484 subrepoutil.writestate(repo, newsubstate)
2482
2485
2483 ms = mergemod.mergestate.read(repo)
2486 ms = mergemod.mergestate.read(repo)
2484 mergeutil.checkunresolved(ms)
2487 mergeutil.checkunresolved(ms)
2485
2488
2486 filestoamend = set(f for f in wctx.files() if matcher(f))
2489 filestoamend = set(f for f in wctx.files() if matcher(f))
2487
2490
2488 changes = (len(filestoamend) > 0)
2491 changes = (len(filestoamend) > 0)
2489 if changes:
2492 if changes:
2490 # Recompute copies (avoid recording a -> b -> a)
2493 # Recompute copies (avoid recording a -> b -> a)
2491 copied = copies.pathcopies(base, wctx, matcher)
2494 copied = copies.pathcopies(base, wctx, matcher)
2492 if old.p2:
2495 if old.p2:
2493 copied.update(copies.pathcopies(old.p2(), wctx, matcher))
2496 copied.update(copies.pathcopies(old.p2(), wctx, matcher))
2494
2497
2495 # Prune files which were reverted by the updates: if old
2498 # Prune files which were reverted by the updates: if old
2496 # introduced file X and the file was renamed in the working
2499 # introduced file X and the file was renamed in the working
2497 # copy, then those two files are the same and
2500 # copy, then those two files are the same and
2498 # we can discard X from our list of files. Likewise if X
2501 # we can discard X from our list of files. Likewise if X
2499 # was removed, it's no longer relevant. If X is missing (aka
2502 # was removed, it's no longer relevant. If X is missing (aka
2500 # deleted), old X must be preserved.
2503 # deleted), old X must be preserved.
2501 files.update(filestoamend)
2504 files.update(filestoamend)
2502 files = [f for f in files if (not samefile(f, wctx, base)
2505 files = [f for f in files if (not samefile(f, wctx, base)
2503 or f in wctx.deleted())]
2506 or f in wctx.deleted())]
2504
2507
2505 def filectxfn(repo, ctx_, path):
2508 def filectxfn(repo, ctx_, path):
2506 try:
2509 try:
2507 # If the file being considered is not amongst the files
2510 # If the file being considered is not amongst the files
2508 # to be amended, we should return the file context from the
2511 # to be amended, we should return the file context from the
2509 # old changeset. This avoids issues when only some files in
2512 # old changeset. This avoids issues when only some files in
2510 # the working copy are being amended but there are also
2513 # the working copy are being amended but there are also
2511 # changes to other files from the old changeset.
2514 # changes to other files from the old changeset.
2512 if path not in filestoamend:
2515 if path not in filestoamend:
2513 return old.filectx(path)
2516 return old.filectx(path)
2514
2517
2515 # Return None for removed files.
2518 # Return None for removed files.
2516 if path in wctx.removed():
2519 if path in wctx.removed():
2517 return None
2520 return None
2518
2521
2519 fctx = wctx[path]
2522 fctx = wctx[path]
2520 flags = fctx.flags()
2523 flags = fctx.flags()
2521 mctx = context.memfilectx(repo, ctx_,
2524 mctx = context.memfilectx(repo, ctx_,
2522 fctx.path(), fctx.data(),
2525 fctx.path(), fctx.data(),
2523 islink='l' in flags,
2526 islink='l' in flags,
2524 isexec='x' in flags,
2527 isexec='x' in flags,
2525 copied=copied.get(path))
2528 copied=copied.get(path))
2526 return mctx
2529 return mctx
2527 except KeyError:
2530 except KeyError:
2528 return None
2531 return None
2529 else:
2532 else:
2530 ui.note(_('copying changeset %s to %s\n') % (old, base))
2533 ui.note(_('copying changeset %s to %s\n') % (old, base))
2531
2534
2532 # Use version of files as in the old cset
2535 # Use version of files as in the old cset
2533 def filectxfn(repo, ctx_, path):
2536 def filectxfn(repo, ctx_, path):
2534 try:
2537 try:
2535 return old.filectx(path)
2538 return old.filectx(path)
2536 except KeyError:
2539 except KeyError:
2537 return None
2540 return None
2538
2541
2539 # See if we got a message from -m or -l, if not, open the editor with
2542 # See if we got a message from -m or -l, if not, open the editor with
2540 # the message of the changeset to amend.
2543 # the message of the changeset to amend.
2541 message = logmessage(ui, opts)
2544 message = logmessage(ui, opts)
2542
2545
2543 editform = mergeeditform(old, 'commit.amend')
2546 editform = mergeeditform(old, 'commit.amend')
2544 editor = getcommiteditor(editform=editform,
2547 editor = getcommiteditor(editform=editform,
2545 **pycompat.strkwargs(opts))
2548 **pycompat.strkwargs(opts))
2546
2549
2547 if not message:
2550 if not message:
2548 editor = getcommiteditor(edit=True, editform=editform)
2551 editor = getcommiteditor(edit=True, editform=editform)
2549 message = old.description()
2552 message = old.description()
2550
2553
2551 pureextra = extra.copy()
2554 pureextra = extra.copy()
2552 extra['amend_source'] = old.hex()
2555 extra['amend_source'] = old.hex()
2553
2556
2554 new = context.memctx(repo,
2557 new = context.memctx(repo,
2555 parents=[base.node(), old.p2().node()],
2558 parents=[base.node(), old.p2().node()],
2556 text=message,
2559 text=message,
2557 files=files,
2560 files=files,
2558 filectxfn=filectxfn,
2561 filectxfn=filectxfn,
2559 user=user,
2562 user=user,
2560 date=date,
2563 date=date,
2561 extra=extra,
2564 extra=extra,
2562 editor=editor)
2565 editor=editor)
2563
2566
2564 newdesc = changelog.stripdesc(new.description())
2567 newdesc = changelog.stripdesc(new.description())
2565 if ((not changes)
2568 if ((not changes)
2566 and newdesc == old.description()
2569 and newdesc == old.description()
2567 and user == old.user()
2570 and user == old.user()
2568 and (date == old.date() or datemaydiffer)
2571 and (date == old.date() or datemaydiffer)
2569 and pureextra == old.extra()):
2572 and pureextra == old.extra()):
2570 # nothing changed. continuing here would create a new node
2573 # nothing changed. continuing here would create a new node
2571 # anyway because of the amend_source noise.
2574 # anyway because of the amend_source noise.
2572 #
2575 #
2573 # This not what we expect from amend.
2576 # This not what we expect from amend.
2574 return old.node()
2577 return old.node()
2575
2578
2576 commitphase = None
2579 commitphase = None
2577 if opts.get('secret'):
2580 if opts.get('secret'):
2578 commitphase = phases.secret
2581 commitphase = phases.secret
2579 newid = repo.commitctx(new)
2582 newid = repo.commitctx(new)
2580
2583
2581 # Reroute the working copy parent to the new changeset
2584 # Reroute the working copy parent to the new changeset
2582 repo.setparents(newid, nullid)
2585 repo.setparents(newid, nullid)
2583 mapping = {old.node(): (newid,)}
2586 mapping = {old.node(): (newid,)}
2584 obsmetadata = None
2587 obsmetadata = None
2585 if opts.get('note'):
2588 if opts.get('note'):
2586 obsmetadata = {'note': encoding.fromlocal(opts['note'])}
2589 obsmetadata = {'note': encoding.fromlocal(opts['note'])}
2587 backup = ui.configbool('rewrite', 'backup-bundle')
2590 backup = ui.configbool('rewrite', 'backup-bundle')
2588 scmutil.cleanupnodes(repo, mapping, 'amend', metadata=obsmetadata,
2591 scmutil.cleanupnodes(repo, mapping, 'amend', metadata=obsmetadata,
2589 fixphase=True, targetphase=commitphase,
2592 fixphase=True, targetphase=commitphase,
2590 backup=backup)
2593 backup=backup)
2591
2594
2592 # Fixing the dirstate because localrepo.commitctx does not update
2595 # Fixing the dirstate because localrepo.commitctx does not update
2593 # it. This is rather convenient because we did not need to update
2596 # it. This is rather convenient because we did not need to update
2594 # the dirstate for all the files in the new commit which commitctx
2597 # the dirstate for all the files in the new commit which commitctx
2595 # could have done if it updated the dirstate. Now, we can
2598 # could have done if it updated the dirstate. Now, we can
2596 # selectively update the dirstate only for the amended files.
2599 # selectively update the dirstate only for the amended files.
2597 dirstate = repo.dirstate
2600 dirstate = repo.dirstate
2598
2601
2599 # Update the state of the files which were added and
2602 # Update the state of the files which were added and
2600 # and modified in the amend to "normal" in the dirstate.
2603 # and modified in the amend to "normal" in the dirstate.
2601 normalfiles = set(wctx.modified() + wctx.added()) & filestoamend
2604 normalfiles = set(wctx.modified() + wctx.added()) & filestoamend
2602 for f in normalfiles:
2605 for f in normalfiles:
2603 dirstate.normal(f)
2606 dirstate.normal(f)
2604
2607
2605 # Update the state of files which were removed in the amend
2608 # Update the state of files which were removed in the amend
2606 # to "removed" in the dirstate.
2609 # to "removed" in the dirstate.
2607 removedfiles = set(wctx.removed()) & filestoamend
2610 removedfiles = set(wctx.removed()) & filestoamend
2608 for f in removedfiles:
2611 for f in removedfiles:
2609 dirstate.drop(f)
2612 dirstate.drop(f)
2610
2613
2611 return newid
2614 return newid
2612
2615
2613 def commiteditor(repo, ctx, subs, editform=''):
2616 def commiteditor(repo, ctx, subs, editform=''):
2614 if ctx.description():
2617 if ctx.description():
2615 return ctx.description()
2618 return ctx.description()
2616 return commitforceeditor(repo, ctx, subs, editform=editform,
2619 return commitforceeditor(repo, ctx, subs, editform=editform,
2617 unchangedmessagedetection=True)
2620 unchangedmessagedetection=True)
2618
2621
2619 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
2622 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
2620 editform='', unchangedmessagedetection=False):
2623 editform='', unchangedmessagedetection=False):
2621 if not extramsg:
2624 if not extramsg:
2622 extramsg = _("Leave message empty to abort commit.")
2625 extramsg = _("Leave message empty to abort commit.")
2623
2626
2624 forms = [e for e in editform.split('.') if e]
2627 forms = [e for e in editform.split('.') if e]
2625 forms.insert(0, 'changeset')
2628 forms.insert(0, 'changeset')
2626 templatetext = None
2629 templatetext = None
2627 while forms:
2630 while forms:
2628 ref = '.'.join(forms)
2631 ref = '.'.join(forms)
2629 if repo.ui.config('committemplate', ref):
2632 if repo.ui.config('committemplate', ref):
2630 templatetext = committext = buildcommittemplate(
2633 templatetext = committext = buildcommittemplate(
2631 repo, ctx, subs, extramsg, ref)
2634 repo, ctx, subs, extramsg, ref)
2632 break
2635 break
2633 forms.pop()
2636 forms.pop()
2634 else:
2637 else:
2635 committext = buildcommittext(repo, ctx, subs, extramsg)
2638 committext = buildcommittext(repo, ctx, subs, extramsg)
2636
2639
2637 # run editor in the repository root
2640 # run editor in the repository root
2638 olddir = encoding.getcwd()
2641 olddir = encoding.getcwd()
2639 os.chdir(repo.root)
2642 os.chdir(repo.root)
2640
2643
2641 # make in-memory changes visible to external process
2644 # make in-memory changes visible to external process
2642 tr = repo.currenttransaction()
2645 tr = repo.currenttransaction()
2643 repo.dirstate.write(tr)
2646 repo.dirstate.write(tr)
2644 pending = tr and tr.writepending() and repo.root
2647 pending = tr and tr.writepending() and repo.root
2645
2648
2646 editortext = repo.ui.edit(committext, ctx.user(), ctx.extra(),
2649 editortext = repo.ui.edit(committext, ctx.user(), ctx.extra(),
2647 editform=editform, pending=pending,
2650 editform=editform, pending=pending,
2648 repopath=repo.path, action='commit')
2651 repopath=repo.path, action='commit')
2649 text = editortext
2652 text = editortext
2650
2653
2651 # strip away anything below this special string (used for editors that want
2654 # strip away anything below this special string (used for editors that want
2652 # to display the diff)
2655 # to display the diff)
2653 stripbelow = re.search(_linebelow, text, flags=re.MULTILINE)
2656 stripbelow = re.search(_linebelow, text, flags=re.MULTILINE)
2654 if stripbelow:
2657 if stripbelow:
2655 text = text[:stripbelow.start()]
2658 text = text[:stripbelow.start()]
2656
2659
2657 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
2660 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
2658 os.chdir(olddir)
2661 os.chdir(olddir)
2659
2662
2660 if finishdesc:
2663 if finishdesc:
2661 text = finishdesc(text)
2664 text = finishdesc(text)
2662 if not text.strip():
2665 if not text.strip():
2663 raise error.Abort(_("empty commit message"))
2666 raise error.Abort(_("empty commit message"))
2664 if unchangedmessagedetection and editortext == templatetext:
2667 if unchangedmessagedetection and editortext == templatetext:
2665 raise error.Abort(_("commit message unchanged"))
2668 raise error.Abort(_("commit message unchanged"))
2666
2669
2667 return text
2670 return text
2668
2671
2669 def buildcommittemplate(repo, ctx, subs, extramsg, ref):
2672 def buildcommittemplate(repo, ctx, subs, extramsg, ref):
2670 ui = repo.ui
2673 ui = repo.ui
2671 spec = formatter.templatespec(ref, None, None)
2674 spec = formatter.templatespec(ref, None, None)
2672 t = logcmdutil.changesettemplater(ui, repo, spec)
2675 t = logcmdutil.changesettemplater(ui, repo, spec)
2673 t.t.cache.update((k, templater.unquotestring(v))
2676 t.t.cache.update((k, templater.unquotestring(v))
2674 for k, v in repo.ui.configitems('committemplate'))
2677 for k, v in repo.ui.configitems('committemplate'))
2675
2678
2676 if not extramsg:
2679 if not extramsg:
2677 extramsg = '' # ensure that extramsg is string
2680 extramsg = '' # ensure that extramsg is string
2678
2681
2679 ui.pushbuffer()
2682 ui.pushbuffer()
2680 t.show(ctx, extramsg=extramsg)
2683 t.show(ctx, extramsg=extramsg)
2681 return ui.popbuffer()
2684 return ui.popbuffer()
2682
2685
2683 def hgprefix(msg):
2686 def hgprefix(msg):
2684 return "\n".join(["HG: %s" % a for a in msg.split("\n") if a])
2687 return "\n".join(["HG: %s" % a for a in msg.split("\n") if a])
2685
2688
2686 def buildcommittext(repo, ctx, subs, extramsg):
2689 def buildcommittext(repo, ctx, subs, extramsg):
2687 edittext = []
2690 edittext = []
2688 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2691 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2689 if ctx.description():
2692 if ctx.description():
2690 edittext.append(ctx.description())
2693 edittext.append(ctx.description())
2691 edittext.append("")
2694 edittext.append("")
2692 edittext.append("") # Empty line between message and comments.
2695 edittext.append("") # Empty line between message and comments.
2693 edittext.append(hgprefix(_("Enter commit message."
2696 edittext.append(hgprefix(_("Enter commit message."
2694 " Lines beginning with 'HG:' are removed.")))
2697 " Lines beginning with 'HG:' are removed.")))
2695 edittext.append(hgprefix(extramsg))
2698 edittext.append(hgprefix(extramsg))
2696 edittext.append("HG: --")
2699 edittext.append("HG: --")
2697 edittext.append(hgprefix(_("user: %s") % ctx.user()))
2700 edittext.append(hgprefix(_("user: %s") % ctx.user()))
2698 if ctx.p2():
2701 if ctx.p2():
2699 edittext.append(hgprefix(_("branch merge")))
2702 edittext.append(hgprefix(_("branch merge")))
2700 if ctx.branch():
2703 if ctx.branch():
2701 edittext.append(hgprefix(_("branch '%s'") % ctx.branch()))
2704 edittext.append(hgprefix(_("branch '%s'") % ctx.branch()))
2702 if bookmarks.isactivewdirparent(repo):
2705 if bookmarks.isactivewdirparent(repo):
2703 edittext.append(hgprefix(_("bookmark '%s'") % repo._activebookmark))
2706 edittext.append(hgprefix(_("bookmark '%s'") % repo._activebookmark))
2704 edittext.extend([hgprefix(_("subrepo %s") % s) for s in subs])
2707 edittext.extend([hgprefix(_("subrepo %s") % s) for s in subs])
2705 edittext.extend([hgprefix(_("added %s") % f) for f in added])
2708 edittext.extend([hgprefix(_("added %s") % f) for f in added])
2706 edittext.extend([hgprefix(_("changed %s") % f) for f in modified])
2709 edittext.extend([hgprefix(_("changed %s") % f) for f in modified])
2707 edittext.extend([hgprefix(_("removed %s") % f) for f in removed])
2710 edittext.extend([hgprefix(_("removed %s") % f) for f in removed])
2708 if not added and not modified and not removed:
2711 if not added and not modified and not removed:
2709 edittext.append(hgprefix(_("no files changed")))
2712 edittext.append(hgprefix(_("no files changed")))
2710 edittext.append("")
2713 edittext.append("")
2711
2714
2712 return "\n".join(edittext)
2715 return "\n".join(edittext)
2713
2716
2714 def commitstatus(repo, node, branch, bheads=None, opts=None):
2717 def commitstatus(repo, node, branch, bheads=None, opts=None):
2715 if opts is None:
2718 if opts is None:
2716 opts = {}
2719 opts = {}
2717 ctx = repo[node]
2720 ctx = repo[node]
2718 parents = ctx.parents()
2721 parents = ctx.parents()
2719
2722
2720 if (not opts.get('amend') and bheads and node not in bheads and not
2723 if (not opts.get('amend') and bheads and node not in bheads and not
2721 [x for x in parents if x.node() in bheads and x.branch() == branch]):
2724 [x for x in parents if x.node() in bheads and x.branch() == branch]):
2722 repo.ui.status(_('created new head\n'))
2725 repo.ui.status(_('created new head\n'))
2723 # The message is not printed for initial roots. For the other
2726 # The message is not printed for initial roots. For the other
2724 # changesets, it is printed in the following situations:
2727 # changesets, it is printed in the following situations:
2725 #
2728 #
2726 # Par column: for the 2 parents with ...
2729 # Par column: for the 2 parents with ...
2727 # N: null or no parent
2730 # N: null or no parent
2728 # B: parent is on another named branch
2731 # B: parent is on another named branch
2729 # C: parent is a regular non head changeset
2732 # C: parent is a regular non head changeset
2730 # H: parent was a branch head of the current branch
2733 # H: parent was a branch head of the current branch
2731 # Msg column: whether we print "created new head" message
2734 # Msg column: whether we print "created new head" message
2732 # In the following, it is assumed that there already exists some
2735 # In the following, it is assumed that there already exists some
2733 # initial branch heads of the current branch, otherwise nothing is
2736 # initial branch heads of the current branch, otherwise nothing is
2734 # printed anyway.
2737 # printed anyway.
2735 #
2738 #
2736 # Par Msg Comment
2739 # Par Msg Comment
2737 # N N y additional topo root
2740 # N N y additional topo root
2738 #
2741 #
2739 # B N y additional branch root
2742 # B N y additional branch root
2740 # C N y additional topo head
2743 # C N y additional topo head
2741 # H N n usual case
2744 # H N n usual case
2742 #
2745 #
2743 # B B y weird additional branch root
2746 # B B y weird additional branch root
2744 # C B y branch merge
2747 # C B y branch merge
2745 # H B n merge with named branch
2748 # H B n merge with named branch
2746 #
2749 #
2747 # C C y additional head from merge
2750 # C C y additional head from merge
2748 # C H n merge with a head
2751 # C H n merge with a head
2749 #
2752 #
2750 # H H n head merge: head count decreases
2753 # H H n head merge: head count decreases
2751
2754
2752 if not opts.get('close_branch'):
2755 if not opts.get('close_branch'):
2753 for r in parents:
2756 for r in parents:
2754 if r.closesbranch() and r.branch() == branch:
2757 if r.closesbranch() and r.branch() == branch:
2755 repo.ui.status(_('reopening closed branch head %d\n') % r.rev())
2758 repo.ui.status(_('reopening closed branch head %d\n') % r.rev())
2756
2759
2757 if repo.ui.debugflag:
2760 if repo.ui.debugflag:
2758 repo.ui.write(_('committed changeset %d:%s\n') % (ctx.rev(), ctx.hex()))
2761 repo.ui.write(_('committed changeset %d:%s\n') % (ctx.rev(), ctx.hex()))
2759 elif repo.ui.verbose:
2762 elif repo.ui.verbose:
2760 repo.ui.write(_('committed changeset %d:%s\n') % (ctx.rev(), ctx))
2763 repo.ui.write(_('committed changeset %d:%s\n') % (ctx.rev(), ctx))
2761
2764
2762 def postcommitstatus(repo, pats, opts):
2765 def postcommitstatus(repo, pats, opts):
2763 return repo.status(match=scmutil.match(repo[None], pats, opts))
2766 return repo.status(match=scmutil.match(repo[None], pats, opts))
2764
2767
2765 def revert(ui, repo, ctx, parents, *pats, **opts):
2768 def revert(ui, repo, ctx, parents, *pats, **opts):
2766 opts = pycompat.byteskwargs(opts)
2769 opts = pycompat.byteskwargs(opts)
2767 parent, p2 = parents
2770 parent, p2 = parents
2768 node = ctx.node()
2771 node = ctx.node()
2769
2772
2770 mf = ctx.manifest()
2773 mf = ctx.manifest()
2771 if node == p2:
2774 if node == p2:
2772 parent = p2
2775 parent = p2
2773
2776
2774 # need all matching names in dirstate and manifest of target rev,
2777 # need all matching names in dirstate and manifest of target rev,
2775 # so have to walk both. do not print errors if files exist in one
2778 # so have to walk both. do not print errors if files exist in one
2776 # but not other. in both cases, filesets should be evaluated against
2779 # but not other. in both cases, filesets should be evaluated against
2777 # workingctx to get consistent result (issue4497). this means 'set:**'
2780 # workingctx to get consistent result (issue4497). this means 'set:**'
2778 # cannot be used to select missing files from target rev.
2781 # cannot be used to select missing files from target rev.
2779
2782
2780 # `names` is a mapping for all elements in working copy and target revision
2783 # `names` is a mapping for all elements in working copy and target revision
2781 # The mapping is in the form:
2784 # The mapping is in the form:
2782 # <abs path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
2785 # <abs path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
2783 names = {}
2786 names = {}
2784 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
2787 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
2785
2788
2786 with repo.wlock():
2789 with repo.wlock():
2787 ## filling of the `names` mapping
2790 ## filling of the `names` mapping
2788 # walk dirstate to fill `names`
2791 # walk dirstate to fill `names`
2789
2792
2790 interactive = opts.get('interactive', False)
2793 interactive = opts.get('interactive', False)
2791 wctx = repo[None]
2794 wctx = repo[None]
2792 m = scmutil.match(wctx, pats, opts)
2795 m = scmutil.match(wctx, pats, opts)
2793
2796
2794 # we'll need this later
2797 # we'll need this later
2795 targetsubs = sorted(s for s in wctx.substate if m(s))
2798 targetsubs = sorted(s for s in wctx.substate if m(s))
2796
2799
2797 if not m.always():
2800 if not m.always():
2798 matcher = matchmod.badmatch(m, lambda x, y: False)
2801 matcher = matchmod.badmatch(m, lambda x, y: False)
2799 for abs in wctx.walk(matcher):
2802 for abs in wctx.walk(matcher):
2800 names[abs] = m.exact(abs)
2803 names[abs] = m.exact(abs)
2801
2804
2802 # walk target manifest to fill `names`
2805 # walk target manifest to fill `names`
2803
2806
2804 def badfn(path, msg):
2807 def badfn(path, msg):
2805 if path in names:
2808 if path in names:
2806 return
2809 return
2807 if path in ctx.substate:
2810 if path in ctx.substate:
2808 return
2811 return
2809 path_ = path + '/'
2812 path_ = path + '/'
2810 for f in names:
2813 for f in names:
2811 if f.startswith(path_):
2814 if f.startswith(path_):
2812 return
2815 return
2813 ui.warn("%s: %s\n" % (m.rel(path), msg))
2816 ui.warn("%s: %s\n" % (m.rel(path), msg))
2814
2817
2815 for abs in ctx.walk(matchmod.badmatch(m, badfn)):
2818 for abs in ctx.walk(matchmod.badmatch(m, badfn)):
2816 if abs not in names:
2819 if abs not in names:
2817 names[abs] = m.exact(abs)
2820 names[abs] = m.exact(abs)
2818
2821
2819 # Find status of all file in `names`.
2822 # Find status of all file in `names`.
2820 m = scmutil.matchfiles(repo, names)
2823 m = scmutil.matchfiles(repo, names)
2821
2824
2822 changes = repo.status(node1=node, match=m,
2825 changes = repo.status(node1=node, match=m,
2823 unknown=True, ignored=True, clean=True)
2826 unknown=True, ignored=True, clean=True)
2824 else:
2827 else:
2825 changes = repo.status(node1=node, match=m)
2828 changes = repo.status(node1=node, match=m)
2826 for kind in changes:
2829 for kind in changes:
2827 for abs in kind:
2830 for abs in kind:
2828 names[abs] = m.exact(abs)
2831 names[abs] = m.exact(abs)
2829
2832
2830 m = scmutil.matchfiles(repo, names)
2833 m = scmutil.matchfiles(repo, names)
2831
2834
2832 modified = set(changes.modified)
2835 modified = set(changes.modified)
2833 added = set(changes.added)
2836 added = set(changes.added)
2834 removed = set(changes.removed)
2837 removed = set(changes.removed)
2835 _deleted = set(changes.deleted)
2838 _deleted = set(changes.deleted)
2836 unknown = set(changes.unknown)
2839 unknown = set(changes.unknown)
2837 unknown.update(changes.ignored)
2840 unknown.update(changes.ignored)
2838 clean = set(changes.clean)
2841 clean = set(changes.clean)
2839 modadded = set()
2842 modadded = set()
2840
2843
2841 # We need to account for the state of the file in the dirstate,
2844 # We need to account for the state of the file in the dirstate,
2842 # even when we revert against something else than parent. This will
2845 # even when we revert against something else than parent. This will
2843 # slightly alter the behavior of revert (doing back up or not, delete
2846 # slightly alter the behavior of revert (doing back up or not, delete
2844 # or just forget etc).
2847 # or just forget etc).
2845 if parent == node:
2848 if parent == node:
2846 dsmodified = modified
2849 dsmodified = modified
2847 dsadded = added
2850 dsadded = added
2848 dsremoved = removed
2851 dsremoved = removed
2849 # store all local modifications, useful later for rename detection
2852 # store all local modifications, useful later for rename detection
2850 localchanges = dsmodified | dsadded
2853 localchanges = dsmodified | dsadded
2851 modified, added, removed = set(), set(), set()
2854 modified, added, removed = set(), set(), set()
2852 else:
2855 else:
2853 changes = repo.status(node1=parent, match=m)
2856 changes = repo.status(node1=parent, match=m)
2854 dsmodified = set(changes.modified)
2857 dsmodified = set(changes.modified)
2855 dsadded = set(changes.added)
2858 dsadded = set(changes.added)
2856 dsremoved = set(changes.removed)
2859 dsremoved = set(changes.removed)
2857 # store all local modifications, useful later for rename detection
2860 # store all local modifications, useful later for rename detection
2858 localchanges = dsmodified | dsadded
2861 localchanges = dsmodified | dsadded
2859
2862
2860 # only take into account for removes between wc and target
2863 # only take into account for removes between wc and target
2861 clean |= dsremoved - removed
2864 clean |= dsremoved - removed
2862 dsremoved &= removed
2865 dsremoved &= removed
2863 # distinct between dirstate remove and other
2866 # distinct between dirstate remove and other
2864 removed -= dsremoved
2867 removed -= dsremoved
2865
2868
2866 modadded = added & dsmodified
2869 modadded = added & dsmodified
2867 added -= modadded
2870 added -= modadded
2868
2871
2869 # tell newly modified apart.
2872 # tell newly modified apart.
2870 dsmodified &= modified
2873 dsmodified &= modified
2871 dsmodified |= modified & dsadded # dirstate added may need backup
2874 dsmodified |= modified & dsadded # dirstate added may need backup
2872 modified -= dsmodified
2875 modified -= dsmodified
2873
2876
2874 # We need to wait for some post-processing to update this set
2877 # We need to wait for some post-processing to update this set
2875 # before making the distinction. The dirstate will be used for
2878 # before making the distinction. The dirstate will be used for
2876 # that purpose.
2879 # that purpose.
2877 dsadded = added
2880 dsadded = added
2878
2881
2879 # in case of merge, files that are actually added can be reported as
2882 # in case of merge, files that are actually added can be reported as
2880 # modified, we need to post process the result
2883 # modified, we need to post process the result
2881 if p2 != nullid:
2884 if p2 != nullid:
2882 mergeadd = set(dsmodified)
2885 mergeadd = set(dsmodified)
2883 for path in dsmodified:
2886 for path in dsmodified:
2884 if path in mf:
2887 if path in mf:
2885 mergeadd.remove(path)
2888 mergeadd.remove(path)
2886 dsadded |= mergeadd
2889 dsadded |= mergeadd
2887 dsmodified -= mergeadd
2890 dsmodified -= mergeadd
2888
2891
2889 # if f is a rename, update `names` to also revert the source
2892 # if f is a rename, update `names` to also revert the source
2890 for f in localchanges:
2893 for f in localchanges:
2891 src = repo.dirstate.copied(f)
2894 src = repo.dirstate.copied(f)
2892 # XXX should we check for rename down to target node?
2895 # XXX should we check for rename down to target node?
2893 if src and src not in names and repo.dirstate[src] == 'r':
2896 if src and src not in names and repo.dirstate[src] == 'r':
2894 dsremoved.add(src)
2897 dsremoved.add(src)
2895 names[src] = True
2898 names[src] = True
2896
2899
2897 # determine the exact nature of the deleted changesets
2900 # determine the exact nature of the deleted changesets
2898 deladded = set(_deleted)
2901 deladded = set(_deleted)
2899 for path in _deleted:
2902 for path in _deleted:
2900 if path in mf:
2903 if path in mf:
2901 deladded.remove(path)
2904 deladded.remove(path)
2902 deleted = _deleted - deladded
2905 deleted = _deleted - deladded
2903
2906
2904 # distinguish between file to forget and the other
2907 # distinguish between file to forget and the other
2905 added = set()
2908 added = set()
2906 for abs in dsadded:
2909 for abs in dsadded:
2907 if repo.dirstate[abs] != 'a':
2910 if repo.dirstate[abs] != 'a':
2908 added.add(abs)
2911 added.add(abs)
2909 dsadded -= added
2912 dsadded -= added
2910
2913
2911 for abs in deladded:
2914 for abs in deladded:
2912 if repo.dirstate[abs] == 'a':
2915 if repo.dirstate[abs] == 'a':
2913 dsadded.add(abs)
2916 dsadded.add(abs)
2914 deladded -= dsadded
2917 deladded -= dsadded
2915
2918
2916 # For files marked as removed, we check if an unknown file is present at
2919 # For files marked as removed, we check if an unknown file is present at
2917 # the same path. If a such file exists it may need to be backed up.
2920 # the same path. If a such file exists it may need to be backed up.
2918 # Making the distinction at this stage helps have simpler backup
2921 # Making the distinction at this stage helps have simpler backup
2919 # logic.
2922 # logic.
2920 removunk = set()
2923 removunk = set()
2921 for abs in removed:
2924 for abs in removed:
2922 target = repo.wjoin(abs)
2925 target = repo.wjoin(abs)
2923 if os.path.lexists(target):
2926 if os.path.lexists(target):
2924 removunk.add(abs)
2927 removunk.add(abs)
2925 removed -= removunk
2928 removed -= removunk
2926
2929
2927 dsremovunk = set()
2930 dsremovunk = set()
2928 for abs in dsremoved:
2931 for abs in dsremoved:
2929 target = repo.wjoin(abs)
2932 target = repo.wjoin(abs)
2930 if os.path.lexists(target):
2933 if os.path.lexists(target):
2931 dsremovunk.add(abs)
2934 dsremovunk.add(abs)
2932 dsremoved -= dsremovunk
2935 dsremoved -= dsremovunk
2933
2936
2934 # action to be actually performed by revert
2937 # action to be actually performed by revert
2935 # (<list of file>, message>) tuple
2938 # (<list of file>, message>) tuple
2936 actions = {'revert': ([], _('reverting %s\n')),
2939 actions = {'revert': ([], _('reverting %s\n')),
2937 'add': ([], _('adding %s\n')),
2940 'add': ([], _('adding %s\n')),
2938 'remove': ([], _('removing %s\n')),
2941 'remove': ([], _('removing %s\n')),
2939 'drop': ([], _('removing %s\n')),
2942 'drop': ([], _('removing %s\n')),
2940 'forget': ([], _('forgetting %s\n')),
2943 'forget': ([], _('forgetting %s\n')),
2941 'undelete': ([], _('undeleting %s\n')),
2944 'undelete': ([], _('undeleting %s\n')),
2942 'noop': (None, _('no changes needed to %s\n')),
2945 'noop': (None, _('no changes needed to %s\n')),
2943 'unknown': (None, _('file not managed: %s\n')),
2946 'unknown': (None, _('file not managed: %s\n')),
2944 }
2947 }
2945
2948
2946 # "constant" that convey the backup strategy.
2949 # "constant" that convey the backup strategy.
2947 # All set to `discard` if `no-backup` is set do avoid checking
2950 # All set to `discard` if `no-backup` is set do avoid checking
2948 # no_backup lower in the code.
2951 # no_backup lower in the code.
2949 # These values are ordered for comparison purposes
2952 # These values are ordered for comparison purposes
2950 backupinteractive = 3 # do backup if interactively modified
2953 backupinteractive = 3 # do backup if interactively modified
2951 backup = 2 # unconditionally do backup
2954 backup = 2 # unconditionally do backup
2952 check = 1 # check if the existing file differs from target
2955 check = 1 # check if the existing file differs from target
2953 discard = 0 # never do backup
2956 discard = 0 # never do backup
2954 if opts.get('no_backup'):
2957 if opts.get('no_backup'):
2955 backupinteractive = backup = check = discard
2958 backupinteractive = backup = check = discard
2956 if interactive:
2959 if interactive:
2957 dsmodifiedbackup = backupinteractive
2960 dsmodifiedbackup = backupinteractive
2958 else:
2961 else:
2959 dsmodifiedbackup = backup
2962 dsmodifiedbackup = backup
2960 tobackup = set()
2963 tobackup = set()
2961
2964
2962 backupanddel = actions['remove']
2965 backupanddel = actions['remove']
2963 if not opts.get('no_backup'):
2966 if not opts.get('no_backup'):
2964 backupanddel = actions['drop']
2967 backupanddel = actions['drop']
2965
2968
2966 disptable = (
2969 disptable = (
2967 # dispatch table:
2970 # dispatch table:
2968 # file state
2971 # file state
2969 # action
2972 # action
2970 # make backup
2973 # make backup
2971
2974
2972 ## Sets that results that will change file on disk
2975 ## Sets that results that will change file on disk
2973 # Modified compared to target, no local change
2976 # Modified compared to target, no local change
2974 (modified, actions['revert'], discard),
2977 (modified, actions['revert'], discard),
2975 # Modified compared to target, but local file is deleted
2978 # Modified compared to target, but local file is deleted
2976 (deleted, actions['revert'], discard),
2979 (deleted, actions['revert'], discard),
2977 # Modified compared to target, local change
2980 # Modified compared to target, local change
2978 (dsmodified, actions['revert'], dsmodifiedbackup),
2981 (dsmodified, actions['revert'], dsmodifiedbackup),
2979 # Added since target
2982 # Added since target
2980 (added, actions['remove'], discard),
2983 (added, actions['remove'], discard),
2981 # Added in working directory
2984 # Added in working directory
2982 (dsadded, actions['forget'], discard),
2985 (dsadded, actions['forget'], discard),
2983 # Added since target, have local modification
2986 # Added since target, have local modification
2984 (modadded, backupanddel, backup),
2987 (modadded, backupanddel, backup),
2985 # Added since target but file is missing in working directory
2988 # Added since target but file is missing in working directory
2986 (deladded, actions['drop'], discard),
2989 (deladded, actions['drop'], discard),
2987 # Removed since target, before working copy parent
2990 # Removed since target, before working copy parent
2988 (removed, actions['add'], discard),
2991 (removed, actions['add'], discard),
2989 # Same as `removed` but an unknown file exists at the same path
2992 # Same as `removed` but an unknown file exists at the same path
2990 (removunk, actions['add'], check),
2993 (removunk, actions['add'], check),
2991 # Removed since targe, marked as such in working copy parent
2994 # Removed since targe, marked as such in working copy parent
2992 (dsremoved, actions['undelete'], discard),
2995 (dsremoved, actions['undelete'], discard),
2993 # Same as `dsremoved` but an unknown file exists at the same path
2996 # Same as `dsremoved` but an unknown file exists at the same path
2994 (dsremovunk, actions['undelete'], check),
2997 (dsremovunk, actions['undelete'], check),
2995 ## the following sets does not result in any file changes
2998 ## the following sets does not result in any file changes
2996 # File with no modification
2999 # File with no modification
2997 (clean, actions['noop'], discard),
3000 (clean, actions['noop'], discard),
2998 # Existing file, not tracked anywhere
3001 # Existing file, not tracked anywhere
2999 (unknown, actions['unknown'], discard),
3002 (unknown, actions['unknown'], discard),
3000 )
3003 )
3001
3004
3002 for abs, exact in sorted(names.items()):
3005 for abs, exact in sorted(names.items()):
3003 # target file to be touch on disk (relative to cwd)
3006 # target file to be touch on disk (relative to cwd)
3004 target = repo.wjoin(abs)
3007 target = repo.wjoin(abs)
3005 # search the entry in the dispatch table.
3008 # search the entry in the dispatch table.
3006 # if the file is in any of these sets, it was touched in the working
3009 # if the file is in any of these sets, it was touched in the working
3007 # directory parent and we are sure it needs to be reverted.
3010 # directory parent and we are sure it needs to be reverted.
3008 for table, (xlist, msg), dobackup in disptable:
3011 for table, (xlist, msg), dobackup in disptable:
3009 if abs not in table:
3012 if abs not in table:
3010 continue
3013 continue
3011 if xlist is not None:
3014 if xlist is not None:
3012 xlist.append(abs)
3015 xlist.append(abs)
3013 if dobackup:
3016 if dobackup:
3014 # If in interactive mode, don't automatically create
3017 # If in interactive mode, don't automatically create
3015 # .orig files (issue4793)
3018 # .orig files (issue4793)
3016 if dobackup == backupinteractive:
3019 if dobackup == backupinteractive:
3017 tobackup.add(abs)
3020 tobackup.add(abs)
3018 elif (backup <= dobackup or wctx[abs].cmp(ctx[abs])):
3021 elif (backup <= dobackup or wctx[abs].cmp(ctx[abs])):
3019 absbakname = scmutil.backuppath(ui, repo, abs)
3022 absbakname = scmutil.backuppath(ui, repo, abs)
3020 bakname = os.path.relpath(absbakname,
3023 bakname = os.path.relpath(absbakname,
3021 start=repo.root)
3024 start=repo.root)
3022 ui.note(_('saving current version of %s as %s\n') %
3025 ui.note(_('saving current version of %s as %s\n') %
3023 (uipathfn(abs), uipathfn(bakname)))
3026 (uipathfn(abs), uipathfn(bakname)))
3024 if not opts.get('dry_run'):
3027 if not opts.get('dry_run'):
3025 if interactive:
3028 if interactive:
3026 util.copyfile(target, absbakname)
3029 util.copyfile(target, absbakname)
3027 else:
3030 else:
3028 util.rename(target, absbakname)
3031 util.rename(target, absbakname)
3029 if opts.get('dry_run'):
3032 if opts.get('dry_run'):
3030 if ui.verbose or not exact:
3033 if ui.verbose or not exact:
3031 ui.status(msg % uipathfn(abs))
3034 ui.status(msg % uipathfn(abs))
3032 elif exact:
3035 elif exact:
3033 ui.warn(msg % uipathfn(abs))
3036 ui.warn(msg % uipathfn(abs))
3034 break
3037 break
3035
3038
3036 if not opts.get('dry_run'):
3039 if not opts.get('dry_run'):
3037 needdata = ('revert', 'add', 'undelete')
3040 needdata = ('revert', 'add', 'undelete')
3038 oplist = [actions[name][0] for name in needdata]
3041 oplist = [actions[name][0] for name in needdata]
3039 prefetch = scmutil.prefetchfiles
3042 prefetch = scmutil.prefetchfiles
3040 matchfiles = scmutil.matchfiles
3043 matchfiles = scmutil.matchfiles
3041 prefetch(repo, [ctx.rev()],
3044 prefetch(repo, [ctx.rev()],
3042 matchfiles(repo,
3045 matchfiles(repo,
3043 [f for sublist in oplist for f in sublist]))
3046 [f for sublist in oplist for f in sublist]))
3044 _performrevert(repo, parents, ctx, names, uipathfn, actions,
3047 _performrevert(repo, parents, ctx, names, uipathfn, actions,
3045 interactive, tobackup)
3048 interactive, tobackup)
3046
3049
3047 if targetsubs:
3050 if targetsubs:
3048 # Revert the subrepos on the revert list
3051 # Revert the subrepos on the revert list
3049 for sub in targetsubs:
3052 for sub in targetsubs:
3050 try:
3053 try:
3051 wctx.sub(sub).revert(ctx.substate[sub], *pats,
3054 wctx.sub(sub).revert(ctx.substate[sub], *pats,
3052 **pycompat.strkwargs(opts))
3055 **pycompat.strkwargs(opts))
3053 except KeyError:
3056 except KeyError:
3054 raise error.Abort("subrepository '%s' does not exist in %s!"
3057 raise error.Abort("subrepository '%s' does not exist in %s!"
3055 % (sub, short(ctx.node())))
3058 % (sub, short(ctx.node())))
3056
3059
3057 def _performrevert(repo, parents, ctx, names, uipathfn, actions,
3060 def _performrevert(repo, parents, ctx, names, uipathfn, actions,
3058 interactive=False, tobackup=None):
3061 interactive=False, tobackup=None):
3059 """function that actually perform all the actions computed for revert
3062 """function that actually perform all the actions computed for revert
3060
3063
3061 This is an independent function to let extension to plug in and react to
3064 This is an independent function to let extension to plug in and react to
3062 the imminent revert.
3065 the imminent revert.
3063
3066
3064 Make sure you have the working directory locked when calling this function.
3067 Make sure you have the working directory locked when calling this function.
3065 """
3068 """
3066 parent, p2 = parents
3069 parent, p2 = parents
3067 node = ctx.node()
3070 node = ctx.node()
3068 excluded_files = []
3071 excluded_files = []
3069
3072
3070 def checkout(f):
3073 def checkout(f):
3071 fc = ctx[f]
3074 fc = ctx[f]
3072 repo.wwrite(f, fc.data(), fc.flags())
3075 repo.wwrite(f, fc.data(), fc.flags())
3073
3076
3074 def doremove(f):
3077 def doremove(f):
3075 try:
3078 try:
3076 rmdir = repo.ui.configbool('experimental', 'removeemptydirs')
3079 rmdir = repo.ui.configbool('experimental', 'removeemptydirs')
3077 repo.wvfs.unlinkpath(f, rmdir=rmdir)
3080 repo.wvfs.unlinkpath(f, rmdir=rmdir)
3078 except OSError:
3081 except OSError:
3079 pass
3082 pass
3080 repo.dirstate.remove(f)
3083 repo.dirstate.remove(f)
3081
3084
3082 def prntstatusmsg(action, f):
3085 def prntstatusmsg(action, f):
3083 exact = names[f]
3086 exact = names[f]
3084 if repo.ui.verbose or not exact:
3087 if repo.ui.verbose or not exact:
3085 repo.ui.status(actions[action][1] % uipathfn(f))
3088 repo.ui.status(actions[action][1] % uipathfn(f))
3086
3089
3087 audit_path = pathutil.pathauditor(repo.root, cached=True)
3090 audit_path = pathutil.pathauditor(repo.root, cached=True)
3088 for f in actions['forget'][0]:
3091 for f in actions['forget'][0]:
3089 if interactive:
3092 if interactive:
3090 choice = repo.ui.promptchoice(
3093 choice = repo.ui.promptchoice(
3091 _("forget added file %s (Yn)?$$ &Yes $$ &No") % uipathfn(f))
3094 _("forget added file %s (Yn)?$$ &Yes $$ &No") % uipathfn(f))
3092 if choice == 0:
3095 if choice == 0:
3093 prntstatusmsg('forget', f)
3096 prntstatusmsg('forget', f)
3094 repo.dirstate.drop(f)
3097 repo.dirstate.drop(f)
3095 else:
3098 else:
3096 excluded_files.append(f)
3099 excluded_files.append(f)
3097 else:
3100 else:
3098 prntstatusmsg('forget', f)
3101 prntstatusmsg('forget', f)
3099 repo.dirstate.drop(f)
3102 repo.dirstate.drop(f)
3100 for f in actions['remove'][0]:
3103 for f in actions['remove'][0]:
3101 audit_path(f)
3104 audit_path(f)
3102 if interactive:
3105 if interactive:
3103 choice = repo.ui.promptchoice(
3106 choice = repo.ui.promptchoice(
3104 _("remove added file %s (Yn)?$$ &Yes $$ &No") % uipathfn(f))
3107 _("remove added file %s (Yn)?$$ &Yes $$ &No") % uipathfn(f))
3105 if choice == 0:
3108 if choice == 0:
3106 prntstatusmsg('remove', f)
3109 prntstatusmsg('remove', f)
3107 doremove(f)
3110 doremove(f)
3108 else:
3111 else:
3109 excluded_files.append(f)
3112 excluded_files.append(f)
3110 else:
3113 else:
3111 prntstatusmsg('remove', f)
3114 prntstatusmsg('remove', f)
3112 doremove(f)
3115 doremove(f)
3113 for f in actions['drop'][0]:
3116 for f in actions['drop'][0]:
3114 audit_path(f)
3117 audit_path(f)
3115 prntstatusmsg('drop', f)
3118 prntstatusmsg('drop', f)
3116 repo.dirstate.remove(f)
3119 repo.dirstate.remove(f)
3117
3120
3118 normal = None
3121 normal = None
3119 if node == parent:
3122 if node == parent:
3120 # We're reverting to our parent. If possible, we'd like status
3123 # We're reverting to our parent. If possible, we'd like status
3121 # to report the file as clean. We have to use normallookup for
3124 # to report the file as clean. We have to use normallookup for
3122 # merges to avoid losing information about merged/dirty files.
3125 # merges to avoid losing information about merged/dirty files.
3123 if p2 != nullid:
3126 if p2 != nullid:
3124 normal = repo.dirstate.normallookup
3127 normal = repo.dirstate.normallookup
3125 else:
3128 else:
3126 normal = repo.dirstate.normal
3129 normal = repo.dirstate.normal
3127
3130
3128 newlyaddedandmodifiedfiles = set()
3131 newlyaddedandmodifiedfiles = set()
3129 if interactive:
3132 if interactive:
3130 # Prompt the user for changes to revert
3133 # Prompt the user for changes to revert
3131 torevert = [f for f in actions['revert'][0] if f not in excluded_files]
3134 torevert = [f for f in actions['revert'][0] if f not in excluded_files]
3132 m = scmutil.matchfiles(repo, torevert)
3135 m = scmutil.matchfiles(repo, torevert)
3133 diffopts = patch.difffeatureopts(repo.ui, whitespace=True,
3136 diffopts = patch.difffeatureopts(repo.ui, whitespace=True,
3134 section='commands',
3137 section='commands',
3135 configprefix='revert.interactive.')
3138 configprefix='revert.interactive.')
3136 diffopts.nodates = True
3139 diffopts.nodates = True
3137 diffopts.git = True
3140 diffopts.git = True
3138 operation = 'discard'
3141 operation = 'discard'
3139 reversehunks = True
3142 reversehunks = True
3140 if node != parent:
3143 if node != parent:
3141 operation = 'apply'
3144 operation = 'apply'
3142 reversehunks = False
3145 reversehunks = False
3143 if reversehunks:
3146 if reversehunks:
3144 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
3147 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
3145 else:
3148 else:
3146 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
3149 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
3147 originalchunks = patch.parsepatch(diff)
3150 originalchunks = patch.parsepatch(diff)
3148
3151
3149 try:
3152 try:
3150
3153
3151 chunks, opts = recordfilter(repo.ui, originalchunks,
3154 chunks, opts = recordfilter(repo.ui, originalchunks,
3152 operation=operation)
3155 operation=operation)
3153 if reversehunks:
3156 if reversehunks:
3154 chunks = patch.reversehunks(chunks)
3157 chunks = patch.reversehunks(chunks)
3155
3158
3156 except error.PatchError as err:
3159 except error.PatchError as err:
3157 raise error.Abort(_('error parsing patch: %s') % err)
3160 raise error.Abort(_('error parsing patch: %s') % err)
3158
3161
3159 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
3162 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
3160 if tobackup is None:
3163 if tobackup is None:
3161 tobackup = set()
3164 tobackup = set()
3162 # Apply changes
3165 # Apply changes
3163 fp = stringio()
3166 fp = stringio()
3164 # chunks are serialized per file, but files aren't sorted
3167 # chunks are serialized per file, but files aren't sorted
3165 for f in sorted(set(c.header.filename() for c in chunks if ishunk(c))):
3168 for f in sorted(set(c.header.filename() for c in chunks if ishunk(c))):
3166 prntstatusmsg('revert', f)
3169 prntstatusmsg('revert', f)
3167 for c in chunks:
3170 for c in chunks:
3168 if ishunk(c):
3171 if ishunk(c):
3169 abs = c.header.filename()
3172 abs = c.header.filename()
3170 # Create a backup file only if this hunk should be backed up
3173 # Create a backup file only if this hunk should be backed up
3171 if c.header.filename() in tobackup:
3174 if c.header.filename() in tobackup:
3172 target = repo.wjoin(abs)
3175 target = repo.wjoin(abs)
3173 bakname = scmutil.backuppath(repo.ui, repo, abs)
3176 bakname = scmutil.backuppath(repo.ui, repo, abs)
3174 util.copyfile(target, bakname)
3177 util.copyfile(target, bakname)
3175 tobackup.remove(abs)
3178 tobackup.remove(abs)
3176 c.write(fp)
3179 c.write(fp)
3177 dopatch = fp.tell()
3180 dopatch = fp.tell()
3178 fp.seek(0)
3181 fp.seek(0)
3179 if dopatch:
3182 if dopatch:
3180 try:
3183 try:
3181 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3184 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3182 except error.PatchError as err:
3185 except error.PatchError as err:
3183 raise error.Abort(pycompat.bytestr(err))
3186 raise error.Abort(pycompat.bytestr(err))
3184 del fp
3187 del fp
3185 else:
3188 else:
3186 for f in actions['revert'][0]:
3189 for f in actions['revert'][0]:
3187 prntstatusmsg('revert', f)
3190 prntstatusmsg('revert', f)
3188 checkout(f)
3191 checkout(f)
3189 if normal:
3192 if normal:
3190 normal(f)
3193 normal(f)
3191
3194
3192 for f in actions['add'][0]:
3195 for f in actions['add'][0]:
3193 # Don't checkout modified files, they are already created by the diff
3196 # Don't checkout modified files, they are already created by the diff
3194 if f not in newlyaddedandmodifiedfiles:
3197 if f not in newlyaddedandmodifiedfiles:
3195 prntstatusmsg('add', f)
3198 prntstatusmsg('add', f)
3196 checkout(f)
3199 checkout(f)
3197 repo.dirstate.add(f)
3200 repo.dirstate.add(f)
3198
3201
3199 normal = repo.dirstate.normallookup
3202 normal = repo.dirstate.normallookup
3200 if node == parent and p2 == nullid:
3203 if node == parent and p2 == nullid:
3201 normal = repo.dirstate.normal
3204 normal = repo.dirstate.normal
3202 for f in actions['undelete'][0]:
3205 for f in actions['undelete'][0]:
3203 if interactive:
3206 if interactive:
3204 choice = repo.ui.promptchoice(
3207 choice = repo.ui.promptchoice(
3205 _("add back removed file %s (Yn)?$$ &Yes $$ &No") % f)
3208 _("add back removed file %s (Yn)?$$ &Yes $$ &No") % f)
3206 if choice == 0:
3209 if choice == 0:
3207 prntstatusmsg('undelete', f)
3210 prntstatusmsg('undelete', f)
3208 checkout(f)
3211 checkout(f)
3209 normal(f)
3212 normal(f)
3210 else:
3213 else:
3211 excluded_files.append(f)
3214 excluded_files.append(f)
3212 else:
3215 else:
3213 prntstatusmsg('undelete', f)
3216 prntstatusmsg('undelete', f)
3214 checkout(f)
3217 checkout(f)
3215 normal(f)
3218 normal(f)
3216
3219
3217 copied = copies.pathcopies(repo[parent], ctx)
3220 copied = copies.pathcopies(repo[parent], ctx)
3218
3221
3219 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
3222 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
3220 if f in copied:
3223 if f in copied:
3221 repo.dirstate.copy(copied[f], f)
3224 repo.dirstate.copy(copied[f], f)
3222
3225
3223 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3226 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3224 # commands.outgoing. "missing" is "missing" of the result of
3227 # commands.outgoing. "missing" is "missing" of the result of
3225 # "findcommonoutgoing()"
3228 # "findcommonoutgoing()"
3226 outgoinghooks = util.hooks()
3229 outgoinghooks = util.hooks()
3227
3230
3228 # a list of (ui, repo) functions called by commands.summary
3231 # a list of (ui, repo) functions called by commands.summary
3229 summaryhooks = util.hooks()
3232 summaryhooks = util.hooks()
3230
3233
3231 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3234 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3232 #
3235 #
3233 # functions should return tuple of booleans below, if 'changes' is None:
3236 # functions should return tuple of booleans below, if 'changes' is None:
3234 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3237 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3235 #
3238 #
3236 # otherwise, 'changes' is a tuple of tuples below:
3239 # otherwise, 'changes' is a tuple of tuples below:
3237 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3240 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3238 # - (desturl, destbranch, destpeer, outgoing)
3241 # - (desturl, destbranch, destpeer, outgoing)
3239 summaryremotehooks = util.hooks()
3242 summaryremotehooks = util.hooks()
3240
3243
3241 # A list of state files kept by multistep operations like graft.
3244 # A list of state files kept by multistep operations like graft.
3242 # Since graft cannot be aborted, it is considered 'clearable' by update.
3245 # Since graft cannot be aborted, it is considered 'clearable' by update.
3243 # note: bisect is intentionally excluded
3246 # note: bisect is intentionally excluded
3244 # (state file, clearable, allowcommit, error, hint)
3247 # (state file, clearable, allowcommit, error, hint)
3245 unfinishedstates = [
3248 unfinishedstates = [
3246 ('graftstate', True, False, _('graft in progress'),
3249 ('graftstate', True, False, _('graft in progress'),
3247 _("use 'hg graft --continue' or 'hg graft --stop' to stop")),
3250 _("use 'hg graft --continue' or 'hg graft --stop' to stop")),
3248 ('updatestate', True, False, _('last update was interrupted'),
3251 ('updatestate', True, False, _('last update was interrupted'),
3249 _("use 'hg update' to get a consistent checkout"))
3252 _("use 'hg update' to get a consistent checkout"))
3250 ]
3253 ]
3251
3254
3252 def checkunfinished(repo, commit=False):
3255 def checkunfinished(repo, commit=False):
3253 '''Look for an unfinished multistep operation, like graft, and abort
3256 '''Look for an unfinished multistep operation, like graft, and abort
3254 if found. It's probably good to check this right before
3257 if found. It's probably good to check this right before
3255 bailifchanged().
3258 bailifchanged().
3256 '''
3259 '''
3257 # Check for non-clearable states first, so things like rebase will take
3260 # Check for non-clearable states first, so things like rebase will take
3258 # precedence over update.
3261 # precedence over update.
3259 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3262 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3260 if clearable or (commit and allowcommit):
3263 if clearable or (commit and allowcommit):
3261 continue
3264 continue
3262 if repo.vfs.exists(f):
3265 if repo.vfs.exists(f):
3263 raise error.Abort(msg, hint=hint)
3266 raise error.Abort(msg, hint=hint)
3264
3267
3265 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3268 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3266 if not clearable or (commit and allowcommit):
3269 if not clearable or (commit and allowcommit):
3267 continue
3270 continue
3268 if repo.vfs.exists(f):
3271 if repo.vfs.exists(f):
3269 raise error.Abort(msg, hint=hint)
3272 raise error.Abort(msg, hint=hint)
3270
3273
3271 def clearunfinished(repo):
3274 def clearunfinished(repo):
3272 '''Check for unfinished operations (as above), and clear the ones
3275 '''Check for unfinished operations (as above), and clear the ones
3273 that are clearable.
3276 that are clearable.
3274 '''
3277 '''
3275 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3278 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3276 if not clearable and repo.vfs.exists(f):
3279 if not clearable and repo.vfs.exists(f):
3277 raise error.Abort(msg, hint=hint)
3280 raise error.Abort(msg, hint=hint)
3278 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3281 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3279 if clearable and repo.vfs.exists(f):
3282 if clearable and repo.vfs.exists(f):
3280 util.unlink(repo.vfs.join(f))
3283 util.unlink(repo.vfs.join(f))
3281
3284
3282 afterresolvedstates = [
3285 afterresolvedstates = [
3283 ('graftstate',
3286 ('graftstate',
3284 _('hg graft --continue')),
3287 _('hg graft --continue')),
3285 ]
3288 ]
3286
3289
3287 def howtocontinue(repo):
3290 def howtocontinue(repo):
3288 '''Check for an unfinished operation and return the command to finish
3291 '''Check for an unfinished operation and return the command to finish
3289 it.
3292 it.
3290
3293
3291 afterresolvedstates tuples define a .hg/{file} and the corresponding
3294 afterresolvedstates tuples define a .hg/{file} and the corresponding
3292 command needed to finish it.
3295 command needed to finish it.
3293
3296
3294 Returns a (msg, warning) tuple. 'msg' is a string and 'warning' is
3297 Returns a (msg, warning) tuple. 'msg' is a string and 'warning' is
3295 a boolean.
3298 a boolean.
3296 '''
3299 '''
3297 contmsg = _("continue: %s")
3300 contmsg = _("continue: %s")
3298 for f, msg in afterresolvedstates:
3301 for f, msg in afterresolvedstates:
3299 if repo.vfs.exists(f):
3302 if repo.vfs.exists(f):
3300 return contmsg % msg, True
3303 return contmsg % msg, True
3301 if repo[None].dirty(missing=True, merge=False, branch=False):
3304 if repo[None].dirty(missing=True, merge=False, branch=False):
3302 return contmsg % _("hg commit"), False
3305 return contmsg % _("hg commit"), False
3303 return None, None
3306 return None, None
3304
3307
3305 def checkafterresolved(repo):
3308 def checkafterresolved(repo):
3306 '''Inform the user about the next action after completing hg resolve
3309 '''Inform the user about the next action after completing hg resolve
3307
3310
3308 If there's a matching afterresolvedstates, howtocontinue will yield
3311 If there's a matching afterresolvedstates, howtocontinue will yield
3309 repo.ui.warn as the reporter.
3312 repo.ui.warn as the reporter.
3310
3313
3311 Otherwise, it will yield repo.ui.note.
3314 Otherwise, it will yield repo.ui.note.
3312 '''
3315 '''
3313 msg, warning = howtocontinue(repo)
3316 msg, warning = howtocontinue(repo)
3314 if msg is not None:
3317 if msg is not None:
3315 if warning:
3318 if warning:
3316 repo.ui.warn("%s\n" % msg)
3319 repo.ui.warn("%s\n" % msg)
3317 else:
3320 else:
3318 repo.ui.note("%s\n" % msg)
3321 repo.ui.note("%s\n" % msg)
3319
3322
3320 def wrongtooltocontinue(repo, task):
3323 def wrongtooltocontinue(repo, task):
3321 '''Raise an abort suggesting how to properly continue if there is an
3324 '''Raise an abort suggesting how to properly continue if there is an
3322 active task.
3325 active task.
3323
3326
3324 Uses howtocontinue() to find the active task.
3327 Uses howtocontinue() to find the active task.
3325
3328
3326 If there's no task (repo.ui.note for 'hg commit'), it does not offer
3329 If there's no task (repo.ui.note for 'hg commit'), it does not offer
3327 a hint.
3330 a hint.
3328 '''
3331 '''
3329 after = howtocontinue(repo)
3332 after = howtocontinue(repo)
3330 hint = None
3333 hint = None
3331 if after[1]:
3334 if after[1]:
3332 hint = after[0]
3335 hint = after[0]
3333 raise error.Abort(_('no %s in progress') % task, hint=hint)
3336 raise error.Abort(_('no %s in progress') % task, hint=hint)
@@ -1,6228 +1,6229 b''
1 # commands.py - command processing for mercurial
1 # commands.py - command processing for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import difflib
10 import difflib
11 import errno
11 import errno
12 import os
12 import os
13 import re
13 import re
14 import sys
14 import sys
15
15
16 from .i18n import _
16 from .i18n import _
17 from .node import (
17 from .node import (
18 hex,
18 hex,
19 nullid,
19 nullid,
20 nullrev,
20 nullrev,
21 short,
21 short,
22 wdirhex,
22 wdirhex,
23 wdirrev,
23 wdirrev,
24 )
24 )
25 from . import (
25 from . import (
26 archival,
26 archival,
27 bookmarks,
27 bookmarks,
28 bundle2,
28 bundle2,
29 changegroup,
29 changegroup,
30 cmdutil,
30 cmdutil,
31 copies,
31 copies,
32 debugcommands as debugcommandsmod,
32 debugcommands as debugcommandsmod,
33 destutil,
33 destutil,
34 dirstateguard,
34 dirstateguard,
35 discovery,
35 discovery,
36 encoding,
36 encoding,
37 error,
37 error,
38 exchange,
38 exchange,
39 extensions,
39 extensions,
40 filemerge,
40 filemerge,
41 formatter,
41 formatter,
42 graphmod,
42 graphmod,
43 hbisect,
43 hbisect,
44 help,
44 help,
45 hg,
45 hg,
46 logcmdutil,
46 logcmdutil,
47 merge as mergemod,
47 merge as mergemod,
48 narrowspec,
48 narrowspec,
49 obsolete,
49 obsolete,
50 obsutil,
50 obsutil,
51 patch,
51 patch,
52 phases,
52 phases,
53 pycompat,
53 pycompat,
54 rcutil,
54 rcutil,
55 registrar,
55 registrar,
56 repair,
56 repair,
57 revsetlang,
57 revsetlang,
58 rewriteutil,
58 rewriteutil,
59 scmutil,
59 scmutil,
60 server,
60 server,
61 state as statemod,
61 state as statemod,
62 streamclone,
62 streamclone,
63 tags as tagsmod,
63 tags as tagsmod,
64 templatekw,
64 templatekw,
65 ui as uimod,
65 ui as uimod,
66 util,
66 util,
67 wireprotoserver,
67 wireprotoserver,
68 )
68 )
69 from .utils import (
69 from .utils import (
70 dateutil,
70 dateutil,
71 stringutil,
71 stringutil,
72 )
72 )
73
73
74 table = {}
74 table = {}
75 table.update(debugcommandsmod.command._table)
75 table.update(debugcommandsmod.command._table)
76
76
77 command = registrar.command(table)
77 command = registrar.command(table)
78 INTENT_READONLY = registrar.INTENT_READONLY
78 INTENT_READONLY = registrar.INTENT_READONLY
79
79
80 # common command options
80 # common command options
81
81
82 globalopts = [
82 globalopts = [
83 ('R', 'repository', '',
83 ('R', 'repository', '',
84 _('repository root directory or name of overlay bundle file'),
84 _('repository root directory or name of overlay bundle file'),
85 _('REPO')),
85 _('REPO')),
86 ('', 'cwd', '',
86 ('', 'cwd', '',
87 _('change working directory'), _('DIR')),
87 _('change working directory'), _('DIR')),
88 ('y', 'noninteractive', None,
88 ('y', 'noninteractive', None,
89 _('do not prompt, automatically pick the first choice for all prompts')),
89 _('do not prompt, automatically pick the first choice for all prompts')),
90 ('q', 'quiet', None, _('suppress output')),
90 ('q', 'quiet', None, _('suppress output')),
91 ('v', 'verbose', None, _('enable additional output')),
91 ('v', 'verbose', None, _('enable additional output')),
92 ('', 'color', '',
92 ('', 'color', '',
93 # i18n: 'always', 'auto', 'never', and 'debug' are keywords
93 # i18n: 'always', 'auto', 'never', and 'debug' are keywords
94 # and should not be translated
94 # and should not be translated
95 _("when to colorize (boolean, always, auto, never, or debug)"),
95 _("when to colorize (boolean, always, auto, never, or debug)"),
96 _('TYPE')),
96 _('TYPE')),
97 ('', 'config', [],
97 ('', 'config', [],
98 _('set/override config option (use \'section.name=value\')'),
98 _('set/override config option (use \'section.name=value\')'),
99 _('CONFIG')),
99 _('CONFIG')),
100 ('', 'debug', None, _('enable debugging output')),
100 ('', 'debug', None, _('enable debugging output')),
101 ('', 'debugger', None, _('start debugger')),
101 ('', 'debugger', None, _('start debugger')),
102 ('', 'encoding', encoding.encoding, _('set the charset encoding'),
102 ('', 'encoding', encoding.encoding, _('set the charset encoding'),
103 _('ENCODE')),
103 _('ENCODE')),
104 ('', 'encodingmode', encoding.encodingmode,
104 ('', 'encodingmode', encoding.encodingmode,
105 _('set the charset encoding mode'), _('MODE')),
105 _('set the charset encoding mode'), _('MODE')),
106 ('', 'traceback', None, _('always print a traceback on exception')),
106 ('', 'traceback', None, _('always print a traceback on exception')),
107 ('', 'time', None, _('time how long the command takes')),
107 ('', 'time', None, _('time how long the command takes')),
108 ('', 'profile', None, _('print command execution profile')),
108 ('', 'profile', None, _('print command execution profile')),
109 ('', 'version', None, _('output version information and exit')),
109 ('', 'version', None, _('output version information and exit')),
110 ('h', 'help', None, _('display help and exit')),
110 ('h', 'help', None, _('display help and exit')),
111 ('', 'hidden', False, _('consider hidden changesets')),
111 ('', 'hidden', False, _('consider hidden changesets')),
112 ('', 'pager', 'auto',
112 ('', 'pager', 'auto',
113 _("when to paginate (boolean, always, auto, or never)"), _('TYPE')),
113 _("when to paginate (boolean, always, auto, or never)"), _('TYPE')),
114 ]
114 ]
115
115
116 dryrunopts = cmdutil.dryrunopts
116 dryrunopts = cmdutil.dryrunopts
117 remoteopts = cmdutil.remoteopts
117 remoteopts = cmdutil.remoteopts
118 walkopts = cmdutil.walkopts
118 walkopts = cmdutil.walkopts
119 commitopts = cmdutil.commitopts
119 commitopts = cmdutil.commitopts
120 commitopts2 = cmdutil.commitopts2
120 commitopts2 = cmdutil.commitopts2
121 formatteropts = cmdutil.formatteropts
121 formatteropts = cmdutil.formatteropts
122 templateopts = cmdutil.templateopts
122 templateopts = cmdutil.templateopts
123 logopts = cmdutil.logopts
123 logopts = cmdutil.logopts
124 diffopts = cmdutil.diffopts
124 diffopts = cmdutil.diffopts
125 diffwsopts = cmdutil.diffwsopts
125 diffwsopts = cmdutil.diffwsopts
126 diffopts2 = cmdutil.diffopts2
126 diffopts2 = cmdutil.diffopts2
127 mergetoolopts = cmdutil.mergetoolopts
127 mergetoolopts = cmdutil.mergetoolopts
128 similarityopts = cmdutil.similarityopts
128 similarityopts = cmdutil.similarityopts
129 subrepoopts = cmdutil.subrepoopts
129 subrepoopts = cmdutil.subrepoopts
130 debugrevlogopts = cmdutil.debugrevlogopts
130 debugrevlogopts = cmdutil.debugrevlogopts
131
131
132 # Commands start here, listed alphabetically
132 # Commands start here, listed alphabetically
133
133
134 @command('add',
134 @command('add',
135 walkopts + subrepoopts + dryrunopts,
135 walkopts + subrepoopts + dryrunopts,
136 _('[OPTION]... [FILE]...'),
136 _('[OPTION]... [FILE]...'),
137 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
137 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
138 helpbasic=True, inferrepo=True)
138 helpbasic=True, inferrepo=True)
139 def add(ui, repo, *pats, **opts):
139 def add(ui, repo, *pats, **opts):
140 """add the specified files on the next commit
140 """add the specified files on the next commit
141
141
142 Schedule files to be version controlled and added to the
142 Schedule files to be version controlled and added to the
143 repository.
143 repository.
144
144
145 The files will be added to the repository at the next commit. To
145 The files will be added to the repository at the next commit. To
146 undo an add before that, see :hg:`forget`.
146 undo an add before that, see :hg:`forget`.
147
147
148 If no names are given, add all files to the repository (except
148 If no names are given, add all files to the repository (except
149 files matching ``.hgignore``).
149 files matching ``.hgignore``).
150
150
151 .. container:: verbose
151 .. container:: verbose
152
152
153 Examples:
153 Examples:
154
154
155 - New (unknown) files are added
155 - New (unknown) files are added
156 automatically by :hg:`add`::
156 automatically by :hg:`add`::
157
157
158 $ ls
158 $ ls
159 foo.c
159 foo.c
160 $ hg status
160 $ hg status
161 ? foo.c
161 ? foo.c
162 $ hg add
162 $ hg add
163 adding foo.c
163 adding foo.c
164 $ hg status
164 $ hg status
165 A foo.c
165 A foo.c
166
166
167 - Specific files to be added can be specified::
167 - Specific files to be added can be specified::
168
168
169 $ ls
169 $ ls
170 bar.c foo.c
170 bar.c foo.c
171 $ hg status
171 $ hg status
172 ? bar.c
172 ? bar.c
173 ? foo.c
173 ? foo.c
174 $ hg add bar.c
174 $ hg add bar.c
175 $ hg status
175 $ hg status
176 A bar.c
176 A bar.c
177 ? foo.c
177 ? foo.c
178
178
179 Returns 0 if all files are successfully added.
179 Returns 0 if all files are successfully added.
180 """
180 """
181
181
182 m = scmutil.match(repo[None], pats, pycompat.byteskwargs(opts))
182 m = scmutil.match(repo[None], pats, pycompat.byteskwargs(opts))
183 rejected = cmdutil.add(ui, repo, m, "", False, **opts)
183 uipathfn = scmutil.getuipathfn(repo, forcerelativevalue=True)
184 rejected = cmdutil.add(ui, repo, m, "", uipathfn, False, **opts)
184 return rejected and 1 or 0
185 return rejected and 1 or 0
185
186
186 @command('addremove',
187 @command('addremove',
187 similarityopts + subrepoopts + walkopts + dryrunopts,
188 similarityopts + subrepoopts + walkopts + dryrunopts,
188 _('[OPTION]... [FILE]...'),
189 _('[OPTION]... [FILE]...'),
189 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
190 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
190 inferrepo=True)
191 inferrepo=True)
191 def addremove(ui, repo, *pats, **opts):
192 def addremove(ui, repo, *pats, **opts):
192 """add all new files, delete all missing files
193 """add all new files, delete all missing files
193
194
194 Add all new files and remove all missing files from the
195 Add all new files and remove all missing files from the
195 repository.
196 repository.
196
197
197 Unless names are given, new files are ignored if they match any of
198 Unless names are given, new files are ignored if they match any of
198 the patterns in ``.hgignore``. As with add, these changes take
199 the patterns in ``.hgignore``. As with add, these changes take
199 effect at the next commit.
200 effect at the next commit.
200
201
201 Use the -s/--similarity option to detect renamed files. This
202 Use the -s/--similarity option to detect renamed files. This
202 option takes a percentage between 0 (disabled) and 100 (files must
203 option takes a percentage between 0 (disabled) and 100 (files must
203 be identical) as its parameter. With a parameter greater than 0,
204 be identical) as its parameter. With a parameter greater than 0,
204 this compares every removed file with every added file and records
205 this compares every removed file with every added file and records
205 those similar enough as renames. Detecting renamed files this way
206 those similar enough as renames. Detecting renamed files this way
206 can be expensive. After using this option, :hg:`status -C` can be
207 can be expensive. After using this option, :hg:`status -C` can be
207 used to check which files were identified as moved or renamed. If
208 used to check which files were identified as moved or renamed. If
208 not specified, -s/--similarity defaults to 100 and only renames of
209 not specified, -s/--similarity defaults to 100 and only renames of
209 identical files are detected.
210 identical files are detected.
210
211
211 .. container:: verbose
212 .. container:: verbose
212
213
213 Examples:
214 Examples:
214
215
215 - A number of files (bar.c and foo.c) are new,
216 - A number of files (bar.c and foo.c) are new,
216 while foobar.c has been removed (without using :hg:`remove`)
217 while foobar.c has been removed (without using :hg:`remove`)
217 from the repository::
218 from the repository::
218
219
219 $ ls
220 $ ls
220 bar.c foo.c
221 bar.c foo.c
221 $ hg status
222 $ hg status
222 ! foobar.c
223 ! foobar.c
223 ? bar.c
224 ? bar.c
224 ? foo.c
225 ? foo.c
225 $ hg addremove
226 $ hg addremove
226 adding bar.c
227 adding bar.c
227 adding foo.c
228 adding foo.c
228 removing foobar.c
229 removing foobar.c
229 $ hg status
230 $ hg status
230 A bar.c
231 A bar.c
231 A foo.c
232 A foo.c
232 R foobar.c
233 R foobar.c
233
234
234 - A file foobar.c was moved to foo.c without using :hg:`rename`.
235 - A file foobar.c was moved to foo.c without using :hg:`rename`.
235 Afterwards, it was edited slightly::
236 Afterwards, it was edited slightly::
236
237
237 $ ls
238 $ ls
238 foo.c
239 foo.c
239 $ hg status
240 $ hg status
240 ! foobar.c
241 ! foobar.c
241 ? foo.c
242 ? foo.c
242 $ hg addremove --similarity 90
243 $ hg addremove --similarity 90
243 removing foobar.c
244 removing foobar.c
244 adding foo.c
245 adding foo.c
245 recording removal of foobar.c as rename to foo.c (94% similar)
246 recording removal of foobar.c as rename to foo.c (94% similar)
246 $ hg status -C
247 $ hg status -C
247 A foo.c
248 A foo.c
248 foobar.c
249 foobar.c
249 R foobar.c
250 R foobar.c
250
251
251 Returns 0 if all files are successfully added.
252 Returns 0 if all files are successfully added.
252 """
253 """
253 opts = pycompat.byteskwargs(opts)
254 opts = pycompat.byteskwargs(opts)
254 if not opts.get('similarity'):
255 if not opts.get('similarity'):
255 opts['similarity'] = '100'
256 opts['similarity'] = '100'
256 matcher = scmutil.match(repo[None], pats, opts)
257 matcher = scmutil.match(repo[None], pats, opts)
257 return scmutil.addremove(repo, matcher, "", opts)
258 return scmutil.addremove(repo, matcher, "", opts)
258
259
259 @command('annotate|blame',
260 @command('annotate|blame',
260 [('r', 'rev', '', _('annotate the specified revision'), _('REV')),
261 [('r', 'rev', '', _('annotate the specified revision'), _('REV')),
261 ('', 'follow', None,
262 ('', 'follow', None,
262 _('follow copies/renames and list the filename (DEPRECATED)')),
263 _('follow copies/renames and list the filename (DEPRECATED)')),
263 ('', 'no-follow', None, _("don't follow copies and renames")),
264 ('', 'no-follow', None, _("don't follow copies and renames")),
264 ('a', 'text', None, _('treat all files as text')),
265 ('a', 'text', None, _('treat all files as text')),
265 ('u', 'user', None, _('list the author (long with -v)')),
266 ('u', 'user', None, _('list the author (long with -v)')),
266 ('f', 'file', None, _('list the filename')),
267 ('f', 'file', None, _('list the filename')),
267 ('d', 'date', None, _('list the date (short with -q)')),
268 ('d', 'date', None, _('list the date (short with -q)')),
268 ('n', 'number', None, _('list the revision number (default)')),
269 ('n', 'number', None, _('list the revision number (default)')),
269 ('c', 'changeset', None, _('list the changeset')),
270 ('c', 'changeset', None, _('list the changeset')),
270 ('l', 'line-number', None, _('show line number at the first appearance')),
271 ('l', 'line-number', None, _('show line number at the first appearance')),
271 ('', 'skip', [], _('revision to not display (EXPERIMENTAL)'), _('REV')),
272 ('', 'skip', [], _('revision to not display (EXPERIMENTAL)'), _('REV')),
272 ] + diffwsopts + walkopts + formatteropts,
273 ] + diffwsopts + walkopts + formatteropts,
273 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'),
274 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'),
274 helpcategory=command.CATEGORY_FILE_CONTENTS,
275 helpcategory=command.CATEGORY_FILE_CONTENTS,
275 helpbasic=True, inferrepo=True)
276 helpbasic=True, inferrepo=True)
276 def annotate(ui, repo, *pats, **opts):
277 def annotate(ui, repo, *pats, **opts):
277 """show changeset information by line for each file
278 """show changeset information by line for each file
278
279
279 List changes in files, showing the revision id responsible for
280 List changes in files, showing the revision id responsible for
280 each line.
281 each line.
281
282
282 This command is useful for discovering when a change was made and
283 This command is useful for discovering when a change was made and
283 by whom.
284 by whom.
284
285
285 If you include --file, --user, or --date, the revision number is
286 If you include --file, --user, or --date, the revision number is
286 suppressed unless you also include --number.
287 suppressed unless you also include --number.
287
288
288 Without the -a/--text option, annotate will avoid processing files
289 Without the -a/--text option, annotate will avoid processing files
289 it detects as binary. With -a, annotate will annotate the file
290 it detects as binary. With -a, annotate will annotate the file
290 anyway, although the results will probably be neither useful
291 anyway, although the results will probably be neither useful
291 nor desirable.
292 nor desirable.
292
293
293 .. container:: verbose
294 .. container:: verbose
294
295
295 Template:
296 Template:
296
297
297 The following keywords are supported in addition to the common template
298 The following keywords are supported in addition to the common template
298 keywords and functions. See also :hg:`help templates`.
299 keywords and functions. See also :hg:`help templates`.
299
300
300 :lines: List of lines with annotation data.
301 :lines: List of lines with annotation data.
301 :path: String. Repository-absolute path of the specified file.
302 :path: String. Repository-absolute path of the specified file.
302
303
303 And each entry of ``{lines}`` provides the following sub-keywords in
304 And each entry of ``{lines}`` provides the following sub-keywords in
304 addition to ``{date}``, ``{node}``, ``{rev}``, ``{user}``, etc.
305 addition to ``{date}``, ``{node}``, ``{rev}``, ``{user}``, etc.
305
306
306 :line: String. Line content.
307 :line: String. Line content.
307 :lineno: Integer. Line number at that revision.
308 :lineno: Integer. Line number at that revision.
308 :path: String. Repository-absolute path of the file at that revision.
309 :path: String. Repository-absolute path of the file at that revision.
309
310
310 See :hg:`help templates.operators` for the list expansion syntax.
311 See :hg:`help templates.operators` for the list expansion syntax.
311
312
312 Returns 0 on success.
313 Returns 0 on success.
313 """
314 """
314 opts = pycompat.byteskwargs(opts)
315 opts = pycompat.byteskwargs(opts)
315 if not pats:
316 if not pats:
316 raise error.Abort(_('at least one filename or pattern is required'))
317 raise error.Abort(_('at least one filename or pattern is required'))
317
318
318 if opts.get('follow'):
319 if opts.get('follow'):
319 # --follow is deprecated and now just an alias for -f/--file
320 # --follow is deprecated and now just an alias for -f/--file
320 # to mimic the behavior of Mercurial before version 1.5
321 # to mimic the behavior of Mercurial before version 1.5
321 opts['file'] = True
322 opts['file'] = True
322
323
323 if (not opts.get('user') and not opts.get('changeset')
324 if (not opts.get('user') and not opts.get('changeset')
324 and not opts.get('date') and not opts.get('file')):
325 and not opts.get('date') and not opts.get('file')):
325 opts['number'] = True
326 opts['number'] = True
326
327
327 linenumber = opts.get('line_number') is not None
328 linenumber = opts.get('line_number') is not None
328 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
329 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
329 raise error.Abort(_('at least one of -n/-c is required for -l'))
330 raise error.Abort(_('at least one of -n/-c is required for -l'))
330
331
331 rev = opts.get('rev')
332 rev = opts.get('rev')
332 if rev:
333 if rev:
333 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
334 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
334 ctx = scmutil.revsingle(repo, rev)
335 ctx = scmutil.revsingle(repo, rev)
335
336
336 ui.pager('annotate')
337 ui.pager('annotate')
337 rootfm = ui.formatter('annotate', opts)
338 rootfm = ui.formatter('annotate', opts)
338 if ui.debugflag:
339 if ui.debugflag:
339 shorthex = pycompat.identity
340 shorthex = pycompat.identity
340 else:
341 else:
341 def shorthex(h):
342 def shorthex(h):
342 return h[:12]
343 return h[:12]
343 if ui.quiet:
344 if ui.quiet:
344 datefunc = dateutil.shortdate
345 datefunc = dateutil.shortdate
345 else:
346 else:
346 datefunc = dateutil.datestr
347 datefunc = dateutil.datestr
347 if ctx.rev() is None:
348 if ctx.rev() is None:
348 if opts.get('changeset'):
349 if opts.get('changeset'):
349 # omit "+" suffix which is appended to node hex
350 # omit "+" suffix which is appended to node hex
350 def formatrev(rev):
351 def formatrev(rev):
351 if rev == wdirrev:
352 if rev == wdirrev:
352 return '%d' % ctx.p1().rev()
353 return '%d' % ctx.p1().rev()
353 else:
354 else:
354 return '%d' % rev
355 return '%d' % rev
355 else:
356 else:
356 def formatrev(rev):
357 def formatrev(rev):
357 if rev == wdirrev:
358 if rev == wdirrev:
358 return '%d+' % ctx.p1().rev()
359 return '%d+' % ctx.p1().rev()
359 else:
360 else:
360 return '%d ' % rev
361 return '%d ' % rev
361 def formathex(h):
362 def formathex(h):
362 if h == wdirhex:
363 if h == wdirhex:
363 return '%s+' % shorthex(hex(ctx.p1().node()))
364 return '%s+' % shorthex(hex(ctx.p1().node()))
364 else:
365 else:
365 return '%s ' % shorthex(h)
366 return '%s ' % shorthex(h)
366 else:
367 else:
367 formatrev = b'%d'.__mod__
368 formatrev = b'%d'.__mod__
368 formathex = shorthex
369 formathex = shorthex
369
370
370 opmap = [
371 opmap = [
371 ('user', ' ', lambda x: x.fctx.user(), ui.shortuser),
372 ('user', ' ', lambda x: x.fctx.user(), ui.shortuser),
372 ('rev', ' ', lambda x: scmutil.intrev(x.fctx), formatrev),
373 ('rev', ' ', lambda x: scmutil.intrev(x.fctx), formatrev),
373 ('node', ' ', lambda x: hex(scmutil.binnode(x.fctx)), formathex),
374 ('node', ' ', lambda x: hex(scmutil.binnode(x.fctx)), formathex),
374 ('date', ' ', lambda x: x.fctx.date(), util.cachefunc(datefunc)),
375 ('date', ' ', lambda x: x.fctx.date(), util.cachefunc(datefunc)),
375 ('path', ' ', lambda x: x.fctx.path(), pycompat.bytestr),
376 ('path', ' ', lambda x: x.fctx.path(), pycompat.bytestr),
376 ('lineno', ':', lambda x: x.lineno, pycompat.bytestr),
377 ('lineno', ':', lambda x: x.lineno, pycompat.bytestr),
377 ]
378 ]
378 opnamemap = {
379 opnamemap = {
379 'rev': 'number',
380 'rev': 'number',
380 'node': 'changeset',
381 'node': 'changeset',
381 'path': 'file',
382 'path': 'file',
382 'lineno': 'line_number',
383 'lineno': 'line_number',
383 }
384 }
384
385
385 if rootfm.isplain():
386 if rootfm.isplain():
386 def makefunc(get, fmt):
387 def makefunc(get, fmt):
387 return lambda x: fmt(get(x))
388 return lambda x: fmt(get(x))
388 else:
389 else:
389 def makefunc(get, fmt):
390 def makefunc(get, fmt):
390 return get
391 return get
391 datahint = rootfm.datahint()
392 datahint = rootfm.datahint()
392 funcmap = [(makefunc(get, fmt), sep) for fn, sep, get, fmt in opmap
393 funcmap = [(makefunc(get, fmt), sep) for fn, sep, get, fmt in opmap
393 if opts.get(opnamemap.get(fn, fn)) or fn in datahint]
394 if opts.get(opnamemap.get(fn, fn)) or fn in datahint]
394 funcmap[0] = (funcmap[0][0], '') # no separator in front of first column
395 funcmap[0] = (funcmap[0][0], '') # no separator in front of first column
395 fields = ' '.join(fn for fn, sep, get, fmt in opmap
396 fields = ' '.join(fn for fn, sep, get, fmt in opmap
396 if opts.get(opnamemap.get(fn, fn)) or fn in datahint)
397 if opts.get(opnamemap.get(fn, fn)) or fn in datahint)
397
398
398 def bad(x, y):
399 def bad(x, y):
399 raise error.Abort("%s: %s" % (x, y))
400 raise error.Abort("%s: %s" % (x, y))
400
401
401 m = scmutil.match(ctx, pats, opts, badfn=bad)
402 m = scmutil.match(ctx, pats, opts, badfn=bad)
402
403
403 follow = not opts.get('no_follow')
404 follow = not opts.get('no_follow')
404 diffopts = patch.difffeatureopts(ui, opts, section='annotate',
405 diffopts = patch.difffeatureopts(ui, opts, section='annotate',
405 whitespace=True)
406 whitespace=True)
406 skiprevs = opts.get('skip')
407 skiprevs = opts.get('skip')
407 if skiprevs:
408 if skiprevs:
408 skiprevs = scmutil.revrange(repo, skiprevs)
409 skiprevs = scmutil.revrange(repo, skiprevs)
409
410
410 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
411 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
411 for abs in ctx.walk(m):
412 for abs in ctx.walk(m):
412 fctx = ctx[abs]
413 fctx = ctx[abs]
413 rootfm.startitem()
414 rootfm.startitem()
414 rootfm.data(path=abs)
415 rootfm.data(path=abs)
415 if not opts.get('text') and fctx.isbinary():
416 if not opts.get('text') and fctx.isbinary():
416 rootfm.plain(_("%s: binary file\n") % uipathfn(abs))
417 rootfm.plain(_("%s: binary file\n") % uipathfn(abs))
417 continue
418 continue
418
419
419 fm = rootfm.nested('lines', tmpl='{rev}: {line}')
420 fm = rootfm.nested('lines', tmpl='{rev}: {line}')
420 lines = fctx.annotate(follow=follow, skiprevs=skiprevs,
421 lines = fctx.annotate(follow=follow, skiprevs=skiprevs,
421 diffopts=diffopts)
422 diffopts=diffopts)
422 if not lines:
423 if not lines:
423 fm.end()
424 fm.end()
424 continue
425 continue
425 formats = []
426 formats = []
426 pieces = []
427 pieces = []
427
428
428 for f, sep in funcmap:
429 for f, sep in funcmap:
429 l = [f(n) for n in lines]
430 l = [f(n) for n in lines]
430 if fm.isplain():
431 if fm.isplain():
431 sizes = [encoding.colwidth(x) for x in l]
432 sizes = [encoding.colwidth(x) for x in l]
432 ml = max(sizes)
433 ml = max(sizes)
433 formats.append([sep + ' ' * (ml - w) + '%s' for w in sizes])
434 formats.append([sep + ' ' * (ml - w) + '%s' for w in sizes])
434 else:
435 else:
435 formats.append(['%s' for x in l])
436 formats.append(['%s' for x in l])
436 pieces.append(l)
437 pieces.append(l)
437
438
438 for f, p, n in zip(zip(*formats), zip(*pieces), lines):
439 for f, p, n in zip(zip(*formats), zip(*pieces), lines):
439 fm.startitem()
440 fm.startitem()
440 fm.context(fctx=n.fctx)
441 fm.context(fctx=n.fctx)
441 fm.write(fields, "".join(f), *p)
442 fm.write(fields, "".join(f), *p)
442 if n.skip:
443 if n.skip:
443 fmt = "* %s"
444 fmt = "* %s"
444 else:
445 else:
445 fmt = ": %s"
446 fmt = ": %s"
446 fm.write('line', fmt, n.text)
447 fm.write('line', fmt, n.text)
447
448
448 if not lines[-1].text.endswith('\n'):
449 if not lines[-1].text.endswith('\n'):
449 fm.plain('\n')
450 fm.plain('\n')
450 fm.end()
451 fm.end()
451
452
452 rootfm.end()
453 rootfm.end()
453
454
454 @command('archive',
455 @command('archive',
455 [('', 'no-decode', None, _('do not pass files through decoders')),
456 [('', 'no-decode', None, _('do not pass files through decoders')),
456 ('p', 'prefix', '', _('directory prefix for files in archive'),
457 ('p', 'prefix', '', _('directory prefix for files in archive'),
457 _('PREFIX')),
458 _('PREFIX')),
458 ('r', 'rev', '', _('revision to distribute'), _('REV')),
459 ('r', 'rev', '', _('revision to distribute'), _('REV')),
459 ('t', 'type', '', _('type of distribution to create'), _('TYPE')),
460 ('t', 'type', '', _('type of distribution to create'), _('TYPE')),
460 ] + subrepoopts + walkopts,
461 ] + subrepoopts + walkopts,
461 _('[OPTION]... DEST'),
462 _('[OPTION]... DEST'),
462 helpcategory=command.CATEGORY_IMPORT_EXPORT)
463 helpcategory=command.CATEGORY_IMPORT_EXPORT)
463 def archive(ui, repo, dest, **opts):
464 def archive(ui, repo, dest, **opts):
464 '''create an unversioned archive of a repository revision
465 '''create an unversioned archive of a repository revision
465
466
466 By default, the revision used is the parent of the working
467 By default, the revision used is the parent of the working
467 directory; use -r/--rev to specify a different revision.
468 directory; use -r/--rev to specify a different revision.
468
469
469 The archive type is automatically detected based on file
470 The archive type is automatically detected based on file
470 extension (to override, use -t/--type).
471 extension (to override, use -t/--type).
471
472
472 .. container:: verbose
473 .. container:: verbose
473
474
474 Examples:
475 Examples:
475
476
476 - create a zip file containing the 1.0 release::
477 - create a zip file containing the 1.0 release::
477
478
478 hg archive -r 1.0 project-1.0.zip
479 hg archive -r 1.0 project-1.0.zip
479
480
480 - create a tarball excluding .hg files::
481 - create a tarball excluding .hg files::
481
482
482 hg archive project.tar.gz -X ".hg*"
483 hg archive project.tar.gz -X ".hg*"
483
484
484 Valid types are:
485 Valid types are:
485
486
486 :``files``: a directory full of files (default)
487 :``files``: a directory full of files (default)
487 :``tar``: tar archive, uncompressed
488 :``tar``: tar archive, uncompressed
488 :``tbz2``: tar archive, compressed using bzip2
489 :``tbz2``: tar archive, compressed using bzip2
489 :``tgz``: tar archive, compressed using gzip
490 :``tgz``: tar archive, compressed using gzip
490 :``uzip``: zip archive, uncompressed
491 :``uzip``: zip archive, uncompressed
491 :``zip``: zip archive, compressed using deflate
492 :``zip``: zip archive, compressed using deflate
492
493
493 The exact name of the destination archive or directory is given
494 The exact name of the destination archive or directory is given
494 using a format string; see :hg:`help export` for details.
495 using a format string; see :hg:`help export` for details.
495
496
496 Each member added to an archive file has a directory prefix
497 Each member added to an archive file has a directory prefix
497 prepended. Use -p/--prefix to specify a format string for the
498 prepended. Use -p/--prefix to specify a format string for the
498 prefix. The default is the basename of the archive, with suffixes
499 prefix. The default is the basename of the archive, with suffixes
499 removed.
500 removed.
500
501
501 Returns 0 on success.
502 Returns 0 on success.
502 '''
503 '''
503
504
504 opts = pycompat.byteskwargs(opts)
505 opts = pycompat.byteskwargs(opts)
505 rev = opts.get('rev')
506 rev = opts.get('rev')
506 if rev:
507 if rev:
507 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
508 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
508 ctx = scmutil.revsingle(repo, rev)
509 ctx = scmutil.revsingle(repo, rev)
509 if not ctx:
510 if not ctx:
510 raise error.Abort(_('no working directory: please specify a revision'))
511 raise error.Abort(_('no working directory: please specify a revision'))
511 node = ctx.node()
512 node = ctx.node()
512 dest = cmdutil.makefilename(ctx, dest)
513 dest = cmdutil.makefilename(ctx, dest)
513 if os.path.realpath(dest) == repo.root:
514 if os.path.realpath(dest) == repo.root:
514 raise error.Abort(_('repository root cannot be destination'))
515 raise error.Abort(_('repository root cannot be destination'))
515
516
516 kind = opts.get('type') or archival.guesskind(dest) or 'files'
517 kind = opts.get('type') or archival.guesskind(dest) or 'files'
517 prefix = opts.get('prefix')
518 prefix = opts.get('prefix')
518
519
519 if dest == '-':
520 if dest == '-':
520 if kind == 'files':
521 if kind == 'files':
521 raise error.Abort(_('cannot archive plain files to stdout'))
522 raise error.Abort(_('cannot archive plain files to stdout'))
522 dest = cmdutil.makefileobj(ctx, dest)
523 dest = cmdutil.makefileobj(ctx, dest)
523 if not prefix:
524 if not prefix:
524 prefix = os.path.basename(repo.root) + '-%h'
525 prefix = os.path.basename(repo.root) + '-%h'
525
526
526 prefix = cmdutil.makefilename(ctx, prefix)
527 prefix = cmdutil.makefilename(ctx, prefix)
527 match = scmutil.match(ctx, [], opts)
528 match = scmutil.match(ctx, [], opts)
528 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
529 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
529 match, prefix, subrepos=opts.get('subrepos'))
530 match, prefix, subrepos=opts.get('subrepos'))
530
531
531 @command('backout',
532 @command('backout',
532 [('', 'merge', None, _('merge with old dirstate parent after backout')),
533 [('', 'merge', None, _('merge with old dirstate parent after backout')),
533 ('', 'commit', None,
534 ('', 'commit', None,
534 _('commit if no conflicts were encountered (DEPRECATED)')),
535 _('commit if no conflicts were encountered (DEPRECATED)')),
535 ('', 'no-commit', None, _('do not commit')),
536 ('', 'no-commit', None, _('do not commit')),
536 ('', 'parent', '',
537 ('', 'parent', '',
537 _('parent to choose when backing out merge (DEPRECATED)'), _('REV')),
538 _('parent to choose when backing out merge (DEPRECATED)'), _('REV')),
538 ('r', 'rev', '', _('revision to backout'), _('REV')),
539 ('r', 'rev', '', _('revision to backout'), _('REV')),
539 ('e', 'edit', False, _('invoke editor on commit messages')),
540 ('e', 'edit', False, _('invoke editor on commit messages')),
540 ] + mergetoolopts + walkopts + commitopts + commitopts2,
541 ] + mergetoolopts + walkopts + commitopts + commitopts2,
541 _('[OPTION]... [-r] REV'),
542 _('[OPTION]... [-r] REV'),
542 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT)
543 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT)
543 def backout(ui, repo, node=None, rev=None, **opts):
544 def backout(ui, repo, node=None, rev=None, **opts):
544 '''reverse effect of earlier changeset
545 '''reverse effect of earlier changeset
545
546
546 Prepare a new changeset with the effect of REV undone in the
547 Prepare a new changeset with the effect of REV undone in the
547 current working directory. If no conflicts were encountered,
548 current working directory. If no conflicts were encountered,
548 it will be committed immediately.
549 it will be committed immediately.
549
550
550 If REV is the parent of the working directory, then this new changeset
551 If REV is the parent of the working directory, then this new changeset
551 is committed automatically (unless --no-commit is specified).
552 is committed automatically (unless --no-commit is specified).
552
553
553 .. note::
554 .. note::
554
555
555 :hg:`backout` cannot be used to fix either an unwanted or
556 :hg:`backout` cannot be used to fix either an unwanted or
556 incorrect merge.
557 incorrect merge.
557
558
558 .. container:: verbose
559 .. container:: verbose
559
560
560 Examples:
561 Examples:
561
562
562 - Reverse the effect of the parent of the working directory.
563 - Reverse the effect of the parent of the working directory.
563 This backout will be committed immediately::
564 This backout will be committed immediately::
564
565
565 hg backout -r .
566 hg backout -r .
566
567
567 - Reverse the effect of previous bad revision 23::
568 - Reverse the effect of previous bad revision 23::
568
569
569 hg backout -r 23
570 hg backout -r 23
570
571
571 - Reverse the effect of previous bad revision 23 and
572 - Reverse the effect of previous bad revision 23 and
572 leave changes uncommitted::
573 leave changes uncommitted::
573
574
574 hg backout -r 23 --no-commit
575 hg backout -r 23 --no-commit
575 hg commit -m "Backout revision 23"
576 hg commit -m "Backout revision 23"
576
577
577 By default, the pending changeset will have one parent,
578 By default, the pending changeset will have one parent,
578 maintaining a linear history. With --merge, the pending
579 maintaining a linear history. With --merge, the pending
579 changeset will instead have two parents: the old parent of the
580 changeset will instead have two parents: the old parent of the
580 working directory and a new child of REV that simply undoes REV.
581 working directory and a new child of REV that simply undoes REV.
581
582
582 Before version 1.7, the behavior without --merge was equivalent
583 Before version 1.7, the behavior without --merge was equivalent
583 to specifying --merge followed by :hg:`update --clean .` to
584 to specifying --merge followed by :hg:`update --clean .` to
584 cancel the merge and leave the child of REV as a head to be
585 cancel the merge and leave the child of REV as a head to be
585 merged separately.
586 merged separately.
586
587
587 See :hg:`help dates` for a list of formats valid for -d/--date.
588 See :hg:`help dates` for a list of formats valid for -d/--date.
588
589
589 See :hg:`help revert` for a way to restore files to the state
590 See :hg:`help revert` for a way to restore files to the state
590 of another revision.
591 of another revision.
591
592
592 Returns 0 on success, 1 if nothing to backout or there are unresolved
593 Returns 0 on success, 1 if nothing to backout or there are unresolved
593 files.
594 files.
594 '''
595 '''
595 with repo.wlock(), repo.lock():
596 with repo.wlock(), repo.lock():
596 return _dobackout(ui, repo, node, rev, **opts)
597 return _dobackout(ui, repo, node, rev, **opts)
597
598
598 def _dobackout(ui, repo, node=None, rev=None, **opts):
599 def _dobackout(ui, repo, node=None, rev=None, **opts):
599 opts = pycompat.byteskwargs(opts)
600 opts = pycompat.byteskwargs(opts)
600 if opts.get('commit') and opts.get('no_commit'):
601 if opts.get('commit') and opts.get('no_commit'):
601 raise error.Abort(_("cannot use --commit with --no-commit"))
602 raise error.Abort(_("cannot use --commit with --no-commit"))
602 if opts.get('merge') and opts.get('no_commit'):
603 if opts.get('merge') and opts.get('no_commit'):
603 raise error.Abort(_("cannot use --merge with --no-commit"))
604 raise error.Abort(_("cannot use --merge with --no-commit"))
604
605
605 if rev and node:
606 if rev and node:
606 raise error.Abort(_("please specify just one revision"))
607 raise error.Abort(_("please specify just one revision"))
607
608
608 if not rev:
609 if not rev:
609 rev = node
610 rev = node
610
611
611 if not rev:
612 if not rev:
612 raise error.Abort(_("please specify a revision to backout"))
613 raise error.Abort(_("please specify a revision to backout"))
613
614
614 date = opts.get('date')
615 date = opts.get('date')
615 if date:
616 if date:
616 opts['date'] = dateutil.parsedate(date)
617 opts['date'] = dateutil.parsedate(date)
617
618
618 cmdutil.checkunfinished(repo)
619 cmdutil.checkunfinished(repo)
619 cmdutil.bailifchanged(repo)
620 cmdutil.bailifchanged(repo)
620 node = scmutil.revsingle(repo, rev).node()
621 node = scmutil.revsingle(repo, rev).node()
621
622
622 op1, op2 = repo.dirstate.parents()
623 op1, op2 = repo.dirstate.parents()
623 if not repo.changelog.isancestor(node, op1):
624 if not repo.changelog.isancestor(node, op1):
624 raise error.Abort(_('cannot backout change that is not an ancestor'))
625 raise error.Abort(_('cannot backout change that is not an ancestor'))
625
626
626 p1, p2 = repo.changelog.parents(node)
627 p1, p2 = repo.changelog.parents(node)
627 if p1 == nullid:
628 if p1 == nullid:
628 raise error.Abort(_('cannot backout a change with no parents'))
629 raise error.Abort(_('cannot backout a change with no parents'))
629 if p2 != nullid:
630 if p2 != nullid:
630 if not opts.get('parent'):
631 if not opts.get('parent'):
631 raise error.Abort(_('cannot backout a merge changeset'))
632 raise error.Abort(_('cannot backout a merge changeset'))
632 p = repo.lookup(opts['parent'])
633 p = repo.lookup(opts['parent'])
633 if p not in (p1, p2):
634 if p not in (p1, p2):
634 raise error.Abort(_('%s is not a parent of %s') %
635 raise error.Abort(_('%s is not a parent of %s') %
635 (short(p), short(node)))
636 (short(p), short(node)))
636 parent = p
637 parent = p
637 else:
638 else:
638 if opts.get('parent'):
639 if opts.get('parent'):
639 raise error.Abort(_('cannot use --parent on non-merge changeset'))
640 raise error.Abort(_('cannot use --parent on non-merge changeset'))
640 parent = p1
641 parent = p1
641
642
642 # the backout should appear on the same branch
643 # the backout should appear on the same branch
643 branch = repo.dirstate.branch()
644 branch = repo.dirstate.branch()
644 bheads = repo.branchheads(branch)
645 bheads = repo.branchheads(branch)
645 rctx = scmutil.revsingle(repo, hex(parent))
646 rctx = scmutil.revsingle(repo, hex(parent))
646 if not opts.get('merge') and op1 != node:
647 if not opts.get('merge') and op1 != node:
647 with dirstateguard.dirstateguard(repo, 'backout'):
648 with dirstateguard.dirstateguard(repo, 'backout'):
648 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
649 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
649 with ui.configoverride(overrides, 'backout'):
650 with ui.configoverride(overrides, 'backout'):
650 stats = mergemod.update(repo, parent, branchmerge=True,
651 stats = mergemod.update(repo, parent, branchmerge=True,
651 force=True, ancestor=node,
652 force=True, ancestor=node,
652 mergeancestor=False)
653 mergeancestor=False)
653 repo.setparents(op1, op2)
654 repo.setparents(op1, op2)
654 hg._showstats(repo, stats)
655 hg._showstats(repo, stats)
655 if stats.unresolvedcount:
656 if stats.unresolvedcount:
656 repo.ui.status(_("use 'hg resolve' to retry unresolved "
657 repo.ui.status(_("use 'hg resolve' to retry unresolved "
657 "file merges\n"))
658 "file merges\n"))
658 return 1
659 return 1
659 else:
660 else:
660 hg.clean(repo, node, show_stats=False)
661 hg.clean(repo, node, show_stats=False)
661 repo.dirstate.setbranch(branch)
662 repo.dirstate.setbranch(branch)
662 cmdutil.revert(ui, repo, rctx, repo.dirstate.parents())
663 cmdutil.revert(ui, repo, rctx, repo.dirstate.parents())
663
664
664 if opts.get('no_commit'):
665 if opts.get('no_commit'):
665 msg = _("changeset %s backed out, "
666 msg = _("changeset %s backed out, "
666 "don't forget to commit.\n")
667 "don't forget to commit.\n")
667 ui.status(msg % short(node))
668 ui.status(msg % short(node))
668 return 0
669 return 0
669
670
670 def commitfunc(ui, repo, message, match, opts):
671 def commitfunc(ui, repo, message, match, opts):
671 editform = 'backout'
672 editform = 'backout'
672 e = cmdutil.getcommiteditor(editform=editform,
673 e = cmdutil.getcommiteditor(editform=editform,
673 **pycompat.strkwargs(opts))
674 **pycompat.strkwargs(opts))
674 if not message:
675 if not message:
675 # we don't translate commit messages
676 # we don't translate commit messages
676 message = "Backed out changeset %s" % short(node)
677 message = "Backed out changeset %s" % short(node)
677 e = cmdutil.getcommiteditor(edit=True, editform=editform)
678 e = cmdutil.getcommiteditor(edit=True, editform=editform)
678 return repo.commit(message, opts.get('user'), opts.get('date'),
679 return repo.commit(message, opts.get('user'), opts.get('date'),
679 match, editor=e)
680 match, editor=e)
680 newnode = cmdutil.commit(ui, repo, commitfunc, [], opts)
681 newnode = cmdutil.commit(ui, repo, commitfunc, [], opts)
681 if not newnode:
682 if not newnode:
682 ui.status(_("nothing changed\n"))
683 ui.status(_("nothing changed\n"))
683 return 1
684 return 1
684 cmdutil.commitstatus(repo, newnode, branch, bheads)
685 cmdutil.commitstatus(repo, newnode, branch, bheads)
685
686
686 def nice(node):
687 def nice(node):
687 return '%d:%s' % (repo.changelog.rev(node), short(node))
688 return '%d:%s' % (repo.changelog.rev(node), short(node))
688 ui.status(_('changeset %s backs out changeset %s\n') %
689 ui.status(_('changeset %s backs out changeset %s\n') %
689 (nice(repo.changelog.tip()), nice(node)))
690 (nice(repo.changelog.tip()), nice(node)))
690 if opts.get('merge') and op1 != node:
691 if opts.get('merge') and op1 != node:
691 hg.clean(repo, op1, show_stats=False)
692 hg.clean(repo, op1, show_stats=False)
692 ui.status(_('merging with changeset %s\n')
693 ui.status(_('merging with changeset %s\n')
693 % nice(repo.changelog.tip()))
694 % nice(repo.changelog.tip()))
694 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
695 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
695 with ui.configoverride(overrides, 'backout'):
696 with ui.configoverride(overrides, 'backout'):
696 return hg.merge(repo, hex(repo.changelog.tip()))
697 return hg.merge(repo, hex(repo.changelog.tip()))
697 return 0
698 return 0
698
699
699 @command('bisect',
700 @command('bisect',
700 [('r', 'reset', False, _('reset bisect state')),
701 [('r', 'reset', False, _('reset bisect state')),
701 ('g', 'good', False, _('mark changeset good')),
702 ('g', 'good', False, _('mark changeset good')),
702 ('b', 'bad', False, _('mark changeset bad')),
703 ('b', 'bad', False, _('mark changeset bad')),
703 ('s', 'skip', False, _('skip testing changeset')),
704 ('s', 'skip', False, _('skip testing changeset')),
704 ('e', 'extend', False, _('extend the bisect range')),
705 ('e', 'extend', False, _('extend the bisect range')),
705 ('c', 'command', '', _('use command to check changeset state'), _('CMD')),
706 ('c', 'command', '', _('use command to check changeset state'), _('CMD')),
706 ('U', 'noupdate', False, _('do not update to target'))],
707 ('U', 'noupdate', False, _('do not update to target'))],
707 _("[-gbsr] [-U] [-c CMD] [REV]"),
708 _("[-gbsr] [-U] [-c CMD] [REV]"),
708 helpcategory=command.CATEGORY_CHANGE_NAVIGATION)
709 helpcategory=command.CATEGORY_CHANGE_NAVIGATION)
709 def bisect(ui, repo, rev=None, extra=None, command=None,
710 def bisect(ui, repo, rev=None, extra=None, command=None,
710 reset=None, good=None, bad=None, skip=None, extend=None,
711 reset=None, good=None, bad=None, skip=None, extend=None,
711 noupdate=None):
712 noupdate=None):
712 """subdivision search of changesets
713 """subdivision search of changesets
713
714
714 This command helps to find changesets which introduce problems. To
715 This command helps to find changesets which introduce problems. To
715 use, mark the earliest changeset you know exhibits the problem as
716 use, mark the earliest changeset you know exhibits the problem as
716 bad, then mark the latest changeset which is free from the problem
717 bad, then mark the latest changeset which is free from the problem
717 as good. Bisect will update your working directory to a revision
718 as good. Bisect will update your working directory to a revision
718 for testing (unless the -U/--noupdate option is specified). Once
719 for testing (unless the -U/--noupdate option is specified). Once
719 you have performed tests, mark the working directory as good or
720 you have performed tests, mark the working directory as good or
720 bad, and bisect will either update to another candidate changeset
721 bad, and bisect will either update to another candidate changeset
721 or announce that it has found the bad revision.
722 or announce that it has found the bad revision.
722
723
723 As a shortcut, you can also use the revision argument to mark a
724 As a shortcut, you can also use the revision argument to mark a
724 revision as good or bad without checking it out first.
725 revision as good or bad without checking it out first.
725
726
726 If you supply a command, it will be used for automatic bisection.
727 If you supply a command, it will be used for automatic bisection.
727 The environment variable HG_NODE will contain the ID of the
728 The environment variable HG_NODE will contain the ID of the
728 changeset being tested. The exit status of the command will be
729 changeset being tested. The exit status of the command will be
729 used to mark revisions as good or bad: status 0 means good, 125
730 used to mark revisions as good or bad: status 0 means good, 125
730 means to skip the revision, 127 (command not found) will abort the
731 means to skip the revision, 127 (command not found) will abort the
731 bisection, and any other non-zero exit status means the revision
732 bisection, and any other non-zero exit status means the revision
732 is bad.
733 is bad.
733
734
734 .. container:: verbose
735 .. container:: verbose
735
736
736 Some examples:
737 Some examples:
737
738
738 - start a bisection with known bad revision 34, and good revision 12::
739 - start a bisection with known bad revision 34, and good revision 12::
739
740
740 hg bisect --bad 34
741 hg bisect --bad 34
741 hg bisect --good 12
742 hg bisect --good 12
742
743
743 - advance the current bisection by marking current revision as good or
744 - advance the current bisection by marking current revision as good or
744 bad::
745 bad::
745
746
746 hg bisect --good
747 hg bisect --good
747 hg bisect --bad
748 hg bisect --bad
748
749
749 - mark the current revision, or a known revision, to be skipped (e.g. if
750 - mark the current revision, or a known revision, to be skipped (e.g. if
750 that revision is not usable because of another issue)::
751 that revision is not usable because of another issue)::
751
752
752 hg bisect --skip
753 hg bisect --skip
753 hg bisect --skip 23
754 hg bisect --skip 23
754
755
755 - skip all revisions that do not touch directories ``foo`` or ``bar``::
756 - skip all revisions that do not touch directories ``foo`` or ``bar``::
756
757
757 hg bisect --skip "!( file('path:foo') & file('path:bar') )"
758 hg bisect --skip "!( file('path:foo') & file('path:bar') )"
758
759
759 - forget the current bisection::
760 - forget the current bisection::
760
761
761 hg bisect --reset
762 hg bisect --reset
762
763
763 - use 'make && make tests' to automatically find the first broken
764 - use 'make && make tests' to automatically find the first broken
764 revision::
765 revision::
765
766
766 hg bisect --reset
767 hg bisect --reset
767 hg bisect --bad 34
768 hg bisect --bad 34
768 hg bisect --good 12
769 hg bisect --good 12
769 hg bisect --command "make && make tests"
770 hg bisect --command "make && make tests"
770
771
771 - see all changesets whose states are already known in the current
772 - see all changesets whose states are already known in the current
772 bisection::
773 bisection::
773
774
774 hg log -r "bisect(pruned)"
775 hg log -r "bisect(pruned)"
775
776
776 - see the changeset currently being bisected (especially useful
777 - see the changeset currently being bisected (especially useful
777 if running with -U/--noupdate)::
778 if running with -U/--noupdate)::
778
779
779 hg log -r "bisect(current)"
780 hg log -r "bisect(current)"
780
781
781 - see all changesets that took part in the current bisection::
782 - see all changesets that took part in the current bisection::
782
783
783 hg log -r "bisect(range)"
784 hg log -r "bisect(range)"
784
785
785 - you can even get a nice graph::
786 - you can even get a nice graph::
786
787
787 hg log --graph -r "bisect(range)"
788 hg log --graph -r "bisect(range)"
788
789
789 See :hg:`help revisions.bisect` for more about the `bisect()` predicate.
790 See :hg:`help revisions.bisect` for more about the `bisect()` predicate.
790
791
791 Returns 0 on success.
792 Returns 0 on success.
792 """
793 """
793 # backward compatibility
794 # backward compatibility
794 if rev in "good bad reset init".split():
795 if rev in "good bad reset init".split():
795 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
796 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
796 cmd, rev, extra = rev, extra, None
797 cmd, rev, extra = rev, extra, None
797 if cmd == "good":
798 if cmd == "good":
798 good = True
799 good = True
799 elif cmd == "bad":
800 elif cmd == "bad":
800 bad = True
801 bad = True
801 else:
802 else:
802 reset = True
803 reset = True
803 elif extra:
804 elif extra:
804 raise error.Abort(_('incompatible arguments'))
805 raise error.Abort(_('incompatible arguments'))
805
806
806 incompatibles = {
807 incompatibles = {
807 '--bad': bad,
808 '--bad': bad,
808 '--command': bool(command),
809 '--command': bool(command),
809 '--extend': extend,
810 '--extend': extend,
810 '--good': good,
811 '--good': good,
811 '--reset': reset,
812 '--reset': reset,
812 '--skip': skip,
813 '--skip': skip,
813 }
814 }
814
815
815 enabled = [x for x in incompatibles if incompatibles[x]]
816 enabled = [x for x in incompatibles if incompatibles[x]]
816
817
817 if len(enabled) > 1:
818 if len(enabled) > 1:
818 raise error.Abort(_('%s and %s are incompatible') %
819 raise error.Abort(_('%s and %s are incompatible') %
819 tuple(sorted(enabled)[0:2]))
820 tuple(sorted(enabled)[0:2]))
820
821
821 if reset:
822 if reset:
822 hbisect.resetstate(repo)
823 hbisect.resetstate(repo)
823 return
824 return
824
825
825 state = hbisect.load_state(repo)
826 state = hbisect.load_state(repo)
826
827
827 # update state
828 # update state
828 if good or bad or skip:
829 if good or bad or skip:
829 if rev:
830 if rev:
830 nodes = [repo[i].node() for i in scmutil.revrange(repo, [rev])]
831 nodes = [repo[i].node() for i in scmutil.revrange(repo, [rev])]
831 else:
832 else:
832 nodes = [repo.lookup('.')]
833 nodes = [repo.lookup('.')]
833 if good:
834 if good:
834 state['good'] += nodes
835 state['good'] += nodes
835 elif bad:
836 elif bad:
836 state['bad'] += nodes
837 state['bad'] += nodes
837 elif skip:
838 elif skip:
838 state['skip'] += nodes
839 state['skip'] += nodes
839 hbisect.save_state(repo, state)
840 hbisect.save_state(repo, state)
840 if not (state['good'] and state['bad']):
841 if not (state['good'] and state['bad']):
841 return
842 return
842
843
843 def mayupdate(repo, node, show_stats=True):
844 def mayupdate(repo, node, show_stats=True):
844 """common used update sequence"""
845 """common used update sequence"""
845 if noupdate:
846 if noupdate:
846 return
847 return
847 cmdutil.checkunfinished(repo)
848 cmdutil.checkunfinished(repo)
848 cmdutil.bailifchanged(repo)
849 cmdutil.bailifchanged(repo)
849 return hg.clean(repo, node, show_stats=show_stats)
850 return hg.clean(repo, node, show_stats=show_stats)
850
851
851 displayer = logcmdutil.changesetdisplayer(ui, repo, {})
852 displayer = logcmdutil.changesetdisplayer(ui, repo, {})
852
853
853 if command:
854 if command:
854 changesets = 1
855 changesets = 1
855 if noupdate:
856 if noupdate:
856 try:
857 try:
857 node = state['current'][0]
858 node = state['current'][0]
858 except LookupError:
859 except LookupError:
859 raise error.Abort(_('current bisect revision is unknown - '
860 raise error.Abort(_('current bisect revision is unknown - '
860 'start a new bisect to fix'))
861 'start a new bisect to fix'))
861 else:
862 else:
862 node, p2 = repo.dirstate.parents()
863 node, p2 = repo.dirstate.parents()
863 if p2 != nullid:
864 if p2 != nullid:
864 raise error.Abort(_('current bisect revision is a merge'))
865 raise error.Abort(_('current bisect revision is a merge'))
865 if rev:
866 if rev:
866 node = repo[scmutil.revsingle(repo, rev, node)].node()
867 node = repo[scmutil.revsingle(repo, rev, node)].node()
867 try:
868 try:
868 while changesets:
869 while changesets:
869 # update state
870 # update state
870 state['current'] = [node]
871 state['current'] = [node]
871 hbisect.save_state(repo, state)
872 hbisect.save_state(repo, state)
872 status = ui.system(command, environ={'HG_NODE': hex(node)},
873 status = ui.system(command, environ={'HG_NODE': hex(node)},
873 blockedtag='bisect_check')
874 blockedtag='bisect_check')
874 if status == 125:
875 if status == 125:
875 transition = "skip"
876 transition = "skip"
876 elif status == 0:
877 elif status == 0:
877 transition = "good"
878 transition = "good"
878 # status < 0 means process was killed
879 # status < 0 means process was killed
879 elif status == 127:
880 elif status == 127:
880 raise error.Abort(_("failed to execute %s") % command)
881 raise error.Abort(_("failed to execute %s") % command)
881 elif status < 0:
882 elif status < 0:
882 raise error.Abort(_("%s killed") % command)
883 raise error.Abort(_("%s killed") % command)
883 else:
884 else:
884 transition = "bad"
885 transition = "bad"
885 state[transition].append(node)
886 state[transition].append(node)
886 ctx = repo[node]
887 ctx = repo[node]
887 ui.status(_('changeset %d:%s: %s\n') % (ctx.rev(), ctx,
888 ui.status(_('changeset %d:%s: %s\n') % (ctx.rev(), ctx,
888 transition))
889 transition))
889 hbisect.checkstate(state)
890 hbisect.checkstate(state)
890 # bisect
891 # bisect
891 nodes, changesets, bgood = hbisect.bisect(repo, state)
892 nodes, changesets, bgood = hbisect.bisect(repo, state)
892 # update to next check
893 # update to next check
893 node = nodes[0]
894 node = nodes[0]
894 mayupdate(repo, node, show_stats=False)
895 mayupdate(repo, node, show_stats=False)
895 finally:
896 finally:
896 state['current'] = [node]
897 state['current'] = [node]
897 hbisect.save_state(repo, state)
898 hbisect.save_state(repo, state)
898 hbisect.printresult(ui, repo, state, displayer, nodes, bgood)
899 hbisect.printresult(ui, repo, state, displayer, nodes, bgood)
899 return
900 return
900
901
901 hbisect.checkstate(state)
902 hbisect.checkstate(state)
902
903
903 # actually bisect
904 # actually bisect
904 nodes, changesets, good = hbisect.bisect(repo, state)
905 nodes, changesets, good = hbisect.bisect(repo, state)
905 if extend:
906 if extend:
906 if not changesets:
907 if not changesets:
907 extendnode = hbisect.extendrange(repo, state, nodes, good)
908 extendnode = hbisect.extendrange(repo, state, nodes, good)
908 if extendnode is not None:
909 if extendnode is not None:
909 ui.write(_("Extending search to changeset %d:%s\n")
910 ui.write(_("Extending search to changeset %d:%s\n")
910 % (extendnode.rev(), extendnode))
911 % (extendnode.rev(), extendnode))
911 state['current'] = [extendnode.node()]
912 state['current'] = [extendnode.node()]
912 hbisect.save_state(repo, state)
913 hbisect.save_state(repo, state)
913 return mayupdate(repo, extendnode.node())
914 return mayupdate(repo, extendnode.node())
914 raise error.Abort(_("nothing to extend"))
915 raise error.Abort(_("nothing to extend"))
915
916
916 if changesets == 0:
917 if changesets == 0:
917 hbisect.printresult(ui, repo, state, displayer, nodes, good)
918 hbisect.printresult(ui, repo, state, displayer, nodes, good)
918 else:
919 else:
919 assert len(nodes) == 1 # only a single node can be tested next
920 assert len(nodes) == 1 # only a single node can be tested next
920 node = nodes[0]
921 node = nodes[0]
921 # compute the approximate number of remaining tests
922 # compute the approximate number of remaining tests
922 tests, size = 0, 2
923 tests, size = 0, 2
923 while size <= changesets:
924 while size <= changesets:
924 tests, size = tests + 1, size * 2
925 tests, size = tests + 1, size * 2
925 rev = repo.changelog.rev(node)
926 rev = repo.changelog.rev(node)
926 ui.write(_("Testing changeset %d:%s "
927 ui.write(_("Testing changeset %d:%s "
927 "(%d changesets remaining, ~%d tests)\n")
928 "(%d changesets remaining, ~%d tests)\n")
928 % (rev, short(node), changesets, tests))
929 % (rev, short(node), changesets, tests))
929 state['current'] = [node]
930 state['current'] = [node]
930 hbisect.save_state(repo, state)
931 hbisect.save_state(repo, state)
931 return mayupdate(repo, node)
932 return mayupdate(repo, node)
932
933
933 @command('bookmarks|bookmark',
934 @command('bookmarks|bookmark',
934 [('f', 'force', False, _('force')),
935 [('f', 'force', False, _('force')),
935 ('r', 'rev', '', _('revision for bookmark action'), _('REV')),
936 ('r', 'rev', '', _('revision for bookmark action'), _('REV')),
936 ('d', 'delete', False, _('delete a given bookmark')),
937 ('d', 'delete', False, _('delete a given bookmark')),
937 ('m', 'rename', '', _('rename a given bookmark'), _('OLD')),
938 ('m', 'rename', '', _('rename a given bookmark'), _('OLD')),
938 ('i', 'inactive', False, _('mark a bookmark inactive')),
939 ('i', 'inactive', False, _('mark a bookmark inactive')),
939 ('l', 'list', False, _('list existing bookmarks')),
940 ('l', 'list', False, _('list existing bookmarks')),
940 ] + formatteropts,
941 ] + formatteropts,
941 _('hg bookmarks [OPTIONS]... [NAME]...'),
942 _('hg bookmarks [OPTIONS]... [NAME]...'),
942 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION)
943 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION)
943 def bookmark(ui, repo, *names, **opts):
944 def bookmark(ui, repo, *names, **opts):
944 '''create a new bookmark or list existing bookmarks
945 '''create a new bookmark or list existing bookmarks
945
946
946 Bookmarks are labels on changesets to help track lines of development.
947 Bookmarks are labels on changesets to help track lines of development.
947 Bookmarks are unversioned and can be moved, renamed and deleted.
948 Bookmarks are unversioned and can be moved, renamed and deleted.
948 Deleting or moving a bookmark has no effect on the associated changesets.
949 Deleting or moving a bookmark has no effect on the associated changesets.
949
950
950 Creating or updating to a bookmark causes it to be marked as 'active'.
951 Creating or updating to a bookmark causes it to be marked as 'active'.
951 The active bookmark is indicated with a '*'.
952 The active bookmark is indicated with a '*'.
952 When a commit is made, the active bookmark will advance to the new commit.
953 When a commit is made, the active bookmark will advance to the new commit.
953 A plain :hg:`update` will also advance an active bookmark, if possible.
954 A plain :hg:`update` will also advance an active bookmark, if possible.
954 Updating away from a bookmark will cause it to be deactivated.
955 Updating away from a bookmark will cause it to be deactivated.
955
956
956 Bookmarks can be pushed and pulled between repositories (see
957 Bookmarks can be pushed and pulled between repositories (see
957 :hg:`help push` and :hg:`help pull`). If a shared bookmark has
958 :hg:`help push` and :hg:`help pull`). If a shared bookmark has
958 diverged, a new 'divergent bookmark' of the form 'name@path' will
959 diverged, a new 'divergent bookmark' of the form 'name@path' will
959 be created. Using :hg:`merge` will resolve the divergence.
960 be created. Using :hg:`merge` will resolve the divergence.
960
961
961 Specifying bookmark as '.' to -m/-d/-l options is equivalent to specifying
962 Specifying bookmark as '.' to -m/-d/-l options is equivalent to specifying
962 the active bookmark's name.
963 the active bookmark's name.
963
964
964 A bookmark named '@' has the special property that :hg:`clone` will
965 A bookmark named '@' has the special property that :hg:`clone` will
965 check it out by default if it exists.
966 check it out by default if it exists.
966
967
967 .. container:: verbose
968 .. container:: verbose
968
969
969 Template:
970 Template:
970
971
971 The following keywords are supported in addition to the common template
972 The following keywords are supported in addition to the common template
972 keywords and functions such as ``{bookmark}``. See also
973 keywords and functions such as ``{bookmark}``. See also
973 :hg:`help templates`.
974 :hg:`help templates`.
974
975
975 :active: Boolean. True if the bookmark is active.
976 :active: Boolean. True if the bookmark is active.
976
977
977 Examples:
978 Examples:
978
979
979 - create an active bookmark for a new line of development::
980 - create an active bookmark for a new line of development::
980
981
981 hg book new-feature
982 hg book new-feature
982
983
983 - create an inactive bookmark as a place marker::
984 - create an inactive bookmark as a place marker::
984
985
985 hg book -i reviewed
986 hg book -i reviewed
986
987
987 - create an inactive bookmark on another changeset::
988 - create an inactive bookmark on another changeset::
988
989
989 hg book -r .^ tested
990 hg book -r .^ tested
990
991
991 - rename bookmark turkey to dinner::
992 - rename bookmark turkey to dinner::
992
993
993 hg book -m turkey dinner
994 hg book -m turkey dinner
994
995
995 - move the '@' bookmark from another branch::
996 - move the '@' bookmark from another branch::
996
997
997 hg book -f @
998 hg book -f @
998
999
999 - print only the active bookmark name::
1000 - print only the active bookmark name::
1000
1001
1001 hg book -ql .
1002 hg book -ql .
1002 '''
1003 '''
1003 opts = pycompat.byteskwargs(opts)
1004 opts = pycompat.byteskwargs(opts)
1004 force = opts.get('force')
1005 force = opts.get('force')
1005 rev = opts.get('rev')
1006 rev = opts.get('rev')
1006 inactive = opts.get('inactive') # meaning add/rename to inactive bookmark
1007 inactive = opts.get('inactive') # meaning add/rename to inactive bookmark
1007
1008
1008 selactions = [k for k in ['delete', 'rename', 'list'] if opts.get(k)]
1009 selactions = [k for k in ['delete', 'rename', 'list'] if opts.get(k)]
1009 if len(selactions) > 1:
1010 if len(selactions) > 1:
1010 raise error.Abort(_('--%s and --%s are incompatible')
1011 raise error.Abort(_('--%s and --%s are incompatible')
1011 % tuple(selactions[:2]))
1012 % tuple(selactions[:2]))
1012 if selactions:
1013 if selactions:
1013 action = selactions[0]
1014 action = selactions[0]
1014 elif names or rev:
1015 elif names or rev:
1015 action = 'add'
1016 action = 'add'
1016 elif inactive:
1017 elif inactive:
1017 action = 'inactive' # meaning deactivate
1018 action = 'inactive' # meaning deactivate
1018 else:
1019 else:
1019 action = 'list'
1020 action = 'list'
1020
1021
1021 if rev and action in {'delete', 'rename', 'list'}:
1022 if rev and action in {'delete', 'rename', 'list'}:
1022 raise error.Abort(_("--rev is incompatible with --%s") % action)
1023 raise error.Abort(_("--rev is incompatible with --%s") % action)
1023 if inactive and action in {'delete', 'list'}:
1024 if inactive and action in {'delete', 'list'}:
1024 raise error.Abort(_("--inactive is incompatible with --%s") % action)
1025 raise error.Abort(_("--inactive is incompatible with --%s") % action)
1025 if not names and action in {'add', 'delete'}:
1026 if not names and action in {'add', 'delete'}:
1026 raise error.Abort(_("bookmark name required"))
1027 raise error.Abort(_("bookmark name required"))
1027
1028
1028 if action in {'add', 'delete', 'rename', 'inactive'}:
1029 if action in {'add', 'delete', 'rename', 'inactive'}:
1029 with repo.wlock(), repo.lock(), repo.transaction('bookmark') as tr:
1030 with repo.wlock(), repo.lock(), repo.transaction('bookmark') as tr:
1030 if action == 'delete':
1031 if action == 'delete':
1031 names = pycompat.maplist(repo._bookmarks.expandname, names)
1032 names = pycompat.maplist(repo._bookmarks.expandname, names)
1032 bookmarks.delete(repo, tr, names)
1033 bookmarks.delete(repo, tr, names)
1033 elif action == 'rename':
1034 elif action == 'rename':
1034 if not names:
1035 if not names:
1035 raise error.Abort(_("new bookmark name required"))
1036 raise error.Abort(_("new bookmark name required"))
1036 elif len(names) > 1:
1037 elif len(names) > 1:
1037 raise error.Abort(_("only one new bookmark name allowed"))
1038 raise error.Abort(_("only one new bookmark name allowed"))
1038 oldname = repo._bookmarks.expandname(opts['rename'])
1039 oldname = repo._bookmarks.expandname(opts['rename'])
1039 bookmarks.rename(repo, tr, oldname, names[0], force, inactive)
1040 bookmarks.rename(repo, tr, oldname, names[0], force, inactive)
1040 elif action == 'add':
1041 elif action == 'add':
1041 bookmarks.addbookmarks(repo, tr, names, rev, force, inactive)
1042 bookmarks.addbookmarks(repo, tr, names, rev, force, inactive)
1042 elif action == 'inactive':
1043 elif action == 'inactive':
1043 if len(repo._bookmarks) == 0:
1044 if len(repo._bookmarks) == 0:
1044 ui.status(_("no bookmarks set\n"))
1045 ui.status(_("no bookmarks set\n"))
1045 elif not repo._activebookmark:
1046 elif not repo._activebookmark:
1046 ui.status(_("no active bookmark\n"))
1047 ui.status(_("no active bookmark\n"))
1047 else:
1048 else:
1048 bookmarks.deactivate(repo)
1049 bookmarks.deactivate(repo)
1049 elif action == 'list':
1050 elif action == 'list':
1050 names = pycompat.maplist(repo._bookmarks.expandname, names)
1051 names = pycompat.maplist(repo._bookmarks.expandname, names)
1051 with ui.formatter('bookmarks', opts) as fm:
1052 with ui.formatter('bookmarks', opts) as fm:
1052 bookmarks.printbookmarks(ui, repo, fm, names)
1053 bookmarks.printbookmarks(ui, repo, fm, names)
1053 else:
1054 else:
1054 raise error.ProgrammingError('invalid action: %s' % action)
1055 raise error.ProgrammingError('invalid action: %s' % action)
1055
1056
1056 @command('branch',
1057 @command('branch',
1057 [('f', 'force', None,
1058 [('f', 'force', None,
1058 _('set branch name even if it shadows an existing branch')),
1059 _('set branch name even if it shadows an existing branch')),
1059 ('C', 'clean', None, _('reset branch name to parent branch name')),
1060 ('C', 'clean', None, _('reset branch name to parent branch name')),
1060 ('r', 'rev', [], _('change branches of the given revs (EXPERIMENTAL)')),
1061 ('r', 'rev', [], _('change branches of the given revs (EXPERIMENTAL)')),
1061 ],
1062 ],
1062 _('[-fC] [NAME]'),
1063 _('[-fC] [NAME]'),
1063 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION)
1064 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION)
1064 def branch(ui, repo, label=None, **opts):
1065 def branch(ui, repo, label=None, **opts):
1065 """set or show the current branch name
1066 """set or show the current branch name
1066
1067
1067 .. note::
1068 .. note::
1068
1069
1069 Branch names are permanent and global. Use :hg:`bookmark` to create a
1070 Branch names are permanent and global. Use :hg:`bookmark` to create a
1070 light-weight bookmark instead. See :hg:`help glossary` for more
1071 light-weight bookmark instead. See :hg:`help glossary` for more
1071 information about named branches and bookmarks.
1072 information about named branches and bookmarks.
1072
1073
1073 With no argument, show the current branch name. With one argument,
1074 With no argument, show the current branch name. With one argument,
1074 set the working directory branch name (the branch will not exist
1075 set the working directory branch name (the branch will not exist
1075 in the repository until the next commit). Standard practice
1076 in the repository until the next commit). Standard practice
1076 recommends that primary development take place on the 'default'
1077 recommends that primary development take place on the 'default'
1077 branch.
1078 branch.
1078
1079
1079 Unless -f/--force is specified, branch will not let you set a
1080 Unless -f/--force is specified, branch will not let you set a
1080 branch name that already exists.
1081 branch name that already exists.
1081
1082
1082 Use -C/--clean to reset the working directory branch to that of
1083 Use -C/--clean to reset the working directory branch to that of
1083 the parent of the working directory, negating a previous branch
1084 the parent of the working directory, negating a previous branch
1084 change.
1085 change.
1085
1086
1086 Use the command :hg:`update` to switch to an existing branch. Use
1087 Use the command :hg:`update` to switch to an existing branch. Use
1087 :hg:`commit --close-branch` to mark this branch head as closed.
1088 :hg:`commit --close-branch` to mark this branch head as closed.
1088 When all heads of a branch are closed, the branch will be
1089 When all heads of a branch are closed, the branch will be
1089 considered closed.
1090 considered closed.
1090
1091
1091 Returns 0 on success.
1092 Returns 0 on success.
1092 """
1093 """
1093 opts = pycompat.byteskwargs(opts)
1094 opts = pycompat.byteskwargs(opts)
1094 revs = opts.get('rev')
1095 revs = opts.get('rev')
1095 if label:
1096 if label:
1096 label = label.strip()
1097 label = label.strip()
1097
1098
1098 if not opts.get('clean') and not label:
1099 if not opts.get('clean') and not label:
1099 if revs:
1100 if revs:
1100 raise error.Abort(_("no branch name specified for the revisions"))
1101 raise error.Abort(_("no branch name specified for the revisions"))
1101 ui.write("%s\n" % repo.dirstate.branch())
1102 ui.write("%s\n" % repo.dirstate.branch())
1102 return
1103 return
1103
1104
1104 with repo.wlock():
1105 with repo.wlock():
1105 if opts.get('clean'):
1106 if opts.get('clean'):
1106 label = repo['.'].branch()
1107 label = repo['.'].branch()
1107 repo.dirstate.setbranch(label)
1108 repo.dirstate.setbranch(label)
1108 ui.status(_('reset working directory to branch %s\n') % label)
1109 ui.status(_('reset working directory to branch %s\n') % label)
1109 elif label:
1110 elif label:
1110
1111
1111 scmutil.checknewlabel(repo, label, 'branch')
1112 scmutil.checknewlabel(repo, label, 'branch')
1112 if revs:
1113 if revs:
1113 return cmdutil.changebranch(ui, repo, revs, label)
1114 return cmdutil.changebranch(ui, repo, revs, label)
1114
1115
1115 if not opts.get('force') and label in repo.branchmap():
1116 if not opts.get('force') and label in repo.branchmap():
1116 if label not in [p.branch() for p in repo[None].parents()]:
1117 if label not in [p.branch() for p in repo[None].parents()]:
1117 raise error.Abort(_('a branch of the same name already'
1118 raise error.Abort(_('a branch of the same name already'
1118 ' exists'),
1119 ' exists'),
1119 # i18n: "it" refers to an existing branch
1120 # i18n: "it" refers to an existing branch
1120 hint=_("use 'hg update' to switch to it"))
1121 hint=_("use 'hg update' to switch to it"))
1121
1122
1122 repo.dirstate.setbranch(label)
1123 repo.dirstate.setbranch(label)
1123 ui.status(_('marked working directory as branch %s\n') % label)
1124 ui.status(_('marked working directory as branch %s\n') % label)
1124
1125
1125 # find any open named branches aside from default
1126 # find any open named branches aside from default
1126 others = [n for n, h, t, c in repo.branchmap().iterbranches()
1127 others = [n for n, h, t, c in repo.branchmap().iterbranches()
1127 if n != "default" and not c]
1128 if n != "default" and not c]
1128 if not others:
1129 if not others:
1129 ui.status(_('(branches are permanent and global, '
1130 ui.status(_('(branches are permanent and global, '
1130 'did you want a bookmark?)\n'))
1131 'did you want a bookmark?)\n'))
1131
1132
1132 @command('branches',
1133 @command('branches',
1133 [('a', 'active', False,
1134 [('a', 'active', False,
1134 _('show only branches that have unmerged heads (DEPRECATED)')),
1135 _('show only branches that have unmerged heads (DEPRECATED)')),
1135 ('c', 'closed', False, _('show normal and closed branches')),
1136 ('c', 'closed', False, _('show normal and closed branches')),
1136 ('r', 'rev', [], _('show branch name(s) of the given rev'))
1137 ('r', 'rev', [], _('show branch name(s) of the given rev'))
1137 ] + formatteropts,
1138 ] + formatteropts,
1138 _('[-c]'),
1139 _('[-c]'),
1139 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1140 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
1140 intents={INTENT_READONLY})
1141 intents={INTENT_READONLY})
1141 def branches(ui, repo, active=False, closed=False, **opts):
1142 def branches(ui, repo, active=False, closed=False, **opts):
1142 """list repository named branches
1143 """list repository named branches
1143
1144
1144 List the repository's named branches, indicating which ones are
1145 List the repository's named branches, indicating which ones are
1145 inactive. If -c/--closed is specified, also list branches which have
1146 inactive. If -c/--closed is specified, also list branches which have
1146 been marked closed (see :hg:`commit --close-branch`).
1147 been marked closed (see :hg:`commit --close-branch`).
1147
1148
1148 Use the command :hg:`update` to switch to an existing branch.
1149 Use the command :hg:`update` to switch to an existing branch.
1149
1150
1150 .. container:: verbose
1151 .. container:: verbose
1151
1152
1152 Template:
1153 Template:
1153
1154
1154 The following keywords are supported in addition to the common template
1155 The following keywords are supported in addition to the common template
1155 keywords and functions such as ``{branch}``. See also
1156 keywords and functions such as ``{branch}``. See also
1156 :hg:`help templates`.
1157 :hg:`help templates`.
1157
1158
1158 :active: Boolean. True if the branch is active.
1159 :active: Boolean. True if the branch is active.
1159 :closed: Boolean. True if the branch is closed.
1160 :closed: Boolean. True if the branch is closed.
1160 :current: Boolean. True if it is the current branch.
1161 :current: Boolean. True if it is the current branch.
1161
1162
1162 Returns 0.
1163 Returns 0.
1163 """
1164 """
1164
1165
1165 opts = pycompat.byteskwargs(opts)
1166 opts = pycompat.byteskwargs(opts)
1166 revs = opts.get('rev')
1167 revs = opts.get('rev')
1167 selectedbranches = None
1168 selectedbranches = None
1168 if revs:
1169 if revs:
1169 revs = scmutil.revrange(repo, revs)
1170 revs = scmutil.revrange(repo, revs)
1170 getbi = repo.revbranchcache().branchinfo
1171 getbi = repo.revbranchcache().branchinfo
1171 selectedbranches = {getbi(r)[0] for r in revs}
1172 selectedbranches = {getbi(r)[0] for r in revs}
1172
1173
1173 ui.pager('branches')
1174 ui.pager('branches')
1174 fm = ui.formatter('branches', opts)
1175 fm = ui.formatter('branches', opts)
1175 hexfunc = fm.hexfunc
1176 hexfunc = fm.hexfunc
1176
1177
1177 allheads = set(repo.heads())
1178 allheads = set(repo.heads())
1178 branches = []
1179 branches = []
1179 for tag, heads, tip, isclosed in repo.branchmap().iterbranches():
1180 for tag, heads, tip, isclosed in repo.branchmap().iterbranches():
1180 if selectedbranches is not None and tag not in selectedbranches:
1181 if selectedbranches is not None and tag not in selectedbranches:
1181 continue
1182 continue
1182 isactive = False
1183 isactive = False
1183 if not isclosed:
1184 if not isclosed:
1184 openheads = set(repo.branchmap().iteropen(heads))
1185 openheads = set(repo.branchmap().iteropen(heads))
1185 isactive = bool(openheads & allheads)
1186 isactive = bool(openheads & allheads)
1186 branches.append((tag, repo[tip], isactive, not isclosed))
1187 branches.append((tag, repo[tip], isactive, not isclosed))
1187 branches.sort(key=lambda i: (i[2], i[1].rev(), i[0], i[3]),
1188 branches.sort(key=lambda i: (i[2], i[1].rev(), i[0], i[3]),
1188 reverse=True)
1189 reverse=True)
1189
1190
1190 for tag, ctx, isactive, isopen in branches:
1191 for tag, ctx, isactive, isopen in branches:
1191 if active and not isactive:
1192 if active and not isactive:
1192 continue
1193 continue
1193 if isactive:
1194 if isactive:
1194 label = 'branches.active'
1195 label = 'branches.active'
1195 notice = ''
1196 notice = ''
1196 elif not isopen:
1197 elif not isopen:
1197 if not closed:
1198 if not closed:
1198 continue
1199 continue
1199 label = 'branches.closed'
1200 label = 'branches.closed'
1200 notice = _(' (closed)')
1201 notice = _(' (closed)')
1201 else:
1202 else:
1202 label = 'branches.inactive'
1203 label = 'branches.inactive'
1203 notice = _(' (inactive)')
1204 notice = _(' (inactive)')
1204 current = (tag == repo.dirstate.branch())
1205 current = (tag == repo.dirstate.branch())
1205 if current:
1206 if current:
1206 label = 'branches.current'
1207 label = 'branches.current'
1207
1208
1208 fm.startitem()
1209 fm.startitem()
1209 fm.write('branch', '%s', tag, label=label)
1210 fm.write('branch', '%s', tag, label=label)
1210 rev = ctx.rev()
1211 rev = ctx.rev()
1211 padsize = max(31 - len("%d" % rev) - encoding.colwidth(tag), 0)
1212 padsize = max(31 - len("%d" % rev) - encoding.colwidth(tag), 0)
1212 fmt = ' ' * padsize + ' %d:%s'
1213 fmt = ' ' * padsize + ' %d:%s'
1213 fm.condwrite(not ui.quiet, 'rev node', fmt, rev, hexfunc(ctx.node()),
1214 fm.condwrite(not ui.quiet, 'rev node', fmt, rev, hexfunc(ctx.node()),
1214 label='log.changeset changeset.%s' % ctx.phasestr())
1215 label='log.changeset changeset.%s' % ctx.phasestr())
1215 fm.context(ctx=ctx)
1216 fm.context(ctx=ctx)
1216 fm.data(active=isactive, closed=not isopen, current=current)
1217 fm.data(active=isactive, closed=not isopen, current=current)
1217 if not ui.quiet:
1218 if not ui.quiet:
1218 fm.plain(notice)
1219 fm.plain(notice)
1219 fm.plain('\n')
1220 fm.plain('\n')
1220 fm.end()
1221 fm.end()
1221
1222
1222 @command('bundle',
1223 @command('bundle',
1223 [('f', 'force', None, _('run even when the destination is unrelated')),
1224 [('f', 'force', None, _('run even when the destination is unrelated')),
1224 ('r', 'rev', [], _('a changeset intended to be added to the destination'),
1225 ('r', 'rev', [], _('a changeset intended to be added to the destination'),
1225 _('REV')),
1226 _('REV')),
1226 ('b', 'branch', [], _('a specific branch you would like to bundle'),
1227 ('b', 'branch', [], _('a specific branch you would like to bundle'),
1227 _('BRANCH')),
1228 _('BRANCH')),
1228 ('', 'base', [],
1229 ('', 'base', [],
1229 _('a base changeset assumed to be available at the destination'),
1230 _('a base changeset assumed to be available at the destination'),
1230 _('REV')),
1231 _('REV')),
1231 ('a', 'all', None, _('bundle all changesets in the repository')),
1232 ('a', 'all', None, _('bundle all changesets in the repository')),
1232 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE')),
1233 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE')),
1233 ] + remoteopts,
1234 ] + remoteopts,
1234 _('[-f] [-t BUNDLESPEC] [-a] [-r REV]... [--base REV]... FILE [DEST]'),
1235 _('[-f] [-t BUNDLESPEC] [-a] [-r REV]... [--base REV]... FILE [DEST]'),
1235 helpcategory=command.CATEGORY_IMPORT_EXPORT)
1236 helpcategory=command.CATEGORY_IMPORT_EXPORT)
1236 def bundle(ui, repo, fname, dest=None, **opts):
1237 def bundle(ui, repo, fname, dest=None, **opts):
1237 """create a bundle file
1238 """create a bundle file
1238
1239
1239 Generate a bundle file containing data to be transferred to another
1240 Generate a bundle file containing data to be transferred to another
1240 repository.
1241 repository.
1241
1242
1242 To create a bundle containing all changesets, use -a/--all
1243 To create a bundle containing all changesets, use -a/--all
1243 (or --base null). Otherwise, hg assumes the destination will have
1244 (or --base null). Otherwise, hg assumes the destination will have
1244 all the nodes you specify with --base parameters. Otherwise, hg
1245 all the nodes you specify with --base parameters. Otherwise, hg
1245 will assume the repository has all the nodes in destination, or
1246 will assume the repository has all the nodes in destination, or
1246 default-push/default if no destination is specified, where destination
1247 default-push/default if no destination is specified, where destination
1247 is the repository you provide through DEST option.
1248 is the repository you provide through DEST option.
1248
1249
1249 You can change bundle format with the -t/--type option. See
1250 You can change bundle format with the -t/--type option. See
1250 :hg:`help bundlespec` for documentation on this format. By default,
1251 :hg:`help bundlespec` for documentation on this format. By default,
1251 the most appropriate format is used and compression defaults to
1252 the most appropriate format is used and compression defaults to
1252 bzip2.
1253 bzip2.
1253
1254
1254 The bundle file can then be transferred using conventional means
1255 The bundle file can then be transferred using conventional means
1255 and applied to another repository with the unbundle or pull
1256 and applied to another repository with the unbundle or pull
1256 command. This is useful when direct push and pull are not
1257 command. This is useful when direct push and pull are not
1257 available or when exporting an entire repository is undesirable.
1258 available or when exporting an entire repository is undesirable.
1258
1259
1259 Applying bundles preserves all changeset contents including
1260 Applying bundles preserves all changeset contents including
1260 permissions, copy/rename information, and revision history.
1261 permissions, copy/rename information, and revision history.
1261
1262
1262 Returns 0 on success, 1 if no changes found.
1263 Returns 0 on success, 1 if no changes found.
1263 """
1264 """
1264 opts = pycompat.byteskwargs(opts)
1265 opts = pycompat.byteskwargs(opts)
1265 revs = None
1266 revs = None
1266 if 'rev' in opts:
1267 if 'rev' in opts:
1267 revstrings = opts['rev']
1268 revstrings = opts['rev']
1268 revs = scmutil.revrange(repo, revstrings)
1269 revs = scmutil.revrange(repo, revstrings)
1269 if revstrings and not revs:
1270 if revstrings and not revs:
1270 raise error.Abort(_('no commits to bundle'))
1271 raise error.Abort(_('no commits to bundle'))
1271
1272
1272 bundletype = opts.get('type', 'bzip2').lower()
1273 bundletype = opts.get('type', 'bzip2').lower()
1273 try:
1274 try:
1274 bundlespec = exchange.parsebundlespec(repo, bundletype, strict=False)
1275 bundlespec = exchange.parsebundlespec(repo, bundletype, strict=False)
1275 except error.UnsupportedBundleSpecification as e:
1276 except error.UnsupportedBundleSpecification as e:
1276 raise error.Abort(pycompat.bytestr(e),
1277 raise error.Abort(pycompat.bytestr(e),
1277 hint=_("see 'hg help bundlespec' for supported "
1278 hint=_("see 'hg help bundlespec' for supported "
1278 "values for --type"))
1279 "values for --type"))
1279 cgversion = bundlespec.contentopts["cg.version"]
1280 cgversion = bundlespec.contentopts["cg.version"]
1280
1281
1281 # Packed bundles are a pseudo bundle format for now.
1282 # Packed bundles are a pseudo bundle format for now.
1282 if cgversion == 's1':
1283 if cgversion == 's1':
1283 raise error.Abort(_('packed bundles cannot be produced by "hg bundle"'),
1284 raise error.Abort(_('packed bundles cannot be produced by "hg bundle"'),
1284 hint=_("use 'hg debugcreatestreamclonebundle'"))
1285 hint=_("use 'hg debugcreatestreamclonebundle'"))
1285
1286
1286 if opts.get('all'):
1287 if opts.get('all'):
1287 if dest:
1288 if dest:
1288 raise error.Abort(_("--all is incompatible with specifying "
1289 raise error.Abort(_("--all is incompatible with specifying "
1289 "a destination"))
1290 "a destination"))
1290 if opts.get('base'):
1291 if opts.get('base'):
1291 ui.warn(_("ignoring --base because --all was specified\n"))
1292 ui.warn(_("ignoring --base because --all was specified\n"))
1292 base = [nullrev]
1293 base = [nullrev]
1293 else:
1294 else:
1294 base = scmutil.revrange(repo, opts.get('base'))
1295 base = scmutil.revrange(repo, opts.get('base'))
1295 if cgversion not in changegroup.supportedoutgoingversions(repo):
1296 if cgversion not in changegroup.supportedoutgoingversions(repo):
1296 raise error.Abort(_("repository does not support bundle version %s") %
1297 raise error.Abort(_("repository does not support bundle version %s") %
1297 cgversion)
1298 cgversion)
1298
1299
1299 if base:
1300 if base:
1300 if dest:
1301 if dest:
1301 raise error.Abort(_("--base is incompatible with specifying "
1302 raise error.Abort(_("--base is incompatible with specifying "
1302 "a destination"))
1303 "a destination"))
1303 common = [repo[rev].node() for rev in base]
1304 common = [repo[rev].node() for rev in base]
1304 heads = [repo[r].node() for r in revs] if revs else None
1305 heads = [repo[r].node() for r in revs] if revs else None
1305 outgoing = discovery.outgoing(repo, common, heads)
1306 outgoing = discovery.outgoing(repo, common, heads)
1306 else:
1307 else:
1307 dest = ui.expandpath(dest or 'default-push', dest or 'default')
1308 dest = ui.expandpath(dest or 'default-push', dest or 'default')
1308 dest, branches = hg.parseurl(dest, opts.get('branch'))
1309 dest, branches = hg.parseurl(dest, opts.get('branch'))
1309 other = hg.peer(repo, opts, dest)
1310 other = hg.peer(repo, opts, dest)
1310 revs = [repo[r].hex() for r in revs]
1311 revs = [repo[r].hex() for r in revs]
1311 revs, checkout = hg.addbranchrevs(repo, repo, branches, revs)
1312 revs, checkout = hg.addbranchrevs(repo, repo, branches, revs)
1312 heads = revs and pycompat.maplist(repo.lookup, revs) or revs
1313 heads = revs and pycompat.maplist(repo.lookup, revs) or revs
1313 outgoing = discovery.findcommonoutgoing(repo, other,
1314 outgoing = discovery.findcommonoutgoing(repo, other,
1314 onlyheads=heads,
1315 onlyheads=heads,
1315 force=opts.get('force'),
1316 force=opts.get('force'),
1316 portable=True)
1317 portable=True)
1317
1318
1318 if not outgoing.missing:
1319 if not outgoing.missing:
1319 scmutil.nochangesfound(ui, repo, not base and outgoing.excluded)
1320 scmutil.nochangesfound(ui, repo, not base and outgoing.excluded)
1320 return 1
1321 return 1
1321
1322
1322 if cgversion == '01': #bundle1
1323 if cgversion == '01': #bundle1
1323 bversion = 'HG10' + bundlespec.wirecompression
1324 bversion = 'HG10' + bundlespec.wirecompression
1324 bcompression = None
1325 bcompression = None
1325 elif cgversion in ('02', '03'):
1326 elif cgversion in ('02', '03'):
1326 bversion = 'HG20'
1327 bversion = 'HG20'
1327 bcompression = bundlespec.wirecompression
1328 bcompression = bundlespec.wirecompression
1328 else:
1329 else:
1329 raise error.ProgrammingError(
1330 raise error.ProgrammingError(
1330 'bundle: unexpected changegroup version %s' % cgversion)
1331 'bundle: unexpected changegroup version %s' % cgversion)
1331
1332
1332 # TODO compression options should be derived from bundlespec parsing.
1333 # TODO compression options should be derived from bundlespec parsing.
1333 # This is a temporary hack to allow adjusting bundle compression
1334 # This is a temporary hack to allow adjusting bundle compression
1334 # level without a) formalizing the bundlespec changes to declare it
1335 # level without a) formalizing the bundlespec changes to declare it
1335 # b) introducing a command flag.
1336 # b) introducing a command flag.
1336 compopts = {}
1337 compopts = {}
1337 complevel = ui.configint('experimental',
1338 complevel = ui.configint('experimental',
1338 'bundlecomplevel.' + bundlespec.compression)
1339 'bundlecomplevel.' + bundlespec.compression)
1339 if complevel is None:
1340 if complevel is None:
1340 complevel = ui.configint('experimental', 'bundlecomplevel')
1341 complevel = ui.configint('experimental', 'bundlecomplevel')
1341 if complevel is not None:
1342 if complevel is not None:
1342 compopts['level'] = complevel
1343 compopts['level'] = complevel
1343
1344
1344 # Allow overriding the bundling of obsmarker in phases through
1345 # Allow overriding the bundling of obsmarker in phases through
1345 # configuration while we don't have a bundle version that include them
1346 # configuration while we don't have a bundle version that include them
1346 if repo.ui.configbool('experimental', 'evolution.bundle-obsmarker'):
1347 if repo.ui.configbool('experimental', 'evolution.bundle-obsmarker'):
1347 bundlespec.contentopts['obsolescence'] = True
1348 bundlespec.contentopts['obsolescence'] = True
1348 if repo.ui.configbool('experimental', 'bundle-phases'):
1349 if repo.ui.configbool('experimental', 'bundle-phases'):
1349 bundlespec.contentopts['phases'] = True
1350 bundlespec.contentopts['phases'] = True
1350
1351
1351 bundle2.writenewbundle(ui, repo, 'bundle', fname, bversion, outgoing,
1352 bundle2.writenewbundle(ui, repo, 'bundle', fname, bversion, outgoing,
1352 bundlespec.contentopts, compression=bcompression,
1353 bundlespec.contentopts, compression=bcompression,
1353 compopts=compopts)
1354 compopts=compopts)
1354
1355
1355 @command('cat',
1356 @command('cat',
1356 [('o', 'output', '',
1357 [('o', 'output', '',
1357 _('print output to file with formatted name'), _('FORMAT')),
1358 _('print output to file with formatted name'), _('FORMAT')),
1358 ('r', 'rev', '', _('print the given revision'), _('REV')),
1359 ('r', 'rev', '', _('print the given revision'), _('REV')),
1359 ('', 'decode', None, _('apply any matching decode filter')),
1360 ('', 'decode', None, _('apply any matching decode filter')),
1360 ] + walkopts + formatteropts,
1361 ] + walkopts + formatteropts,
1361 _('[OPTION]... FILE...'),
1362 _('[OPTION]... FILE...'),
1362 helpcategory=command.CATEGORY_FILE_CONTENTS,
1363 helpcategory=command.CATEGORY_FILE_CONTENTS,
1363 inferrepo=True,
1364 inferrepo=True,
1364 intents={INTENT_READONLY})
1365 intents={INTENT_READONLY})
1365 def cat(ui, repo, file1, *pats, **opts):
1366 def cat(ui, repo, file1, *pats, **opts):
1366 """output the current or given revision of files
1367 """output the current or given revision of files
1367
1368
1368 Print the specified files as they were at the given revision. If
1369 Print the specified files as they were at the given revision. If
1369 no revision is given, the parent of the working directory is used.
1370 no revision is given, the parent of the working directory is used.
1370
1371
1371 Output may be to a file, in which case the name of the file is
1372 Output may be to a file, in which case the name of the file is
1372 given using a template string. See :hg:`help templates`. In addition
1373 given using a template string. See :hg:`help templates`. In addition
1373 to the common template keywords, the following formatting rules are
1374 to the common template keywords, the following formatting rules are
1374 supported:
1375 supported:
1375
1376
1376 :``%%``: literal "%" character
1377 :``%%``: literal "%" character
1377 :``%s``: basename of file being printed
1378 :``%s``: basename of file being printed
1378 :``%d``: dirname of file being printed, or '.' if in repository root
1379 :``%d``: dirname of file being printed, or '.' if in repository root
1379 :``%p``: root-relative path name of file being printed
1380 :``%p``: root-relative path name of file being printed
1380 :``%H``: changeset hash (40 hexadecimal digits)
1381 :``%H``: changeset hash (40 hexadecimal digits)
1381 :``%R``: changeset revision number
1382 :``%R``: changeset revision number
1382 :``%h``: short-form changeset hash (12 hexadecimal digits)
1383 :``%h``: short-form changeset hash (12 hexadecimal digits)
1383 :``%r``: zero-padded changeset revision number
1384 :``%r``: zero-padded changeset revision number
1384 :``%b``: basename of the exporting repository
1385 :``%b``: basename of the exporting repository
1385 :``\\``: literal "\\" character
1386 :``\\``: literal "\\" character
1386
1387
1387 .. container:: verbose
1388 .. container:: verbose
1388
1389
1389 Template:
1390 Template:
1390
1391
1391 The following keywords are supported in addition to the common template
1392 The following keywords are supported in addition to the common template
1392 keywords and functions. See also :hg:`help templates`.
1393 keywords and functions. See also :hg:`help templates`.
1393
1394
1394 :data: String. File content.
1395 :data: String. File content.
1395 :path: String. Repository-absolute path of the file.
1396 :path: String. Repository-absolute path of the file.
1396
1397
1397 Returns 0 on success.
1398 Returns 0 on success.
1398 """
1399 """
1399 opts = pycompat.byteskwargs(opts)
1400 opts = pycompat.byteskwargs(opts)
1400 rev = opts.get('rev')
1401 rev = opts.get('rev')
1401 if rev:
1402 if rev:
1402 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
1403 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
1403 ctx = scmutil.revsingle(repo, rev)
1404 ctx = scmutil.revsingle(repo, rev)
1404 m = scmutil.match(ctx, (file1,) + pats, opts)
1405 m = scmutil.match(ctx, (file1,) + pats, opts)
1405 fntemplate = opts.pop('output', '')
1406 fntemplate = opts.pop('output', '')
1406 if cmdutil.isstdiofilename(fntemplate):
1407 if cmdutil.isstdiofilename(fntemplate):
1407 fntemplate = ''
1408 fntemplate = ''
1408
1409
1409 if fntemplate:
1410 if fntemplate:
1410 fm = formatter.nullformatter(ui, 'cat', opts)
1411 fm = formatter.nullformatter(ui, 'cat', opts)
1411 else:
1412 else:
1412 ui.pager('cat')
1413 ui.pager('cat')
1413 fm = ui.formatter('cat', opts)
1414 fm = ui.formatter('cat', opts)
1414 with fm:
1415 with fm:
1415 return cmdutil.cat(ui, repo, ctx, m, fm, fntemplate, '',
1416 return cmdutil.cat(ui, repo, ctx, m, fm, fntemplate, '',
1416 **pycompat.strkwargs(opts))
1417 **pycompat.strkwargs(opts))
1417
1418
1418 @command('clone',
1419 @command('clone',
1419 [('U', 'noupdate', None, _('the clone will include an empty working '
1420 [('U', 'noupdate', None, _('the clone will include an empty working '
1420 'directory (only a repository)')),
1421 'directory (only a repository)')),
1421 ('u', 'updaterev', '', _('revision, tag, or branch to check out'),
1422 ('u', 'updaterev', '', _('revision, tag, or branch to check out'),
1422 _('REV')),
1423 _('REV')),
1423 ('r', 'rev', [], _('do not clone everything, but include this changeset'
1424 ('r', 'rev', [], _('do not clone everything, but include this changeset'
1424 ' and its ancestors'), _('REV')),
1425 ' and its ancestors'), _('REV')),
1425 ('b', 'branch', [], _('do not clone everything, but include this branch\'s'
1426 ('b', 'branch', [], _('do not clone everything, but include this branch\'s'
1426 ' changesets and their ancestors'), _('BRANCH')),
1427 ' changesets and their ancestors'), _('BRANCH')),
1427 ('', 'pull', None, _('use pull protocol to copy metadata')),
1428 ('', 'pull', None, _('use pull protocol to copy metadata')),
1428 ('', 'uncompressed', None,
1429 ('', 'uncompressed', None,
1429 _('an alias to --stream (DEPRECATED)')),
1430 _('an alias to --stream (DEPRECATED)')),
1430 ('', 'stream', None,
1431 ('', 'stream', None,
1431 _('clone with minimal data processing')),
1432 _('clone with minimal data processing')),
1432 ] + remoteopts,
1433 ] + remoteopts,
1433 _('[OPTION]... SOURCE [DEST]'),
1434 _('[OPTION]... SOURCE [DEST]'),
1434 helpcategory=command.CATEGORY_REPO_CREATION,
1435 helpcategory=command.CATEGORY_REPO_CREATION,
1435 helpbasic=True, norepo=True)
1436 helpbasic=True, norepo=True)
1436 def clone(ui, source, dest=None, **opts):
1437 def clone(ui, source, dest=None, **opts):
1437 """make a copy of an existing repository
1438 """make a copy of an existing repository
1438
1439
1439 Create a copy of an existing repository in a new directory.
1440 Create a copy of an existing repository in a new directory.
1440
1441
1441 If no destination directory name is specified, it defaults to the
1442 If no destination directory name is specified, it defaults to the
1442 basename of the source.
1443 basename of the source.
1443
1444
1444 The location of the source is added to the new repository's
1445 The location of the source is added to the new repository's
1445 ``.hg/hgrc`` file, as the default to be used for future pulls.
1446 ``.hg/hgrc`` file, as the default to be used for future pulls.
1446
1447
1447 Only local paths and ``ssh://`` URLs are supported as
1448 Only local paths and ``ssh://`` URLs are supported as
1448 destinations. For ``ssh://`` destinations, no working directory or
1449 destinations. For ``ssh://`` destinations, no working directory or
1449 ``.hg/hgrc`` will be created on the remote side.
1450 ``.hg/hgrc`` will be created on the remote side.
1450
1451
1451 If the source repository has a bookmark called '@' set, that
1452 If the source repository has a bookmark called '@' set, that
1452 revision will be checked out in the new repository by default.
1453 revision will be checked out in the new repository by default.
1453
1454
1454 To check out a particular version, use -u/--update, or
1455 To check out a particular version, use -u/--update, or
1455 -U/--noupdate to create a clone with no working directory.
1456 -U/--noupdate to create a clone with no working directory.
1456
1457
1457 To pull only a subset of changesets, specify one or more revisions
1458 To pull only a subset of changesets, specify one or more revisions
1458 identifiers with -r/--rev or branches with -b/--branch. The
1459 identifiers with -r/--rev or branches with -b/--branch. The
1459 resulting clone will contain only the specified changesets and
1460 resulting clone will contain only the specified changesets and
1460 their ancestors. These options (or 'clone src#rev dest') imply
1461 their ancestors. These options (or 'clone src#rev dest') imply
1461 --pull, even for local source repositories.
1462 --pull, even for local source repositories.
1462
1463
1463 In normal clone mode, the remote normalizes repository data into a common
1464 In normal clone mode, the remote normalizes repository data into a common
1464 exchange format and the receiving end translates this data into its local
1465 exchange format and the receiving end translates this data into its local
1465 storage format. --stream activates a different clone mode that essentially
1466 storage format. --stream activates a different clone mode that essentially
1466 copies repository files from the remote with minimal data processing. This
1467 copies repository files from the remote with minimal data processing. This
1467 significantly reduces the CPU cost of a clone both remotely and locally.
1468 significantly reduces the CPU cost of a clone both remotely and locally.
1468 However, it often increases the transferred data size by 30-40%. This can
1469 However, it often increases the transferred data size by 30-40%. This can
1469 result in substantially faster clones where I/O throughput is plentiful,
1470 result in substantially faster clones where I/O throughput is plentiful,
1470 especially for larger repositories. A side-effect of --stream clones is
1471 especially for larger repositories. A side-effect of --stream clones is
1471 that storage settings and requirements on the remote are applied locally:
1472 that storage settings and requirements on the remote are applied locally:
1472 a modern client may inherit legacy or inefficient storage used by the
1473 a modern client may inherit legacy or inefficient storage used by the
1473 remote or a legacy Mercurial client may not be able to clone from a
1474 remote or a legacy Mercurial client may not be able to clone from a
1474 modern Mercurial remote.
1475 modern Mercurial remote.
1475
1476
1476 .. note::
1477 .. note::
1477
1478
1478 Specifying a tag will include the tagged changeset but not the
1479 Specifying a tag will include the tagged changeset but not the
1479 changeset containing the tag.
1480 changeset containing the tag.
1480
1481
1481 .. container:: verbose
1482 .. container:: verbose
1482
1483
1483 For efficiency, hardlinks are used for cloning whenever the
1484 For efficiency, hardlinks are used for cloning whenever the
1484 source and destination are on the same filesystem (note this
1485 source and destination are on the same filesystem (note this
1485 applies only to the repository data, not to the working
1486 applies only to the repository data, not to the working
1486 directory). Some filesystems, such as AFS, implement hardlinking
1487 directory). Some filesystems, such as AFS, implement hardlinking
1487 incorrectly, but do not report errors. In these cases, use the
1488 incorrectly, but do not report errors. In these cases, use the
1488 --pull option to avoid hardlinking.
1489 --pull option to avoid hardlinking.
1489
1490
1490 Mercurial will update the working directory to the first applicable
1491 Mercurial will update the working directory to the first applicable
1491 revision from this list:
1492 revision from this list:
1492
1493
1493 a) null if -U or the source repository has no changesets
1494 a) null if -U or the source repository has no changesets
1494 b) if -u . and the source repository is local, the first parent of
1495 b) if -u . and the source repository is local, the first parent of
1495 the source repository's working directory
1496 the source repository's working directory
1496 c) the changeset specified with -u (if a branch name, this means the
1497 c) the changeset specified with -u (if a branch name, this means the
1497 latest head of that branch)
1498 latest head of that branch)
1498 d) the changeset specified with -r
1499 d) the changeset specified with -r
1499 e) the tipmost head specified with -b
1500 e) the tipmost head specified with -b
1500 f) the tipmost head specified with the url#branch source syntax
1501 f) the tipmost head specified with the url#branch source syntax
1501 g) the revision marked with the '@' bookmark, if present
1502 g) the revision marked with the '@' bookmark, if present
1502 h) the tipmost head of the default branch
1503 h) the tipmost head of the default branch
1503 i) tip
1504 i) tip
1504
1505
1505 When cloning from servers that support it, Mercurial may fetch
1506 When cloning from servers that support it, Mercurial may fetch
1506 pre-generated data from a server-advertised URL or inline from the
1507 pre-generated data from a server-advertised URL or inline from the
1507 same stream. When this is done, hooks operating on incoming changesets
1508 same stream. When this is done, hooks operating on incoming changesets
1508 and changegroups may fire more than once, once for each pre-generated
1509 and changegroups may fire more than once, once for each pre-generated
1509 bundle and as well as for any additional remaining data. In addition,
1510 bundle and as well as for any additional remaining data. In addition,
1510 if an error occurs, the repository may be rolled back to a partial
1511 if an error occurs, the repository may be rolled back to a partial
1511 clone. This behavior may change in future releases.
1512 clone. This behavior may change in future releases.
1512 See :hg:`help -e clonebundles` for more.
1513 See :hg:`help -e clonebundles` for more.
1513
1514
1514 Examples:
1515 Examples:
1515
1516
1516 - clone a remote repository to a new directory named hg/::
1517 - clone a remote repository to a new directory named hg/::
1517
1518
1518 hg clone https://www.mercurial-scm.org/repo/hg/
1519 hg clone https://www.mercurial-scm.org/repo/hg/
1519
1520
1520 - create a lightweight local clone::
1521 - create a lightweight local clone::
1521
1522
1522 hg clone project/ project-feature/
1523 hg clone project/ project-feature/
1523
1524
1524 - clone from an absolute path on an ssh server (note double-slash)::
1525 - clone from an absolute path on an ssh server (note double-slash)::
1525
1526
1526 hg clone ssh://user@server//home/projects/alpha/
1527 hg clone ssh://user@server//home/projects/alpha/
1527
1528
1528 - do a streaming clone while checking out a specified version::
1529 - do a streaming clone while checking out a specified version::
1529
1530
1530 hg clone --stream http://server/repo -u 1.5
1531 hg clone --stream http://server/repo -u 1.5
1531
1532
1532 - create a repository without changesets after a particular revision::
1533 - create a repository without changesets after a particular revision::
1533
1534
1534 hg clone -r 04e544 experimental/ good/
1535 hg clone -r 04e544 experimental/ good/
1535
1536
1536 - clone (and track) a particular named branch::
1537 - clone (and track) a particular named branch::
1537
1538
1538 hg clone https://www.mercurial-scm.org/repo/hg/#stable
1539 hg clone https://www.mercurial-scm.org/repo/hg/#stable
1539
1540
1540 See :hg:`help urls` for details on specifying URLs.
1541 See :hg:`help urls` for details on specifying URLs.
1541
1542
1542 Returns 0 on success.
1543 Returns 0 on success.
1543 """
1544 """
1544 opts = pycompat.byteskwargs(opts)
1545 opts = pycompat.byteskwargs(opts)
1545 if opts.get('noupdate') and opts.get('updaterev'):
1546 if opts.get('noupdate') and opts.get('updaterev'):
1546 raise error.Abort(_("cannot specify both --noupdate and --updaterev"))
1547 raise error.Abort(_("cannot specify both --noupdate and --updaterev"))
1547
1548
1548 # --include/--exclude can come from narrow or sparse.
1549 # --include/--exclude can come from narrow or sparse.
1549 includepats, excludepats = None, None
1550 includepats, excludepats = None, None
1550
1551
1551 # hg.clone() differentiates between None and an empty set. So make sure
1552 # hg.clone() differentiates between None and an empty set. So make sure
1552 # patterns are sets if narrow is requested without patterns.
1553 # patterns are sets if narrow is requested without patterns.
1553 if opts.get('narrow'):
1554 if opts.get('narrow'):
1554 includepats = set()
1555 includepats = set()
1555 excludepats = set()
1556 excludepats = set()
1556
1557
1557 if opts.get('include'):
1558 if opts.get('include'):
1558 includepats = narrowspec.parsepatterns(opts.get('include'))
1559 includepats = narrowspec.parsepatterns(opts.get('include'))
1559 if opts.get('exclude'):
1560 if opts.get('exclude'):
1560 excludepats = narrowspec.parsepatterns(opts.get('exclude'))
1561 excludepats = narrowspec.parsepatterns(opts.get('exclude'))
1561
1562
1562 r = hg.clone(ui, opts, source, dest,
1563 r = hg.clone(ui, opts, source, dest,
1563 pull=opts.get('pull'),
1564 pull=opts.get('pull'),
1564 stream=opts.get('stream') or opts.get('uncompressed'),
1565 stream=opts.get('stream') or opts.get('uncompressed'),
1565 revs=opts.get('rev'),
1566 revs=opts.get('rev'),
1566 update=opts.get('updaterev') or not opts.get('noupdate'),
1567 update=opts.get('updaterev') or not opts.get('noupdate'),
1567 branch=opts.get('branch'),
1568 branch=opts.get('branch'),
1568 shareopts=opts.get('shareopts'),
1569 shareopts=opts.get('shareopts'),
1569 storeincludepats=includepats,
1570 storeincludepats=includepats,
1570 storeexcludepats=excludepats,
1571 storeexcludepats=excludepats,
1571 depth=opts.get('depth') or None)
1572 depth=opts.get('depth') or None)
1572
1573
1573 return r is None
1574 return r is None
1574
1575
1575 @command('commit|ci',
1576 @command('commit|ci',
1576 [('A', 'addremove', None,
1577 [('A', 'addremove', None,
1577 _('mark new/missing files as added/removed before committing')),
1578 _('mark new/missing files as added/removed before committing')),
1578 ('', 'close-branch', None,
1579 ('', 'close-branch', None,
1579 _('mark a branch head as closed')),
1580 _('mark a branch head as closed')),
1580 ('', 'amend', None, _('amend the parent of the working directory')),
1581 ('', 'amend', None, _('amend the parent of the working directory')),
1581 ('s', 'secret', None, _('use the secret phase for committing')),
1582 ('s', 'secret', None, _('use the secret phase for committing')),
1582 ('e', 'edit', None, _('invoke editor on commit messages')),
1583 ('e', 'edit', None, _('invoke editor on commit messages')),
1583 ('i', 'interactive', None, _('use interactive mode')),
1584 ('i', 'interactive', None, _('use interactive mode')),
1584 ] + walkopts + commitopts + commitopts2 + subrepoopts,
1585 ] + walkopts + commitopts + commitopts2 + subrepoopts,
1585 _('[OPTION]... [FILE]...'),
1586 _('[OPTION]... [FILE]...'),
1586 helpcategory=command.CATEGORY_COMMITTING, helpbasic=True,
1587 helpcategory=command.CATEGORY_COMMITTING, helpbasic=True,
1587 inferrepo=True)
1588 inferrepo=True)
1588 def commit(ui, repo, *pats, **opts):
1589 def commit(ui, repo, *pats, **opts):
1589 """commit the specified files or all outstanding changes
1590 """commit the specified files or all outstanding changes
1590
1591
1591 Commit changes to the given files into the repository. Unlike a
1592 Commit changes to the given files into the repository. Unlike a
1592 centralized SCM, this operation is a local operation. See
1593 centralized SCM, this operation is a local operation. See
1593 :hg:`push` for a way to actively distribute your changes.
1594 :hg:`push` for a way to actively distribute your changes.
1594
1595
1595 If a list of files is omitted, all changes reported by :hg:`status`
1596 If a list of files is omitted, all changes reported by :hg:`status`
1596 will be committed.
1597 will be committed.
1597
1598
1598 If you are committing the result of a merge, do not provide any
1599 If you are committing the result of a merge, do not provide any
1599 filenames or -I/-X filters.
1600 filenames or -I/-X filters.
1600
1601
1601 If no commit message is specified, Mercurial starts your
1602 If no commit message is specified, Mercurial starts your
1602 configured editor where you can enter a message. In case your
1603 configured editor where you can enter a message. In case your
1603 commit fails, you will find a backup of your message in
1604 commit fails, you will find a backup of your message in
1604 ``.hg/last-message.txt``.
1605 ``.hg/last-message.txt``.
1605
1606
1606 The --close-branch flag can be used to mark the current branch
1607 The --close-branch flag can be used to mark the current branch
1607 head closed. When all heads of a branch are closed, the branch
1608 head closed. When all heads of a branch are closed, the branch
1608 will be considered closed and no longer listed.
1609 will be considered closed and no longer listed.
1609
1610
1610 The --amend flag can be used to amend the parent of the
1611 The --amend flag can be used to amend the parent of the
1611 working directory with a new commit that contains the changes
1612 working directory with a new commit that contains the changes
1612 in the parent in addition to those currently reported by :hg:`status`,
1613 in the parent in addition to those currently reported by :hg:`status`,
1613 if there are any. The old commit is stored in a backup bundle in
1614 if there are any. The old commit is stored in a backup bundle in
1614 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
1615 ``.hg/strip-backup`` (see :hg:`help bundle` and :hg:`help unbundle`
1615 on how to restore it).
1616 on how to restore it).
1616
1617
1617 Message, user and date are taken from the amended commit unless
1618 Message, user and date are taken from the amended commit unless
1618 specified. When a message isn't specified on the command line,
1619 specified. When a message isn't specified on the command line,
1619 the editor will open with the message of the amended commit.
1620 the editor will open with the message of the amended commit.
1620
1621
1621 It is not possible to amend public changesets (see :hg:`help phases`)
1622 It is not possible to amend public changesets (see :hg:`help phases`)
1622 or changesets that have children.
1623 or changesets that have children.
1623
1624
1624 See :hg:`help dates` for a list of formats valid for -d/--date.
1625 See :hg:`help dates` for a list of formats valid for -d/--date.
1625
1626
1626 Returns 0 on success, 1 if nothing changed.
1627 Returns 0 on success, 1 if nothing changed.
1627
1628
1628 .. container:: verbose
1629 .. container:: verbose
1629
1630
1630 Examples:
1631 Examples:
1631
1632
1632 - commit all files ending in .py::
1633 - commit all files ending in .py::
1633
1634
1634 hg commit --include "set:**.py"
1635 hg commit --include "set:**.py"
1635
1636
1636 - commit all non-binary files::
1637 - commit all non-binary files::
1637
1638
1638 hg commit --exclude "set:binary()"
1639 hg commit --exclude "set:binary()"
1639
1640
1640 - amend the current commit and set the date to now::
1641 - amend the current commit and set the date to now::
1641
1642
1642 hg commit --amend --date now
1643 hg commit --amend --date now
1643 """
1644 """
1644 with repo.wlock(), repo.lock():
1645 with repo.wlock(), repo.lock():
1645 return _docommit(ui, repo, *pats, **opts)
1646 return _docommit(ui, repo, *pats, **opts)
1646
1647
1647 def _docommit(ui, repo, *pats, **opts):
1648 def _docommit(ui, repo, *pats, **opts):
1648 if opts.get(r'interactive'):
1649 if opts.get(r'interactive'):
1649 opts.pop(r'interactive')
1650 opts.pop(r'interactive')
1650 ret = cmdutil.dorecord(ui, repo, commit, None, False,
1651 ret = cmdutil.dorecord(ui, repo, commit, None, False,
1651 cmdutil.recordfilter, *pats,
1652 cmdutil.recordfilter, *pats,
1652 **opts)
1653 **opts)
1653 # ret can be 0 (no changes to record) or the value returned by
1654 # ret can be 0 (no changes to record) or the value returned by
1654 # commit(), 1 if nothing changed or None on success.
1655 # commit(), 1 if nothing changed or None on success.
1655 return 1 if ret == 0 else ret
1656 return 1 if ret == 0 else ret
1656
1657
1657 opts = pycompat.byteskwargs(opts)
1658 opts = pycompat.byteskwargs(opts)
1658 if opts.get('subrepos'):
1659 if opts.get('subrepos'):
1659 if opts.get('amend'):
1660 if opts.get('amend'):
1660 raise error.Abort(_('cannot amend with --subrepos'))
1661 raise error.Abort(_('cannot amend with --subrepos'))
1661 # Let --subrepos on the command line override config setting.
1662 # Let --subrepos on the command line override config setting.
1662 ui.setconfig('ui', 'commitsubrepos', True, 'commit')
1663 ui.setconfig('ui', 'commitsubrepos', True, 'commit')
1663
1664
1664 cmdutil.checkunfinished(repo, commit=True)
1665 cmdutil.checkunfinished(repo, commit=True)
1665
1666
1666 branch = repo[None].branch()
1667 branch = repo[None].branch()
1667 bheads = repo.branchheads(branch)
1668 bheads = repo.branchheads(branch)
1668
1669
1669 extra = {}
1670 extra = {}
1670 if opts.get('close_branch'):
1671 if opts.get('close_branch'):
1671 extra['close'] = '1'
1672 extra['close'] = '1'
1672
1673
1673 if not bheads:
1674 if not bheads:
1674 raise error.Abort(_('can only close branch heads'))
1675 raise error.Abort(_('can only close branch heads'))
1675 elif opts.get('amend'):
1676 elif opts.get('amend'):
1676 if repo['.'].p1().branch() != branch and \
1677 if repo['.'].p1().branch() != branch and \
1677 repo['.'].p2().branch() != branch:
1678 repo['.'].p2().branch() != branch:
1678 raise error.Abort(_('can only close branch heads'))
1679 raise error.Abort(_('can only close branch heads'))
1679
1680
1680 if opts.get('amend'):
1681 if opts.get('amend'):
1681 if ui.configbool('ui', 'commitsubrepos'):
1682 if ui.configbool('ui', 'commitsubrepos'):
1682 raise error.Abort(_('cannot amend with ui.commitsubrepos enabled'))
1683 raise error.Abort(_('cannot amend with ui.commitsubrepos enabled'))
1683
1684
1684 old = repo['.']
1685 old = repo['.']
1685 rewriteutil.precheck(repo, [old.rev()], 'amend')
1686 rewriteutil.precheck(repo, [old.rev()], 'amend')
1686
1687
1687 # Currently histedit gets confused if an amend happens while histedit
1688 # Currently histedit gets confused if an amend happens while histedit
1688 # is in progress. Since we have a checkunfinished command, we are
1689 # is in progress. Since we have a checkunfinished command, we are
1689 # temporarily honoring it.
1690 # temporarily honoring it.
1690 #
1691 #
1691 # Note: eventually this guard will be removed. Please do not expect
1692 # Note: eventually this guard will be removed. Please do not expect
1692 # this behavior to remain.
1693 # this behavior to remain.
1693 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
1694 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
1694 cmdutil.checkunfinished(repo)
1695 cmdutil.checkunfinished(repo)
1695
1696
1696 node = cmdutil.amend(ui, repo, old, extra, pats, opts)
1697 node = cmdutil.amend(ui, repo, old, extra, pats, opts)
1697 if node == old.node():
1698 if node == old.node():
1698 ui.status(_("nothing changed\n"))
1699 ui.status(_("nothing changed\n"))
1699 return 1
1700 return 1
1700 else:
1701 else:
1701 def commitfunc(ui, repo, message, match, opts):
1702 def commitfunc(ui, repo, message, match, opts):
1702 overrides = {}
1703 overrides = {}
1703 if opts.get('secret'):
1704 if opts.get('secret'):
1704 overrides[('phases', 'new-commit')] = 'secret'
1705 overrides[('phases', 'new-commit')] = 'secret'
1705
1706
1706 baseui = repo.baseui
1707 baseui = repo.baseui
1707 with baseui.configoverride(overrides, 'commit'):
1708 with baseui.configoverride(overrides, 'commit'):
1708 with ui.configoverride(overrides, 'commit'):
1709 with ui.configoverride(overrides, 'commit'):
1709 editform = cmdutil.mergeeditform(repo[None],
1710 editform = cmdutil.mergeeditform(repo[None],
1710 'commit.normal')
1711 'commit.normal')
1711 editor = cmdutil.getcommiteditor(
1712 editor = cmdutil.getcommiteditor(
1712 editform=editform, **pycompat.strkwargs(opts))
1713 editform=editform, **pycompat.strkwargs(opts))
1713 return repo.commit(message,
1714 return repo.commit(message,
1714 opts.get('user'),
1715 opts.get('user'),
1715 opts.get('date'),
1716 opts.get('date'),
1716 match,
1717 match,
1717 editor=editor,
1718 editor=editor,
1718 extra=extra)
1719 extra=extra)
1719
1720
1720 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
1721 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
1721
1722
1722 if not node:
1723 if not node:
1723 stat = cmdutil.postcommitstatus(repo, pats, opts)
1724 stat = cmdutil.postcommitstatus(repo, pats, opts)
1724 if stat[3]:
1725 if stat[3]:
1725 ui.status(_("nothing changed (%d missing files, see "
1726 ui.status(_("nothing changed (%d missing files, see "
1726 "'hg status')\n") % len(stat[3]))
1727 "'hg status')\n") % len(stat[3]))
1727 else:
1728 else:
1728 ui.status(_("nothing changed\n"))
1729 ui.status(_("nothing changed\n"))
1729 return 1
1730 return 1
1730
1731
1731 cmdutil.commitstatus(repo, node, branch, bheads, opts)
1732 cmdutil.commitstatus(repo, node, branch, bheads, opts)
1732
1733
1733 @command('config|showconfig|debugconfig',
1734 @command('config|showconfig|debugconfig',
1734 [('u', 'untrusted', None, _('show untrusted configuration options')),
1735 [('u', 'untrusted', None, _('show untrusted configuration options')),
1735 ('e', 'edit', None, _('edit user config')),
1736 ('e', 'edit', None, _('edit user config')),
1736 ('l', 'local', None, _('edit repository config')),
1737 ('l', 'local', None, _('edit repository config')),
1737 ('g', 'global', None, _('edit global config'))] + formatteropts,
1738 ('g', 'global', None, _('edit global config'))] + formatteropts,
1738 _('[-u] [NAME]...'),
1739 _('[-u] [NAME]...'),
1739 helpcategory=command.CATEGORY_HELP,
1740 helpcategory=command.CATEGORY_HELP,
1740 optionalrepo=True,
1741 optionalrepo=True,
1741 intents={INTENT_READONLY})
1742 intents={INTENT_READONLY})
1742 def config(ui, repo, *values, **opts):
1743 def config(ui, repo, *values, **opts):
1743 """show combined config settings from all hgrc files
1744 """show combined config settings from all hgrc files
1744
1745
1745 With no arguments, print names and values of all config items.
1746 With no arguments, print names and values of all config items.
1746
1747
1747 With one argument of the form section.name, print just the value
1748 With one argument of the form section.name, print just the value
1748 of that config item.
1749 of that config item.
1749
1750
1750 With multiple arguments, print names and values of all config
1751 With multiple arguments, print names and values of all config
1751 items with matching section names or section.names.
1752 items with matching section names or section.names.
1752
1753
1753 With --edit, start an editor on the user-level config file. With
1754 With --edit, start an editor on the user-level config file. With
1754 --global, edit the system-wide config file. With --local, edit the
1755 --global, edit the system-wide config file. With --local, edit the
1755 repository-level config file.
1756 repository-level config file.
1756
1757
1757 With --debug, the source (filename and line number) is printed
1758 With --debug, the source (filename and line number) is printed
1758 for each config item.
1759 for each config item.
1759
1760
1760 See :hg:`help config` for more information about config files.
1761 See :hg:`help config` for more information about config files.
1761
1762
1762 .. container:: verbose
1763 .. container:: verbose
1763
1764
1764 Template:
1765 Template:
1765
1766
1766 The following keywords are supported. See also :hg:`help templates`.
1767 The following keywords are supported. See also :hg:`help templates`.
1767
1768
1768 :name: String. Config name.
1769 :name: String. Config name.
1769 :source: String. Filename and line number where the item is defined.
1770 :source: String. Filename and line number where the item is defined.
1770 :value: String. Config value.
1771 :value: String. Config value.
1771
1772
1772 Returns 0 on success, 1 if NAME does not exist.
1773 Returns 0 on success, 1 if NAME does not exist.
1773
1774
1774 """
1775 """
1775
1776
1776 opts = pycompat.byteskwargs(opts)
1777 opts = pycompat.byteskwargs(opts)
1777 if opts.get('edit') or opts.get('local') or opts.get('global'):
1778 if opts.get('edit') or opts.get('local') or opts.get('global'):
1778 if opts.get('local') and opts.get('global'):
1779 if opts.get('local') and opts.get('global'):
1779 raise error.Abort(_("can't use --local and --global together"))
1780 raise error.Abort(_("can't use --local and --global together"))
1780
1781
1781 if opts.get('local'):
1782 if opts.get('local'):
1782 if not repo:
1783 if not repo:
1783 raise error.Abort(_("can't use --local outside a repository"))
1784 raise error.Abort(_("can't use --local outside a repository"))
1784 paths = [repo.vfs.join('hgrc')]
1785 paths = [repo.vfs.join('hgrc')]
1785 elif opts.get('global'):
1786 elif opts.get('global'):
1786 paths = rcutil.systemrcpath()
1787 paths = rcutil.systemrcpath()
1787 else:
1788 else:
1788 paths = rcutil.userrcpath()
1789 paths = rcutil.userrcpath()
1789
1790
1790 for f in paths:
1791 for f in paths:
1791 if os.path.exists(f):
1792 if os.path.exists(f):
1792 break
1793 break
1793 else:
1794 else:
1794 if opts.get('global'):
1795 if opts.get('global'):
1795 samplehgrc = uimod.samplehgrcs['global']
1796 samplehgrc = uimod.samplehgrcs['global']
1796 elif opts.get('local'):
1797 elif opts.get('local'):
1797 samplehgrc = uimod.samplehgrcs['local']
1798 samplehgrc = uimod.samplehgrcs['local']
1798 else:
1799 else:
1799 samplehgrc = uimod.samplehgrcs['user']
1800 samplehgrc = uimod.samplehgrcs['user']
1800
1801
1801 f = paths[0]
1802 f = paths[0]
1802 fp = open(f, "wb")
1803 fp = open(f, "wb")
1803 fp.write(util.tonativeeol(samplehgrc))
1804 fp.write(util.tonativeeol(samplehgrc))
1804 fp.close()
1805 fp.close()
1805
1806
1806 editor = ui.geteditor()
1807 editor = ui.geteditor()
1807 ui.system("%s \"%s\"" % (editor, f),
1808 ui.system("%s \"%s\"" % (editor, f),
1808 onerr=error.Abort, errprefix=_("edit failed"),
1809 onerr=error.Abort, errprefix=_("edit failed"),
1809 blockedtag='config_edit')
1810 blockedtag='config_edit')
1810 return
1811 return
1811 ui.pager('config')
1812 ui.pager('config')
1812 fm = ui.formatter('config', opts)
1813 fm = ui.formatter('config', opts)
1813 for t, f in rcutil.rccomponents():
1814 for t, f in rcutil.rccomponents():
1814 if t == 'path':
1815 if t == 'path':
1815 ui.debug('read config from: %s\n' % f)
1816 ui.debug('read config from: %s\n' % f)
1816 elif t == 'items':
1817 elif t == 'items':
1817 for section, name, value, source in f:
1818 for section, name, value, source in f:
1818 ui.debug('set config by: %s\n' % source)
1819 ui.debug('set config by: %s\n' % source)
1819 else:
1820 else:
1820 raise error.ProgrammingError('unknown rctype: %s' % t)
1821 raise error.ProgrammingError('unknown rctype: %s' % t)
1821 untrusted = bool(opts.get('untrusted'))
1822 untrusted = bool(opts.get('untrusted'))
1822
1823
1823 selsections = selentries = []
1824 selsections = selentries = []
1824 if values:
1825 if values:
1825 selsections = [v for v in values if '.' not in v]
1826 selsections = [v for v in values if '.' not in v]
1826 selentries = [v for v in values if '.' in v]
1827 selentries = [v for v in values if '.' in v]
1827 uniquesel = (len(selentries) == 1 and not selsections)
1828 uniquesel = (len(selentries) == 1 and not selsections)
1828 selsections = set(selsections)
1829 selsections = set(selsections)
1829 selentries = set(selentries)
1830 selentries = set(selentries)
1830
1831
1831 matched = False
1832 matched = False
1832 for section, name, value in ui.walkconfig(untrusted=untrusted):
1833 for section, name, value in ui.walkconfig(untrusted=untrusted):
1833 source = ui.configsource(section, name, untrusted)
1834 source = ui.configsource(section, name, untrusted)
1834 value = pycompat.bytestr(value)
1835 value = pycompat.bytestr(value)
1835 if fm.isplain():
1836 if fm.isplain():
1836 source = source or 'none'
1837 source = source or 'none'
1837 value = value.replace('\n', '\\n')
1838 value = value.replace('\n', '\\n')
1838 entryname = section + '.' + name
1839 entryname = section + '.' + name
1839 if values and not (section in selsections or entryname in selentries):
1840 if values and not (section in selsections or entryname in selentries):
1840 continue
1841 continue
1841 fm.startitem()
1842 fm.startitem()
1842 fm.condwrite(ui.debugflag, 'source', '%s: ', source)
1843 fm.condwrite(ui.debugflag, 'source', '%s: ', source)
1843 if uniquesel:
1844 if uniquesel:
1844 fm.data(name=entryname)
1845 fm.data(name=entryname)
1845 fm.write('value', '%s\n', value)
1846 fm.write('value', '%s\n', value)
1846 else:
1847 else:
1847 fm.write('name value', '%s=%s\n', entryname, value)
1848 fm.write('name value', '%s=%s\n', entryname, value)
1848 matched = True
1849 matched = True
1849 fm.end()
1850 fm.end()
1850 if matched:
1851 if matched:
1851 return 0
1852 return 0
1852 return 1
1853 return 1
1853
1854
1854 @command('copy|cp',
1855 @command('copy|cp',
1855 [('A', 'after', None, _('record a copy that has already occurred')),
1856 [('A', 'after', None, _('record a copy that has already occurred')),
1856 ('f', 'force', None, _('forcibly copy over an existing managed file')),
1857 ('f', 'force', None, _('forcibly copy over an existing managed file')),
1857 ] + walkopts + dryrunopts,
1858 ] + walkopts + dryrunopts,
1858 _('[OPTION]... [SOURCE]... DEST'),
1859 _('[OPTION]... [SOURCE]... DEST'),
1859 helpcategory=command.CATEGORY_FILE_CONTENTS)
1860 helpcategory=command.CATEGORY_FILE_CONTENTS)
1860 def copy(ui, repo, *pats, **opts):
1861 def copy(ui, repo, *pats, **opts):
1861 """mark files as copied for the next commit
1862 """mark files as copied for the next commit
1862
1863
1863 Mark dest as having copies of source files. If dest is a
1864 Mark dest as having copies of source files. If dest is a
1864 directory, copies are put in that directory. If dest is a file,
1865 directory, copies are put in that directory. If dest is a file,
1865 the source must be a single file.
1866 the source must be a single file.
1866
1867
1867 By default, this command copies the contents of files as they
1868 By default, this command copies the contents of files as they
1868 exist in the working directory. If invoked with -A/--after, the
1869 exist in the working directory. If invoked with -A/--after, the
1869 operation is recorded, but no copying is performed.
1870 operation is recorded, but no copying is performed.
1870
1871
1871 This command takes effect with the next commit. To undo a copy
1872 This command takes effect with the next commit. To undo a copy
1872 before that, see :hg:`revert`.
1873 before that, see :hg:`revert`.
1873
1874
1874 Returns 0 on success, 1 if errors are encountered.
1875 Returns 0 on success, 1 if errors are encountered.
1875 """
1876 """
1876 opts = pycompat.byteskwargs(opts)
1877 opts = pycompat.byteskwargs(opts)
1877 with repo.wlock(False):
1878 with repo.wlock(False):
1878 return cmdutil.copy(ui, repo, pats, opts)
1879 return cmdutil.copy(ui, repo, pats, opts)
1879
1880
1880 @command(
1881 @command(
1881 'debugcommands', [], _('[COMMAND]'),
1882 'debugcommands', [], _('[COMMAND]'),
1882 helpcategory=command.CATEGORY_HELP,
1883 helpcategory=command.CATEGORY_HELP,
1883 norepo=True)
1884 norepo=True)
1884 def debugcommands(ui, cmd='', *args):
1885 def debugcommands(ui, cmd='', *args):
1885 """list all available commands and options"""
1886 """list all available commands and options"""
1886 for cmd, vals in sorted(table.iteritems()):
1887 for cmd, vals in sorted(table.iteritems()):
1887 cmd = cmd.split('|')[0]
1888 cmd = cmd.split('|')[0]
1888 opts = ', '.join([i[1] for i in vals[1]])
1889 opts = ', '.join([i[1] for i in vals[1]])
1889 ui.write('%s: %s\n' % (cmd, opts))
1890 ui.write('%s: %s\n' % (cmd, opts))
1890
1891
1891 @command('debugcomplete',
1892 @command('debugcomplete',
1892 [('o', 'options', None, _('show the command options'))],
1893 [('o', 'options', None, _('show the command options'))],
1893 _('[-o] CMD'),
1894 _('[-o] CMD'),
1894 helpcategory=command.CATEGORY_HELP,
1895 helpcategory=command.CATEGORY_HELP,
1895 norepo=True)
1896 norepo=True)
1896 def debugcomplete(ui, cmd='', **opts):
1897 def debugcomplete(ui, cmd='', **opts):
1897 """returns the completion list associated with the given command"""
1898 """returns the completion list associated with the given command"""
1898
1899
1899 if opts.get(r'options'):
1900 if opts.get(r'options'):
1900 options = []
1901 options = []
1901 otables = [globalopts]
1902 otables = [globalopts]
1902 if cmd:
1903 if cmd:
1903 aliases, entry = cmdutil.findcmd(cmd, table, False)
1904 aliases, entry = cmdutil.findcmd(cmd, table, False)
1904 otables.append(entry[1])
1905 otables.append(entry[1])
1905 for t in otables:
1906 for t in otables:
1906 for o in t:
1907 for o in t:
1907 if "(DEPRECATED)" in o[3]:
1908 if "(DEPRECATED)" in o[3]:
1908 continue
1909 continue
1909 if o[0]:
1910 if o[0]:
1910 options.append('-%s' % o[0])
1911 options.append('-%s' % o[0])
1911 options.append('--%s' % o[1])
1912 options.append('--%s' % o[1])
1912 ui.write("%s\n" % "\n".join(options))
1913 ui.write("%s\n" % "\n".join(options))
1913 return
1914 return
1914
1915
1915 cmdlist, unused_allcmds = cmdutil.findpossible(cmd, table)
1916 cmdlist, unused_allcmds = cmdutil.findpossible(cmd, table)
1916 if ui.verbose:
1917 if ui.verbose:
1917 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
1918 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
1918 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
1919 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
1919
1920
1920 @command('diff',
1921 @command('diff',
1921 [('r', 'rev', [], _('revision'), _('REV')),
1922 [('r', 'rev', [], _('revision'), _('REV')),
1922 ('c', 'change', '', _('change made by revision'), _('REV'))
1923 ('c', 'change', '', _('change made by revision'), _('REV'))
1923 ] + diffopts + diffopts2 + walkopts + subrepoopts,
1924 ] + diffopts + diffopts2 + walkopts + subrepoopts,
1924 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'),
1925 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'),
1925 helpcategory=command.CATEGORY_FILE_CONTENTS,
1926 helpcategory=command.CATEGORY_FILE_CONTENTS,
1926 helpbasic=True, inferrepo=True, intents={INTENT_READONLY})
1927 helpbasic=True, inferrepo=True, intents={INTENT_READONLY})
1927 def diff(ui, repo, *pats, **opts):
1928 def diff(ui, repo, *pats, **opts):
1928 """diff repository (or selected files)
1929 """diff repository (or selected files)
1929
1930
1930 Show differences between revisions for the specified files.
1931 Show differences between revisions for the specified files.
1931
1932
1932 Differences between files are shown using the unified diff format.
1933 Differences between files are shown using the unified diff format.
1933
1934
1934 .. note::
1935 .. note::
1935
1936
1936 :hg:`diff` may generate unexpected results for merges, as it will
1937 :hg:`diff` may generate unexpected results for merges, as it will
1937 default to comparing against the working directory's first
1938 default to comparing against the working directory's first
1938 parent changeset if no revisions are specified.
1939 parent changeset if no revisions are specified.
1939
1940
1940 When two revision arguments are given, then changes are shown
1941 When two revision arguments are given, then changes are shown
1941 between those revisions. If only one revision is specified then
1942 between those revisions. If only one revision is specified then
1942 that revision is compared to the working directory, and, when no
1943 that revision is compared to the working directory, and, when no
1943 revisions are specified, the working directory files are compared
1944 revisions are specified, the working directory files are compared
1944 to its first parent.
1945 to its first parent.
1945
1946
1946 Alternatively you can specify -c/--change with a revision to see
1947 Alternatively you can specify -c/--change with a revision to see
1947 the changes in that changeset relative to its first parent.
1948 the changes in that changeset relative to its first parent.
1948
1949
1949 Without the -a/--text option, diff will avoid generating diffs of
1950 Without the -a/--text option, diff will avoid generating diffs of
1950 files it detects as binary. With -a, diff will generate a diff
1951 files it detects as binary. With -a, diff will generate a diff
1951 anyway, probably with undesirable results.
1952 anyway, probably with undesirable results.
1952
1953
1953 Use the -g/--git option to generate diffs in the git extended diff
1954 Use the -g/--git option to generate diffs in the git extended diff
1954 format. For more information, read :hg:`help diffs`.
1955 format. For more information, read :hg:`help diffs`.
1955
1956
1956 .. container:: verbose
1957 .. container:: verbose
1957
1958
1958 Examples:
1959 Examples:
1959
1960
1960 - compare a file in the current working directory to its parent::
1961 - compare a file in the current working directory to its parent::
1961
1962
1962 hg diff foo.c
1963 hg diff foo.c
1963
1964
1964 - compare two historical versions of a directory, with rename info::
1965 - compare two historical versions of a directory, with rename info::
1965
1966
1966 hg diff --git -r 1.0:1.2 lib/
1967 hg diff --git -r 1.0:1.2 lib/
1967
1968
1968 - get change stats relative to the last change on some date::
1969 - get change stats relative to the last change on some date::
1969
1970
1970 hg diff --stat -r "date('may 2')"
1971 hg diff --stat -r "date('may 2')"
1971
1972
1972 - diff all newly-added files that contain a keyword::
1973 - diff all newly-added files that contain a keyword::
1973
1974
1974 hg diff "set:added() and grep(GNU)"
1975 hg diff "set:added() and grep(GNU)"
1975
1976
1976 - compare a revision and its parents::
1977 - compare a revision and its parents::
1977
1978
1978 hg diff -c 9353 # compare against first parent
1979 hg diff -c 9353 # compare against first parent
1979 hg diff -r 9353^:9353 # same using revset syntax
1980 hg diff -r 9353^:9353 # same using revset syntax
1980 hg diff -r 9353^2:9353 # compare against the second parent
1981 hg diff -r 9353^2:9353 # compare against the second parent
1981
1982
1982 Returns 0 on success.
1983 Returns 0 on success.
1983 """
1984 """
1984
1985
1985 opts = pycompat.byteskwargs(opts)
1986 opts = pycompat.byteskwargs(opts)
1986 revs = opts.get('rev')
1987 revs = opts.get('rev')
1987 change = opts.get('change')
1988 change = opts.get('change')
1988 stat = opts.get('stat')
1989 stat = opts.get('stat')
1989 reverse = opts.get('reverse')
1990 reverse = opts.get('reverse')
1990
1991
1991 if revs and change:
1992 if revs and change:
1992 msg = _('cannot specify --rev and --change at the same time')
1993 msg = _('cannot specify --rev and --change at the same time')
1993 raise error.Abort(msg)
1994 raise error.Abort(msg)
1994 elif change:
1995 elif change:
1995 repo = scmutil.unhidehashlikerevs(repo, [change], 'nowarn')
1996 repo = scmutil.unhidehashlikerevs(repo, [change], 'nowarn')
1996 ctx2 = scmutil.revsingle(repo, change, None)
1997 ctx2 = scmutil.revsingle(repo, change, None)
1997 ctx1 = ctx2.p1()
1998 ctx1 = ctx2.p1()
1998 else:
1999 else:
1999 repo = scmutil.unhidehashlikerevs(repo, revs, 'nowarn')
2000 repo = scmutil.unhidehashlikerevs(repo, revs, 'nowarn')
2000 ctx1, ctx2 = scmutil.revpair(repo, revs)
2001 ctx1, ctx2 = scmutil.revpair(repo, revs)
2001 node1, node2 = ctx1.node(), ctx2.node()
2002 node1, node2 = ctx1.node(), ctx2.node()
2002
2003
2003 if reverse:
2004 if reverse:
2004 node1, node2 = node2, node1
2005 node1, node2 = node2, node1
2005
2006
2006 diffopts = patch.diffallopts(ui, opts)
2007 diffopts = patch.diffallopts(ui, opts)
2007 m = scmutil.match(ctx2, pats, opts)
2008 m = scmutil.match(ctx2, pats, opts)
2008 m = repo.narrowmatch(m)
2009 m = repo.narrowmatch(m)
2009 ui.pager('diff')
2010 ui.pager('diff')
2010 logcmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
2011 logcmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
2011 listsubrepos=opts.get('subrepos'),
2012 listsubrepos=opts.get('subrepos'),
2012 root=opts.get('root'))
2013 root=opts.get('root'))
2013
2014
2014 @command('export',
2015 @command('export',
2015 [('B', 'bookmark', '',
2016 [('B', 'bookmark', '',
2016 _('export changes only reachable by given bookmark'), _('BOOKMARK')),
2017 _('export changes only reachable by given bookmark'), _('BOOKMARK')),
2017 ('o', 'output', '',
2018 ('o', 'output', '',
2018 _('print output to file with formatted name'), _('FORMAT')),
2019 _('print output to file with formatted name'), _('FORMAT')),
2019 ('', 'switch-parent', None, _('diff against the second parent')),
2020 ('', 'switch-parent', None, _('diff against the second parent')),
2020 ('r', 'rev', [], _('revisions to export'), _('REV')),
2021 ('r', 'rev', [], _('revisions to export'), _('REV')),
2021 ] + diffopts + formatteropts,
2022 ] + diffopts + formatteropts,
2022 _('[OPTION]... [-o OUTFILESPEC] [-r] [REV]...'),
2023 _('[OPTION]... [-o OUTFILESPEC] [-r] [REV]...'),
2023 helpcategory=command.CATEGORY_IMPORT_EXPORT,
2024 helpcategory=command.CATEGORY_IMPORT_EXPORT,
2024 helpbasic=True, intents={INTENT_READONLY})
2025 helpbasic=True, intents={INTENT_READONLY})
2025 def export(ui, repo, *changesets, **opts):
2026 def export(ui, repo, *changesets, **opts):
2026 """dump the header and diffs for one or more changesets
2027 """dump the header and diffs for one or more changesets
2027
2028
2028 Print the changeset header and diffs for one or more revisions.
2029 Print the changeset header and diffs for one or more revisions.
2029 If no revision is given, the parent of the working directory is used.
2030 If no revision is given, the parent of the working directory is used.
2030
2031
2031 The information shown in the changeset header is: author, date,
2032 The information shown in the changeset header is: author, date,
2032 branch name (if non-default), changeset hash, parent(s) and commit
2033 branch name (if non-default), changeset hash, parent(s) and commit
2033 comment.
2034 comment.
2034
2035
2035 .. note::
2036 .. note::
2036
2037
2037 :hg:`export` may generate unexpected diff output for merge
2038 :hg:`export` may generate unexpected diff output for merge
2038 changesets, as it will compare the merge changeset against its
2039 changesets, as it will compare the merge changeset against its
2039 first parent only.
2040 first parent only.
2040
2041
2041 Output may be to a file, in which case the name of the file is
2042 Output may be to a file, in which case the name of the file is
2042 given using a template string. See :hg:`help templates`. In addition
2043 given using a template string. See :hg:`help templates`. In addition
2043 to the common template keywords, the following formatting rules are
2044 to the common template keywords, the following formatting rules are
2044 supported:
2045 supported:
2045
2046
2046 :``%%``: literal "%" character
2047 :``%%``: literal "%" character
2047 :``%H``: changeset hash (40 hexadecimal digits)
2048 :``%H``: changeset hash (40 hexadecimal digits)
2048 :``%N``: number of patches being generated
2049 :``%N``: number of patches being generated
2049 :``%R``: changeset revision number
2050 :``%R``: changeset revision number
2050 :``%b``: basename of the exporting repository
2051 :``%b``: basename of the exporting repository
2051 :``%h``: short-form changeset hash (12 hexadecimal digits)
2052 :``%h``: short-form changeset hash (12 hexadecimal digits)
2052 :``%m``: first line of the commit message (only alphanumeric characters)
2053 :``%m``: first line of the commit message (only alphanumeric characters)
2053 :``%n``: zero-padded sequence number, starting at 1
2054 :``%n``: zero-padded sequence number, starting at 1
2054 :``%r``: zero-padded changeset revision number
2055 :``%r``: zero-padded changeset revision number
2055 :``\\``: literal "\\" character
2056 :``\\``: literal "\\" character
2056
2057
2057 Without the -a/--text option, export will avoid generating diffs
2058 Without the -a/--text option, export will avoid generating diffs
2058 of files it detects as binary. With -a, export will generate a
2059 of files it detects as binary. With -a, export will generate a
2059 diff anyway, probably with undesirable results.
2060 diff anyway, probably with undesirable results.
2060
2061
2061 With -B/--bookmark changesets reachable by the given bookmark are
2062 With -B/--bookmark changesets reachable by the given bookmark are
2062 selected.
2063 selected.
2063
2064
2064 Use the -g/--git option to generate diffs in the git extended diff
2065 Use the -g/--git option to generate diffs in the git extended diff
2065 format. See :hg:`help diffs` for more information.
2066 format. See :hg:`help diffs` for more information.
2066
2067
2067 With the --switch-parent option, the diff will be against the
2068 With the --switch-parent option, the diff will be against the
2068 second parent. It can be useful to review a merge.
2069 second parent. It can be useful to review a merge.
2069
2070
2070 .. container:: verbose
2071 .. container:: verbose
2071
2072
2072 Template:
2073 Template:
2073
2074
2074 The following keywords are supported in addition to the common template
2075 The following keywords are supported in addition to the common template
2075 keywords and functions. See also :hg:`help templates`.
2076 keywords and functions. See also :hg:`help templates`.
2076
2077
2077 :diff: String. Diff content.
2078 :diff: String. Diff content.
2078 :parents: List of strings. Parent nodes of the changeset.
2079 :parents: List of strings. Parent nodes of the changeset.
2079
2080
2080 Examples:
2081 Examples:
2081
2082
2082 - use export and import to transplant a bugfix to the current
2083 - use export and import to transplant a bugfix to the current
2083 branch::
2084 branch::
2084
2085
2085 hg export -r 9353 | hg import -
2086 hg export -r 9353 | hg import -
2086
2087
2087 - export all the changesets between two revisions to a file with
2088 - export all the changesets between two revisions to a file with
2088 rename information::
2089 rename information::
2089
2090
2090 hg export --git -r 123:150 > changes.txt
2091 hg export --git -r 123:150 > changes.txt
2091
2092
2092 - split outgoing changes into a series of patches with
2093 - split outgoing changes into a series of patches with
2093 descriptive names::
2094 descriptive names::
2094
2095
2095 hg export -r "outgoing()" -o "%n-%m.patch"
2096 hg export -r "outgoing()" -o "%n-%m.patch"
2096
2097
2097 Returns 0 on success.
2098 Returns 0 on success.
2098 """
2099 """
2099 opts = pycompat.byteskwargs(opts)
2100 opts = pycompat.byteskwargs(opts)
2100 bookmark = opts.get('bookmark')
2101 bookmark = opts.get('bookmark')
2101 changesets += tuple(opts.get('rev', []))
2102 changesets += tuple(opts.get('rev', []))
2102
2103
2103 if bookmark and changesets:
2104 if bookmark and changesets:
2104 raise error.Abort(_("-r and -B are mutually exclusive"))
2105 raise error.Abort(_("-r and -B are mutually exclusive"))
2105
2106
2106 if bookmark:
2107 if bookmark:
2107 if bookmark not in repo._bookmarks:
2108 if bookmark not in repo._bookmarks:
2108 raise error.Abort(_("bookmark '%s' not found") % bookmark)
2109 raise error.Abort(_("bookmark '%s' not found") % bookmark)
2109
2110
2110 revs = scmutil.bookmarkrevs(repo, bookmark)
2111 revs = scmutil.bookmarkrevs(repo, bookmark)
2111 else:
2112 else:
2112 if not changesets:
2113 if not changesets:
2113 changesets = ['.']
2114 changesets = ['.']
2114
2115
2115 repo = scmutil.unhidehashlikerevs(repo, changesets, 'nowarn')
2116 repo = scmutil.unhidehashlikerevs(repo, changesets, 'nowarn')
2116 revs = scmutil.revrange(repo, changesets)
2117 revs = scmutil.revrange(repo, changesets)
2117
2118
2118 if not revs:
2119 if not revs:
2119 raise error.Abort(_("export requires at least one changeset"))
2120 raise error.Abort(_("export requires at least one changeset"))
2120 if len(revs) > 1:
2121 if len(revs) > 1:
2121 ui.note(_('exporting patches:\n'))
2122 ui.note(_('exporting patches:\n'))
2122 else:
2123 else:
2123 ui.note(_('exporting patch:\n'))
2124 ui.note(_('exporting patch:\n'))
2124
2125
2125 fntemplate = opts.get('output')
2126 fntemplate = opts.get('output')
2126 if cmdutil.isstdiofilename(fntemplate):
2127 if cmdutil.isstdiofilename(fntemplate):
2127 fntemplate = ''
2128 fntemplate = ''
2128
2129
2129 if fntemplate:
2130 if fntemplate:
2130 fm = formatter.nullformatter(ui, 'export', opts)
2131 fm = formatter.nullformatter(ui, 'export', opts)
2131 else:
2132 else:
2132 ui.pager('export')
2133 ui.pager('export')
2133 fm = ui.formatter('export', opts)
2134 fm = ui.formatter('export', opts)
2134 with fm:
2135 with fm:
2135 cmdutil.export(repo, revs, fm, fntemplate=fntemplate,
2136 cmdutil.export(repo, revs, fm, fntemplate=fntemplate,
2136 switch_parent=opts.get('switch_parent'),
2137 switch_parent=opts.get('switch_parent'),
2137 opts=patch.diffallopts(ui, opts))
2138 opts=patch.diffallopts(ui, opts))
2138
2139
2139 @command('files',
2140 @command('files',
2140 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
2141 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
2141 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
2142 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
2142 ] + walkopts + formatteropts + subrepoopts,
2143 ] + walkopts + formatteropts + subrepoopts,
2143 _('[OPTION]... [FILE]...'),
2144 _('[OPTION]... [FILE]...'),
2144 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2145 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2145 intents={INTENT_READONLY})
2146 intents={INTENT_READONLY})
2146 def files(ui, repo, *pats, **opts):
2147 def files(ui, repo, *pats, **opts):
2147 """list tracked files
2148 """list tracked files
2148
2149
2149 Print files under Mercurial control in the working directory or
2150 Print files under Mercurial control in the working directory or
2150 specified revision for given files (excluding removed files).
2151 specified revision for given files (excluding removed files).
2151 Files can be specified as filenames or filesets.
2152 Files can be specified as filenames or filesets.
2152
2153
2153 If no files are given to match, this command prints the names
2154 If no files are given to match, this command prints the names
2154 of all files under Mercurial control.
2155 of all files under Mercurial control.
2155
2156
2156 .. container:: verbose
2157 .. container:: verbose
2157
2158
2158 Template:
2159 Template:
2159
2160
2160 The following keywords are supported in addition to the common template
2161 The following keywords are supported in addition to the common template
2161 keywords and functions. See also :hg:`help templates`.
2162 keywords and functions. See also :hg:`help templates`.
2162
2163
2163 :flags: String. Character denoting file's symlink and executable bits.
2164 :flags: String. Character denoting file's symlink and executable bits.
2164 :path: String. Repository-absolute path of the file.
2165 :path: String. Repository-absolute path of the file.
2165 :size: Integer. Size of the file in bytes.
2166 :size: Integer. Size of the file in bytes.
2166
2167
2167 Examples:
2168 Examples:
2168
2169
2169 - list all files under the current directory::
2170 - list all files under the current directory::
2170
2171
2171 hg files .
2172 hg files .
2172
2173
2173 - shows sizes and flags for current revision::
2174 - shows sizes and flags for current revision::
2174
2175
2175 hg files -vr .
2176 hg files -vr .
2176
2177
2177 - list all files named README::
2178 - list all files named README::
2178
2179
2179 hg files -I "**/README"
2180 hg files -I "**/README"
2180
2181
2181 - list all binary files::
2182 - list all binary files::
2182
2183
2183 hg files "set:binary()"
2184 hg files "set:binary()"
2184
2185
2185 - find files containing a regular expression::
2186 - find files containing a regular expression::
2186
2187
2187 hg files "set:grep('bob')"
2188 hg files "set:grep('bob')"
2188
2189
2189 - search tracked file contents with xargs and grep::
2190 - search tracked file contents with xargs and grep::
2190
2191
2191 hg files -0 | xargs -0 grep foo
2192 hg files -0 | xargs -0 grep foo
2192
2193
2193 See :hg:`help patterns` and :hg:`help filesets` for more information
2194 See :hg:`help patterns` and :hg:`help filesets` for more information
2194 on specifying file patterns.
2195 on specifying file patterns.
2195
2196
2196 Returns 0 if a match is found, 1 otherwise.
2197 Returns 0 if a match is found, 1 otherwise.
2197
2198
2198 """
2199 """
2199
2200
2200 opts = pycompat.byteskwargs(opts)
2201 opts = pycompat.byteskwargs(opts)
2201 rev = opts.get('rev')
2202 rev = opts.get('rev')
2202 if rev:
2203 if rev:
2203 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
2204 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
2204 ctx = scmutil.revsingle(repo, rev, None)
2205 ctx = scmutil.revsingle(repo, rev, None)
2205
2206
2206 end = '\n'
2207 end = '\n'
2207 if opts.get('print0'):
2208 if opts.get('print0'):
2208 end = '\0'
2209 end = '\0'
2209 fmt = '%s' + end
2210 fmt = '%s' + end
2210
2211
2211 m = scmutil.match(ctx, pats, opts)
2212 m = scmutil.match(ctx, pats, opts)
2212 ui.pager('files')
2213 ui.pager('files')
2213 with ui.formatter('files', opts) as fm:
2214 with ui.formatter('files', opts) as fm:
2214 return cmdutil.files(ui, ctx, m, fm, fmt, opts.get('subrepos'))
2215 return cmdutil.files(ui, ctx, m, fm, fmt, opts.get('subrepos'))
2215
2216
2216 @command(
2217 @command(
2217 'forget',
2218 'forget',
2218 [('i', 'interactive', None, _('use interactive mode')),
2219 [('i', 'interactive', None, _('use interactive mode')),
2219 ] + walkopts + dryrunopts,
2220 ] + walkopts + dryrunopts,
2220 _('[OPTION]... FILE...'),
2221 _('[OPTION]... FILE...'),
2221 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2222 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
2222 helpbasic=True, inferrepo=True)
2223 helpbasic=True, inferrepo=True)
2223 def forget(ui, repo, *pats, **opts):
2224 def forget(ui, repo, *pats, **opts):
2224 """forget the specified files on the next commit
2225 """forget the specified files on the next commit
2225
2226
2226 Mark the specified files so they will no longer be tracked
2227 Mark the specified files so they will no longer be tracked
2227 after the next commit.
2228 after the next commit.
2228
2229
2229 This only removes files from the current branch, not from the
2230 This only removes files from the current branch, not from the
2230 entire project history, and it does not delete them from the
2231 entire project history, and it does not delete them from the
2231 working directory.
2232 working directory.
2232
2233
2233 To delete the file from the working directory, see :hg:`remove`.
2234 To delete the file from the working directory, see :hg:`remove`.
2234
2235
2235 To undo a forget before the next commit, see :hg:`add`.
2236 To undo a forget before the next commit, see :hg:`add`.
2236
2237
2237 .. container:: verbose
2238 .. container:: verbose
2238
2239
2239 Examples:
2240 Examples:
2240
2241
2241 - forget newly-added binary files::
2242 - forget newly-added binary files::
2242
2243
2243 hg forget "set:added() and binary()"
2244 hg forget "set:added() and binary()"
2244
2245
2245 - forget files that would be excluded by .hgignore::
2246 - forget files that would be excluded by .hgignore::
2246
2247
2247 hg forget "set:hgignore()"
2248 hg forget "set:hgignore()"
2248
2249
2249 Returns 0 on success.
2250 Returns 0 on success.
2250 """
2251 """
2251
2252
2252 opts = pycompat.byteskwargs(opts)
2253 opts = pycompat.byteskwargs(opts)
2253 if not pats:
2254 if not pats:
2254 raise error.Abort(_('no files specified'))
2255 raise error.Abort(_('no files specified'))
2255
2256
2256 m = scmutil.match(repo[None], pats, opts)
2257 m = scmutil.match(repo[None], pats, opts)
2257 dryrun, interactive = opts.get('dry_run'), opts.get('interactive')
2258 dryrun, interactive = opts.get('dry_run'), opts.get('interactive')
2258 rejected = cmdutil.forget(ui, repo, m, prefix="",
2259 rejected = cmdutil.forget(ui, repo, m, prefix="",
2259 explicitonly=False, dryrun=dryrun,
2260 explicitonly=False, dryrun=dryrun,
2260 interactive=interactive)[0]
2261 interactive=interactive)[0]
2261 return rejected and 1 or 0
2262 return rejected and 1 or 0
2262
2263
2263 @command(
2264 @command(
2264 'graft',
2265 'graft',
2265 [('r', 'rev', [], _('revisions to graft'), _('REV')),
2266 [('r', 'rev', [], _('revisions to graft'), _('REV')),
2266 ('', 'base', '',
2267 ('', 'base', '',
2267 _('base revision when doing the graft merge (ADVANCED)'), _('REV')),
2268 _('base revision when doing the graft merge (ADVANCED)'), _('REV')),
2268 ('c', 'continue', False, _('resume interrupted graft')),
2269 ('c', 'continue', False, _('resume interrupted graft')),
2269 ('', 'stop', False, _('stop interrupted graft')),
2270 ('', 'stop', False, _('stop interrupted graft')),
2270 ('', 'abort', False, _('abort interrupted graft')),
2271 ('', 'abort', False, _('abort interrupted graft')),
2271 ('e', 'edit', False, _('invoke editor on commit messages')),
2272 ('e', 'edit', False, _('invoke editor on commit messages')),
2272 ('', 'log', None, _('append graft info to log message')),
2273 ('', 'log', None, _('append graft info to log message')),
2273 ('', 'no-commit', None,
2274 ('', 'no-commit', None,
2274 _("don't commit, just apply the changes in working directory")),
2275 _("don't commit, just apply the changes in working directory")),
2275 ('f', 'force', False, _('force graft')),
2276 ('f', 'force', False, _('force graft')),
2276 ('D', 'currentdate', False,
2277 ('D', 'currentdate', False,
2277 _('record the current date as commit date')),
2278 _('record the current date as commit date')),
2278 ('U', 'currentuser', False,
2279 ('U', 'currentuser', False,
2279 _('record the current user as committer'))]
2280 _('record the current user as committer'))]
2280 + commitopts2 + mergetoolopts + dryrunopts,
2281 + commitopts2 + mergetoolopts + dryrunopts,
2281 _('[OPTION]... [-r REV]... REV...'),
2282 _('[OPTION]... [-r REV]... REV...'),
2282 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT)
2283 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT)
2283 def graft(ui, repo, *revs, **opts):
2284 def graft(ui, repo, *revs, **opts):
2284 '''copy changes from other branches onto the current branch
2285 '''copy changes from other branches onto the current branch
2285
2286
2286 This command uses Mercurial's merge logic to copy individual
2287 This command uses Mercurial's merge logic to copy individual
2287 changes from other branches without merging branches in the
2288 changes from other branches without merging branches in the
2288 history graph. This is sometimes known as 'backporting' or
2289 history graph. This is sometimes known as 'backporting' or
2289 'cherry-picking'. By default, graft will copy user, date, and
2290 'cherry-picking'. By default, graft will copy user, date, and
2290 description from the source changesets.
2291 description from the source changesets.
2291
2292
2292 Changesets that are ancestors of the current revision, that have
2293 Changesets that are ancestors of the current revision, that have
2293 already been grafted, or that are merges will be skipped.
2294 already been grafted, or that are merges will be skipped.
2294
2295
2295 If --log is specified, log messages will have a comment appended
2296 If --log is specified, log messages will have a comment appended
2296 of the form::
2297 of the form::
2297
2298
2298 (grafted from CHANGESETHASH)
2299 (grafted from CHANGESETHASH)
2299
2300
2300 If --force is specified, revisions will be grafted even if they
2301 If --force is specified, revisions will be grafted even if they
2301 are already ancestors of, or have been grafted to, the destination.
2302 are already ancestors of, or have been grafted to, the destination.
2302 This is useful when the revisions have since been backed out.
2303 This is useful when the revisions have since been backed out.
2303
2304
2304 If a graft merge results in conflicts, the graft process is
2305 If a graft merge results in conflicts, the graft process is
2305 interrupted so that the current merge can be manually resolved.
2306 interrupted so that the current merge can be manually resolved.
2306 Once all conflicts are addressed, the graft process can be
2307 Once all conflicts are addressed, the graft process can be
2307 continued with the -c/--continue option.
2308 continued with the -c/--continue option.
2308
2309
2309 The -c/--continue option reapplies all the earlier options.
2310 The -c/--continue option reapplies all the earlier options.
2310
2311
2311 .. container:: verbose
2312 .. container:: verbose
2312
2313
2313 The --base option exposes more of how graft internally uses merge with a
2314 The --base option exposes more of how graft internally uses merge with a
2314 custom base revision. --base can be used to specify another ancestor than
2315 custom base revision. --base can be used to specify another ancestor than
2315 the first and only parent.
2316 the first and only parent.
2316
2317
2317 The command::
2318 The command::
2318
2319
2319 hg graft -r 345 --base 234
2320 hg graft -r 345 --base 234
2320
2321
2321 is thus pretty much the same as::
2322 is thus pretty much the same as::
2322
2323
2323 hg diff -r 234 -r 345 | hg import
2324 hg diff -r 234 -r 345 | hg import
2324
2325
2325 but using merge to resolve conflicts and track moved files.
2326 but using merge to resolve conflicts and track moved files.
2326
2327
2327 The result of a merge can thus be backported as a single commit by
2328 The result of a merge can thus be backported as a single commit by
2328 specifying one of the merge parents as base, and thus effectively
2329 specifying one of the merge parents as base, and thus effectively
2329 grafting the changes from the other side.
2330 grafting the changes from the other side.
2330
2331
2331 It is also possible to collapse multiple changesets and clean up history
2332 It is also possible to collapse multiple changesets and clean up history
2332 by specifying another ancestor as base, much like rebase --collapse
2333 by specifying another ancestor as base, much like rebase --collapse
2333 --keep.
2334 --keep.
2334
2335
2335 The commit message can be tweaked after the fact using commit --amend .
2336 The commit message can be tweaked after the fact using commit --amend .
2336
2337
2337 For using non-ancestors as the base to backout changes, see the backout
2338 For using non-ancestors as the base to backout changes, see the backout
2338 command and the hidden --parent option.
2339 command and the hidden --parent option.
2339
2340
2340 .. container:: verbose
2341 .. container:: verbose
2341
2342
2342 Examples:
2343 Examples:
2343
2344
2344 - copy a single change to the stable branch and edit its description::
2345 - copy a single change to the stable branch and edit its description::
2345
2346
2346 hg update stable
2347 hg update stable
2347 hg graft --edit 9393
2348 hg graft --edit 9393
2348
2349
2349 - graft a range of changesets with one exception, updating dates::
2350 - graft a range of changesets with one exception, updating dates::
2350
2351
2351 hg graft -D "2085::2093 and not 2091"
2352 hg graft -D "2085::2093 and not 2091"
2352
2353
2353 - continue a graft after resolving conflicts::
2354 - continue a graft after resolving conflicts::
2354
2355
2355 hg graft -c
2356 hg graft -c
2356
2357
2357 - show the source of a grafted changeset::
2358 - show the source of a grafted changeset::
2358
2359
2359 hg log --debug -r .
2360 hg log --debug -r .
2360
2361
2361 - show revisions sorted by date::
2362 - show revisions sorted by date::
2362
2363
2363 hg log -r "sort(all(), date)"
2364 hg log -r "sort(all(), date)"
2364
2365
2365 - backport the result of a merge as a single commit::
2366 - backport the result of a merge as a single commit::
2366
2367
2367 hg graft -r 123 --base 123^
2368 hg graft -r 123 --base 123^
2368
2369
2369 - land a feature branch as one changeset::
2370 - land a feature branch as one changeset::
2370
2371
2371 hg up -cr default
2372 hg up -cr default
2372 hg graft -r featureX --base "ancestor('featureX', 'default')"
2373 hg graft -r featureX --base "ancestor('featureX', 'default')"
2373
2374
2374 See :hg:`help revisions` for more about specifying revisions.
2375 See :hg:`help revisions` for more about specifying revisions.
2375
2376
2376 Returns 0 on successful completion.
2377 Returns 0 on successful completion.
2377 '''
2378 '''
2378 with repo.wlock():
2379 with repo.wlock():
2379 return _dograft(ui, repo, *revs, **opts)
2380 return _dograft(ui, repo, *revs, **opts)
2380
2381
2381 def _dograft(ui, repo, *revs, **opts):
2382 def _dograft(ui, repo, *revs, **opts):
2382 opts = pycompat.byteskwargs(opts)
2383 opts = pycompat.byteskwargs(opts)
2383 if revs and opts.get('rev'):
2384 if revs and opts.get('rev'):
2384 ui.warn(_('warning: inconsistent use of --rev might give unexpected '
2385 ui.warn(_('warning: inconsistent use of --rev might give unexpected '
2385 'revision ordering!\n'))
2386 'revision ordering!\n'))
2386
2387
2387 revs = list(revs)
2388 revs = list(revs)
2388 revs.extend(opts.get('rev'))
2389 revs.extend(opts.get('rev'))
2389 basectx = None
2390 basectx = None
2390 if opts.get('base'):
2391 if opts.get('base'):
2391 basectx = scmutil.revsingle(repo, opts['base'], None)
2392 basectx = scmutil.revsingle(repo, opts['base'], None)
2392 # a dict of data to be stored in state file
2393 # a dict of data to be stored in state file
2393 statedata = {}
2394 statedata = {}
2394 # list of new nodes created by ongoing graft
2395 # list of new nodes created by ongoing graft
2395 statedata['newnodes'] = []
2396 statedata['newnodes'] = []
2396
2397
2397 if opts.get('user') and opts.get('currentuser'):
2398 if opts.get('user') and opts.get('currentuser'):
2398 raise error.Abort(_('--user and --currentuser are mutually exclusive'))
2399 raise error.Abort(_('--user and --currentuser are mutually exclusive'))
2399 if opts.get('date') and opts.get('currentdate'):
2400 if opts.get('date') and opts.get('currentdate'):
2400 raise error.Abort(_('--date and --currentdate are mutually exclusive'))
2401 raise error.Abort(_('--date and --currentdate are mutually exclusive'))
2401 if not opts.get('user') and opts.get('currentuser'):
2402 if not opts.get('user') and opts.get('currentuser'):
2402 opts['user'] = ui.username()
2403 opts['user'] = ui.username()
2403 if not opts.get('date') and opts.get('currentdate'):
2404 if not opts.get('date') and opts.get('currentdate'):
2404 opts['date'] = "%d %d" % dateutil.makedate()
2405 opts['date'] = "%d %d" % dateutil.makedate()
2405
2406
2406 editor = cmdutil.getcommiteditor(editform='graft',
2407 editor = cmdutil.getcommiteditor(editform='graft',
2407 **pycompat.strkwargs(opts))
2408 **pycompat.strkwargs(opts))
2408
2409
2409 cont = False
2410 cont = False
2410 if opts.get('no_commit'):
2411 if opts.get('no_commit'):
2411 if opts.get('edit'):
2412 if opts.get('edit'):
2412 raise error.Abort(_("cannot specify --no-commit and "
2413 raise error.Abort(_("cannot specify --no-commit and "
2413 "--edit together"))
2414 "--edit together"))
2414 if opts.get('currentuser'):
2415 if opts.get('currentuser'):
2415 raise error.Abort(_("cannot specify --no-commit and "
2416 raise error.Abort(_("cannot specify --no-commit and "
2416 "--currentuser together"))
2417 "--currentuser together"))
2417 if opts.get('currentdate'):
2418 if opts.get('currentdate'):
2418 raise error.Abort(_("cannot specify --no-commit and "
2419 raise error.Abort(_("cannot specify --no-commit and "
2419 "--currentdate together"))
2420 "--currentdate together"))
2420 if opts.get('log'):
2421 if opts.get('log'):
2421 raise error.Abort(_("cannot specify --no-commit and "
2422 raise error.Abort(_("cannot specify --no-commit and "
2422 "--log together"))
2423 "--log together"))
2423
2424
2424 graftstate = statemod.cmdstate(repo, 'graftstate')
2425 graftstate = statemod.cmdstate(repo, 'graftstate')
2425
2426
2426 if opts.get('stop'):
2427 if opts.get('stop'):
2427 if opts.get('continue'):
2428 if opts.get('continue'):
2428 raise error.Abort(_("cannot use '--continue' and "
2429 raise error.Abort(_("cannot use '--continue' and "
2429 "'--stop' together"))
2430 "'--stop' together"))
2430 if opts.get('abort'):
2431 if opts.get('abort'):
2431 raise error.Abort(_("cannot use '--abort' and '--stop' together"))
2432 raise error.Abort(_("cannot use '--abort' and '--stop' together"))
2432
2433
2433 if any((opts.get('edit'), opts.get('log'), opts.get('user'),
2434 if any((opts.get('edit'), opts.get('log'), opts.get('user'),
2434 opts.get('date'), opts.get('currentdate'),
2435 opts.get('date'), opts.get('currentdate'),
2435 opts.get('currentuser'), opts.get('rev'))):
2436 opts.get('currentuser'), opts.get('rev'))):
2436 raise error.Abort(_("cannot specify any other flag with '--stop'"))
2437 raise error.Abort(_("cannot specify any other flag with '--stop'"))
2437 return _stopgraft(ui, repo, graftstate)
2438 return _stopgraft(ui, repo, graftstate)
2438 elif opts.get('abort'):
2439 elif opts.get('abort'):
2439 if opts.get('continue'):
2440 if opts.get('continue'):
2440 raise error.Abort(_("cannot use '--continue' and "
2441 raise error.Abort(_("cannot use '--continue' and "
2441 "'--abort' together"))
2442 "'--abort' together"))
2442 if any((opts.get('edit'), opts.get('log'), opts.get('user'),
2443 if any((opts.get('edit'), opts.get('log'), opts.get('user'),
2443 opts.get('date'), opts.get('currentdate'),
2444 opts.get('date'), opts.get('currentdate'),
2444 opts.get('currentuser'), opts.get('rev'))):
2445 opts.get('currentuser'), opts.get('rev'))):
2445 raise error.Abort(_("cannot specify any other flag with '--abort'"))
2446 raise error.Abort(_("cannot specify any other flag with '--abort'"))
2446
2447
2447 return _abortgraft(ui, repo, graftstate)
2448 return _abortgraft(ui, repo, graftstate)
2448 elif opts.get('continue'):
2449 elif opts.get('continue'):
2449 cont = True
2450 cont = True
2450 if revs:
2451 if revs:
2451 raise error.Abort(_("can't specify --continue and revisions"))
2452 raise error.Abort(_("can't specify --continue and revisions"))
2452 # read in unfinished revisions
2453 # read in unfinished revisions
2453 if graftstate.exists():
2454 if graftstate.exists():
2454 statedata = _readgraftstate(repo, graftstate)
2455 statedata = _readgraftstate(repo, graftstate)
2455 if statedata.get('date'):
2456 if statedata.get('date'):
2456 opts['date'] = statedata['date']
2457 opts['date'] = statedata['date']
2457 if statedata.get('user'):
2458 if statedata.get('user'):
2458 opts['user'] = statedata['user']
2459 opts['user'] = statedata['user']
2459 if statedata.get('log'):
2460 if statedata.get('log'):
2460 opts['log'] = True
2461 opts['log'] = True
2461 if statedata.get('no_commit'):
2462 if statedata.get('no_commit'):
2462 opts['no_commit'] = statedata.get('no_commit')
2463 opts['no_commit'] = statedata.get('no_commit')
2463 nodes = statedata['nodes']
2464 nodes = statedata['nodes']
2464 revs = [repo[node].rev() for node in nodes]
2465 revs = [repo[node].rev() for node in nodes]
2465 else:
2466 else:
2466 cmdutil.wrongtooltocontinue(repo, _('graft'))
2467 cmdutil.wrongtooltocontinue(repo, _('graft'))
2467 else:
2468 else:
2468 if not revs:
2469 if not revs:
2469 raise error.Abort(_('no revisions specified'))
2470 raise error.Abort(_('no revisions specified'))
2470 cmdutil.checkunfinished(repo)
2471 cmdutil.checkunfinished(repo)
2471 cmdutil.bailifchanged(repo)
2472 cmdutil.bailifchanged(repo)
2472 revs = scmutil.revrange(repo, revs)
2473 revs = scmutil.revrange(repo, revs)
2473
2474
2474 skipped = set()
2475 skipped = set()
2475 if basectx is None:
2476 if basectx is None:
2476 # check for merges
2477 # check for merges
2477 for rev in repo.revs('%ld and merge()', revs):
2478 for rev in repo.revs('%ld and merge()', revs):
2478 ui.warn(_('skipping ungraftable merge revision %d\n') % rev)
2479 ui.warn(_('skipping ungraftable merge revision %d\n') % rev)
2479 skipped.add(rev)
2480 skipped.add(rev)
2480 revs = [r for r in revs if r not in skipped]
2481 revs = [r for r in revs if r not in skipped]
2481 if not revs:
2482 if not revs:
2482 return -1
2483 return -1
2483 if basectx is not None and len(revs) != 1:
2484 if basectx is not None and len(revs) != 1:
2484 raise error.Abort(_('only one revision allowed with --base '))
2485 raise error.Abort(_('only one revision allowed with --base '))
2485
2486
2486 # Don't check in the --continue case, in effect retaining --force across
2487 # Don't check in the --continue case, in effect retaining --force across
2487 # --continues. That's because without --force, any revisions we decided to
2488 # --continues. That's because without --force, any revisions we decided to
2488 # skip would have been filtered out here, so they wouldn't have made their
2489 # skip would have been filtered out here, so they wouldn't have made their
2489 # way to the graftstate. With --force, any revisions we would have otherwise
2490 # way to the graftstate. With --force, any revisions we would have otherwise
2490 # skipped would not have been filtered out, and if they hadn't been applied
2491 # skipped would not have been filtered out, and if they hadn't been applied
2491 # already, they'd have been in the graftstate.
2492 # already, they'd have been in the graftstate.
2492 if not (cont or opts.get('force')) and basectx is None:
2493 if not (cont or opts.get('force')) and basectx is None:
2493 # check for ancestors of dest branch
2494 # check for ancestors of dest branch
2494 crev = repo['.'].rev()
2495 crev = repo['.'].rev()
2495 ancestors = repo.changelog.ancestors([crev], inclusive=True)
2496 ancestors = repo.changelog.ancestors([crev], inclusive=True)
2496 # XXX make this lazy in the future
2497 # XXX make this lazy in the future
2497 # don't mutate while iterating, create a copy
2498 # don't mutate while iterating, create a copy
2498 for rev in list(revs):
2499 for rev in list(revs):
2499 if rev in ancestors:
2500 if rev in ancestors:
2500 ui.warn(_('skipping ancestor revision %d:%s\n') %
2501 ui.warn(_('skipping ancestor revision %d:%s\n') %
2501 (rev, repo[rev]))
2502 (rev, repo[rev]))
2502 # XXX remove on list is slow
2503 # XXX remove on list is slow
2503 revs.remove(rev)
2504 revs.remove(rev)
2504 if not revs:
2505 if not revs:
2505 return -1
2506 return -1
2506
2507
2507 # analyze revs for earlier grafts
2508 # analyze revs for earlier grafts
2508 ids = {}
2509 ids = {}
2509 for ctx in repo.set("%ld", revs):
2510 for ctx in repo.set("%ld", revs):
2510 ids[ctx.hex()] = ctx.rev()
2511 ids[ctx.hex()] = ctx.rev()
2511 n = ctx.extra().get('source')
2512 n = ctx.extra().get('source')
2512 if n:
2513 if n:
2513 ids[n] = ctx.rev()
2514 ids[n] = ctx.rev()
2514
2515
2515 # check ancestors for earlier grafts
2516 # check ancestors for earlier grafts
2516 ui.debug('scanning for duplicate grafts\n')
2517 ui.debug('scanning for duplicate grafts\n')
2517
2518
2518 # The only changesets we can be sure doesn't contain grafts of any
2519 # The only changesets we can be sure doesn't contain grafts of any
2519 # revs, are the ones that are common ancestors of *all* revs:
2520 # revs, are the ones that are common ancestors of *all* revs:
2520 for rev in repo.revs('only(%d,ancestor(%ld))', crev, revs):
2521 for rev in repo.revs('only(%d,ancestor(%ld))', crev, revs):
2521 ctx = repo[rev]
2522 ctx = repo[rev]
2522 n = ctx.extra().get('source')
2523 n = ctx.extra().get('source')
2523 if n in ids:
2524 if n in ids:
2524 try:
2525 try:
2525 r = repo[n].rev()
2526 r = repo[n].rev()
2526 except error.RepoLookupError:
2527 except error.RepoLookupError:
2527 r = None
2528 r = None
2528 if r in revs:
2529 if r in revs:
2529 ui.warn(_('skipping revision %d:%s '
2530 ui.warn(_('skipping revision %d:%s '
2530 '(already grafted to %d:%s)\n')
2531 '(already grafted to %d:%s)\n')
2531 % (r, repo[r], rev, ctx))
2532 % (r, repo[r], rev, ctx))
2532 revs.remove(r)
2533 revs.remove(r)
2533 elif ids[n] in revs:
2534 elif ids[n] in revs:
2534 if r is None:
2535 if r is None:
2535 ui.warn(_('skipping already grafted revision %d:%s '
2536 ui.warn(_('skipping already grafted revision %d:%s '
2536 '(%d:%s also has unknown origin %s)\n')
2537 '(%d:%s also has unknown origin %s)\n')
2537 % (ids[n], repo[ids[n]], rev, ctx, n[:12]))
2538 % (ids[n], repo[ids[n]], rev, ctx, n[:12]))
2538 else:
2539 else:
2539 ui.warn(_('skipping already grafted revision %d:%s '
2540 ui.warn(_('skipping already grafted revision %d:%s '
2540 '(%d:%s also has origin %d:%s)\n')
2541 '(%d:%s also has origin %d:%s)\n')
2541 % (ids[n], repo[ids[n]], rev, ctx, r, n[:12]))
2542 % (ids[n], repo[ids[n]], rev, ctx, r, n[:12]))
2542 revs.remove(ids[n])
2543 revs.remove(ids[n])
2543 elif ctx.hex() in ids:
2544 elif ctx.hex() in ids:
2544 r = ids[ctx.hex()]
2545 r = ids[ctx.hex()]
2545 if r in revs:
2546 if r in revs:
2546 ui.warn(_('skipping already grafted revision %d:%s '
2547 ui.warn(_('skipping already grafted revision %d:%s '
2547 '(was grafted from %d:%s)\n') %
2548 '(was grafted from %d:%s)\n') %
2548 (r, repo[r], rev, ctx))
2549 (r, repo[r], rev, ctx))
2549 revs.remove(r)
2550 revs.remove(r)
2550 if not revs:
2551 if not revs:
2551 return -1
2552 return -1
2552
2553
2553 if opts.get('no_commit'):
2554 if opts.get('no_commit'):
2554 statedata['no_commit'] = True
2555 statedata['no_commit'] = True
2555 for pos, ctx in enumerate(repo.set("%ld", revs)):
2556 for pos, ctx in enumerate(repo.set("%ld", revs)):
2556 desc = '%d:%s "%s"' % (ctx.rev(), ctx,
2557 desc = '%d:%s "%s"' % (ctx.rev(), ctx,
2557 ctx.description().split('\n', 1)[0])
2558 ctx.description().split('\n', 1)[0])
2558 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
2559 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
2559 if names:
2560 if names:
2560 desc += ' (%s)' % ' '.join(names)
2561 desc += ' (%s)' % ' '.join(names)
2561 ui.status(_('grafting %s\n') % desc)
2562 ui.status(_('grafting %s\n') % desc)
2562 if opts.get('dry_run'):
2563 if opts.get('dry_run'):
2563 continue
2564 continue
2564
2565
2565 source = ctx.extra().get('source')
2566 source = ctx.extra().get('source')
2566 extra = {}
2567 extra = {}
2567 if source:
2568 if source:
2568 extra['source'] = source
2569 extra['source'] = source
2569 extra['intermediate-source'] = ctx.hex()
2570 extra['intermediate-source'] = ctx.hex()
2570 else:
2571 else:
2571 extra['source'] = ctx.hex()
2572 extra['source'] = ctx.hex()
2572 user = ctx.user()
2573 user = ctx.user()
2573 if opts.get('user'):
2574 if opts.get('user'):
2574 user = opts['user']
2575 user = opts['user']
2575 statedata['user'] = user
2576 statedata['user'] = user
2576 date = ctx.date()
2577 date = ctx.date()
2577 if opts.get('date'):
2578 if opts.get('date'):
2578 date = opts['date']
2579 date = opts['date']
2579 statedata['date'] = date
2580 statedata['date'] = date
2580 message = ctx.description()
2581 message = ctx.description()
2581 if opts.get('log'):
2582 if opts.get('log'):
2582 message += '\n(grafted from %s)' % ctx.hex()
2583 message += '\n(grafted from %s)' % ctx.hex()
2583 statedata['log'] = True
2584 statedata['log'] = True
2584
2585
2585 # we don't merge the first commit when continuing
2586 # we don't merge the first commit when continuing
2586 if not cont:
2587 if not cont:
2587 # perform the graft merge with p1(rev) as 'ancestor'
2588 # perform the graft merge with p1(rev) as 'ancestor'
2588 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
2589 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
2589 base = ctx.p1() if basectx is None else basectx
2590 base = ctx.p1() if basectx is None else basectx
2590 with ui.configoverride(overrides, 'graft'):
2591 with ui.configoverride(overrides, 'graft'):
2591 stats = mergemod.graft(repo, ctx, base, ['local', 'graft'])
2592 stats = mergemod.graft(repo, ctx, base, ['local', 'graft'])
2592 # report any conflicts
2593 # report any conflicts
2593 if stats.unresolvedcount > 0:
2594 if stats.unresolvedcount > 0:
2594 # write out state for --continue
2595 # write out state for --continue
2595 nodes = [repo[rev].hex() for rev in revs[pos:]]
2596 nodes = [repo[rev].hex() for rev in revs[pos:]]
2596 statedata['nodes'] = nodes
2597 statedata['nodes'] = nodes
2597 stateversion = 1
2598 stateversion = 1
2598 graftstate.save(stateversion, statedata)
2599 graftstate.save(stateversion, statedata)
2599 hint = _("use 'hg resolve' and 'hg graft --continue'")
2600 hint = _("use 'hg resolve' and 'hg graft --continue'")
2600 raise error.Abort(
2601 raise error.Abort(
2601 _("unresolved conflicts, can't continue"),
2602 _("unresolved conflicts, can't continue"),
2602 hint=hint)
2603 hint=hint)
2603 else:
2604 else:
2604 cont = False
2605 cont = False
2605
2606
2606 # commit if --no-commit is false
2607 # commit if --no-commit is false
2607 if not opts.get('no_commit'):
2608 if not opts.get('no_commit'):
2608 node = repo.commit(text=message, user=user, date=date, extra=extra,
2609 node = repo.commit(text=message, user=user, date=date, extra=extra,
2609 editor=editor)
2610 editor=editor)
2610 if node is None:
2611 if node is None:
2611 ui.warn(
2612 ui.warn(
2612 _('note: graft of %d:%s created no changes to commit\n') %
2613 _('note: graft of %d:%s created no changes to commit\n') %
2613 (ctx.rev(), ctx))
2614 (ctx.rev(), ctx))
2614 # checking that newnodes exist because old state files won't have it
2615 # checking that newnodes exist because old state files won't have it
2615 elif statedata.get('newnodes') is not None:
2616 elif statedata.get('newnodes') is not None:
2616 statedata['newnodes'].append(node)
2617 statedata['newnodes'].append(node)
2617
2618
2618 # remove state when we complete successfully
2619 # remove state when we complete successfully
2619 if not opts.get('dry_run'):
2620 if not opts.get('dry_run'):
2620 graftstate.delete()
2621 graftstate.delete()
2621
2622
2622 return 0
2623 return 0
2623
2624
2624 def _abortgraft(ui, repo, graftstate):
2625 def _abortgraft(ui, repo, graftstate):
2625 """abort the interrupted graft and rollbacks to the state before interrupted
2626 """abort the interrupted graft and rollbacks to the state before interrupted
2626 graft"""
2627 graft"""
2627 if not graftstate.exists():
2628 if not graftstate.exists():
2628 raise error.Abort(_("no interrupted graft to abort"))
2629 raise error.Abort(_("no interrupted graft to abort"))
2629 statedata = _readgraftstate(repo, graftstate)
2630 statedata = _readgraftstate(repo, graftstate)
2630 newnodes = statedata.get('newnodes')
2631 newnodes = statedata.get('newnodes')
2631 if newnodes is None:
2632 if newnodes is None:
2632 # and old graft state which does not have all the data required to abort
2633 # and old graft state which does not have all the data required to abort
2633 # the graft
2634 # the graft
2634 raise error.Abort(_("cannot abort using an old graftstate"))
2635 raise error.Abort(_("cannot abort using an old graftstate"))
2635
2636
2636 # changeset from which graft operation was started
2637 # changeset from which graft operation was started
2637 if len(newnodes) > 0:
2638 if len(newnodes) > 0:
2638 startctx = repo[newnodes[0]].p1()
2639 startctx = repo[newnodes[0]].p1()
2639 else:
2640 else:
2640 startctx = repo['.']
2641 startctx = repo['.']
2641 # whether to strip or not
2642 # whether to strip or not
2642 cleanup = False
2643 cleanup = False
2643 if newnodes:
2644 if newnodes:
2644 newnodes = [repo[r].rev() for r in newnodes]
2645 newnodes = [repo[r].rev() for r in newnodes]
2645 cleanup = True
2646 cleanup = True
2646 # checking that none of the newnodes turned public or is public
2647 # checking that none of the newnodes turned public or is public
2647 immutable = [c for c in newnodes if not repo[c].mutable()]
2648 immutable = [c for c in newnodes if not repo[c].mutable()]
2648 if immutable:
2649 if immutable:
2649 repo.ui.warn(_("cannot clean up public changesets %s\n")
2650 repo.ui.warn(_("cannot clean up public changesets %s\n")
2650 % ', '.join(bytes(repo[r]) for r in immutable),
2651 % ', '.join(bytes(repo[r]) for r in immutable),
2651 hint=_("see 'hg help phases' for details"))
2652 hint=_("see 'hg help phases' for details"))
2652 cleanup = False
2653 cleanup = False
2653
2654
2654 # checking that no new nodes are created on top of grafted revs
2655 # checking that no new nodes are created on top of grafted revs
2655 desc = set(repo.changelog.descendants(newnodes))
2656 desc = set(repo.changelog.descendants(newnodes))
2656 if desc - set(newnodes):
2657 if desc - set(newnodes):
2657 repo.ui.warn(_("new changesets detected on destination "
2658 repo.ui.warn(_("new changesets detected on destination "
2658 "branch, can't strip\n"))
2659 "branch, can't strip\n"))
2659 cleanup = False
2660 cleanup = False
2660
2661
2661 if cleanup:
2662 if cleanup:
2662 with repo.wlock(), repo.lock():
2663 with repo.wlock(), repo.lock():
2663 hg.updaterepo(repo, startctx.node(), overwrite=True)
2664 hg.updaterepo(repo, startctx.node(), overwrite=True)
2664 # stripping the new nodes created
2665 # stripping the new nodes created
2665 strippoints = [c.node() for c in repo.set("roots(%ld)",
2666 strippoints = [c.node() for c in repo.set("roots(%ld)",
2666 newnodes)]
2667 newnodes)]
2667 repair.strip(repo.ui, repo, strippoints, backup=False)
2668 repair.strip(repo.ui, repo, strippoints, backup=False)
2668
2669
2669 if not cleanup:
2670 if not cleanup:
2670 # we don't update to the startnode if we can't strip
2671 # we don't update to the startnode if we can't strip
2671 startctx = repo['.']
2672 startctx = repo['.']
2672 hg.updaterepo(repo, startctx.node(), overwrite=True)
2673 hg.updaterepo(repo, startctx.node(), overwrite=True)
2673
2674
2674 ui.status(_("graft aborted\n"))
2675 ui.status(_("graft aborted\n"))
2675 ui.status(_("working directory is now at %s\n") % startctx.hex()[:12])
2676 ui.status(_("working directory is now at %s\n") % startctx.hex()[:12])
2676 graftstate.delete()
2677 graftstate.delete()
2677 return 0
2678 return 0
2678
2679
2679 def _readgraftstate(repo, graftstate):
2680 def _readgraftstate(repo, graftstate):
2680 """read the graft state file and return a dict of the data stored in it"""
2681 """read the graft state file and return a dict of the data stored in it"""
2681 try:
2682 try:
2682 return graftstate.read()
2683 return graftstate.read()
2683 except error.CorruptedState:
2684 except error.CorruptedState:
2684 nodes = repo.vfs.read('graftstate').splitlines()
2685 nodes = repo.vfs.read('graftstate').splitlines()
2685 return {'nodes': nodes}
2686 return {'nodes': nodes}
2686
2687
2687 def _stopgraft(ui, repo, graftstate):
2688 def _stopgraft(ui, repo, graftstate):
2688 """stop the interrupted graft"""
2689 """stop the interrupted graft"""
2689 if not graftstate.exists():
2690 if not graftstate.exists():
2690 raise error.Abort(_("no interrupted graft found"))
2691 raise error.Abort(_("no interrupted graft found"))
2691 pctx = repo['.']
2692 pctx = repo['.']
2692 hg.updaterepo(repo, pctx.node(), overwrite=True)
2693 hg.updaterepo(repo, pctx.node(), overwrite=True)
2693 graftstate.delete()
2694 graftstate.delete()
2694 ui.status(_("stopped the interrupted graft\n"))
2695 ui.status(_("stopped the interrupted graft\n"))
2695 ui.status(_("working directory is now at %s\n") % pctx.hex()[:12])
2696 ui.status(_("working directory is now at %s\n") % pctx.hex()[:12])
2696 return 0
2697 return 0
2697
2698
2698 @command('grep',
2699 @command('grep',
2699 [('0', 'print0', None, _('end fields with NUL')),
2700 [('0', 'print0', None, _('end fields with NUL')),
2700 ('', 'all', None, _('print all revisions that match (DEPRECATED) ')),
2701 ('', 'all', None, _('print all revisions that match (DEPRECATED) ')),
2701 ('', 'diff', None, _('print all revisions when the term was introduced '
2702 ('', 'diff', None, _('print all revisions when the term was introduced '
2702 'or removed')),
2703 'or removed')),
2703 ('a', 'text', None, _('treat all files as text')),
2704 ('a', 'text', None, _('treat all files as text')),
2704 ('f', 'follow', None,
2705 ('f', 'follow', None,
2705 _('follow changeset history,'
2706 _('follow changeset history,'
2706 ' or file history across copies and renames')),
2707 ' or file history across copies and renames')),
2707 ('i', 'ignore-case', None, _('ignore case when matching')),
2708 ('i', 'ignore-case', None, _('ignore case when matching')),
2708 ('l', 'files-with-matches', None,
2709 ('l', 'files-with-matches', None,
2709 _('print only filenames and revisions that match')),
2710 _('print only filenames and revisions that match')),
2710 ('n', 'line-number', None, _('print matching line numbers')),
2711 ('n', 'line-number', None, _('print matching line numbers')),
2711 ('r', 'rev', [],
2712 ('r', 'rev', [],
2712 _('only search files changed within revision range'), _('REV')),
2713 _('only search files changed within revision range'), _('REV')),
2713 ('', 'all-files', None,
2714 ('', 'all-files', None,
2714 _('include all files in the changeset while grepping (EXPERIMENTAL)')),
2715 _('include all files in the changeset while grepping (EXPERIMENTAL)')),
2715 ('u', 'user', None, _('list the author (long with -v)')),
2716 ('u', 'user', None, _('list the author (long with -v)')),
2716 ('d', 'date', None, _('list the date (short with -q)')),
2717 ('d', 'date', None, _('list the date (short with -q)')),
2717 ] + formatteropts + walkopts,
2718 ] + formatteropts + walkopts,
2718 _('[OPTION]... PATTERN [FILE]...'),
2719 _('[OPTION]... PATTERN [FILE]...'),
2719 helpcategory=command.CATEGORY_FILE_CONTENTS,
2720 helpcategory=command.CATEGORY_FILE_CONTENTS,
2720 inferrepo=True,
2721 inferrepo=True,
2721 intents={INTENT_READONLY})
2722 intents={INTENT_READONLY})
2722 def grep(ui, repo, pattern, *pats, **opts):
2723 def grep(ui, repo, pattern, *pats, **opts):
2723 """search revision history for a pattern in specified files
2724 """search revision history for a pattern in specified files
2724
2725
2725 Search revision history for a regular expression in the specified
2726 Search revision history for a regular expression in the specified
2726 files or the entire project.
2727 files or the entire project.
2727
2728
2728 By default, grep prints the most recent revision number for each
2729 By default, grep prints the most recent revision number for each
2729 file in which it finds a match. To get it to print every revision
2730 file in which it finds a match. To get it to print every revision
2730 that contains a change in match status ("-" for a match that becomes
2731 that contains a change in match status ("-" for a match that becomes
2731 a non-match, or "+" for a non-match that becomes a match), use the
2732 a non-match, or "+" for a non-match that becomes a match), use the
2732 --diff flag.
2733 --diff flag.
2733
2734
2734 PATTERN can be any Python (roughly Perl-compatible) regular
2735 PATTERN can be any Python (roughly Perl-compatible) regular
2735 expression.
2736 expression.
2736
2737
2737 If no FILEs are specified (and -f/--follow isn't set), all files in
2738 If no FILEs are specified (and -f/--follow isn't set), all files in
2738 the repository are searched, including those that don't exist in the
2739 the repository are searched, including those that don't exist in the
2739 current branch or have been deleted in a prior changeset.
2740 current branch or have been deleted in a prior changeset.
2740
2741
2741 .. container:: verbose
2742 .. container:: verbose
2742
2743
2743 Template:
2744 Template:
2744
2745
2745 The following keywords are supported in addition to the common template
2746 The following keywords are supported in addition to the common template
2746 keywords and functions. See also :hg:`help templates`.
2747 keywords and functions. See also :hg:`help templates`.
2747
2748
2748 :change: String. Character denoting insertion ``+`` or removal ``-``.
2749 :change: String. Character denoting insertion ``+`` or removal ``-``.
2749 Available if ``--diff`` is specified.
2750 Available if ``--diff`` is specified.
2750 :lineno: Integer. Line number of the match.
2751 :lineno: Integer. Line number of the match.
2751 :path: String. Repository-absolute path of the file.
2752 :path: String. Repository-absolute path of the file.
2752 :texts: List of text chunks.
2753 :texts: List of text chunks.
2753
2754
2754 And each entry of ``{texts}`` provides the following sub-keywords.
2755 And each entry of ``{texts}`` provides the following sub-keywords.
2755
2756
2756 :matched: Boolean. True if the chunk matches the specified pattern.
2757 :matched: Boolean. True if the chunk matches the specified pattern.
2757 :text: String. Chunk content.
2758 :text: String. Chunk content.
2758
2759
2759 See :hg:`help templates.operators` for the list expansion syntax.
2760 See :hg:`help templates.operators` for the list expansion syntax.
2760
2761
2761 Returns 0 if a match is found, 1 otherwise.
2762 Returns 0 if a match is found, 1 otherwise.
2762 """
2763 """
2763 opts = pycompat.byteskwargs(opts)
2764 opts = pycompat.byteskwargs(opts)
2764 diff = opts.get('all') or opts.get('diff')
2765 diff = opts.get('all') or opts.get('diff')
2765 all_files = opts.get('all_files')
2766 all_files = opts.get('all_files')
2766 if diff and opts.get('all_files'):
2767 if diff and opts.get('all_files'):
2767 raise error.Abort(_('--diff and --all-files are mutually exclusive'))
2768 raise error.Abort(_('--diff and --all-files are mutually exclusive'))
2768 # TODO: remove "not opts.get('rev')" if --all-files -rMULTIREV gets working
2769 # TODO: remove "not opts.get('rev')" if --all-files -rMULTIREV gets working
2769 if opts.get('all_files') is None and not opts.get('rev') and not diff:
2770 if opts.get('all_files') is None and not opts.get('rev') and not diff:
2770 # experimental config: commands.grep.all-files
2771 # experimental config: commands.grep.all-files
2771 opts['all_files'] = ui.configbool('commands', 'grep.all-files')
2772 opts['all_files'] = ui.configbool('commands', 'grep.all-files')
2772 plaingrep = opts.get('all_files') and not opts.get('rev')
2773 plaingrep = opts.get('all_files') and not opts.get('rev')
2773 if plaingrep:
2774 if plaingrep:
2774 opts['rev'] = ['wdir()']
2775 opts['rev'] = ['wdir()']
2775
2776
2776 reflags = re.M
2777 reflags = re.M
2777 if opts.get('ignore_case'):
2778 if opts.get('ignore_case'):
2778 reflags |= re.I
2779 reflags |= re.I
2779 try:
2780 try:
2780 regexp = util.re.compile(pattern, reflags)
2781 regexp = util.re.compile(pattern, reflags)
2781 except re.error as inst:
2782 except re.error as inst:
2782 ui.warn(_("grep: invalid match pattern: %s\n") % pycompat.bytestr(inst))
2783 ui.warn(_("grep: invalid match pattern: %s\n") % pycompat.bytestr(inst))
2783 return 1
2784 return 1
2784 sep, eol = ':', '\n'
2785 sep, eol = ':', '\n'
2785 if opts.get('print0'):
2786 if opts.get('print0'):
2786 sep = eol = '\0'
2787 sep = eol = '\0'
2787
2788
2788 getfile = util.lrucachefunc(repo.file)
2789 getfile = util.lrucachefunc(repo.file)
2789
2790
2790 def matchlines(body):
2791 def matchlines(body):
2791 begin = 0
2792 begin = 0
2792 linenum = 0
2793 linenum = 0
2793 while begin < len(body):
2794 while begin < len(body):
2794 match = regexp.search(body, begin)
2795 match = regexp.search(body, begin)
2795 if not match:
2796 if not match:
2796 break
2797 break
2797 mstart, mend = match.span()
2798 mstart, mend = match.span()
2798 linenum += body.count('\n', begin, mstart) + 1
2799 linenum += body.count('\n', begin, mstart) + 1
2799 lstart = body.rfind('\n', begin, mstart) + 1 or begin
2800 lstart = body.rfind('\n', begin, mstart) + 1 or begin
2800 begin = body.find('\n', mend) + 1 or len(body) + 1
2801 begin = body.find('\n', mend) + 1 or len(body) + 1
2801 lend = begin - 1
2802 lend = begin - 1
2802 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
2803 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
2803
2804
2804 class linestate(object):
2805 class linestate(object):
2805 def __init__(self, line, linenum, colstart, colend):
2806 def __init__(self, line, linenum, colstart, colend):
2806 self.line = line
2807 self.line = line
2807 self.linenum = linenum
2808 self.linenum = linenum
2808 self.colstart = colstart
2809 self.colstart = colstart
2809 self.colend = colend
2810 self.colend = colend
2810
2811
2811 def __hash__(self):
2812 def __hash__(self):
2812 return hash((self.linenum, self.line))
2813 return hash((self.linenum, self.line))
2813
2814
2814 def __eq__(self, other):
2815 def __eq__(self, other):
2815 return self.line == other.line
2816 return self.line == other.line
2816
2817
2817 def findpos(self):
2818 def findpos(self):
2818 """Iterate all (start, end) indices of matches"""
2819 """Iterate all (start, end) indices of matches"""
2819 yield self.colstart, self.colend
2820 yield self.colstart, self.colend
2820 p = self.colend
2821 p = self.colend
2821 while p < len(self.line):
2822 while p < len(self.line):
2822 m = regexp.search(self.line, p)
2823 m = regexp.search(self.line, p)
2823 if not m:
2824 if not m:
2824 break
2825 break
2825 yield m.span()
2826 yield m.span()
2826 p = m.end()
2827 p = m.end()
2827
2828
2828 matches = {}
2829 matches = {}
2829 copies = {}
2830 copies = {}
2830 def grepbody(fn, rev, body):
2831 def grepbody(fn, rev, body):
2831 matches[rev].setdefault(fn, [])
2832 matches[rev].setdefault(fn, [])
2832 m = matches[rev][fn]
2833 m = matches[rev][fn]
2833 for lnum, cstart, cend, line in matchlines(body):
2834 for lnum, cstart, cend, line in matchlines(body):
2834 s = linestate(line, lnum, cstart, cend)
2835 s = linestate(line, lnum, cstart, cend)
2835 m.append(s)
2836 m.append(s)
2836
2837
2837 def difflinestates(a, b):
2838 def difflinestates(a, b):
2838 sm = difflib.SequenceMatcher(None, a, b)
2839 sm = difflib.SequenceMatcher(None, a, b)
2839 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
2840 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
2840 if tag == r'insert':
2841 if tag == r'insert':
2841 for i in pycompat.xrange(blo, bhi):
2842 for i in pycompat.xrange(blo, bhi):
2842 yield ('+', b[i])
2843 yield ('+', b[i])
2843 elif tag == r'delete':
2844 elif tag == r'delete':
2844 for i in pycompat.xrange(alo, ahi):
2845 for i in pycompat.xrange(alo, ahi):
2845 yield ('-', a[i])
2846 yield ('-', a[i])
2846 elif tag == r'replace':
2847 elif tag == r'replace':
2847 for i in pycompat.xrange(alo, ahi):
2848 for i in pycompat.xrange(alo, ahi):
2848 yield ('-', a[i])
2849 yield ('-', a[i])
2849 for i in pycompat.xrange(blo, bhi):
2850 for i in pycompat.xrange(blo, bhi):
2850 yield ('+', b[i])
2851 yield ('+', b[i])
2851
2852
2852 uipathfn = scmutil.getuipathfn(repo)
2853 uipathfn = scmutil.getuipathfn(repo)
2853 def display(fm, fn, ctx, pstates, states):
2854 def display(fm, fn, ctx, pstates, states):
2854 rev = scmutil.intrev(ctx)
2855 rev = scmutil.intrev(ctx)
2855 if fm.isplain():
2856 if fm.isplain():
2856 formatuser = ui.shortuser
2857 formatuser = ui.shortuser
2857 else:
2858 else:
2858 formatuser = pycompat.bytestr
2859 formatuser = pycompat.bytestr
2859 if ui.quiet:
2860 if ui.quiet:
2860 datefmt = '%Y-%m-%d'
2861 datefmt = '%Y-%m-%d'
2861 else:
2862 else:
2862 datefmt = '%a %b %d %H:%M:%S %Y %1%2'
2863 datefmt = '%a %b %d %H:%M:%S %Y %1%2'
2863 found = False
2864 found = False
2864 @util.cachefunc
2865 @util.cachefunc
2865 def binary():
2866 def binary():
2866 flog = getfile(fn)
2867 flog = getfile(fn)
2867 try:
2868 try:
2868 return stringutil.binary(flog.read(ctx.filenode(fn)))
2869 return stringutil.binary(flog.read(ctx.filenode(fn)))
2869 except error.WdirUnsupported:
2870 except error.WdirUnsupported:
2870 return ctx[fn].isbinary()
2871 return ctx[fn].isbinary()
2871
2872
2872 fieldnamemap = {'linenumber': 'lineno'}
2873 fieldnamemap = {'linenumber': 'lineno'}
2873 if diff:
2874 if diff:
2874 iter = difflinestates(pstates, states)
2875 iter = difflinestates(pstates, states)
2875 else:
2876 else:
2876 iter = [('', l) for l in states]
2877 iter = [('', l) for l in states]
2877 for change, l in iter:
2878 for change, l in iter:
2878 fm.startitem()
2879 fm.startitem()
2879 fm.context(ctx=ctx)
2880 fm.context(ctx=ctx)
2880 fm.data(node=fm.hexfunc(scmutil.binnode(ctx)), path=fn)
2881 fm.data(node=fm.hexfunc(scmutil.binnode(ctx)), path=fn)
2881 fm.plain(uipathfn(fn), label='grep.filename')
2882 fm.plain(uipathfn(fn), label='grep.filename')
2882
2883
2883 cols = [
2884 cols = [
2884 ('rev', '%d', rev, not plaingrep),
2885 ('rev', '%d', rev, not plaingrep),
2885 ('linenumber', '%d', l.linenum, opts.get('line_number')),
2886 ('linenumber', '%d', l.linenum, opts.get('line_number')),
2886 ]
2887 ]
2887 if diff:
2888 if diff:
2888 cols.append(('change', '%s', change, True))
2889 cols.append(('change', '%s', change, True))
2889 cols.extend([
2890 cols.extend([
2890 ('user', '%s', formatuser(ctx.user()), opts.get('user')),
2891 ('user', '%s', formatuser(ctx.user()), opts.get('user')),
2891 ('date', '%s', fm.formatdate(ctx.date(), datefmt),
2892 ('date', '%s', fm.formatdate(ctx.date(), datefmt),
2892 opts.get('date')),
2893 opts.get('date')),
2893 ])
2894 ])
2894 for name, fmt, data, cond in cols:
2895 for name, fmt, data, cond in cols:
2895 if cond:
2896 if cond:
2896 fm.plain(sep, label='grep.sep')
2897 fm.plain(sep, label='grep.sep')
2897 field = fieldnamemap.get(name, name)
2898 field = fieldnamemap.get(name, name)
2898 fm.condwrite(cond, field, fmt, data, label='grep.%s' % name)
2899 fm.condwrite(cond, field, fmt, data, label='grep.%s' % name)
2899 if not opts.get('files_with_matches'):
2900 if not opts.get('files_with_matches'):
2900 fm.plain(sep, label='grep.sep')
2901 fm.plain(sep, label='grep.sep')
2901 if not opts.get('text') and binary():
2902 if not opts.get('text') and binary():
2902 fm.plain(_(" Binary file matches"))
2903 fm.plain(_(" Binary file matches"))
2903 else:
2904 else:
2904 displaymatches(fm.nested('texts', tmpl='{text}'), l)
2905 displaymatches(fm.nested('texts', tmpl='{text}'), l)
2905 fm.plain(eol)
2906 fm.plain(eol)
2906 found = True
2907 found = True
2907 if opts.get('files_with_matches'):
2908 if opts.get('files_with_matches'):
2908 break
2909 break
2909 return found
2910 return found
2910
2911
2911 def displaymatches(fm, l):
2912 def displaymatches(fm, l):
2912 p = 0
2913 p = 0
2913 for s, e in l.findpos():
2914 for s, e in l.findpos():
2914 if p < s:
2915 if p < s:
2915 fm.startitem()
2916 fm.startitem()
2916 fm.write('text', '%s', l.line[p:s])
2917 fm.write('text', '%s', l.line[p:s])
2917 fm.data(matched=False)
2918 fm.data(matched=False)
2918 fm.startitem()
2919 fm.startitem()
2919 fm.write('text', '%s', l.line[s:e], label='grep.match')
2920 fm.write('text', '%s', l.line[s:e], label='grep.match')
2920 fm.data(matched=True)
2921 fm.data(matched=True)
2921 p = e
2922 p = e
2922 if p < len(l.line):
2923 if p < len(l.line):
2923 fm.startitem()
2924 fm.startitem()
2924 fm.write('text', '%s', l.line[p:])
2925 fm.write('text', '%s', l.line[p:])
2925 fm.data(matched=False)
2926 fm.data(matched=False)
2926 fm.end()
2927 fm.end()
2927
2928
2928 skip = set()
2929 skip = set()
2929 revfiles = {}
2930 revfiles = {}
2930 match = scmutil.match(repo[None], pats, opts)
2931 match = scmutil.match(repo[None], pats, opts)
2931 found = False
2932 found = False
2932 follow = opts.get('follow')
2933 follow = opts.get('follow')
2933
2934
2934 def prep(ctx, fns):
2935 def prep(ctx, fns):
2935 rev = ctx.rev()
2936 rev = ctx.rev()
2936 pctx = ctx.p1()
2937 pctx = ctx.p1()
2937 parent = pctx.rev()
2938 parent = pctx.rev()
2938 matches.setdefault(rev, {})
2939 matches.setdefault(rev, {})
2939 matches.setdefault(parent, {})
2940 matches.setdefault(parent, {})
2940 files = revfiles.setdefault(rev, [])
2941 files = revfiles.setdefault(rev, [])
2941 for fn in fns:
2942 for fn in fns:
2942 flog = getfile(fn)
2943 flog = getfile(fn)
2943 try:
2944 try:
2944 fnode = ctx.filenode(fn)
2945 fnode = ctx.filenode(fn)
2945 except error.LookupError:
2946 except error.LookupError:
2946 continue
2947 continue
2947 copy = None
2948 copy = None
2948 if follow:
2949 if follow:
2949 try:
2950 try:
2950 copied = flog.renamed(fnode)
2951 copied = flog.renamed(fnode)
2951 except error.WdirUnsupported:
2952 except error.WdirUnsupported:
2952 copied = ctx[fn].renamed()
2953 copied = ctx[fn].renamed()
2953 copy = copied and copied[0]
2954 copy = copied and copied[0]
2954 if copy:
2955 if copy:
2955 copies.setdefault(rev, {})[fn] = copy
2956 copies.setdefault(rev, {})[fn] = copy
2956 if fn in skip:
2957 if fn in skip:
2957 skip.add(copy)
2958 skip.add(copy)
2958 if fn in skip:
2959 if fn in skip:
2959 continue
2960 continue
2960 files.append(fn)
2961 files.append(fn)
2961
2962
2962 if fn not in matches[rev]:
2963 if fn not in matches[rev]:
2963 try:
2964 try:
2964 content = flog.read(fnode)
2965 content = flog.read(fnode)
2965 except error.WdirUnsupported:
2966 except error.WdirUnsupported:
2966 content = ctx[fn].data()
2967 content = ctx[fn].data()
2967 grepbody(fn, rev, content)
2968 grepbody(fn, rev, content)
2968
2969
2969 pfn = copy or fn
2970 pfn = copy or fn
2970 if pfn not in matches[parent]:
2971 if pfn not in matches[parent]:
2971 try:
2972 try:
2972 fnode = pctx.filenode(pfn)
2973 fnode = pctx.filenode(pfn)
2973 grepbody(pfn, parent, flog.read(fnode))
2974 grepbody(pfn, parent, flog.read(fnode))
2974 except error.LookupError:
2975 except error.LookupError:
2975 pass
2976 pass
2976
2977
2977 ui.pager('grep')
2978 ui.pager('grep')
2978 fm = ui.formatter('grep', opts)
2979 fm = ui.formatter('grep', opts)
2979 for ctx in cmdutil.walkchangerevs(repo, match, opts, prep):
2980 for ctx in cmdutil.walkchangerevs(repo, match, opts, prep):
2980 rev = ctx.rev()
2981 rev = ctx.rev()
2981 parent = ctx.p1().rev()
2982 parent = ctx.p1().rev()
2982 for fn in sorted(revfiles.get(rev, [])):
2983 for fn in sorted(revfiles.get(rev, [])):
2983 states = matches[rev][fn]
2984 states = matches[rev][fn]
2984 copy = copies.get(rev, {}).get(fn)
2985 copy = copies.get(rev, {}).get(fn)
2985 if fn in skip:
2986 if fn in skip:
2986 if copy:
2987 if copy:
2987 skip.add(copy)
2988 skip.add(copy)
2988 continue
2989 continue
2989 pstates = matches.get(parent, {}).get(copy or fn, [])
2990 pstates = matches.get(parent, {}).get(copy or fn, [])
2990 if pstates or states:
2991 if pstates or states:
2991 r = display(fm, fn, ctx, pstates, states)
2992 r = display(fm, fn, ctx, pstates, states)
2992 found = found or r
2993 found = found or r
2993 if r and not diff and not all_files:
2994 if r and not diff and not all_files:
2994 skip.add(fn)
2995 skip.add(fn)
2995 if copy:
2996 if copy:
2996 skip.add(copy)
2997 skip.add(copy)
2997 del revfiles[rev]
2998 del revfiles[rev]
2998 # We will keep the matches dict for the duration of the window
2999 # We will keep the matches dict for the duration of the window
2999 # clear the matches dict once the window is over
3000 # clear the matches dict once the window is over
3000 if not revfiles:
3001 if not revfiles:
3001 matches.clear()
3002 matches.clear()
3002 fm.end()
3003 fm.end()
3003
3004
3004 return not found
3005 return not found
3005
3006
3006 @command('heads',
3007 @command('heads',
3007 [('r', 'rev', '',
3008 [('r', 'rev', '',
3008 _('show only heads which are descendants of STARTREV'), _('STARTREV')),
3009 _('show only heads which are descendants of STARTREV'), _('STARTREV')),
3009 ('t', 'topo', False, _('show topological heads only')),
3010 ('t', 'topo', False, _('show topological heads only')),
3010 ('a', 'active', False, _('show active branchheads only (DEPRECATED)')),
3011 ('a', 'active', False, _('show active branchheads only (DEPRECATED)')),
3011 ('c', 'closed', False, _('show normal and closed branch heads')),
3012 ('c', 'closed', False, _('show normal and closed branch heads')),
3012 ] + templateopts,
3013 ] + templateopts,
3013 _('[-ct] [-r STARTREV] [REV]...'),
3014 _('[-ct] [-r STARTREV] [REV]...'),
3014 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3015 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3015 intents={INTENT_READONLY})
3016 intents={INTENT_READONLY})
3016 def heads(ui, repo, *branchrevs, **opts):
3017 def heads(ui, repo, *branchrevs, **opts):
3017 """show branch heads
3018 """show branch heads
3018
3019
3019 With no arguments, show all open branch heads in the repository.
3020 With no arguments, show all open branch heads in the repository.
3020 Branch heads are changesets that have no descendants on the
3021 Branch heads are changesets that have no descendants on the
3021 same branch. They are where development generally takes place and
3022 same branch. They are where development generally takes place and
3022 are the usual targets for update and merge operations.
3023 are the usual targets for update and merge operations.
3023
3024
3024 If one or more REVs are given, only open branch heads on the
3025 If one or more REVs are given, only open branch heads on the
3025 branches associated with the specified changesets are shown. This
3026 branches associated with the specified changesets are shown. This
3026 means that you can use :hg:`heads .` to see the heads on the
3027 means that you can use :hg:`heads .` to see the heads on the
3027 currently checked-out branch.
3028 currently checked-out branch.
3028
3029
3029 If -c/--closed is specified, also show branch heads marked closed
3030 If -c/--closed is specified, also show branch heads marked closed
3030 (see :hg:`commit --close-branch`).
3031 (see :hg:`commit --close-branch`).
3031
3032
3032 If STARTREV is specified, only those heads that are descendants of
3033 If STARTREV is specified, only those heads that are descendants of
3033 STARTREV will be displayed.
3034 STARTREV will be displayed.
3034
3035
3035 If -t/--topo is specified, named branch mechanics will be ignored and only
3036 If -t/--topo is specified, named branch mechanics will be ignored and only
3036 topological heads (changesets with no children) will be shown.
3037 topological heads (changesets with no children) will be shown.
3037
3038
3038 Returns 0 if matching heads are found, 1 if not.
3039 Returns 0 if matching heads are found, 1 if not.
3039 """
3040 """
3040
3041
3041 opts = pycompat.byteskwargs(opts)
3042 opts = pycompat.byteskwargs(opts)
3042 start = None
3043 start = None
3043 rev = opts.get('rev')
3044 rev = opts.get('rev')
3044 if rev:
3045 if rev:
3045 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
3046 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
3046 start = scmutil.revsingle(repo, rev, None).node()
3047 start = scmutil.revsingle(repo, rev, None).node()
3047
3048
3048 if opts.get('topo'):
3049 if opts.get('topo'):
3049 heads = [repo[h] for h in repo.heads(start)]
3050 heads = [repo[h] for h in repo.heads(start)]
3050 else:
3051 else:
3051 heads = []
3052 heads = []
3052 for branch in repo.branchmap():
3053 for branch in repo.branchmap():
3053 heads += repo.branchheads(branch, start, opts.get('closed'))
3054 heads += repo.branchheads(branch, start, opts.get('closed'))
3054 heads = [repo[h] for h in heads]
3055 heads = [repo[h] for h in heads]
3055
3056
3056 if branchrevs:
3057 if branchrevs:
3057 branches = set(repo[r].branch()
3058 branches = set(repo[r].branch()
3058 for r in scmutil.revrange(repo, branchrevs))
3059 for r in scmutil.revrange(repo, branchrevs))
3059 heads = [h for h in heads if h.branch() in branches]
3060 heads = [h for h in heads if h.branch() in branches]
3060
3061
3061 if opts.get('active') and branchrevs:
3062 if opts.get('active') and branchrevs:
3062 dagheads = repo.heads(start)
3063 dagheads = repo.heads(start)
3063 heads = [h for h in heads if h.node() in dagheads]
3064 heads = [h for h in heads if h.node() in dagheads]
3064
3065
3065 if branchrevs:
3066 if branchrevs:
3066 haveheads = set(h.branch() for h in heads)
3067 haveheads = set(h.branch() for h in heads)
3067 if branches - haveheads:
3068 if branches - haveheads:
3068 headless = ', '.join(b for b in branches - haveheads)
3069 headless = ', '.join(b for b in branches - haveheads)
3069 msg = _('no open branch heads found on branches %s')
3070 msg = _('no open branch heads found on branches %s')
3070 if opts.get('rev'):
3071 if opts.get('rev'):
3071 msg += _(' (started at %s)') % opts['rev']
3072 msg += _(' (started at %s)') % opts['rev']
3072 ui.warn((msg + '\n') % headless)
3073 ui.warn((msg + '\n') % headless)
3073
3074
3074 if not heads:
3075 if not heads:
3075 return 1
3076 return 1
3076
3077
3077 ui.pager('heads')
3078 ui.pager('heads')
3078 heads = sorted(heads, key=lambda x: -x.rev())
3079 heads = sorted(heads, key=lambda x: -x.rev())
3079 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
3080 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
3080 for ctx in heads:
3081 for ctx in heads:
3081 displayer.show(ctx)
3082 displayer.show(ctx)
3082 displayer.close()
3083 displayer.close()
3083
3084
3084 @command('help',
3085 @command('help',
3085 [('e', 'extension', None, _('show only help for extensions')),
3086 [('e', 'extension', None, _('show only help for extensions')),
3086 ('c', 'command', None, _('show only help for commands')),
3087 ('c', 'command', None, _('show only help for commands')),
3087 ('k', 'keyword', None, _('show topics matching keyword')),
3088 ('k', 'keyword', None, _('show topics matching keyword')),
3088 ('s', 'system', [],
3089 ('s', 'system', [],
3089 _('show help for specific platform(s)'), _('PLATFORM')),
3090 _('show help for specific platform(s)'), _('PLATFORM')),
3090 ],
3091 ],
3091 _('[-eck] [-s PLATFORM] [TOPIC]'),
3092 _('[-eck] [-s PLATFORM] [TOPIC]'),
3092 helpcategory=command.CATEGORY_HELP,
3093 helpcategory=command.CATEGORY_HELP,
3093 norepo=True,
3094 norepo=True,
3094 intents={INTENT_READONLY})
3095 intents={INTENT_READONLY})
3095 def help_(ui, name=None, **opts):
3096 def help_(ui, name=None, **opts):
3096 """show help for a given topic or a help overview
3097 """show help for a given topic or a help overview
3097
3098
3098 With no arguments, print a list of commands with short help messages.
3099 With no arguments, print a list of commands with short help messages.
3099
3100
3100 Given a topic, extension, or command name, print help for that
3101 Given a topic, extension, or command name, print help for that
3101 topic.
3102 topic.
3102
3103
3103 Returns 0 if successful.
3104 Returns 0 if successful.
3104 """
3105 """
3105
3106
3106 keep = opts.get(r'system') or []
3107 keep = opts.get(r'system') or []
3107 if len(keep) == 0:
3108 if len(keep) == 0:
3108 if pycompat.sysplatform.startswith('win'):
3109 if pycompat.sysplatform.startswith('win'):
3109 keep.append('windows')
3110 keep.append('windows')
3110 elif pycompat.sysplatform == 'OpenVMS':
3111 elif pycompat.sysplatform == 'OpenVMS':
3111 keep.append('vms')
3112 keep.append('vms')
3112 elif pycompat.sysplatform == 'plan9':
3113 elif pycompat.sysplatform == 'plan9':
3113 keep.append('plan9')
3114 keep.append('plan9')
3114 else:
3115 else:
3115 keep.append('unix')
3116 keep.append('unix')
3116 keep.append(pycompat.sysplatform.lower())
3117 keep.append(pycompat.sysplatform.lower())
3117 if ui.verbose:
3118 if ui.verbose:
3118 keep.append('verbose')
3119 keep.append('verbose')
3119
3120
3120 commands = sys.modules[__name__]
3121 commands = sys.modules[__name__]
3121 formatted = help.formattedhelp(ui, commands, name, keep=keep, **opts)
3122 formatted = help.formattedhelp(ui, commands, name, keep=keep, **opts)
3122 ui.pager('help')
3123 ui.pager('help')
3123 ui.write(formatted)
3124 ui.write(formatted)
3124
3125
3125
3126
3126 @command('identify|id',
3127 @command('identify|id',
3127 [('r', 'rev', '',
3128 [('r', 'rev', '',
3128 _('identify the specified revision'), _('REV')),
3129 _('identify the specified revision'), _('REV')),
3129 ('n', 'num', None, _('show local revision number')),
3130 ('n', 'num', None, _('show local revision number')),
3130 ('i', 'id', None, _('show global revision id')),
3131 ('i', 'id', None, _('show global revision id')),
3131 ('b', 'branch', None, _('show branch')),
3132 ('b', 'branch', None, _('show branch')),
3132 ('t', 'tags', None, _('show tags')),
3133 ('t', 'tags', None, _('show tags')),
3133 ('B', 'bookmarks', None, _('show bookmarks')),
3134 ('B', 'bookmarks', None, _('show bookmarks')),
3134 ] + remoteopts + formatteropts,
3135 ] + remoteopts + formatteropts,
3135 _('[-nibtB] [-r REV] [SOURCE]'),
3136 _('[-nibtB] [-r REV] [SOURCE]'),
3136 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3137 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3137 optionalrepo=True,
3138 optionalrepo=True,
3138 intents={INTENT_READONLY})
3139 intents={INTENT_READONLY})
3139 def identify(ui, repo, source=None, rev=None,
3140 def identify(ui, repo, source=None, rev=None,
3140 num=None, id=None, branch=None, tags=None, bookmarks=None, **opts):
3141 num=None, id=None, branch=None, tags=None, bookmarks=None, **opts):
3141 """identify the working directory or specified revision
3142 """identify the working directory or specified revision
3142
3143
3143 Print a summary identifying the repository state at REV using one or
3144 Print a summary identifying the repository state at REV using one or
3144 two parent hash identifiers, followed by a "+" if the working
3145 two parent hash identifiers, followed by a "+" if the working
3145 directory has uncommitted changes, the branch name (if not default),
3146 directory has uncommitted changes, the branch name (if not default),
3146 a list of tags, and a list of bookmarks.
3147 a list of tags, and a list of bookmarks.
3147
3148
3148 When REV is not given, print a summary of the current state of the
3149 When REV is not given, print a summary of the current state of the
3149 repository including the working directory. Specify -r. to get information
3150 repository including the working directory. Specify -r. to get information
3150 of the working directory parent without scanning uncommitted changes.
3151 of the working directory parent without scanning uncommitted changes.
3151
3152
3152 Specifying a path to a repository root or Mercurial bundle will
3153 Specifying a path to a repository root or Mercurial bundle will
3153 cause lookup to operate on that repository/bundle.
3154 cause lookup to operate on that repository/bundle.
3154
3155
3155 .. container:: verbose
3156 .. container:: verbose
3156
3157
3157 Template:
3158 Template:
3158
3159
3159 The following keywords are supported in addition to the common template
3160 The following keywords are supported in addition to the common template
3160 keywords and functions. See also :hg:`help templates`.
3161 keywords and functions. See also :hg:`help templates`.
3161
3162
3162 :dirty: String. Character ``+`` denoting if the working directory has
3163 :dirty: String. Character ``+`` denoting if the working directory has
3163 uncommitted changes.
3164 uncommitted changes.
3164 :id: String. One or two nodes, optionally followed by ``+``.
3165 :id: String. One or two nodes, optionally followed by ``+``.
3165 :parents: List of strings. Parent nodes of the changeset.
3166 :parents: List of strings. Parent nodes of the changeset.
3166
3167
3167 Examples:
3168 Examples:
3168
3169
3169 - generate a build identifier for the working directory::
3170 - generate a build identifier for the working directory::
3170
3171
3171 hg id --id > build-id.dat
3172 hg id --id > build-id.dat
3172
3173
3173 - find the revision corresponding to a tag::
3174 - find the revision corresponding to a tag::
3174
3175
3175 hg id -n -r 1.3
3176 hg id -n -r 1.3
3176
3177
3177 - check the most recent revision of a remote repository::
3178 - check the most recent revision of a remote repository::
3178
3179
3179 hg id -r tip https://www.mercurial-scm.org/repo/hg/
3180 hg id -r tip https://www.mercurial-scm.org/repo/hg/
3180
3181
3181 See :hg:`log` for generating more information about specific revisions,
3182 See :hg:`log` for generating more information about specific revisions,
3182 including full hash identifiers.
3183 including full hash identifiers.
3183
3184
3184 Returns 0 if successful.
3185 Returns 0 if successful.
3185 """
3186 """
3186
3187
3187 opts = pycompat.byteskwargs(opts)
3188 opts = pycompat.byteskwargs(opts)
3188 if not repo and not source:
3189 if not repo and not source:
3189 raise error.Abort(_("there is no Mercurial repository here "
3190 raise error.Abort(_("there is no Mercurial repository here "
3190 "(.hg not found)"))
3191 "(.hg not found)"))
3191
3192
3192 default = not (num or id or branch or tags or bookmarks)
3193 default = not (num or id or branch or tags or bookmarks)
3193 output = []
3194 output = []
3194 revs = []
3195 revs = []
3195
3196
3196 if source:
3197 if source:
3197 source, branches = hg.parseurl(ui.expandpath(source))
3198 source, branches = hg.parseurl(ui.expandpath(source))
3198 peer = hg.peer(repo or ui, opts, source) # only pass ui when no repo
3199 peer = hg.peer(repo or ui, opts, source) # only pass ui when no repo
3199 repo = peer.local()
3200 repo = peer.local()
3200 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
3201 revs, checkout = hg.addbranchrevs(repo, peer, branches, None)
3201
3202
3202 fm = ui.formatter('identify', opts)
3203 fm = ui.formatter('identify', opts)
3203 fm.startitem()
3204 fm.startitem()
3204
3205
3205 if not repo:
3206 if not repo:
3206 if num or branch or tags:
3207 if num or branch or tags:
3207 raise error.Abort(
3208 raise error.Abort(
3208 _("can't query remote revision number, branch, or tags"))
3209 _("can't query remote revision number, branch, or tags"))
3209 if not rev and revs:
3210 if not rev and revs:
3210 rev = revs[0]
3211 rev = revs[0]
3211 if not rev:
3212 if not rev:
3212 rev = "tip"
3213 rev = "tip"
3213
3214
3214 remoterev = peer.lookup(rev)
3215 remoterev = peer.lookup(rev)
3215 hexrev = fm.hexfunc(remoterev)
3216 hexrev = fm.hexfunc(remoterev)
3216 if default or id:
3217 if default or id:
3217 output = [hexrev]
3218 output = [hexrev]
3218 fm.data(id=hexrev)
3219 fm.data(id=hexrev)
3219
3220
3220 @util.cachefunc
3221 @util.cachefunc
3221 def getbms():
3222 def getbms():
3222 bms = []
3223 bms = []
3223
3224
3224 if 'bookmarks' in peer.listkeys('namespaces'):
3225 if 'bookmarks' in peer.listkeys('namespaces'):
3225 hexremoterev = hex(remoterev)
3226 hexremoterev = hex(remoterev)
3226 bms = [bm for bm, bmr in peer.listkeys('bookmarks').iteritems()
3227 bms = [bm for bm, bmr in peer.listkeys('bookmarks').iteritems()
3227 if bmr == hexremoterev]
3228 if bmr == hexremoterev]
3228
3229
3229 return sorted(bms)
3230 return sorted(bms)
3230
3231
3231 if fm.isplain():
3232 if fm.isplain():
3232 if bookmarks:
3233 if bookmarks:
3233 output.extend(getbms())
3234 output.extend(getbms())
3234 elif default and not ui.quiet:
3235 elif default and not ui.quiet:
3235 # multiple bookmarks for a single parent separated by '/'
3236 # multiple bookmarks for a single parent separated by '/'
3236 bm = '/'.join(getbms())
3237 bm = '/'.join(getbms())
3237 if bm:
3238 if bm:
3238 output.append(bm)
3239 output.append(bm)
3239 else:
3240 else:
3240 fm.data(node=hex(remoterev))
3241 fm.data(node=hex(remoterev))
3241 if bookmarks or 'bookmarks' in fm.datahint():
3242 if bookmarks or 'bookmarks' in fm.datahint():
3242 fm.data(bookmarks=fm.formatlist(getbms(), name='bookmark'))
3243 fm.data(bookmarks=fm.formatlist(getbms(), name='bookmark'))
3243 else:
3244 else:
3244 if rev:
3245 if rev:
3245 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
3246 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
3246 ctx = scmutil.revsingle(repo, rev, None)
3247 ctx = scmutil.revsingle(repo, rev, None)
3247
3248
3248 if ctx.rev() is None:
3249 if ctx.rev() is None:
3249 ctx = repo[None]
3250 ctx = repo[None]
3250 parents = ctx.parents()
3251 parents = ctx.parents()
3251 taglist = []
3252 taglist = []
3252 for p in parents:
3253 for p in parents:
3253 taglist.extend(p.tags())
3254 taglist.extend(p.tags())
3254
3255
3255 dirty = ""
3256 dirty = ""
3256 if ctx.dirty(missing=True, merge=False, branch=False):
3257 if ctx.dirty(missing=True, merge=False, branch=False):
3257 dirty = '+'
3258 dirty = '+'
3258 fm.data(dirty=dirty)
3259 fm.data(dirty=dirty)
3259
3260
3260 hexoutput = [fm.hexfunc(p.node()) for p in parents]
3261 hexoutput = [fm.hexfunc(p.node()) for p in parents]
3261 if default or id:
3262 if default or id:
3262 output = ["%s%s" % ('+'.join(hexoutput), dirty)]
3263 output = ["%s%s" % ('+'.join(hexoutput), dirty)]
3263 fm.data(id="%s%s" % ('+'.join(hexoutput), dirty))
3264 fm.data(id="%s%s" % ('+'.join(hexoutput), dirty))
3264
3265
3265 if num:
3266 if num:
3266 numoutput = ["%d" % p.rev() for p in parents]
3267 numoutput = ["%d" % p.rev() for p in parents]
3267 output.append("%s%s" % ('+'.join(numoutput), dirty))
3268 output.append("%s%s" % ('+'.join(numoutput), dirty))
3268
3269
3269 fm.data(parents=fm.formatlist([fm.hexfunc(p.node())
3270 fm.data(parents=fm.formatlist([fm.hexfunc(p.node())
3270 for p in parents], name='node'))
3271 for p in parents], name='node'))
3271 else:
3272 else:
3272 hexoutput = fm.hexfunc(ctx.node())
3273 hexoutput = fm.hexfunc(ctx.node())
3273 if default or id:
3274 if default or id:
3274 output = [hexoutput]
3275 output = [hexoutput]
3275 fm.data(id=hexoutput)
3276 fm.data(id=hexoutput)
3276
3277
3277 if num:
3278 if num:
3278 output.append(pycompat.bytestr(ctx.rev()))
3279 output.append(pycompat.bytestr(ctx.rev()))
3279 taglist = ctx.tags()
3280 taglist = ctx.tags()
3280
3281
3281 if default and not ui.quiet:
3282 if default and not ui.quiet:
3282 b = ctx.branch()
3283 b = ctx.branch()
3283 if b != 'default':
3284 if b != 'default':
3284 output.append("(%s)" % b)
3285 output.append("(%s)" % b)
3285
3286
3286 # multiple tags for a single parent separated by '/'
3287 # multiple tags for a single parent separated by '/'
3287 t = '/'.join(taglist)
3288 t = '/'.join(taglist)
3288 if t:
3289 if t:
3289 output.append(t)
3290 output.append(t)
3290
3291
3291 # multiple bookmarks for a single parent separated by '/'
3292 # multiple bookmarks for a single parent separated by '/'
3292 bm = '/'.join(ctx.bookmarks())
3293 bm = '/'.join(ctx.bookmarks())
3293 if bm:
3294 if bm:
3294 output.append(bm)
3295 output.append(bm)
3295 else:
3296 else:
3296 if branch:
3297 if branch:
3297 output.append(ctx.branch())
3298 output.append(ctx.branch())
3298
3299
3299 if tags:
3300 if tags:
3300 output.extend(taglist)
3301 output.extend(taglist)
3301
3302
3302 if bookmarks:
3303 if bookmarks:
3303 output.extend(ctx.bookmarks())
3304 output.extend(ctx.bookmarks())
3304
3305
3305 fm.data(node=ctx.hex())
3306 fm.data(node=ctx.hex())
3306 fm.data(branch=ctx.branch())
3307 fm.data(branch=ctx.branch())
3307 fm.data(tags=fm.formatlist(taglist, name='tag', sep=':'))
3308 fm.data(tags=fm.formatlist(taglist, name='tag', sep=':'))
3308 fm.data(bookmarks=fm.formatlist(ctx.bookmarks(), name='bookmark'))
3309 fm.data(bookmarks=fm.formatlist(ctx.bookmarks(), name='bookmark'))
3309 fm.context(ctx=ctx)
3310 fm.context(ctx=ctx)
3310
3311
3311 fm.plain("%s\n" % ' '.join(output))
3312 fm.plain("%s\n" % ' '.join(output))
3312 fm.end()
3313 fm.end()
3313
3314
3314 @command('import|patch',
3315 @command('import|patch',
3315 [('p', 'strip', 1,
3316 [('p', 'strip', 1,
3316 _('directory strip option for patch. This has the same '
3317 _('directory strip option for patch. This has the same '
3317 'meaning as the corresponding patch option'), _('NUM')),
3318 'meaning as the corresponding patch option'), _('NUM')),
3318 ('b', 'base', '', _('base path (DEPRECATED)'), _('PATH')),
3319 ('b', 'base', '', _('base path (DEPRECATED)'), _('PATH')),
3319 ('e', 'edit', False, _('invoke editor on commit messages')),
3320 ('e', 'edit', False, _('invoke editor on commit messages')),
3320 ('f', 'force', None,
3321 ('f', 'force', None,
3321 _('skip check for outstanding uncommitted changes (DEPRECATED)')),
3322 _('skip check for outstanding uncommitted changes (DEPRECATED)')),
3322 ('', 'no-commit', None,
3323 ('', 'no-commit', None,
3323 _("don't commit, just update the working directory")),
3324 _("don't commit, just update the working directory")),
3324 ('', 'bypass', None,
3325 ('', 'bypass', None,
3325 _("apply patch without touching the working directory")),
3326 _("apply patch without touching the working directory")),
3326 ('', 'partial', None,
3327 ('', 'partial', None,
3327 _('commit even if some hunks fail')),
3328 _('commit even if some hunks fail')),
3328 ('', 'exact', None,
3329 ('', 'exact', None,
3329 _('abort if patch would apply lossily')),
3330 _('abort if patch would apply lossily')),
3330 ('', 'prefix', '',
3331 ('', 'prefix', '',
3331 _('apply patch to subdirectory'), _('DIR')),
3332 _('apply patch to subdirectory'), _('DIR')),
3332 ('', 'import-branch', None,
3333 ('', 'import-branch', None,
3333 _('use any branch information in patch (implied by --exact)'))] +
3334 _('use any branch information in patch (implied by --exact)'))] +
3334 commitopts + commitopts2 + similarityopts,
3335 commitopts + commitopts2 + similarityopts,
3335 _('[OPTION]... PATCH...'),
3336 _('[OPTION]... PATCH...'),
3336 helpcategory=command.CATEGORY_IMPORT_EXPORT)
3337 helpcategory=command.CATEGORY_IMPORT_EXPORT)
3337 def import_(ui, repo, patch1=None, *patches, **opts):
3338 def import_(ui, repo, patch1=None, *patches, **opts):
3338 """import an ordered set of patches
3339 """import an ordered set of patches
3339
3340
3340 Import a list of patches and commit them individually (unless
3341 Import a list of patches and commit them individually (unless
3341 --no-commit is specified).
3342 --no-commit is specified).
3342
3343
3343 To read a patch from standard input (stdin), use "-" as the patch
3344 To read a patch from standard input (stdin), use "-" as the patch
3344 name. If a URL is specified, the patch will be downloaded from
3345 name. If a URL is specified, the patch will be downloaded from
3345 there.
3346 there.
3346
3347
3347 Import first applies changes to the working directory (unless
3348 Import first applies changes to the working directory (unless
3348 --bypass is specified), import will abort if there are outstanding
3349 --bypass is specified), import will abort if there are outstanding
3349 changes.
3350 changes.
3350
3351
3351 Use --bypass to apply and commit patches directly to the
3352 Use --bypass to apply and commit patches directly to the
3352 repository, without affecting the working directory. Without
3353 repository, without affecting the working directory. Without
3353 --exact, patches will be applied on top of the working directory
3354 --exact, patches will be applied on top of the working directory
3354 parent revision.
3355 parent revision.
3355
3356
3356 You can import a patch straight from a mail message. Even patches
3357 You can import a patch straight from a mail message. Even patches
3357 as attachments work (to use the body part, it must have type
3358 as attachments work (to use the body part, it must have type
3358 text/plain or text/x-patch). From and Subject headers of email
3359 text/plain or text/x-patch). From and Subject headers of email
3359 message are used as default committer and commit message. All
3360 message are used as default committer and commit message. All
3360 text/plain body parts before first diff are added to the commit
3361 text/plain body parts before first diff are added to the commit
3361 message.
3362 message.
3362
3363
3363 If the imported patch was generated by :hg:`export`, user and
3364 If the imported patch was generated by :hg:`export`, user and
3364 description from patch override values from message headers and
3365 description from patch override values from message headers and
3365 body. Values given on command line with -m/--message and -u/--user
3366 body. Values given on command line with -m/--message and -u/--user
3366 override these.
3367 override these.
3367
3368
3368 If --exact is specified, import will set the working directory to
3369 If --exact is specified, import will set the working directory to
3369 the parent of each patch before applying it, and will abort if the
3370 the parent of each patch before applying it, and will abort if the
3370 resulting changeset has a different ID than the one recorded in
3371 resulting changeset has a different ID than the one recorded in
3371 the patch. This will guard against various ways that portable
3372 the patch. This will guard against various ways that portable
3372 patch formats and mail systems might fail to transfer Mercurial
3373 patch formats and mail systems might fail to transfer Mercurial
3373 data or metadata. See :hg:`bundle` for lossless transmission.
3374 data or metadata. See :hg:`bundle` for lossless transmission.
3374
3375
3375 Use --partial to ensure a changeset will be created from the patch
3376 Use --partial to ensure a changeset will be created from the patch
3376 even if some hunks fail to apply. Hunks that fail to apply will be
3377 even if some hunks fail to apply. Hunks that fail to apply will be
3377 written to a <target-file>.rej file. Conflicts can then be resolved
3378 written to a <target-file>.rej file. Conflicts can then be resolved
3378 by hand before :hg:`commit --amend` is run to update the created
3379 by hand before :hg:`commit --amend` is run to update the created
3379 changeset. This flag exists to let people import patches that
3380 changeset. This flag exists to let people import patches that
3380 partially apply without losing the associated metadata (author,
3381 partially apply without losing the associated metadata (author,
3381 date, description, ...).
3382 date, description, ...).
3382
3383
3383 .. note::
3384 .. note::
3384
3385
3385 When no hunks apply cleanly, :hg:`import --partial` will create
3386 When no hunks apply cleanly, :hg:`import --partial` will create
3386 an empty changeset, importing only the patch metadata.
3387 an empty changeset, importing only the patch metadata.
3387
3388
3388 With -s/--similarity, hg will attempt to discover renames and
3389 With -s/--similarity, hg will attempt to discover renames and
3389 copies in the patch in the same way as :hg:`addremove`.
3390 copies in the patch in the same way as :hg:`addremove`.
3390
3391
3391 It is possible to use external patch programs to perform the patch
3392 It is possible to use external patch programs to perform the patch
3392 by setting the ``ui.patch`` configuration option. For the default
3393 by setting the ``ui.patch`` configuration option. For the default
3393 internal tool, the fuzz can also be configured via ``patch.fuzz``.
3394 internal tool, the fuzz can also be configured via ``patch.fuzz``.
3394 See :hg:`help config` for more information about configuration
3395 See :hg:`help config` for more information about configuration
3395 files and how to use these options.
3396 files and how to use these options.
3396
3397
3397 See :hg:`help dates` for a list of formats valid for -d/--date.
3398 See :hg:`help dates` for a list of formats valid for -d/--date.
3398
3399
3399 .. container:: verbose
3400 .. container:: verbose
3400
3401
3401 Examples:
3402 Examples:
3402
3403
3403 - import a traditional patch from a website and detect renames::
3404 - import a traditional patch from a website and detect renames::
3404
3405
3405 hg import -s 80 http://example.com/bugfix.patch
3406 hg import -s 80 http://example.com/bugfix.patch
3406
3407
3407 - import a changeset from an hgweb server::
3408 - import a changeset from an hgweb server::
3408
3409
3409 hg import https://www.mercurial-scm.org/repo/hg/rev/5ca8c111e9aa
3410 hg import https://www.mercurial-scm.org/repo/hg/rev/5ca8c111e9aa
3410
3411
3411 - import all the patches in an Unix-style mbox::
3412 - import all the patches in an Unix-style mbox::
3412
3413
3413 hg import incoming-patches.mbox
3414 hg import incoming-patches.mbox
3414
3415
3415 - import patches from stdin::
3416 - import patches from stdin::
3416
3417
3417 hg import -
3418 hg import -
3418
3419
3419 - attempt to exactly restore an exported changeset (not always
3420 - attempt to exactly restore an exported changeset (not always
3420 possible)::
3421 possible)::
3421
3422
3422 hg import --exact proposed-fix.patch
3423 hg import --exact proposed-fix.patch
3423
3424
3424 - use an external tool to apply a patch which is too fuzzy for
3425 - use an external tool to apply a patch which is too fuzzy for
3425 the default internal tool.
3426 the default internal tool.
3426
3427
3427 hg import --config ui.patch="patch --merge" fuzzy.patch
3428 hg import --config ui.patch="patch --merge" fuzzy.patch
3428
3429
3429 - change the default fuzzing from 2 to a less strict 7
3430 - change the default fuzzing from 2 to a less strict 7
3430
3431
3431 hg import --config ui.fuzz=7 fuzz.patch
3432 hg import --config ui.fuzz=7 fuzz.patch
3432
3433
3433 Returns 0 on success, 1 on partial success (see --partial).
3434 Returns 0 on success, 1 on partial success (see --partial).
3434 """
3435 """
3435
3436
3436 opts = pycompat.byteskwargs(opts)
3437 opts = pycompat.byteskwargs(opts)
3437 if not patch1:
3438 if not patch1:
3438 raise error.Abort(_('need at least one patch to import'))
3439 raise error.Abort(_('need at least one patch to import'))
3439
3440
3440 patches = (patch1,) + patches
3441 patches = (patch1,) + patches
3441
3442
3442 date = opts.get('date')
3443 date = opts.get('date')
3443 if date:
3444 if date:
3444 opts['date'] = dateutil.parsedate(date)
3445 opts['date'] = dateutil.parsedate(date)
3445
3446
3446 exact = opts.get('exact')
3447 exact = opts.get('exact')
3447 update = not opts.get('bypass')
3448 update = not opts.get('bypass')
3448 if not update and opts.get('no_commit'):
3449 if not update and opts.get('no_commit'):
3449 raise error.Abort(_('cannot use --no-commit with --bypass'))
3450 raise error.Abort(_('cannot use --no-commit with --bypass'))
3450 try:
3451 try:
3451 sim = float(opts.get('similarity') or 0)
3452 sim = float(opts.get('similarity') or 0)
3452 except ValueError:
3453 except ValueError:
3453 raise error.Abort(_('similarity must be a number'))
3454 raise error.Abort(_('similarity must be a number'))
3454 if sim < 0 or sim > 100:
3455 if sim < 0 or sim > 100:
3455 raise error.Abort(_('similarity must be between 0 and 100'))
3456 raise error.Abort(_('similarity must be between 0 and 100'))
3456 if sim and not update:
3457 if sim and not update:
3457 raise error.Abort(_('cannot use --similarity with --bypass'))
3458 raise error.Abort(_('cannot use --similarity with --bypass'))
3458 if exact:
3459 if exact:
3459 if opts.get('edit'):
3460 if opts.get('edit'):
3460 raise error.Abort(_('cannot use --exact with --edit'))
3461 raise error.Abort(_('cannot use --exact with --edit'))
3461 if opts.get('prefix'):
3462 if opts.get('prefix'):
3462 raise error.Abort(_('cannot use --exact with --prefix'))
3463 raise error.Abort(_('cannot use --exact with --prefix'))
3463
3464
3464 base = opts["base"]
3465 base = opts["base"]
3465 msgs = []
3466 msgs = []
3466 ret = 0
3467 ret = 0
3467
3468
3468 with repo.wlock():
3469 with repo.wlock():
3469 if update:
3470 if update:
3470 cmdutil.checkunfinished(repo)
3471 cmdutil.checkunfinished(repo)
3471 if (exact or not opts.get('force')):
3472 if (exact or not opts.get('force')):
3472 cmdutil.bailifchanged(repo)
3473 cmdutil.bailifchanged(repo)
3473
3474
3474 if not opts.get('no_commit'):
3475 if not opts.get('no_commit'):
3475 lock = repo.lock
3476 lock = repo.lock
3476 tr = lambda: repo.transaction('import')
3477 tr = lambda: repo.transaction('import')
3477 dsguard = util.nullcontextmanager
3478 dsguard = util.nullcontextmanager
3478 else:
3479 else:
3479 lock = util.nullcontextmanager
3480 lock = util.nullcontextmanager
3480 tr = util.nullcontextmanager
3481 tr = util.nullcontextmanager
3481 dsguard = lambda: dirstateguard.dirstateguard(repo, 'import')
3482 dsguard = lambda: dirstateguard.dirstateguard(repo, 'import')
3482 with lock(), tr(), dsguard():
3483 with lock(), tr(), dsguard():
3483 parents = repo[None].parents()
3484 parents = repo[None].parents()
3484 for patchurl in patches:
3485 for patchurl in patches:
3485 if patchurl == '-':
3486 if patchurl == '-':
3486 ui.status(_('applying patch from stdin\n'))
3487 ui.status(_('applying patch from stdin\n'))
3487 patchfile = ui.fin
3488 patchfile = ui.fin
3488 patchurl = 'stdin' # for error message
3489 patchurl = 'stdin' # for error message
3489 else:
3490 else:
3490 patchurl = os.path.join(base, patchurl)
3491 patchurl = os.path.join(base, patchurl)
3491 ui.status(_('applying %s\n') % patchurl)
3492 ui.status(_('applying %s\n') % patchurl)
3492 patchfile = hg.openpath(ui, patchurl)
3493 patchfile = hg.openpath(ui, patchurl)
3493
3494
3494 haspatch = False
3495 haspatch = False
3495 for hunk in patch.split(patchfile):
3496 for hunk in patch.split(patchfile):
3496 with patch.extract(ui, hunk) as patchdata:
3497 with patch.extract(ui, hunk) as patchdata:
3497 msg, node, rej = cmdutil.tryimportone(ui, repo,
3498 msg, node, rej = cmdutil.tryimportone(ui, repo,
3498 patchdata,
3499 patchdata,
3499 parents, opts,
3500 parents, opts,
3500 msgs, hg.clean)
3501 msgs, hg.clean)
3501 if msg:
3502 if msg:
3502 haspatch = True
3503 haspatch = True
3503 ui.note(msg + '\n')
3504 ui.note(msg + '\n')
3504 if update or exact:
3505 if update or exact:
3505 parents = repo[None].parents()
3506 parents = repo[None].parents()
3506 else:
3507 else:
3507 parents = [repo[node]]
3508 parents = [repo[node]]
3508 if rej:
3509 if rej:
3509 ui.write_err(_("patch applied partially\n"))
3510 ui.write_err(_("patch applied partially\n"))
3510 ui.write_err(_("(fix the .rej files and run "
3511 ui.write_err(_("(fix the .rej files and run "
3511 "`hg commit --amend`)\n"))
3512 "`hg commit --amend`)\n"))
3512 ret = 1
3513 ret = 1
3513 break
3514 break
3514
3515
3515 if not haspatch:
3516 if not haspatch:
3516 raise error.Abort(_('%s: no diffs found') % patchurl)
3517 raise error.Abort(_('%s: no diffs found') % patchurl)
3517
3518
3518 if msgs:
3519 if msgs:
3519 repo.savecommitmessage('\n* * *\n'.join(msgs))
3520 repo.savecommitmessage('\n* * *\n'.join(msgs))
3520 return ret
3521 return ret
3521
3522
3522 @command('incoming|in',
3523 @command('incoming|in',
3523 [('f', 'force', None,
3524 [('f', 'force', None,
3524 _('run even if remote repository is unrelated')),
3525 _('run even if remote repository is unrelated')),
3525 ('n', 'newest-first', None, _('show newest record first')),
3526 ('n', 'newest-first', None, _('show newest record first')),
3526 ('', 'bundle', '',
3527 ('', 'bundle', '',
3527 _('file to store the bundles into'), _('FILE')),
3528 _('file to store the bundles into'), _('FILE')),
3528 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
3529 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
3529 ('B', 'bookmarks', False, _("compare bookmarks")),
3530 ('B', 'bookmarks', False, _("compare bookmarks")),
3530 ('b', 'branch', [],
3531 ('b', 'branch', [],
3531 _('a specific branch you would like to pull'), _('BRANCH')),
3532 _('a specific branch you would like to pull'), _('BRANCH')),
3532 ] + logopts + remoteopts + subrepoopts,
3533 ] + logopts + remoteopts + subrepoopts,
3533 _('[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'),
3534 _('[-p] [-n] [-M] [-f] [-r REV]... [--bundle FILENAME] [SOURCE]'),
3534 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT)
3535 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT)
3535 def incoming(ui, repo, source="default", **opts):
3536 def incoming(ui, repo, source="default", **opts):
3536 """show new changesets found in source
3537 """show new changesets found in source
3537
3538
3538 Show new changesets found in the specified path/URL or the default
3539 Show new changesets found in the specified path/URL or the default
3539 pull location. These are the changesets that would have been pulled
3540 pull location. These are the changesets that would have been pulled
3540 by :hg:`pull` at the time you issued this command.
3541 by :hg:`pull` at the time you issued this command.
3541
3542
3542 See pull for valid source format details.
3543 See pull for valid source format details.
3543
3544
3544 .. container:: verbose
3545 .. container:: verbose
3545
3546
3546 With -B/--bookmarks, the result of bookmark comparison between
3547 With -B/--bookmarks, the result of bookmark comparison between
3547 local and remote repositories is displayed. With -v/--verbose,
3548 local and remote repositories is displayed. With -v/--verbose,
3548 status is also displayed for each bookmark like below::
3549 status is also displayed for each bookmark like below::
3549
3550
3550 BM1 01234567890a added
3551 BM1 01234567890a added
3551 BM2 1234567890ab advanced
3552 BM2 1234567890ab advanced
3552 BM3 234567890abc diverged
3553 BM3 234567890abc diverged
3553 BM4 34567890abcd changed
3554 BM4 34567890abcd changed
3554
3555
3555 The action taken locally when pulling depends on the
3556 The action taken locally when pulling depends on the
3556 status of each bookmark:
3557 status of each bookmark:
3557
3558
3558 :``added``: pull will create it
3559 :``added``: pull will create it
3559 :``advanced``: pull will update it
3560 :``advanced``: pull will update it
3560 :``diverged``: pull will create a divergent bookmark
3561 :``diverged``: pull will create a divergent bookmark
3561 :``changed``: result depends on remote changesets
3562 :``changed``: result depends on remote changesets
3562
3563
3563 From the point of view of pulling behavior, bookmark
3564 From the point of view of pulling behavior, bookmark
3564 existing only in the remote repository are treated as ``added``,
3565 existing only in the remote repository are treated as ``added``,
3565 even if it is in fact locally deleted.
3566 even if it is in fact locally deleted.
3566
3567
3567 .. container:: verbose
3568 .. container:: verbose
3568
3569
3569 For remote repository, using --bundle avoids downloading the
3570 For remote repository, using --bundle avoids downloading the
3570 changesets twice if the incoming is followed by a pull.
3571 changesets twice if the incoming is followed by a pull.
3571
3572
3572 Examples:
3573 Examples:
3573
3574
3574 - show incoming changes with patches and full description::
3575 - show incoming changes with patches and full description::
3575
3576
3576 hg incoming -vp
3577 hg incoming -vp
3577
3578
3578 - show incoming changes excluding merges, store a bundle::
3579 - show incoming changes excluding merges, store a bundle::
3579
3580
3580 hg in -vpM --bundle incoming.hg
3581 hg in -vpM --bundle incoming.hg
3581 hg pull incoming.hg
3582 hg pull incoming.hg
3582
3583
3583 - briefly list changes inside a bundle::
3584 - briefly list changes inside a bundle::
3584
3585
3585 hg in changes.hg -T "{desc|firstline}\\n"
3586 hg in changes.hg -T "{desc|firstline}\\n"
3586
3587
3587 Returns 0 if there are incoming changes, 1 otherwise.
3588 Returns 0 if there are incoming changes, 1 otherwise.
3588 """
3589 """
3589 opts = pycompat.byteskwargs(opts)
3590 opts = pycompat.byteskwargs(opts)
3590 if opts.get('graph'):
3591 if opts.get('graph'):
3591 logcmdutil.checkunsupportedgraphflags([], opts)
3592 logcmdutil.checkunsupportedgraphflags([], opts)
3592 def display(other, chlist, displayer):
3593 def display(other, chlist, displayer):
3593 revdag = logcmdutil.graphrevs(other, chlist, opts)
3594 revdag = logcmdutil.graphrevs(other, chlist, opts)
3594 logcmdutil.displaygraph(ui, repo, revdag, displayer,
3595 logcmdutil.displaygraph(ui, repo, revdag, displayer,
3595 graphmod.asciiedges)
3596 graphmod.asciiedges)
3596
3597
3597 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
3598 hg._incoming(display, lambda: 1, ui, repo, source, opts, buffered=True)
3598 return 0
3599 return 0
3599
3600
3600 if opts.get('bundle') and opts.get('subrepos'):
3601 if opts.get('bundle') and opts.get('subrepos'):
3601 raise error.Abort(_('cannot combine --bundle and --subrepos'))
3602 raise error.Abort(_('cannot combine --bundle and --subrepos'))
3602
3603
3603 if opts.get('bookmarks'):
3604 if opts.get('bookmarks'):
3604 source, branches = hg.parseurl(ui.expandpath(source),
3605 source, branches = hg.parseurl(ui.expandpath(source),
3605 opts.get('branch'))
3606 opts.get('branch'))
3606 other = hg.peer(repo, opts, source)
3607 other = hg.peer(repo, opts, source)
3607 if 'bookmarks' not in other.listkeys('namespaces'):
3608 if 'bookmarks' not in other.listkeys('namespaces'):
3608 ui.warn(_("remote doesn't support bookmarks\n"))
3609 ui.warn(_("remote doesn't support bookmarks\n"))
3609 return 0
3610 return 0
3610 ui.pager('incoming')
3611 ui.pager('incoming')
3611 ui.status(_('comparing with %s\n') % util.hidepassword(source))
3612 ui.status(_('comparing with %s\n') % util.hidepassword(source))
3612 return bookmarks.incoming(ui, repo, other)
3613 return bookmarks.incoming(ui, repo, other)
3613
3614
3614 repo._subtoppath = ui.expandpath(source)
3615 repo._subtoppath = ui.expandpath(source)
3615 try:
3616 try:
3616 return hg.incoming(ui, repo, source, opts)
3617 return hg.incoming(ui, repo, source, opts)
3617 finally:
3618 finally:
3618 del repo._subtoppath
3619 del repo._subtoppath
3619
3620
3620
3621
3621 @command('init', remoteopts, _('[-e CMD] [--remotecmd CMD] [DEST]'),
3622 @command('init', remoteopts, _('[-e CMD] [--remotecmd CMD] [DEST]'),
3622 helpcategory=command.CATEGORY_REPO_CREATION,
3623 helpcategory=command.CATEGORY_REPO_CREATION,
3623 helpbasic=True, norepo=True)
3624 helpbasic=True, norepo=True)
3624 def init(ui, dest=".", **opts):
3625 def init(ui, dest=".", **opts):
3625 """create a new repository in the given directory
3626 """create a new repository in the given directory
3626
3627
3627 Initialize a new repository in the given directory. If the given
3628 Initialize a new repository in the given directory. If the given
3628 directory does not exist, it will be created.
3629 directory does not exist, it will be created.
3629
3630
3630 If no directory is given, the current directory is used.
3631 If no directory is given, the current directory is used.
3631
3632
3632 It is possible to specify an ``ssh://`` URL as the destination.
3633 It is possible to specify an ``ssh://`` URL as the destination.
3633 See :hg:`help urls` for more information.
3634 See :hg:`help urls` for more information.
3634
3635
3635 Returns 0 on success.
3636 Returns 0 on success.
3636 """
3637 """
3637 opts = pycompat.byteskwargs(opts)
3638 opts = pycompat.byteskwargs(opts)
3638 hg.peer(ui, opts, ui.expandpath(dest), create=True)
3639 hg.peer(ui, opts, ui.expandpath(dest), create=True)
3639
3640
3640 @command('locate',
3641 @command('locate',
3641 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
3642 [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
3642 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
3643 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
3643 ('f', 'fullpath', None, _('print complete paths from the filesystem root')),
3644 ('f', 'fullpath', None, _('print complete paths from the filesystem root')),
3644 ] + walkopts,
3645 ] + walkopts,
3645 _('[OPTION]... [PATTERN]...'),
3646 _('[OPTION]... [PATTERN]...'),
3646 helpcategory=command.CATEGORY_WORKING_DIRECTORY)
3647 helpcategory=command.CATEGORY_WORKING_DIRECTORY)
3647 def locate(ui, repo, *pats, **opts):
3648 def locate(ui, repo, *pats, **opts):
3648 """locate files matching specific patterns (DEPRECATED)
3649 """locate files matching specific patterns (DEPRECATED)
3649
3650
3650 Print files under Mercurial control in the working directory whose
3651 Print files under Mercurial control in the working directory whose
3651 names match the given patterns.
3652 names match the given patterns.
3652
3653
3653 By default, this command searches all directories in the working
3654 By default, this command searches all directories in the working
3654 directory. To search just the current directory and its
3655 directory. To search just the current directory and its
3655 subdirectories, use "--include .".
3656 subdirectories, use "--include .".
3656
3657
3657 If no patterns are given to match, this command prints the names
3658 If no patterns are given to match, this command prints the names
3658 of all files under Mercurial control in the working directory.
3659 of all files under Mercurial control in the working directory.
3659
3660
3660 If you want to feed the output of this command into the "xargs"
3661 If you want to feed the output of this command into the "xargs"
3661 command, use the -0 option to both this command and "xargs". This
3662 command, use the -0 option to both this command and "xargs". This
3662 will avoid the problem of "xargs" treating single filenames that
3663 will avoid the problem of "xargs" treating single filenames that
3663 contain whitespace as multiple filenames.
3664 contain whitespace as multiple filenames.
3664
3665
3665 See :hg:`help files` for a more versatile command.
3666 See :hg:`help files` for a more versatile command.
3666
3667
3667 Returns 0 if a match is found, 1 otherwise.
3668 Returns 0 if a match is found, 1 otherwise.
3668 """
3669 """
3669 opts = pycompat.byteskwargs(opts)
3670 opts = pycompat.byteskwargs(opts)
3670 if opts.get('print0'):
3671 if opts.get('print0'):
3671 end = '\0'
3672 end = '\0'
3672 else:
3673 else:
3673 end = '\n'
3674 end = '\n'
3674 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
3675 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
3675
3676
3676 ret = 1
3677 ret = 1
3677 m = scmutil.match(ctx, pats, opts, default='relglob',
3678 m = scmutil.match(ctx, pats, opts, default='relglob',
3678 badfn=lambda x, y: False)
3679 badfn=lambda x, y: False)
3679
3680
3680 ui.pager('locate')
3681 ui.pager('locate')
3681 if ctx.rev() is None:
3682 if ctx.rev() is None:
3682 # When run on the working copy, "locate" includes removed files, so
3683 # When run on the working copy, "locate" includes removed files, so
3683 # we get the list of files from the dirstate.
3684 # we get the list of files from the dirstate.
3684 filesgen = sorted(repo.dirstate.matches(m))
3685 filesgen = sorted(repo.dirstate.matches(m))
3685 else:
3686 else:
3686 filesgen = ctx.matches(m)
3687 filesgen = ctx.matches(m)
3687 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=bool(pats))
3688 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=bool(pats))
3688 for abs in filesgen:
3689 for abs in filesgen:
3689 if opts.get('fullpath'):
3690 if opts.get('fullpath'):
3690 ui.write(repo.wjoin(abs), end)
3691 ui.write(repo.wjoin(abs), end)
3691 else:
3692 else:
3692 ui.write(uipathfn(abs), end)
3693 ui.write(uipathfn(abs), end)
3693 ret = 0
3694 ret = 0
3694
3695
3695 return ret
3696 return ret
3696
3697
3697 @command('log|history',
3698 @command('log|history',
3698 [('f', 'follow', None,
3699 [('f', 'follow', None,
3699 _('follow changeset history, or file history across copies and renames')),
3700 _('follow changeset history, or file history across copies and renames')),
3700 ('', 'follow-first', None,
3701 ('', 'follow-first', None,
3701 _('only follow the first parent of merge changesets (DEPRECATED)')),
3702 _('only follow the first parent of merge changesets (DEPRECATED)')),
3702 ('d', 'date', '', _('show revisions matching date spec'), _('DATE')),
3703 ('d', 'date', '', _('show revisions matching date spec'), _('DATE')),
3703 ('C', 'copies', None, _('show copied files')),
3704 ('C', 'copies', None, _('show copied files')),
3704 ('k', 'keyword', [],
3705 ('k', 'keyword', [],
3705 _('do case-insensitive search for a given text'), _('TEXT')),
3706 _('do case-insensitive search for a given text'), _('TEXT')),
3706 ('r', 'rev', [], _('show the specified revision or revset'), _('REV')),
3707 ('r', 'rev', [], _('show the specified revision or revset'), _('REV')),
3707 ('L', 'line-range', [],
3708 ('L', 'line-range', [],
3708 _('follow line range of specified file (EXPERIMENTAL)'),
3709 _('follow line range of specified file (EXPERIMENTAL)'),
3709 _('FILE,RANGE')),
3710 _('FILE,RANGE')),
3710 ('', 'removed', None, _('include revisions where files were removed')),
3711 ('', 'removed', None, _('include revisions where files were removed')),
3711 ('m', 'only-merges', None, _('show only merges (DEPRECATED)')),
3712 ('m', 'only-merges', None, _('show only merges (DEPRECATED)')),
3712 ('u', 'user', [], _('revisions committed by user'), _('USER')),
3713 ('u', 'user', [], _('revisions committed by user'), _('USER')),
3713 ('', 'only-branch', [],
3714 ('', 'only-branch', [],
3714 _('show only changesets within the given named branch (DEPRECATED)'),
3715 _('show only changesets within the given named branch (DEPRECATED)'),
3715 _('BRANCH')),
3716 _('BRANCH')),
3716 ('b', 'branch', [],
3717 ('b', 'branch', [],
3717 _('show changesets within the given named branch'), _('BRANCH')),
3718 _('show changesets within the given named branch'), _('BRANCH')),
3718 ('P', 'prune', [],
3719 ('P', 'prune', [],
3719 _('do not display revision or any of its ancestors'), _('REV')),
3720 _('do not display revision or any of its ancestors'), _('REV')),
3720 ] + logopts + walkopts,
3721 ] + logopts + walkopts,
3721 _('[OPTION]... [FILE]'),
3722 _('[OPTION]... [FILE]'),
3722 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3723 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
3723 helpbasic=True, inferrepo=True,
3724 helpbasic=True, inferrepo=True,
3724 intents={INTENT_READONLY})
3725 intents={INTENT_READONLY})
3725 def log(ui, repo, *pats, **opts):
3726 def log(ui, repo, *pats, **opts):
3726 """show revision history of entire repository or files
3727 """show revision history of entire repository or files
3727
3728
3728 Print the revision history of the specified files or the entire
3729 Print the revision history of the specified files or the entire
3729 project.
3730 project.
3730
3731
3731 If no revision range is specified, the default is ``tip:0`` unless
3732 If no revision range is specified, the default is ``tip:0`` unless
3732 --follow is set, in which case the working directory parent is
3733 --follow is set, in which case the working directory parent is
3733 used as the starting revision.
3734 used as the starting revision.
3734
3735
3735 File history is shown without following rename or copy history of
3736 File history is shown without following rename or copy history of
3736 files. Use -f/--follow with a filename to follow history across
3737 files. Use -f/--follow with a filename to follow history across
3737 renames and copies. --follow without a filename will only show
3738 renames and copies. --follow without a filename will only show
3738 ancestors of the starting revision.
3739 ancestors of the starting revision.
3739
3740
3740 By default this command prints revision number and changeset id,
3741 By default this command prints revision number and changeset id,
3741 tags, non-trivial parents, user, date and time, and a summary for
3742 tags, non-trivial parents, user, date and time, and a summary for
3742 each commit. When the -v/--verbose switch is used, the list of
3743 each commit. When the -v/--verbose switch is used, the list of
3743 changed files and full commit message are shown.
3744 changed files and full commit message are shown.
3744
3745
3745 With --graph the revisions are shown as an ASCII art DAG with the most
3746 With --graph the revisions are shown as an ASCII art DAG with the most
3746 recent changeset at the top.
3747 recent changeset at the top.
3747 'o' is a changeset, '@' is a working directory parent, '_' closes a branch,
3748 'o' is a changeset, '@' is a working directory parent, '_' closes a branch,
3748 'x' is obsolete, '*' is unstable, and '+' represents a fork where the
3749 'x' is obsolete, '*' is unstable, and '+' represents a fork where the
3749 changeset from the lines below is a parent of the 'o' merge on the same
3750 changeset from the lines below is a parent of the 'o' merge on the same
3750 line.
3751 line.
3751 Paths in the DAG are represented with '|', '/' and so forth. ':' in place
3752 Paths in the DAG are represented with '|', '/' and so forth. ':' in place
3752 of a '|' indicates one or more revisions in a path are omitted.
3753 of a '|' indicates one or more revisions in a path are omitted.
3753
3754
3754 .. container:: verbose
3755 .. container:: verbose
3755
3756
3756 Use -L/--line-range FILE,M:N options to follow the history of lines
3757 Use -L/--line-range FILE,M:N options to follow the history of lines
3757 from M to N in FILE. With -p/--patch only diff hunks affecting
3758 from M to N in FILE. With -p/--patch only diff hunks affecting
3758 specified line range will be shown. This option requires --follow;
3759 specified line range will be shown. This option requires --follow;
3759 it can be specified multiple times. Currently, this option is not
3760 it can be specified multiple times. Currently, this option is not
3760 compatible with --graph. This option is experimental.
3761 compatible with --graph. This option is experimental.
3761
3762
3762 .. note::
3763 .. note::
3763
3764
3764 :hg:`log --patch` may generate unexpected diff output for merge
3765 :hg:`log --patch` may generate unexpected diff output for merge
3765 changesets, as it will only compare the merge changeset against
3766 changesets, as it will only compare the merge changeset against
3766 its first parent. Also, only files different from BOTH parents
3767 its first parent. Also, only files different from BOTH parents
3767 will appear in files:.
3768 will appear in files:.
3768
3769
3769 .. note::
3770 .. note::
3770
3771
3771 For performance reasons, :hg:`log FILE` may omit duplicate changes
3772 For performance reasons, :hg:`log FILE` may omit duplicate changes
3772 made on branches and will not show removals or mode changes. To
3773 made on branches and will not show removals or mode changes. To
3773 see all such changes, use the --removed switch.
3774 see all such changes, use the --removed switch.
3774
3775
3775 .. container:: verbose
3776 .. container:: verbose
3776
3777
3777 .. note::
3778 .. note::
3778
3779
3779 The history resulting from -L/--line-range options depends on diff
3780 The history resulting from -L/--line-range options depends on diff
3780 options; for instance if white-spaces are ignored, respective changes
3781 options; for instance if white-spaces are ignored, respective changes
3781 with only white-spaces in specified line range will not be listed.
3782 with only white-spaces in specified line range will not be listed.
3782
3783
3783 .. container:: verbose
3784 .. container:: verbose
3784
3785
3785 Some examples:
3786 Some examples:
3786
3787
3787 - changesets with full descriptions and file lists::
3788 - changesets with full descriptions and file lists::
3788
3789
3789 hg log -v
3790 hg log -v
3790
3791
3791 - changesets ancestral to the working directory::
3792 - changesets ancestral to the working directory::
3792
3793
3793 hg log -f
3794 hg log -f
3794
3795
3795 - last 10 commits on the current branch::
3796 - last 10 commits on the current branch::
3796
3797
3797 hg log -l 10 -b .
3798 hg log -l 10 -b .
3798
3799
3799 - changesets showing all modifications of a file, including removals::
3800 - changesets showing all modifications of a file, including removals::
3800
3801
3801 hg log --removed file.c
3802 hg log --removed file.c
3802
3803
3803 - all changesets that touch a directory, with diffs, excluding merges::
3804 - all changesets that touch a directory, with diffs, excluding merges::
3804
3805
3805 hg log -Mp lib/
3806 hg log -Mp lib/
3806
3807
3807 - all revision numbers that match a keyword::
3808 - all revision numbers that match a keyword::
3808
3809
3809 hg log -k bug --template "{rev}\\n"
3810 hg log -k bug --template "{rev}\\n"
3810
3811
3811 - the full hash identifier of the working directory parent::
3812 - the full hash identifier of the working directory parent::
3812
3813
3813 hg log -r . --template "{node}\\n"
3814 hg log -r . --template "{node}\\n"
3814
3815
3815 - list available log templates::
3816 - list available log templates::
3816
3817
3817 hg log -T list
3818 hg log -T list
3818
3819
3819 - check if a given changeset is included in a tagged release::
3820 - check if a given changeset is included in a tagged release::
3820
3821
3821 hg log -r "a21ccf and ancestor(1.9)"
3822 hg log -r "a21ccf and ancestor(1.9)"
3822
3823
3823 - find all changesets by some user in a date range::
3824 - find all changesets by some user in a date range::
3824
3825
3825 hg log -k alice -d "may 2008 to jul 2008"
3826 hg log -k alice -d "may 2008 to jul 2008"
3826
3827
3827 - summary of all changesets after the last tag::
3828 - summary of all changesets after the last tag::
3828
3829
3829 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
3830 hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
3830
3831
3831 - changesets touching lines 13 to 23 for file.c::
3832 - changesets touching lines 13 to 23 for file.c::
3832
3833
3833 hg log -L file.c,13:23
3834 hg log -L file.c,13:23
3834
3835
3835 - changesets touching lines 13 to 23 for file.c and lines 2 to 6 of
3836 - changesets touching lines 13 to 23 for file.c and lines 2 to 6 of
3836 main.c with patch::
3837 main.c with patch::
3837
3838
3838 hg log -L file.c,13:23 -L main.c,2:6 -p
3839 hg log -L file.c,13:23 -L main.c,2:6 -p
3839
3840
3840 See :hg:`help dates` for a list of formats valid for -d/--date.
3841 See :hg:`help dates` for a list of formats valid for -d/--date.
3841
3842
3842 See :hg:`help revisions` for more about specifying and ordering
3843 See :hg:`help revisions` for more about specifying and ordering
3843 revisions.
3844 revisions.
3844
3845
3845 See :hg:`help templates` for more about pre-packaged styles and
3846 See :hg:`help templates` for more about pre-packaged styles and
3846 specifying custom templates. The default template used by the log
3847 specifying custom templates. The default template used by the log
3847 command can be customized via the ``ui.logtemplate`` configuration
3848 command can be customized via the ``ui.logtemplate`` configuration
3848 setting.
3849 setting.
3849
3850
3850 Returns 0 on success.
3851 Returns 0 on success.
3851
3852
3852 """
3853 """
3853 opts = pycompat.byteskwargs(opts)
3854 opts = pycompat.byteskwargs(opts)
3854 linerange = opts.get('line_range')
3855 linerange = opts.get('line_range')
3855
3856
3856 if linerange and not opts.get('follow'):
3857 if linerange and not opts.get('follow'):
3857 raise error.Abort(_('--line-range requires --follow'))
3858 raise error.Abort(_('--line-range requires --follow'))
3858
3859
3859 if linerange and pats:
3860 if linerange and pats:
3860 # TODO: take pats as patterns with no line-range filter
3861 # TODO: take pats as patterns with no line-range filter
3861 raise error.Abort(
3862 raise error.Abort(
3862 _('FILE arguments are not compatible with --line-range option')
3863 _('FILE arguments are not compatible with --line-range option')
3863 )
3864 )
3864
3865
3865 repo = scmutil.unhidehashlikerevs(repo, opts.get('rev'), 'nowarn')
3866 repo = scmutil.unhidehashlikerevs(repo, opts.get('rev'), 'nowarn')
3866 revs, differ = logcmdutil.getrevs(repo, pats, opts)
3867 revs, differ = logcmdutil.getrevs(repo, pats, opts)
3867 if linerange:
3868 if linerange:
3868 # TODO: should follow file history from logcmdutil._initialrevs(),
3869 # TODO: should follow file history from logcmdutil._initialrevs(),
3869 # then filter the result by logcmdutil._makerevset() and --limit
3870 # then filter the result by logcmdutil._makerevset() and --limit
3870 revs, differ = logcmdutil.getlinerangerevs(repo, revs, opts)
3871 revs, differ = logcmdutil.getlinerangerevs(repo, revs, opts)
3871
3872
3872 getrenamed = None
3873 getrenamed = None
3873 if opts.get('copies'):
3874 if opts.get('copies'):
3874 endrev = None
3875 endrev = None
3875 if revs:
3876 if revs:
3876 endrev = revs.max() + 1
3877 endrev = revs.max() + 1
3877 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
3878 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
3878
3879
3879 ui.pager('log')
3880 ui.pager('log')
3880 displayer = logcmdutil.changesetdisplayer(ui, repo, opts, differ,
3881 displayer = logcmdutil.changesetdisplayer(ui, repo, opts, differ,
3881 buffered=True)
3882 buffered=True)
3882 if opts.get('graph'):
3883 if opts.get('graph'):
3883 displayfn = logcmdutil.displaygraphrevs
3884 displayfn = logcmdutil.displaygraphrevs
3884 else:
3885 else:
3885 displayfn = logcmdutil.displayrevs
3886 displayfn = logcmdutil.displayrevs
3886 displayfn(ui, repo, revs, displayer, getrenamed)
3887 displayfn(ui, repo, revs, displayer, getrenamed)
3887
3888
3888 @command('manifest',
3889 @command('manifest',
3889 [('r', 'rev', '', _('revision to display'), _('REV')),
3890 [('r', 'rev', '', _('revision to display'), _('REV')),
3890 ('', 'all', False, _("list files from all revisions"))]
3891 ('', 'all', False, _("list files from all revisions"))]
3891 + formatteropts,
3892 + formatteropts,
3892 _('[-r REV]'),
3893 _('[-r REV]'),
3893 helpcategory=command.CATEGORY_MAINTENANCE,
3894 helpcategory=command.CATEGORY_MAINTENANCE,
3894 intents={INTENT_READONLY})
3895 intents={INTENT_READONLY})
3895 def manifest(ui, repo, node=None, rev=None, **opts):
3896 def manifest(ui, repo, node=None, rev=None, **opts):
3896 """output the current or given revision of the project manifest
3897 """output the current or given revision of the project manifest
3897
3898
3898 Print a list of version controlled files for the given revision.
3899 Print a list of version controlled files for the given revision.
3899 If no revision is given, the first parent of the working directory
3900 If no revision is given, the first parent of the working directory
3900 is used, or the null revision if no revision is checked out.
3901 is used, or the null revision if no revision is checked out.
3901
3902
3902 With -v, print file permissions, symlink and executable bits.
3903 With -v, print file permissions, symlink and executable bits.
3903 With --debug, print file revision hashes.
3904 With --debug, print file revision hashes.
3904
3905
3905 If option --all is specified, the list of all files from all revisions
3906 If option --all is specified, the list of all files from all revisions
3906 is printed. This includes deleted and renamed files.
3907 is printed. This includes deleted and renamed files.
3907
3908
3908 Returns 0 on success.
3909 Returns 0 on success.
3909 """
3910 """
3910 opts = pycompat.byteskwargs(opts)
3911 opts = pycompat.byteskwargs(opts)
3911 fm = ui.formatter('manifest', opts)
3912 fm = ui.formatter('manifest', opts)
3912
3913
3913 if opts.get('all'):
3914 if opts.get('all'):
3914 if rev or node:
3915 if rev or node:
3915 raise error.Abort(_("can't specify a revision with --all"))
3916 raise error.Abort(_("can't specify a revision with --all"))
3916
3917
3917 res = set()
3918 res = set()
3918 for rev in repo:
3919 for rev in repo:
3919 ctx = repo[rev]
3920 ctx = repo[rev]
3920 res |= set(ctx.files())
3921 res |= set(ctx.files())
3921
3922
3922 ui.pager('manifest')
3923 ui.pager('manifest')
3923 for f in sorted(res):
3924 for f in sorted(res):
3924 fm.startitem()
3925 fm.startitem()
3925 fm.write("path", '%s\n', f)
3926 fm.write("path", '%s\n', f)
3926 fm.end()
3927 fm.end()
3927 return
3928 return
3928
3929
3929 if rev and node:
3930 if rev and node:
3930 raise error.Abort(_("please specify just one revision"))
3931 raise error.Abort(_("please specify just one revision"))
3931
3932
3932 if not node:
3933 if not node:
3933 node = rev
3934 node = rev
3934
3935
3935 char = {'l': '@', 'x': '*', '': '', 't': 'd'}
3936 char = {'l': '@', 'x': '*', '': '', 't': 'd'}
3936 mode = {'l': '644', 'x': '755', '': '644', 't': '755'}
3937 mode = {'l': '644', 'x': '755', '': '644', 't': '755'}
3937 if node:
3938 if node:
3938 repo = scmutil.unhidehashlikerevs(repo, [node], 'nowarn')
3939 repo = scmutil.unhidehashlikerevs(repo, [node], 'nowarn')
3939 ctx = scmutil.revsingle(repo, node)
3940 ctx = scmutil.revsingle(repo, node)
3940 mf = ctx.manifest()
3941 mf = ctx.manifest()
3941 ui.pager('manifest')
3942 ui.pager('manifest')
3942 for f in ctx:
3943 for f in ctx:
3943 fm.startitem()
3944 fm.startitem()
3944 fm.context(ctx=ctx)
3945 fm.context(ctx=ctx)
3945 fl = ctx[f].flags()
3946 fl = ctx[f].flags()
3946 fm.condwrite(ui.debugflag, 'hash', '%s ', hex(mf[f]))
3947 fm.condwrite(ui.debugflag, 'hash', '%s ', hex(mf[f]))
3947 fm.condwrite(ui.verbose, 'mode type', '%s %1s ', mode[fl], char[fl])
3948 fm.condwrite(ui.verbose, 'mode type', '%s %1s ', mode[fl], char[fl])
3948 fm.write('path', '%s\n', f)
3949 fm.write('path', '%s\n', f)
3949 fm.end()
3950 fm.end()
3950
3951
3951 @command('merge',
3952 @command('merge',
3952 [('f', 'force', None,
3953 [('f', 'force', None,
3953 _('force a merge including outstanding changes (DEPRECATED)')),
3954 _('force a merge including outstanding changes (DEPRECATED)')),
3954 ('r', 'rev', '', _('revision to merge'), _('REV')),
3955 ('r', 'rev', '', _('revision to merge'), _('REV')),
3955 ('P', 'preview', None,
3956 ('P', 'preview', None,
3956 _('review revisions to merge (no merge is performed)')),
3957 _('review revisions to merge (no merge is performed)')),
3957 ('', 'abort', None, _('abort the ongoing merge')),
3958 ('', 'abort', None, _('abort the ongoing merge')),
3958 ] + mergetoolopts,
3959 ] + mergetoolopts,
3959 _('[-P] [[-r] REV]'),
3960 _('[-P] [[-r] REV]'),
3960 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT, helpbasic=True)
3961 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT, helpbasic=True)
3961 def merge(ui, repo, node=None, **opts):
3962 def merge(ui, repo, node=None, **opts):
3962 """merge another revision into working directory
3963 """merge another revision into working directory
3963
3964
3964 The current working directory is updated with all changes made in
3965 The current working directory is updated with all changes made in
3965 the requested revision since the last common predecessor revision.
3966 the requested revision since the last common predecessor revision.
3966
3967
3967 Files that changed between either parent are marked as changed for
3968 Files that changed between either parent are marked as changed for
3968 the next commit and a commit must be performed before any further
3969 the next commit and a commit must be performed before any further
3969 updates to the repository are allowed. The next commit will have
3970 updates to the repository are allowed. The next commit will have
3970 two parents.
3971 two parents.
3971
3972
3972 ``--tool`` can be used to specify the merge tool used for file
3973 ``--tool`` can be used to specify the merge tool used for file
3973 merges. It overrides the HGMERGE environment variable and your
3974 merges. It overrides the HGMERGE environment variable and your
3974 configuration files. See :hg:`help merge-tools` for options.
3975 configuration files. See :hg:`help merge-tools` for options.
3975
3976
3976 If no revision is specified, the working directory's parent is a
3977 If no revision is specified, the working directory's parent is a
3977 head revision, and the current branch contains exactly one other
3978 head revision, and the current branch contains exactly one other
3978 head, the other head is merged with by default. Otherwise, an
3979 head, the other head is merged with by default. Otherwise, an
3979 explicit revision with which to merge with must be provided.
3980 explicit revision with which to merge with must be provided.
3980
3981
3981 See :hg:`help resolve` for information on handling file conflicts.
3982 See :hg:`help resolve` for information on handling file conflicts.
3982
3983
3983 To undo an uncommitted merge, use :hg:`merge --abort` which
3984 To undo an uncommitted merge, use :hg:`merge --abort` which
3984 will check out a clean copy of the original merge parent, losing
3985 will check out a clean copy of the original merge parent, losing
3985 all changes.
3986 all changes.
3986
3987
3987 Returns 0 on success, 1 if there are unresolved files.
3988 Returns 0 on success, 1 if there are unresolved files.
3988 """
3989 """
3989
3990
3990 opts = pycompat.byteskwargs(opts)
3991 opts = pycompat.byteskwargs(opts)
3991 abort = opts.get('abort')
3992 abort = opts.get('abort')
3992 if abort and repo.dirstate.p2() == nullid:
3993 if abort and repo.dirstate.p2() == nullid:
3993 cmdutil.wrongtooltocontinue(repo, _('merge'))
3994 cmdutil.wrongtooltocontinue(repo, _('merge'))
3994 if abort:
3995 if abort:
3995 if node:
3996 if node:
3996 raise error.Abort(_("cannot specify a node with --abort"))
3997 raise error.Abort(_("cannot specify a node with --abort"))
3997 if opts.get('rev'):
3998 if opts.get('rev'):
3998 raise error.Abort(_("cannot specify both --rev and --abort"))
3999 raise error.Abort(_("cannot specify both --rev and --abort"))
3999 if opts.get('preview'):
4000 if opts.get('preview'):
4000 raise error.Abort(_("cannot specify --preview with --abort"))
4001 raise error.Abort(_("cannot specify --preview with --abort"))
4001 if opts.get('rev') and node:
4002 if opts.get('rev') and node:
4002 raise error.Abort(_("please specify just one revision"))
4003 raise error.Abort(_("please specify just one revision"))
4003 if not node:
4004 if not node:
4004 node = opts.get('rev')
4005 node = opts.get('rev')
4005
4006
4006 if node:
4007 if node:
4007 node = scmutil.revsingle(repo, node).node()
4008 node = scmutil.revsingle(repo, node).node()
4008
4009
4009 if not node and not abort:
4010 if not node and not abort:
4010 node = repo[destutil.destmerge(repo)].node()
4011 node = repo[destutil.destmerge(repo)].node()
4011
4012
4012 if opts.get('preview'):
4013 if opts.get('preview'):
4013 # find nodes that are ancestors of p2 but not of p1
4014 # find nodes that are ancestors of p2 but not of p1
4014 p1 = repo.lookup('.')
4015 p1 = repo.lookup('.')
4015 p2 = node
4016 p2 = node
4016 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
4017 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
4017
4018
4018 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
4019 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
4019 for node in nodes:
4020 for node in nodes:
4020 displayer.show(repo[node])
4021 displayer.show(repo[node])
4021 displayer.close()
4022 displayer.close()
4022 return 0
4023 return 0
4023
4024
4024 # ui.forcemerge is an internal variable, do not document
4025 # ui.forcemerge is an internal variable, do not document
4025 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
4026 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
4026 with ui.configoverride(overrides, 'merge'):
4027 with ui.configoverride(overrides, 'merge'):
4027 force = opts.get('force')
4028 force = opts.get('force')
4028 labels = ['working copy', 'merge rev']
4029 labels = ['working copy', 'merge rev']
4029 return hg.merge(repo, node, force=force, mergeforce=force,
4030 return hg.merge(repo, node, force=force, mergeforce=force,
4030 labels=labels, abort=abort)
4031 labels=labels, abort=abort)
4031
4032
4032 @command('outgoing|out',
4033 @command('outgoing|out',
4033 [('f', 'force', None, _('run even when the destination is unrelated')),
4034 [('f', 'force', None, _('run even when the destination is unrelated')),
4034 ('r', 'rev', [],
4035 ('r', 'rev', [],
4035 _('a changeset intended to be included in the destination'), _('REV')),
4036 _('a changeset intended to be included in the destination'), _('REV')),
4036 ('n', 'newest-first', None, _('show newest record first')),
4037 ('n', 'newest-first', None, _('show newest record first')),
4037 ('B', 'bookmarks', False, _('compare bookmarks')),
4038 ('B', 'bookmarks', False, _('compare bookmarks')),
4038 ('b', 'branch', [], _('a specific branch you would like to push'),
4039 ('b', 'branch', [], _('a specific branch you would like to push'),
4039 _('BRANCH')),
4040 _('BRANCH')),
4040 ] + logopts + remoteopts + subrepoopts,
4041 ] + logopts + remoteopts + subrepoopts,
4041 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]'),
4042 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]'),
4042 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT)
4043 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT)
4043 def outgoing(ui, repo, dest=None, **opts):
4044 def outgoing(ui, repo, dest=None, **opts):
4044 """show changesets not found in the destination
4045 """show changesets not found in the destination
4045
4046
4046 Show changesets not found in the specified destination repository
4047 Show changesets not found in the specified destination repository
4047 or the default push location. These are the changesets that would
4048 or the default push location. These are the changesets that would
4048 be pushed if a push was requested.
4049 be pushed if a push was requested.
4049
4050
4050 See pull for details of valid destination formats.
4051 See pull for details of valid destination formats.
4051
4052
4052 .. container:: verbose
4053 .. container:: verbose
4053
4054
4054 With -B/--bookmarks, the result of bookmark comparison between
4055 With -B/--bookmarks, the result of bookmark comparison between
4055 local and remote repositories is displayed. With -v/--verbose,
4056 local and remote repositories is displayed. With -v/--verbose,
4056 status is also displayed for each bookmark like below::
4057 status is also displayed for each bookmark like below::
4057
4058
4058 BM1 01234567890a added
4059 BM1 01234567890a added
4059 BM2 deleted
4060 BM2 deleted
4060 BM3 234567890abc advanced
4061 BM3 234567890abc advanced
4061 BM4 34567890abcd diverged
4062 BM4 34567890abcd diverged
4062 BM5 4567890abcde changed
4063 BM5 4567890abcde changed
4063
4064
4064 The action taken when pushing depends on the
4065 The action taken when pushing depends on the
4065 status of each bookmark:
4066 status of each bookmark:
4066
4067
4067 :``added``: push with ``-B`` will create it
4068 :``added``: push with ``-B`` will create it
4068 :``deleted``: push with ``-B`` will delete it
4069 :``deleted``: push with ``-B`` will delete it
4069 :``advanced``: push will update it
4070 :``advanced``: push will update it
4070 :``diverged``: push with ``-B`` will update it
4071 :``diverged``: push with ``-B`` will update it
4071 :``changed``: push with ``-B`` will update it
4072 :``changed``: push with ``-B`` will update it
4072
4073
4073 From the point of view of pushing behavior, bookmarks
4074 From the point of view of pushing behavior, bookmarks
4074 existing only in the remote repository are treated as
4075 existing only in the remote repository are treated as
4075 ``deleted``, even if it is in fact added remotely.
4076 ``deleted``, even if it is in fact added remotely.
4076
4077
4077 Returns 0 if there are outgoing changes, 1 otherwise.
4078 Returns 0 if there are outgoing changes, 1 otherwise.
4078 """
4079 """
4079 # hg._outgoing() needs to re-resolve the path in order to handle #branch
4080 # hg._outgoing() needs to re-resolve the path in order to handle #branch
4080 # style URLs, so don't overwrite dest.
4081 # style URLs, so don't overwrite dest.
4081 path = ui.paths.getpath(dest, default=('default-push', 'default'))
4082 path = ui.paths.getpath(dest, default=('default-push', 'default'))
4082 if not path:
4083 if not path:
4083 raise error.Abort(_('default repository not configured!'),
4084 raise error.Abort(_('default repository not configured!'),
4084 hint=_("see 'hg help config.paths'"))
4085 hint=_("see 'hg help config.paths'"))
4085
4086
4086 opts = pycompat.byteskwargs(opts)
4087 opts = pycompat.byteskwargs(opts)
4087 if opts.get('graph'):
4088 if opts.get('graph'):
4088 logcmdutil.checkunsupportedgraphflags([], opts)
4089 logcmdutil.checkunsupportedgraphflags([], opts)
4089 o, other = hg._outgoing(ui, repo, dest, opts)
4090 o, other = hg._outgoing(ui, repo, dest, opts)
4090 if not o:
4091 if not o:
4091 cmdutil.outgoinghooks(ui, repo, other, opts, o)
4092 cmdutil.outgoinghooks(ui, repo, other, opts, o)
4092 return
4093 return
4093
4094
4094 revdag = logcmdutil.graphrevs(repo, o, opts)
4095 revdag = logcmdutil.graphrevs(repo, o, opts)
4095 ui.pager('outgoing')
4096 ui.pager('outgoing')
4096 displayer = logcmdutil.changesetdisplayer(ui, repo, opts, buffered=True)
4097 displayer = logcmdutil.changesetdisplayer(ui, repo, opts, buffered=True)
4097 logcmdutil.displaygraph(ui, repo, revdag, displayer,
4098 logcmdutil.displaygraph(ui, repo, revdag, displayer,
4098 graphmod.asciiedges)
4099 graphmod.asciiedges)
4099 cmdutil.outgoinghooks(ui, repo, other, opts, o)
4100 cmdutil.outgoinghooks(ui, repo, other, opts, o)
4100 return 0
4101 return 0
4101
4102
4102 if opts.get('bookmarks'):
4103 if opts.get('bookmarks'):
4103 dest = path.pushloc or path.loc
4104 dest = path.pushloc or path.loc
4104 other = hg.peer(repo, opts, dest)
4105 other = hg.peer(repo, opts, dest)
4105 if 'bookmarks' not in other.listkeys('namespaces'):
4106 if 'bookmarks' not in other.listkeys('namespaces'):
4106 ui.warn(_("remote doesn't support bookmarks\n"))
4107 ui.warn(_("remote doesn't support bookmarks\n"))
4107 return 0
4108 return 0
4108 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
4109 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
4109 ui.pager('outgoing')
4110 ui.pager('outgoing')
4110 return bookmarks.outgoing(ui, repo, other)
4111 return bookmarks.outgoing(ui, repo, other)
4111
4112
4112 repo._subtoppath = path.pushloc or path.loc
4113 repo._subtoppath = path.pushloc or path.loc
4113 try:
4114 try:
4114 return hg.outgoing(ui, repo, dest, opts)
4115 return hg.outgoing(ui, repo, dest, opts)
4115 finally:
4116 finally:
4116 del repo._subtoppath
4117 del repo._subtoppath
4117
4118
4118 @command('parents',
4119 @command('parents',
4119 [('r', 'rev', '', _('show parents of the specified revision'), _('REV')),
4120 [('r', 'rev', '', _('show parents of the specified revision'), _('REV')),
4120 ] + templateopts,
4121 ] + templateopts,
4121 _('[-r REV] [FILE]'),
4122 _('[-r REV] [FILE]'),
4122 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
4123 helpcategory=command.CATEGORY_CHANGE_NAVIGATION,
4123 inferrepo=True)
4124 inferrepo=True)
4124 def parents(ui, repo, file_=None, **opts):
4125 def parents(ui, repo, file_=None, **opts):
4125 """show the parents of the working directory or revision (DEPRECATED)
4126 """show the parents of the working directory or revision (DEPRECATED)
4126
4127
4127 Print the working directory's parent revisions. If a revision is
4128 Print the working directory's parent revisions. If a revision is
4128 given via -r/--rev, the parent of that revision will be printed.
4129 given via -r/--rev, the parent of that revision will be printed.
4129 If a file argument is given, the revision in which the file was
4130 If a file argument is given, the revision in which the file was
4130 last changed (before the working directory revision or the
4131 last changed (before the working directory revision or the
4131 argument to --rev if given) is printed.
4132 argument to --rev if given) is printed.
4132
4133
4133 This command is equivalent to::
4134 This command is equivalent to::
4134
4135
4135 hg log -r "p1()+p2()" or
4136 hg log -r "p1()+p2()" or
4136 hg log -r "p1(REV)+p2(REV)" or
4137 hg log -r "p1(REV)+p2(REV)" or
4137 hg log -r "max(::p1() and file(FILE))+max(::p2() and file(FILE))" or
4138 hg log -r "max(::p1() and file(FILE))+max(::p2() and file(FILE))" or
4138 hg log -r "max(::p1(REV) and file(FILE))+max(::p2(REV) and file(FILE))"
4139 hg log -r "max(::p1(REV) and file(FILE))+max(::p2(REV) and file(FILE))"
4139
4140
4140 See :hg:`summary` and :hg:`help revsets` for related information.
4141 See :hg:`summary` and :hg:`help revsets` for related information.
4141
4142
4142 Returns 0 on success.
4143 Returns 0 on success.
4143 """
4144 """
4144
4145
4145 opts = pycompat.byteskwargs(opts)
4146 opts = pycompat.byteskwargs(opts)
4146 rev = opts.get('rev')
4147 rev = opts.get('rev')
4147 if rev:
4148 if rev:
4148 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
4149 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
4149 ctx = scmutil.revsingle(repo, rev, None)
4150 ctx = scmutil.revsingle(repo, rev, None)
4150
4151
4151 if file_:
4152 if file_:
4152 m = scmutil.match(ctx, (file_,), opts)
4153 m = scmutil.match(ctx, (file_,), opts)
4153 if m.anypats() or len(m.files()) != 1:
4154 if m.anypats() or len(m.files()) != 1:
4154 raise error.Abort(_('can only specify an explicit filename'))
4155 raise error.Abort(_('can only specify an explicit filename'))
4155 file_ = m.files()[0]
4156 file_ = m.files()[0]
4156 filenodes = []
4157 filenodes = []
4157 for cp in ctx.parents():
4158 for cp in ctx.parents():
4158 if not cp:
4159 if not cp:
4159 continue
4160 continue
4160 try:
4161 try:
4161 filenodes.append(cp.filenode(file_))
4162 filenodes.append(cp.filenode(file_))
4162 except error.LookupError:
4163 except error.LookupError:
4163 pass
4164 pass
4164 if not filenodes:
4165 if not filenodes:
4165 raise error.Abort(_("'%s' not found in manifest!") % file_)
4166 raise error.Abort(_("'%s' not found in manifest!") % file_)
4166 p = []
4167 p = []
4167 for fn in filenodes:
4168 for fn in filenodes:
4168 fctx = repo.filectx(file_, fileid=fn)
4169 fctx = repo.filectx(file_, fileid=fn)
4169 p.append(fctx.node())
4170 p.append(fctx.node())
4170 else:
4171 else:
4171 p = [cp.node() for cp in ctx.parents()]
4172 p = [cp.node() for cp in ctx.parents()]
4172
4173
4173 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
4174 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
4174 for n in p:
4175 for n in p:
4175 if n != nullid:
4176 if n != nullid:
4176 displayer.show(repo[n])
4177 displayer.show(repo[n])
4177 displayer.close()
4178 displayer.close()
4178
4179
4179 @command('paths', formatteropts, _('[NAME]'),
4180 @command('paths', formatteropts, _('[NAME]'),
4180 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4181 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4181 optionalrepo=True, intents={INTENT_READONLY})
4182 optionalrepo=True, intents={INTENT_READONLY})
4182 def paths(ui, repo, search=None, **opts):
4183 def paths(ui, repo, search=None, **opts):
4183 """show aliases for remote repositories
4184 """show aliases for remote repositories
4184
4185
4185 Show definition of symbolic path name NAME. If no name is given,
4186 Show definition of symbolic path name NAME. If no name is given,
4186 show definition of all available names.
4187 show definition of all available names.
4187
4188
4188 Option -q/--quiet suppresses all output when searching for NAME
4189 Option -q/--quiet suppresses all output when searching for NAME
4189 and shows only the path names when listing all definitions.
4190 and shows only the path names when listing all definitions.
4190
4191
4191 Path names are defined in the [paths] section of your
4192 Path names are defined in the [paths] section of your
4192 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
4193 configuration file and in ``/etc/mercurial/hgrc``. If run inside a
4193 repository, ``.hg/hgrc`` is used, too.
4194 repository, ``.hg/hgrc`` is used, too.
4194
4195
4195 The path names ``default`` and ``default-push`` have a special
4196 The path names ``default`` and ``default-push`` have a special
4196 meaning. When performing a push or pull operation, they are used
4197 meaning. When performing a push or pull operation, they are used
4197 as fallbacks if no location is specified on the command-line.
4198 as fallbacks if no location is specified on the command-line.
4198 When ``default-push`` is set, it will be used for push and
4199 When ``default-push`` is set, it will be used for push and
4199 ``default`` will be used for pull; otherwise ``default`` is used
4200 ``default`` will be used for pull; otherwise ``default`` is used
4200 as the fallback for both. When cloning a repository, the clone
4201 as the fallback for both. When cloning a repository, the clone
4201 source is written as ``default`` in ``.hg/hgrc``.
4202 source is written as ``default`` in ``.hg/hgrc``.
4202
4203
4203 .. note::
4204 .. note::
4204
4205
4205 ``default`` and ``default-push`` apply to all inbound (e.g.
4206 ``default`` and ``default-push`` apply to all inbound (e.g.
4206 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email`
4207 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email`
4207 and :hg:`bundle`) operations.
4208 and :hg:`bundle`) operations.
4208
4209
4209 See :hg:`help urls` for more information.
4210 See :hg:`help urls` for more information.
4210
4211
4211 .. container:: verbose
4212 .. container:: verbose
4212
4213
4213 Template:
4214 Template:
4214
4215
4215 The following keywords are supported. See also :hg:`help templates`.
4216 The following keywords are supported. See also :hg:`help templates`.
4216
4217
4217 :name: String. Symbolic name of the path alias.
4218 :name: String. Symbolic name of the path alias.
4218 :pushurl: String. URL for push operations.
4219 :pushurl: String. URL for push operations.
4219 :url: String. URL or directory path for the other operations.
4220 :url: String. URL or directory path for the other operations.
4220
4221
4221 Returns 0 on success.
4222 Returns 0 on success.
4222 """
4223 """
4223
4224
4224 opts = pycompat.byteskwargs(opts)
4225 opts = pycompat.byteskwargs(opts)
4225 ui.pager('paths')
4226 ui.pager('paths')
4226 if search:
4227 if search:
4227 pathitems = [(name, path) for name, path in ui.paths.iteritems()
4228 pathitems = [(name, path) for name, path in ui.paths.iteritems()
4228 if name == search]
4229 if name == search]
4229 else:
4230 else:
4230 pathitems = sorted(ui.paths.iteritems())
4231 pathitems = sorted(ui.paths.iteritems())
4231
4232
4232 fm = ui.formatter('paths', opts)
4233 fm = ui.formatter('paths', opts)
4233 if fm.isplain():
4234 if fm.isplain():
4234 hidepassword = util.hidepassword
4235 hidepassword = util.hidepassword
4235 else:
4236 else:
4236 hidepassword = bytes
4237 hidepassword = bytes
4237 if ui.quiet:
4238 if ui.quiet:
4238 namefmt = '%s\n'
4239 namefmt = '%s\n'
4239 else:
4240 else:
4240 namefmt = '%s = '
4241 namefmt = '%s = '
4241 showsubopts = not search and not ui.quiet
4242 showsubopts = not search and not ui.quiet
4242
4243
4243 for name, path in pathitems:
4244 for name, path in pathitems:
4244 fm.startitem()
4245 fm.startitem()
4245 fm.condwrite(not search, 'name', namefmt, name)
4246 fm.condwrite(not search, 'name', namefmt, name)
4246 fm.condwrite(not ui.quiet, 'url', '%s\n', hidepassword(path.rawloc))
4247 fm.condwrite(not ui.quiet, 'url', '%s\n', hidepassword(path.rawloc))
4247 for subopt, value in sorted(path.suboptions.items()):
4248 for subopt, value in sorted(path.suboptions.items()):
4248 assert subopt not in ('name', 'url')
4249 assert subopt not in ('name', 'url')
4249 if showsubopts:
4250 if showsubopts:
4250 fm.plain('%s:%s = ' % (name, subopt))
4251 fm.plain('%s:%s = ' % (name, subopt))
4251 fm.condwrite(showsubopts, subopt, '%s\n', value)
4252 fm.condwrite(showsubopts, subopt, '%s\n', value)
4252
4253
4253 fm.end()
4254 fm.end()
4254
4255
4255 if search and not pathitems:
4256 if search and not pathitems:
4256 if not ui.quiet:
4257 if not ui.quiet:
4257 ui.warn(_("not found!\n"))
4258 ui.warn(_("not found!\n"))
4258 return 1
4259 return 1
4259 else:
4260 else:
4260 return 0
4261 return 0
4261
4262
4262 @command('phase',
4263 @command('phase',
4263 [('p', 'public', False, _('set changeset phase to public')),
4264 [('p', 'public', False, _('set changeset phase to public')),
4264 ('d', 'draft', False, _('set changeset phase to draft')),
4265 ('d', 'draft', False, _('set changeset phase to draft')),
4265 ('s', 'secret', False, _('set changeset phase to secret')),
4266 ('s', 'secret', False, _('set changeset phase to secret')),
4266 ('f', 'force', False, _('allow to move boundary backward')),
4267 ('f', 'force', False, _('allow to move boundary backward')),
4267 ('r', 'rev', [], _('target revision'), _('REV')),
4268 ('r', 'rev', [], _('target revision'), _('REV')),
4268 ],
4269 ],
4269 _('[-p|-d|-s] [-f] [-r] [REV...]'),
4270 _('[-p|-d|-s] [-f] [-r] [REV...]'),
4270 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION)
4271 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION)
4271 def phase(ui, repo, *revs, **opts):
4272 def phase(ui, repo, *revs, **opts):
4272 """set or show the current phase name
4273 """set or show the current phase name
4273
4274
4274 With no argument, show the phase name of the current revision(s).
4275 With no argument, show the phase name of the current revision(s).
4275
4276
4276 With one of -p/--public, -d/--draft or -s/--secret, change the
4277 With one of -p/--public, -d/--draft or -s/--secret, change the
4277 phase value of the specified revisions.
4278 phase value of the specified revisions.
4278
4279
4279 Unless -f/--force is specified, :hg:`phase` won't move changesets from a
4280 Unless -f/--force is specified, :hg:`phase` won't move changesets from a
4280 lower phase to a higher phase. Phases are ordered as follows::
4281 lower phase to a higher phase. Phases are ordered as follows::
4281
4282
4282 public < draft < secret
4283 public < draft < secret
4283
4284
4284 Returns 0 on success, 1 if some phases could not be changed.
4285 Returns 0 on success, 1 if some phases could not be changed.
4285
4286
4286 (For more information about the phases concept, see :hg:`help phases`.)
4287 (For more information about the phases concept, see :hg:`help phases`.)
4287 """
4288 """
4288 opts = pycompat.byteskwargs(opts)
4289 opts = pycompat.byteskwargs(opts)
4289 # search for a unique phase argument
4290 # search for a unique phase argument
4290 targetphase = None
4291 targetphase = None
4291 for idx, name in enumerate(phases.cmdphasenames):
4292 for idx, name in enumerate(phases.cmdphasenames):
4292 if opts[name]:
4293 if opts[name]:
4293 if targetphase is not None:
4294 if targetphase is not None:
4294 raise error.Abort(_('only one phase can be specified'))
4295 raise error.Abort(_('only one phase can be specified'))
4295 targetphase = idx
4296 targetphase = idx
4296
4297
4297 # look for specified revision
4298 # look for specified revision
4298 revs = list(revs)
4299 revs = list(revs)
4299 revs.extend(opts['rev'])
4300 revs.extend(opts['rev'])
4300 if not revs:
4301 if not revs:
4301 # display both parents as the second parent phase can influence
4302 # display both parents as the second parent phase can influence
4302 # the phase of a merge commit
4303 # the phase of a merge commit
4303 revs = [c.rev() for c in repo[None].parents()]
4304 revs = [c.rev() for c in repo[None].parents()]
4304
4305
4305 revs = scmutil.revrange(repo, revs)
4306 revs = scmutil.revrange(repo, revs)
4306
4307
4307 ret = 0
4308 ret = 0
4308 if targetphase is None:
4309 if targetphase is None:
4309 # display
4310 # display
4310 for r in revs:
4311 for r in revs:
4311 ctx = repo[r]
4312 ctx = repo[r]
4312 ui.write('%i: %s\n' % (ctx.rev(), ctx.phasestr()))
4313 ui.write('%i: %s\n' % (ctx.rev(), ctx.phasestr()))
4313 else:
4314 else:
4314 with repo.lock(), repo.transaction("phase") as tr:
4315 with repo.lock(), repo.transaction("phase") as tr:
4315 # set phase
4316 # set phase
4316 if not revs:
4317 if not revs:
4317 raise error.Abort(_('empty revision set'))
4318 raise error.Abort(_('empty revision set'))
4318 nodes = [repo[r].node() for r in revs]
4319 nodes = [repo[r].node() for r in revs]
4319 # moving revision from public to draft may hide them
4320 # moving revision from public to draft may hide them
4320 # We have to check result on an unfiltered repository
4321 # We have to check result on an unfiltered repository
4321 unfi = repo.unfiltered()
4322 unfi = repo.unfiltered()
4322 getphase = unfi._phasecache.phase
4323 getphase = unfi._phasecache.phase
4323 olddata = [getphase(unfi, r) for r in unfi]
4324 olddata = [getphase(unfi, r) for r in unfi]
4324 phases.advanceboundary(repo, tr, targetphase, nodes)
4325 phases.advanceboundary(repo, tr, targetphase, nodes)
4325 if opts['force']:
4326 if opts['force']:
4326 phases.retractboundary(repo, tr, targetphase, nodes)
4327 phases.retractboundary(repo, tr, targetphase, nodes)
4327 getphase = unfi._phasecache.phase
4328 getphase = unfi._phasecache.phase
4328 newdata = [getphase(unfi, r) for r in unfi]
4329 newdata = [getphase(unfi, r) for r in unfi]
4329 changes = sum(newdata[r] != olddata[r] for r in unfi)
4330 changes = sum(newdata[r] != olddata[r] for r in unfi)
4330 cl = unfi.changelog
4331 cl = unfi.changelog
4331 rejected = [n for n in nodes
4332 rejected = [n for n in nodes
4332 if newdata[cl.rev(n)] < targetphase]
4333 if newdata[cl.rev(n)] < targetphase]
4333 if rejected:
4334 if rejected:
4334 ui.warn(_('cannot move %i changesets to a higher '
4335 ui.warn(_('cannot move %i changesets to a higher '
4335 'phase, use --force\n') % len(rejected))
4336 'phase, use --force\n') % len(rejected))
4336 ret = 1
4337 ret = 1
4337 if changes:
4338 if changes:
4338 msg = _('phase changed for %i changesets\n') % changes
4339 msg = _('phase changed for %i changesets\n') % changes
4339 if ret:
4340 if ret:
4340 ui.status(msg)
4341 ui.status(msg)
4341 else:
4342 else:
4342 ui.note(msg)
4343 ui.note(msg)
4343 else:
4344 else:
4344 ui.warn(_('no phases changed\n'))
4345 ui.warn(_('no phases changed\n'))
4345 return ret
4346 return ret
4346
4347
4347 def postincoming(ui, repo, modheads, optupdate, checkout, brev):
4348 def postincoming(ui, repo, modheads, optupdate, checkout, brev):
4348 """Run after a changegroup has been added via pull/unbundle
4349 """Run after a changegroup has been added via pull/unbundle
4349
4350
4350 This takes arguments below:
4351 This takes arguments below:
4351
4352
4352 :modheads: change of heads by pull/unbundle
4353 :modheads: change of heads by pull/unbundle
4353 :optupdate: updating working directory is needed or not
4354 :optupdate: updating working directory is needed or not
4354 :checkout: update destination revision (or None to default destination)
4355 :checkout: update destination revision (or None to default destination)
4355 :brev: a name, which might be a bookmark to be activated after updating
4356 :brev: a name, which might be a bookmark to be activated after updating
4356 """
4357 """
4357 if modheads == 0:
4358 if modheads == 0:
4358 return
4359 return
4359 if optupdate:
4360 if optupdate:
4360 try:
4361 try:
4361 return hg.updatetotally(ui, repo, checkout, brev)
4362 return hg.updatetotally(ui, repo, checkout, brev)
4362 except error.UpdateAbort as inst:
4363 except error.UpdateAbort as inst:
4363 msg = _("not updating: %s") % stringutil.forcebytestr(inst)
4364 msg = _("not updating: %s") % stringutil.forcebytestr(inst)
4364 hint = inst.hint
4365 hint = inst.hint
4365 raise error.UpdateAbort(msg, hint=hint)
4366 raise error.UpdateAbort(msg, hint=hint)
4366 if modheads is not None and modheads > 1:
4367 if modheads is not None and modheads > 1:
4367 currentbranchheads = len(repo.branchheads())
4368 currentbranchheads = len(repo.branchheads())
4368 if currentbranchheads == modheads:
4369 if currentbranchheads == modheads:
4369 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
4370 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
4370 elif currentbranchheads > 1:
4371 elif currentbranchheads > 1:
4371 ui.status(_("(run 'hg heads .' to see heads, 'hg merge' to "
4372 ui.status(_("(run 'hg heads .' to see heads, 'hg merge' to "
4372 "merge)\n"))
4373 "merge)\n"))
4373 else:
4374 else:
4374 ui.status(_("(run 'hg heads' to see heads)\n"))
4375 ui.status(_("(run 'hg heads' to see heads)\n"))
4375 elif not ui.configbool('commands', 'update.requiredest'):
4376 elif not ui.configbool('commands', 'update.requiredest'):
4376 ui.status(_("(run 'hg update' to get a working copy)\n"))
4377 ui.status(_("(run 'hg update' to get a working copy)\n"))
4377
4378
4378 @command('pull',
4379 @command('pull',
4379 [('u', 'update', None,
4380 [('u', 'update', None,
4380 _('update to new branch head if new descendants were pulled')),
4381 _('update to new branch head if new descendants were pulled')),
4381 ('f', 'force', None, _('run even when remote repository is unrelated')),
4382 ('f', 'force', None, _('run even when remote repository is unrelated')),
4382 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
4383 ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
4383 ('B', 'bookmark', [], _("bookmark to pull"), _('BOOKMARK')),
4384 ('B', 'bookmark', [], _("bookmark to pull"), _('BOOKMARK')),
4384 ('b', 'branch', [], _('a specific branch you would like to pull'),
4385 ('b', 'branch', [], _('a specific branch you would like to pull'),
4385 _('BRANCH')),
4386 _('BRANCH')),
4386 ] + remoteopts,
4387 ] + remoteopts,
4387 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'),
4388 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]'),
4388 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4389 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4389 helpbasic=True)
4390 helpbasic=True)
4390 def pull(ui, repo, source="default", **opts):
4391 def pull(ui, repo, source="default", **opts):
4391 """pull changes from the specified source
4392 """pull changes from the specified source
4392
4393
4393 Pull changes from a remote repository to a local one.
4394 Pull changes from a remote repository to a local one.
4394
4395
4395 This finds all changes from the repository at the specified path
4396 This finds all changes from the repository at the specified path
4396 or URL and adds them to a local repository (the current one unless
4397 or URL and adds them to a local repository (the current one unless
4397 -R is specified). By default, this does not update the copy of the
4398 -R is specified). By default, this does not update the copy of the
4398 project in the working directory.
4399 project in the working directory.
4399
4400
4400 When cloning from servers that support it, Mercurial may fetch
4401 When cloning from servers that support it, Mercurial may fetch
4401 pre-generated data. When this is done, hooks operating on incoming
4402 pre-generated data. When this is done, hooks operating on incoming
4402 changesets and changegroups may fire more than once, once for each
4403 changesets and changegroups may fire more than once, once for each
4403 pre-generated bundle and as well as for any additional remaining
4404 pre-generated bundle and as well as for any additional remaining
4404 data. See :hg:`help -e clonebundles` for more.
4405 data. See :hg:`help -e clonebundles` for more.
4405
4406
4406 Use :hg:`incoming` if you want to see what would have been added
4407 Use :hg:`incoming` if you want to see what would have been added
4407 by a pull at the time you issued this command. If you then decide
4408 by a pull at the time you issued this command. If you then decide
4408 to add those changes to the repository, you should use :hg:`pull
4409 to add those changes to the repository, you should use :hg:`pull
4409 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
4410 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
4410
4411
4411 If SOURCE is omitted, the 'default' path will be used.
4412 If SOURCE is omitted, the 'default' path will be used.
4412 See :hg:`help urls` for more information.
4413 See :hg:`help urls` for more information.
4413
4414
4414 Specifying bookmark as ``.`` is equivalent to specifying the active
4415 Specifying bookmark as ``.`` is equivalent to specifying the active
4415 bookmark's name.
4416 bookmark's name.
4416
4417
4417 Returns 0 on success, 1 if an update had unresolved files.
4418 Returns 0 on success, 1 if an update had unresolved files.
4418 """
4419 """
4419
4420
4420 opts = pycompat.byteskwargs(opts)
4421 opts = pycompat.byteskwargs(opts)
4421 if ui.configbool('commands', 'update.requiredest') and opts.get('update'):
4422 if ui.configbool('commands', 'update.requiredest') and opts.get('update'):
4422 msg = _('update destination required by configuration')
4423 msg = _('update destination required by configuration')
4423 hint = _('use hg pull followed by hg update DEST')
4424 hint = _('use hg pull followed by hg update DEST')
4424 raise error.Abort(msg, hint=hint)
4425 raise error.Abort(msg, hint=hint)
4425
4426
4426 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
4427 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
4427 ui.status(_('pulling from %s\n') % util.hidepassword(source))
4428 ui.status(_('pulling from %s\n') % util.hidepassword(source))
4428 other = hg.peer(repo, opts, source)
4429 other = hg.peer(repo, opts, source)
4429 try:
4430 try:
4430 revs, checkout = hg.addbranchrevs(repo, other, branches,
4431 revs, checkout = hg.addbranchrevs(repo, other, branches,
4431 opts.get('rev'))
4432 opts.get('rev'))
4432
4433
4433 pullopargs = {}
4434 pullopargs = {}
4434
4435
4435 nodes = None
4436 nodes = None
4436 if opts.get('bookmark') or revs:
4437 if opts.get('bookmark') or revs:
4437 # The list of bookmark used here is the same used to actually update
4438 # The list of bookmark used here is the same used to actually update
4438 # the bookmark names, to avoid the race from issue 4689 and we do
4439 # the bookmark names, to avoid the race from issue 4689 and we do
4439 # all lookup and bookmark queries in one go so they see the same
4440 # all lookup and bookmark queries in one go so they see the same
4440 # version of the server state (issue 4700).
4441 # version of the server state (issue 4700).
4441 nodes = []
4442 nodes = []
4442 fnodes = []
4443 fnodes = []
4443 revs = revs or []
4444 revs = revs or []
4444 if revs and not other.capable('lookup'):
4445 if revs and not other.capable('lookup'):
4445 err = _("other repository doesn't support revision lookup, "
4446 err = _("other repository doesn't support revision lookup, "
4446 "so a rev cannot be specified.")
4447 "so a rev cannot be specified.")
4447 raise error.Abort(err)
4448 raise error.Abort(err)
4448 with other.commandexecutor() as e:
4449 with other.commandexecutor() as e:
4449 fremotebookmarks = e.callcommand('listkeys', {
4450 fremotebookmarks = e.callcommand('listkeys', {
4450 'namespace': 'bookmarks'
4451 'namespace': 'bookmarks'
4451 })
4452 })
4452 for r in revs:
4453 for r in revs:
4453 fnodes.append(e.callcommand('lookup', {'key': r}))
4454 fnodes.append(e.callcommand('lookup', {'key': r}))
4454 remotebookmarks = fremotebookmarks.result()
4455 remotebookmarks = fremotebookmarks.result()
4455 remotebookmarks = bookmarks.unhexlifybookmarks(remotebookmarks)
4456 remotebookmarks = bookmarks.unhexlifybookmarks(remotebookmarks)
4456 pullopargs['remotebookmarks'] = remotebookmarks
4457 pullopargs['remotebookmarks'] = remotebookmarks
4457 for b in opts.get('bookmark', []):
4458 for b in opts.get('bookmark', []):
4458 b = repo._bookmarks.expandname(b)
4459 b = repo._bookmarks.expandname(b)
4459 if b not in remotebookmarks:
4460 if b not in remotebookmarks:
4460 raise error.Abort(_('remote bookmark %s not found!') % b)
4461 raise error.Abort(_('remote bookmark %s not found!') % b)
4461 nodes.append(remotebookmarks[b])
4462 nodes.append(remotebookmarks[b])
4462 for i, rev in enumerate(revs):
4463 for i, rev in enumerate(revs):
4463 node = fnodes[i].result()
4464 node = fnodes[i].result()
4464 nodes.append(node)
4465 nodes.append(node)
4465 if rev == checkout:
4466 if rev == checkout:
4466 checkout = node
4467 checkout = node
4467
4468
4468 wlock = util.nullcontextmanager()
4469 wlock = util.nullcontextmanager()
4469 if opts.get('update'):
4470 if opts.get('update'):
4470 wlock = repo.wlock()
4471 wlock = repo.wlock()
4471 with wlock:
4472 with wlock:
4472 pullopargs.update(opts.get('opargs', {}))
4473 pullopargs.update(opts.get('opargs', {}))
4473 modheads = exchange.pull(repo, other, heads=nodes,
4474 modheads = exchange.pull(repo, other, heads=nodes,
4474 force=opts.get('force'),
4475 force=opts.get('force'),
4475 bookmarks=opts.get('bookmark', ()),
4476 bookmarks=opts.get('bookmark', ()),
4476 opargs=pullopargs).cgresult
4477 opargs=pullopargs).cgresult
4477
4478
4478 # brev is a name, which might be a bookmark to be activated at
4479 # brev is a name, which might be a bookmark to be activated at
4479 # the end of the update. In other words, it is an explicit
4480 # the end of the update. In other words, it is an explicit
4480 # destination of the update
4481 # destination of the update
4481 brev = None
4482 brev = None
4482
4483
4483 if checkout:
4484 if checkout:
4484 checkout = repo.changelog.rev(checkout)
4485 checkout = repo.changelog.rev(checkout)
4485
4486
4486 # order below depends on implementation of
4487 # order below depends on implementation of
4487 # hg.addbranchrevs(). opts['bookmark'] is ignored,
4488 # hg.addbranchrevs(). opts['bookmark'] is ignored,
4488 # because 'checkout' is determined without it.
4489 # because 'checkout' is determined without it.
4489 if opts.get('rev'):
4490 if opts.get('rev'):
4490 brev = opts['rev'][0]
4491 brev = opts['rev'][0]
4491 elif opts.get('branch'):
4492 elif opts.get('branch'):
4492 brev = opts['branch'][0]
4493 brev = opts['branch'][0]
4493 else:
4494 else:
4494 brev = branches[0]
4495 brev = branches[0]
4495 repo._subtoppath = source
4496 repo._subtoppath = source
4496 try:
4497 try:
4497 ret = postincoming(ui, repo, modheads, opts.get('update'),
4498 ret = postincoming(ui, repo, modheads, opts.get('update'),
4498 checkout, brev)
4499 checkout, brev)
4499
4500
4500 finally:
4501 finally:
4501 del repo._subtoppath
4502 del repo._subtoppath
4502
4503
4503 finally:
4504 finally:
4504 other.close()
4505 other.close()
4505 return ret
4506 return ret
4506
4507
4507 @command('push',
4508 @command('push',
4508 [('f', 'force', None, _('force push')),
4509 [('f', 'force', None, _('force push')),
4509 ('r', 'rev', [],
4510 ('r', 'rev', [],
4510 _('a changeset intended to be included in the destination'),
4511 _('a changeset intended to be included in the destination'),
4511 _('REV')),
4512 _('REV')),
4512 ('B', 'bookmark', [], _("bookmark to push"), _('BOOKMARK')),
4513 ('B', 'bookmark', [], _("bookmark to push"), _('BOOKMARK')),
4513 ('b', 'branch', [],
4514 ('b', 'branch', [],
4514 _('a specific branch you would like to push'), _('BRANCH')),
4515 _('a specific branch you would like to push'), _('BRANCH')),
4515 ('', 'new-branch', False, _('allow pushing a new branch')),
4516 ('', 'new-branch', False, _('allow pushing a new branch')),
4516 ('', 'pushvars', [], _('variables that can be sent to server (ADVANCED)')),
4517 ('', 'pushvars', [], _('variables that can be sent to server (ADVANCED)')),
4517 ('', 'publish', False, _('push the changeset as public (EXPERIMENTAL)')),
4518 ('', 'publish', False, _('push the changeset as public (EXPERIMENTAL)')),
4518 ] + remoteopts,
4519 ] + remoteopts,
4519 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'),
4520 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'),
4520 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4521 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
4521 helpbasic=True)
4522 helpbasic=True)
4522 def push(ui, repo, dest=None, **opts):
4523 def push(ui, repo, dest=None, **opts):
4523 """push changes to the specified destination
4524 """push changes to the specified destination
4524
4525
4525 Push changesets from the local repository to the specified
4526 Push changesets from the local repository to the specified
4526 destination.
4527 destination.
4527
4528
4528 This operation is symmetrical to pull: it is identical to a pull
4529 This operation is symmetrical to pull: it is identical to a pull
4529 in the destination repository from the current one.
4530 in the destination repository from the current one.
4530
4531
4531 By default, push will not allow creation of new heads at the
4532 By default, push will not allow creation of new heads at the
4532 destination, since multiple heads would make it unclear which head
4533 destination, since multiple heads would make it unclear which head
4533 to use. In this situation, it is recommended to pull and merge
4534 to use. In this situation, it is recommended to pull and merge
4534 before pushing.
4535 before pushing.
4535
4536
4536 Use --new-branch if you want to allow push to create a new named
4537 Use --new-branch if you want to allow push to create a new named
4537 branch that is not present at the destination. This allows you to
4538 branch that is not present at the destination. This allows you to
4538 only create a new branch without forcing other changes.
4539 only create a new branch without forcing other changes.
4539
4540
4540 .. note::
4541 .. note::
4541
4542
4542 Extra care should be taken with the -f/--force option,
4543 Extra care should be taken with the -f/--force option,
4543 which will push all new heads on all branches, an action which will
4544 which will push all new heads on all branches, an action which will
4544 almost always cause confusion for collaborators.
4545 almost always cause confusion for collaborators.
4545
4546
4546 If -r/--rev is used, the specified revision and all its ancestors
4547 If -r/--rev is used, the specified revision and all its ancestors
4547 will be pushed to the remote repository.
4548 will be pushed to the remote repository.
4548
4549
4549 If -B/--bookmark is used, the specified bookmarked revision, its
4550 If -B/--bookmark is used, the specified bookmarked revision, its
4550 ancestors, and the bookmark will be pushed to the remote
4551 ancestors, and the bookmark will be pushed to the remote
4551 repository. Specifying ``.`` is equivalent to specifying the active
4552 repository. Specifying ``.`` is equivalent to specifying the active
4552 bookmark's name.
4553 bookmark's name.
4553
4554
4554 Please see :hg:`help urls` for important details about ``ssh://``
4555 Please see :hg:`help urls` for important details about ``ssh://``
4555 URLs. If DESTINATION is omitted, a default path will be used.
4556 URLs. If DESTINATION is omitted, a default path will be used.
4556
4557
4557 .. container:: verbose
4558 .. container:: verbose
4558
4559
4559 The --pushvars option sends strings to the server that become
4560 The --pushvars option sends strings to the server that become
4560 environment variables prepended with ``HG_USERVAR_``. For example,
4561 environment variables prepended with ``HG_USERVAR_``. For example,
4561 ``--pushvars ENABLE_FEATURE=true``, provides the server side hooks with
4562 ``--pushvars ENABLE_FEATURE=true``, provides the server side hooks with
4562 ``HG_USERVAR_ENABLE_FEATURE=true`` as part of their environment.
4563 ``HG_USERVAR_ENABLE_FEATURE=true`` as part of their environment.
4563
4564
4564 pushvars can provide for user-overridable hooks as well as set debug
4565 pushvars can provide for user-overridable hooks as well as set debug
4565 levels. One example is having a hook that blocks commits containing
4566 levels. One example is having a hook that blocks commits containing
4566 conflict markers, but enables the user to override the hook if the file
4567 conflict markers, but enables the user to override the hook if the file
4567 is using conflict markers for testing purposes or the file format has
4568 is using conflict markers for testing purposes or the file format has
4568 strings that look like conflict markers.
4569 strings that look like conflict markers.
4569
4570
4570 By default, servers will ignore `--pushvars`. To enable it add the
4571 By default, servers will ignore `--pushvars`. To enable it add the
4571 following to your configuration file::
4572 following to your configuration file::
4572
4573
4573 [push]
4574 [push]
4574 pushvars.server = true
4575 pushvars.server = true
4575
4576
4576 Returns 0 if push was successful, 1 if nothing to push.
4577 Returns 0 if push was successful, 1 if nothing to push.
4577 """
4578 """
4578
4579
4579 opts = pycompat.byteskwargs(opts)
4580 opts = pycompat.byteskwargs(opts)
4580 if opts.get('bookmark'):
4581 if opts.get('bookmark'):
4581 ui.setconfig('bookmarks', 'pushing', opts['bookmark'], 'push')
4582 ui.setconfig('bookmarks', 'pushing', opts['bookmark'], 'push')
4582 for b in opts['bookmark']:
4583 for b in opts['bookmark']:
4583 # translate -B options to -r so changesets get pushed
4584 # translate -B options to -r so changesets get pushed
4584 b = repo._bookmarks.expandname(b)
4585 b = repo._bookmarks.expandname(b)
4585 if b in repo._bookmarks:
4586 if b in repo._bookmarks:
4586 opts.setdefault('rev', []).append(b)
4587 opts.setdefault('rev', []).append(b)
4587 else:
4588 else:
4588 # if we try to push a deleted bookmark, translate it to null
4589 # if we try to push a deleted bookmark, translate it to null
4589 # this lets simultaneous -r, -b options continue working
4590 # this lets simultaneous -r, -b options continue working
4590 opts.setdefault('rev', []).append("null")
4591 opts.setdefault('rev', []).append("null")
4591
4592
4592 path = ui.paths.getpath(dest, default=('default-push', 'default'))
4593 path = ui.paths.getpath(dest, default=('default-push', 'default'))
4593 if not path:
4594 if not path:
4594 raise error.Abort(_('default repository not configured!'),
4595 raise error.Abort(_('default repository not configured!'),
4595 hint=_("see 'hg help config.paths'"))
4596 hint=_("see 'hg help config.paths'"))
4596 dest = path.pushloc or path.loc
4597 dest = path.pushloc or path.loc
4597 branches = (path.branch, opts.get('branch') or [])
4598 branches = (path.branch, opts.get('branch') or [])
4598 ui.status(_('pushing to %s\n') % util.hidepassword(dest))
4599 ui.status(_('pushing to %s\n') % util.hidepassword(dest))
4599 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
4600 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
4600 other = hg.peer(repo, opts, dest)
4601 other = hg.peer(repo, opts, dest)
4601
4602
4602 if revs:
4603 if revs:
4603 revs = [repo[r].node() for r in scmutil.revrange(repo, revs)]
4604 revs = [repo[r].node() for r in scmutil.revrange(repo, revs)]
4604 if not revs:
4605 if not revs:
4605 raise error.Abort(_("specified revisions evaluate to an empty set"),
4606 raise error.Abort(_("specified revisions evaluate to an empty set"),
4606 hint=_("use different revision arguments"))
4607 hint=_("use different revision arguments"))
4607 elif path.pushrev:
4608 elif path.pushrev:
4608 # It doesn't make any sense to specify ancestor revisions. So limit
4609 # It doesn't make any sense to specify ancestor revisions. So limit
4609 # to DAG heads to make discovery simpler.
4610 # to DAG heads to make discovery simpler.
4610 expr = revsetlang.formatspec('heads(%r)', path.pushrev)
4611 expr = revsetlang.formatspec('heads(%r)', path.pushrev)
4611 revs = scmutil.revrange(repo, [expr])
4612 revs = scmutil.revrange(repo, [expr])
4612 revs = [repo[rev].node() for rev in revs]
4613 revs = [repo[rev].node() for rev in revs]
4613 if not revs:
4614 if not revs:
4614 raise error.Abort(_('default push revset for path evaluates to an '
4615 raise error.Abort(_('default push revset for path evaluates to an '
4615 'empty set'))
4616 'empty set'))
4616
4617
4617 repo._subtoppath = dest
4618 repo._subtoppath = dest
4618 try:
4619 try:
4619 # push subrepos depth-first for coherent ordering
4620 # push subrepos depth-first for coherent ordering
4620 c = repo['.']
4621 c = repo['.']
4621 subs = c.substate # only repos that are committed
4622 subs = c.substate # only repos that are committed
4622 for s in sorted(subs):
4623 for s in sorted(subs):
4623 result = c.sub(s).push(opts)
4624 result = c.sub(s).push(opts)
4624 if result == 0:
4625 if result == 0:
4625 return not result
4626 return not result
4626 finally:
4627 finally:
4627 del repo._subtoppath
4628 del repo._subtoppath
4628
4629
4629 opargs = dict(opts.get('opargs', {})) # copy opargs since we may mutate it
4630 opargs = dict(opts.get('opargs', {})) # copy opargs since we may mutate it
4630 opargs.setdefault('pushvars', []).extend(opts.get('pushvars', []))
4631 opargs.setdefault('pushvars', []).extend(opts.get('pushvars', []))
4631
4632
4632 pushop = exchange.push(repo, other, opts.get('force'), revs=revs,
4633 pushop = exchange.push(repo, other, opts.get('force'), revs=revs,
4633 newbranch=opts.get('new_branch'),
4634 newbranch=opts.get('new_branch'),
4634 bookmarks=opts.get('bookmark', ()),
4635 bookmarks=opts.get('bookmark', ()),
4635 publish=opts.get('publish'),
4636 publish=opts.get('publish'),
4636 opargs=opargs)
4637 opargs=opargs)
4637
4638
4638 result = not pushop.cgresult
4639 result = not pushop.cgresult
4639
4640
4640 if pushop.bkresult is not None:
4641 if pushop.bkresult is not None:
4641 if pushop.bkresult == 2:
4642 if pushop.bkresult == 2:
4642 result = 2
4643 result = 2
4643 elif not result and pushop.bkresult:
4644 elif not result and pushop.bkresult:
4644 result = 2
4645 result = 2
4645
4646
4646 return result
4647 return result
4647
4648
4648 @command('recover', [], helpcategory=command.CATEGORY_MAINTENANCE)
4649 @command('recover', [], helpcategory=command.CATEGORY_MAINTENANCE)
4649 def recover(ui, repo):
4650 def recover(ui, repo):
4650 """roll back an interrupted transaction
4651 """roll back an interrupted transaction
4651
4652
4652 Recover from an interrupted commit or pull.
4653 Recover from an interrupted commit or pull.
4653
4654
4654 This command tries to fix the repository status after an
4655 This command tries to fix the repository status after an
4655 interrupted operation. It should only be necessary when Mercurial
4656 interrupted operation. It should only be necessary when Mercurial
4656 suggests it.
4657 suggests it.
4657
4658
4658 Returns 0 if successful, 1 if nothing to recover or verify fails.
4659 Returns 0 if successful, 1 if nothing to recover or verify fails.
4659 """
4660 """
4660 if repo.recover():
4661 if repo.recover():
4661 return hg.verify(repo)
4662 return hg.verify(repo)
4662 return 1
4663 return 1
4663
4664
4664 @command('remove|rm',
4665 @command('remove|rm',
4665 [('A', 'after', None, _('record delete for missing files')),
4666 [('A', 'after', None, _('record delete for missing files')),
4666 ('f', 'force', None,
4667 ('f', 'force', None,
4667 _('forget added files, delete modified files')),
4668 _('forget added files, delete modified files')),
4668 ] + subrepoopts + walkopts + dryrunopts,
4669 ] + subrepoopts + walkopts + dryrunopts,
4669 _('[OPTION]... FILE...'),
4670 _('[OPTION]... FILE...'),
4670 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
4671 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
4671 helpbasic=True, inferrepo=True)
4672 helpbasic=True, inferrepo=True)
4672 def remove(ui, repo, *pats, **opts):
4673 def remove(ui, repo, *pats, **opts):
4673 """remove the specified files on the next commit
4674 """remove the specified files on the next commit
4674
4675
4675 Schedule the indicated files for removal from the current branch.
4676 Schedule the indicated files for removal from the current branch.
4676
4677
4677 This command schedules the files to be removed at the next commit.
4678 This command schedules the files to be removed at the next commit.
4678 To undo a remove before that, see :hg:`revert`. To undo added
4679 To undo a remove before that, see :hg:`revert`. To undo added
4679 files, see :hg:`forget`.
4680 files, see :hg:`forget`.
4680
4681
4681 .. container:: verbose
4682 .. container:: verbose
4682
4683
4683 -A/--after can be used to remove only files that have already
4684 -A/--after can be used to remove only files that have already
4684 been deleted, -f/--force can be used to force deletion, and -Af
4685 been deleted, -f/--force can be used to force deletion, and -Af
4685 can be used to remove files from the next revision without
4686 can be used to remove files from the next revision without
4686 deleting them from the working directory.
4687 deleting them from the working directory.
4687
4688
4688 The following table details the behavior of remove for different
4689 The following table details the behavior of remove for different
4689 file states (columns) and option combinations (rows). The file
4690 file states (columns) and option combinations (rows). The file
4690 states are Added [A], Clean [C], Modified [M] and Missing [!]
4691 states are Added [A], Clean [C], Modified [M] and Missing [!]
4691 (as reported by :hg:`status`). The actions are Warn, Remove
4692 (as reported by :hg:`status`). The actions are Warn, Remove
4692 (from branch) and Delete (from disk):
4693 (from branch) and Delete (from disk):
4693
4694
4694 ========= == == == ==
4695 ========= == == == ==
4695 opt/state A C M !
4696 opt/state A C M !
4696 ========= == == == ==
4697 ========= == == == ==
4697 none W RD W R
4698 none W RD W R
4698 -f R RD RD R
4699 -f R RD RD R
4699 -A W W W R
4700 -A W W W R
4700 -Af R R R R
4701 -Af R R R R
4701 ========= == == == ==
4702 ========= == == == ==
4702
4703
4703 .. note::
4704 .. note::
4704
4705
4705 :hg:`remove` never deletes files in Added [A] state from the
4706 :hg:`remove` never deletes files in Added [A] state from the
4706 working directory, not even if ``--force`` is specified.
4707 working directory, not even if ``--force`` is specified.
4707
4708
4708 Returns 0 on success, 1 if any warnings encountered.
4709 Returns 0 on success, 1 if any warnings encountered.
4709 """
4710 """
4710
4711
4711 opts = pycompat.byteskwargs(opts)
4712 opts = pycompat.byteskwargs(opts)
4712 after, force = opts.get('after'), opts.get('force')
4713 after, force = opts.get('after'), opts.get('force')
4713 dryrun = opts.get('dry_run')
4714 dryrun = opts.get('dry_run')
4714 if not pats and not after:
4715 if not pats and not after:
4715 raise error.Abort(_('no files specified'))
4716 raise error.Abort(_('no files specified'))
4716
4717
4717 m = scmutil.match(repo[None], pats, opts)
4718 m = scmutil.match(repo[None], pats, opts)
4718 subrepos = opts.get('subrepos')
4719 subrepos = opts.get('subrepos')
4719 return cmdutil.remove(ui, repo, m, "", after, force, subrepos,
4720 return cmdutil.remove(ui, repo, m, "", after, force, subrepos,
4720 dryrun=dryrun)
4721 dryrun=dryrun)
4721
4722
4722 @command('rename|move|mv',
4723 @command('rename|move|mv',
4723 [('A', 'after', None, _('record a rename that has already occurred')),
4724 [('A', 'after', None, _('record a rename that has already occurred')),
4724 ('f', 'force', None, _('forcibly copy over an existing managed file')),
4725 ('f', 'force', None, _('forcibly copy over an existing managed file')),
4725 ] + walkopts + dryrunopts,
4726 ] + walkopts + dryrunopts,
4726 _('[OPTION]... SOURCE... DEST'),
4727 _('[OPTION]... SOURCE... DEST'),
4727 helpcategory=command.CATEGORY_WORKING_DIRECTORY)
4728 helpcategory=command.CATEGORY_WORKING_DIRECTORY)
4728 def rename(ui, repo, *pats, **opts):
4729 def rename(ui, repo, *pats, **opts):
4729 """rename files; equivalent of copy + remove
4730 """rename files; equivalent of copy + remove
4730
4731
4731 Mark dest as copies of sources; mark sources for deletion. If dest
4732 Mark dest as copies of sources; mark sources for deletion. If dest
4732 is a directory, copies are put in that directory. If dest is a
4733 is a directory, copies are put in that directory. If dest is a
4733 file, there can only be one source.
4734 file, there can only be one source.
4734
4735
4735 By default, this command copies the contents of files as they
4736 By default, this command copies the contents of files as they
4736 exist in the working directory. If invoked with -A/--after, the
4737 exist in the working directory. If invoked with -A/--after, the
4737 operation is recorded, but no copying is performed.
4738 operation is recorded, but no copying is performed.
4738
4739
4739 This command takes effect at the next commit. To undo a rename
4740 This command takes effect at the next commit. To undo a rename
4740 before that, see :hg:`revert`.
4741 before that, see :hg:`revert`.
4741
4742
4742 Returns 0 on success, 1 if errors are encountered.
4743 Returns 0 on success, 1 if errors are encountered.
4743 """
4744 """
4744 opts = pycompat.byteskwargs(opts)
4745 opts = pycompat.byteskwargs(opts)
4745 with repo.wlock(False):
4746 with repo.wlock(False):
4746 return cmdutil.copy(ui, repo, pats, opts, rename=True)
4747 return cmdutil.copy(ui, repo, pats, opts, rename=True)
4747
4748
4748 @command('resolve',
4749 @command('resolve',
4749 [('a', 'all', None, _('select all unresolved files')),
4750 [('a', 'all', None, _('select all unresolved files')),
4750 ('l', 'list', None, _('list state of files needing merge')),
4751 ('l', 'list', None, _('list state of files needing merge')),
4751 ('m', 'mark', None, _('mark files as resolved')),
4752 ('m', 'mark', None, _('mark files as resolved')),
4752 ('u', 'unmark', None, _('mark files as unresolved')),
4753 ('u', 'unmark', None, _('mark files as unresolved')),
4753 ('n', 'no-status', None, _('hide status prefix')),
4754 ('n', 'no-status', None, _('hide status prefix')),
4754 ('', 're-merge', None, _('re-merge files'))]
4755 ('', 're-merge', None, _('re-merge files'))]
4755 + mergetoolopts + walkopts + formatteropts,
4756 + mergetoolopts + walkopts + formatteropts,
4756 _('[OPTION]... [FILE]...'),
4757 _('[OPTION]... [FILE]...'),
4757 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
4758 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
4758 inferrepo=True)
4759 inferrepo=True)
4759 def resolve(ui, repo, *pats, **opts):
4760 def resolve(ui, repo, *pats, **opts):
4760 """redo merges or set/view the merge status of files
4761 """redo merges or set/view the merge status of files
4761
4762
4762 Merges with unresolved conflicts are often the result of
4763 Merges with unresolved conflicts are often the result of
4763 non-interactive merging using the ``internal:merge`` configuration
4764 non-interactive merging using the ``internal:merge`` configuration
4764 setting, or a command-line merge tool like ``diff3``. The resolve
4765 setting, or a command-line merge tool like ``diff3``. The resolve
4765 command is used to manage the files involved in a merge, after
4766 command is used to manage the files involved in a merge, after
4766 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
4767 :hg:`merge` has been run, and before :hg:`commit` is run (i.e. the
4767 working directory must have two parents). See :hg:`help
4768 working directory must have two parents). See :hg:`help
4768 merge-tools` for information on configuring merge tools.
4769 merge-tools` for information on configuring merge tools.
4769
4770
4770 The resolve command can be used in the following ways:
4771 The resolve command can be used in the following ways:
4771
4772
4772 - :hg:`resolve [--re-merge] [--tool TOOL] FILE...`: attempt to re-merge
4773 - :hg:`resolve [--re-merge] [--tool TOOL] FILE...`: attempt to re-merge
4773 the specified files, discarding any previous merge attempts. Re-merging
4774 the specified files, discarding any previous merge attempts. Re-merging
4774 is not performed for files already marked as resolved. Use ``--all/-a``
4775 is not performed for files already marked as resolved. Use ``--all/-a``
4775 to select all unresolved files. ``--tool`` can be used to specify
4776 to select all unresolved files. ``--tool`` can be used to specify
4776 the merge tool used for the given files. It overrides the HGMERGE
4777 the merge tool used for the given files. It overrides the HGMERGE
4777 environment variable and your configuration files. Previous file
4778 environment variable and your configuration files. Previous file
4778 contents are saved with a ``.orig`` suffix.
4779 contents are saved with a ``.orig`` suffix.
4779
4780
4780 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
4781 - :hg:`resolve -m [FILE]`: mark a file as having been resolved
4781 (e.g. after having manually fixed-up the files). The default is
4782 (e.g. after having manually fixed-up the files). The default is
4782 to mark all unresolved files.
4783 to mark all unresolved files.
4783
4784
4784 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
4785 - :hg:`resolve -u [FILE]...`: mark a file as unresolved. The
4785 default is to mark all resolved files.
4786 default is to mark all resolved files.
4786
4787
4787 - :hg:`resolve -l`: list files which had or still have conflicts.
4788 - :hg:`resolve -l`: list files which had or still have conflicts.
4788 In the printed list, ``U`` = unresolved and ``R`` = resolved.
4789 In the printed list, ``U`` = unresolved and ``R`` = resolved.
4789 You can use ``set:unresolved()`` or ``set:resolved()`` to filter
4790 You can use ``set:unresolved()`` or ``set:resolved()`` to filter
4790 the list. See :hg:`help filesets` for details.
4791 the list. See :hg:`help filesets` for details.
4791
4792
4792 .. note::
4793 .. note::
4793
4794
4794 Mercurial will not let you commit files with unresolved merge
4795 Mercurial will not let you commit files with unresolved merge
4795 conflicts. You must use :hg:`resolve -m ...` before you can
4796 conflicts. You must use :hg:`resolve -m ...` before you can
4796 commit after a conflicting merge.
4797 commit after a conflicting merge.
4797
4798
4798 .. container:: verbose
4799 .. container:: verbose
4799
4800
4800 Template:
4801 Template:
4801
4802
4802 The following keywords are supported in addition to the common template
4803 The following keywords are supported in addition to the common template
4803 keywords and functions. See also :hg:`help templates`.
4804 keywords and functions. See also :hg:`help templates`.
4804
4805
4805 :mergestatus: String. Character denoting merge conflicts, ``U`` or ``R``.
4806 :mergestatus: String. Character denoting merge conflicts, ``U`` or ``R``.
4806 :path: String. Repository-absolute path of the file.
4807 :path: String. Repository-absolute path of the file.
4807
4808
4808 Returns 0 on success, 1 if any files fail a resolve attempt.
4809 Returns 0 on success, 1 if any files fail a resolve attempt.
4809 """
4810 """
4810
4811
4811 opts = pycompat.byteskwargs(opts)
4812 opts = pycompat.byteskwargs(opts)
4812 confirm = ui.configbool('commands', 'resolve.confirm')
4813 confirm = ui.configbool('commands', 'resolve.confirm')
4813 flaglist = 'all mark unmark list no_status re_merge'.split()
4814 flaglist = 'all mark unmark list no_status re_merge'.split()
4814 all, mark, unmark, show, nostatus, remerge = \
4815 all, mark, unmark, show, nostatus, remerge = \
4815 [opts.get(o) for o in flaglist]
4816 [opts.get(o) for o in flaglist]
4816
4817
4817 actioncount = len(list(filter(None, [show, mark, unmark, remerge])))
4818 actioncount = len(list(filter(None, [show, mark, unmark, remerge])))
4818 if actioncount > 1:
4819 if actioncount > 1:
4819 raise error.Abort(_("too many actions specified"))
4820 raise error.Abort(_("too many actions specified"))
4820 elif (actioncount == 0
4821 elif (actioncount == 0
4821 and ui.configbool('commands', 'resolve.explicit-re-merge')):
4822 and ui.configbool('commands', 'resolve.explicit-re-merge')):
4822 hint = _('use --mark, --unmark, --list or --re-merge')
4823 hint = _('use --mark, --unmark, --list or --re-merge')
4823 raise error.Abort(_('no action specified'), hint=hint)
4824 raise error.Abort(_('no action specified'), hint=hint)
4824 if pats and all:
4825 if pats and all:
4825 raise error.Abort(_("can't specify --all and patterns"))
4826 raise error.Abort(_("can't specify --all and patterns"))
4826 if not (all or pats or show or mark or unmark):
4827 if not (all or pats or show or mark or unmark):
4827 raise error.Abort(_('no files or directories specified'),
4828 raise error.Abort(_('no files or directories specified'),
4828 hint=('use --all to re-merge all unresolved files'))
4829 hint=('use --all to re-merge all unresolved files'))
4829
4830
4830 if confirm:
4831 if confirm:
4831 if all:
4832 if all:
4832 if ui.promptchoice(_(b're-merge all unresolved files (yn)?'
4833 if ui.promptchoice(_(b're-merge all unresolved files (yn)?'
4833 b'$$ &Yes $$ &No')):
4834 b'$$ &Yes $$ &No')):
4834 raise error.Abort(_('user quit'))
4835 raise error.Abort(_('user quit'))
4835 if mark and not pats:
4836 if mark and not pats:
4836 if ui.promptchoice(_(b'mark all unresolved files as resolved (yn)?'
4837 if ui.promptchoice(_(b'mark all unresolved files as resolved (yn)?'
4837 b'$$ &Yes $$ &No')):
4838 b'$$ &Yes $$ &No')):
4838 raise error.Abort(_('user quit'))
4839 raise error.Abort(_('user quit'))
4839 if unmark and not pats:
4840 if unmark and not pats:
4840 if ui.promptchoice(_(b'mark all resolved files as unresolved (yn)?'
4841 if ui.promptchoice(_(b'mark all resolved files as unresolved (yn)?'
4841 b'$$ &Yes $$ &No')):
4842 b'$$ &Yes $$ &No')):
4842 raise error.Abort(_('user quit'))
4843 raise error.Abort(_('user quit'))
4843
4844
4844 uipathfn = scmutil.getuipathfn(repo)
4845 uipathfn = scmutil.getuipathfn(repo)
4845
4846
4846 if show:
4847 if show:
4847 ui.pager('resolve')
4848 ui.pager('resolve')
4848 fm = ui.formatter('resolve', opts)
4849 fm = ui.formatter('resolve', opts)
4849 ms = mergemod.mergestate.read(repo)
4850 ms = mergemod.mergestate.read(repo)
4850 wctx = repo[None]
4851 wctx = repo[None]
4851 m = scmutil.match(wctx, pats, opts)
4852 m = scmutil.match(wctx, pats, opts)
4852
4853
4853 # Labels and keys based on merge state. Unresolved path conflicts show
4854 # Labels and keys based on merge state. Unresolved path conflicts show
4854 # as 'P'. Resolved path conflicts show as 'R', the same as normal
4855 # as 'P'. Resolved path conflicts show as 'R', the same as normal
4855 # resolved conflicts.
4856 # resolved conflicts.
4856 mergestateinfo = {
4857 mergestateinfo = {
4857 mergemod.MERGE_RECORD_UNRESOLVED: ('resolve.unresolved', 'U'),
4858 mergemod.MERGE_RECORD_UNRESOLVED: ('resolve.unresolved', 'U'),
4858 mergemod.MERGE_RECORD_RESOLVED: ('resolve.resolved', 'R'),
4859 mergemod.MERGE_RECORD_RESOLVED: ('resolve.resolved', 'R'),
4859 mergemod.MERGE_RECORD_UNRESOLVED_PATH: ('resolve.unresolved', 'P'),
4860 mergemod.MERGE_RECORD_UNRESOLVED_PATH: ('resolve.unresolved', 'P'),
4860 mergemod.MERGE_RECORD_RESOLVED_PATH: ('resolve.resolved', 'R'),
4861 mergemod.MERGE_RECORD_RESOLVED_PATH: ('resolve.resolved', 'R'),
4861 mergemod.MERGE_RECORD_DRIVER_RESOLVED: ('resolve.driverresolved',
4862 mergemod.MERGE_RECORD_DRIVER_RESOLVED: ('resolve.driverresolved',
4862 'D'),
4863 'D'),
4863 }
4864 }
4864
4865
4865 for f in ms:
4866 for f in ms:
4866 if not m(f):
4867 if not m(f):
4867 continue
4868 continue
4868
4869
4869 label, key = mergestateinfo[ms[f]]
4870 label, key = mergestateinfo[ms[f]]
4870 fm.startitem()
4871 fm.startitem()
4871 fm.context(ctx=wctx)
4872 fm.context(ctx=wctx)
4872 fm.condwrite(not nostatus, 'mergestatus', '%s ', key, label=label)
4873 fm.condwrite(not nostatus, 'mergestatus', '%s ', key, label=label)
4873 fm.data(path=f)
4874 fm.data(path=f)
4874 fm.plain('%s\n' % uipathfn(f), label=label)
4875 fm.plain('%s\n' % uipathfn(f), label=label)
4875 fm.end()
4876 fm.end()
4876 return 0
4877 return 0
4877
4878
4878 with repo.wlock():
4879 with repo.wlock():
4879 ms = mergemod.mergestate.read(repo)
4880 ms = mergemod.mergestate.read(repo)
4880
4881
4881 if not (ms.active() or repo.dirstate.p2() != nullid):
4882 if not (ms.active() or repo.dirstate.p2() != nullid):
4882 raise error.Abort(
4883 raise error.Abort(
4883 _('resolve command not applicable when not merging'))
4884 _('resolve command not applicable when not merging'))
4884
4885
4885 wctx = repo[None]
4886 wctx = repo[None]
4886
4887
4887 if (ms.mergedriver
4888 if (ms.mergedriver
4888 and ms.mdstate() == mergemod.MERGE_DRIVER_STATE_UNMARKED):
4889 and ms.mdstate() == mergemod.MERGE_DRIVER_STATE_UNMARKED):
4889 proceed = mergemod.driverpreprocess(repo, ms, wctx)
4890 proceed = mergemod.driverpreprocess(repo, ms, wctx)
4890 ms.commit()
4891 ms.commit()
4891 # allow mark and unmark to go through
4892 # allow mark and unmark to go through
4892 if not mark and not unmark and not proceed:
4893 if not mark and not unmark and not proceed:
4893 return 1
4894 return 1
4894
4895
4895 m = scmutil.match(wctx, pats, opts)
4896 m = scmutil.match(wctx, pats, opts)
4896 ret = 0
4897 ret = 0
4897 didwork = False
4898 didwork = False
4898 runconclude = False
4899 runconclude = False
4899
4900
4900 tocomplete = []
4901 tocomplete = []
4901 hasconflictmarkers = []
4902 hasconflictmarkers = []
4902 if mark:
4903 if mark:
4903 markcheck = ui.config('commands', 'resolve.mark-check')
4904 markcheck = ui.config('commands', 'resolve.mark-check')
4904 if markcheck not in ['warn', 'abort']:
4905 if markcheck not in ['warn', 'abort']:
4905 # Treat all invalid / unrecognized values as 'none'.
4906 # Treat all invalid / unrecognized values as 'none'.
4906 markcheck = False
4907 markcheck = False
4907 for f in ms:
4908 for f in ms:
4908 if not m(f):
4909 if not m(f):
4909 continue
4910 continue
4910
4911
4911 didwork = True
4912 didwork = True
4912
4913
4913 # don't let driver-resolved files be marked, and run the conclude
4914 # don't let driver-resolved files be marked, and run the conclude
4914 # step if asked to resolve
4915 # step if asked to resolve
4915 if ms[f] == mergemod.MERGE_RECORD_DRIVER_RESOLVED:
4916 if ms[f] == mergemod.MERGE_RECORD_DRIVER_RESOLVED:
4916 exact = m.exact(f)
4917 exact = m.exact(f)
4917 if mark:
4918 if mark:
4918 if exact:
4919 if exact:
4919 ui.warn(_('not marking %s as it is driver-resolved\n')
4920 ui.warn(_('not marking %s as it is driver-resolved\n')
4920 % f)
4921 % f)
4921 elif unmark:
4922 elif unmark:
4922 if exact:
4923 if exact:
4923 ui.warn(_('not unmarking %s as it is driver-resolved\n')
4924 ui.warn(_('not unmarking %s as it is driver-resolved\n')
4924 % f)
4925 % f)
4925 else:
4926 else:
4926 runconclude = True
4927 runconclude = True
4927 continue
4928 continue
4928
4929
4929 # path conflicts must be resolved manually
4930 # path conflicts must be resolved manually
4930 if ms[f] in (mergemod.MERGE_RECORD_UNRESOLVED_PATH,
4931 if ms[f] in (mergemod.MERGE_RECORD_UNRESOLVED_PATH,
4931 mergemod.MERGE_RECORD_RESOLVED_PATH):
4932 mergemod.MERGE_RECORD_RESOLVED_PATH):
4932 if mark:
4933 if mark:
4933 ms.mark(f, mergemod.MERGE_RECORD_RESOLVED_PATH)
4934 ms.mark(f, mergemod.MERGE_RECORD_RESOLVED_PATH)
4934 elif unmark:
4935 elif unmark:
4935 ms.mark(f, mergemod.MERGE_RECORD_UNRESOLVED_PATH)
4936 ms.mark(f, mergemod.MERGE_RECORD_UNRESOLVED_PATH)
4936 elif ms[f] == mergemod.MERGE_RECORD_UNRESOLVED_PATH:
4937 elif ms[f] == mergemod.MERGE_RECORD_UNRESOLVED_PATH:
4937 ui.warn(_('%s: path conflict must be resolved manually\n')
4938 ui.warn(_('%s: path conflict must be resolved manually\n')
4938 % f)
4939 % f)
4939 continue
4940 continue
4940
4941
4941 if mark:
4942 if mark:
4942 if markcheck:
4943 if markcheck:
4943 fdata = repo.wvfs.tryread(f)
4944 fdata = repo.wvfs.tryread(f)
4944 if filemerge.hasconflictmarkers(fdata) and \
4945 if filemerge.hasconflictmarkers(fdata) and \
4945 ms[f] != mergemod.MERGE_RECORD_RESOLVED:
4946 ms[f] != mergemod.MERGE_RECORD_RESOLVED:
4946 hasconflictmarkers.append(f)
4947 hasconflictmarkers.append(f)
4947 ms.mark(f, mergemod.MERGE_RECORD_RESOLVED)
4948 ms.mark(f, mergemod.MERGE_RECORD_RESOLVED)
4948 elif unmark:
4949 elif unmark:
4949 ms.mark(f, mergemod.MERGE_RECORD_UNRESOLVED)
4950 ms.mark(f, mergemod.MERGE_RECORD_UNRESOLVED)
4950 else:
4951 else:
4951 # backup pre-resolve (merge uses .orig for its own purposes)
4952 # backup pre-resolve (merge uses .orig for its own purposes)
4952 a = repo.wjoin(f)
4953 a = repo.wjoin(f)
4953 try:
4954 try:
4954 util.copyfile(a, a + ".resolve")
4955 util.copyfile(a, a + ".resolve")
4955 except (IOError, OSError) as inst:
4956 except (IOError, OSError) as inst:
4956 if inst.errno != errno.ENOENT:
4957 if inst.errno != errno.ENOENT:
4957 raise
4958 raise
4958
4959
4959 try:
4960 try:
4960 # preresolve file
4961 # preresolve file
4961 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
4962 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
4962 with ui.configoverride(overrides, 'resolve'):
4963 with ui.configoverride(overrides, 'resolve'):
4963 complete, r = ms.preresolve(f, wctx)
4964 complete, r = ms.preresolve(f, wctx)
4964 if not complete:
4965 if not complete:
4965 tocomplete.append(f)
4966 tocomplete.append(f)
4966 elif r:
4967 elif r:
4967 ret = 1
4968 ret = 1
4968 finally:
4969 finally:
4969 ms.commit()
4970 ms.commit()
4970
4971
4971 # replace filemerge's .orig file with our resolve file, but only
4972 # replace filemerge's .orig file with our resolve file, but only
4972 # for merges that are complete
4973 # for merges that are complete
4973 if complete:
4974 if complete:
4974 try:
4975 try:
4975 util.rename(a + ".resolve",
4976 util.rename(a + ".resolve",
4976 scmutil.backuppath(ui, repo, f))
4977 scmutil.backuppath(ui, repo, f))
4977 except OSError as inst:
4978 except OSError as inst:
4978 if inst.errno != errno.ENOENT:
4979 if inst.errno != errno.ENOENT:
4979 raise
4980 raise
4980
4981
4981 if hasconflictmarkers:
4982 if hasconflictmarkers:
4982 ui.warn(_('warning: the following files still have conflict '
4983 ui.warn(_('warning: the following files still have conflict '
4983 'markers:\n ') + '\n '.join(hasconflictmarkers) + '\n')
4984 'markers:\n ') + '\n '.join(hasconflictmarkers) + '\n')
4984 if markcheck == 'abort' and not all and not pats:
4985 if markcheck == 'abort' and not all and not pats:
4985 raise error.Abort(_('conflict markers detected'),
4986 raise error.Abort(_('conflict markers detected'),
4986 hint=_('use --all to mark anyway'))
4987 hint=_('use --all to mark anyway'))
4987
4988
4988 for f in tocomplete:
4989 for f in tocomplete:
4989 try:
4990 try:
4990 # resolve file
4991 # resolve file
4991 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
4992 overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
4992 with ui.configoverride(overrides, 'resolve'):
4993 with ui.configoverride(overrides, 'resolve'):
4993 r = ms.resolve(f, wctx)
4994 r = ms.resolve(f, wctx)
4994 if r:
4995 if r:
4995 ret = 1
4996 ret = 1
4996 finally:
4997 finally:
4997 ms.commit()
4998 ms.commit()
4998
4999
4999 # replace filemerge's .orig file with our resolve file
5000 # replace filemerge's .orig file with our resolve file
5000 a = repo.wjoin(f)
5001 a = repo.wjoin(f)
5001 try:
5002 try:
5002 util.rename(a + ".resolve", scmutil.backuppath(ui, repo, f))
5003 util.rename(a + ".resolve", scmutil.backuppath(ui, repo, f))
5003 except OSError as inst:
5004 except OSError as inst:
5004 if inst.errno != errno.ENOENT:
5005 if inst.errno != errno.ENOENT:
5005 raise
5006 raise
5006
5007
5007 ms.commit()
5008 ms.commit()
5008 ms.recordactions()
5009 ms.recordactions()
5009
5010
5010 if not didwork and pats:
5011 if not didwork and pats:
5011 hint = None
5012 hint = None
5012 if not any([p for p in pats if p.find(':') >= 0]):
5013 if not any([p for p in pats if p.find(':') >= 0]):
5013 pats = ['path:%s' % p for p in pats]
5014 pats = ['path:%s' % p for p in pats]
5014 m = scmutil.match(wctx, pats, opts)
5015 m = scmutil.match(wctx, pats, opts)
5015 for f in ms:
5016 for f in ms:
5016 if not m(f):
5017 if not m(f):
5017 continue
5018 continue
5018 def flag(o):
5019 def flag(o):
5019 if o == 're_merge':
5020 if o == 're_merge':
5020 return '--re-merge '
5021 return '--re-merge '
5021 return '-%s ' % o[0:1]
5022 return '-%s ' % o[0:1]
5022 flags = ''.join([flag(o) for o in flaglist if opts.get(o)])
5023 flags = ''.join([flag(o) for o in flaglist if opts.get(o)])
5023 hint = _("(try: hg resolve %s%s)\n") % (
5024 hint = _("(try: hg resolve %s%s)\n") % (
5024 flags,
5025 flags,
5025 ' '.join(pats))
5026 ' '.join(pats))
5026 break
5027 break
5027 ui.warn(_("arguments do not match paths that need resolving\n"))
5028 ui.warn(_("arguments do not match paths that need resolving\n"))
5028 if hint:
5029 if hint:
5029 ui.warn(hint)
5030 ui.warn(hint)
5030 elif ms.mergedriver and ms.mdstate() != 's':
5031 elif ms.mergedriver and ms.mdstate() != 's':
5031 # run conclude step when either a driver-resolved file is requested
5032 # run conclude step when either a driver-resolved file is requested
5032 # or there are no driver-resolved files
5033 # or there are no driver-resolved files
5033 # we can't use 'ret' to determine whether any files are unresolved
5034 # we can't use 'ret' to determine whether any files are unresolved
5034 # because we might not have tried to resolve some
5035 # because we might not have tried to resolve some
5035 if ((runconclude or not list(ms.driverresolved()))
5036 if ((runconclude or not list(ms.driverresolved()))
5036 and not list(ms.unresolved())):
5037 and not list(ms.unresolved())):
5037 proceed = mergemod.driverconclude(repo, ms, wctx)
5038 proceed = mergemod.driverconclude(repo, ms, wctx)
5038 ms.commit()
5039 ms.commit()
5039 if not proceed:
5040 if not proceed:
5040 return 1
5041 return 1
5041
5042
5042 # Nudge users into finishing an unfinished operation
5043 # Nudge users into finishing an unfinished operation
5043 unresolvedf = list(ms.unresolved())
5044 unresolvedf = list(ms.unresolved())
5044 driverresolvedf = list(ms.driverresolved())
5045 driverresolvedf = list(ms.driverresolved())
5045 if not unresolvedf and not driverresolvedf:
5046 if not unresolvedf and not driverresolvedf:
5046 ui.status(_('(no more unresolved files)\n'))
5047 ui.status(_('(no more unresolved files)\n'))
5047 cmdutil.checkafterresolved(repo)
5048 cmdutil.checkafterresolved(repo)
5048 elif not unresolvedf:
5049 elif not unresolvedf:
5049 ui.status(_('(no more unresolved files -- '
5050 ui.status(_('(no more unresolved files -- '
5050 'run "hg resolve --all" to conclude)\n'))
5051 'run "hg resolve --all" to conclude)\n'))
5051
5052
5052 return ret
5053 return ret
5053
5054
5054 @command('revert',
5055 @command('revert',
5055 [('a', 'all', None, _('revert all changes when no arguments given')),
5056 [('a', 'all', None, _('revert all changes when no arguments given')),
5056 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
5057 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
5057 ('r', 'rev', '', _('revert to the specified revision'), _('REV')),
5058 ('r', 'rev', '', _('revert to the specified revision'), _('REV')),
5058 ('C', 'no-backup', None, _('do not save backup copies of files')),
5059 ('C', 'no-backup', None, _('do not save backup copies of files')),
5059 ('i', 'interactive', None, _('interactively select the changes')),
5060 ('i', 'interactive', None, _('interactively select the changes')),
5060 ] + walkopts + dryrunopts,
5061 ] + walkopts + dryrunopts,
5061 _('[OPTION]... [-r REV] [NAME]...'),
5062 _('[OPTION]... [-r REV] [NAME]...'),
5062 helpcategory=command.CATEGORY_WORKING_DIRECTORY)
5063 helpcategory=command.CATEGORY_WORKING_DIRECTORY)
5063 def revert(ui, repo, *pats, **opts):
5064 def revert(ui, repo, *pats, **opts):
5064 """restore files to their checkout state
5065 """restore files to their checkout state
5065
5066
5066 .. note::
5067 .. note::
5067
5068
5068 To check out earlier revisions, you should use :hg:`update REV`.
5069 To check out earlier revisions, you should use :hg:`update REV`.
5069 To cancel an uncommitted merge (and lose your changes),
5070 To cancel an uncommitted merge (and lose your changes),
5070 use :hg:`merge --abort`.
5071 use :hg:`merge --abort`.
5071
5072
5072 With no revision specified, revert the specified files or directories
5073 With no revision specified, revert the specified files or directories
5073 to the contents they had in the parent of the working directory.
5074 to the contents they had in the parent of the working directory.
5074 This restores the contents of files to an unmodified
5075 This restores the contents of files to an unmodified
5075 state and unschedules adds, removes, copies, and renames. If the
5076 state and unschedules adds, removes, copies, and renames. If the
5076 working directory has two parents, you must explicitly specify a
5077 working directory has two parents, you must explicitly specify a
5077 revision.
5078 revision.
5078
5079
5079 Using the -r/--rev or -d/--date options, revert the given files or
5080 Using the -r/--rev or -d/--date options, revert the given files or
5080 directories to their states as of a specific revision. Because
5081 directories to their states as of a specific revision. Because
5081 revert does not change the working directory parents, this will
5082 revert does not change the working directory parents, this will
5082 cause these files to appear modified. This can be helpful to "back
5083 cause these files to appear modified. This can be helpful to "back
5083 out" some or all of an earlier change. See :hg:`backout` for a
5084 out" some or all of an earlier change. See :hg:`backout` for a
5084 related method.
5085 related method.
5085
5086
5086 Modified files are saved with a .orig suffix before reverting.
5087 Modified files are saved with a .orig suffix before reverting.
5087 To disable these backups, use --no-backup. It is possible to store
5088 To disable these backups, use --no-backup. It is possible to store
5088 the backup files in a custom directory relative to the root of the
5089 the backup files in a custom directory relative to the root of the
5089 repository by setting the ``ui.origbackuppath`` configuration
5090 repository by setting the ``ui.origbackuppath`` configuration
5090 option.
5091 option.
5091
5092
5092 See :hg:`help dates` for a list of formats valid for -d/--date.
5093 See :hg:`help dates` for a list of formats valid for -d/--date.
5093
5094
5094 See :hg:`help backout` for a way to reverse the effect of an
5095 See :hg:`help backout` for a way to reverse the effect of an
5095 earlier changeset.
5096 earlier changeset.
5096
5097
5097 Returns 0 on success.
5098 Returns 0 on success.
5098 """
5099 """
5099
5100
5100 opts = pycompat.byteskwargs(opts)
5101 opts = pycompat.byteskwargs(opts)
5101 if opts.get("date"):
5102 if opts.get("date"):
5102 if opts.get("rev"):
5103 if opts.get("rev"):
5103 raise error.Abort(_("you can't specify a revision and a date"))
5104 raise error.Abort(_("you can't specify a revision and a date"))
5104 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
5105 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
5105
5106
5106 parent, p2 = repo.dirstate.parents()
5107 parent, p2 = repo.dirstate.parents()
5107 if not opts.get('rev') and p2 != nullid:
5108 if not opts.get('rev') and p2 != nullid:
5108 # revert after merge is a trap for new users (issue2915)
5109 # revert after merge is a trap for new users (issue2915)
5109 raise error.Abort(_('uncommitted merge with no revision specified'),
5110 raise error.Abort(_('uncommitted merge with no revision specified'),
5110 hint=_("use 'hg update' or see 'hg help revert'"))
5111 hint=_("use 'hg update' or see 'hg help revert'"))
5111
5112
5112 rev = opts.get('rev')
5113 rev = opts.get('rev')
5113 if rev:
5114 if rev:
5114 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
5115 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
5115 ctx = scmutil.revsingle(repo, rev)
5116 ctx = scmutil.revsingle(repo, rev)
5116
5117
5117 if (not (pats or opts.get('include') or opts.get('exclude') or
5118 if (not (pats or opts.get('include') or opts.get('exclude') or
5118 opts.get('all') or opts.get('interactive'))):
5119 opts.get('all') or opts.get('interactive'))):
5119 msg = _("no files or directories specified")
5120 msg = _("no files or directories specified")
5120 if p2 != nullid:
5121 if p2 != nullid:
5121 hint = _("uncommitted merge, use --all to discard all changes,"
5122 hint = _("uncommitted merge, use --all to discard all changes,"
5122 " or 'hg update -C .' to abort the merge")
5123 " or 'hg update -C .' to abort the merge")
5123 raise error.Abort(msg, hint=hint)
5124 raise error.Abort(msg, hint=hint)
5124 dirty = any(repo.status())
5125 dirty = any(repo.status())
5125 node = ctx.node()
5126 node = ctx.node()
5126 if node != parent:
5127 if node != parent:
5127 if dirty:
5128 if dirty:
5128 hint = _("uncommitted changes, use --all to discard all"
5129 hint = _("uncommitted changes, use --all to discard all"
5129 " changes, or 'hg update %d' to update") % ctx.rev()
5130 " changes, or 'hg update %d' to update") % ctx.rev()
5130 else:
5131 else:
5131 hint = _("use --all to revert all files,"
5132 hint = _("use --all to revert all files,"
5132 " or 'hg update %d' to update") % ctx.rev()
5133 " or 'hg update %d' to update") % ctx.rev()
5133 elif dirty:
5134 elif dirty:
5134 hint = _("uncommitted changes, use --all to discard all changes")
5135 hint = _("uncommitted changes, use --all to discard all changes")
5135 else:
5136 else:
5136 hint = _("use --all to revert all files")
5137 hint = _("use --all to revert all files")
5137 raise error.Abort(msg, hint=hint)
5138 raise error.Abort(msg, hint=hint)
5138
5139
5139 return cmdutil.revert(ui, repo, ctx, (parent, p2), *pats,
5140 return cmdutil.revert(ui, repo, ctx, (parent, p2), *pats,
5140 **pycompat.strkwargs(opts))
5141 **pycompat.strkwargs(opts))
5141
5142
5142 @command(
5143 @command(
5143 'rollback',
5144 'rollback',
5144 dryrunopts + [('f', 'force', False, _('ignore safety measures'))],
5145 dryrunopts + [('f', 'force', False, _('ignore safety measures'))],
5145 helpcategory=command.CATEGORY_MAINTENANCE)
5146 helpcategory=command.CATEGORY_MAINTENANCE)
5146 def rollback(ui, repo, **opts):
5147 def rollback(ui, repo, **opts):
5147 """roll back the last transaction (DANGEROUS) (DEPRECATED)
5148 """roll back the last transaction (DANGEROUS) (DEPRECATED)
5148
5149
5149 Please use :hg:`commit --amend` instead of rollback to correct
5150 Please use :hg:`commit --amend` instead of rollback to correct
5150 mistakes in the last commit.
5151 mistakes in the last commit.
5151
5152
5152 This command should be used with care. There is only one level of
5153 This command should be used with care. There is only one level of
5153 rollback, and there is no way to undo a rollback. It will also
5154 rollback, and there is no way to undo a rollback. It will also
5154 restore the dirstate at the time of the last transaction, losing
5155 restore the dirstate at the time of the last transaction, losing
5155 any dirstate changes since that time. This command does not alter
5156 any dirstate changes since that time. This command does not alter
5156 the working directory.
5157 the working directory.
5157
5158
5158 Transactions are used to encapsulate the effects of all commands
5159 Transactions are used to encapsulate the effects of all commands
5159 that create new changesets or propagate existing changesets into a
5160 that create new changesets or propagate existing changesets into a
5160 repository.
5161 repository.
5161
5162
5162 .. container:: verbose
5163 .. container:: verbose
5163
5164
5164 For example, the following commands are transactional, and their
5165 For example, the following commands are transactional, and their
5165 effects can be rolled back:
5166 effects can be rolled back:
5166
5167
5167 - commit
5168 - commit
5168 - import
5169 - import
5169 - pull
5170 - pull
5170 - push (with this repository as the destination)
5171 - push (with this repository as the destination)
5171 - unbundle
5172 - unbundle
5172
5173
5173 To avoid permanent data loss, rollback will refuse to rollback a
5174 To avoid permanent data loss, rollback will refuse to rollback a
5174 commit transaction if it isn't checked out. Use --force to
5175 commit transaction if it isn't checked out. Use --force to
5175 override this protection.
5176 override this protection.
5176
5177
5177 The rollback command can be entirely disabled by setting the
5178 The rollback command can be entirely disabled by setting the
5178 ``ui.rollback`` configuration setting to false. If you're here
5179 ``ui.rollback`` configuration setting to false. If you're here
5179 because you want to use rollback and it's disabled, you can
5180 because you want to use rollback and it's disabled, you can
5180 re-enable the command by setting ``ui.rollback`` to true.
5181 re-enable the command by setting ``ui.rollback`` to true.
5181
5182
5182 This command is not intended for use on public repositories. Once
5183 This command is not intended for use on public repositories. Once
5183 changes are visible for pull by other users, rolling a transaction
5184 changes are visible for pull by other users, rolling a transaction
5184 back locally is ineffective (someone else may already have pulled
5185 back locally is ineffective (someone else may already have pulled
5185 the changes). Furthermore, a race is possible with readers of the
5186 the changes). Furthermore, a race is possible with readers of the
5186 repository; for example an in-progress pull from the repository
5187 repository; for example an in-progress pull from the repository
5187 may fail if a rollback is performed.
5188 may fail if a rollback is performed.
5188
5189
5189 Returns 0 on success, 1 if no rollback data is available.
5190 Returns 0 on success, 1 if no rollback data is available.
5190 """
5191 """
5191 if not ui.configbool('ui', 'rollback'):
5192 if not ui.configbool('ui', 'rollback'):
5192 raise error.Abort(_('rollback is disabled because it is unsafe'),
5193 raise error.Abort(_('rollback is disabled because it is unsafe'),
5193 hint=('see `hg help -v rollback` for information'))
5194 hint=('see `hg help -v rollback` for information'))
5194 return repo.rollback(dryrun=opts.get(r'dry_run'),
5195 return repo.rollback(dryrun=opts.get(r'dry_run'),
5195 force=opts.get(r'force'))
5196 force=opts.get(r'force'))
5196
5197
5197 @command(
5198 @command(
5198 'root', [], intents={INTENT_READONLY},
5199 'root', [], intents={INTENT_READONLY},
5199 helpcategory=command.CATEGORY_WORKING_DIRECTORY)
5200 helpcategory=command.CATEGORY_WORKING_DIRECTORY)
5200 def root(ui, repo):
5201 def root(ui, repo):
5201 """print the root (top) of the current working directory
5202 """print the root (top) of the current working directory
5202
5203
5203 Print the root directory of the current repository.
5204 Print the root directory of the current repository.
5204
5205
5205 Returns 0 on success.
5206 Returns 0 on success.
5206 """
5207 """
5207 ui.write(repo.root + "\n")
5208 ui.write(repo.root + "\n")
5208
5209
5209 @command('serve',
5210 @command('serve',
5210 [('A', 'accesslog', '', _('name of access log file to write to'),
5211 [('A', 'accesslog', '', _('name of access log file to write to'),
5211 _('FILE')),
5212 _('FILE')),
5212 ('d', 'daemon', None, _('run server in background')),
5213 ('d', 'daemon', None, _('run server in background')),
5213 ('', 'daemon-postexec', [], _('used internally by daemon mode')),
5214 ('', 'daemon-postexec', [], _('used internally by daemon mode')),
5214 ('E', 'errorlog', '', _('name of error log file to write to'), _('FILE')),
5215 ('E', 'errorlog', '', _('name of error log file to write to'), _('FILE')),
5215 # use string type, then we can check if something was passed
5216 # use string type, then we can check if something was passed
5216 ('p', 'port', '', _('port to listen on (default: 8000)'), _('PORT')),
5217 ('p', 'port', '', _('port to listen on (default: 8000)'), _('PORT')),
5217 ('a', 'address', '', _('address to listen on (default: all interfaces)'),
5218 ('a', 'address', '', _('address to listen on (default: all interfaces)'),
5218 _('ADDR')),
5219 _('ADDR')),
5219 ('', 'prefix', '', _('prefix path to serve from (default: server root)'),
5220 ('', 'prefix', '', _('prefix path to serve from (default: server root)'),
5220 _('PREFIX')),
5221 _('PREFIX')),
5221 ('n', 'name', '',
5222 ('n', 'name', '',
5222 _('name to show in web pages (default: working directory)'), _('NAME')),
5223 _('name to show in web pages (default: working directory)'), _('NAME')),
5223 ('', 'web-conf', '',
5224 ('', 'web-conf', '',
5224 _("name of the hgweb config file (see 'hg help hgweb')"), _('FILE')),
5225 _("name of the hgweb config file (see 'hg help hgweb')"), _('FILE')),
5225 ('', 'webdir-conf', '', _('name of the hgweb config file (DEPRECATED)'),
5226 ('', 'webdir-conf', '', _('name of the hgweb config file (DEPRECATED)'),
5226 _('FILE')),
5227 _('FILE')),
5227 ('', 'pid-file', '', _('name of file to write process ID to'), _('FILE')),
5228 ('', 'pid-file', '', _('name of file to write process ID to'), _('FILE')),
5228 ('', 'stdio', None, _('for remote clients (ADVANCED)')),
5229 ('', 'stdio', None, _('for remote clients (ADVANCED)')),
5229 ('', 'cmdserver', '', _('for remote clients (ADVANCED)'), _('MODE')),
5230 ('', 'cmdserver', '', _('for remote clients (ADVANCED)'), _('MODE')),
5230 ('t', 'templates', '', _('web templates to use'), _('TEMPLATE')),
5231 ('t', 'templates', '', _('web templates to use'), _('TEMPLATE')),
5231 ('', 'style', '', _('template style to use'), _('STYLE')),
5232 ('', 'style', '', _('template style to use'), _('STYLE')),
5232 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
5233 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
5233 ('', 'certificate', '', _('SSL certificate file'), _('FILE')),
5234 ('', 'certificate', '', _('SSL certificate file'), _('FILE')),
5234 ('', 'print-url', None, _('start and print only the URL'))]
5235 ('', 'print-url', None, _('start and print only the URL'))]
5235 + subrepoopts,
5236 + subrepoopts,
5236 _('[OPTION]...'),
5237 _('[OPTION]...'),
5237 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5238 helpcategory=command.CATEGORY_REMOTE_REPO_MANAGEMENT,
5238 helpbasic=True, optionalrepo=True)
5239 helpbasic=True, optionalrepo=True)
5239 def serve(ui, repo, **opts):
5240 def serve(ui, repo, **opts):
5240 """start stand-alone webserver
5241 """start stand-alone webserver
5241
5242
5242 Start a local HTTP repository browser and pull server. You can use
5243 Start a local HTTP repository browser and pull server. You can use
5243 this for ad-hoc sharing and browsing of repositories. It is
5244 this for ad-hoc sharing and browsing of repositories. It is
5244 recommended to use a real web server to serve a repository for
5245 recommended to use a real web server to serve a repository for
5245 longer periods of time.
5246 longer periods of time.
5246
5247
5247 Please note that the server does not implement access control.
5248 Please note that the server does not implement access control.
5248 This means that, by default, anybody can read from the server and
5249 This means that, by default, anybody can read from the server and
5249 nobody can write to it by default. Set the ``web.allow-push``
5250 nobody can write to it by default. Set the ``web.allow-push``
5250 option to ``*`` to allow everybody to push to the server. You
5251 option to ``*`` to allow everybody to push to the server. You
5251 should use a real web server if you need to authenticate users.
5252 should use a real web server if you need to authenticate users.
5252
5253
5253 By default, the server logs accesses to stdout and errors to
5254 By default, the server logs accesses to stdout and errors to
5254 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
5255 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
5255 files.
5256 files.
5256
5257
5257 To have the server choose a free port number to listen on, specify
5258 To have the server choose a free port number to listen on, specify
5258 a port number of 0; in this case, the server will print the port
5259 a port number of 0; in this case, the server will print the port
5259 number it uses.
5260 number it uses.
5260
5261
5261 Returns 0 on success.
5262 Returns 0 on success.
5262 """
5263 """
5263
5264
5264 opts = pycompat.byteskwargs(opts)
5265 opts = pycompat.byteskwargs(opts)
5265 if opts["stdio"] and opts["cmdserver"]:
5266 if opts["stdio"] and opts["cmdserver"]:
5266 raise error.Abort(_("cannot use --stdio with --cmdserver"))
5267 raise error.Abort(_("cannot use --stdio with --cmdserver"))
5267 if opts["print_url"] and ui.verbose:
5268 if opts["print_url"] and ui.verbose:
5268 raise error.Abort(_("cannot use --print-url with --verbose"))
5269 raise error.Abort(_("cannot use --print-url with --verbose"))
5269
5270
5270 if opts["stdio"]:
5271 if opts["stdio"]:
5271 if repo is None:
5272 if repo is None:
5272 raise error.RepoError(_("there is no Mercurial repository here"
5273 raise error.RepoError(_("there is no Mercurial repository here"
5273 " (.hg not found)"))
5274 " (.hg not found)"))
5274 s = wireprotoserver.sshserver(ui, repo)
5275 s = wireprotoserver.sshserver(ui, repo)
5275 s.serve_forever()
5276 s.serve_forever()
5276
5277
5277 service = server.createservice(ui, repo, opts)
5278 service = server.createservice(ui, repo, opts)
5278 return server.runservice(opts, initfn=service.init, runfn=service.run)
5279 return server.runservice(opts, initfn=service.init, runfn=service.run)
5279
5280
5280 _NOTTERSE = 'nothing'
5281 _NOTTERSE = 'nothing'
5281
5282
5282 @command('status|st',
5283 @command('status|st',
5283 [('A', 'all', None, _('show status of all files')),
5284 [('A', 'all', None, _('show status of all files')),
5284 ('m', 'modified', None, _('show only modified files')),
5285 ('m', 'modified', None, _('show only modified files')),
5285 ('a', 'added', None, _('show only added files')),
5286 ('a', 'added', None, _('show only added files')),
5286 ('r', 'removed', None, _('show only removed files')),
5287 ('r', 'removed', None, _('show only removed files')),
5287 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
5288 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
5288 ('c', 'clean', None, _('show only files without changes')),
5289 ('c', 'clean', None, _('show only files without changes')),
5289 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
5290 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
5290 ('i', 'ignored', None, _('show only ignored files')),
5291 ('i', 'ignored', None, _('show only ignored files')),
5291 ('n', 'no-status', None, _('hide status prefix')),
5292 ('n', 'no-status', None, _('hide status prefix')),
5292 ('t', 'terse', _NOTTERSE, _('show the terse output (EXPERIMENTAL)')),
5293 ('t', 'terse', _NOTTERSE, _('show the terse output (EXPERIMENTAL)')),
5293 ('C', 'copies', None, _('show source of copied files')),
5294 ('C', 'copies', None, _('show source of copied files')),
5294 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
5295 ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
5295 ('', 'rev', [], _('show difference from revision'), _('REV')),
5296 ('', 'rev', [], _('show difference from revision'), _('REV')),
5296 ('', 'change', '', _('list the changed files of a revision'), _('REV')),
5297 ('', 'change', '', _('list the changed files of a revision'), _('REV')),
5297 ] + walkopts + subrepoopts + formatteropts,
5298 ] + walkopts + subrepoopts + formatteropts,
5298 _('[OPTION]... [FILE]...'),
5299 _('[OPTION]... [FILE]...'),
5299 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5300 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5300 helpbasic=True, inferrepo=True,
5301 helpbasic=True, inferrepo=True,
5301 intents={INTENT_READONLY})
5302 intents={INTENT_READONLY})
5302 def status(ui, repo, *pats, **opts):
5303 def status(ui, repo, *pats, **opts):
5303 """show changed files in the working directory
5304 """show changed files in the working directory
5304
5305
5305 Show status of files in the repository. If names are given, only
5306 Show status of files in the repository. If names are given, only
5306 files that match are shown. Files that are clean or ignored or
5307 files that match are shown. Files that are clean or ignored or
5307 the source of a copy/move operation, are not listed unless
5308 the source of a copy/move operation, are not listed unless
5308 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
5309 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
5309 Unless options described with "show only ..." are given, the
5310 Unless options described with "show only ..." are given, the
5310 options -mardu are used.
5311 options -mardu are used.
5311
5312
5312 Option -q/--quiet hides untracked (unknown and ignored) files
5313 Option -q/--quiet hides untracked (unknown and ignored) files
5313 unless explicitly requested with -u/--unknown or -i/--ignored.
5314 unless explicitly requested with -u/--unknown or -i/--ignored.
5314
5315
5315 .. note::
5316 .. note::
5316
5317
5317 :hg:`status` may appear to disagree with diff if permissions have
5318 :hg:`status` may appear to disagree with diff if permissions have
5318 changed or a merge has occurred. The standard diff format does
5319 changed or a merge has occurred. The standard diff format does
5319 not report permission changes and diff only reports changes
5320 not report permission changes and diff only reports changes
5320 relative to one merge parent.
5321 relative to one merge parent.
5321
5322
5322 If one revision is given, it is used as the base revision.
5323 If one revision is given, it is used as the base revision.
5323 If two revisions are given, the differences between them are
5324 If two revisions are given, the differences between them are
5324 shown. The --change option can also be used as a shortcut to list
5325 shown. The --change option can also be used as a shortcut to list
5325 the changed files of a revision from its first parent.
5326 the changed files of a revision from its first parent.
5326
5327
5327 The codes used to show the status of files are::
5328 The codes used to show the status of files are::
5328
5329
5329 M = modified
5330 M = modified
5330 A = added
5331 A = added
5331 R = removed
5332 R = removed
5332 C = clean
5333 C = clean
5333 ! = missing (deleted by non-hg command, but still tracked)
5334 ! = missing (deleted by non-hg command, but still tracked)
5334 ? = not tracked
5335 ? = not tracked
5335 I = ignored
5336 I = ignored
5336 = origin of the previous file (with --copies)
5337 = origin of the previous file (with --copies)
5337
5338
5338 .. container:: verbose
5339 .. container:: verbose
5339
5340
5340 The -t/--terse option abbreviates the output by showing only the directory
5341 The -t/--terse option abbreviates the output by showing only the directory
5341 name if all the files in it share the same status. The option takes an
5342 name if all the files in it share the same status. The option takes an
5342 argument indicating the statuses to abbreviate: 'm' for 'modified', 'a'
5343 argument indicating the statuses to abbreviate: 'm' for 'modified', 'a'
5343 for 'added', 'r' for 'removed', 'd' for 'deleted', 'u' for 'unknown', 'i'
5344 for 'added', 'r' for 'removed', 'd' for 'deleted', 'u' for 'unknown', 'i'
5344 for 'ignored' and 'c' for clean.
5345 for 'ignored' and 'c' for clean.
5345
5346
5346 It abbreviates only those statuses which are passed. Note that clean and
5347 It abbreviates only those statuses which are passed. Note that clean and
5347 ignored files are not displayed with '--terse ic' unless the -c/--clean
5348 ignored files are not displayed with '--terse ic' unless the -c/--clean
5348 and -i/--ignored options are also used.
5349 and -i/--ignored options are also used.
5349
5350
5350 The -v/--verbose option shows information when the repository is in an
5351 The -v/--verbose option shows information when the repository is in an
5351 unfinished merge, shelve, rebase state etc. You can have this behavior
5352 unfinished merge, shelve, rebase state etc. You can have this behavior
5352 turned on by default by enabling the ``commands.status.verbose`` option.
5353 turned on by default by enabling the ``commands.status.verbose`` option.
5353
5354
5354 You can skip displaying some of these states by setting
5355 You can skip displaying some of these states by setting
5355 ``commands.status.skipstates`` to one or more of: 'bisect', 'graft',
5356 ``commands.status.skipstates`` to one or more of: 'bisect', 'graft',
5356 'histedit', 'merge', 'rebase', or 'unshelve'.
5357 'histedit', 'merge', 'rebase', or 'unshelve'.
5357
5358
5358 Template:
5359 Template:
5359
5360
5360 The following keywords are supported in addition to the common template
5361 The following keywords are supported in addition to the common template
5361 keywords and functions. See also :hg:`help templates`.
5362 keywords and functions. See also :hg:`help templates`.
5362
5363
5363 :path: String. Repository-absolute path of the file.
5364 :path: String. Repository-absolute path of the file.
5364 :source: String. Repository-absolute path of the file originated from.
5365 :source: String. Repository-absolute path of the file originated from.
5365 Available if ``--copies`` is specified.
5366 Available if ``--copies`` is specified.
5366 :status: String. Character denoting file's status.
5367 :status: String. Character denoting file's status.
5367
5368
5368 Examples:
5369 Examples:
5369
5370
5370 - show changes in the working directory relative to a
5371 - show changes in the working directory relative to a
5371 changeset::
5372 changeset::
5372
5373
5373 hg status --rev 9353
5374 hg status --rev 9353
5374
5375
5375 - show changes in the working directory relative to the
5376 - show changes in the working directory relative to the
5376 current directory (see :hg:`help patterns` for more information)::
5377 current directory (see :hg:`help patterns` for more information)::
5377
5378
5378 hg status re:
5379 hg status re:
5379
5380
5380 - show all changes including copies in an existing changeset::
5381 - show all changes including copies in an existing changeset::
5381
5382
5382 hg status --copies --change 9353
5383 hg status --copies --change 9353
5383
5384
5384 - get a NUL separated list of added files, suitable for xargs::
5385 - get a NUL separated list of added files, suitable for xargs::
5385
5386
5386 hg status -an0
5387 hg status -an0
5387
5388
5388 - show more information about the repository status, abbreviating
5389 - show more information about the repository status, abbreviating
5389 added, removed, modified, deleted, and untracked paths::
5390 added, removed, modified, deleted, and untracked paths::
5390
5391
5391 hg status -v -t mardu
5392 hg status -v -t mardu
5392
5393
5393 Returns 0 on success.
5394 Returns 0 on success.
5394
5395
5395 """
5396 """
5396
5397
5397 opts = pycompat.byteskwargs(opts)
5398 opts = pycompat.byteskwargs(opts)
5398 revs = opts.get('rev')
5399 revs = opts.get('rev')
5399 change = opts.get('change')
5400 change = opts.get('change')
5400 terse = opts.get('terse')
5401 terse = opts.get('terse')
5401 if terse is _NOTTERSE:
5402 if terse is _NOTTERSE:
5402 if revs:
5403 if revs:
5403 terse = ''
5404 terse = ''
5404 else:
5405 else:
5405 terse = ui.config('commands', 'status.terse')
5406 terse = ui.config('commands', 'status.terse')
5406
5407
5407 if revs and change:
5408 if revs and change:
5408 msg = _('cannot specify --rev and --change at the same time')
5409 msg = _('cannot specify --rev and --change at the same time')
5409 raise error.Abort(msg)
5410 raise error.Abort(msg)
5410 elif revs and terse:
5411 elif revs and terse:
5411 msg = _('cannot use --terse with --rev')
5412 msg = _('cannot use --terse with --rev')
5412 raise error.Abort(msg)
5413 raise error.Abort(msg)
5413 elif change:
5414 elif change:
5414 repo = scmutil.unhidehashlikerevs(repo, [change], 'nowarn')
5415 repo = scmutil.unhidehashlikerevs(repo, [change], 'nowarn')
5415 ctx2 = scmutil.revsingle(repo, change, None)
5416 ctx2 = scmutil.revsingle(repo, change, None)
5416 ctx1 = ctx2.p1()
5417 ctx1 = ctx2.p1()
5417 else:
5418 else:
5418 repo = scmutil.unhidehashlikerevs(repo, revs, 'nowarn')
5419 repo = scmutil.unhidehashlikerevs(repo, revs, 'nowarn')
5419 ctx1, ctx2 = scmutil.revpair(repo, revs)
5420 ctx1, ctx2 = scmutil.revpair(repo, revs)
5420
5421
5421 forcerelativevalue = None
5422 forcerelativevalue = None
5422 if ui.hasconfig('commands', 'status.relative'):
5423 if ui.hasconfig('commands', 'status.relative'):
5423 forcerelativevalue = ui.configbool('commands', 'status.relative')
5424 forcerelativevalue = ui.configbool('commands', 'status.relative')
5424 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=bool(pats),
5425 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=bool(pats),
5425 forcerelativevalue=forcerelativevalue)
5426 forcerelativevalue=forcerelativevalue)
5426
5427
5427 if opts.get('print0'):
5428 if opts.get('print0'):
5428 end = '\0'
5429 end = '\0'
5429 else:
5430 else:
5430 end = '\n'
5431 end = '\n'
5431 copy = {}
5432 copy = {}
5432 states = 'modified added removed deleted unknown ignored clean'.split()
5433 states = 'modified added removed deleted unknown ignored clean'.split()
5433 show = [k for k in states if opts.get(k)]
5434 show = [k for k in states if opts.get(k)]
5434 if opts.get('all'):
5435 if opts.get('all'):
5435 show += ui.quiet and (states[:4] + ['clean']) or states
5436 show += ui.quiet and (states[:4] + ['clean']) or states
5436
5437
5437 if not show:
5438 if not show:
5438 if ui.quiet:
5439 if ui.quiet:
5439 show = states[:4]
5440 show = states[:4]
5440 else:
5441 else:
5441 show = states[:5]
5442 show = states[:5]
5442
5443
5443 m = scmutil.match(ctx2, pats, opts)
5444 m = scmutil.match(ctx2, pats, opts)
5444 if terse:
5445 if terse:
5445 # we need to compute clean and unknown to terse
5446 # we need to compute clean and unknown to terse
5446 stat = repo.status(ctx1.node(), ctx2.node(), m,
5447 stat = repo.status(ctx1.node(), ctx2.node(), m,
5447 'ignored' in show or 'i' in terse,
5448 'ignored' in show or 'i' in terse,
5448 clean=True, unknown=True,
5449 clean=True, unknown=True,
5449 listsubrepos=opts.get('subrepos'))
5450 listsubrepos=opts.get('subrepos'))
5450
5451
5451 stat = cmdutil.tersedir(stat, terse)
5452 stat = cmdutil.tersedir(stat, terse)
5452 else:
5453 else:
5453 stat = repo.status(ctx1.node(), ctx2.node(), m,
5454 stat = repo.status(ctx1.node(), ctx2.node(), m,
5454 'ignored' in show, 'clean' in show,
5455 'ignored' in show, 'clean' in show,
5455 'unknown' in show, opts.get('subrepos'))
5456 'unknown' in show, opts.get('subrepos'))
5456
5457
5457 changestates = zip(states, pycompat.iterbytestr('MAR!?IC'), stat)
5458 changestates = zip(states, pycompat.iterbytestr('MAR!?IC'), stat)
5458
5459
5459 if (opts.get('all') or opts.get('copies')
5460 if (opts.get('all') or opts.get('copies')
5460 or ui.configbool('ui', 'statuscopies')) and not opts.get('no_status'):
5461 or ui.configbool('ui', 'statuscopies')) and not opts.get('no_status'):
5461 copy = copies.pathcopies(ctx1, ctx2, m)
5462 copy = copies.pathcopies(ctx1, ctx2, m)
5462
5463
5463 ui.pager('status')
5464 ui.pager('status')
5464 fm = ui.formatter('status', opts)
5465 fm = ui.formatter('status', opts)
5465 fmt = '%s' + end
5466 fmt = '%s' + end
5466 showchar = not opts.get('no_status')
5467 showchar = not opts.get('no_status')
5467
5468
5468 for state, char, files in changestates:
5469 for state, char, files in changestates:
5469 if state in show:
5470 if state in show:
5470 label = 'status.' + state
5471 label = 'status.' + state
5471 for f in files:
5472 for f in files:
5472 fm.startitem()
5473 fm.startitem()
5473 fm.context(ctx=ctx2)
5474 fm.context(ctx=ctx2)
5474 fm.data(path=f)
5475 fm.data(path=f)
5475 fm.condwrite(showchar, 'status', '%s ', char, label=label)
5476 fm.condwrite(showchar, 'status', '%s ', char, label=label)
5476 fm.plain(fmt % uipathfn(f), label=label)
5477 fm.plain(fmt % uipathfn(f), label=label)
5477 if f in copy:
5478 if f in copy:
5478 fm.data(source=copy[f])
5479 fm.data(source=copy[f])
5479 fm.plain((' %s' + end) % uipathfn(copy[f]),
5480 fm.plain((' %s' + end) % uipathfn(copy[f]),
5480 label='status.copied')
5481 label='status.copied')
5481
5482
5482 if ((ui.verbose or ui.configbool('commands', 'status.verbose'))
5483 if ((ui.verbose or ui.configbool('commands', 'status.verbose'))
5483 and not ui.plain()):
5484 and not ui.plain()):
5484 cmdutil.morestatus(repo, fm)
5485 cmdutil.morestatus(repo, fm)
5485 fm.end()
5486 fm.end()
5486
5487
5487 @command('summary|sum',
5488 @command('summary|sum',
5488 [('', 'remote', None, _('check for push and pull'))],
5489 [('', 'remote', None, _('check for push and pull'))],
5489 '[--remote]',
5490 '[--remote]',
5490 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5491 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
5491 helpbasic=True,
5492 helpbasic=True,
5492 intents={INTENT_READONLY})
5493 intents={INTENT_READONLY})
5493 def summary(ui, repo, **opts):
5494 def summary(ui, repo, **opts):
5494 """summarize working directory state
5495 """summarize working directory state
5495
5496
5496 This generates a brief summary of the working directory state,
5497 This generates a brief summary of the working directory state,
5497 including parents, branch, commit status, phase and available updates.
5498 including parents, branch, commit status, phase and available updates.
5498
5499
5499 With the --remote option, this will check the default paths for
5500 With the --remote option, this will check the default paths for
5500 incoming and outgoing changes. This can be time-consuming.
5501 incoming and outgoing changes. This can be time-consuming.
5501
5502
5502 Returns 0 on success.
5503 Returns 0 on success.
5503 """
5504 """
5504
5505
5505 opts = pycompat.byteskwargs(opts)
5506 opts = pycompat.byteskwargs(opts)
5506 ui.pager('summary')
5507 ui.pager('summary')
5507 ctx = repo[None]
5508 ctx = repo[None]
5508 parents = ctx.parents()
5509 parents = ctx.parents()
5509 pnode = parents[0].node()
5510 pnode = parents[0].node()
5510 marks = []
5511 marks = []
5511
5512
5512 try:
5513 try:
5513 ms = mergemod.mergestate.read(repo)
5514 ms = mergemod.mergestate.read(repo)
5514 except error.UnsupportedMergeRecords as e:
5515 except error.UnsupportedMergeRecords as e:
5515 s = ' '.join(e.recordtypes)
5516 s = ' '.join(e.recordtypes)
5516 ui.warn(
5517 ui.warn(
5517 _('warning: merge state has unsupported record types: %s\n') % s)
5518 _('warning: merge state has unsupported record types: %s\n') % s)
5518 unresolved = []
5519 unresolved = []
5519 else:
5520 else:
5520 unresolved = list(ms.unresolved())
5521 unresolved = list(ms.unresolved())
5521
5522
5522 for p in parents:
5523 for p in parents:
5523 # label with log.changeset (instead of log.parent) since this
5524 # label with log.changeset (instead of log.parent) since this
5524 # shows a working directory parent *changeset*:
5525 # shows a working directory parent *changeset*:
5525 # i18n: column positioning for "hg summary"
5526 # i18n: column positioning for "hg summary"
5526 ui.write(_('parent: %d:%s ') % (p.rev(), p),
5527 ui.write(_('parent: %d:%s ') % (p.rev(), p),
5527 label=logcmdutil.changesetlabels(p))
5528 label=logcmdutil.changesetlabels(p))
5528 ui.write(' '.join(p.tags()), label='log.tag')
5529 ui.write(' '.join(p.tags()), label='log.tag')
5529 if p.bookmarks():
5530 if p.bookmarks():
5530 marks.extend(p.bookmarks())
5531 marks.extend(p.bookmarks())
5531 if p.rev() == -1:
5532 if p.rev() == -1:
5532 if not len(repo):
5533 if not len(repo):
5533 ui.write(_(' (empty repository)'))
5534 ui.write(_(' (empty repository)'))
5534 else:
5535 else:
5535 ui.write(_(' (no revision checked out)'))
5536 ui.write(_(' (no revision checked out)'))
5536 if p.obsolete():
5537 if p.obsolete():
5537 ui.write(_(' (obsolete)'))
5538 ui.write(_(' (obsolete)'))
5538 if p.isunstable():
5539 if p.isunstable():
5539 instabilities = (ui.label(instability, 'trouble.%s' % instability)
5540 instabilities = (ui.label(instability, 'trouble.%s' % instability)
5540 for instability in p.instabilities())
5541 for instability in p.instabilities())
5541 ui.write(' ('
5542 ui.write(' ('
5542 + ', '.join(instabilities)
5543 + ', '.join(instabilities)
5543 + ')')
5544 + ')')
5544 ui.write('\n')
5545 ui.write('\n')
5545 if p.description():
5546 if p.description():
5546 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
5547 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
5547 label='log.summary')
5548 label='log.summary')
5548
5549
5549 branch = ctx.branch()
5550 branch = ctx.branch()
5550 bheads = repo.branchheads(branch)
5551 bheads = repo.branchheads(branch)
5551 # i18n: column positioning for "hg summary"
5552 # i18n: column positioning for "hg summary"
5552 m = _('branch: %s\n') % branch
5553 m = _('branch: %s\n') % branch
5553 if branch != 'default':
5554 if branch != 'default':
5554 ui.write(m, label='log.branch')
5555 ui.write(m, label='log.branch')
5555 else:
5556 else:
5556 ui.status(m, label='log.branch')
5557 ui.status(m, label='log.branch')
5557
5558
5558 if marks:
5559 if marks:
5559 active = repo._activebookmark
5560 active = repo._activebookmark
5560 # i18n: column positioning for "hg summary"
5561 # i18n: column positioning for "hg summary"
5561 ui.write(_('bookmarks:'), label='log.bookmark')
5562 ui.write(_('bookmarks:'), label='log.bookmark')
5562 if active is not None:
5563 if active is not None:
5563 if active in marks:
5564 if active in marks:
5564 ui.write(' *' + active, label=bookmarks.activebookmarklabel)
5565 ui.write(' *' + active, label=bookmarks.activebookmarklabel)
5565 marks.remove(active)
5566 marks.remove(active)
5566 else:
5567 else:
5567 ui.write(' [%s]' % active, label=bookmarks.activebookmarklabel)
5568 ui.write(' [%s]' % active, label=bookmarks.activebookmarklabel)
5568 for m in marks:
5569 for m in marks:
5569 ui.write(' ' + m, label='log.bookmark')
5570 ui.write(' ' + m, label='log.bookmark')
5570 ui.write('\n', label='log.bookmark')
5571 ui.write('\n', label='log.bookmark')
5571
5572
5572 status = repo.status(unknown=True)
5573 status = repo.status(unknown=True)
5573
5574
5574 c = repo.dirstate.copies()
5575 c = repo.dirstate.copies()
5575 copied, renamed = [], []
5576 copied, renamed = [], []
5576 for d, s in c.iteritems():
5577 for d, s in c.iteritems():
5577 if s in status.removed:
5578 if s in status.removed:
5578 status.removed.remove(s)
5579 status.removed.remove(s)
5579 renamed.append(d)
5580 renamed.append(d)
5580 else:
5581 else:
5581 copied.append(d)
5582 copied.append(d)
5582 if d in status.added:
5583 if d in status.added:
5583 status.added.remove(d)
5584 status.added.remove(d)
5584
5585
5585 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
5586 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
5586
5587
5587 labels = [(ui.label(_('%d modified'), 'status.modified'), status.modified),
5588 labels = [(ui.label(_('%d modified'), 'status.modified'), status.modified),
5588 (ui.label(_('%d added'), 'status.added'), status.added),
5589 (ui.label(_('%d added'), 'status.added'), status.added),
5589 (ui.label(_('%d removed'), 'status.removed'), status.removed),
5590 (ui.label(_('%d removed'), 'status.removed'), status.removed),
5590 (ui.label(_('%d renamed'), 'status.copied'), renamed),
5591 (ui.label(_('%d renamed'), 'status.copied'), renamed),
5591 (ui.label(_('%d copied'), 'status.copied'), copied),
5592 (ui.label(_('%d copied'), 'status.copied'), copied),
5592 (ui.label(_('%d deleted'), 'status.deleted'), status.deleted),
5593 (ui.label(_('%d deleted'), 'status.deleted'), status.deleted),
5593 (ui.label(_('%d unknown'), 'status.unknown'), status.unknown),
5594 (ui.label(_('%d unknown'), 'status.unknown'), status.unknown),
5594 (ui.label(_('%d unresolved'), 'resolve.unresolved'), unresolved),
5595 (ui.label(_('%d unresolved'), 'resolve.unresolved'), unresolved),
5595 (ui.label(_('%d subrepos'), 'status.modified'), subs)]
5596 (ui.label(_('%d subrepos'), 'status.modified'), subs)]
5596 t = []
5597 t = []
5597 for l, s in labels:
5598 for l, s in labels:
5598 if s:
5599 if s:
5599 t.append(l % len(s))
5600 t.append(l % len(s))
5600
5601
5601 t = ', '.join(t)
5602 t = ', '.join(t)
5602 cleanworkdir = False
5603 cleanworkdir = False
5603
5604
5604 if repo.vfs.exists('graftstate'):
5605 if repo.vfs.exists('graftstate'):
5605 t += _(' (graft in progress)')
5606 t += _(' (graft in progress)')
5606 if repo.vfs.exists('updatestate'):
5607 if repo.vfs.exists('updatestate'):
5607 t += _(' (interrupted update)')
5608 t += _(' (interrupted update)')
5608 elif len(parents) > 1:
5609 elif len(parents) > 1:
5609 t += _(' (merge)')
5610 t += _(' (merge)')
5610 elif branch != parents[0].branch():
5611 elif branch != parents[0].branch():
5611 t += _(' (new branch)')
5612 t += _(' (new branch)')
5612 elif (parents[0].closesbranch() and
5613 elif (parents[0].closesbranch() and
5613 pnode in repo.branchheads(branch, closed=True)):
5614 pnode in repo.branchheads(branch, closed=True)):
5614 t += _(' (head closed)')
5615 t += _(' (head closed)')
5615 elif not (status.modified or status.added or status.removed or renamed or
5616 elif not (status.modified or status.added or status.removed or renamed or
5616 copied or subs):
5617 copied or subs):
5617 t += _(' (clean)')
5618 t += _(' (clean)')
5618 cleanworkdir = True
5619 cleanworkdir = True
5619 elif pnode not in bheads:
5620 elif pnode not in bheads:
5620 t += _(' (new branch head)')
5621 t += _(' (new branch head)')
5621
5622
5622 if parents:
5623 if parents:
5623 pendingphase = max(p.phase() for p in parents)
5624 pendingphase = max(p.phase() for p in parents)
5624 else:
5625 else:
5625 pendingphase = phases.public
5626 pendingphase = phases.public
5626
5627
5627 if pendingphase > phases.newcommitphase(ui):
5628 if pendingphase > phases.newcommitphase(ui):
5628 t += ' (%s)' % phases.phasenames[pendingphase]
5629 t += ' (%s)' % phases.phasenames[pendingphase]
5629
5630
5630 if cleanworkdir:
5631 if cleanworkdir:
5631 # i18n: column positioning for "hg summary"
5632 # i18n: column positioning for "hg summary"
5632 ui.status(_('commit: %s\n') % t.strip())
5633 ui.status(_('commit: %s\n') % t.strip())
5633 else:
5634 else:
5634 # i18n: column positioning for "hg summary"
5635 # i18n: column positioning for "hg summary"
5635 ui.write(_('commit: %s\n') % t.strip())
5636 ui.write(_('commit: %s\n') % t.strip())
5636
5637
5637 # all ancestors of branch heads - all ancestors of parent = new csets
5638 # all ancestors of branch heads - all ancestors of parent = new csets
5638 new = len(repo.changelog.findmissing([pctx.node() for pctx in parents],
5639 new = len(repo.changelog.findmissing([pctx.node() for pctx in parents],
5639 bheads))
5640 bheads))
5640
5641
5641 if new == 0:
5642 if new == 0:
5642 # i18n: column positioning for "hg summary"
5643 # i18n: column positioning for "hg summary"
5643 ui.status(_('update: (current)\n'))
5644 ui.status(_('update: (current)\n'))
5644 elif pnode not in bheads:
5645 elif pnode not in bheads:
5645 # i18n: column positioning for "hg summary"
5646 # i18n: column positioning for "hg summary"
5646 ui.write(_('update: %d new changesets (update)\n') % new)
5647 ui.write(_('update: %d new changesets (update)\n') % new)
5647 else:
5648 else:
5648 # i18n: column positioning for "hg summary"
5649 # i18n: column positioning for "hg summary"
5649 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
5650 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
5650 (new, len(bheads)))
5651 (new, len(bheads)))
5651
5652
5652 t = []
5653 t = []
5653 draft = len(repo.revs('draft()'))
5654 draft = len(repo.revs('draft()'))
5654 if draft:
5655 if draft:
5655 t.append(_('%d draft') % draft)
5656 t.append(_('%d draft') % draft)
5656 secret = len(repo.revs('secret()'))
5657 secret = len(repo.revs('secret()'))
5657 if secret:
5658 if secret:
5658 t.append(_('%d secret') % secret)
5659 t.append(_('%d secret') % secret)
5659
5660
5660 if draft or secret:
5661 if draft or secret:
5661 ui.status(_('phases: %s\n') % ', '.join(t))
5662 ui.status(_('phases: %s\n') % ', '.join(t))
5662
5663
5663 if obsolete.isenabled(repo, obsolete.createmarkersopt):
5664 if obsolete.isenabled(repo, obsolete.createmarkersopt):
5664 for trouble in ("orphan", "contentdivergent", "phasedivergent"):
5665 for trouble in ("orphan", "contentdivergent", "phasedivergent"):
5665 numtrouble = len(repo.revs(trouble + "()"))
5666 numtrouble = len(repo.revs(trouble + "()"))
5666 # We write all the possibilities to ease translation
5667 # We write all the possibilities to ease translation
5667 troublemsg = {
5668 troublemsg = {
5668 "orphan": _("orphan: %d changesets"),
5669 "orphan": _("orphan: %d changesets"),
5669 "contentdivergent": _("content-divergent: %d changesets"),
5670 "contentdivergent": _("content-divergent: %d changesets"),
5670 "phasedivergent": _("phase-divergent: %d changesets"),
5671 "phasedivergent": _("phase-divergent: %d changesets"),
5671 }
5672 }
5672 if numtrouble > 0:
5673 if numtrouble > 0:
5673 ui.status(troublemsg[trouble] % numtrouble + "\n")
5674 ui.status(troublemsg[trouble] % numtrouble + "\n")
5674
5675
5675 cmdutil.summaryhooks(ui, repo)
5676 cmdutil.summaryhooks(ui, repo)
5676
5677
5677 if opts.get('remote'):
5678 if opts.get('remote'):
5678 needsincoming, needsoutgoing = True, True
5679 needsincoming, needsoutgoing = True, True
5679 else:
5680 else:
5680 needsincoming, needsoutgoing = False, False
5681 needsincoming, needsoutgoing = False, False
5681 for i, o in cmdutil.summaryremotehooks(ui, repo, opts, None):
5682 for i, o in cmdutil.summaryremotehooks(ui, repo, opts, None):
5682 if i:
5683 if i:
5683 needsincoming = True
5684 needsincoming = True
5684 if o:
5685 if o:
5685 needsoutgoing = True
5686 needsoutgoing = True
5686 if not needsincoming and not needsoutgoing:
5687 if not needsincoming and not needsoutgoing:
5687 return
5688 return
5688
5689
5689 def getincoming():
5690 def getincoming():
5690 source, branches = hg.parseurl(ui.expandpath('default'))
5691 source, branches = hg.parseurl(ui.expandpath('default'))
5691 sbranch = branches[0]
5692 sbranch = branches[0]
5692 try:
5693 try:
5693 other = hg.peer(repo, {}, source)
5694 other = hg.peer(repo, {}, source)
5694 except error.RepoError:
5695 except error.RepoError:
5695 if opts.get('remote'):
5696 if opts.get('remote'):
5696 raise
5697 raise
5697 return source, sbranch, None, None, None
5698 return source, sbranch, None, None, None
5698 revs, checkout = hg.addbranchrevs(repo, other, branches, None)
5699 revs, checkout = hg.addbranchrevs(repo, other, branches, None)
5699 if revs:
5700 if revs:
5700 revs = [other.lookup(rev) for rev in revs]
5701 revs = [other.lookup(rev) for rev in revs]
5701 ui.debug('comparing with %s\n' % util.hidepassword(source))
5702 ui.debug('comparing with %s\n' % util.hidepassword(source))
5702 repo.ui.pushbuffer()
5703 repo.ui.pushbuffer()
5703 commoninc = discovery.findcommonincoming(repo, other, heads=revs)
5704 commoninc = discovery.findcommonincoming(repo, other, heads=revs)
5704 repo.ui.popbuffer()
5705 repo.ui.popbuffer()
5705 return source, sbranch, other, commoninc, commoninc[1]
5706 return source, sbranch, other, commoninc, commoninc[1]
5706
5707
5707 if needsincoming:
5708 if needsincoming:
5708 source, sbranch, sother, commoninc, incoming = getincoming()
5709 source, sbranch, sother, commoninc, incoming = getincoming()
5709 else:
5710 else:
5710 source = sbranch = sother = commoninc = incoming = None
5711 source = sbranch = sother = commoninc = incoming = None
5711
5712
5712 def getoutgoing():
5713 def getoutgoing():
5713 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
5714 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
5714 dbranch = branches[0]
5715 dbranch = branches[0]
5715 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
5716 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
5716 if source != dest:
5717 if source != dest:
5717 try:
5718 try:
5718 dother = hg.peer(repo, {}, dest)
5719 dother = hg.peer(repo, {}, dest)
5719 except error.RepoError:
5720 except error.RepoError:
5720 if opts.get('remote'):
5721 if opts.get('remote'):
5721 raise
5722 raise
5722 return dest, dbranch, None, None
5723 return dest, dbranch, None, None
5723 ui.debug('comparing with %s\n' % util.hidepassword(dest))
5724 ui.debug('comparing with %s\n' % util.hidepassword(dest))
5724 elif sother is None:
5725 elif sother is None:
5725 # there is no explicit destination peer, but source one is invalid
5726 # there is no explicit destination peer, but source one is invalid
5726 return dest, dbranch, None, None
5727 return dest, dbranch, None, None
5727 else:
5728 else:
5728 dother = sother
5729 dother = sother
5729 if (source != dest or (sbranch is not None and sbranch != dbranch)):
5730 if (source != dest or (sbranch is not None and sbranch != dbranch)):
5730 common = None
5731 common = None
5731 else:
5732 else:
5732 common = commoninc
5733 common = commoninc
5733 if revs:
5734 if revs:
5734 revs = [repo.lookup(rev) for rev in revs]
5735 revs = [repo.lookup(rev) for rev in revs]
5735 repo.ui.pushbuffer()
5736 repo.ui.pushbuffer()
5736 outgoing = discovery.findcommonoutgoing(repo, dother, onlyheads=revs,
5737 outgoing = discovery.findcommonoutgoing(repo, dother, onlyheads=revs,
5737 commoninc=common)
5738 commoninc=common)
5738 repo.ui.popbuffer()
5739 repo.ui.popbuffer()
5739 return dest, dbranch, dother, outgoing
5740 return dest, dbranch, dother, outgoing
5740
5741
5741 if needsoutgoing:
5742 if needsoutgoing:
5742 dest, dbranch, dother, outgoing = getoutgoing()
5743 dest, dbranch, dother, outgoing = getoutgoing()
5743 else:
5744 else:
5744 dest = dbranch = dother = outgoing = None
5745 dest = dbranch = dother = outgoing = None
5745
5746
5746 if opts.get('remote'):
5747 if opts.get('remote'):
5747 t = []
5748 t = []
5748 if incoming:
5749 if incoming:
5749 t.append(_('1 or more incoming'))
5750 t.append(_('1 or more incoming'))
5750 o = outgoing.missing
5751 o = outgoing.missing
5751 if o:
5752 if o:
5752 t.append(_('%d outgoing') % len(o))
5753 t.append(_('%d outgoing') % len(o))
5753 other = dother or sother
5754 other = dother or sother
5754 if 'bookmarks' in other.listkeys('namespaces'):
5755 if 'bookmarks' in other.listkeys('namespaces'):
5755 counts = bookmarks.summary(repo, other)
5756 counts = bookmarks.summary(repo, other)
5756 if counts[0] > 0:
5757 if counts[0] > 0:
5757 t.append(_('%d incoming bookmarks') % counts[0])
5758 t.append(_('%d incoming bookmarks') % counts[0])
5758 if counts[1] > 0:
5759 if counts[1] > 0:
5759 t.append(_('%d outgoing bookmarks') % counts[1])
5760 t.append(_('%d outgoing bookmarks') % counts[1])
5760
5761
5761 if t:
5762 if t:
5762 # i18n: column positioning for "hg summary"
5763 # i18n: column positioning for "hg summary"
5763 ui.write(_('remote: %s\n') % (', '.join(t)))
5764 ui.write(_('remote: %s\n') % (', '.join(t)))
5764 else:
5765 else:
5765 # i18n: column positioning for "hg summary"
5766 # i18n: column positioning for "hg summary"
5766 ui.status(_('remote: (synced)\n'))
5767 ui.status(_('remote: (synced)\n'))
5767
5768
5768 cmdutil.summaryremotehooks(ui, repo, opts,
5769 cmdutil.summaryremotehooks(ui, repo, opts,
5769 ((source, sbranch, sother, commoninc),
5770 ((source, sbranch, sother, commoninc),
5770 (dest, dbranch, dother, outgoing)))
5771 (dest, dbranch, dother, outgoing)))
5771
5772
5772 @command('tag',
5773 @command('tag',
5773 [('f', 'force', None, _('force tag')),
5774 [('f', 'force', None, _('force tag')),
5774 ('l', 'local', None, _('make the tag local')),
5775 ('l', 'local', None, _('make the tag local')),
5775 ('r', 'rev', '', _('revision to tag'), _('REV')),
5776 ('r', 'rev', '', _('revision to tag'), _('REV')),
5776 ('', 'remove', None, _('remove a tag')),
5777 ('', 'remove', None, _('remove a tag')),
5777 # -l/--local is already there, commitopts cannot be used
5778 # -l/--local is already there, commitopts cannot be used
5778 ('e', 'edit', None, _('invoke editor on commit messages')),
5779 ('e', 'edit', None, _('invoke editor on commit messages')),
5779 ('m', 'message', '', _('use text as commit message'), _('TEXT')),
5780 ('m', 'message', '', _('use text as commit message'), _('TEXT')),
5780 ] + commitopts2,
5781 ] + commitopts2,
5781 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'),
5782 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...'),
5782 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION)
5783 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION)
5783 def tag(ui, repo, name1, *names, **opts):
5784 def tag(ui, repo, name1, *names, **opts):
5784 """add one or more tags for the current or given revision
5785 """add one or more tags for the current or given revision
5785
5786
5786 Name a particular revision using <name>.
5787 Name a particular revision using <name>.
5787
5788
5788 Tags are used to name particular revisions of the repository and are
5789 Tags are used to name particular revisions of the repository and are
5789 very useful to compare different revisions, to go back to significant
5790 very useful to compare different revisions, to go back to significant
5790 earlier versions or to mark branch points as releases, etc. Changing
5791 earlier versions or to mark branch points as releases, etc. Changing
5791 an existing tag is normally disallowed; use -f/--force to override.
5792 an existing tag is normally disallowed; use -f/--force to override.
5792
5793
5793 If no revision is given, the parent of the working directory is
5794 If no revision is given, the parent of the working directory is
5794 used.
5795 used.
5795
5796
5796 To facilitate version control, distribution, and merging of tags,
5797 To facilitate version control, distribution, and merging of tags,
5797 they are stored as a file named ".hgtags" which is managed similarly
5798 they are stored as a file named ".hgtags" which is managed similarly
5798 to other project files and can be hand-edited if necessary. This
5799 to other project files and can be hand-edited if necessary. This
5799 also means that tagging creates a new commit. The file
5800 also means that tagging creates a new commit. The file
5800 ".hg/localtags" is used for local tags (not shared among
5801 ".hg/localtags" is used for local tags (not shared among
5801 repositories).
5802 repositories).
5802
5803
5803 Tag commits are usually made at the head of a branch. If the parent
5804 Tag commits are usually made at the head of a branch. If the parent
5804 of the working directory is not a branch head, :hg:`tag` aborts; use
5805 of the working directory is not a branch head, :hg:`tag` aborts; use
5805 -f/--force to force the tag commit to be based on a non-head
5806 -f/--force to force the tag commit to be based on a non-head
5806 changeset.
5807 changeset.
5807
5808
5808 See :hg:`help dates` for a list of formats valid for -d/--date.
5809 See :hg:`help dates` for a list of formats valid for -d/--date.
5809
5810
5810 Since tag names have priority over branch names during revision
5811 Since tag names have priority over branch names during revision
5811 lookup, using an existing branch name as a tag name is discouraged.
5812 lookup, using an existing branch name as a tag name is discouraged.
5812
5813
5813 Returns 0 on success.
5814 Returns 0 on success.
5814 """
5815 """
5815 opts = pycompat.byteskwargs(opts)
5816 opts = pycompat.byteskwargs(opts)
5816 with repo.wlock(), repo.lock():
5817 with repo.wlock(), repo.lock():
5817 rev_ = "."
5818 rev_ = "."
5818 names = [t.strip() for t in (name1,) + names]
5819 names = [t.strip() for t in (name1,) + names]
5819 if len(names) != len(set(names)):
5820 if len(names) != len(set(names)):
5820 raise error.Abort(_('tag names must be unique'))
5821 raise error.Abort(_('tag names must be unique'))
5821 for n in names:
5822 for n in names:
5822 scmutil.checknewlabel(repo, n, 'tag')
5823 scmutil.checknewlabel(repo, n, 'tag')
5823 if not n:
5824 if not n:
5824 raise error.Abort(_('tag names cannot consist entirely of '
5825 raise error.Abort(_('tag names cannot consist entirely of '
5825 'whitespace'))
5826 'whitespace'))
5826 if opts.get('rev') and opts.get('remove'):
5827 if opts.get('rev') and opts.get('remove'):
5827 raise error.Abort(_("--rev and --remove are incompatible"))
5828 raise error.Abort(_("--rev and --remove are incompatible"))
5828 if opts.get('rev'):
5829 if opts.get('rev'):
5829 rev_ = opts['rev']
5830 rev_ = opts['rev']
5830 message = opts.get('message')
5831 message = opts.get('message')
5831 if opts.get('remove'):
5832 if opts.get('remove'):
5832 if opts.get('local'):
5833 if opts.get('local'):
5833 expectedtype = 'local'
5834 expectedtype = 'local'
5834 else:
5835 else:
5835 expectedtype = 'global'
5836 expectedtype = 'global'
5836
5837
5837 for n in names:
5838 for n in names:
5838 if repo.tagtype(n) == 'global':
5839 if repo.tagtype(n) == 'global':
5839 alltags = tagsmod.findglobaltags(ui, repo)
5840 alltags = tagsmod.findglobaltags(ui, repo)
5840 if alltags[n][0] == nullid:
5841 if alltags[n][0] == nullid:
5841 raise error.Abort(_("tag '%s' is already removed") % n)
5842 raise error.Abort(_("tag '%s' is already removed") % n)
5842 if not repo.tagtype(n):
5843 if not repo.tagtype(n):
5843 raise error.Abort(_("tag '%s' does not exist") % n)
5844 raise error.Abort(_("tag '%s' does not exist") % n)
5844 if repo.tagtype(n) != expectedtype:
5845 if repo.tagtype(n) != expectedtype:
5845 if expectedtype == 'global':
5846 if expectedtype == 'global':
5846 raise error.Abort(_("tag '%s' is not a global tag") % n)
5847 raise error.Abort(_("tag '%s' is not a global tag") % n)
5847 else:
5848 else:
5848 raise error.Abort(_("tag '%s' is not a local tag") % n)
5849 raise error.Abort(_("tag '%s' is not a local tag") % n)
5849 rev_ = 'null'
5850 rev_ = 'null'
5850 if not message:
5851 if not message:
5851 # we don't translate commit messages
5852 # we don't translate commit messages
5852 message = 'Removed tag %s' % ', '.join(names)
5853 message = 'Removed tag %s' % ', '.join(names)
5853 elif not opts.get('force'):
5854 elif not opts.get('force'):
5854 for n in names:
5855 for n in names:
5855 if n in repo.tags():
5856 if n in repo.tags():
5856 raise error.Abort(_("tag '%s' already exists "
5857 raise error.Abort(_("tag '%s' already exists "
5857 "(use -f to force)") % n)
5858 "(use -f to force)") % n)
5858 if not opts.get('local'):
5859 if not opts.get('local'):
5859 p1, p2 = repo.dirstate.parents()
5860 p1, p2 = repo.dirstate.parents()
5860 if p2 != nullid:
5861 if p2 != nullid:
5861 raise error.Abort(_('uncommitted merge'))
5862 raise error.Abort(_('uncommitted merge'))
5862 bheads = repo.branchheads()
5863 bheads = repo.branchheads()
5863 if not opts.get('force') and bheads and p1 not in bheads:
5864 if not opts.get('force') and bheads and p1 not in bheads:
5864 raise error.Abort(_('working directory is not at a branch head '
5865 raise error.Abort(_('working directory is not at a branch head '
5865 '(use -f to force)'))
5866 '(use -f to force)'))
5866 node = scmutil.revsingle(repo, rev_).node()
5867 node = scmutil.revsingle(repo, rev_).node()
5867
5868
5868 if not message:
5869 if not message:
5869 # we don't translate commit messages
5870 # we don't translate commit messages
5870 message = ('Added tag %s for changeset %s' %
5871 message = ('Added tag %s for changeset %s' %
5871 (', '.join(names), short(node)))
5872 (', '.join(names), short(node)))
5872
5873
5873 date = opts.get('date')
5874 date = opts.get('date')
5874 if date:
5875 if date:
5875 date = dateutil.parsedate(date)
5876 date = dateutil.parsedate(date)
5876
5877
5877 if opts.get('remove'):
5878 if opts.get('remove'):
5878 editform = 'tag.remove'
5879 editform = 'tag.remove'
5879 else:
5880 else:
5880 editform = 'tag.add'
5881 editform = 'tag.add'
5881 editor = cmdutil.getcommiteditor(editform=editform,
5882 editor = cmdutil.getcommiteditor(editform=editform,
5882 **pycompat.strkwargs(opts))
5883 **pycompat.strkwargs(opts))
5883
5884
5884 # don't allow tagging the null rev
5885 # don't allow tagging the null rev
5885 if (not opts.get('remove') and
5886 if (not opts.get('remove') and
5886 scmutil.revsingle(repo, rev_).rev() == nullrev):
5887 scmutil.revsingle(repo, rev_).rev() == nullrev):
5887 raise error.Abort(_("cannot tag null revision"))
5888 raise error.Abort(_("cannot tag null revision"))
5888
5889
5889 tagsmod.tag(repo, names, node, message, opts.get('local'),
5890 tagsmod.tag(repo, names, node, message, opts.get('local'),
5890 opts.get('user'), date, editor=editor)
5891 opts.get('user'), date, editor=editor)
5891
5892
5892 @command(
5893 @command(
5893 'tags', formatteropts, '',
5894 'tags', formatteropts, '',
5894 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
5895 helpcategory=command.CATEGORY_CHANGE_ORGANIZATION,
5895 intents={INTENT_READONLY})
5896 intents={INTENT_READONLY})
5896 def tags(ui, repo, **opts):
5897 def tags(ui, repo, **opts):
5897 """list repository tags
5898 """list repository tags
5898
5899
5899 This lists both regular and local tags. When the -v/--verbose
5900 This lists both regular and local tags. When the -v/--verbose
5900 switch is used, a third column "local" is printed for local tags.
5901 switch is used, a third column "local" is printed for local tags.
5901 When the -q/--quiet switch is used, only the tag name is printed.
5902 When the -q/--quiet switch is used, only the tag name is printed.
5902
5903
5903 .. container:: verbose
5904 .. container:: verbose
5904
5905
5905 Template:
5906 Template:
5906
5907
5907 The following keywords are supported in addition to the common template
5908 The following keywords are supported in addition to the common template
5908 keywords and functions such as ``{tag}``. See also
5909 keywords and functions such as ``{tag}``. See also
5909 :hg:`help templates`.
5910 :hg:`help templates`.
5910
5911
5911 :type: String. ``local`` for local tags.
5912 :type: String. ``local`` for local tags.
5912
5913
5913 Returns 0 on success.
5914 Returns 0 on success.
5914 """
5915 """
5915
5916
5916 opts = pycompat.byteskwargs(opts)
5917 opts = pycompat.byteskwargs(opts)
5917 ui.pager('tags')
5918 ui.pager('tags')
5918 fm = ui.formatter('tags', opts)
5919 fm = ui.formatter('tags', opts)
5919 hexfunc = fm.hexfunc
5920 hexfunc = fm.hexfunc
5920
5921
5921 for t, n in reversed(repo.tagslist()):
5922 for t, n in reversed(repo.tagslist()):
5922 hn = hexfunc(n)
5923 hn = hexfunc(n)
5923 label = 'tags.normal'
5924 label = 'tags.normal'
5924 tagtype = ''
5925 tagtype = ''
5925 if repo.tagtype(t) == 'local':
5926 if repo.tagtype(t) == 'local':
5926 label = 'tags.local'
5927 label = 'tags.local'
5927 tagtype = 'local'
5928 tagtype = 'local'
5928
5929
5929 fm.startitem()
5930 fm.startitem()
5930 fm.context(repo=repo)
5931 fm.context(repo=repo)
5931 fm.write('tag', '%s', t, label=label)
5932 fm.write('tag', '%s', t, label=label)
5932 fmt = " " * (30 - encoding.colwidth(t)) + ' %5d:%s'
5933 fmt = " " * (30 - encoding.colwidth(t)) + ' %5d:%s'
5933 fm.condwrite(not ui.quiet, 'rev node', fmt,
5934 fm.condwrite(not ui.quiet, 'rev node', fmt,
5934 repo.changelog.rev(n), hn, label=label)
5935 repo.changelog.rev(n), hn, label=label)
5935 fm.condwrite(ui.verbose and tagtype, 'type', ' %s',
5936 fm.condwrite(ui.verbose and tagtype, 'type', ' %s',
5936 tagtype, label=label)
5937 tagtype, label=label)
5937 fm.plain('\n')
5938 fm.plain('\n')
5938 fm.end()
5939 fm.end()
5939
5940
5940 @command('tip',
5941 @command('tip',
5941 [('p', 'patch', None, _('show patch')),
5942 [('p', 'patch', None, _('show patch')),
5942 ('g', 'git', None, _('use git extended diff format')),
5943 ('g', 'git', None, _('use git extended diff format')),
5943 ] + templateopts,
5944 ] + templateopts,
5944 _('[-p] [-g]'),
5945 _('[-p] [-g]'),
5945 helpcategory=command.CATEGORY_CHANGE_NAVIGATION)
5946 helpcategory=command.CATEGORY_CHANGE_NAVIGATION)
5946 def tip(ui, repo, **opts):
5947 def tip(ui, repo, **opts):
5947 """show the tip revision (DEPRECATED)
5948 """show the tip revision (DEPRECATED)
5948
5949
5949 The tip revision (usually just called the tip) is the changeset
5950 The tip revision (usually just called the tip) is the changeset
5950 most recently added to the repository (and therefore the most
5951 most recently added to the repository (and therefore the most
5951 recently changed head).
5952 recently changed head).
5952
5953
5953 If you have just made a commit, that commit will be the tip. If
5954 If you have just made a commit, that commit will be the tip. If
5954 you have just pulled changes from another repository, the tip of
5955 you have just pulled changes from another repository, the tip of
5955 that repository becomes the current tip. The "tip" tag is special
5956 that repository becomes the current tip. The "tip" tag is special
5956 and cannot be renamed or assigned to a different changeset.
5957 and cannot be renamed or assigned to a different changeset.
5957
5958
5958 This command is deprecated, please use :hg:`heads` instead.
5959 This command is deprecated, please use :hg:`heads` instead.
5959
5960
5960 Returns 0 on success.
5961 Returns 0 on success.
5961 """
5962 """
5962 opts = pycompat.byteskwargs(opts)
5963 opts = pycompat.byteskwargs(opts)
5963 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
5964 displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
5964 displayer.show(repo['tip'])
5965 displayer.show(repo['tip'])
5965 displayer.close()
5966 displayer.close()
5966
5967
5967 @command('unbundle',
5968 @command('unbundle',
5968 [('u', 'update', None,
5969 [('u', 'update', None,
5969 _('update to new branch head if changesets were unbundled'))],
5970 _('update to new branch head if changesets were unbundled'))],
5970 _('[-u] FILE...'),
5971 _('[-u] FILE...'),
5971 helpcategory=command.CATEGORY_IMPORT_EXPORT)
5972 helpcategory=command.CATEGORY_IMPORT_EXPORT)
5972 def unbundle(ui, repo, fname1, *fnames, **opts):
5973 def unbundle(ui, repo, fname1, *fnames, **opts):
5973 """apply one or more bundle files
5974 """apply one or more bundle files
5974
5975
5975 Apply one or more bundle files generated by :hg:`bundle`.
5976 Apply one or more bundle files generated by :hg:`bundle`.
5976
5977
5977 Returns 0 on success, 1 if an update has unresolved files.
5978 Returns 0 on success, 1 if an update has unresolved files.
5978 """
5979 """
5979 fnames = (fname1,) + fnames
5980 fnames = (fname1,) + fnames
5980
5981
5981 with repo.lock():
5982 with repo.lock():
5982 for fname in fnames:
5983 for fname in fnames:
5983 f = hg.openpath(ui, fname)
5984 f = hg.openpath(ui, fname)
5984 gen = exchange.readbundle(ui, f, fname)
5985 gen = exchange.readbundle(ui, f, fname)
5985 if isinstance(gen, streamclone.streamcloneapplier):
5986 if isinstance(gen, streamclone.streamcloneapplier):
5986 raise error.Abort(
5987 raise error.Abort(
5987 _('packed bundles cannot be applied with '
5988 _('packed bundles cannot be applied with '
5988 '"hg unbundle"'),
5989 '"hg unbundle"'),
5989 hint=_('use "hg debugapplystreamclonebundle"'))
5990 hint=_('use "hg debugapplystreamclonebundle"'))
5990 url = 'bundle:' + fname
5991 url = 'bundle:' + fname
5991 try:
5992 try:
5992 txnname = 'unbundle'
5993 txnname = 'unbundle'
5993 if not isinstance(gen, bundle2.unbundle20):
5994 if not isinstance(gen, bundle2.unbundle20):
5994 txnname = 'unbundle\n%s' % util.hidepassword(url)
5995 txnname = 'unbundle\n%s' % util.hidepassword(url)
5995 with repo.transaction(txnname) as tr:
5996 with repo.transaction(txnname) as tr:
5996 op = bundle2.applybundle(repo, gen, tr, source='unbundle',
5997 op = bundle2.applybundle(repo, gen, tr, source='unbundle',
5997 url=url)
5998 url=url)
5998 except error.BundleUnknownFeatureError as exc:
5999 except error.BundleUnknownFeatureError as exc:
5999 raise error.Abort(
6000 raise error.Abort(
6000 _('%s: unknown bundle feature, %s') % (fname, exc),
6001 _('%s: unknown bundle feature, %s') % (fname, exc),
6001 hint=_("see https://mercurial-scm.org/"
6002 hint=_("see https://mercurial-scm.org/"
6002 "wiki/BundleFeature for more "
6003 "wiki/BundleFeature for more "
6003 "information"))
6004 "information"))
6004 modheads = bundle2.combinechangegroupresults(op)
6005 modheads = bundle2.combinechangegroupresults(op)
6005
6006
6006 return postincoming(ui, repo, modheads, opts.get(r'update'), None, None)
6007 return postincoming(ui, repo, modheads, opts.get(r'update'), None, None)
6007
6008
6008 @command('update|up|checkout|co',
6009 @command('update|up|checkout|co',
6009 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
6010 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
6010 ('c', 'check', None, _('require clean working directory')),
6011 ('c', 'check', None, _('require clean working directory')),
6011 ('m', 'merge', None, _('merge uncommitted changes')),
6012 ('m', 'merge', None, _('merge uncommitted changes')),
6012 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
6013 ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
6013 ('r', 'rev', '', _('revision'), _('REV'))
6014 ('r', 'rev', '', _('revision'), _('REV'))
6014 ] + mergetoolopts,
6015 ] + mergetoolopts,
6015 _('[-C|-c|-m] [-d DATE] [[-r] REV]'),
6016 _('[-C|-c|-m] [-d DATE] [[-r] REV]'),
6016 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6017 helpcategory=command.CATEGORY_WORKING_DIRECTORY,
6017 helpbasic=True)
6018 helpbasic=True)
6018 def update(ui, repo, node=None, **opts):
6019 def update(ui, repo, node=None, **opts):
6019 """update working directory (or switch revisions)
6020 """update working directory (or switch revisions)
6020
6021
6021 Update the repository's working directory to the specified
6022 Update the repository's working directory to the specified
6022 changeset. If no changeset is specified, update to the tip of the
6023 changeset. If no changeset is specified, update to the tip of the
6023 current named branch and move the active bookmark (see :hg:`help
6024 current named branch and move the active bookmark (see :hg:`help
6024 bookmarks`).
6025 bookmarks`).
6025
6026
6026 Update sets the working directory's parent revision to the specified
6027 Update sets the working directory's parent revision to the specified
6027 changeset (see :hg:`help parents`).
6028 changeset (see :hg:`help parents`).
6028
6029
6029 If the changeset is not a descendant or ancestor of the working
6030 If the changeset is not a descendant or ancestor of the working
6030 directory's parent and there are uncommitted changes, the update is
6031 directory's parent and there are uncommitted changes, the update is
6031 aborted. With the -c/--check option, the working directory is checked
6032 aborted. With the -c/--check option, the working directory is checked
6032 for uncommitted changes; if none are found, the working directory is
6033 for uncommitted changes; if none are found, the working directory is
6033 updated to the specified changeset.
6034 updated to the specified changeset.
6034
6035
6035 .. container:: verbose
6036 .. container:: verbose
6036
6037
6037 The -C/--clean, -c/--check, and -m/--merge options control what
6038 The -C/--clean, -c/--check, and -m/--merge options control what
6038 happens if the working directory contains uncommitted changes.
6039 happens if the working directory contains uncommitted changes.
6039 At most of one of them can be specified.
6040 At most of one of them can be specified.
6040
6041
6041 1. If no option is specified, and if
6042 1. If no option is specified, and if
6042 the requested changeset is an ancestor or descendant of
6043 the requested changeset is an ancestor or descendant of
6043 the working directory's parent, the uncommitted changes
6044 the working directory's parent, the uncommitted changes
6044 are merged into the requested changeset and the merged
6045 are merged into the requested changeset and the merged
6045 result is left uncommitted. If the requested changeset is
6046 result is left uncommitted. If the requested changeset is
6046 not an ancestor or descendant (that is, it is on another
6047 not an ancestor or descendant (that is, it is on another
6047 branch), the update is aborted and the uncommitted changes
6048 branch), the update is aborted and the uncommitted changes
6048 are preserved.
6049 are preserved.
6049
6050
6050 2. With the -m/--merge option, the update is allowed even if the
6051 2. With the -m/--merge option, the update is allowed even if the
6051 requested changeset is not an ancestor or descendant of
6052 requested changeset is not an ancestor or descendant of
6052 the working directory's parent.
6053 the working directory's parent.
6053
6054
6054 3. With the -c/--check option, the update is aborted and the
6055 3. With the -c/--check option, the update is aborted and the
6055 uncommitted changes are preserved.
6056 uncommitted changes are preserved.
6056
6057
6057 4. With the -C/--clean option, uncommitted changes are discarded and
6058 4. With the -C/--clean option, uncommitted changes are discarded and
6058 the working directory is updated to the requested changeset.
6059 the working directory is updated to the requested changeset.
6059
6060
6060 To cancel an uncommitted merge (and lose your changes), use
6061 To cancel an uncommitted merge (and lose your changes), use
6061 :hg:`merge --abort`.
6062 :hg:`merge --abort`.
6062
6063
6063 Use null as the changeset to remove the working directory (like
6064 Use null as the changeset to remove the working directory (like
6064 :hg:`clone -U`).
6065 :hg:`clone -U`).
6065
6066
6066 If you want to revert just one file to an older revision, use
6067 If you want to revert just one file to an older revision, use
6067 :hg:`revert [-r REV] NAME`.
6068 :hg:`revert [-r REV] NAME`.
6068
6069
6069 See :hg:`help dates` for a list of formats valid for -d/--date.
6070 See :hg:`help dates` for a list of formats valid for -d/--date.
6070
6071
6071 Returns 0 on success, 1 if there are unresolved files.
6072 Returns 0 on success, 1 if there are unresolved files.
6072 """
6073 """
6073 rev = opts.get(r'rev')
6074 rev = opts.get(r'rev')
6074 date = opts.get(r'date')
6075 date = opts.get(r'date')
6075 clean = opts.get(r'clean')
6076 clean = opts.get(r'clean')
6076 check = opts.get(r'check')
6077 check = opts.get(r'check')
6077 merge = opts.get(r'merge')
6078 merge = opts.get(r'merge')
6078 if rev and node:
6079 if rev and node:
6079 raise error.Abort(_("please specify just one revision"))
6080 raise error.Abort(_("please specify just one revision"))
6080
6081
6081 if ui.configbool('commands', 'update.requiredest'):
6082 if ui.configbool('commands', 'update.requiredest'):
6082 if not node and not rev and not date:
6083 if not node and not rev and not date:
6083 raise error.Abort(_('you must specify a destination'),
6084 raise error.Abort(_('you must specify a destination'),
6084 hint=_('for example: hg update ".::"'))
6085 hint=_('for example: hg update ".::"'))
6085
6086
6086 if rev is None or rev == '':
6087 if rev is None or rev == '':
6087 rev = node
6088 rev = node
6088
6089
6089 if date and rev is not None:
6090 if date and rev is not None:
6090 raise error.Abort(_("you can't specify a revision and a date"))
6091 raise error.Abort(_("you can't specify a revision and a date"))
6091
6092
6092 if len([x for x in (clean, check, merge) if x]) > 1:
6093 if len([x for x in (clean, check, merge) if x]) > 1:
6093 raise error.Abort(_("can only specify one of -C/--clean, -c/--check, "
6094 raise error.Abort(_("can only specify one of -C/--clean, -c/--check, "
6094 "or -m/--merge"))
6095 "or -m/--merge"))
6095
6096
6096 updatecheck = None
6097 updatecheck = None
6097 if check:
6098 if check:
6098 updatecheck = 'abort'
6099 updatecheck = 'abort'
6099 elif merge:
6100 elif merge:
6100 updatecheck = 'none'
6101 updatecheck = 'none'
6101
6102
6102 with repo.wlock():
6103 with repo.wlock():
6103 cmdutil.clearunfinished(repo)
6104 cmdutil.clearunfinished(repo)
6104
6105
6105 if date:
6106 if date:
6106 rev = cmdutil.finddate(ui, repo, date)
6107 rev = cmdutil.finddate(ui, repo, date)
6107
6108
6108 # if we defined a bookmark, we have to remember the original name
6109 # if we defined a bookmark, we have to remember the original name
6109 brev = rev
6110 brev = rev
6110 if rev:
6111 if rev:
6111 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
6112 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
6112 ctx = scmutil.revsingle(repo, rev, default=None)
6113 ctx = scmutil.revsingle(repo, rev, default=None)
6113 rev = ctx.rev()
6114 rev = ctx.rev()
6114 hidden = ctx.hidden()
6115 hidden = ctx.hidden()
6115 overrides = {('ui', 'forcemerge'): opts.get(r'tool', '')}
6116 overrides = {('ui', 'forcemerge'): opts.get(r'tool', '')}
6116 with ui.configoverride(overrides, 'update'):
6117 with ui.configoverride(overrides, 'update'):
6117 ret = hg.updatetotally(ui, repo, rev, brev, clean=clean,
6118 ret = hg.updatetotally(ui, repo, rev, brev, clean=clean,
6118 updatecheck=updatecheck)
6119 updatecheck=updatecheck)
6119 if hidden:
6120 if hidden:
6120 ctxstr = ctx.hex()[:12]
6121 ctxstr = ctx.hex()[:12]
6121 ui.warn(_("updated to hidden changeset %s\n") % ctxstr)
6122 ui.warn(_("updated to hidden changeset %s\n") % ctxstr)
6122
6123
6123 if ctx.obsolete():
6124 if ctx.obsolete():
6124 obsfatemsg = obsutil._getfilteredreason(repo, ctxstr, ctx)
6125 obsfatemsg = obsutil._getfilteredreason(repo, ctxstr, ctx)
6125 ui.warn("(%s)\n" % obsfatemsg)
6126 ui.warn("(%s)\n" % obsfatemsg)
6126 return ret
6127 return ret
6127
6128
6128 @command('verify', [], helpcategory=command.CATEGORY_MAINTENANCE)
6129 @command('verify', [], helpcategory=command.CATEGORY_MAINTENANCE)
6129 def verify(ui, repo):
6130 def verify(ui, repo):
6130 """verify the integrity of the repository
6131 """verify the integrity of the repository
6131
6132
6132 Verify the integrity of the current repository.
6133 Verify the integrity of the current repository.
6133
6134
6134 This will perform an extensive check of the repository's
6135 This will perform an extensive check of the repository's
6135 integrity, validating the hashes and checksums of each entry in
6136 integrity, validating the hashes and checksums of each entry in
6136 the changelog, manifest, and tracked files, as well as the
6137 the changelog, manifest, and tracked files, as well as the
6137 integrity of their crosslinks and indices.
6138 integrity of their crosslinks and indices.
6138
6139
6139 Please see https://mercurial-scm.org/wiki/RepositoryCorruption
6140 Please see https://mercurial-scm.org/wiki/RepositoryCorruption
6140 for more information about recovery from corruption of the
6141 for more information about recovery from corruption of the
6141 repository.
6142 repository.
6142
6143
6143 Returns 0 on success, 1 if errors are encountered.
6144 Returns 0 on success, 1 if errors are encountered.
6144 """
6145 """
6145 return hg.verify(repo)
6146 return hg.verify(repo)
6146
6147
6147 @command(
6148 @command(
6148 'version', [] + formatteropts, helpcategory=command.CATEGORY_HELP,
6149 'version', [] + formatteropts, helpcategory=command.CATEGORY_HELP,
6149 norepo=True, intents={INTENT_READONLY})
6150 norepo=True, intents={INTENT_READONLY})
6150 def version_(ui, **opts):
6151 def version_(ui, **opts):
6151 """output version and copyright information
6152 """output version and copyright information
6152
6153
6153 .. container:: verbose
6154 .. container:: verbose
6154
6155
6155 Template:
6156 Template:
6156
6157
6157 The following keywords are supported. See also :hg:`help templates`.
6158 The following keywords are supported. See also :hg:`help templates`.
6158
6159
6159 :extensions: List of extensions.
6160 :extensions: List of extensions.
6160 :ver: String. Version number.
6161 :ver: String. Version number.
6161
6162
6162 And each entry of ``{extensions}`` provides the following sub-keywords
6163 And each entry of ``{extensions}`` provides the following sub-keywords
6163 in addition to ``{ver}``.
6164 in addition to ``{ver}``.
6164
6165
6165 :bundled: Boolean. True if included in the release.
6166 :bundled: Boolean. True if included in the release.
6166 :name: String. Extension name.
6167 :name: String. Extension name.
6167 """
6168 """
6168 opts = pycompat.byteskwargs(opts)
6169 opts = pycompat.byteskwargs(opts)
6169 if ui.verbose:
6170 if ui.verbose:
6170 ui.pager('version')
6171 ui.pager('version')
6171 fm = ui.formatter("version", opts)
6172 fm = ui.formatter("version", opts)
6172 fm.startitem()
6173 fm.startitem()
6173 fm.write("ver", _("Mercurial Distributed SCM (version %s)\n"),
6174 fm.write("ver", _("Mercurial Distributed SCM (version %s)\n"),
6174 util.version())
6175 util.version())
6175 license = _(
6176 license = _(
6176 "(see https://mercurial-scm.org for more information)\n"
6177 "(see https://mercurial-scm.org for more information)\n"
6177 "\nCopyright (C) 2005-2019 Matt Mackall and others\n"
6178 "\nCopyright (C) 2005-2019 Matt Mackall and others\n"
6178 "This is free software; see the source for copying conditions. "
6179 "This is free software; see the source for copying conditions. "
6179 "There is NO\nwarranty; "
6180 "There is NO\nwarranty; "
6180 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
6181 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
6181 )
6182 )
6182 if not ui.quiet:
6183 if not ui.quiet:
6183 fm.plain(license)
6184 fm.plain(license)
6184
6185
6185 if ui.verbose:
6186 if ui.verbose:
6186 fm.plain(_("\nEnabled extensions:\n\n"))
6187 fm.plain(_("\nEnabled extensions:\n\n"))
6187 # format names and versions into columns
6188 # format names and versions into columns
6188 names = []
6189 names = []
6189 vers = []
6190 vers = []
6190 isinternals = []
6191 isinternals = []
6191 for name, module in extensions.extensions():
6192 for name, module in extensions.extensions():
6192 names.append(name)
6193 names.append(name)
6193 vers.append(extensions.moduleversion(module) or None)
6194 vers.append(extensions.moduleversion(module) or None)
6194 isinternals.append(extensions.ismoduleinternal(module))
6195 isinternals.append(extensions.ismoduleinternal(module))
6195 fn = fm.nested("extensions", tmpl='{name}\n')
6196 fn = fm.nested("extensions", tmpl='{name}\n')
6196 if names:
6197 if names:
6197 namefmt = " %%-%ds " % max(len(n) for n in names)
6198 namefmt = " %%-%ds " % max(len(n) for n in names)
6198 places = [_("external"), _("internal")]
6199 places = [_("external"), _("internal")]
6199 for n, v, p in zip(names, vers, isinternals):
6200 for n, v, p in zip(names, vers, isinternals):
6200 fn.startitem()
6201 fn.startitem()
6201 fn.condwrite(ui.verbose, "name", namefmt, n)
6202 fn.condwrite(ui.verbose, "name", namefmt, n)
6202 if ui.verbose:
6203 if ui.verbose:
6203 fn.plain("%s " % places[p])
6204 fn.plain("%s " % places[p])
6204 fn.data(bundled=p)
6205 fn.data(bundled=p)
6205 fn.condwrite(ui.verbose and v, "ver", "%s", v)
6206 fn.condwrite(ui.verbose and v, "ver", "%s", v)
6206 if ui.verbose:
6207 if ui.verbose:
6207 fn.plain("\n")
6208 fn.plain("\n")
6208 fn.end()
6209 fn.end()
6209 fm.end()
6210 fm.end()
6210
6211
6211 def loadcmdtable(ui, name, cmdtable):
6212 def loadcmdtable(ui, name, cmdtable):
6212 """Load command functions from specified cmdtable
6213 """Load command functions from specified cmdtable
6213 """
6214 """
6214 cmdtable = cmdtable.copy()
6215 cmdtable = cmdtable.copy()
6215 for cmd in list(cmdtable):
6216 for cmd in list(cmdtable):
6216 if not cmd.startswith('^'):
6217 if not cmd.startswith('^'):
6217 continue
6218 continue
6218 ui.deprecwarn("old-style command registration '%s' in extension '%s'"
6219 ui.deprecwarn("old-style command registration '%s' in extension '%s'"
6219 % (cmd, name), '4.8')
6220 % (cmd, name), '4.8')
6220 entry = cmdtable.pop(cmd)
6221 entry = cmdtable.pop(cmd)
6221 entry[0].helpbasic = True
6222 entry[0].helpbasic = True
6222 cmdtable[cmd[1:]] = entry
6223 cmdtable[cmd[1:]] = entry
6223
6224
6224 overrides = [cmd for cmd in cmdtable if cmd in table]
6225 overrides = [cmd for cmd in cmdtable if cmd in table]
6225 if overrides:
6226 if overrides:
6226 ui.warn(_("extension '%s' overrides commands: %s\n")
6227 ui.warn(_("extension '%s' overrides commands: %s\n")
6227 % (name, " ".join(overrides)))
6228 % (name, " ".join(overrides)))
6228 table.update(cmdtable)
6229 table.update(cmdtable)
@@ -1,1841 +1,1846 b''
1 # scmutil.py - Mercurial core utility functions
1 # scmutil.py - Mercurial core utility functions
2 #
2 #
3 # Copyright Matt Mackall <mpm@selenic.com>
3 # Copyright Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import errno
10 import errno
11 import glob
11 import glob
12 import hashlib
12 import hashlib
13 import os
13 import os
14 import posixpath
14 import re
15 import re
15 import subprocess
16 import subprocess
16 import weakref
17 import weakref
17
18
18 from .i18n import _
19 from .i18n import _
19 from .node import (
20 from .node import (
20 bin,
21 bin,
21 hex,
22 hex,
22 nullid,
23 nullid,
23 nullrev,
24 nullrev,
24 short,
25 short,
25 wdirid,
26 wdirid,
26 wdirrev,
27 wdirrev,
27 )
28 )
28
29
29 from . import (
30 from . import (
30 encoding,
31 encoding,
31 error,
32 error,
32 match as matchmod,
33 match as matchmod,
33 obsolete,
34 obsolete,
34 obsutil,
35 obsutil,
35 pathutil,
36 pathutil,
36 phases,
37 phases,
37 policy,
38 policy,
38 pycompat,
39 pycompat,
39 revsetlang,
40 revsetlang,
40 similar,
41 similar,
41 smartset,
42 smartset,
42 url,
43 url,
43 util,
44 util,
44 vfs,
45 vfs,
45 )
46 )
46
47
47 from .utils import (
48 from .utils import (
48 procutil,
49 procutil,
49 stringutil,
50 stringutil,
50 )
51 )
51
52
52 if pycompat.iswindows:
53 if pycompat.iswindows:
53 from . import scmwindows as scmplatform
54 from . import scmwindows as scmplatform
54 else:
55 else:
55 from . import scmposix as scmplatform
56 from . import scmposix as scmplatform
56
57
57 parsers = policy.importmod(r'parsers')
58 parsers = policy.importmod(r'parsers')
58
59
59 termsize = scmplatform.termsize
60 termsize = scmplatform.termsize
60
61
61 class status(tuple):
62 class status(tuple):
62 '''Named tuple with a list of files per status. The 'deleted', 'unknown'
63 '''Named tuple with a list of files per status. The 'deleted', 'unknown'
63 and 'ignored' properties are only relevant to the working copy.
64 and 'ignored' properties are only relevant to the working copy.
64 '''
65 '''
65
66
66 __slots__ = ()
67 __slots__ = ()
67
68
68 def __new__(cls, modified, added, removed, deleted, unknown, ignored,
69 def __new__(cls, modified, added, removed, deleted, unknown, ignored,
69 clean):
70 clean):
70 return tuple.__new__(cls, (modified, added, removed, deleted, unknown,
71 return tuple.__new__(cls, (modified, added, removed, deleted, unknown,
71 ignored, clean))
72 ignored, clean))
72
73
73 @property
74 @property
74 def modified(self):
75 def modified(self):
75 '''files that have been modified'''
76 '''files that have been modified'''
76 return self[0]
77 return self[0]
77
78
78 @property
79 @property
79 def added(self):
80 def added(self):
80 '''files that have been added'''
81 '''files that have been added'''
81 return self[1]
82 return self[1]
82
83
83 @property
84 @property
84 def removed(self):
85 def removed(self):
85 '''files that have been removed'''
86 '''files that have been removed'''
86 return self[2]
87 return self[2]
87
88
88 @property
89 @property
89 def deleted(self):
90 def deleted(self):
90 '''files that are in the dirstate, but have been deleted from the
91 '''files that are in the dirstate, but have been deleted from the
91 working copy (aka "missing")
92 working copy (aka "missing")
92 '''
93 '''
93 return self[3]
94 return self[3]
94
95
95 @property
96 @property
96 def unknown(self):
97 def unknown(self):
97 '''files not in the dirstate that are not ignored'''
98 '''files not in the dirstate that are not ignored'''
98 return self[4]
99 return self[4]
99
100
100 @property
101 @property
101 def ignored(self):
102 def ignored(self):
102 '''files not in the dirstate that are ignored (by _dirignore())'''
103 '''files not in the dirstate that are ignored (by _dirignore())'''
103 return self[5]
104 return self[5]
104
105
105 @property
106 @property
106 def clean(self):
107 def clean(self):
107 '''files that have not been modified'''
108 '''files that have not been modified'''
108 return self[6]
109 return self[6]
109
110
110 def __repr__(self, *args, **kwargs):
111 def __repr__(self, *args, **kwargs):
111 return ((r'<status modified=%s, added=%s, removed=%s, deleted=%s, '
112 return ((r'<status modified=%s, added=%s, removed=%s, deleted=%s, '
112 r'unknown=%s, ignored=%s, clean=%s>') %
113 r'unknown=%s, ignored=%s, clean=%s>') %
113 tuple(pycompat.sysstr(stringutil.pprint(v)) for v in self))
114 tuple(pycompat.sysstr(stringutil.pprint(v)) for v in self))
114
115
115 def itersubrepos(ctx1, ctx2):
116 def itersubrepos(ctx1, ctx2):
116 """find subrepos in ctx1 or ctx2"""
117 """find subrepos in ctx1 or ctx2"""
117 # Create a (subpath, ctx) mapping where we prefer subpaths from
118 # Create a (subpath, ctx) mapping where we prefer subpaths from
118 # ctx1. The subpaths from ctx2 are important when the .hgsub file
119 # ctx1. The subpaths from ctx2 are important when the .hgsub file
119 # has been modified (in ctx2) but not yet committed (in ctx1).
120 # has been modified (in ctx2) but not yet committed (in ctx1).
120 subpaths = dict.fromkeys(ctx2.substate, ctx2)
121 subpaths = dict.fromkeys(ctx2.substate, ctx2)
121 subpaths.update(dict.fromkeys(ctx1.substate, ctx1))
122 subpaths.update(dict.fromkeys(ctx1.substate, ctx1))
122
123
123 missing = set()
124 missing = set()
124
125
125 for subpath in ctx2.substate:
126 for subpath in ctx2.substate:
126 if subpath not in ctx1.substate:
127 if subpath not in ctx1.substate:
127 del subpaths[subpath]
128 del subpaths[subpath]
128 missing.add(subpath)
129 missing.add(subpath)
129
130
130 for subpath, ctx in sorted(subpaths.iteritems()):
131 for subpath, ctx in sorted(subpaths.iteritems()):
131 yield subpath, ctx.sub(subpath)
132 yield subpath, ctx.sub(subpath)
132
133
133 # Yield an empty subrepo based on ctx1 for anything only in ctx2. That way,
134 # Yield an empty subrepo based on ctx1 for anything only in ctx2. That way,
134 # status and diff will have an accurate result when it does
135 # status and diff will have an accurate result when it does
135 # 'sub.{status|diff}(rev2)'. Otherwise, the ctx2 subrepo is compared
136 # 'sub.{status|diff}(rev2)'. Otherwise, the ctx2 subrepo is compared
136 # against itself.
137 # against itself.
137 for subpath in missing:
138 for subpath in missing:
138 yield subpath, ctx2.nullsub(subpath, ctx1)
139 yield subpath, ctx2.nullsub(subpath, ctx1)
139
140
140 def nochangesfound(ui, repo, excluded=None):
141 def nochangesfound(ui, repo, excluded=None):
141 '''Report no changes for push/pull, excluded is None or a list of
142 '''Report no changes for push/pull, excluded is None or a list of
142 nodes excluded from the push/pull.
143 nodes excluded from the push/pull.
143 '''
144 '''
144 secretlist = []
145 secretlist = []
145 if excluded:
146 if excluded:
146 for n in excluded:
147 for n in excluded:
147 ctx = repo[n]
148 ctx = repo[n]
148 if ctx.phase() >= phases.secret and not ctx.extinct():
149 if ctx.phase() >= phases.secret and not ctx.extinct():
149 secretlist.append(n)
150 secretlist.append(n)
150
151
151 if secretlist:
152 if secretlist:
152 ui.status(_("no changes found (ignored %d secret changesets)\n")
153 ui.status(_("no changes found (ignored %d secret changesets)\n")
153 % len(secretlist))
154 % len(secretlist))
154 else:
155 else:
155 ui.status(_("no changes found\n"))
156 ui.status(_("no changes found\n"))
156
157
157 def callcatch(ui, func):
158 def callcatch(ui, func):
158 """call func() with global exception handling
159 """call func() with global exception handling
159
160
160 return func() if no exception happens. otherwise do some error handling
161 return func() if no exception happens. otherwise do some error handling
161 and return an exit code accordingly. does not handle all exceptions.
162 and return an exit code accordingly. does not handle all exceptions.
162 """
163 """
163 try:
164 try:
164 try:
165 try:
165 return func()
166 return func()
166 except: # re-raises
167 except: # re-raises
167 ui.traceback()
168 ui.traceback()
168 raise
169 raise
169 # Global exception handling, alphabetically
170 # Global exception handling, alphabetically
170 # Mercurial-specific first, followed by built-in and library exceptions
171 # Mercurial-specific first, followed by built-in and library exceptions
171 except error.LockHeld as inst:
172 except error.LockHeld as inst:
172 if inst.errno == errno.ETIMEDOUT:
173 if inst.errno == errno.ETIMEDOUT:
173 reason = _('timed out waiting for lock held by %r') % (
174 reason = _('timed out waiting for lock held by %r') % (
174 pycompat.bytestr(inst.locker))
175 pycompat.bytestr(inst.locker))
175 else:
176 else:
176 reason = _('lock held by %r') % inst.locker
177 reason = _('lock held by %r') % inst.locker
177 ui.error(_("abort: %s: %s\n") % (
178 ui.error(_("abort: %s: %s\n") % (
178 inst.desc or stringutil.forcebytestr(inst.filename), reason))
179 inst.desc or stringutil.forcebytestr(inst.filename), reason))
179 if not inst.locker:
180 if not inst.locker:
180 ui.error(_("(lock might be very busy)\n"))
181 ui.error(_("(lock might be very busy)\n"))
181 except error.LockUnavailable as inst:
182 except error.LockUnavailable as inst:
182 ui.error(_("abort: could not lock %s: %s\n") %
183 ui.error(_("abort: could not lock %s: %s\n") %
183 (inst.desc or stringutil.forcebytestr(inst.filename),
184 (inst.desc or stringutil.forcebytestr(inst.filename),
184 encoding.strtolocal(inst.strerror)))
185 encoding.strtolocal(inst.strerror)))
185 except error.OutOfBandError as inst:
186 except error.OutOfBandError as inst:
186 if inst.args:
187 if inst.args:
187 msg = _("abort: remote error:\n")
188 msg = _("abort: remote error:\n")
188 else:
189 else:
189 msg = _("abort: remote error\n")
190 msg = _("abort: remote error\n")
190 ui.error(msg)
191 ui.error(msg)
191 if inst.args:
192 if inst.args:
192 ui.error(''.join(inst.args))
193 ui.error(''.join(inst.args))
193 if inst.hint:
194 if inst.hint:
194 ui.error('(%s)\n' % inst.hint)
195 ui.error('(%s)\n' % inst.hint)
195 except error.RepoError as inst:
196 except error.RepoError as inst:
196 ui.error(_("abort: %s!\n") % inst)
197 ui.error(_("abort: %s!\n") % inst)
197 if inst.hint:
198 if inst.hint:
198 ui.error(_("(%s)\n") % inst.hint)
199 ui.error(_("(%s)\n") % inst.hint)
199 except error.ResponseError as inst:
200 except error.ResponseError as inst:
200 ui.error(_("abort: %s") % inst.args[0])
201 ui.error(_("abort: %s") % inst.args[0])
201 msg = inst.args[1]
202 msg = inst.args[1]
202 if isinstance(msg, type(u'')):
203 if isinstance(msg, type(u'')):
203 msg = pycompat.sysbytes(msg)
204 msg = pycompat.sysbytes(msg)
204 if not isinstance(msg, bytes):
205 if not isinstance(msg, bytes):
205 ui.error(" %r\n" % (msg,))
206 ui.error(" %r\n" % (msg,))
206 elif not msg:
207 elif not msg:
207 ui.error(_(" empty string\n"))
208 ui.error(_(" empty string\n"))
208 else:
209 else:
209 ui.error("\n%r\n" % pycompat.bytestr(stringutil.ellipsis(msg)))
210 ui.error("\n%r\n" % pycompat.bytestr(stringutil.ellipsis(msg)))
210 except error.CensoredNodeError as inst:
211 except error.CensoredNodeError as inst:
211 ui.error(_("abort: file censored %s!\n") % inst)
212 ui.error(_("abort: file censored %s!\n") % inst)
212 except error.StorageError as inst:
213 except error.StorageError as inst:
213 ui.error(_("abort: %s!\n") % inst)
214 ui.error(_("abort: %s!\n") % inst)
214 if inst.hint:
215 if inst.hint:
215 ui.error(_("(%s)\n") % inst.hint)
216 ui.error(_("(%s)\n") % inst.hint)
216 except error.InterventionRequired as inst:
217 except error.InterventionRequired as inst:
217 ui.error("%s\n" % inst)
218 ui.error("%s\n" % inst)
218 if inst.hint:
219 if inst.hint:
219 ui.error(_("(%s)\n") % inst.hint)
220 ui.error(_("(%s)\n") % inst.hint)
220 return 1
221 return 1
221 except error.WdirUnsupported:
222 except error.WdirUnsupported:
222 ui.error(_("abort: working directory revision cannot be specified\n"))
223 ui.error(_("abort: working directory revision cannot be specified\n"))
223 except error.Abort as inst:
224 except error.Abort as inst:
224 ui.error(_("abort: %s\n") % inst)
225 ui.error(_("abort: %s\n") % inst)
225 if inst.hint:
226 if inst.hint:
226 ui.error(_("(%s)\n") % inst.hint)
227 ui.error(_("(%s)\n") % inst.hint)
227 except ImportError as inst:
228 except ImportError as inst:
228 ui.error(_("abort: %s!\n") % stringutil.forcebytestr(inst))
229 ui.error(_("abort: %s!\n") % stringutil.forcebytestr(inst))
229 m = stringutil.forcebytestr(inst).split()[-1]
230 m = stringutil.forcebytestr(inst).split()[-1]
230 if m in "mpatch bdiff".split():
231 if m in "mpatch bdiff".split():
231 ui.error(_("(did you forget to compile extensions?)\n"))
232 ui.error(_("(did you forget to compile extensions?)\n"))
232 elif m in "zlib".split():
233 elif m in "zlib".split():
233 ui.error(_("(is your Python install correct?)\n"))
234 ui.error(_("(is your Python install correct?)\n"))
234 except (IOError, OSError) as inst:
235 except (IOError, OSError) as inst:
235 if util.safehasattr(inst, "code"): # HTTPError
236 if util.safehasattr(inst, "code"): # HTTPError
236 ui.error(_("abort: %s\n") % stringutil.forcebytestr(inst))
237 ui.error(_("abort: %s\n") % stringutil.forcebytestr(inst))
237 elif util.safehasattr(inst, "reason"): # URLError or SSLError
238 elif util.safehasattr(inst, "reason"): # URLError or SSLError
238 try: # usually it is in the form (errno, strerror)
239 try: # usually it is in the form (errno, strerror)
239 reason = inst.reason.args[1]
240 reason = inst.reason.args[1]
240 except (AttributeError, IndexError):
241 except (AttributeError, IndexError):
241 # it might be anything, for example a string
242 # it might be anything, for example a string
242 reason = inst.reason
243 reason = inst.reason
243 if isinstance(reason, pycompat.unicode):
244 if isinstance(reason, pycompat.unicode):
244 # SSLError of Python 2.7.9 contains a unicode
245 # SSLError of Python 2.7.9 contains a unicode
245 reason = encoding.unitolocal(reason)
246 reason = encoding.unitolocal(reason)
246 ui.error(_("abort: error: %s\n") % reason)
247 ui.error(_("abort: error: %s\n") % reason)
247 elif (util.safehasattr(inst, "args")
248 elif (util.safehasattr(inst, "args")
248 and inst.args and inst.args[0] == errno.EPIPE):
249 and inst.args and inst.args[0] == errno.EPIPE):
249 pass
250 pass
250 elif getattr(inst, "strerror", None): # common IOError or OSError
251 elif getattr(inst, "strerror", None): # common IOError or OSError
251 if getattr(inst, "filename", None) is not None:
252 if getattr(inst, "filename", None) is not None:
252 ui.error(_("abort: %s: '%s'\n") % (
253 ui.error(_("abort: %s: '%s'\n") % (
253 encoding.strtolocal(inst.strerror),
254 encoding.strtolocal(inst.strerror),
254 stringutil.forcebytestr(inst.filename)))
255 stringutil.forcebytestr(inst.filename)))
255 else:
256 else:
256 ui.error(_("abort: %s\n") % encoding.strtolocal(inst.strerror))
257 ui.error(_("abort: %s\n") % encoding.strtolocal(inst.strerror))
257 else: # suspicious IOError
258 else: # suspicious IOError
258 raise
259 raise
259 except MemoryError:
260 except MemoryError:
260 ui.error(_("abort: out of memory\n"))
261 ui.error(_("abort: out of memory\n"))
261 except SystemExit as inst:
262 except SystemExit as inst:
262 # Commands shouldn't sys.exit directly, but give a return code.
263 # Commands shouldn't sys.exit directly, but give a return code.
263 # Just in case catch this and and pass exit code to caller.
264 # Just in case catch this and and pass exit code to caller.
264 return inst.code
265 return inst.code
265
266
266 return -1
267 return -1
267
268
268 def checknewlabel(repo, lbl, kind):
269 def checknewlabel(repo, lbl, kind):
269 # Do not use the "kind" parameter in ui output.
270 # Do not use the "kind" parameter in ui output.
270 # It makes strings difficult to translate.
271 # It makes strings difficult to translate.
271 if lbl in ['tip', '.', 'null']:
272 if lbl in ['tip', '.', 'null']:
272 raise error.Abort(_("the name '%s' is reserved") % lbl)
273 raise error.Abort(_("the name '%s' is reserved") % lbl)
273 for c in (':', '\0', '\n', '\r'):
274 for c in (':', '\0', '\n', '\r'):
274 if c in lbl:
275 if c in lbl:
275 raise error.Abort(
276 raise error.Abort(
276 _("%r cannot be used in a name") % pycompat.bytestr(c))
277 _("%r cannot be used in a name") % pycompat.bytestr(c))
277 try:
278 try:
278 int(lbl)
279 int(lbl)
279 raise error.Abort(_("cannot use an integer as a name"))
280 raise error.Abort(_("cannot use an integer as a name"))
280 except ValueError:
281 except ValueError:
281 pass
282 pass
282 if lbl.strip() != lbl:
283 if lbl.strip() != lbl:
283 raise error.Abort(_("leading or trailing whitespace in name %r") % lbl)
284 raise error.Abort(_("leading or trailing whitespace in name %r") % lbl)
284
285
285 def checkfilename(f):
286 def checkfilename(f):
286 '''Check that the filename f is an acceptable filename for a tracked file'''
287 '''Check that the filename f is an acceptable filename for a tracked file'''
287 if '\r' in f or '\n' in f:
288 if '\r' in f or '\n' in f:
288 raise error.Abort(_("'\\n' and '\\r' disallowed in filenames: %r")
289 raise error.Abort(_("'\\n' and '\\r' disallowed in filenames: %r")
289 % pycompat.bytestr(f))
290 % pycompat.bytestr(f))
290
291
291 def checkportable(ui, f):
292 def checkportable(ui, f):
292 '''Check if filename f is portable and warn or abort depending on config'''
293 '''Check if filename f is portable and warn or abort depending on config'''
293 checkfilename(f)
294 checkfilename(f)
294 abort, warn = checkportabilityalert(ui)
295 abort, warn = checkportabilityalert(ui)
295 if abort or warn:
296 if abort or warn:
296 msg = util.checkwinfilename(f)
297 msg = util.checkwinfilename(f)
297 if msg:
298 if msg:
298 msg = "%s: %s" % (msg, procutil.shellquote(f))
299 msg = "%s: %s" % (msg, procutil.shellquote(f))
299 if abort:
300 if abort:
300 raise error.Abort(msg)
301 raise error.Abort(msg)
301 ui.warn(_("warning: %s\n") % msg)
302 ui.warn(_("warning: %s\n") % msg)
302
303
303 def checkportabilityalert(ui):
304 def checkportabilityalert(ui):
304 '''check if the user's config requests nothing, a warning, or abort for
305 '''check if the user's config requests nothing, a warning, or abort for
305 non-portable filenames'''
306 non-portable filenames'''
306 val = ui.config('ui', 'portablefilenames')
307 val = ui.config('ui', 'portablefilenames')
307 lval = val.lower()
308 lval = val.lower()
308 bval = stringutil.parsebool(val)
309 bval = stringutil.parsebool(val)
309 abort = pycompat.iswindows or lval == 'abort'
310 abort = pycompat.iswindows or lval == 'abort'
310 warn = bval or lval == 'warn'
311 warn = bval or lval == 'warn'
311 if bval is None and not (warn or abort or lval == 'ignore'):
312 if bval is None and not (warn or abort or lval == 'ignore'):
312 raise error.ConfigError(
313 raise error.ConfigError(
313 _("ui.portablefilenames value is invalid ('%s')") % val)
314 _("ui.portablefilenames value is invalid ('%s')") % val)
314 return abort, warn
315 return abort, warn
315
316
316 class casecollisionauditor(object):
317 class casecollisionauditor(object):
317 def __init__(self, ui, abort, dirstate):
318 def __init__(self, ui, abort, dirstate):
318 self._ui = ui
319 self._ui = ui
319 self._abort = abort
320 self._abort = abort
320 allfiles = '\0'.join(dirstate._map)
321 allfiles = '\0'.join(dirstate._map)
321 self._loweredfiles = set(encoding.lower(allfiles).split('\0'))
322 self._loweredfiles = set(encoding.lower(allfiles).split('\0'))
322 self._dirstate = dirstate
323 self._dirstate = dirstate
323 # The purpose of _newfiles is so that we don't complain about
324 # The purpose of _newfiles is so that we don't complain about
324 # case collisions if someone were to call this object with the
325 # case collisions if someone were to call this object with the
325 # same filename twice.
326 # same filename twice.
326 self._newfiles = set()
327 self._newfiles = set()
327
328
328 def __call__(self, f):
329 def __call__(self, f):
329 if f in self._newfiles:
330 if f in self._newfiles:
330 return
331 return
331 fl = encoding.lower(f)
332 fl = encoding.lower(f)
332 if fl in self._loweredfiles and f not in self._dirstate:
333 if fl in self._loweredfiles and f not in self._dirstate:
333 msg = _('possible case-folding collision for %s') % f
334 msg = _('possible case-folding collision for %s') % f
334 if self._abort:
335 if self._abort:
335 raise error.Abort(msg)
336 raise error.Abort(msg)
336 self._ui.warn(_("warning: %s\n") % msg)
337 self._ui.warn(_("warning: %s\n") % msg)
337 self._loweredfiles.add(fl)
338 self._loweredfiles.add(fl)
338 self._newfiles.add(f)
339 self._newfiles.add(f)
339
340
340 def filteredhash(repo, maxrev):
341 def filteredhash(repo, maxrev):
341 """build hash of filtered revisions in the current repoview.
342 """build hash of filtered revisions in the current repoview.
342
343
343 Multiple caches perform up-to-date validation by checking that the
344 Multiple caches perform up-to-date validation by checking that the
344 tiprev and tipnode stored in the cache file match the current repository.
345 tiprev and tipnode stored in the cache file match the current repository.
345 However, this is not sufficient for validating repoviews because the set
346 However, this is not sufficient for validating repoviews because the set
346 of revisions in the view may change without the repository tiprev and
347 of revisions in the view may change without the repository tiprev and
347 tipnode changing.
348 tipnode changing.
348
349
349 This function hashes all the revs filtered from the view and returns
350 This function hashes all the revs filtered from the view and returns
350 that SHA-1 digest.
351 that SHA-1 digest.
351 """
352 """
352 cl = repo.changelog
353 cl = repo.changelog
353 if not cl.filteredrevs:
354 if not cl.filteredrevs:
354 return None
355 return None
355 key = None
356 key = None
356 revs = sorted(r for r in cl.filteredrevs if r <= maxrev)
357 revs = sorted(r for r in cl.filteredrevs if r <= maxrev)
357 if revs:
358 if revs:
358 s = hashlib.sha1()
359 s = hashlib.sha1()
359 for rev in revs:
360 for rev in revs:
360 s.update('%d;' % rev)
361 s.update('%d;' % rev)
361 key = s.digest()
362 key = s.digest()
362 return key
363 return key
363
364
364 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
365 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
365 '''yield every hg repository under path, always recursively.
366 '''yield every hg repository under path, always recursively.
366 The recurse flag will only control recursion into repo working dirs'''
367 The recurse flag will only control recursion into repo working dirs'''
367 def errhandler(err):
368 def errhandler(err):
368 if err.filename == path:
369 if err.filename == path:
369 raise err
370 raise err
370 samestat = getattr(os.path, 'samestat', None)
371 samestat = getattr(os.path, 'samestat', None)
371 if followsym and samestat is not None:
372 if followsym and samestat is not None:
372 def adddir(dirlst, dirname):
373 def adddir(dirlst, dirname):
373 dirstat = os.stat(dirname)
374 dirstat = os.stat(dirname)
374 match = any(samestat(dirstat, lstdirstat) for lstdirstat in dirlst)
375 match = any(samestat(dirstat, lstdirstat) for lstdirstat in dirlst)
375 if not match:
376 if not match:
376 dirlst.append(dirstat)
377 dirlst.append(dirstat)
377 return not match
378 return not match
378 else:
379 else:
379 followsym = False
380 followsym = False
380
381
381 if (seen_dirs is None) and followsym:
382 if (seen_dirs is None) and followsym:
382 seen_dirs = []
383 seen_dirs = []
383 adddir(seen_dirs, path)
384 adddir(seen_dirs, path)
384 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
385 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
385 dirs.sort()
386 dirs.sort()
386 if '.hg' in dirs:
387 if '.hg' in dirs:
387 yield root # found a repository
388 yield root # found a repository
388 qroot = os.path.join(root, '.hg', 'patches')
389 qroot = os.path.join(root, '.hg', 'patches')
389 if os.path.isdir(os.path.join(qroot, '.hg')):
390 if os.path.isdir(os.path.join(qroot, '.hg')):
390 yield qroot # we have a patch queue repo here
391 yield qroot # we have a patch queue repo here
391 if recurse:
392 if recurse:
392 # avoid recursing inside the .hg directory
393 # avoid recursing inside the .hg directory
393 dirs.remove('.hg')
394 dirs.remove('.hg')
394 else:
395 else:
395 dirs[:] = [] # don't descend further
396 dirs[:] = [] # don't descend further
396 elif followsym:
397 elif followsym:
397 newdirs = []
398 newdirs = []
398 for d in dirs:
399 for d in dirs:
399 fname = os.path.join(root, d)
400 fname = os.path.join(root, d)
400 if adddir(seen_dirs, fname):
401 if adddir(seen_dirs, fname):
401 if os.path.islink(fname):
402 if os.path.islink(fname):
402 for hgname in walkrepos(fname, True, seen_dirs):
403 for hgname in walkrepos(fname, True, seen_dirs):
403 yield hgname
404 yield hgname
404 else:
405 else:
405 newdirs.append(d)
406 newdirs.append(d)
406 dirs[:] = newdirs
407 dirs[:] = newdirs
407
408
408 def binnode(ctx):
409 def binnode(ctx):
409 """Return binary node id for a given basectx"""
410 """Return binary node id for a given basectx"""
410 node = ctx.node()
411 node = ctx.node()
411 if node is None:
412 if node is None:
412 return wdirid
413 return wdirid
413 return node
414 return node
414
415
415 def intrev(ctx):
416 def intrev(ctx):
416 """Return integer for a given basectx that can be used in comparison or
417 """Return integer for a given basectx that can be used in comparison or
417 arithmetic operation"""
418 arithmetic operation"""
418 rev = ctx.rev()
419 rev = ctx.rev()
419 if rev is None:
420 if rev is None:
420 return wdirrev
421 return wdirrev
421 return rev
422 return rev
422
423
423 def formatchangeid(ctx):
424 def formatchangeid(ctx):
424 """Format changectx as '{rev}:{node|formatnode}', which is the default
425 """Format changectx as '{rev}:{node|formatnode}', which is the default
425 template provided by logcmdutil.changesettemplater"""
426 template provided by logcmdutil.changesettemplater"""
426 repo = ctx.repo()
427 repo = ctx.repo()
427 return formatrevnode(repo.ui, intrev(ctx), binnode(ctx))
428 return formatrevnode(repo.ui, intrev(ctx), binnode(ctx))
428
429
429 def formatrevnode(ui, rev, node):
430 def formatrevnode(ui, rev, node):
430 """Format given revision and node depending on the current verbosity"""
431 """Format given revision and node depending on the current verbosity"""
431 if ui.debugflag:
432 if ui.debugflag:
432 hexfunc = hex
433 hexfunc = hex
433 else:
434 else:
434 hexfunc = short
435 hexfunc = short
435 return '%d:%s' % (rev, hexfunc(node))
436 return '%d:%s' % (rev, hexfunc(node))
436
437
437 def resolvehexnodeidprefix(repo, prefix):
438 def resolvehexnodeidprefix(repo, prefix):
438 if (prefix.startswith('x') and
439 if (prefix.startswith('x') and
439 repo.ui.configbool('experimental', 'revisions.prefixhexnode')):
440 repo.ui.configbool('experimental', 'revisions.prefixhexnode')):
440 prefix = prefix[1:]
441 prefix = prefix[1:]
441 try:
442 try:
442 # Uses unfiltered repo because it's faster when prefix is ambiguous/
443 # Uses unfiltered repo because it's faster when prefix is ambiguous/
443 # This matches the shortesthexnodeidprefix() function below.
444 # This matches the shortesthexnodeidprefix() function below.
444 node = repo.unfiltered().changelog._partialmatch(prefix)
445 node = repo.unfiltered().changelog._partialmatch(prefix)
445 except error.AmbiguousPrefixLookupError:
446 except error.AmbiguousPrefixLookupError:
446 revset = repo.ui.config('experimental', 'revisions.disambiguatewithin')
447 revset = repo.ui.config('experimental', 'revisions.disambiguatewithin')
447 if revset:
448 if revset:
448 # Clear config to avoid infinite recursion
449 # Clear config to avoid infinite recursion
449 configoverrides = {('experimental',
450 configoverrides = {('experimental',
450 'revisions.disambiguatewithin'): None}
451 'revisions.disambiguatewithin'): None}
451 with repo.ui.configoverride(configoverrides):
452 with repo.ui.configoverride(configoverrides):
452 revs = repo.anyrevs([revset], user=True)
453 revs = repo.anyrevs([revset], user=True)
453 matches = []
454 matches = []
454 for rev in revs:
455 for rev in revs:
455 node = repo.changelog.node(rev)
456 node = repo.changelog.node(rev)
456 if hex(node).startswith(prefix):
457 if hex(node).startswith(prefix):
457 matches.append(node)
458 matches.append(node)
458 if len(matches) == 1:
459 if len(matches) == 1:
459 return matches[0]
460 return matches[0]
460 raise
461 raise
461 if node is None:
462 if node is None:
462 return
463 return
463 repo.changelog.rev(node) # make sure node isn't filtered
464 repo.changelog.rev(node) # make sure node isn't filtered
464 return node
465 return node
465
466
466 def mayberevnum(repo, prefix):
467 def mayberevnum(repo, prefix):
467 """Checks if the given prefix may be mistaken for a revision number"""
468 """Checks if the given prefix may be mistaken for a revision number"""
468 try:
469 try:
469 i = int(prefix)
470 i = int(prefix)
470 # if we are a pure int, then starting with zero will not be
471 # if we are a pure int, then starting with zero will not be
471 # confused as a rev; or, obviously, if the int is larger
472 # confused as a rev; or, obviously, if the int is larger
472 # than the value of the tip rev. We still need to disambiguate if
473 # than the value of the tip rev. We still need to disambiguate if
473 # prefix == '0', since that *is* a valid revnum.
474 # prefix == '0', since that *is* a valid revnum.
474 if (prefix != b'0' and prefix[0:1] == b'0') or i >= len(repo):
475 if (prefix != b'0' and prefix[0:1] == b'0') or i >= len(repo):
475 return False
476 return False
476 return True
477 return True
477 except ValueError:
478 except ValueError:
478 return False
479 return False
479
480
480 def shortesthexnodeidprefix(repo, node, minlength=1, cache=None):
481 def shortesthexnodeidprefix(repo, node, minlength=1, cache=None):
481 """Find the shortest unambiguous prefix that matches hexnode.
482 """Find the shortest unambiguous prefix that matches hexnode.
482
483
483 If "cache" is not None, it must be a dictionary that can be used for
484 If "cache" is not None, it must be a dictionary that can be used for
484 caching between calls to this method.
485 caching between calls to this method.
485 """
486 """
486 # _partialmatch() of filtered changelog could take O(len(repo)) time,
487 # _partialmatch() of filtered changelog could take O(len(repo)) time,
487 # which would be unacceptably slow. so we look for hash collision in
488 # which would be unacceptably slow. so we look for hash collision in
488 # unfiltered space, which means some hashes may be slightly longer.
489 # unfiltered space, which means some hashes may be slightly longer.
489
490
490 minlength=max(minlength, 1)
491 minlength=max(minlength, 1)
491
492
492 def disambiguate(prefix):
493 def disambiguate(prefix):
493 """Disambiguate against revnums."""
494 """Disambiguate against revnums."""
494 if repo.ui.configbool('experimental', 'revisions.prefixhexnode'):
495 if repo.ui.configbool('experimental', 'revisions.prefixhexnode'):
495 if mayberevnum(repo, prefix):
496 if mayberevnum(repo, prefix):
496 return 'x' + prefix
497 return 'x' + prefix
497 else:
498 else:
498 return prefix
499 return prefix
499
500
500 hexnode = hex(node)
501 hexnode = hex(node)
501 for length in range(len(prefix), len(hexnode) + 1):
502 for length in range(len(prefix), len(hexnode) + 1):
502 prefix = hexnode[:length]
503 prefix = hexnode[:length]
503 if not mayberevnum(repo, prefix):
504 if not mayberevnum(repo, prefix):
504 return prefix
505 return prefix
505
506
506 cl = repo.unfiltered().changelog
507 cl = repo.unfiltered().changelog
507 revset = repo.ui.config('experimental', 'revisions.disambiguatewithin')
508 revset = repo.ui.config('experimental', 'revisions.disambiguatewithin')
508 if revset:
509 if revset:
509 revs = None
510 revs = None
510 if cache is not None:
511 if cache is not None:
511 revs = cache.get('disambiguationrevset')
512 revs = cache.get('disambiguationrevset')
512 if revs is None:
513 if revs is None:
513 revs = repo.anyrevs([revset], user=True)
514 revs = repo.anyrevs([revset], user=True)
514 if cache is not None:
515 if cache is not None:
515 cache['disambiguationrevset'] = revs
516 cache['disambiguationrevset'] = revs
516 if cl.rev(node) in revs:
517 if cl.rev(node) in revs:
517 hexnode = hex(node)
518 hexnode = hex(node)
518 nodetree = None
519 nodetree = None
519 if cache is not None:
520 if cache is not None:
520 nodetree = cache.get('disambiguationnodetree')
521 nodetree = cache.get('disambiguationnodetree')
521 if not nodetree:
522 if not nodetree:
522 try:
523 try:
523 nodetree = parsers.nodetree(cl.index, len(revs))
524 nodetree = parsers.nodetree(cl.index, len(revs))
524 except AttributeError:
525 except AttributeError:
525 # no native nodetree
526 # no native nodetree
526 pass
527 pass
527 else:
528 else:
528 for r in revs:
529 for r in revs:
529 nodetree.insert(r)
530 nodetree.insert(r)
530 if cache is not None:
531 if cache is not None:
531 cache['disambiguationnodetree'] = nodetree
532 cache['disambiguationnodetree'] = nodetree
532 if nodetree is not None:
533 if nodetree is not None:
533 length = max(nodetree.shortest(node), minlength)
534 length = max(nodetree.shortest(node), minlength)
534 prefix = hexnode[:length]
535 prefix = hexnode[:length]
535 return disambiguate(prefix)
536 return disambiguate(prefix)
536 for length in range(minlength, len(hexnode) + 1):
537 for length in range(minlength, len(hexnode) + 1):
537 matches = []
538 matches = []
538 prefix = hexnode[:length]
539 prefix = hexnode[:length]
539 for rev in revs:
540 for rev in revs:
540 otherhexnode = repo[rev].hex()
541 otherhexnode = repo[rev].hex()
541 if prefix == otherhexnode[:length]:
542 if prefix == otherhexnode[:length]:
542 matches.append(otherhexnode)
543 matches.append(otherhexnode)
543 if len(matches) == 1:
544 if len(matches) == 1:
544 return disambiguate(prefix)
545 return disambiguate(prefix)
545
546
546 try:
547 try:
547 return disambiguate(cl.shortest(node, minlength))
548 return disambiguate(cl.shortest(node, minlength))
548 except error.LookupError:
549 except error.LookupError:
549 raise error.RepoLookupError()
550 raise error.RepoLookupError()
550
551
551 def isrevsymbol(repo, symbol):
552 def isrevsymbol(repo, symbol):
552 """Checks if a symbol exists in the repo.
553 """Checks if a symbol exists in the repo.
553
554
554 See revsymbol() for details. Raises error.AmbiguousPrefixLookupError if the
555 See revsymbol() for details. Raises error.AmbiguousPrefixLookupError if the
555 symbol is an ambiguous nodeid prefix.
556 symbol is an ambiguous nodeid prefix.
556 """
557 """
557 try:
558 try:
558 revsymbol(repo, symbol)
559 revsymbol(repo, symbol)
559 return True
560 return True
560 except error.RepoLookupError:
561 except error.RepoLookupError:
561 return False
562 return False
562
563
563 def revsymbol(repo, symbol):
564 def revsymbol(repo, symbol):
564 """Returns a context given a single revision symbol (as string).
565 """Returns a context given a single revision symbol (as string).
565
566
566 This is similar to revsingle(), but accepts only a single revision symbol,
567 This is similar to revsingle(), but accepts only a single revision symbol,
567 i.e. things like ".", "tip", "1234", "deadbeef", "my-bookmark" work, but
568 i.e. things like ".", "tip", "1234", "deadbeef", "my-bookmark" work, but
568 not "max(public())".
569 not "max(public())".
569 """
570 """
570 if not isinstance(symbol, bytes):
571 if not isinstance(symbol, bytes):
571 msg = ("symbol (%s of type %s) was not a string, did you mean "
572 msg = ("symbol (%s of type %s) was not a string, did you mean "
572 "repo[symbol]?" % (symbol, type(symbol)))
573 "repo[symbol]?" % (symbol, type(symbol)))
573 raise error.ProgrammingError(msg)
574 raise error.ProgrammingError(msg)
574 try:
575 try:
575 if symbol in ('.', 'tip', 'null'):
576 if symbol in ('.', 'tip', 'null'):
576 return repo[symbol]
577 return repo[symbol]
577
578
578 try:
579 try:
579 r = int(symbol)
580 r = int(symbol)
580 if '%d' % r != symbol:
581 if '%d' % r != symbol:
581 raise ValueError
582 raise ValueError
582 l = len(repo.changelog)
583 l = len(repo.changelog)
583 if r < 0:
584 if r < 0:
584 r += l
585 r += l
585 if r < 0 or r >= l and r != wdirrev:
586 if r < 0 or r >= l and r != wdirrev:
586 raise ValueError
587 raise ValueError
587 return repo[r]
588 return repo[r]
588 except error.FilteredIndexError:
589 except error.FilteredIndexError:
589 raise
590 raise
590 except (ValueError, OverflowError, IndexError):
591 except (ValueError, OverflowError, IndexError):
591 pass
592 pass
592
593
593 if len(symbol) == 40:
594 if len(symbol) == 40:
594 try:
595 try:
595 node = bin(symbol)
596 node = bin(symbol)
596 rev = repo.changelog.rev(node)
597 rev = repo.changelog.rev(node)
597 return repo[rev]
598 return repo[rev]
598 except error.FilteredLookupError:
599 except error.FilteredLookupError:
599 raise
600 raise
600 except (TypeError, LookupError):
601 except (TypeError, LookupError):
601 pass
602 pass
602
603
603 # look up bookmarks through the name interface
604 # look up bookmarks through the name interface
604 try:
605 try:
605 node = repo.names.singlenode(repo, symbol)
606 node = repo.names.singlenode(repo, symbol)
606 rev = repo.changelog.rev(node)
607 rev = repo.changelog.rev(node)
607 return repo[rev]
608 return repo[rev]
608 except KeyError:
609 except KeyError:
609 pass
610 pass
610
611
611 node = resolvehexnodeidprefix(repo, symbol)
612 node = resolvehexnodeidprefix(repo, symbol)
612 if node is not None:
613 if node is not None:
613 rev = repo.changelog.rev(node)
614 rev = repo.changelog.rev(node)
614 return repo[rev]
615 return repo[rev]
615
616
616 raise error.RepoLookupError(_("unknown revision '%s'") % symbol)
617 raise error.RepoLookupError(_("unknown revision '%s'") % symbol)
617
618
618 except error.WdirUnsupported:
619 except error.WdirUnsupported:
619 return repo[None]
620 return repo[None]
620 except (error.FilteredIndexError, error.FilteredLookupError,
621 except (error.FilteredIndexError, error.FilteredLookupError,
621 error.FilteredRepoLookupError):
622 error.FilteredRepoLookupError):
622 raise _filterederror(repo, symbol)
623 raise _filterederror(repo, symbol)
623
624
624 def _filterederror(repo, changeid):
625 def _filterederror(repo, changeid):
625 """build an exception to be raised about a filtered changeid
626 """build an exception to be raised about a filtered changeid
626
627
627 This is extracted in a function to help extensions (eg: evolve) to
628 This is extracted in a function to help extensions (eg: evolve) to
628 experiment with various message variants."""
629 experiment with various message variants."""
629 if repo.filtername.startswith('visible'):
630 if repo.filtername.startswith('visible'):
630
631
631 # Check if the changeset is obsolete
632 # Check if the changeset is obsolete
632 unfilteredrepo = repo.unfiltered()
633 unfilteredrepo = repo.unfiltered()
633 ctx = revsymbol(unfilteredrepo, changeid)
634 ctx = revsymbol(unfilteredrepo, changeid)
634
635
635 # If the changeset is obsolete, enrich the message with the reason
636 # If the changeset is obsolete, enrich the message with the reason
636 # that made this changeset not visible
637 # that made this changeset not visible
637 if ctx.obsolete():
638 if ctx.obsolete():
638 msg = obsutil._getfilteredreason(repo, changeid, ctx)
639 msg = obsutil._getfilteredreason(repo, changeid, ctx)
639 else:
640 else:
640 msg = _("hidden revision '%s'") % changeid
641 msg = _("hidden revision '%s'") % changeid
641
642
642 hint = _('use --hidden to access hidden revisions')
643 hint = _('use --hidden to access hidden revisions')
643
644
644 return error.FilteredRepoLookupError(msg, hint=hint)
645 return error.FilteredRepoLookupError(msg, hint=hint)
645 msg = _("filtered revision '%s' (not in '%s' subset)")
646 msg = _("filtered revision '%s' (not in '%s' subset)")
646 msg %= (changeid, repo.filtername)
647 msg %= (changeid, repo.filtername)
647 return error.FilteredRepoLookupError(msg)
648 return error.FilteredRepoLookupError(msg)
648
649
649 def revsingle(repo, revspec, default='.', localalias=None):
650 def revsingle(repo, revspec, default='.', localalias=None):
650 if not revspec and revspec != 0:
651 if not revspec and revspec != 0:
651 return repo[default]
652 return repo[default]
652
653
653 l = revrange(repo, [revspec], localalias=localalias)
654 l = revrange(repo, [revspec], localalias=localalias)
654 if not l:
655 if not l:
655 raise error.Abort(_('empty revision set'))
656 raise error.Abort(_('empty revision set'))
656 return repo[l.last()]
657 return repo[l.last()]
657
658
658 def _pairspec(revspec):
659 def _pairspec(revspec):
659 tree = revsetlang.parse(revspec)
660 tree = revsetlang.parse(revspec)
660 return tree and tree[0] in ('range', 'rangepre', 'rangepost', 'rangeall')
661 return tree and tree[0] in ('range', 'rangepre', 'rangepost', 'rangeall')
661
662
662 def revpair(repo, revs):
663 def revpair(repo, revs):
663 if not revs:
664 if not revs:
664 return repo['.'], repo[None]
665 return repo['.'], repo[None]
665
666
666 l = revrange(repo, revs)
667 l = revrange(repo, revs)
667
668
668 if not l:
669 if not l:
669 raise error.Abort(_('empty revision range'))
670 raise error.Abort(_('empty revision range'))
670
671
671 first = l.first()
672 first = l.first()
672 second = l.last()
673 second = l.last()
673
674
674 if (first == second and len(revs) >= 2
675 if (first == second and len(revs) >= 2
675 and not all(revrange(repo, [r]) for r in revs)):
676 and not all(revrange(repo, [r]) for r in revs)):
676 raise error.Abort(_('empty revision on one side of range'))
677 raise error.Abort(_('empty revision on one side of range'))
677
678
678 # if top-level is range expression, the result must always be a pair
679 # if top-level is range expression, the result must always be a pair
679 if first == second and len(revs) == 1 and not _pairspec(revs[0]):
680 if first == second and len(revs) == 1 and not _pairspec(revs[0]):
680 return repo[first], repo[None]
681 return repo[first], repo[None]
681
682
682 return repo[first], repo[second]
683 return repo[first], repo[second]
683
684
684 def revrange(repo, specs, localalias=None):
685 def revrange(repo, specs, localalias=None):
685 """Execute 1 to many revsets and return the union.
686 """Execute 1 to many revsets and return the union.
686
687
687 This is the preferred mechanism for executing revsets using user-specified
688 This is the preferred mechanism for executing revsets using user-specified
688 config options, such as revset aliases.
689 config options, such as revset aliases.
689
690
690 The revsets specified by ``specs`` will be executed via a chained ``OR``
691 The revsets specified by ``specs`` will be executed via a chained ``OR``
691 expression. If ``specs`` is empty, an empty result is returned.
692 expression. If ``specs`` is empty, an empty result is returned.
692
693
693 ``specs`` can contain integers, in which case they are assumed to be
694 ``specs`` can contain integers, in which case they are assumed to be
694 revision numbers.
695 revision numbers.
695
696
696 It is assumed the revsets are already formatted. If you have arguments
697 It is assumed the revsets are already formatted. If you have arguments
697 that need to be expanded in the revset, call ``revsetlang.formatspec()``
698 that need to be expanded in the revset, call ``revsetlang.formatspec()``
698 and pass the result as an element of ``specs``.
699 and pass the result as an element of ``specs``.
699
700
700 Specifying a single revset is allowed.
701 Specifying a single revset is allowed.
701
702
702 Returns a ``revset.abstractsmartset`` which is a list-like interface over
703 Returns a ``revset.abstractsmartset`` which is a list-like interface over
703 integer revisions.
704 integer revisions.
704 """
705 """
705 allspecs = []
706 allspecs = []
706 for spec in specs:
707 for spec in specs:
707 if isinstance(spec, int):
708 if isinstance(spec, int):
708 spec = revsetlang.formatspec('%d', spec)
709 spec = revsetlang.formatspec('%d', spec)
709 allspecs.append(spec)
710 allspecs.append(spec)
710 return repo.anyrevs(allspecs, user=True, localalias=localalias)
711 return repo.anyrevs(allspecs, user=True, localalias=localalias)
711
712
712 def meaningfulparents(repo, ctx):
713 def meaningfulparents(repo, ctx):
713 """Return list of meaningful (or all if debug) parentrevs for rev.
714 """Return list of meaningful (or all if debug) parentrevs for rev.
714
715
715 For merges (two non-nullrev revisions) both parents are meaningful.
716 For merges (two non-nullrev revisions) both parents are meaningful.
716 Otherwise the first parent revision is considered meaningful if it
717 Otherwise the first parent revision is considered meaningful if it
717 is not the preceding revision.
718 is not the preceding revision.
718 """
719 """
719 parents = ctx.parents()
720 parents = ctx.parents()
720 if len(parents) > 1:
721 if len(parents) > 1:
721 return parents
722 return parents
722 if repo.ui.debugflag:
723 if repo.ui.debugflag:
723 return [parents[0], repo[nullrev]]
724 return [parents[0], repo[nullrev]]
724 if parents[0].rev() >= intrev(ctx) - 1:
725 if parents[0].rev() >= intrev(ctx) - 1:
725 return []
726 return []
726 return parents
727 return parents
727
728
728 def getuipathfn(repo, legacyrelativevalue=False, forcerelativevalue=None):
729 def getuipathfn(repo, legacyrelativevalue=False, forcerelativevalue=None):
729 """Return a function that produced paths for presenting to the user.
730 """Return a function that produced paths for presenting to the user.
730
731
731 The returned function takes a repo-relative path and produces a path
732 The returned function takes a repo-relative path and produces a path
732 that can be presented in the UI.
733 that can be presented in the UI.
733
734
734 Depending on the value of ui.relative-paths, either a repo-relative or
735 Depending on the value of ui.relative-paths, either a repo-relative or
735 cwd-relative path will be produced.
736 cwd-relative path will be produced.
736
737
737 legacyrelativevalue is the value to use if ui.relative-paths=legacy
738 legacyrelativevalue is the value to use if ui.relative-paths=legacy
738
739
739 If forcerelativevalue is not None, then that value will be used regardless
740 If forcerelativevalue is not None, then that value will be used regardless
740 of what ui.relative-paths is set to.
741 of what ui.relative-paths is set to.
741 """
742 """
742 if forcerelativevalue is not None:
743 if forcerelativevalue is not None:
743 relative = forcerelativevalue
744 relative = forcerelativevalue
744 else:
745 else:
745 config = repo.ui.config('ui', 'relative-paths')
746 config = repo.ui.config('ui', 'relative-paths')
746 if config == 'legacy':
747 if config == 'legacy':
747 relative = legacyrelativevalue
748 relative = legacyrelativevalue
748 else:
749 else:
749 relative = stringutil.parsebool(config)
750 relative = stringutil.parsebool(config)
750 if relative is None:
751 if relative is None:
751 raise error.ConfigError(
752 raise error.ConfigError(
752 _("ui.relative-paths is not a boolean ('%s')") % config)
753 _("ui.relative-paths is not a boolean ('%s')") % config)
753
754
754 if relative:
755 if relative:
755 cwd = repo.getcwd()
756 cwd = repo.getcwd()
756 pathto = repo.pathto
757 pathto = repo.pathto
757 return lambda f: pathto(f, cwd)
758 return lambda f: pathto(f, cwd)
758 else:
759 else:
759 return lambda f: f
760 return lambda f: f
760
761
762 def subdiruipathfn(subpath, uipathfn):
763 '''Create a new uipathfn that treats the file as relative to subpath.'''
764 return lambda f: uipathfn(posixpath.join(subpath, f))
765
761 def expandpats(pats):
766 def expandpats(pats):
762 '''Expand bare globs when running on windows.
767 '''Expand bare globs when running on windows.
763 On posix we assume it already has already been done by sh.'''
768 On posix we assume it already has already been done by sh.'''
764 if not util.expandglobs:
769 if not util.expandglobs:
765 return list(pats)
770 return list(pats)
766 ret = []
771 ret = []
767 for kindpat in pats:
772 for kindpat in pats:
768 kind, pat = matchmod._patsplit(kindpat, None)
773 kind, pat = matchmod._patsplit(kindpat, None)
769 if kind is None:
774 if kind is None:
770 try:
775 try:
771 globbed = glob.glob(pat)
776 globbed = glob.glob(pat)
772 except re.error:
777 except re.error:
773 globbed = [pat]
778 globbed = [pat]
774 if globbed:
779 if globbed:
775 ret.extend(globbed)
780 ret.extend(globbed)
776 continue
781 continue
777 ret.append(kindpat)
782 ret.append(kindpat)
778 return ret
783 return ret
779
784
780 def matchandpats(ctx, pats=(), opts=None, globbed=False, default='relpath',
785 def matchandpats(ctx, pats=(), opts=None, globbed=False, default='relpath',
781 badfn=None):
786 badfn=None):
782 '''Return a matcher and the patterns that were used.
787 '''Return a matcher and the patterns that were used.
783 The matcher will warn about bad matches, unless an alternate badfn callback
788 The matcher will warn about bad matches, unless an alternate badfn callback
784 is provided.'''
789 is provided.'''
785 if pats == ("",):
790 if pats == ("",):
786 pats = []
791 pats = []
787 if opts is None:
792 if opts is None:
788 opts = {}
793 opts = {}
789 if not globbed and default == 'relpath':
794 if not globbed and default == 'relpath':
790 pats = expandpats(pats or [])
795 pats = expandpats(pats or [])
791
796
792 def bad(f, msg):
797 def bad(f, msg):
793 ctx.repo().ui.warn("%s: %s\n" % (m.rel(f), msg))
798 ctx.repo().ui.warn("%s: %s\n" % (m.rel(f), msg))
794
799
795 if badfn is None:
800 if badfn is None:
796 badfn = bad
801 badfn = bad
797
802
798 m = ctx.match(pats, opts.get('include'), opts.get('exclude'),
803 m = ctx.match(pats, opts.get('include'), opts.get('exclude'),
799 default, listsubrepos=opts.get('subrepos'), badfn=badfn)
804 default, listsubrepos=opts.get('subrepos'), badfn=badfn)
800
805
801 if m.always():
806 if m.always():
802 pats = []
807 pats = []
803 return m, pats
808 return m, pats
804
809
805 def match(ctx, pats=(), opts=None, globbed=False, default='relpath',
810 def match(ctx, pats=(), opts=None, globbed=False, default='relpath',
806 badfn=None):
811 badfn=None):
807 '''Return a matcher that will warn about bad matches.'''
812 '''Return a matcher that will warn about bad matches.'''
808 return matchandpats(ctx, pats, opts, globbed, default, badfn=badfn)[0]
813 return matchandpats(ctx, pats, opts, globbed, default, badfn=badfn)[0]
809
814
810 def matchall(repo):
815 def matchall(repo):
811 '''Return a matcher that will efficiently match everything.'''
816 '''Return a matcher that will efficiently match everything.'''
812 return matchmod.always(repo.root, repo.getcwd())
817 return matchmod.always(repo.root, repo.getcwd())
813
818
814 def matchfiles(repo, files, badfn=None):
819 def matchfiles(repo, files, badfn=None):
815 '''Return a matcher that will efficiently match exactly these files.'''
820 '''Return a matcher that will efficiently match exactly these files.'''
816 return matchmod.exact(repo.root, repo.getcwd(), files, badfn=badfn)
821 return matchmod.exact(repo.root, repo.getcwd(), files, badfn=badfn)
817
822
818 def parsefollowlinespattern(repo, rev, pat, msg):
823 def parsefollowlinespattern(repo, rev, pat, msg):
819 """Return a file name from `pat` pattern suitable for usage in followlines
824 """Return a file name from `pat` pattern suitable for usage in followlines
820 logic.
825 logic.
821 """
826 """
822 if not matchmod.patkind(pat):
827 if not matchmod.patkind(pat):
823 return pathutil.canonpath(repo.root, repo.getcwd(), pat)
828 return pathutil.canonpath(repo.root, repo.getcwd(), pat)
824 else:
829 else:
825 ctx = repo[rev]
830 ctx = repo[rev]
826 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=ctx)
831 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=ctx)
827 files = [f for f in ctx if m(f)]
832 files = [f for f in ctx if m(f)]
828 if len(files) != 1:
833 if len(files) != 1:
829 raise error.ParseError(msg)
834 raise error.ParseError(msg)
830 return files[0]
835 return files[0]
831
836
832 def getorigvfs(ui, repo):
837 def getorigvfs(ui, repo):
833 """return a vfs suitable to save 'orig' file
838 """return a vfs suitable to save 'orig' file
834
839
835 return None if no special directory is configured"""
840 return None if no special directory is configured"""
836 origbackuppath = ui.config('ui', 'origbackuppath')
841 origbackuppath = ui.config('ui', 'origbackuppath')
837 if not origbackuppath:
842 if not origbackuppath:
838 return None
843 return None
839 return vfs.vfs(repo.wvfs.join(origbackuppath))
844 return vfs.vfs(repo.wvfs.join(origbackuppath))
840
845
841 def backuppath(ui, repo, filepath):
846 def backuppath(ui, repo, filepath):
842 '''customize where working copy backup files (.orig files) are created
847 '''customize where working copy backup files (.orig files) are created
843
848
844 Fetch user defined path from config file: [ui] origbackuppath = <path>
849 Fetch user defined path from config file: [ui] origbackuppath = <path>
845 Fall back to default (filepath with .orig suffix) if not specified
850 Fall back to default (filepath with .orig suffix) if not specified
846
851
847 filepath is repo-relative
852 filepath is repo-relative
848
853
849 Returns an absolute path
854 Returns an absolute path
850 '''
855 '''
851 origvfs = getorigvfs(ui, repo)
856 origvfs = getorigvfs(ui, repo)
852 if origvfs is None:
857 if origvfs is None:
853 return repo.wjoin(filepath + ".orig")
858 return repo.wjoin(filepath + ".orig")
854
859
855 origbackupdir = origvfs.dirname(filepath)
860 origbackupdir = origvfs.dirname(filepath)
856 if not origvfs.isdir(origbackupdir) or origvfs.islink(origbackupdir):
861 if not origvfs.isdir(origbackupdir) or origvfs.islink(origbackupdir):
857 ui.note(_('creating directory: %s\n') % origvfs.join(origbackupdir))
862 ui.note(_('creating directory: %s\n') % origvfs.join(origbackupdir))
858
863
859 # Remove any files that conflict with the backup file's path
864 # Remove any files that conflict with the backup file's path
860 for f in reversed(list(util.finddirs(filepath))):
865 for f in reversed(list(util.finddirs(filepath))):
861 if origvfs.isfileorlink(f):
866 if origvfs.isfileorlink(f):
862 ui.note(_('removing conflicting file: %s\n')
867 ui.note(_('removing conflicting file: %s\n')
863 % origvfs.join(f))
868 % origvfs.join(f))
864 origvfs.unlink(f)
869 origvfs.unlink(f)
865 break
870 break
866
871
867 origvfs.makedirs(origbackupdir)
872 origvfs.makedirs(origbackupdir)
868
873
869 if origvfs.isdir(filepath) and not origvfs.islink(filepath):
874 if origvfs.isdir(filepath) and not origvfs.islink(filepath):
870 ui.note(_('removing conflicting directory: %s\n')
875 ui.note(_('removing conflicting directory: %s\n')
871 % origvfs.join(filepath))
876 % origvfs.join(filepath))
872 origvfs.rmtree(filepath, forcibly=True)
877 origvfs.rmtree(filepath, forcibly=True)
873
878
874 return origvfs.join(filepath)
879 return origvfs.join(filepath)
875
880
876 class _containsnode(object):
881 class _containsnode(object):
877 """proxy __contains__(node) to container.__contains__ which accepts revs"""
882 """proxy __contains__(node) to container.__contains__ which accepts revs"""
878
883
879 def __init__(self, repo, revcontainer):
884 def __init__(self, repo, revcontainer):
880 self._torev = repo.changelog.rev
885 self._torev = repo.changelog.rev
881 self._revcontains = revcontainer.__contains__
886 self._revcontains = revcontainer.__contains__
882
887
883 def __contains__(self, node):
888 def __contains__(self, node):
884 return self._revcontains(self._torev(node))
889 return self._revcontains(self._torev(node))
885
890
886 def cleanupnodes(repo, replacements, operation, moves=None, metadata=None,
891 def cleanupnodes(repo, replacements, operation, moves=None, metadata=None,
887 fixphase=False, targetphase=None, backup=True):
892 fixphase=False, targetphase=None, backup=True):
888 """do common cleanups when old nodes are replaced by new nodes
893 """do common cleanups when old nodes are replaced by new nodes
889
894
890 That includes writing obsmarkers or stripping nodes, and moving bookmarks.
895 That includes writing obsmarkers or stripping nodes, and moving bookmarks.
891 (we might also want to move working directory parent in the future)
896 (we might also want to move working directory parent in the future)
892
897
893 By default, bookmark moves are calculated automatically from 'replacements',
898 By default, bookmark moves are calculated automatically from 'replacements',
894 but 'moves' can be used to override that. Also, 'moves' may include
899 but 'moves' can be used to override that. Also, 'moves' may include
895 additional bookmark moves that should not have associated obsmarkers.
900 additional bookmark moves that should not have associated obsmarkers.
896
901
897 replacements is {oldnode: [newnode]} or a iterable of nodes if they do not
902 replacements is {oldnode: [newnode]} or a iterable of nodes if they do not
898 have replacements. operation is a string, like "rebase".
903 have replacements. operation is a string, like "rebase".
899
904
900 metadata is dictionary containing metadata to be stored in obsmarker if
905 metadata is dictionary containing metadata to be stored in obsmarker if
901 obsolescence is enabled.
906 obsolescence is enabled.
902 """
907 """
903 assert fixphase or targetphase is None
908 assert fixphase or targetphase is None
904 if not replacements and not moves:
909 if not replacements and not moves:
905 return
910 return
906
911
907 # translate mapping's other forms
912 # translate mapping's other forms
908 if not util.safehasattr(replacements, 'items'):
913 if not util.safehasattr(replacements, 'items'):
909 replacements = {(n,): () for n in replacements}
914 replacements = {(n,): () for n in replacements}
910 else:
915 else:
911 # upgrading non tuple "source" to tuple ones for BC
916 # upgrading non tuple "source" to tuple ones for BC
912 repls = {}
917 repls = {}
913 for key, value in replacements.items():
918 for key, value in replacements.items():
914 if not isinstance(key, tuple):
919 if not isinstance(key, tuple):
915 key = (key,)
920 key = (key,)
916 repls[key] = value
921 repls[key] = value
917 replacements = repls
922 replacements = repls
918
923
919 # Unfiltered repo is needed since nodes in replacements might be hidden.
924 # Unfiltered repo is needed since nodes in replacements might be hidden.
920 unfi = repo.unfiltered()
925 unfi = repo.unfiltered()
921
926
922 # Calculate bookmark movements
927 # Calculate bookmark movements
923 if moves is None:
928 if moves is None:
924 moves = {}
929 moves = {}
925 for oldnodes, newnodes in replacements.items():
930 for oldnodes, newnodes in replacements.items():
926 for oldnode in oldnodes:
931 for oldnode in oldnodes:
927 if oldnode in moves:
932 if oldnode in moves:
928 continue
933 continue
929 if len(newnodes) > 1:
934 if len(newnodes) > 1:
930 # usually a split, take the one with biggest rev number
935 # usually a split, take the one with biggest rev number
931 newnode = next(unfi.set('max(%ln)', newnodes)).node()
936 newnode = next(unfi.set('max(%ln)', newnodes)).node()
932 elif len(newnodes) == 0:
937 elif len(newnodes) == 0:
933 # move bookmark backwards
938 # move bookmark backwards
934 allreplaced = []
939 allreplaced = []
935 for rep in replacements:
940 for rep in replacements:
936 allreplaced.extend(rep)
941 allreplaced.extend(rep)
937 roots = list(unfi.set('max((::%n) - %ln)', oldnode,
942 roots = list(unfi.set('max((::%n) - %ln)', oldnode,
938 allreplaced))
943 allreplaced))
939 if roots:
944 if roots:
940 newnode = roots[0].node()
945 newnode = roots[0].node()
941 else:
946 else:
942 newnode = nullid
947 newnode = nullid
943 else:
948 else:
944 newnode = newnodes[0]
949 newnode = newnodes[0]
945 moves[oldnode] = newnode
950 moves[oldnode] = newnode
946
951
947 allnewnodes = [n for ns in replacements.values() for n in ns]
952 allnewnodes = [n for ns in replacements.values() for n in ns]
948 toretract = {}
953 toretract = {}
949 toadvance = {}
954 toadvance = {}
950 if fixphase:
955 if fixphase:
951 precursors = {}
956 precursors = {}
952 for oldnodes, newnodes in replacements.items():
957 for oldnodes, newnodes in replacements.items():
953 for oldnode in oldnodes:
958 for oldnode in oldnodes:
954 for newnode in newnodes:
959 for newnode in newnodes:
955 precursors.setdefault(newnode, []).append(oldnode)
960 precursors.setdefault(newnode, []).append(oldnode)
956
961
957 allnewnodes.sort(key=lambda n: unfi[n].rev())
962 allnewnodes.sort(key=lambda n: unfi[n].rev())
958 newphases = {}
963 newphases = {}
959 def phase(ctx):
964 def phase(ctx):
960 return newphases.get(ctx.node(), ctx.phase())
965 return newphases.get(ctx.node(), ctx.phase())
961 for newnode in allnewnodes:
966 for newnode in allnewnodes:
962 ctx = unfi[newnode]
967 ctx = unfi[newnode]
963 parentphase = max(phase(p) for p in ctx.parents())
968 parentphase = max(phase(p) for p in ctx.parents())
964 if targetphase is None:
969 if targetphase is None:
965 oldphase = max(unfi[oldnode].phase()
970 oldphase = max(unfi[oldnode].phase()
966 for oldnode in precursors[newnode])
971 for oldnode in precursors[newnode])
967 newphase = max(oldphase, parentphase)
972 newphase = max(oldphase, parentphase)
968 else:
973 else:
969 newphase = max(targetphase, parentphase)
974 newphase = max(targetphase, parentphase)
970 newphases[newnode] = newphase
975 newphases[newnode] = newphase
971 if newphase > ctx.phase():
976 if newphase > ctx.phase():
972 toretract.setdefault(newphase, []).append(newnode)
977 toretract.setdefault(newphase, []).append(newnode)
973 elif newphase < ctx.phase():
978 elif newphase < ctx.phase():
974 toadvance.setdefault(newphase, []).append(newnode)
979 toadvance.setdefault(newphase, []).append(newnode)
975
980
976 with repo.transaction('cleanup') as tr:
981 with repo.transaction('cleanup') as tr:
977 # Move bookmarks
982 # Move bookmarks
978 bmarks = repo._bookmarks
983 bmarks = repo._bookmarks
979 bmarkchanges = []
984 bmarkchanges = []
980 for oldnode, newnode in moves.items():
985 for oldnode, newnode in moves.items():
981 oldbmarks = repo.nodebookmarks(oldnode)
986 oldbmarks = repo.nodebookmarks(oldnode)
982 if not oldbmarks:
987 if not oldbmarks:
983 continue
988 continue
984 from . import bookmarks # avoid import cycle
989 from . import bookmarks # avoid import cycle
985 repo.ui.debug('moving bookmarks %r from %s to %s\n' %
990 repo.ui.debug('moving bookmarks %r from %s to %s\n' %
986 (pycompat.rapply(pycompat.maybebytestr, oldbmarks),
991 (pycompat.rapply(pycompat.maybebytestr, oldbmarks),
987 hex(oldnode), hex(newnode)))
992 hex(oldnode), hex(newnode)))
988 # Delete divergent bookmarks being parents of related newnodes
993 # Delete divergent bookmarks being parents of related newnodes
989 deleterevs = repo.revs('parents(roots(%ln & (::%n))) - parents(%n)',
994 deleterevs = repo.revs('parents(roots(%ln & (::%n))) - parents(%n)',
990 allnewnodes, newnode, oldnode)
995 allnewnodes, newnode, oldnode)
991 deletenodes = _containsnode(repo, deleterevs)
996 deletenodes = _containsnode(repo, deleterevs)
992 for name in oldbmarks:
997 for name in oldbmarks:
993 bmarkchanges.append((name, newnode))
998 bmarkchanges.append((name, newnode))
994 for b in bookmarks.divergent2delete(repo, deletenodes, name):
999 for b in bookmarks.divergent2delete(repo, deletenodes, name):
995 bmarkchanges.append((b, None))
1000 bmarkchanges.append((b, None))
996
1001
997 if bmarkchanges:
1002 if bmarkchanges:
998 bmarks.applychanges(repo, tr, bmarkchanges)
1003 bmarks.applychanges(repo, tr, bmarkchanges)
999
1004
1000 for phase, nodes in toretract.items():
1005 for phase, nodes in toretract.items():
1001 phases.retractboundary(repo, tr, phase, nodes)
1006 phases.retractboundary(repo, tr, phase, nodes)
1002 for phase, nodes in toadvance.items():
1007 for phase, nodes in toadvance.items():
1003 phases.advanceboundary(repo, tr, phase, nodes)
1008 phases.advanceboundary(repo, tr, phase, nodes)
1004
1009
1005 # Obsolete or strip nodes
1010 # Obsolete or strip nodes
1006 if obsolete.isenabled(repo, obsolete.createmarkersopt):
1011 if obsolete.isenabled(repo, obsolete.createmarkersopt):
1007 # If a node is already obsoleted, and we want to obsolete it
1012 # If a node is already obsoleted, and we want to obsolete it
1008 # without a successor, skip that obssolete request since it's
1013 # without a successor, skip that obssolete request since it's
1009 # unnecessary. That's the "if s or not isobs(n)" check below.
1014 # unnecessary. That's the "if s or not isobs(n)" check below.
1010 # Also sort the node in topology order, that might be useful for
1015 # Also sort the node in topology order, that might be useful for
1011 # some obsstore logic.
1016 # some obsstore logic.
1012 # NOTE: the sorting might belong to createmarkers.
1017 # NOTE: the sorting might belong to createmarkers.
1013 torev = unfi.changelog.rev
1018 torev = unfi.changelog.rev
1014 sortfunc = lambda ns: torev(ns[0][0])
1019 sortfunc = lambda ns: torev(ns[0][0])
1015 rels = []
1020 rels = []
1016 for ns, s in sorted(replacements.items(), key=sortfunc):
1021 for ns, s in sorted(replacements.items(), key=sortfunc):
1017 rel = (tuple(unfi[n] for n in ns), tuple(unfi[m] for m in s))
1022 rel = (tuple(unfi[n] for n in ns), tuple(unfi[m] for m in s))
1018 rels.append(rel)
1023 rels.append(rel)
1019 if rels:
1024 if rels:
1020 obsolete.createmarkers(repo, rels, operation=operation,
1025 obsolete.createmarkers(repo, rels, operation=operation,
1021 metadata=metadata)
1026 metadata=metadata)
1022 else:
1027 else:
1023 from . import repair # avoid import cycle
1028 from . import repair # avoid import cycle
1024 tostrip = list(n for ns in replacements for n in ns)
1029 tostrip = list(n for ns in replacements for n in ns)
1025 if tostrip:
1030 if tostrip:
1026 repair.delayedstrip(repo.ui, repo, tostrip, operation,
1031 repair.delayedstrip(repo.ui, repo, tostrip, operation,
1027 backup=backup)
1032 backup=backup)
1028
1033
1029 def addremove(repo, matcher, prefix, opts=None):
1034 def addremove(repo, matcher, prefix, opts=None):
1030 if opts is None:
1035 if opts is None:
1031 opts = {}
1036 opts = {}
1032 m = matcher
1037 m = matcher
1033 dry_run = opts.get('dry_run')
1038 dry_run = opts.get('dry_run')
1034 try:
1039 try:
1035 similarity = float(opts.get('similarity') or 0)
1040 similarity = float(opts.get('similarity') or 0)
1036 except ValueError:
1041 except ValueError:
1037 raise error.Abort(_('similarity must be a number'))
1042 raise error.Abort(_('similarity must be a number'))
1038 if similarity < 0 or similarity > 100:
1043 if similarity < 0 or similarity > 100:
1039 raise error.Abort(_('similarity must be between 0 and 100'))
1044 raise error.Abort(_('similarity must be between 0 and 100'))
1040 similarity /= 100.0
1045 similarity /= 100.0
1041
1046
1042 ret = 0
1047 ret = 0
1043
1048
1044 wctx = repo[None]
1049 wctx = repo[None]
1045 for subpath in sorted(wctx.substate):
1050 for subpath in sorted(wctx.substate):
1046 submatch = matchmod.subdirmatcher(subpath, m)
1051 submatch = matchmod.subdirmatcher(subpath, m)
1047 if opts.get('subrepos') or m.exact(subpath) or any(submatch.files()):
1052 if opts.get('subrepos') or m.exact(subpath) or any(submatch.files()):
1048 sub = wctx.sub(subpath)
1053 sub = wctx.sub(subpath)
1049 subprefix = repo.wvfs.reljoin(prefix, subpath)
1054 subprefix = repo.wvfs.reljoin(prefix, subpath)
1050 try:
1055 try:
1051 if sub.addremove(submatch, subprefix, opts):
1056 if sub.addremove(submatch, subprefix, opts):
1052 ret = 1
1057 ret = 1
1053 except error.LookupError:
1058 except error.LookupError:
1054 repo.ui.status(_("skipping missing subrepository: %s\n")
1059 repo.ui.status(_("skipping missing subrepository: %s\n")
1055 % m.uipath(subpath))
1060 % m.uipath(subpath))
1056
1061
1057 rejected = []
1062 rejected = []
1058 def badfn(f, msg):
1063 def badfn(f, msg):
1059 if f in m.files():
1064 if f in m.files():
1060 m.bad(f, msg)
1065 m.bad(f, msg)
1061 rejected.append(f)
1066 rejected.append(f)
1062
1067
1063 badmatch = matchmod.badmatch(m, badfn)
1068 badmatch = matchmod.badmatch(m, badfn)
1064 added, unknown, deleted, removed, forgotten = _interestingfiles(repo,
1069 added, unknown, deleted, removed, forgotten = _interestingfiles(repo,
1065 badmatch)
1070 badmatch)
1066
1071
1067 unknownset = set(unknown + forgotten)
1072 unknownset = set(unknown + forgotten)
1068 toprint = unknownset.copy()
1073 toprint = unknownset.copy()
1069 toprint.update(deleted)
1074 toprint.update(deleted)
1070 for abs in sorted(toprint):
1075 for abs in sorted(toprint):
1071 if repo.ui.verbose or not m.exact(abs):
1076 if repo.ui.verbose or not m.exact(abs):
1072 if abs in unknownset:
1077 if abs in unknownset:
1073 status = _('adding %s\n') % m.uipath(abs)
1078 status = _('adding %s\n') % m.uipath(abs)
1074 label = 'ui.addremove.added'
1079 label = 'ui.addremove.added'
1075 else:
1080 else:
1076 status = _('removing %s\n') % m.uipath(abs)
1081 status = _('removing %s\n') % m.uipath(abs)
1077 label = 'ui.addremove.removed'
1082 label = 'ui.addremove.removed'
1078 repo.ui.status(status, label=label)
1083 repo.ui.status(status, label=label)
1079
1084
1080 renames = _findrenames(repo, m, added + unknown, removed + deleted,
1085 renames = _findrenames(repo, m, added + unknown, removed + deleted,
1081 similarity)
1086 similarity)
1082
1087
1083 if not dry_run:
1088 if not dry_run:
1084 _markchanges(repo, unknown + forgotten, deleted, renames)
1089 _markchanges(repo, unknown + forgotten, deleted, renames)
1085
1090
1086 for f in rejected:
1091 for f in rejected:
1087 if f in m.files():
1092 if f in m.files():
1088 return 1
1093 return 1
1089 return ret
1094 return ret
1090
1095
1091 def marktouched(repo, files, similarity=0.0):
1096 def marktouched(repo, files, similarity=0.0):
1092 '''Assert that files have somehow been operated upon. files are relative to
1097 '''Assert that files have somehow been operated upon. files are relative to
1093 the repo root.'''
1098 the repo root.'''
1094 m = matchfiles(repo, files, badfn=lambda x, y: rejected.append(x))
1099 m = matchfiles(repo, files, badfn=lambda x, y: rejected.append(x))
1095 rejected = []
1100 rejected = []
1096
1101
1097 added, unknown, deleted, removed, forgotten = _interestingfiles(repo, m)
1102 added, unknown, deleted, removed, forgotten = _interestingfiles(repo, m)
1098
1103
1099 if repo.ui.verbose:
1104 if repo.ui.verbose:
1100 unknownset = set(unknown + forgotten)
1105 unknownset = set(unknown + forgotten)
1101 toprint = unknownset.copy()
1106 toprint = unknownset.copy()
1102 toprint.update(deleted)
1107 toprint.update(deleted)
1103 for abs in sorted(toprint):
1108 for abs in sorted(toprint):
1104 if abs in unknownset:
1109 if abs in unknownset:
1105 status = _('adding %s\n') % abs
1110 status = _('adding %s\n') % abs
1106 else:
1111 else:
1107 status = _('removing %s\n') % abs
1112 status = _('removing %s\n') % abs
1108 repo.ui.status(status)
1113 repo.ui.status(status)
1109
1114
1110 renames = _findrenames(repo, m, added + unknown, removed + deleted,
1115 renames = _findrenames(repo, m, added + unknown, removed + deleted,
1111 similarity)
1116 similarity)
1112
1117
1113 _markchanges(repo, unknown + forgotten, deleted, renames)
1118 _markchanges(repo, unknown + forgotten, deleted, renames)
1114
1119
1115 for f in rejected:
1120 for f in rejected:
1116 if f in m.files():
1121 if f in m.files():
1117 return 1
1122 return 1
1118 return 0
1123 return 0
1119
1124
1120 def _interestingfiles(repo, matcher):
1125 def _interestingfiles(repo, matcher):
1121 '''Walk dirstate with matcher, looking for files that addremove would care
1126 '''Walk dirstate with matcher, looking for files that addremove would care
1122 about.
1127 about.
1123
1128
1124 This is different from dirstate.status because it doesn't care about
1129 This is different from dirstate.status because it doesn't care about
1125 whether files are modified or clean.'''
1130 whether files are modified or clean.'''
1126 added, unknown, deleted, removed, forgotten = [], [], [], [], []
1131 added, unknown, deleted, removed, forgotten = [], [], [], [], []
1127 audit_path = pathutil.pathauditor(repo.root, cached=True)
1132 audit_path = pathutil.pathauditor(repo.root, cached=True)
1128
1133
1129 ctx = repo[None]
1134 ctx = repo[None]
1130 dirstate = repo.dirstate
1135 dirstate = repo.dirstate
1131 matcher = repo.narrowmatch(matcher, includeexact=True)
1136 matcher = repo.narrowmatch(matcher, includeexact=True)
1132 walkresults = dirstate.walk(matcher, subrepos=sorted(ctx.substate),
1137 walkresults = dirstate.walk(matcher, subrepos=sorted(ctx.substate),
1133 unknown=True, ignored=False, full=False)
1138 unknown=True, ignored=False, full=False)
1134 for abs, st in walkresults.iteritems():
1139 for abs, st in walkresults.iteritems():
1135 dstate = dirstate[abs]
1140 dstate = dirstate[abs]
1136 if dstate == '?' and audit_path.check(abs):
1141 if dstate == '?' and audit_path.check(abs):
1137 unknown.append(abs)
1142 unknown.append(abs)
1138 elif dstate != 'r' and not st:
1143 elif dstate != 'r' and not st:
1139 deleted.append(abs)
1144 deleted.append(abs)
1140 elif dstate == 'r' and st:
1145 elif dstate == 'r' and st:
1141 forgotten.append(abs)
1146 forgotten.append(abs)
1142 # for finding renames
1147 # for finding renames
1143 elif dstate == 'r' and not st:
1148 elif dstate == 'r' and not st:
1144 removed.append(abs)
1149 removed.append(abs)
1145 elif dstate == 'a':
1150 elif dstate == 'a':
1146 added.append(abs)
1151 added.append(abs)
1147
1152
1148 return added, unknown, deleted, removed, forgotten
1153 return added, unknown, deleted, removed, forgotten
1149
1154
1150 def _findrenames(repo, matcher, added, removed, similarity):
1155 def _findrenames(repo, matcher, added, removed, similarity):
1151 '''Find renames from removed files to added ones.'''
1156 '''Find renames from removed files to added ones.'''
1152 renames = {}
1157 renames = {}
1153 if similarity > 0:
1158 if similarity > 0:
1154 for old, new, score in similar.findrenames(repo, added, removed,
1159 for old, new, score in similar.findrenames(repo, added, removed,
1155 similarity):
1160 similarity):
1156 if (repo.ui.verbose or not matcher.exact(old)
1161 if (repo.ui.verbose or not matcher.exact(old)
1157 or not matcher.exact(new)):
1162 or not matcher.exact(new)):
1158 repo.ui.status(_('recording removal of %s as rename to %s '
1163 repo.ui.status(_('recording removal of %s as rename to %s '
1159 '(%d%% similar)\n') %
1164 '(%d%% similar)\n') %
1160 (matcher.rel(old), matcher.rel(new),
1165 (matcher.rel(old), matcher.rel(new),
1161 score * 100))
1166 score * 100))
1162 renames[new] = old
1167 renames[new] = old
1163 return renames
1168 return renames
1164
1169
1165 def _markchanges(repo, unknown, deleted, renames):
1170 def _markchanges(repo, unknown, deleted, renames):
1166 '''Marks the files in unknown as added, the files in deleted as removed,
1171 '''Marks the files in unknown as added, the files in deleted as removed,
1167 and the files in renames as copied.'''
1172 and the files in renames as copied.'''
1168 wctx = repo[None]
1173 wctx = repo[None]
1169 with repo.wlock():
1174 with repo.wlock():
1170 wctx.forget(deleted)
1175 wctx.forget(deleted)
1171 wctx.add(unknown)
1176 wctx.add(unknown)
1172 for new, old in renames.iteritems():
1177 for new, old in renames.iteritems():
1173 wctx.copy(old, new)
1178 wctx.copy(old, new)
1174
1179
1175 def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None):
1180 def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None):
1176 """Update the dirstate to reflect the intent of copying src to dst. For
1181 """Update the dirstate to reflect the intent of copying src to dst. For
1177 different reasons it might not end with dst being marked as copied from src.
1182 different reasons it might not end with dst being marked as copied from src.
1178 """
1183 """
1179 origsrc = repo.dirstate.copied(src) or src
1184 origsrc = repo.dirstate.copied(src) or src
1180 if dst == origsrc: # copying back a copy?
1185 if dst == origsrc: # copying back a copy?
1181 if repo.dirstate[dst] not in 'mn' and not dryrun:
1186 if repo.dirstate[dst] not in 'mn' and not dryrun:
1182 repo.dirstate.normallookup(dst)
1187 repo.dirstate.normallookup(dst)
1183 else:
1188 else:
1184 if repo.dirstate[origsrc] == 'a' and origsrc == src:
1189 if repo.dirstate[origsrc] == 'a' and origsrc == src:
1185 if not ui.quiet:
1190 if not ui.quiet:
1186 ui.warn(_("%s has not been committed yet, so no copy "
1191 ui.warn(_("%s has not been committed yet, so no copy "
1187 "data will be stored for %s.\n")
1192 "data will be stored for %s.\n")
1188 % (repo.pathto(origsrc, cwd), repo.pathto(dst, cwd)))
1193 % (repo.pathto(origsrc, cwd), repo.pathto(dst, cwd)))
1189 if repo.dirstate[dst] in '?r' and not dryrun:
1194 if repo.dirstate[dst] in '?r' and not dryrun:
1190 wctx.add([dst])
1195 wctx.add([dst])
1191 elif not dryrun:
1196 elif not dryrun:
1192 wctx.copy(origsrc, dst)
1197 wctx.copy(origsrc, dst)
1193
1198
1194 def writerequires(opener, requirements):
1199 def writerequires(opener, requirements):
1195 with opener('requires', 'w', atomictemp=True) as fp:
1200 with opener('requires', 'w', atomictemp=True) as fp:
1196 for r in sorted(requirements):
1201 for r in sorted(requirements):
1197 fp.write("%s\n" % r)
1202 fp.write("%s\n" % r)
1198
1203
1199 class filecachesubentry(object):
1204 class filecachesubentry(object):
1200 def __init__(self, path, stat):
1205 def __init__(self, path, stat):
1201 self.path = path
1206 self.path = path
1202 self.cachestat = None
1207 self.cachestat = None
1203 self._cacheable = None
1208 self._cacheable = None
1204
1209
1205 if stat:
1210 if stat:
1206 self.cachestat = filecachesubentry.stat(self.path)
1211 self.cachestat = filecachesubentry.stat(self.path)
1207
1212
1208 if self.cachestat:
1213 if self.cachestat:
1209 self._cacheable = self.cachestat.cacheable()
1214 self._cacheable = self.cachestat.cacheable()
1210 else:
1215 else:
1211 # None means we don't know yet
1216 # None means we don't know yet
1212 self._cacheable = None
1217 self._cacheable = None
1213
1218
1214 def refresh(self):
1219 def refresh(self):
1215 if self.cacheable():
1220 if self.cacheable():
1216 self.cachestat = filecachesubentry.stat(self.path)
1221 self.cachestat = filecachesubentry.stat(self.path)
1217
1222
1218 def cacheable(self):
1223 def cacheable(self):
1219 if self._cacheable is not None:
1224 if self._cacheable is not None:
1220 return self._cacheable
1225 return self._cacheable
1221
1226
1222 # we don't know yet, assume it is for now
1227 # we don't know yet, assume it is for now
1223 return True
1228 return True
1224
1229
1225 def changed(self):
1230 def changed(self):
1226 # no point in going further if we can't cache it
1231 # no point in going further if we can't cache it
1227 if not self.cacheable():
1232 if not self.cacheable():
1228 return True
1233 return True
1229
1234
1230 newstat = filecachesubentry.stat(self.path)
1235 newstat = filecachesubentry.stat(self.path)
1231
1236
1232 # we may not know if it's cacheable yet, check again now
1237 # we may not know if it's cacheable yet, check again now
1233 if newstat and self._cacheable is None:
1238 if newstat and self._cacheable is None:
1234 self._cacheable = newstat.cacheable()
1239 self._cacheable = newstat.cacheable()
1235
1240
1236 # check again
1241 # check again
1237 if not self._cacheable:
1242 if not self._cacheable:
1238 return True
1243 return True
1239
1244
1240 if self.cachestat != newstat:
1245 if self.cachestat != newstat:
1241 self.cachestat = newstat
1246 self.cachestat = newstat
1242 return True
1247 return True
1243 else:
1248 else:
1244 return False
1249 return False
1245
1250
1246 @staticmethod
1251 @staticmethod
1247 def stat(path):
1252 def stat(path):
1248 try:
1253 try:
1249 return util.cachestat(path)
1254 return util.cachestat(path)
1250 except OSError as e:
1255 except OSError as e:
1251 if e.errno != errno.ENOENT:
1256 if e.errno != errno.ENOENT:
1252 raise
1257 raise
1253
1258
1254 class filecacheentry(object):
1259 class filecacheentry(object):
1255 def __init__(self, paths, stat=True):
1260 def __init__(self, paths, stat=True):
1256 self._entries = []
1261 self._entries = []
1257 for path in paths:
1262 for path in paths:
1258 self._entries.append(filecachesubentry(path, stat))
1263 self._entries.append(filecachesubentry(path, stat))
1259
1264
1260 def changed(self):
1265 def changed(self):
1261 '''true if any entry has changed'''
1266 '''true if any entry has changed'''
1262 for entry in self._entries:
1267 for entry in self._entries:
1263 if entry.changed():
1268 if entry.changed():
1264 return True
1269 return True
1265 return False
1270 return False
1266
1271
1267 def refresh(self):
1272 def refresh(self):
1268 for entry in self._entries:
1273 for entry in self._entries:
1269 entry.refresh()
1274 entry.refresh()
1270
1275
1271 class filecache(object):
1276 class filecache(object):
1272 """A property like decorator that tracks files under .hg/ for updates.
1277 """A property like decorator that tracks files under .hg/ for updates.
1273
1278
1274 On first access, the files defined as arguments are stat()ed and the
1279 On first access, the files defined as arguments are stat()ed and the
1275 results cached. The decorated function is called. The results are stashed
1280 results cached. The decorated function is called. The results are stashed
1276 away in a ``_filecache`` dict on the object whose method is decorated.
1281 away in a ``_filecache`` dict on the object whose method is decorated.
1277
1282
1278 On subsequent access, the cached result is used as it is set to the
1283 On subsequent access, the cached result is used as it is set to the
1279 instance dictionary.
1284 instance dictionary.
1280
1285
1281 On external property set/delete operations, the caller must update the
1286 On external property set/delete operations, the caller must update the
1282 corresponding _filecache entry appropriately. Use __class__.<attr>.set()
1287 corresponding _filecache entry appropriately. Use __class__.<attr>.set()
1283 instead of directly setting <attr>.
1288 instead of directly setting <attr>.
1284
1289
1285 When using the property API, the cached data is always used if available.
1290 When using the property API, the cached data is always used if available.
1286 No stat() is performed to check if the file has changed.
1291 No stat() is performed to check if the file has changed.
1287
1292
1288 Others can muck about with the state of the ``_filecache`` dict. e.g. they
1293 Others can muck about with the state of the ``_filecache`` dict. e.g. they
1289 can populate an entry before the property's getter is called. In this case,
1294 can populate an entry before the property's getter is called. In this case,
1290 entries in ``_filecache`` will be used during property operations,
1295 entries in ``_filecache`` will be used during property operations,
1291 if available. If the underlying file changes, it is up to external callers
1296 if available. If the underlying file changes, it is up to external callers
1292 to reflect this by e.g. calling ``delattr(obj, attr)`` to remove the cached
1297 to reflect this by e.g. calling ``delattr(obj, attr)`` to remove the cached
1293 method result as well as possibly calling ``del obj._filecache[attr]`` to
1298 method result as well as possibly calling ``del obj._filecache[attr]`` to
1294 remove the ``filecacheentry``.
1299 remove the ``filecacheentry``.
1295 """
1300 """
1296
1301
1297 def __init__(self, *paths):
1302 def __init__(self, *paths):
1298 self.paths = paths
1303 self.paths = paths
1299
1304
1300 def join(self, obj, fname):
1305 def join(self, obj, fname):
1301 """Used to compute the runtime path of a cached file.
1306 """Used to compute the runtime path of a cached file.
1302
1307
1303 Users should subclass filecache and provide their own version of this
1308 Users should subclass filecache and provide their own version of this
1304 function to call the appropriate join function on 'obj' (an instance
1309 function to call the appropriate join function on 'obj' (an instance
1305 of the class that its member function was decorated).
1310 of the class that its member function was decorated).
1306 """
1311 """
1307 raise NotImplementedError
1312 raise NotImplementedError
1308
1313
1309 def __call__(self, func):
1314 def __call__(self, func):
1310 self.func = func
1315 self.func = func
1311 self.sname = func.__name__
1316 self.sname = func.__name__
1312 self.name = pycompat.sysbytes(self.sname)
1317 self.name = pycompat.sysbytes(self.sname)
1313 return self
1318 return self
1314
1319
1315 def __get__(self, obj, type=None):
1320 def __get__(self, obj, type=None):
1316 # if accessed on the class, return the descriptor itself.
1321 # if accessed on the class, return the descriptor itself.
1317 if obj is None:
1322 if obj is None:
1318 return self
1323 return self
1319
1324
1320 assert self.sname not in obj.__dict__
1325 assert self.sname not in obj.__dict__
1321
1326
1322 entry = obj._filecache.get(self.name)
1327 entry = obj._filecache.get(self.name)
1323
1328
1324 if entry:
1329 if entry:
1325 if entry.changed():
1330 if entry.changed():
1326 entry.obj = self.func(obj)
1331 entry.obj = self.func(obj)
1327 else:
1332 else:
1328 paths = [self.join(obj, path) for path in self.paths]
1333 paths = [self.join(obj, path) for path in self.paths]
1329
1334
1330 # We stat -before- creating the object so our cache doesn't lie if
1335 # We stat -before- creating the object so our cache doesn't lie if
1331 # a writer modified between the time we read and stat
1336 # a writer modified between the time we read and stat
1332 entry = filecacheentry(paths, True)
1337 entry = filecacheentry(paths, True)
1333 entry.obj = self.func(obj)
1338 entry.obj = self.func(obj)
1334
1339
1335 obj._filecache[self.name] = entry
1340 obj._filecache[self.name] = entry
1336
1341
1337 obj.__dict__[self.sname] = entry.obj
1342 obj.__dict__[self.sname] = entry.obj
1338 return entry.obj
1343 return entry.obj
1339
1344
1340 # don't implement __set__(), which would make __dict__ lookup as slow as
1345 # don't implement __set__(), which would make __dict__ lookup as slow as
1341 # function call.
1346 # function call.
1342
1347
1343 def set(self, obj, value):
1348 def set(self, obj, value):
1344 if self.name not in obj._filecache:
1349 if self.name not in obj._filecache:
1345 # we add an entry for the missing value because X in __dict__
1350 # we add an entry for the missing value because X in __dict__
1346 # implies X in _filecache
1351 # implies X in _filecache
1347 paths = [self.join(obj, path) for path in self.paths]
1352 paths = [self.join(obj, path) for path in self.paths]
1348 ce = filecacheentry(paths, False)
1353 ce = filecacheentry(paths, False)
1349 obj._filecache[self.name] = ce
1354 obj._filecache[self.name] = ce
1350 else:
1355 else:
1351 ce = obj._filecache[self.name]
1356 ce = obj._filecache[self.name]
1352
1357
1353 ce.obj = value # update cached copy
1358 ce.obj = value # update cached copy
1354 obj.__dict__[self.sname] = value # update copy returned by obj.x
1359 obj.__dict__[self.sname] = value # update copy returned by obj.x
1355
1360
1356 def extdatasource(repo, source):
1361 def extdatasource(repo, source):
1357 """Gather a map of rev -> value dict from the specified source
1362 """Gather a map of rev -> value dict from the specified source
1358
1363
1359 A source spec is treated as a URL, with a special case shell: type
1364 A source spec is treated as a URL, with a special case shell: type
1360 for parsing the output from a shell command.
1365 for parsing the output from a shell command.
1361
1366
1362 The data is parsed as a series of newline-separated records where
1367 The data is parsed as a series of newline-separated records where
1363 each record is a revision specifier optionally followed by a space
1368 each record is a revision specifier optionally followed by a space
1364 and a freeform string value. If the revision is known locally, it
1369 and a freeform string value. If the revision is known locally, it
1365 is converted to a rev, otherwise the record is skipped.
1370 is converted to a rev, otherwise the record is skipped.
1366
1371
1367 Note that both key and value are treated as UTF-8 and converted to
1372 Note that both key and value are treated as UTF-8 and converted to
1368 the local encoding. This allows uniformity between local and
1373 the local encoding. This allows uniformity between local and
1369 remote data sources.
1374 remote data sources.
1370 """
1375 """
1371
1376
1372 spec = repo.ui.config("extdata", source)
1377 spec = repo.ui.config("extdata", source)
1373 if not spec:
1378 if not spec:
1374 raise error.Abort(_("unknown extdata source '%s'") % source)
1379 raise error.Abort(_("unknown extdata source '%s'") % source)
1375
1380
1376 data = {}
1381 data = {}
1377 src = proc = None
1382 src = proc = None
1378 try:
1383 try:
1379 if spec.startswith("shell:"):
1384 if spec.startswith("shell:"):
1380 # external commands should be run relative to the repo root
1385 # external commands should be run relative to the repo root
1381 cmd = spec[6:]
1386 cmd = spec[6:]
1382 proc = subprocess.Popen(procutil.tonativestr(cmd),
1387 proc = subprocess.Popen(procutil.tonativestr(cmd),
1383 shell=True, bufsize=-1,
1388 shell=True, bufsize=-1,
1384 close_fds=procutil.closefds,
1389 close_fds=procutil.closefds,
1385 stdout=subprocess.PIPE,
1390 stdout=subprocess.PIPE,
1386 cwd=procutil.tonativestr(repo.root))
1391 cwd=procutil.tonativestr(repo.root))
1387 src = proc.stdout
1392 src = proc.stdout
1388 else:
1393 else:
1389 # treat as a URL or file
1394 # treat as a URL or file
1390 src = url.open(repo.ui, spec)
1395 src = url.open(repo.ui, spec)
1391 for l in src:
1396 for l in src:
1392 if " " in l:
1397 if " " in l:
1393 k, v = l.strip().split(" ", 1)
1398 k, v = l.strip().split(" ", 1)
1394 else:
1399 else:
1395 k, v = l.strip(), ""
1400 k, v = l.strip(), ""
1396
1401
1397 k = encoding.tolocal(k)
1402 k = encoding.tolocal(k)
1398 try:
1403 try:
1399 data[revsingle(repo, k).rev()] = encoding.tolocal(v)
1404 data[revsingle(repo, k).rev()] = encoding.tolocal(v)
1400 except (error.LookupError, error.RepoLookupError):
1405 except (error.LookupError, error.RepoLookupError):
1401 pass # we ignore data for nodes that don't exist locally
1406 pass # we ignore data for nodes that don't exist locally
1402 finally:
1407 finally:
1403 if proc:
1408 if proc:
1404 proc.communicate()
1409 proc.communicate()
1405 if src:
1410 if src:
1406 src.close()
1411 src.close()
1407 if proc and proc.returncode != 0:
1412 if proc and proc.returncode != 0:
1408 raise error.Abort(_("extdata command '%s' failed: %s")
1413 raise error.Abort(_("extdata command '%s' failed: %s")
1409 % (cmd, procutil.explainexit(proc.returncode)))
1414 % (cmd, procutil.explainexit(proc.returncode)))
1410
1415
1411 return data
1416 return data
1412
1417
1413 def _locksub(repo, lock, envvar, cmd, environ=None, *args, **kwargs):
1418 def _locksub(repo, lock, envvar, cmd, environ=None, *args, **kwargs):
1414 if lock is None:
1419 if lock is None:
1415 raise error.LockInheritanceContractViolation(
1420 raise error.LockInheritanceContractViolation(
1416 'lock can only be inherited while held')
1421 'lock can only be inherited while held')
1417 if environ is None:
1422 if environ is None:
1418 environ = {}
1423 environ = {}
1419 with lock.inherit() as locker:
1424 with lock.inherit() as locker:
1420 environ[envvar] = locker
1425 environ[envvar] = locker
1421 return repo.ui.system(cmd, environ=environ, *args, **kwargs)
1426 return repo.ui.system(cmd, environ=environ, *args, **kwargs)
1422
1427
1423 def wlocksub(repo, cmd, *args, **kwargs):
1428 def wlocksub(repo, cmd, *args, **kwargs):
1424 """run cmd as a subprocess that allows inheriting repo's wlock
1429 """run cmd as a subprocess that allows inheriting repo's wlock
1425
1430
1426 This can only be called while the wlock is held. This takes all the
1431 This can only be called while the wlock is held. This takes all the
1427 arguments that ui.system does, and returns the exit code of the
1432 arguments that ui.system does, and returns the exit code of the
1428 subprocess."""
1433 subprocess."""
1429 return _locksub(repo, repo.currentwlock(), 'HG_WLOCK_LOCKER', cmd, *args,
1434 return _locksub(repo, repo.currentwlock(), 'HG_WLOCK_LOCKER', cmd, *args,
1430 **kwargs)
1435 **kwargs)
1431
1436
1432 class progress(object):
1437 class progress(object):
1433 def __init__(self, ui, updatebar, topic, unit="", total=None):
1438 def __init__(self, ui, updatebar, topic, unit="", total=None):
1434 self.ui = ui
1439 self.ui = ui
1435 self.pos = 0
1440 self.pos = 0
1436 self.topic = topic
1441 self.topic = topic
1437 self.unit = unit
1442 self.unit = unit
1438 self.total = total
1443 self.total = total
1439 self.debug = ui.configbool('progress', 'debug')
1444 self.debug = ui.configbool('progress', 'debug')
1440 self._updatebar = updatebar
1445 self._updatebar = updatebar
1441
1446
1442 def __enter__(self):
1447 def __enter__(self):
1443 return self
1448 return self
1444
1449
1445 def __exit__(self, exc_type, exc_value, exc_tb):
1450 def __exit__(self, exc_type, exc_value, exc_tb):
1446 self.complete()
1451 self.complete()
1447
1452
1448 def update(self, pos, item="", total=None):
1453 def update(self, pos, item="", total=None):
1449 assert pos is not None
1454 assert pos is not None
1450 if total:
1455 if total:
1451 self.total = total
1456 self.total = total
1452 self.pos = pos
1457 self.pos = pos
1453 self._updatebar(self.topic, self.pos, item, self.unit, self.total)
1458 self._updatebar(self.topic, self.pos, item, self.unit, self.total)
1454 if self.debug:
1459 if self.debug:
1455 self._printdebug(item)
1460 self._printdebug(item)
1456
1461
1457 def increment(self, step=1, item="", total=None):
1462 def increment(self, step=1, item="", total=None):
1458 self.update(self.pos + step, item, total)
1463 self.update(self.pos + step, item, total)
1459
1464
1460 def complete(self):
1465 def complete(self):
1461 self.pos = None
1466 self.pos = None
1462 self.unit = ""
1467 self.unit = ""
1463 self.total = None
1468 self.total = None
1464 self._updatebar(self.topic, self.pos, "", self.unit, self.total)
1469 self._updatebar(self.topic, self.pos, "", self.unit, self.total)
1465
1470
1466 def _printdebug(self, item):
1471 def _printdebug(self, item):
1467 if self.unit:
1472 if self.unit:
1468 unit = ' ' + self.unit
1473 unit = ' ' + self.unit
1469 if item:
1474 if item:
1470 item = ' ' + item
1475 item = ' ' + item
1471
1476
1472 if self.total:
1477 if self.total:
1473 pct = 100.0 * self.pos / self.total
1478 pct = 100.0 * self.pos / self.total
1474 self.ui.debug('%s:%s %d/%d%s (%4.2f%%)\n'
1479 self.ui.debug('%s:%s %d/%d%s (%4.2f%%)\n'
1475 % (self.topic, item, self.pos, self.total, unit, pct))
1480 % (self.topic, item, self.pos, self.total, unit, pct))
1476 else:
1481 else:
1477 self.ui.debug('%s:%s %d%s\n' % (self.topic, item, self.pos, unit))
1482 self.ui.debug('%s:%s %d%s\n' % (self.topic, item, self.pos, unit))
1478
1483
1479 def gdinitconfig(ui):
1484 def gdinitconfig(ui):
1480 """helper function to know if a repo should be created as general delta
1485 """helper function to know if a repo should be created as general delta
1481 """
1486 """
1482 # experimental config: format.generaldelta
1487 # experimental config: format.generaldelta
1483 return (ui.configbool('format', 'generaldelta')
1488 return (ui.configbool('format', 'generaldelta')
1484 or ui.configbool('format', 'usegeneraldelta'))
1489 or ui.configbool('format', 'usegeneraldelta'))
1485
1490
1486 def gddeltaconfig(ui):
1491 def gddeltaconfig(ui):
1487 """helper function to know if incoming delta should be optimised
1492 """helper function to know if incoming delta should be optimised
1488 """
1493 """
1489 # experimental config: format.generaldelta
1494 # experimental config: format.generaldelta
1490 return ui.configbool('format', 'generaldelta')
1495 return ui.configbool('format', 'generaldelta')
1491
1496
1492 class simplekeyvaluefile(object):
1497 class simplekeyvaluefile(object):
1493 """A simple file with key=value lines
1498 """A simple file with key=value lines
1494
1499
1495 Keys must be alphanumerics and start with a letter, values must not
1500 Keys must be alphanumerics and start with a letter, values must not
1496 contain '\n' characters"""
1501 contain '\n' characters"""
1497 firstlinekey = '__firstline'
1502 firstlinekey = '__firstline'
1498
1503
1499 def __init__(self, vfs, path, keys=None):
1504 def __init__(self, vfs, path, keys=None):
1500 self.vfs = vfs
1505 self.vfs = vfs
1501 self.path = path
1506 self.path = path
1502
1507
1503 def read(self, firstlinenonkeyval=False):
1508 def read(self, firstlinenonkeyval=False):
1504 """Read the contents of a simple key-value file
1509 """Read the contents of a simple key-value file
1505
1510
1506 'firstlinenonkeyval' indicates whether the first line of file should
1511 'firstlinenonkeyval' indicates whether the first line of file should
1507 be treated as a key-value pair or reuturned fully under the
1512 be treated as a key-value pair or reuturned fully under the
1508 __firstline key."""
1513 __firstline key."""
1509 lines = self.vfs.readlines(self.path)
1514 lines = self.vfs.readlines(self.path)
1510 d = {}
1515 d = {}
1511 if firstlinenonkeyval:
1516 if firstlinenonkeyval:
1512 if not lines:
1517 if not lines:
1513 e = _("empty simplekeyvalue file")
1518 e = _("empty simplekeyvalue file")
1514 raise error.CorruptedState(e)
1519 raise error.CorruptedState(e)
1515 # we don't want to include '\n' in the __firstline
1520 # we don't want to include '\n' in the __firstline
1516 d[self.firstlinekey] = lines[0][:-1]
1521 d[self.firstlinekey] = lines[0][:-1]
1517 del lines[0]
1522 del lines[0]
1518
1523
1519 try:
1524 try:
1520 # the 'if line.strip()' part prevents us from failing on empty
1525 # the 'if line.strip()' part prevents us from failing on empty
1521 # lines which only contain '\n' therefore are not skipped
1526 # lines which only contain '\n' therefore are not skipped
1522 # by 'if line'
1527 # by 'if line'
1523 updatedict = dict(line[:-1].split('=', 1) for line in lines
1528 updatedict = dict(line[:-1].split('=', 1) for line in lines
1524 if line.strip())
1529 if line.strip())
1525 if self.firstlinekey in updatedict:
1530 if self.firstlinekey in updatedict:
1526 e = _("%r can't be used as a key")
1531 e = _("%r can't be used as a key")
1527 raise error.CorruptedState(e % self.firstlinekey)
1532 raise error.CorruptedState(e % self.firstlinekey)
1528 d.update(updatedict)
1533 d.update(updatedict)
1529 except ValueError as e:
1534 except ValueError as e:
1530 raise error.CorruptedState(str(e))
1535 raise error.CorruptedState(str(e))
1531 return d
1536 return d
1532
1537
1533 def write(self, data, firstline=None):
1538 def write(self, data, firstline=None):
1534 """Write key=>value mapping to a file
1539 """Write key=>value mapping to a file
1535 data is a dict. Keys must be alphanumerical and start with a letter.
1540 data is a dict. Keys must be alphanumerical and start with a letter.
1536 Values must not contain newline characters.
1541 Values must not contain newline characters.
1537
1542
1538 If 'firstline' is not None, it is written to file before
1543 If 'firstline' is not None, it is written to file before
1539 everything else, as it is, not in a key=value form"""
1544 everything else, as it is, not in a key=value form"""
1540 lines = []
1545 lines = []
1541 if firstline is not None:
1546 if firstline is not None:
1542 lines.append('%s\n' % firstline)
1547 lines.append('%s\n' % firstline)
1543
1548
1544 for k, v in data.items():
1549 for k, v in data.items():
1545 if k == self.firstlinekey:
1550 if k == self.firstlinekey:
1546 e = "key name '%s' is reserved" % self.firstlinekey
1551 e = "key name '%s' is reserved" % self.firstlinekey
1547 raise error.ProgrammingError(e)
1552 raise error.ProgrammingError(e)
1548 if not k[0:1].isalpha():
1553 if not k[0:1].isalpha():
1549 e = "keys must start with a letter in a key-value file"
1554 e = "keys must start with a letter in a key-value file"
1550 raise error.ProgrammingError(e)
1555 raise error.ProgrammingError(e)
1551 if not k.isalnum():
1556 if not k.isalnum():
1552 e = "invalid key name in a simple key-value file"
1557 e = "invalid key name in a simple key-value file"
1553 raise error.ProgrammingError(e)
1558 raise error.ProgrammingError(e)
1554 if '\n' in v:
1559 if '\n' in v:
1555 e = "invalid value in a simple key-value file"
1560 e = "invalid value in a simple key-value file"
1556 raise error.ProgrammingError(e)
1561 raise error.ProgrammingError(e)
1557 lines.append("%s=%s\n" % (k, v))
1562 lines.append("%s=%s\n" % (k, v))
1558 with self.vfs(self.path, mode='wb', atomictemp=True) as fp:
1563 with self.vfs(self.path, mode='wb', atomictemp=True) as fp:
1559 fp.write(''.join(lines))
1564 fp.write(''.join(lines))
1560
1565
1561 _reportobsoletedsource = [
1566 _reportobsoletedsource = [
1562 'debugobsolete',
1567 'debugobsolete',
1563 'pull',
1568 'pull',
1564 'push',
1569 'push',
1565 'serve',
1570 'serve',
1566 'unbundle',
1571 'unbundle',
1567 ]
1572 ]
1568
1573
1569 _reportnewcssource = [
1574 _reportnewcssource = [
1570 'pull',
1575 'pull',
1571 'unbundle',
1576 'unbundle',
1572 ]
1577 ]
1573
1578
1574 def prefetchfiles(repo, revs, match):
1579 def prefetchfiles(repo, revs, match):
1575 """Invokes the registered file prefetch functions, allowing extensions to
1580 """Invokes the registered file prefetch functions, allowing extensions to
1576 ensure the corresponding files are available locally, before the command
1581 ensure the corresponding files are available locally, before the command
1577 uses them."""
1582 uses them."""
1578 if match:
1583 if match:
1579 # The command itself will complain about files that don't exist, so
1584 # The command itself will complain about files that don't exist, so
1580 # don't duplicate the message.
1585 # don't duplicate the message.
1581 match = matchmod.badmatch(match, lambda fn, msg: None)
1586 match = matchmod.badmatch(match, lambda fn, msg: None)
1582 else:
1587 else:
1583 match = matchall(repo)
1588 match = matchall(repo)
1584
1589
1585 fileprefetchhooks(repo, revs, match)
1590 fileprefetchhooks(repo, revs, match)
1586
1591
1587 # a list of (repo, revs, match) prefetch functions
1592 # a list of (repo, revs, match) prefetch functions
1588 fileprefetchhooks = util.hooks()
1593 fileprefetchhooks = util.hooks()
1589
1594
1590 # A marker that tells the evolve extension to suppress its own reporting
1595 # A marker that tells the evolve extension to suppress its own reporting
1591 _reportstroubledchangesets = True
1596 _reportstroubledchangesets = True
1592
1597
1593 def registersummarycallback(repo, otr, txnname=''):
1598 def registersummarycallback(repo, otr, txnname=''):
1594 """register a callback to issue a summary after the transaction is closed
1599 """register a callback to issue a summary after the transaction is closed
1595 """
1600 """
1596 def txmatch(sources):
1601 def txmatch(sources):
1597 return any(txnname.startswith(source) for source in sources)
1602 return any(txnname.startswith(source) for source in sources)
1598
1603
1599 categories = []
1604 categories = []
1600
1605
1601 def reportsummary(func):
1606 def reportsummary(func):
1602 """decorator for report callbacks."""
1607 """decorator for report callbacks."""
1603 # The repoview life cycle is shorter than the one of the actual
1608 # The repoview life cycle is shorter than the one of the actual
1604 # underlying repository. So the filtered object can die before the
1609 # underlying repository. So the filtered object can die before the
1605 # weakref is used leading to troubles. We keep a reference to the
1610 # weakref is used leading to troubles. We keep a reference to the
1606 # unfiltered object and restore the filtering when retrieving the
1611 # unfiltered object and restore the filtering when retrieving the
1607 # repository through the weakref.
1612 # repository through the weakref.
1608 filtername = repo.filtername
1613 filtername = repo.filtername
1609 reporef = weakref.ref(repo.unfiltered())
1614 reporef = weakref.ref(repo.unfiltered())
1610 def wrapped(tr):
1615 def wrapped(tr):
1611 repo = reporef()
1616 repo = reporef()
1612 if filtername:
1617 if filtername:
1613 repo = repo.filtered(filtername)
1618 repo = repo.filtered(filtername)
1614 func(repo, tr)
1619 func(repo, tr)
1615 newcat = '%02i-txnreport' % len(categories)
1620 newcat = '%02i-txnreport' % len(categories)
1616 otr.addpostclose(newcat, wrapped)
1621 otr.addpostclose(newcat, wrapped)
1617 categories.append(newcat)
1622 categories.append(newcat)
1618 return wrapped
1623 return wrapped
1619
1624
1620 if txmatch(_reportobsoletedsource):
1625 if txmatch(_reportobsoletedsource):
1621 @reportsummary
1626 @reportsummary
1622 def reportobsoleted(repo, tr):
1627 def reportobsoleted(repo, tr):
1623 obsoleted = obsutil.getobsoleted(repo, tr)
1628 obsoleted = obsutil.getobsoleted(repo, tr)
1624 if obsoleted:
1629 if obsoleted:
1625 repo.ui.status(_('obsoleted %i changesets\n')
1630 repo.ui.status(_('obsoleted %i changesets\n')
1626 % len(obsoleted))
1631 % len(obsoleted))
1627
1632
1628 if (obsolete.isenabled(repo, obsolete.createmarkersopt) and
1633 if (obsolete.isenabled(repo, obsolete.createmarkersopt) and
1629 repo.ui.configbool('experimental', 'evolution.report-instabilities')):
1634 repo.ui.configbool('experimental', 'evolution.report-instabilities')):
1630 instabilitytypes = [
1635 instabilitytypes = [
1631 ('orphan', 'orphan'),
1636 ('orphan', 'orphan'),
1632 ('phase-divergent', 'phasedivergent'),
1637 ('phase-divergent', 'phasedivergent'),
1633 ('content-divergent', 'contentdivergent'),
1638 ('content-divergent', 'contentdivergent'),
1634 ]
1639 ]
1635
1640
1636 def getinstabilitycounts(repo):
1641 def getinstabilitycounts(repo):
1637 filtered = repo.changelog.filteredrevs
1642 filtered = repo.changelog.filteredrevs
1638 counts = {}
1643 counts = {}
1639 for instability, revset in instabilitytypes:
1644 for instability, revset in instabilitytypes:
1640 counts[instability] = len(set(obsolete.getrevs(repo, revset)) -
1645 counts[instability] = len(set(obsolete.getrevs(repo, revset)) -
1641 filtered)
1646 filtered)
1642 return counts
1647 return counts
1643
1648
1644 oldinstabilitycounts = getinstabilitycounts(repo)
1649 oldinstabilitycounts = getinstabilitycounts(repo)
1645 @reportsummary
1650 @reportsummary
1646 def reportnewinstabilities(repo, tr):
1651 def reportnewinstabilities(repo, tr):
1647 newinstabilitycounts = getinstabilitycounts(repo)
1652 newinstabilitycounts = getinstabilitycounts(repo)
1648 for instability, revset in instabilitytypes:
1653 for instability, revset in instabilitytypes:
1649 delta = (newinstabilitycounts[instability] -
1654 delta = (newinstabilitycounts[instability] -
1650 oldinstabilitycounts[instability])
1655 oldinstabilitycounts[instability])
1651 msg = getinstabilitymessage(delta, instability)
1656 msg = getinstabilitymessage(delta, instability)
1652 if msg:
1657 if msg:
1653 repo.ui.warn(msg)
1658 repo.ui.warn(msg)
1654
1659
1655 if txmatch(_reportnewcssource):
1660 if txmatch(_reportnewcssource):
1656 @reportsummary
1661 @reportsummary
1657 def reportnewcs(repo, tr):
1662 def reportnewcs(repo, tr):
1658 """Report the range of new revisions pulled/unbundled."""
1663 """Report the range of new revisions pulled/unbundled."""
1659 origrepolen = tr.changes.get('origrepolen', len(repo))
1664 origrepolen = tr.changes.get('origrepolen', len(repo))
1660 unfi = repo.unfiltered()
1665 unfi = repo.unfiltered()
1661 if origrepolen >= len(unfi):
1666 if origrepolen >= len(unfi):
1662 return
1667 return
1663
1668
1664 # Compute the bounds of new visible revisions' range.
1669 # Compute the bounds of new visible revisions' range.
1665 revs = smartset.spanset(repo, start=origrepolen)
1670 revs = smartset.spanset(repo, start=origrepolen)
1666 if revs:
1671 if revs:
1667 minrev, maxrev = repo[revs.min()], repo[revs.max()]
1672 minrev, maxrev = repo[revs.min()], repo[revs.max()]
1668
1673
1669 if minrev == maxrev:
1674 if minrev == maxrev:
1670 revrange = minrev
1675 revrange = minrev
1671 else:
1676 else:
1672 revrange = '%s:%s' % (minrev, maxrev)
1677 revrange = '%s:%s' % (minrev, maxrev)
1673 draft = len(repo.revs('%ld and draft()', revs))
1678 draft = len(repo.revs('%ld and draft()', revs))
1674 secret = len(repo.revs('%ld and secret()', revs))
1679 secret = len(repo.revs('%ld and secret()', revs))
1675 if not (draft or secret):
1680 if not (draft or secret):
1676 msg = _('new changesets %s\n') % revrange
1681 msg = _('new changesets %s\n') % revrange
1677 elif draft and secret:
1682 elif draft and secret:
1678 msg = _('new changesets %s (%d drafts, %d secrets)\n')
1683 msg = _('new changesets %s (%d drafts, %d secrets)\n')
1679 msg %= (revrange, draft, secret)
1684 msg %= (revrange, draft, secret)
1680 elif draft:
1685 elif draft:
1681 msg = _('new changesets %s (%d drafts)\n')
1686 msg = _('new changesets %s (%d drafts)\n')
1682 msg %= (revrange, draft)
1687 msg %= (revrange, draft)
1683 elif secret:
1688 elif secret:
1684 msg = _('new changesets %s (%d secrets)\n')
1689 msg = _('new changesets %s (%d secrets)\n')
1685 msg %= (revrange, secret)
1690 msg %= (revrange, secret)
1686 else:
1691 else:
1687 errormsg = 'entered unreachable condition'
1692 errormsg = 'entered unreachable condition'
1688 raise error.ProgrammingError(errormsg)
1693 raise error.ProgrammingError(errormsg)
1689 repo.ui.status(msg)
1694 repo.ui.status(msg)
1690
1695
1691 # search new changesets directly pulled as obsolete
1696 # search new changesets directly pulled as obsolete
1692 duplicates = tr.changes.get('revduplicates', ())
1697 duplicates = tr.changes.get('revduplicates', ())
1693 obsadded = unfi.revs('(%d: + %ld) and obsolete()',
1698 obsadded = unfi.revs('(%d: + %ld) and obsolete()',
1694 origrepolen, duplicates)
1699 origrepolen, duplicates)
1695 cl = repo.changelog
1700 cl = repo.changelog
1696 extinctadded = [r for r in obsadded if r not in cl]
1701 extinctadded = [r for r in obsadded if r not in cl]
1697 if extinctadded:
1702 if extinctadded:
1698 # They are not just obsolete, but obsolete and invisible
1703 # They are not just obsolete, but obsolete and invisible
1699 # we call them "extinct" internally but the terms have not been
1704 # we call them "extinct" internally but the terms have not been
1700 # exposed to users.
1705 # exposed to users.
1701 msg = '(%d other changesets obsolete on arrival)\n'
1706 msg = '(%d other changesets obsolete on arrival)\n'
1702 repo.ui.status(msg % len(extinctadded))
1707 repo.ui.status(msg % len(extinctadded))
1703
1708
1704 @reportsummary
1709 @reportsummary
1705 def reportphasechanges(repo, tr):
1710 def reportphasechanges(repo, tr):
1706 """Report statistics of phase changes for changesets pre-existing
1711 """Report statistics of phase changes for changesets pre-existing
1707 pull/unbundle.
1712 pull/unbundle.
1708 """
1713 """
1709 origrepolen = tr.changes.get('origrepolen', len(repo))
1714 origrepolen = tr.changes.get('origrepolen', len(repo))
1710 phasetracking = tr.changes.get('phases', {})
1715 phasetracking = tr.changes.get('phases', {})
1711 if not phasetracking:
1716 if not phasetracking:
1712 return
1717 return
1713 published = [
1718 published = [
1714 rev for rev, (old, new) in phasetracking.iteritems()
1719 rev for rev, (old, new) in phasetracking.iteritems()
1715 if new == phases.public and rev < origrepolen
1720 if new == phases.public and rev < origrepolen
1716 ]
1721 ]
1717 if not published:
1722 if not published:
1718 return
1723 return
1719 repo.ui.status(_('%d local changesets published\n')
1724 repo.ui.status(_('%d local changesets published\n')
1720 % len(published))
1725 % len(published))
1721
1726
1722 def getinstabilitymessage(delta, instability):
1727 def getinstabilitymessage(delta, instability):
1723 """function to return the message to show warning about new instabilities
1728 """function to return the message to show warning about new instabilities
1724
1729
1725 exists as a separate function so that extension can wrap to show more
1730 exists as a separate function so that extension can wrap to show more
1726 information like how to fix instabilities"""
1731 information like how to fix instabilities"""
1727 if delta > 0:
1732 if delta > 0:
1728 return _('%i new %s changesets\n') % (delta, instability)
1733 return _('%i new %s changesets\n') % (delta, instability)
1729
1734
1730 def nodesummaries(repo, nodes, maxnumnodes=4):
1735 def nodesummaries(repo, nodes, maxnumnodes=4):
1731 if len(nodes) <= maxnumnodes or repo.ui.verbose:
1736 if len(nodes) <= maxnumnodes or repo.ui.verbose:
1732 return ' '.join(short(h) for h in nodes)
1737 return ' '.join(short(h) for h in nodes)
1733 first = ' '.join(short(h) for h in nodes[:maxnumnodes])
1738 first = ' '.join(short(h) for h in nodes[:maxnumnodes])
1734 return _("%s and %d others") % (first, len(nodes) - maxnumnodes)
1739 return _("%s and %d others") % (first, len(nodes) - maxnumnodes)
1735
1740
1736 def enforcesinglehead(repo, tr, desc):
1741 def enforcesinglehead(repo, tr, desc):
1737 """check that no named branch has multiple heads"""
1742 """check that no named branch has multiple heads"""
1738 if desc in ('strip', 'repair'):
1743 if desc in ('strip', 'repair'):
1739 # skip the logic during strip
1744 # skip the logic during strip
1740 return
1745 return
1741 visible = repo.filtered('visible')
1746 visible = repo.filtered('visible')
1742 # possible improvement: we could restrict the check to affected branch
1747 # possible improvement: we could restrict the check to affected branch
1743 for name, heads in visible.branchmap().iteritems():
1748 for name, heads in visible.branchmap().iteritems():
1744 if len(heads) > 1:
1749 if len(heads) > 1:
1745 msg = _('rejecting multiple heads on branch "%s"')
1750 msg = _('rejecting multiple heads on branch "%s"')
1746 msg %= name
1751 msg %= name
1747 hint = _('%d heads: %s')
1752 hint = _('%d heads: %s')
1748 hint %= (len(heads), nodesummaries(repo, heads))
1753 hint %= (len(heads), nodesummaries(repo, heads))
1749 raise error.Abort(msg, hint=hint)
1754 raise error.Abort(msg, hint=hint)
1750
1755
1751 def wrapconvertsink(sink):
1756 def wrapconvertsink(sink):
1752 """Allow extensions to wrap the sink returned by convcmd.convertsink()
1757 """Allow extensions to wrap the sink returned by convcmd.convertsink()
1753 before it is used, whether or not the convert extension was formally loaded.
1758 before it is used, whether or not the convert extension was formally loaded.
1754 """
1759 """
1755 return sink
1760 return sink
1756
1761
1757 def unhidehashlikerevs(repo, specs, hiddentype):
1762 def unhidehashlikerevs(repo, specs, hiddentype):
1758 """parse the user specs and unhide changesets whose hash or revision number
1763 """parse the user specs and unhide changesets whose hash or revision number
1759 is passed.
1764 is passed.
1760
1765
1761 hiddentype can be: 1) 'warn': warn while unhiding changesets
1766 hiddentype can be: 1) 'warn': warn while unhiding changesets
1762 2) 'nowarn': don't warn while unhiding changesets
1767 2) 'nowarn': don't warn while unhiding changesets
1763
1768
1764 returns a repo object with the required changesets unhidden
1769 returns a repo object with the required changesets unhidden
1765 """
1770 """
1766 if not repo.filtername or not repo.ui.configbool('experimental',
1771 if not repo.filtername or not repo.ui.configbool('experimental',
1767 'directaccess'):
1772 'directaccess'):
1768 return repo
1773 return repo
1769
1774
1770 if repo.filtername not in ('visible', 'visible-hidden'):
1775 if repo.filtername not in ('visible', 'visible-hidden'):
1771 return repo
1776 return repo
1772
1777
1773 symbols = set()
1778 symbols = set()
1774 for spec in specs:
1779 for spec in specs:
1775 try:
1780 try:
1776 tree = revsetlang.parse(spec)
1781 tree = revsetlang.parse(spec)
1777 except error.ParseError: # will be reported by scmutil.revrange()
1782 except error.ParseError: # will be reported by scmutil.revrange()
1778 continue
1783 continue
1779
1784
1780 symbols.update(revsetlang.gethashlikesymbols(tree))
1785 symbols.update(revsetlang.gethashlikesymbols(tree))
1781
1786
1782 if not symbols:
1787 if not symbols:
1783 return repo
1788 return repo
1784
1789
1785 revs = _getrevsfromsymbols(repo, symbols)
1790 revs = _getrevsfromsymbols(repo, symbols)
1786
1791
1787 if not revs:
1792 if not revs:
1788 return repo
1793 return repo
1789
1794
1790 if hiddentype == 'warn':
1795 if hiddentype == 'warn':
1791 unfi = repo.unfiltered()
1796 unfi = repo.unfiltered()
1792 revstr = ", ".join([pycompat.bytestr(unfi[l]) for l in revs])
1797 revstr = ", ".join([pycompat.bytestr(unfi[l]) for l in revs])
1793 repo.ui.warn(_("warning: accessing hidden changesets for write "
1798 repo.ui.warn(_("warning: accessing hidden changesets for write "
1794 "operation: %s\n") % revstr)
1799 "operation: %s\n") % revstr)
1795
1800
1796 # we have to use new filtername to separate branch/tags cache until we can
1801 # we have to use new filtername to separate branch/tags cache until we can
1797 # disbale these cache when revisions are dynamically pinned.
1802 # disbale these cache when revisions are dynamically pinned.
1798 return repo.filtered('visible-hidden', revs)
1803 return repo.filtered('visible-hidden', revs)
1799
1804
1800 def _getrevsfromsymbols(repo, symbols):
1805 def _getrevsfromsymbols(repo, symbols):
1801 """parse the list of symbols and returns a set of revision numbers of hidden
1806 """parse the list of symbols and returns a set of revision numbers of hidden
1802 changesets present in symbols"""
1807 changesets present in symbols"""
1803 revs = set()
1808 revs = set()
1804 unfi = repo.unfiltered()
1809 unfi = repo.unfiltered()
1805 unficl = unfi.changelog
1810 unficl = unfi.changelog
1806 cl = repo.changelog
1811 cl = repo.changelog
1807 tiprev = len(unficl)
1812 tiprev = len(unficl)
1808 allowrevnums = repo.ui.configbool('experimental', 'directaccess.revnums')
1813 allowrevnums = repo.ui.configbool('experimental', 'directaccess.revnums')
1809 for s in symbols:
1814 for s in symbols:
1810 try:
1815 try:
1811 n = int(s)
1816 n = int(s)
1812 if n <= tiprev:
1817 if n <= tiprev:
1813 if not allowrevnums:
1818 if not allowrevnums:
1814 continue
1819 continue
1815 else:
1820 else:
1816 if n not in cl:
1821 if n not in cl:
1817 revs.add(n)
1822 revs.add(n)
1818 continue
1823 continue
1819 except ValueError:
1824 except ValueError:
1820 pass
1825 pass
1821
1826
1822 try:
1827 try:
1823 s = resolvehexnodeidprefix(unfi, s)
1828 s = resolvehexnodeidprefix(unfi, s)
1824 except (error.LookupError, error.WdirUnsupported):
1829 except (error.LookupError, error.WdirUnsupported):
1825 s = None
1830 s = None
1826
1831
1827 if s is not None:
1832 if s is not None:
1828 rev = unficl.rev(s)
1833 rev = unficl.rev(s)
1829 if rev not in cl:
1834 if rev not in cl:
1830 revs.add(rev)
1835 revs.add(rev)
1831
1836
1832 return revs
1837 return revs
1833
1838
1834 def bookmarkrevs(repo, mark):
1839 def bookmarkrevs(repo, mark):
1835 """
1840 """
1836 Select revisions reachable by a given bookmark
1841 Select revisions reachable by a given bookmark
1837 """
1842 """
1838 return repo.revs("ancestors(bookmark(%s)) - "
1843 return repo.revs("ancestors(bookmark(%s)) - "
1839 "ancestors(head() and not bookmark(%s)) - "
1844 "ancestors(head() and not bookmark(%s)) - "
1840 "ancestors(bookmark() and not bookmark(%s))",
1845 "ancestors(bookmark() and not bookmark(%s))",
1841 mark, mark, mark)
1846 mark, mark, mark)
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
General Comments 0
You need to be logged in to leave comments. Login now