##// END OF EJS Templates
messages: replace some instances of "folder" by "directory"...
Martin von Zweigbergk -
r42269:b63b8b7c default
parent child Browse files
Show More
@@ -1,2551 +1,2551 b''
1 # context.py - changeset and file context objects for mercurial
1 # context.py - changeset and file context objects for mercurial
2 #
2 #
3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2006, 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 filecmp
11 import filecmp
12 import os
12 import os
13 import stat
13 import stat
14
14
15 from .i18n import _
15 from .i18n import _
16 from .node import (
16 from .node import (
17 addednodeid,
17 addednodeid,
18 hex,
18 hex,
19 modifiednodeid,
19 modifiednodeid,
20 nullid,
20 nullid,
21 nullrev,
21 nullrev,
22 short,
22 short,
23 wdirfilenodeids,
23 wdirfilenodeids,
24 wdirhex,
24 wdirhex,
25 )
25 )
26 from . import (
26 from . import (
27 dagop,
27 dagop,
28 encoding,
28 encoding,
29 error,
29 error,
30 fileset,
30 fileset,
31 match as matchmod,
31 match as matchmod,
32 obsolete as obsmod,
32 obsolete as obsmod,
33 patch,
33 patch,
34 pathutil,
34 pathutil,
35 phases,
35 phases,
36 pycompat,
36 pycompat,
37 repoview,
37 repoview,
38 scmutil,
38 scmutil,
39 sparse,
39 sparse,
40 subrepo,
40 subrepo,
41 subrepoutil,
41 subrepoutil,
42 util,
42 util,
43 )
43 )
44 from .utils import (
44 from .utils import (
45 dateutil,
45 dateutil,
46 stringutil,
46 stringutil,
47 )
47 )
48
48
49 propertycache = util.propertycache
49 propertycache = util.propertycache
50
50
51 class basectx(object):
51 class basectx(object):
52 """A basectx object represents the common logic for its children:
52 """A basectx object represents the common logic for its children:
53 changectx: read-only context that is already present in the repo,
53 changectx: read-only context that is already present in the repo,
54 workingctx: a context that represents the working directory and can
54 workingctx: a context that represents the working directory and can
55 be committed,
55 be committed,
56 memctx: a context that represents changes in-memory and can also
56 memctx: a context that represents changes in-memory and can also
57 be committed."""
57 be committed."""
58
58
59 def __init__(self, repo):
59 def __init__(self, repo):
60 self._repo = repo
60 self._repo = repo
61
61
62 def __bytes__(self):
62 def __bytes__(self):
63 return short(self.node())
63 return short(self.node())
64
64
65 __str__ = encoding.strmethod(__bytes__)
65 __str__ = encoding.strmethod(__bytes__)
66
66
67 def __repr__(self):
67 def __repr__(self):
68 return r"<%s %s>" % (type(self).__name__, str(self))
68 return r"<%s %s>" % (type(self).__name__, str(self))
69
69
70 def __eq__(self, other):
70 def __eq__(self, other):
71 try:
71 try:
72 return type(self) == type(other) and self._rev == other._rev
72 return type(self) == type(other) and self._rev == other._rev
73 except AttributeError:
73 except AttributeError:
74 return False
74 return False
75
75
76 def __ne__(self, other):
76 def __ne__(self, other):
77 return not (self == other)
77 return not (self == other)
78
78
79 def __contains__(self, key):
79 def __contains__(self, key):
80 return key in self._manifest
80 return key in self._manifest
81
81
82 def __getitem__(self, key):
82 def __getitem__(self, key):
83 return self.filectx(key)
83 return self.filectx(key)
84
84
85 def __iter__(self):
85 def __iter__(self):
86 return iter(self._manifest)
86 return iter(self._manifest)
87
87
88 def _buildstatusmanifest(self, status):
88 def _buildstatusmanifest(self, status):
89 """Builds a manifest that includes the given status results, if this is
89 """Builds a manifest that includes the given status results, if this is
90 a working copy context. For non-working copy contexts, it just returns
90 a working copy context. For non-working copy contexts, it just returns
91 the normal manifest."""
91 the normal manifest."""
92 return self.manifest()
92 return self.manifest()
93
93
94 def _matchstatus(self, other, match):
94 def _matchstatus(self, other, match):
95 """This internal method provides a way for child objects to override the
95 """This internal method provides a way for child objects to override the
96 match operator.
96 match operator.
97 """
97 """
98 return match
98 return match
99
99
100 def _buildstatus(self, other, s, match, listignored, listclean,
100 def _buildstatus(self, other, s, match, listignored, listclean,
101 listunknown):
101 listunknown):
102 """build a status with respect to another context"""
102 """build a status with respect to another context"""
103 # Load earliest manifest first for caching reasons. More specifically,
103 # Load earliest manifest first for caching reasons. More specifically,
104 # if you have revisions 1000 and 1001, 1001 is probably stored as a
104 # if you have revisions 1000 and 1001, 1001 is probably stored as a
105 # delta against 1000. Thus, if you read 1000 first, we'll reconstruct
105 # delta against 1000. Thus, if you read 1000 first, we'll reconstruct
106 # 1000 and cache it so that when you read 1001, we just need to apply a
106 # 1000 and cache it so that when you read 1001, we just need to apply a
107 # delta to what's in the cache. So that's one full reconstruction + one
107 # delta to what's in the cache. So that's one full reconstruction + one
108 # delta application.
108 # delta application.
109 mf2 = None
109 mf2 = None
110 if self.rev() is not None and self.rev() < other.rev():
110 if self.rev() is not None and self.rev() < other.rev():
111 mf2 = self._buildstatusmanifest(s)
111 mf2 = self._buildstatusmanifest(s)
112 mf1 = other._buildstatusmanifest(s)
112 mf1 = other._buildstatusmanifest(s)
113 if mf2 is None:
113 if mf2 is None:
114 mf2 = self._buildstatusmanifest(s)
114 mf2 = self._buildstatusmanifest(s)
115
115
116 modified, added = [], []
116 modified, added = [], []
117 removed = []
117 removed = []
118 clean = []
118 clean = []
119 deleted, unknown, ignored = s.deleted, s.unknown, s.ignored
119 deleted, unknown, ignored = s.deleted, s.unknown, s.ignored
120 deletedset = set(deleted)
120 deletedset = set(deleted)
121 d = mf1.diff(mf2, match=match, clean=listclean)
121 d = mf1.diff(mf2, match=match, clean=listclean)
122 for fn, value in d.iteritems():
122 for fn, value in d.iteritems():
123 if fn in deletedset:
123 if fn in deletedset:
124 continue
124 continue
125 if value is None:
125 if value is None:
126 clean.append(fn)
126 clean.append(fn)
127 continue
127 continue
128 (node1, flag1), (node2, flag2) = value
128 (node1, flag1), (node2, flag2) = value
129 if node1 is None:
129 if node1 is None:
130 added.append(fn)
130 added.append(fn)
131 elif node2 is None:
131 elif node2 is None:
132 removed.append(fn)
132 removed.append(fn)
133 elif flag1 != flag2:
133 elif flag1 != flag2:
134 modified.append(fn)
134 modified.append(fn)
135 elif node2 not in wdirfilenodeids:
135 elif node2 not in wdirfilenodeids:
136 # When comparing files between two commits, we save time by
136 # When comparing files between two commits, we save time by
137 # not comparing the file contents when the nodeids differ.
137 # not comparing the file contents when the nodeids differ.
138 # Note that this means we incorrectly report a reverted change
138 # Note that this means we incorrectly report a reverted change
139 # to a file as a modification.
139 # to a file as a modification.
140 modified.append(fn)
140 modified.append(fn)
141 elif self[fn].cmp(other[fn]):
141 elif self[fn].cmp(other[fn]):
142 modified.append(fn)
142 modified.append(fn)
143 else:
143 else:
144 clean.append(fn)
144 clean.append(fn)
145
145
146 if removed:
146 if removed:
147 # need to filter files if they are already reported as removed
147 # need to filter files if they are already reported as removed
148 unknown = [fn for fn in unknown if fn not in mf1 and
148 unknown = [fn for fn in unknown if fn not in mf1 and
149 (not match or match(fn))]
149 (not match or match(fn))]
150 ignored = [fn for fn in ignored if fn not in mf1 and
150 ignored = [fn for fn in ignored if fn not in mf1 and
151 (not match or match(fn))]
151 (not match or match(fn))]
152 # if they're deleted, don't report them as removed
152 # if they're deleted, don't report them as removed
153 removed = [fn for fn in removed if fn not in deletedset]
153 removed = [fn for fn in removed if fn not in deletedset]
154
154
155 return scmutil.status(modified, added, removed, deleted, unknown,
155 return scmutil.status(modified, added, removed, deleted, unknown,
156 ignored, clean)
156 ignored, clean)
157
157
158 @propertycache
158 @propertycache
159 def substate(self):
159 def substate(self):
160 return subrepoutil.state(self, self._repo.ui)
160 return subrepoutil.state(self, self._repo.ui)
161
161
162 def subrev(self, subpath):
162 def subrev(self, subpath):
163 return self.substate[subpath][1]
163 return self.substate[subpath][1]
164
164
165 def rev(self):
165 def rev(self):
166 return self._rev
166 return self._rev
167 def node(self):
167 def node(self):
168 return self._node
168 return self._node
169 def hex(self):
169 def hex(self):
170 return hex(self.node())
170 return hex(self.node())
171 def manifest(self):
171 def manifest(self):
172 return self._manifest
172 return self._manifest
173 def manifestctx(self):
173 def manifestctx(self):
174 return self._manifestctx
174 return self._manifestctx
175 def repo(self):
175 def repo(self):
176 return self._repo
176 return self._repo
177 def phasestr(self):
177 def phasestr(self):
178 return phases.phasenames[self.phase()]
178 return phases.phasenames[self.phase()]
179 def mutable(self):
179 def mutable(self):
180 return self.phase() > phases.public
180 return self.phase() > phases.public
181
181
182 def matchfileset(self, expr, badfn=None):
182 def matchfileset(self, expr, badfn=None):
183 return fileset.match(self, expr, badfn=badfn)
183 return fileset.match(self, expr, badfn=badfn)
184
184
185 def obsolete(self):
185 def obsolete(self):
186 """True if the changeset is obsolete"""
186 """True if the changeset is obsolete"""
187 return self.rev() in obsmod.getrevs(self._repo, 'obsolete')
187 return self.rev() in obsmod.getrevs(self._repo, 'obsolete')
188
188
189 def extinct(self):
189 def extinct(self):
190 """True if the changeset is extinct"""
190 """True if the changeset is extinct"""
191 return self.rev() in obsmod.getrevs(self._repo, 'extinct')
191 return self.rev() in obsmod.getrevs(self._repo, 'extinct')
192
192
193 def orphan(self):
193 def orphan(self):
194 """True if the changeset is not obsolete, but its ancestor is"""
194 """True if the changeset is not obsolete, but its ancestor is"""
195 return self.rev() in obsmod.getrevs(self._repo, 'orphan')
195 return self.rev() in obsmod.getrevs(self._repo, 'orphan')
196
196
197 def phasedivergent(self):
197 def phasedivergent(self):
198 """True if the changeset tries to be a successor of a public changeset
198 """True if the changeset tries to be a successor of a public changeset
199
199
200 Only non-public and non-obsolete changesets may be phase-divergent.
200 Only non-public and non-obsolete changesets may be phase-divergent.
201 """
201 """
202 return self.rev() in obsmod.getrevs(self._repo, 'phasedivergent')
202 return self.rev() in obsmod.getrevs(self._repo, 'phasedivergent')
203
203
204 def contentdivergent(self):
204 def contentdivergent(self):
205 """Is a successor of a changeset with multiple possible successor sets
205 """Is a successor of a changeset with multiple possible successor sets
206
206
207 Only non-public and non-obsolete changesets may be content-divergent.
207 Only non-public and non-obsolete changesets may be content-divergent.
208 """
208 """
209 return self.rev() in obsmod.getrevs(self._repo, 'contentdivergent')
209 return self.rev() in obsmod.getrevs(self._repo, 'contentdivergent')
210
210
211 def isunstable(self):
211 def isunstable(self):
212 """True if the changeset is either orphan, phase-divergent or
212 """True if the changeset is either orphan, phase-divergent or
213 content-divergent"""
213 content-divergent"""
214 return self.orphan() or self.phasedivergent() or self.contentdivergent()
214 return self.orphan() or self.phasedivergent() or self.contentdivergent()
215
215
216 def instabilities(self):
216 def instabilities(self):
217 """return the list of instabilities affecting this changeset.
217 """return the list of instabilities affecting this changeset.
218
218
219 Instabilities are returned as strings. possible values are:
219 Instabilities are returned as strings. possible values are:
220 - orphan,
220 - orphan,
221 - phase-divergent,
221 - phase-divergent,
222 - content-divergent.
222 - content-divergent.
223 """
223 """
224 instabilities = []
224 instabilities = []
225 if self.orphan():
225 if self.orphan():
226 instabilities.append('orphan')
226 instabilities.append('orphan')
227 if self.phasedivergent():
227 if self.phasedivergent():
228 instabilities.append('phase-divergent')
228 instabilities.append('phase-divergent')
229 if self.contentdivergent():
229 if self.contentdivergent():
230 instabilities.append('content-divergent')
230 instabilities.append('content-divergent')
231 return instabilities
231 return instabilities
232
232
233 def parents(self):
233 def parents(self):
234 """return contexts for each parent changeset"""
234 """return contexts for each parent changeset"""
235 return self._parents
235 return self._parents
236
236
237 def p1(self):
237 def p1(self):
238 return self._parents[0]
238 return self._parents[0]
239
239
240 def p2(self):
240 def p2(self):
241 parents = self._parents
241 parents = self._parents
242 if len(parents) == 2:
242 if len(parents) == 2:
243 return parents[1]
243 return parents[1]
244 return self._repo[nullrev]
244 return self._repo[nullrev]
245
245
246 def _fileinfo(self, path):
246 def _fileinfo(self, path):
247 if r'_manifest' in self.__dict__:
247 if r'_manifest' in self.__dict__:
248 try:
248 try:
249 return self._manifest[path], self._manifest.flags(path)
249 return self._manifest[path], self._manifest.flags(path)
250 except KeyError:
250 except KeyError:
251 raise error.ManifestLookupError(self._node, path,
251 raise error.ManifestLookupError(self._node, path,
252 _('not found in manifest'))
252 _('not found in manifest'))
253 if r'_manifestdelta' in self.__dict__ or path in self.files():
253 if r'_manifestdelta' in self.__dict__ or path in self.files():
254 if path in self._manifestdelta:
254 if path in self._manifestdelta:
255 return (self._manifestdelta[path],
255 return (self._manifestdelta[path],
256 self._manifestdelta.flags(path))
256 self._manifestdelta.flags(path))
257 mfl = self._repo.manifestlog
257 mfl = self._repo.manifestlog
258 try:
258 try:
259 node, flag = mfl[self._changeset.manifest].find(path)
259 node, flag = mfl[self._changeset.manifest].find(path)
260 except KeyError:
260 except KeyError:
261 raise error.ManifestLookupError(self._node, path,
261 raise error.ManifestLookupError(self._node, path,
262 _('not found in manifest'))
262 _('not found in manifest'))
263
263
264 return node, flag
264 return node, flag
265
265
266 def filenode(self, path):
266 def filenode(self, path):
267 return self._fileinfo(path)[0]
267 return self._fileinfo(path)[0]
268
268
269 def flags(self, path):
269 def flags(self, path):
270 try:
270 try:
271 return self._fileinfo(path)[1]
271 return self._fileinfo(path)[1]
272 except error.LookupError:
272 except error.LookupError:
273 return ''
273 return ''
274
274
275 def sub(self, path, allowcreate=True):
275 def sub(self, path, allowcreate=True):
276 '''return a subrepo for the stored revision of path, never wdir()'''
276 '''return a subrepo for the stored revision of path, never wdir()'''
277 return subrepo.subrepo(self, path, allowcreate=allowcreate)
277 return subrepo.subrepo(self, path, allowcreate=allowcreate)
278
278
279 def nullsub(self, path, pctx):
279 def nullsub(self, path, pctx):
280 return subrepo.nullsubrepo(self, path, pctx)
280 return subrepo.nullsubrepo(self, path, pctx)
281
281
282 def workingsub(self, path):
282 def workingsub(self, path):
283 '''return a subrepo for the stored revision, or wdir if this is a wdir
283 '''return a subrepo for the stored revision, or wdir if this is a wdir
284 context.
284 context.
285 '''
285 '''
286 return subrepo.subrepo(self, path, allowwdir=True)
286 return subrepo.subrepo(self, path, allowwdir=True)
287
287
288 def match(self, pats=None, include=None, exclude=None, default='glob',
288 def match(self, pats=None, include=None, exclude=None, default='glob',
289 listsubrepos=False, badfn=None):
289 listsubrepos=False, badfn=None):
290 r = self._repo
290 r = self._repo
291 return matchmod.match(r.root, r.getcwd(), pats,
291 return matchmod.match(r.root, r.getcwd(), pats,
292 include, exclude, default,
292 include, exclude, default,
293 auditor=r.nofsauditor, ctx=self,
293 auditor=r.nofsauditor, ctx=self,
294 listsubrepos=listsubrepos, badfn=badfn)
294 listsubrepos=listsubrepos, badfn=badfn)
295
295
296 def diff(self, ctx2=None, match=None, changes=None, opts=None,
296 def diff(self, ctx2=None, match=None, changes=None, opts=None,
297 losedatafn=None, pathfn=None, copy=None,
297 losedatafn=None, pathfn=None, copy=None,
298 copysourcematch=None, hunksfilterfn=None):
298 copysourcematch=None, hunksfilterfn=None):
299 """Returns a diff generator for the given contexts and matcher"""
299 """Returns a diff generator for the given contexts and matcher"""
300 if ctx2 is None:
300 if ctx2 is None:
301 ctx2 = self.p1()
301 ctx2 = self.p1()
302 if ctx2 is not None:
302 if ctx2 is not None:
303 ctx2 = self._repo[ctx2]
303 ctx2 = self._repo[ctx2]
304 return patch.diff(self._repo, ctx2, self, match=match, changes=changes,
304 return patch.diff(self._repo, ctx2, self, match=match, changes=changes,
305 opts=opts, losedatafn=losedatafn, pathfn=pathfn,
305 opts=opts, losedatafn=losedatafn, pathfn=pathfn,
306 copy=copy, copysourcematch=copysourcematch,
306 copy=copy, copysourcematch=copysourcematch,
307 hunksfilterfn=hunksfilterfn)
307 hunksfilterfn=hunksfilterfn)
308
308
309 def dirs(self):
309 def dirs(self):
310 return self._manifest.dirs()
310 return self._manifest.dirs()
311
311
312 def hasdir(self, dir):
312 def hasdir(self, dir):
313 return self._manifest.hasdir(dir)
313 return self._manifest.hasdir(dir)
314
314
315 def status(self, other=None, match=None, listignored=False,
315 def status(self, other=None, match=None, listignored=False,
316 listclean=False, listunknown=False, listsubrepos=False):
316 listclean=False, listunknown=False, listsubrepos=False):
317 """return status of files between two nodes or node and working
317 """return status of files between two nodes or node and working
318 directory.
318 directory.
319
319
320 If other is None, compare this node with working directory.
320 If other is None, compare this node with working directory.
321
321
322 returns (modified, added, removed, deleted, unknown, ignored, clean)
322 returns (modified, added, removed, deleted, unknown, ignored, clean)
323 """
323 """
324
324
325 ctx1 = self
325 ctx1 = self
326 ctx2 = self._repo[other]
326 ctx2 = self._repo[other]
327
327
328 # This next code block is, admittedly, fragile logic that tests for
328 # This next code block is, admittedly, fragile logic that tests for
329 # reversing the contexts and wouldn't need to exist if it weren't for
329 # reversing the contexts and wouldn't need to exist if it weren't for
330 # the fast (and common) code path of comparing the working directory
330 # the fast (and common) code path of comparing the working directory
331 # with its first parent.
331 # with its first parent.
332 #
332 #
333 # What we're aiming for here is the ability to call:
333 # What we're aiming for here is the ability to call:
334 #
334 #
335 # workingctx.status(parentctx)
335 # workingctx.status(parentctx)
336 #
336 #
337 # If we always built the manifest for each context and compared those,
337 # If we always built the manifest for each context and compared those,
338 # then we'd be done. But the special case of the above call means we
338 # then we'd be done. But the special case of the above call means we
339 # just copy the manifest of the parent.
339 # just copy the manifest of the parent.
340 reversed = False
340 reversed = False
341 if (not isinstance(ctx1, changectx)
341 if (not isinstance(ctx1, changectx)
342 and isinstance(ctx2, changectx)):
342 and isinstance(ctx2, changectx)):
343 reversed = True
343 reversed = True
344 ctx1, ctx2 = ctx2, ctx1
344 ctx1, ctx2 = ctx2, ctx1
345
345
346 match = self._repo.narrowmatch(match)
346 match = self._repo.narrowmatch(match)
347 match = ctx2._matchstatus(ctx1, match)
347 match = ctx2._matchstatus(ctx1, match)
348 r = scmutil.status([], [], [], [], [], [], [])
348 r = scmutil.status([], [], [], [], [], [], [])
349 r = ctx2._buildstatus(ctx1, r, match, listignored, listclean,
349 r = ctx2._buildstatus(ctx1, r, match, listignored, listclean,
350 listunknown)
350 listunknown)
351
351
352 if reversed:
352 if reversed:
353 # Reverse added and removed. Clear deleted, unknown and ignored as
353 # Reverse added and removed. Clear deleted, unknown and ignored as
354 # these make no sense to reverse.
354 # these make no sense to reverse.
355 r = scmutil.status(r.modified, r.removed, r.added, [], [], [],
355 r = scmutil.status(r.modified, r.removed, r.added, [], [], [],
356 r.clean)
356 r.clean)
357
357
358 if listsubrepos:
358 if listsubrepos:
359 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
359 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
360 try:
360 try:
361 rev2 = ctx2.subrev(subpath)
361 rev2 = ctx2.subrev(subpath)
362 except KeyError:
362 except KeyError:
363 # A subrepo that existed in node1 was deleted between
363 # A subrepo that existed in node1 was deleted between
364 # node1 and node2 (inclusive). Thus, ctx2's substate
364 # node1 and node2 (inclusive). Thus, ctx2's substate
365 # won't contain that subpath. The best we can do ignore it.
365 # won't contain that subpath. The best we can do ignore it.
366 rev2 = None
366 rev2 = None
367 submatch = matchmod.subdirmatcher(subpath, match)
367 submatch = matchmod.subdirmatcher(subpath, match)
368 s = sub.status(rev2, match=submatch, ignored=listignored,
368 s = sub.status(rev2, match=submatch, ignored=listignored,
369 clean=listclean, unknown=listunknown,
369 clean=listclean, unknown=listunknown,
370 listsubrepos=True)
370 listsubrepos=True)
371 for rfiles, sfiles in zip(r, s):
371 for rfiles, sfiles in zip(r, s):
372 rfiles.extend("%s/%s" % (subpath, f) for f in sfiles)
372 rfiles.extend("%s/%s" % (subpath, f) for f in sfiles)
373
373
374 for l in r:
374 for l in r:
375 l.sort()
375 l.sort()
376
376
377 return r
377 return r
378
378
379 class changectx(basectx):
379 class changectx(basectx):
380 """A changecontext object makes access to data related to a particular
380 """A changecontext object makes access to data related to a particular
381 changeset convenient. It represents a read-only context already present in
381 changeset convenient. It represents a read-only context already present in
382 the repo."""
382 the repo."""
383 def __init__(self, repo, rev, node):
383 def __init__(self, repo, rev, node):
384 super(changectx, self).__init__(repo)
384 super(changectx, self).__init__(repo)
385 self._rev = rev
385 self._rev = rev
386 self._node = node
386 self._node = node
387
387
388 def __hash__(self):
388 def __hash__(self):
389 try:
389 try:
390 return hash(self._rev)
390 return hash(self._rev)
391 except AttributeError:
391 except AttributeError:
392 return id(self)
392 return id(self)
393
393
394 def __nonzero__(self):
394 def __nonzero__(self):
395 return self._rev != nullrev
395 return self._rev != nullrev
396
396
397 __bool__ = __nonzero__
397 __bool__ = __nonzero__
398
398
399 @propertycache
399 @propertycache
400 def _changeset(self):
400 def _changeset(self):
401 return self._repo.changelog.changelogrevision(self.rev())
401 return self._repo.changelog.changelogrevision(self.rev())
402
402
403 @propertycache
403 @propertycache
404 def _manifest(self):
404 def _manifest(self):
405 return self._manifestctx.read()
405 return self._manifestctx.read()
406
406
407 @property
407 @property
408 def _manifestctx(self):
408 def _manifestctx(self):
409 return self._repo.manifestlog[self._changeset.manifest]
409 return self._repo.manifestlog[self._changeset.manifest]
410
410
411 @propertycache
411 @propertycache
412 def _manifestdelta(self):
412 def _manifestdelta(self):
413 return self._manifestctx.readdelta()
413 return self._manifestctx.readdelta()
414
414
415 @propertycache
415 @propertycache
416 def _parents(self):
416 def _parents(self):
417 repo = self._repo
417 repo = self._repo
418 p1, p2 = repo.changelog.parentrevs(self._rev)
418 p1, p2 = repo.changelog.parentrevs(self._rev)
419 if p2 == nullrev:
419 if p2 == nullrev:
420 return [repo[p1]]
420 return [repo[p1]]
421 return [repo[p1], repo[p2]]
421 return [repo[p1], repo[p2]]
422
422
423 def changeset(self):
423 def changeset(self):
424 c = self._changeset
424 c = self._changeset
425 return (
425 return (
426 c.manifest,
426 c.manifest,
427 c.user,
427 c.user,
428 c.date,
428 c.date,
429 c.files,
429 c.files,
430 c.description,
430 c.description,
431 c.extra,
431 c.extra,
432 )
432 )
433 def manifestnode(self):
433 def manifestnode(self):
434 return self._changeset.manifest
434 return self._changeset.manifest
435
435
436 def user(self):
436 def user(self):
437 return self._changeset.user
437 return self._changeset.user
438 def date(self):
438 def date(self):
439 return self._changeset.date
439 return self._changeset.date
440 def files(self):
440 def files(self):
441 return self._changeset.files
441 return self._changeset.files
442 @propertycache
442 @propertycache
443 def _copies(self):
443 def _copies(self):
444 p1copies = {}
444 p1copies = {}
445 p2copies = {}
445 p2copies = {}
446 p1 = self.p1()
446 p1 = self.p1()
447 p2 = self.p2()
447 p2 = self.p2()
448 narrowmatch = self._repo.narrowmatch()
448 narrowmatch = self._repo.narrowmatch()
449 for dst in self.files():
449 for dst in self.files():
450 if not narrowmatch(dst) or dst not in self:
450 if not narrowmatch(dst) or dst not in self:
451 continue
451 continue
452 copied = self[dst].renamed()
452 copied = self[dst].renamed()
453 if not copied:
453 if not copied:
454 continue
454 continue
455 src, srcnode = copied
455 src, srcnode = copied
456 if src in p1 and p1[src].filenode() == srcnode:
456 if src in p1 and p1[src].filenode() == srcnode:
457 p1copies[dst] = src
457 p1copies[dst] = src
458 elif src in p2 and p2[src].filenode() == srcnode:
458 elif src in p2 and p2[src].filenode() == srcnode:
459 p2copies[dst] = src
459 p2copies[dst] = src
460 return p1copies, p2copies
460 return p1copies, p2copies
461 def p1copies(self):
461 def p1copies(self):
462 return self._copies[0]
462 return self._copies[0]
463 def p2copies(self):
463 def p2copies(self):
464 return self._copies[1]
464 return self._copies[1]
465 def description(self):
465 def description(self):
466 return self._changeset.description
466 return self._changeset.description
467 def branch(self):
467 def branch(self):
468 return encoding.tolocal(self._changeset.extra.get("branch"))
468 return encoding.tolocal(self._changeset.extra.get("branch"))
469 def closesbranch(self):
469 def closesbranch(self):
470 return 'close' in self._changeset.extra
470 return 'close' in self._changeset.extra
471 def extra(self):
471 def extra(self):
472 """Return a dict of extra information."""
472 """Return a dict of extra information."""
473 return self._changeset.extra
473 return self._changeset.extra
474 def tags(self):
474 def tags(self):
475 """Return a list of byte tag names"""
475 """Return a list of byte tag names"""
476 return self._repo.nodetags(self._node)
476 return self._repo.nodetags(self._node)
477 def bookmarks(self):
477 def bookmarks(self):
478 """Return a list of byte bookmark names."""
478 """Return a list of byte bookmark names."""
479 return self._repo.nodebookmarks(self._node)
479 return self._repo.nodebookmarks(self._node)
480 def phase(self):
480 def phase(self):
481 return self._repo._phasecache.phase(self._repo, self._rev)
481 return self._repo._phasecache.phase(self._repo, self._rev)
482 def hidden(self):
482 def hidden(self):
483 return self._rev in repoview.filterrevs(self._repo, 'visible')
483 return self._rev in repoview.filterrevs(self._repo, 'visible')
484
484
485 def isinmemory(self):
485 def isinmemory(self):
486 return False
486 return False
487
487
488 def children(self):
488 def children(self):
489 """return list of changectx contexts for each child changeset.
489 """return list of changectx contexts for each child changeset.
490
490
491 This returns only the immediate child changesets. Use descendants() to
491 This returns only the immediate child changesets. Use descendants() to
492 recursively walk children.
492 recursively walk children.
493 """
493 """
494 c = self._repo.changelog.children(self._node)
494 c = self._repo.changelog.children(self._node)
495 return [self._repo[x] for x in c]
495 return [self._repo[x] for x in c]
496
496
497 def ancestors(self):
497 def ancestors(self):
498 for a in self._repo.changelog.ancestors([self._rev]):
498 for a in self._repo.changelog.ancestors([self._rev]):
499 yield self._repo[a]
499 yield self._repo[a]
500
500
501 def descendants(self):
501 def descendants(self):
502 """Recursively yield all children of the changeset.
502 """Recursively yield all children of the changeset.
503
503
504 For just the immediate children, use children()
504 For just the immediate children, use children()
505 """
505 """
506 for d in self._repo.changelog.descendants([self._rev]):
506 for d in self._repo.changelog.descendants([self._rev]):
507 yield self._repo[d]
507 yield self._repo[d]
508
508
509 def filectx(self, path, fileid=None, filelog=None):
509 def filectx(self, path, fileid=None, filelog=None):
510 """get a file context from this changeset"""
510 """get a file context from this changeset"""
511 if fileid is None:
511 if fileid is None:
512 fileid = self.filenode(path)
512 fileid = self.filenode(path)
513 return filectx(self._repo, path, fileid=fileid,
513 return filectx(self._repo, path, fileid=fileid,
514 changectx=self, filelog=filelog)
514 changectx=self, filelog=filelog)
515
515
516 def ancestor(self, c2, warn=False):
516 def ancestor(self, c2, warn=False):
517 """return the "best" ancestor context of self and c2
517 """return the "best" ancestor context of self and c2
518
518
519 If there are multiple candidates, it will show a message and check
519 If there are multiple candidates, it will show a message and check
520 merge.preferancestor configuration before falling back to the
520 merge.preferancestor configuration before falling back to the
521 revlog ancestor."""
521 revlog ancestor."""
522 # deal with workingctxs
522 # deal with workingctxs
523 n2 = c2._node
523 n2 = c2._node
524 if n2 is None:
524 if n2 is None:
525 n2 = c2._parents[0]._node
525 n2 = c2._parents[0]._node
526 cahs = self._repo.changelog.commonancestorsheads(self._node, n2)
526 cahs = self._repo.changelog.commonancestorsheads(self._node, n2)
527 if not cahs:
527 if not cahs:
528 anc = nullid
528 anc = nullid
529 elif len(cahs) == 1:
529 elif len(cahs) == 1:
530 anc = cahs[0]
530 anc = cahs[0]
531 else:
531 else:
532 # experimental config: merge.preferancestor
532 # experimental config: merge.preferancestor
533 for r in self._repo.ui.configlist('merge', 'preferancestor'):
533 for r in self._repo.ui.configlist('merge', 'preferancestor'):
534 try:
534 try:
535 ctx = scmutil.revsymbol(self._repo, r)
535 ctx = scmutil.revsymbol(self._repo, r)
536 except error.RepoLookupError:
536 except error.RepoLookupError:
537 continue
537 continue
538 anc = ctx.node()
538 anc = ctx.node()
539 if anc in cahs:
539 if anc in cahs:
540 break
540 break
541 else:
541 else:
542 anc = self._repo.changelog.ancestor(self._node, n2)
542 anc = self._repo.changelog.ancestor(self._node, n2)
543 if warn:
543 if warn:
544 self._repo.ui.status(
544 self._repo.ui.status(
545 (_("note: using %s as ancestor of %s and %s\n") %
545 (_("note: using %s as ancestor of %s and %s\n") %
546 (short(anc), short(self._node), short(n2))) +
546 (short(anc), short(self._node), short(n2))) +
547 ''.join(_(" alternatively, use --config "
547 ''.join(_(" alternatively, use --config "
548 "merge.preferancestor=%s\n") %
548 "merge.preferancestor=%s\n") %
549 short(n) for n in sorted(cahs) if n != anc))
549 short(n) for n in sorted(cahs) if n != anc))
550 return self._repo[anc]
550 return self._repo[anc]
551
551
552 def isancestorof(self, other):
552 def isancestorof(self, other):
553 """True if this changeset is an ancestor of other"""
553 """True if this changeset is an ancestor of other"""
554 return self._repo.changelog.isancestorrev(self._rev, other._rev)
554 return self._repo.changelog.isancestorrev(self._rev, other._rev)
555
555
556 def walk(self, match):
556 def walk(self, match):
557 '''Generates matching file names.'''
557 '''Generates matching file names.'''
558
558
559 # Wrap match.bad method to have message with nodeid
559 # Wrap match.bad method to have message with nodeid
560 def bad(fn, msg):
560 def bad(fn, msg):
561 # The manifest doesn't know about subrepos, so don't complain about
561 # The manifest doesn't know about subrepos, so don't complain about
562 # paths into valid subrepos.
562 # paths into valid subrepos.
563 if any(fn == s or fn.startswith(s + '/')
563 if any(fn == s or fn.startswith(s + '/')
564 for s in self.substate):
564 for s in self.substate):
565 return
565 return
566 match.bad(fn, _('no such file in rev %s') % self)
566 match.bad(fn, _('no such file in rev %s') % self)
567
567
568 m = matchmod.badmatch(self._repo.narrowmatch(match), bad)
568 m = matchmod.badmatch(self._repo.narrowmatch(match), bad)
569 return self._manifest.walk(m)
569 return self._manifest.walk(m)
570
570
571 def matches(self, match):
571 def matches(self, match):
572 return self.walk(match)
572 return self.walk(match)
573
573
574 class basefilectx(object):
574 class basefilectx(object):
575 """A filecontext object represents the common logic for its children:
575 """A filecontext object represents the common logic for its children:
576 filectx: read-only access to a filerevision that is already present
576 filectx: read-only access to a filerevision that is already present
577 in the repo,
577 in the repo,
578 workingfilectx: a filecontext that represents files from the working
578 workingfilectx: a filecontext that represents files from the working
579 directory,
579 directory,
580 memfilectx: a filecontext that represents files in-memory,
580 memfilectx: a filecontext that represents files in-memory,
581 """
581 """
582 @propertycache
582 @propertycache
583 def _filelog(self):
583 def _filelog(self):
584 return self._repo.file(self._path)
584 return self._repo.file(self._path)
585
585
586 @propertycache
586 @propertycache
587 def _changeid(self):
587 def _changeid(self):
588 if r'_changectx' in self.__dict__:
588 if r'_changectx' in self.__dict__:
589 return self._changectx.rev()
589 return self._changectx.rev()
590 elif r'_descendantrev' in self.__dict__:
590 elif r'_descendantrev' in self.__dict__:
591 # this file context was created from a revision with a known
591 # this file context was created from a revision with a known
592 # descendant, we can (lazily) correct for linkrev aliases
592 # descendant, we can (lazily) correct for linkrev aliases
593 return self._adjustlinkrev(self._descendantrev)
593 return self._adjustlinkrev(self._descendantrev)
594 else:
594 else:
595 return self._filelog.linkrev(self._filerev)
595 return self._filelog.linkrev(self._filerev)
596
596
597 @propertycache
597 @propertycache
598 def _filenode(self):
598 def _filenode(self):
599 if r'_fileid' in self.__dict__:
599 if r'_fileid' in self.__dict__:
600 return self._filelog.lookup(self._fileid)
600 return self._filelog.lookup(self._fileid)
601 else:
601 else:
602 return self._changectx.filenode(self._path)
602 return self._changectx.filenode(self._path)
603
603
604 @propertycache
604 @propertycache
605 def _filerev(self):
605 def _filerev(self):
606 return self._filelog.rev(self._filenode)
606 return self._filelog.rev(self._filenode)
607
607
608 @propertycache
608 @propertycache
609 def _repopath(self):
609 def _repopath(self):
610 return self._path
610 return self._path
611
611
612 def __nonzero__(self):
612 def __nonzero__(self):
613 try:
613 try:
614 self._filenode
614 self._filenode
615 return True
615 return True
616 except error.LookupError:
616 except error.LookupError:
617 # file is missing
617 # file is missing
618 return False
618 return False
619
619
620 __bool__ = __nonzero__
620 __bool__ = __nonzero__
621
621
622 def __bytes__(self):
622 def __bytes__(self):
623 try:
623 try:
624 return "%s@%s" % (self.path(), self._changectx)
624 return "%s@%s" % (self.path(), self._changectx)
625 except error.LookupError:
625 except error.LookupError:
626 return "%s@???" % self.path()
626 return "%s@???" % self.path()
627
627
628 __str__ = encoding.strmethod(__bytes__)
628 __str__ = encoding.strmethod(__bytes__)
629
629
630 def __repr__(self):
630 def __repr__(self):
631 return r"<%s %s>" % (type(self).__name__, str(self))
631 return r"<%s %s>" % (type(self).__name__, str(self))
632
632
633 def __hash__(self):
633 def __hash__(self):
634 try:
634 try:
635 return hash((self._path, self._filenode))
635 return hash((self._path, self._filenode))
636 except AttributeError:
636 except AttributeError:
637 return id(self)
637 return id(self)
638
638
639 def __eq__(self, other):
639 def __eq__(self, other):
640 try:
640 try:
641 return (type(self) == type(other) and self._path == other._path
641 return (type(self) == type(other) and self._path == other._path
642 and self._filenode == other._filenode)
642 and self._filenode == other._filenode)
643 except AttributeError:
643 except AttributeError:
644 return False
644 return False
645
645
646 def __ne__(self, other):
646 def __ne__(self, other):
647 return not (self == other)
647 return not (self == other)
648
648
649 def filerev(self):
649 def filerev(self):
650 return self._filerev
650 return self._filerev
651 def filenode(self):
651 def filenode(self):
652 return self._filenode
652 return self._filenode
653 @propertycache
653 @propertycache
654 def _flags(self):
654 def _flags(self):
655 return self._changectx.flags(self._path)
655 return self._changectx.flags(self._path)
656 def flags(self):
656 def flags(self):
657 return self._flags
657 return self._flags
658 def filelog(self):
658 def filelog(self):
659 return self._filelog
659 return self._filelog
660 def rev(self):
660 def rev(self):
661 return self._changeid
661 return self._changeid
662 def linkrev(self):
662 def linkrev(self):
663 return self._filelog.linkrev(self._filerev)
663 return self._filelog.linkrev(self._filerev)
664 def node(self):
664 def node(self):
665 return self._changectx.node()
665 return self._changectx.node()
666 def hex(self):
666 def hex(self):
667 return self._changectx.hex()
667 return self._changectx.hex()
668 def user(self):
668 def user(self):
669 return self._changectx.user()
669 return self._changectx.user()
670 def date(self):
670 def date(self):
671 return self._changectx.date()
671 return self._changectx.date()
672 def files(self):
672 def files(self):
673 return self._changectx.files()
673 return self._changectx.files()
674 def description(self):
674 def description(self):
675 return self._changectx.description()
675 return self._changectx.description()
676 def branch(self):
676 def branch(self):
677 return self._changectx.branch()
677 return self._changectx.branch()
678 def extra(self):
678 def extra(self):
679 return self._changectx.extra()
679 return self._changectx.extra()
680 def phase(self):
680 def phase(self):
681 return self._changectx.phase()
681 return self._changectx.phase()
682 def phasestr(self):
682 def phasestr(self):
683 return self._changectx.phasestr()
683 return self._changectx.phasestr()
684 def obsolete(self):
684 def obsolete(self):
685 return self._changectx.obsolete()
685 return self._changectx.obsolete()
686 def instabilities(self):
686 def instabilities(self):
687 return self._changectx.instabilities()
687 return self._changectx.instabilities()
688 def manifest(self):
688 def manifest(self):
689 return self._changectx.manifest()
689 return self._changectx.manifest()
690 def changectx(self):
690 def changectx(self):
691 return self._changectx
691 return self._changectx
692 def renamed(self):
692 def renamed(self):
693 return self._copied
693 return self._copied
694 def copysource(self):
694 def copysource(self):
695 return self._copied and self._copied[0]
695 return self._copied and self._copied[0]
696 def repo(self):
696 def repo(self):
697 return self._repo
697 return self._repo
698 def size(self):
698 def size(self):
699 return len(self.data())
699 return len(self.data())
700
700
701 def path(self):
701 def path(self):
702 return self._path
702 return self._path
703
703
704 def isbinary(self):
704 def isbinary(self):
705 try:
705 try:
706 return stringutil.binary(self.data())
706 return stringutil.binary(self.data())
707 except IOError:
707 except IOError:
708 return False
708 return False
709 def isexec(self):
709 def isexec(self):
710 return 'x' in self.flags()
710 return 'x' in self.flags()
711 def islink(self):
711 def islink(self):
712 return 'l' in self.flags()
712 return 'l' in self.flags()
713
713
714 def isabsent(self):
714 def isabsent(self):
715 """whether this filectx represents a file not in self._changectx
715 """whether this filectx represents a file not in self._changectx
716
716
717 This is mainly for merge code to detect change/delete conflicts. This is
717 This is mainly for merge code to detect change/delete conflicts. This is
718 expected to be True for all subclasses of basectx."""
718 expected to be True for all subclasses of basectx."""
719 return False
719 return False
720
720
721 _customcmp = False
721 _customcmp = False
722 def cmp(self, fctx):
722 def cmp(self, fctx):
723 """compare with other file context
723 """compare with other file context
724
724
725 returns True if different than fctx.
725 returns True if different than fctx.
726 """
726 """
727 if fctx._customcmp:
727 if fctx._customcmp:
728 return fctx.cmp(self)
728 return fctx.cmp(self)
729
729
730 if self._filenode is None:
730 if self._filenode is None:
731 raise error.ProgrammingError(
731 raise error.ProgrammingError(
732 'filectx.cmp() must be reimplemented if not backed by revlog')
732 'filectx.cmp() must be reimplemented if not backed by revlog')
733
733
734 if fctx._filenode is None:
734 if fctx._filenode is None:
735 if self._repo._encodefilterpats:
735 if self._repo._encodefilterpats:
736 # can't rely on size() because wdir content may be decoded
736 # can't rely on size() because wdir content may be decoded
737 return self._filelog.cmp(self._filenode, fctx.data())
737 return self._filelog.cmp(self._filenode, fctx.data())
738 if self.size() - 4 == fctx.size():
738 if self.size() - 4 == fctx.size():
739 # size() can match:
739 # size() can match:
740 # if file data starts with '\1\n', empty metadata block is
740 # if file data starts with '\1\n', empty metadata block is
741 # prepended, which adds 4 bytes to filelog.size().
741 # prepended, which adds 4 bytes to filelog.size().
742 return self._filelog.cmp(self._filenode, fctx.data())
742 return self._filelog.cmp(self._filenode, fctx.data())
743 if self.size() == fctx.size():
743 if self.size() == fctx.size():
744 # size() matches: need to compare content
744 # size() matches: need to compare content
745 return self._filelog.cmp(self._filenode, fctx.data())
745 return self._filelog.cmp(self._filenode, fctx.data())
746
746
747 # size() differs
747 # size() differs
748 return True
748 return True
749
749
750 def _adjustlinkrev(self, srcrev, inclusive=False, stoprev=None):
750 def _adjustlinkrev(self, srcrev, inclusive=False, stoprev=None):
751 """return the first ancestor of <srcrev> introducing <fnode>
751 """return the first ancestor of <srcrev> introducing <fnode>
752
752
753 If the linkrev of the file revision does not point to an ancestor of
753 If the linkrev of the file revision does not point to an ancestor of
754 srcrev, we'll walk down the ancestors until we find one introducing
754 srcrev, we'll walk down the ancestors until we find one introducing
755 this file revision.
755 this file revision.
756
756
757 :srcrev: the changeset revision we search ancestors from
757 :srcrev: the changeset revision we search ancestors from
758 :inclusive: if true, the src revision will also be checked
758 :inclusive: if true, the src revision will also be checked
759 :stoprev: an optional revision to stop the walk at. If no introduction
759 :stoprev: an optional revision to stop the walk at. If no introduction
760 of this file content could be found before this floor
760 of this file content could be found before this floor
761 revision, the function will returns "None" and stops its
761 revision, the function will returns "None" and stops its
762 iteration.
762 iteration.
763 """
763 """
764 repo = self._repo
764 repo = self._repo
765 cl = repo.unfiltered().changelog
765 cl = repo.unfiltered().changelog
766 mfl = repo.manifestlog
766 mfl = repo.manifestlog
767 # fetch the linkrev
767 # fetch the linkrev
768 lkr = self.linkrev()
768 lkr = self.linkrev()
769 if srcrev == lkr:
769 if srcrev == lkr:
770 return lkr
770 return lkr
771 # hack to reuse ancestor computation when searching for renames
771 # hack to reuse ancestor computation when searching for renames
772 memberanc = getattr(self, '_ancestrycontext', None)
772 memberanc = getattr(self, '_ancestrycontext', None)
773 iteranc = None
773 iteranc = None
774 if srcrev is None:
774 if srcrev is None:
775 # wctx case, used by workingfilectx during mergecopy
775 # wctx case, used by workingfilectx during mergecopy
776 revs = [p.rev() for p in self._repo[None].parents()]
776 revs = [p.rev() for p in self._repo[None].parents()]
777 inclusive = True # we skipped the real (revless) source
777 inclusive = True # we skipped the real (revless) source
778 else:
778 else:
779 revs = [srcrev]
779 revs = [srcrev]
780 if memberanc is None:
780 if memberanc is None:
781 memberanc = iteranc = cl.ancestors(revs, lkr,
781 memberanc = iteranc = cl.ancestors(revs, lkr,
782 inclusive=inclusive)
782 inclusive=inclusive)
783 # check if this linkrev is an ancestor of srcrev
783 # check if this linkrev is an ancestor of srcrev
784 if lkr not in memberanc:
784 if lkr not in memberanc:
785 if iteranc is None:
785 if iteranc is None:
786 iteranc = cl.ancestors(revs, lkr, inclusive=inclusive)
786 iteranc = cl.ancestors(revs, lkr, inclusive=inclusive)
787 fnode = self._filenode
787 fnode = self._filenode
788 path = self._path
788 path = self._path
789 for a in iteranc:
789 for a in iteranc:
790 if stoprev is not None and a < stoprev:
790 if stoprev is not None and a < stoprev:
791 return None
791 return None
792 ac = cl.read(a) # get changeset data (we avoid object creation)
792 ac = cl.read(a) # get changeset data (we avoid object creation)
793 if path in ac[3]: # checking the 'files' field.
793 if path in ac[3]: # checking the 'files' field.
794 # The file has been touched, check if the content is
794 # The file has been touched, check if the content is
795 # similar to the one we search for.
795 # similar to the one we search for.
796 if fnode == mfl[ac[0]].readfast().get(path):
796 if fnode == mfl[ac[0]].readfast().get(path):
797 return a
797 return a
798 # In theory, we should never get out of that loop without a result.
798 # In theory, we should never get out of that loop without a result.
799 # But if manifest uses a buggy file revision (not children of the
799 # But if manifest uses a buggy file revision (not children of the
800 # one it replaces) we could. Such a buggy situation will likely
800 # one it replaces) we could. Such a buggy situation will likely
801 # result is crash somewhere else at to some point.
801 # result is crash somewhere else at to some point.
802 return lkr
802 return lkr
803
803
804 def isintroducedafter(self, changelogrev):
804 def isintroducedafter(self, changelogrev):
805 """True if a filectx has been introduced after a given floor revision
805 """True if a filectx has been introduced after a given floor revision
806 """
806 """
807 if self.linkrev() >= changelogrev:
807 if self.linkrev() >= changelogrev:
808 return True
808 return True
809 introrev = self._introrev(stoprev=changelogrev)
809 introrev = self._introrev(stoprev=changelogrev)
810 if introrev is None:
810 if introrev is None:
811 return False
811 return False
812 return introrev >= changelogrev
812 return introrev >= changelogrev
813
813
814 def introrev(self):
814 def introrev(self):
815 """return the rev of the changeset which introduced this file revision
815 """return the rev of the changeset which introduced this file revision
816
816
817 This method is different from linkrev because it take into account the
817 This method is different from linkrev because it take into account the
818 changeset the filectx was created from. It ensures the returned
818 changeset the filectx was created from. It ensures the returned
819 revision is one of its ancestors. This prevents bugs from
819 revision is one of its ancestors. This prevents bugs from
820 'linkrev-shadowing' when a file revision is used by multiple
820 'linkrev-shadowing' when a file revision is used by multiple
821 changesets.
821 changesets.
822 """
822 """
823 return self._introrev()
823 return self._introrev()
824
824
825 def _introrev(self, stoprev=None):
825 def _introrev(self, stoprev=None):
826 """
826 """
827 Same as `introrev` but, with an extra argument to limit changelog
827 Same as `introrev` but, with an extra argument to limit changelog
828 iteration range in some internal usecase.
828 iteration range in some internal usecase.
829
829
830 If `stoprev` is set, the `introrev` will not be searched past that
830 If `stoprev` is set, the `introrev` will not be searched past that
831 `stoprev` revision and "None" might be returned. This is useful to
831 `stoprev` revision and "None" might be returned. This is useful to
832 limit the iteration range.
832 limit the iteration range.
833 """
833 """
834 toprev = None
834 toprev = None
835 attrs = vars(self)
835 attrs = vars(self)
836 if r'_changeid' in attrs:
836 if r'_changeid' in attrs:
837 # We have a cached value already
837 # We have a cached value already
838 toprev = self._changeid
838 toprev = self._changeid
839 elif r'_changectx' in attrs:
839 elif r'_changectx' in attrs:
840 # We know which changelog entry we are coming from
840 # We know which changelog entry we are coming from
841 toprev = self._changectx.rev()
841 toprev = self._changectx.rev()
842
842
843 if toprev is not None:
843 if toprev is not None:
844 return self._adjustlinkrev(toprev, inclusive=True, stoprev=stoprev)
844 return self._adjustlinkrev(toprev, inclusive=True, stoprev=stoprev)
845 elif r'_descendantrev' in attrs:
845 elif r'_descendantrev' in attrs:
846 introrev = self._adjustlinkrev(self._descendantrev, stoprev=stoprev)
846 introrev = self._adjustlinkrev(self._descendantrev, stoprev=stoprev)
847 # be nice and cache the result of the computation
847 # be nice and cache the result of the computation
848 if introrev is not None:
848 if introrev is not None:
849 self._changeid = introrev
849 self._changeid = introrev
850 return introrev
850 return introrev
851 else:
851 else:
852 return self.linkrev()
852 return self.linkrev()
853
853
854 def introfilectx(self):
854 def introfilectx(self):
855 """Return filectx having identical contents, but pointing to the
855 """Return filectx having identical contents, but pointing to the
856 changeset revision where this filectx was introduced"""
856 changeset revision where this filectx was introduced"""
857 introrev = self.introrev()
857 introrev = self.introrev()
858 if self.rev() == introrev:
858 if self.rev() == introrev:
859 return self
859 return self
860 return self.filectx(self.filenode(), changeid=introrev)
860 return self.filectx(self.filenode(), changeid=introrev)
861
861
862 def _parentfilectx(self, path, fileid, filelog):
862 def _parentfilectx(self, path, fileid, filelog):
863 """create parent filectx keeping ancestry info for _adjustlinkrev()"""
863 """create parent filectx keeping ancestry info for _adjustlinkrev()"""
864 fctx = filectx(self._repo, path, fileid=fileid, filelog=filelog)
864 fctx = filectx(self._repo, path, fileid=fileid, filelog=filelog)
865 if r'_changeid' in vars(self) or r'_changectx' in vars(self):
865 if r'_changeid' in vars(self) or r'_changectx' in vars(self):
866 # If self is associated with a changeset (probably explicitly
866 # If self is associated with a changeset (probably explicitly
867 # fed), ensure the created filectx is associated with a
867 # fed), ensure the created filectx is associated with a
868 # changeset that is an ancestor of self.changectx.
868 # changeset that is an ancestor of self.changectx.
869 # This lets us later use _adjustlinkrev to get a correct link.
869 # This lets us later use _adjustlinkrev to get a correct link.
870 fctx._descendantrev = self.rev()
870 fctx._descendantrev = self.rev()
871 fctx._ancestrycontext = getattr(self, '_ancestrycontext', None)
871 fctx._ancestrycontext = getattr(self, '_ancestrycontext', None)
872 elif r'_descendantrev' in vars(self):
872 elif r'_descendantrev' in vars(self):
873 # Otherwise propagate _descendantrev if we have one associated.
873 # Otherwise propagate _descendantrev if we have one associated.
874 fctx._descendantrev = self._descendantrev
874 fctx._descendantrev = self._descendantrev
875 fctx._ancestrycontext = getattr(self, '_ancestrycontext', None)
875 fctx._ancestrycontext = getattr(self, '_ancestrycontext', None)
876 return fctx
876 return fctx
877
877
878 def parents(self):
878 def parents(self):
879 _path = self._path
879 _path = self._path
880 fl = self._filelog
880 fl = self._filelog
881 parents = self._filelog.parents(self._filenode)
881 parents = self._filelog.parents(self._filenode)
882 pl = [(_path, node, fl) for node in parents if node != nullid]
882 pl = [(_path, node, fl) for node in parents if node != nullid]
883
883
884 r = fl.renamed(self._filenode)
884 r = fl.renamed(self._filenode)
885 if r:
885 if r:
886 # - In the simple rename case, both parent are nullid, pl is empty.
886 # - In the simple rename case, both parent are nullid, pl is empty.
887 # - In case of merge, only one of the parent is null id and should
887 # - In case of merge, only one of the parent is null id and should
888 # be replaced with the rename information. This parent is -always-
888 # be replaced with the rename information. This parent is -always-
889 # the first one.
889 # the first one.
890 #
890 #
891 # As null id have always been filtered out in the previous list
891 # As null id have always been filtered out in the previous list
892 # comprehension, inserting to 0 will always result in "replacing
892 # comprehension, inserting to 0 will always result in "replacing
893 # first nullid parent with rename information.
893 # first nullid parent with rename information.
894 pl.insert(0, (r[0], r[1], self._repo.file(r[0])))
894 pl.insert(0, (r[0], r[1], self._repo.file(r[0])))
895
895
896 return [self._parentfilectx(path, fnode, l) for path, fnode, l in pl]
896 return [self._parentfilectx(path, fnode, l) for path, fnode, l in pl]
897
897
898 def p1(self):
898 def p1(self):
899 return self.parents()[0]
899 return self.parents()[0]
900
900
901 def p2(self):
901 def p2(self):
902 p = self.parents()
902 p = self.parents()
903 if len(p) == 2:
903 if len(p) == 2:
904 return p[1]
904 return p[1]
905 return filectx(self._repo, self._path, fileid=-1, filelog=self._filelog)
905 return filectx(self._repo, self._path, fileid=-1, filelog=self._filelog)
906
906
907 def annotate(self, follow=False, skiprevs=None, diffopts=None):
907 def annotate(self, follow=False, skiprevs=None, diffopts=None):
908 """Returns a list of annotateline objects for each line in the file
908 """Returns a list of annotateline objects for each line in the file
909
909
910 - line.fctx is the filectx of the node where that line was last changed
910 - line.fctx is the filectx of the node where that line was last changed
911 - line.lineno is the line number at the first appearance in the managed
911 - line.lineno is the line number at the first appearance in the managed
912 file
912 file
913 - line.text is the data on that line (including newline character)
913 - line.text is the data on that line (including newline character)
914 """
914 """
915 getlog = util.lrucachefunc(lambda x: self._repo.file(x))
915 getlog = util.lrucachefunc(lambda x: self._repo.file(x))
916
916
917 def parents(f):
917 def parents(f):
918 # Cut _descendantrev here to mitigate the penalty of lazy linkrev
918 # Cut _descendantrev here to mitigate the penalty of lazy linkrev
919 # adjustment. Otherwise, p._adjustlinkrev() would walk changelog
919 # adjustment. Otherwise, p._adjustlinkrev() would walk changelog
920 # from the topmost introrev (= srcrev) down to p.linkrev() if it
920 # from the topmost introrev (= srcrev) down to p.linkrev() if it
921 # isn't an ancestor of the srcrev.
921 # isn't an ancestor of the srcrev.
922 f._changeid
922 f._changeid
923 pl = f.parents()
923 pl = f.parents()
924
924
925 # Don't return renamed parents if we aren't following.
925 # Don't return renamed parents if we aren't following.
926 if not follow:
926 if not follow:
927 pl = [p for p in pl if p.path() == f.path()]
927 pl = [p for p in pl if p.path() == f.path()]
928
928
929 # renamed filectx won't have a filelog yet, so set it
929 # renamed filectx won't have a filelog yet, so set it
930 # from the cache to save time
930 # from the cache to save time
931 for p in pl:
931 for p in pl:
932 if not r'_filelog' in p.__dict__:
932 if not r'_filelog' in p.__dict__:
933 p._filelog = getlog(p.path())
933 p._filelog = getlog(p.path())
934
934
935 return pl
935 return pl
936
936
937 # use linkrev to find the first changeset where self appeared
937 # use linkrev to find the first changeset where self appeared
938 base = self.introfilectx()
938 base = self.introfilectx()
939 if getattr(base, '_ancestrycontext', None) is None:
939 if getattr(base, '_ancestrycontext', None) is None:
940 cl = self._repo.changelog
940 cl = self._repo.changelog
941 if base.rev() is None:
941 if base.rev() is None:
942 # wctx is not inclusive, but works because _ancestrycontext
942 # wctx is not inclusive, but works because _ancestrycontext
943 # is used to test filelog revisions
943 # is used to test filelog revisions
944 ac = cl.ancestors([p.rev() for p in base.parents()],
944 ac = cl.ancestors([p.rev() for p in base.parents()],
945 inclusive=True)
945 inclusive=True)
946 else:
946 else:
947 ac = cl.ancestors([base.rev()], inclusive=True)
947 ac = cl.ancestors([base.rev()], inclusive=True)
948 base._ancestrycontext = ac
948 base._ancestrycontext = ac
949
949
950 return dagop.annotate(base, parents, skiprevs=skiprevs,
950 return dagop.annotate(base, parents, skiprevs=skiprevs,
951 diffopts=diffopts)
951 diffopts=diffopts)
952
952
953 def ancestors(self, followfirst=False):
953 def ancestors(self, followfirst=False):
954 visit = {}
954 visit = {}
955 c = self
955 c = self
956 if followfirst:
956 if followfirst:
957 cut = 1
957 cut = 1
958 else:
958 else:
959 cut = None
959 cut = None
960
960
961 while True:
961 while True:
962 for parent in c.parents()[:cut]:
962 for parent in c.parents()[:cut]:
963 visit[(parent.linkrev(), parent.filenode())] = parent
963 visit[(parent.linkrev(), parent.filenode())] = parent
964 if not visit:
964 if not visit:
965 break
965 break
966 c = visit.pop(max(visit))
966 c = visit.pop(max(visit))
967 yield c
967 yield c
968
968
969 def decodeddata(self):
969 def decodeddata(self):
970 """Returns `data()` after running repository decoding filters.
970 """Returns `data()` after running repository decoding filters.
971
971
972 This is often equivalent to how the data would be expressed on disk.
972 This is often equivalent to how the data would be expressed on disk.
973 """
973 """
974 return self._repo.wwritedata(self.path(), self.data())
974 return self._repo.wwritedata(self.path(), self.data())
975
975
976 class filectx(basefilectx):
976 class filectx(basefilectx):
977 """A filecontext object makes access to data related to a particular
977 """A filecontext object makes access to data related to a particular
978 filerevision convenient."""
978 filerevision convenient."""
979 def __init__(self, repo, path, changeid=None, fileid=None,
979 def __init__(self, repo, path, changeid=None, fileid=None,
980 filelog=None, changectx=None):
980 filelog=None, changectx=None):
981 """changeid must be a revision number, if specified.
981 """changeid must be a revision number, if specified.
982 fileid can be a file revision or node."""
982 fileid can be a file revision or node."""
983 self._repo = repo
983 self._repo = repo
984 self._path = path
984 self._path = path
985
985
986 assert (changeid is not None
986 assert (changeid is not None
987 or fileid is not None
987 or fileid is not None
988 or changectx is not None), (
988 or changectx is not None), (
989 "bad args: changeid=%r, fileid=%r, changectx=%r"
989 "bad args: changeid=%r, fileid=%r, changectx=%r"
990 % (changeid, fileid, changectx))
990 % (changeid, fileid, changectx))
991
991
992 if filelog is not None:
992 if filelog is not None:
993 self._filelog = filelog
993 self._filelog = filelog
994
994
995 if changeid is not None:
995 if changeid is not None:
996 self._changeid = changeid
996 self._changeid = changeid
997 if changectx is not None:
997 if changectx is not None:
998 self._changectx = changectx
998 self._changectx = changectx
999 if fileid is not None:
999 if fileid is not None:
1000 self._fileid = fileid
1000 self._fileid = fileid
1001
1001
1002 @propertycache
1002 @propertycache
1003 def _changectx(self):
1003 def _changectx(self):
1004 try:
1004 try:
1005 return self._repo[self._changeid]
1005 return self._repo[self._changeid]
1006 except error.FilteredRepoLookupError:
1006 except error.FilteredRepoLookupError:
1007 # Linkrev may point to any revision in the repository. When the
1007 # Linkrev may point to any revision in the repository. When the
1008 # repository is filtered this may lead to `filectx` trying to build
1008 # repository is filtered this may lead to `filectx` trying to build
1009 # `changectx` for filtered revision. In such case we fallback to
1009 # `changectx` for filtered revision. In such case we fallback to
1010 # creating `changectx` on the unfiltered version of the reposition.
1010 # creating `changectx` on the unfiltered version of the reposition.
1011 # This fallback should not be an issue because `changectx` from
1011 # This fallback should not be an issue because `changectx` from
1012 # `filectx` are not used in complex operations that care about
1012 # `filectx` are not used in complex operations that care about
1013 # filtering.
1013 # filtering.
1014 #
1014 #
1015 # This fallback is a cheap and dirty fix that prevent several
1015 # This fallback is a cheap and dirty fix that prevent several
1016 # crashes. It does not ensure the behavior is correct. However the
1016 # crashes. It does not ensure the behavior is correct. However the
1017 # behavior was not correct before filtering either and "incorrect
1017 # behavior was not correct before filtering either and "incorrect
1018 # behavior" is seen as better as "crash"
1018 # behavior" is seen as better as "crash"
1019 #
1019 #
1020 # Linkrevs have several serious troubles with filtering that are
1020 # Linkrevs have several serious troubles with filtering that are
1021 # complicated to solve. Proper handling of the issue here should be
1021 # complicated to solve. Proper handling of the issue here should be
1022 # considered when solving linkrev issue are on the table.
1022 # considered when solving linkrev issue are on the table.
1023 return self._repo.unfiltered()[self._changeid]
1023 return self._repo.unfiltered()[self._changeid]
1024
1024
1025 def filectx(self, fileid, changeid=None):
1025 def filectx(self, fileid, changeid=None):
1026 '''opens an arbitrary revision of the file without
1026 '''opens an arbitrary revision of the file without
1027 opening a new filelog'''
1027 opening a new filelog'''
1028 return filectx(self._repo, self._path, fileid=fileid,
1028 return filectx(self._repo, self._path, fileid=fileid,
1029 filelog=self._filelog, changeid=changeid)
1029 filelog=self._filelog, changeid=changeid)
1030
1030
1031 def rawdata(self):
1031 def rawdata(self):
1032 return self._filelog.revision(self._filenode, raw=True)
1032 return self._filelog.revision(self._filenode, raw=True)
1033
1033
1034 def rawflags(self):
1034 def rawflags(self):
1035 """low-level revlog flags"""
1035 """low-level revlog flags"""
1036 return self._filelog.flags(self._filerev)
1036 return self._filelog.flags(self._filerev)
1037
1037
1038 def data(self):
1038 def data(self):
1039 try:
1039 try:
1040 return self._filelog.read(self._filenode)
1040 return self._filelog.read(self._filenode)
1041 except error.CensoredNodeError:
1041 except error.CensoredNodeError:
1042 if self._repo.ui.config("censor", "policy") == "ignore":
1042 if self._repo.ui.config("censor", "policy") == "ignore":
1043 return ""
1043 return ""
1044 raise error.Abort(_("censored node: %s") % short(self._filenode),
1044 raise error.Abort(_("censored node: %s") % short(self._filenode),
1045 hint=_("set censor.policy to ignore errors"))
1045 hint=_("set censor.policy to ignore errors"))
1046
1046
1047 def size(self):
1047 def size(self):
1048 return self._filelog.size(self._filerev)
1048 return self._filelog.size(self._filerev)
1049
1049
1050 @propertycache
1050 @propertycache
1051 def _copied(self):
1051 def _copied(self):
1052 """check if file was actually renamed in this changeset revision
1052 """check if file was actually renamed in this changeset revision
1053
1053
1054 If rename logged in file revision, we report copy for changeset only
1054 If rename logged in file revision, we report copy for changeset only
1055 if file revisions linkrev points back to the changeset in question
1055 if file revisions linkrev points back to the changeset in question
1056 or both changeset parents contain different file revisions.
1056 or both changeset parents contain different file revisions.
1057 """
1057 """
1058
1058
1059 renamed = self._filelog.renamed(self._filenode)
1059 renamed = self._filelog.renamed(self._filenode)
1060 if not renamed:
1060 if not renamed:
1061 return None
1061 return None
1062
1062
1063 if self.rev() == self.linkrev():
1063 if self.rev() == self.linkrev():
1064 return renamed
1064 return renamed
1065
1065
1066 name = self.path()
1066 name = self.path()
1067 fnode = self._filenode
1067 fnode = self._filenode
1068 for p in self._changectx.parents():
1068 for p in self._changectx.parents():
1069 try:
1069 try:
1070 if fnode == p.filenode(name):
1070 if fnode == p.filenode(name):
1071 return None
1071 return None
1072 except error.LookupError:
1072 except error.LookupError:
1073 pass
1073 pass
1074 return renamed
1074 return renamed
1075
1075
1076 def children(self):
1076 def children(self):
1077 # hard for renames
1077 # hard for renames
1078 c = self._filelog.children(self._filenode)
1078 c = self._filelog.children(self._filenode)
1079 return [filectx(self._repo, self._path, fileid=x,
1079 return [filectx(self._repo, self._path, fileid=x,
1080 filelog=self._filelog) for x in c]
1080 filelog=self._filelog) for x in c]
1081
1081
1082 class committablectx(basectx):
1082 class committablectx(basectx):
1083 """A committablectx object provides common functionality for a context that
1083 """A committablectx object provides common functionality for a context that
1084 wants the ability to commit, e.g. workingctx or memctx."""
1084 wants the ability to commit, e.g. workingctx or memctx."""
1085 def __init__(self, repo, text="", user=None, date=None, extra=None,
1085 def __init__(self, repo, text="", user=None, date=None, extra=None,
1086 changes=None):
1086 changes=None):
1087 super(committablectx, self).__init__(repo)
1087 super(committablectx, self).__init__(repo)
1088 self._rev = None
1088 self._rev = None
1089 self._node = None
1089 self._node = None
1090 self._text = text
1090 self._text = text
1091 if date:
1091 if date:
1092 self._date = dateutil.parsedate(date)
1092 self._date = dateutil.parsedate(date)
1093 if user:
1093 if user:
1094 self._user = user
1094 self._user = user
1095 if changes:
1095 if changes:
1096 self._status = changes
1096 self._status = changes
1097
1097
1098 self._extra = {}
1098 self._extra = {}
1099 if extra:
1099 if extra:
1100 self._extra = extra.copy()
1100 self._extra = extra.copy()
1101 if 'branch' not in self._extra:
1101 if 'branch' not in self._extra:
1102 try:
1102 try:
1103 branch = encoding.fromlocal(self._repo.dirstate.branch())
1103 branch = encoding.fromlocal(self._repo.dirstate.branch())
1104 except UnicodeDecodeError:
1104 except UnicodeDecodeError:
1105 raise error.Abort(_('branch name not in UTF-8!'))
1105 raise error.Abort(_('branch name not in UTF-8!'))
1106 self._extra['branch'] = branch
1106 self._extra['branch'] = branch
1107 if self._extra['branch'] == '':
1107 if self._extra['branch'] == '':
1108 self._extra['branch'] = 'default'
1108 self._extra['branch'] = 'default'
1109
1109
1110 def __bytes__(self):
1110 def __bytes__(self):
1111 return bytes(self._parents[0]) + "+"
1111 return bytes(self._parents[0]) + "+"
1112
1112
1113 __str__ = encoding.strmethod(__bytes__)
1113 __str__ = encoding.strmethod(__bytes__)
1114
1114
1115 def __nonzero__(self):
1115 def __nonzero__(self):
1116 return True
1116 return True
1117
1117
1118 __bool__ = __nonzero__
1118 __bool__ = __nonzero__
1119
1119
1120 def _buildflagfunc(self):
1120 def _buildflagfunc(self):
1121 # Create a fallback function for getting file flags when the
1121 # Create a fallback function for getting file flags when the
1122 # filesystem doesn't support them
1122 # filesystem doesn't support them
1123
1123
1124 copiesget = self._repo.dirstate.copies().get
1124 copiesget = self._repo.dirstate.copies().get
1125 parents = self.parents()
1125 parents = self.parents()
1126 if len(parents) < 2:
1126 if len(parents) < 2:
1127 # when we have one parent, it's easy: copy from parent
1127 # when we have one parent, it's easy: copy from parent
1128 man = parents[0].manifest()
1128 man = parents[0].manifest()
1129 def func(f):
1129 def func(f):
1130 f = copiesget(f, f)
1130 f = copiesget(f, f)
1131 return man.flags(f)
1131 return man.flags(f)
1132 else:
1132 else:
1133 # merges are tricky: we try to reconstruct the unstored
1133 # merges are tricky: we try to reconstruct the unstored
1134 # result from the merge (issue1802)
1134 # result from the merge (issue1802)
1135 p1, p2 = parents
1135 p1, p2 = parents
1136 pa = p1.ancestor(p2)
1136 pa = p1.ancestor(p2)
1137 m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest()
1137 m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest()
1138
1138
1139 def func(f):
1139 def func(f):
1140 f = copiesget(f, f) # may be wrong for merges with copies
1140 f = copiesget(f, f) # may be wrong for merges with copies
1141 fl1, fl2, fla = m1.flags(f), m2.flags(f), ma.flags(f)
1141 fl1, fl2, fla = m1.flags(f), m2.flags(f), ma.flags(f)
1142 if fl1 == fl2:
1142 if fl1 == fl2:
1143 return fl1
1143 return fl1
1144 if fl1 == fla:
1144 if fl1 == fla:
1145 return fl2
1145 return fl2
1146 if fl2 == fla:
1146 if fl2 == fla:
1147 return fl1
1147 return fl1
1148 return '' # punt for conflicts
1148 return '' # punt for conflicts
1149
1149
1150 return func
1150 return func
1151
1151
1152 @propertycache
1152 @propertycache
1153 def _flagfunc(self):
1153 def _flagfunc(self):
1154 return self._repo.dirstate.flagfunc(self._buildflagfunc)
1154 return self._repo.dirstate.flagfunc(self._buildflagfunc)
1155
1155
1156 @propertycache
1156 @propertycache
1157 def _status(self):
1157 def _status(self):
1158 return self._repo.status()
1158 return self._repo.status()
1159
1159
1160 @propertycache
1160 @propertycache
1161 def _user(self):
1161 def _user(self):
1162 return self._repo.ui.username()
1162 return self._repo.ui.username()
1163
1163
1164 @propertycache
1164 @propertycache
1165 def _date(self):
1165 def _date(self):
1166 ui = self._repo.ui
1166 ui = self._repo.ui
1167 date = ui.configdate('devel', 'default-date')
1167 date = ui.configdate('devel', 'default-date')
1168 if date is None:
1168 if date is None:
1169 date = dateutil.makedate()
1169 date = dateutil.makedate()
1170 return date
1170 return date
1171
1171
1172 def subrev(self, subpath):
1172 def subrev(self, subpath):
1173 return None
1173 return None
1174
1174
1175 def manifestnode(self):
1175 def manifestnode(self):
1176 return None
1176 return None
1177 def user(self):
1177 def user(self):
1178 return self._user or self._repo.ui.username()
1178 return self._user or self._repo.ui.username()
1179 def date(self):
1179 def date(self):
1180 return self._date
1180 return self._date
1181 def description(self):
1181 def description(self):
1182 return self._text
1182 return self._text
1183 def files(self):
1183 def files(self):
1184 return sorted(self._status.modified + self._status.added +
1184 return sorted(self._status.modified + self._status.added +
1185 self._status.removed)
1185 self._status.removed)
1186 def modified(self):
1186 def modified(self):
1187 return self._status.modified
1187 return self._status.modified
1188 def added(self):
1188 def added(self):
1189 return self._status.added
1189 return self._status.added
1190 def removed(self):
1190 def removed(self):
1191 return self._status.removed
1191 return self._status.removed
1192 def deleted(self):
1192 def deleted(self):
1193 return self._status.deleted
1193 return self._status.deleted
1194 @propertycache
1194 @propertycache
1195 def _copies(self):
1195 def _copies(self):
1196 p1copies = {}
1196 p1copies = {}
1197 p2copies = {}
1197 p2copies = {}
1198 parents = self._repo.dirstate.parents()
1198 parents = self._repo.dirstate.parents()
1199 p1manifest = self._repo[parents[0]].manifest()
1199 p1manifest = self._repo[parents[0]].manifest()
1200 p2manifest = self._repo[parents[1]].manifest()
1200 p2manifest = self._repo[parents[1]].manifest()
1201 narrowmatch = self._repo.narrowmatch()
1201 narrowmatch = self._repo.narrowmatch()
1202 for dst, src in self._repo.dirstate.copies().items():
1202 for dst, src in self._repo.dirstate.copies().items():
1203 if not narrowmatch(dst):
1203 if not narrowmatch(dst):
1204 continue
1204 continue
1205 if src in p1manifest:
1205 if src in p1manifest:
1206 p1copies[dst] = src
1206 p1copies[dst] = src
1207 elif src in p2manifest:
1207 elif src in p2manifest:
1208 p2copies[dst] = src
1208 p2copies[dst] = src
1209 return p1copies, p2copies
1209 return p1copies, p2copies
1210 def p1copies(self):
1210 def p1copies(self):
1211 return self._copies[0]
1211 return self._copies[0]
1212 def p2copies(self):
1212 def p2copies(self):
1213 return self._copies[1]
1213 return self._copies[1]
1214 def branch(self):
1214 def branch(self):
1215 return encoding.tolocal(self._extra['branch'])
1215 return encoding.tolocal(self._extra['branch'])
1216 def closesbranch(self):
1216 def closesbranch(self):
1217 return 'close' in self._extra
1217 return 'close' in self._extra
1218 def extra(self):
1218 def extra(self):
1219 return self._extra
1219 return self._extra
1220
1220
1221 def isinmemory(self):
1221 def isinmemory(self):
1222 return False
1222 return False
1223
1223
1224 def tags(self):
1224 def tags(self):
1225 return []
1225 return []
1226
1226
1227 def bookmarks(self):
1227 def bookmarks(self):
1228 b = []
1228 b = []
1229 for p in self.parents():
1229 for p in self.parents():
1230 b.extend(p.bookmarks())
1230 b.extend(p.bookmarks())
1231 return b
1231 return b
1232
1232
1233 def phase(self):
1233 def phase(self):
1234 phase = phases.draft # default phase to draft
1234 phase = phases.draft # default phase to draft
1235 for p in self.parents():
1235 for p in self.parents():
1236 phase = max(phase, p.phase())
1236 phase = max(phase, p.phase())
1237 return phase
1237 return phase
1238
1238
1239 def hidden(self):
1239 def hidden(self):
1240 return False
1240 return False
1241
1241
1242 def children(self):
1242 def children(self):
1243 return []
1243 return []
1244
1244
1245 def flags(self, path):
1245 def flags(self, path):
1246 if r'_manifest' in self.__dict__:
1246 if r'_manifest' in self.__dict__:
1247 try:
1247 try:
1248 return self._manifest.flags(path)
1248 return self._manifest.flags(path)
1249 except KeyError:
1249 except KeyError:
1250 return ''
1250 return ''
1251
1251
1252 try:
1252 try:
1253 return self._flagfunc(path)
1253 return self._flagfunc(path)
1254 except OSError:
1254 except OSError:
1255 return ''
1255 return ''
1256
1256
1257 def ancestor(self, c2):
1257 def ancestor(self, c2):
1258 """return the "best" ancestor context of self and c2"""
1258 """return the "best" ancestor context of self and c2"""
1259 return self._parents[0].ancestor(c2) # punt on two parents for now
1259 return self._parents[0].ancestor(c2) # punt on two parents for now
1260
1260
1261 def walk(self, match):
1261 def walk(self, match):
1262 '''Generates matching file names.'''
1262 '''Generates matching file names.'''
1263 return sorted(self._repo.dirstate.walk(self._repo.narrowmatch(match),
1263 return sorted(self._repo.dirstate.walk(self._repo.narrowmatch(match),
1264 subrepos=sorted(self.substate),
1264 subrepos=sorted(self.substate),
1265 unknown=True, ignored=False))
1265 unknown=True, ignored=False))
1266
1266
1267 def matches(self, match):
1267 def matches(self, match):
1268 match = self._repo.narrowmatch(match)
1268 match = self._repo.narrowmatch(match)
1269 ds = self._repo.dirstate
1269 ds = self._repo.dirstate
1270 return sorted(f for f in ds.matches(match) if ds[f] != 'r')
1270 return sorted(f for f in ds.matches(match) if ds[f] != 'r')
1271
1271
1272 def ancestors(self):
1272 def ancestors(self):
1273 for p in self._parents:
1273 for p in self._parents:
1274 yield p
1274 yield p
1275 for a in self._repo.changelog.ancestors(
1275 for a in self._repo.changelog.ancestors(
1276 [p.rev() for p in self._parents]):
1276 [p.rev() for p in self._parents]):
1277 yield self._repo[a]
1277 yield self._repo[a]
1278
1278
1279 def markcommitted(self, node):
1279 def markcommitted(self, node):
1280 """Perform post-commit cleanup necessary after committing this ctx
1280 """Perform post-commit cleanup necessary after committing this ctx
1281
1281
1282 Specifically, this updates backing stores this working context
1282 Specifically, this updates backing stores this working context
1283 wraps to reflect the fact that the changes reflected by this
1283 wraps to reflect the fact that the changes reflected by this
1284 workingctx have been committed. For example, it marks
1284 workingctx have been committed. For example, it marks
1285 modified and added files as normal in the dirstate.
1285 modified and added files as normal in the dirstate.
1286
1286
1287 """
1287 """
1288
1288
1289 with self._repo.dirstate.parentchange():
1289 with self._repo.dirstate.parentchange():
1290 for f in self.modified() + self.added():
1290 for f in self.modified() + self.added():
1291 self._repo.dirstate.normal(f)
1291 self._repo.dirstate.normal(f)
1292 for f in self.removed():
1292 for f in self.removed():
1293 self._repo.dirstate.drop(f)
1293 self._repo.dirstate.drop(f)
1294 self._repo.dirstate.setparents(node)
1294 self._repo.dirstate.setparents(node)
1295
1295
1296 # write changes out explicitly, because nesting wlock at
1296 # write changes out explicitly, because nesting wlock at
1297 # runtime may prevent 'wlock.release()' in 'repo.commit()'
1297 # runtime may prevent 'wlock.release()' in 'repo.commit()'
1298 # from immediately doing so for subsequent changing files
1298 # from immediately doing so for subsequent changing files
1299 self._repo.dirstate.write(self._repo.currenttransaction())
1299 self._repo.dirstate.write(self._repo.currenttransaction())
1300
1300
1301 def dirty(self, missing=False, merge=True, branch=True):
1301 def dirty(self, missing=False, merge=True, branch=True):
1302 return False
1302 return False
1303
1303
1304 class workingctx(committablectx):
1304 class workingctx(committablectx):
1305 """A workingctx object makes access to data related to
1305 """A workingctx object makes access to data related to
1306 the current working directory convenient.
1306 the current working directory convenient.
1307 date - any valid date string or (unixtime, offset), or None.
1307 date - any valid date string or (unixtime, offset), or None.
1308 user - username string, or None.
1308 user - username string, or None.
1309 extra - a dictionary of extra values, or None.
1309 extra - a dictionary of extra values, or None.
1310 changes - a list of file lists as returned by localrepo.status()
1310 changes - a list of file lists as returned by localrepo.status()
1311 or None to use the repository status.
1311 or None to use the repository status.
1312 """
1312 """
1313 def __init__(self, repo, text="", user=None, date=None, extra=None,
1313 def __init__(self, repo, text="", user=None, date=None, extra=None,
1314 changes=None):
1314 changes=None):
1315 super(workingctx, self).__init__(repo, text, user, date, extra, changes)
1315 super(workingctx, self).__init__(repo, text, user, date, extra, changes)
1316
1316
1317 def __iter__(self):
1317 def __iter__(self):
1318 d = self._repo.dirstate
1318 d = self._repo.dirstate
1319 for f in d:
1319 for f in d:
1320 if d[f] != 'r':
1320 if d[f] != 'r':
1321 yield f
1321 yield f
1322
1322
1323 def __contains__(self, key):
1323 def __contains__(self, key):
1324 return self._repo.dirstate[key] not in "?r"
1324 return self._repo.dirstate[key] not in "?r"
1325
1325
1326 def hex(self):
1326 def hex(self):
1327 return wdirhex
1327 return wdirhex
1328
1328
1329 @propertycache
1329 @propertycache
1330 def _parents(self):
1330 def _parents(self):
1331 p = self._repo.dirstate.parents()
1331 p = self._repo.dirstate.parents()
1332 if p[1] == nullid:
1332 if p[1] == nullid:
1333 p = p[:-1]
1333 p = p[:-1]
1334 # use unfiltered repo to delay/avoid loading obsmarkers
1334 # use unfiltered repo to delay/avoid loading obsmarkers
1335 unfi = self._repo.unfiltered()
1335 unfi = self._repo.unfiltered()
1336 return [changectx(self._repo, unfi.changelog.rev(n), n) for n in p]
1336 return [changectx(self._repo, unfi.changelog.rev(n), n) for n in p]
1337
1337
1338 def _fileinfo(self, path):
1338 def _fileinfo(self, path):
1339 # populate __dict__['_manifest'] as workingctx has no _manifestdelta
1339 # populate __dict__['_manifest'] as workingctx has no _manifestdelta
1340 self._manifest
1340 self._manifest
1341 return super(workingctx, self)._fileinfo(path)
1341 return super(workingctx, self)._fileinfo(path)
1342
1342
1343 def filectx(self, path, filelog=None):
1343 def filectx(self, path, filelog=None):
1344 """get a file context from the working directory"""
1344 """get a file context from the working directory"""
1345 return workingfilectx(self._repo, path, workingctx=self,
1345 return workingfilectx(self._repo, path, workingctx=self,
1346 filelog=filelog)
1346 filelog=filelog)
1347
1347
1348 def dirty(self, missing=False, merge=True, branch=True):
1348 def dirty(self, missing=False, merge=True, branch=True):
1349 "check whether a working directory is modified"
1349 "check whether a working directory is modified"
1350 # check subrepos first
1350 # check subrepos first
1351 for s in sorted(self.substate):
1351 for s in sorted(self.substate):
1352 if self.sub(s).dirty(missing=missing):
1352 if self.sub(s).dirty(missing=missing):
1353 return True
1353 return True
1354 # check current working dir
1354 # check current working dir
1355 return ((merge and self.p2()) or
1355 return ((merge and self.p2()) or
1356 (branch and self.branch() != self.p1().branch()) or
1356 (branch and self.branch() != self.p1().branch()) or
1357 self.modified() or self.added() or self.removed() or
1357 self.modified() or self.added() or self.removed() or
1358 (missing and self.deleted()))
1358 (missing and self.deleted()))
1359
1359
1360 def add(self, list, prefix=""):
1360 def add(self, list, prefix=""):
1361 with self._repo.wlock():
1361 with self._repo.wlock():
1362 ui, ds = self._repo.ui, self._repo.dirstate
1362 ui, ds = self._repo.ui, self._repo.dirstate
1363 uipath = lambda f: ds.pathto(pathutil.join(prefix, f))
1363 uipath = lambda f: ds.pathto(pathutil.join(prefix, f))
1364 rejected = []
1364 rejected = []
1365 lstat = self._repo.wvfs.lstat
1365 lstat = self._repo.wvfs.lstat
1366 for f in list:
1366 for f in list:
1367 # ds.pathto() returns an absolute file when this is invoked from
1367 # ds.pathto() returns an absolute file when this is invoked from
1368 # the keyword extension. That gets flagged as non-portable on
1368 # the keyword extension. That gets flagged as non-portable on
1369 # Windows, since it contains the drive letter and colon.
1369 # Windows, since it contains the drive letter and colon.
1370 scmutil.checkportable(ui, os.path.join(prefix, f))
1370 scmutil.checkportable(ui, os.path.join(prefix, f))
1371 try:
1371 try:
1372 st = lstat(f)
1372 st = lstat(f)
1373 except OSError:
1373 except OSError:
1374 ui.warn(_("%s does not exist!\n") % uipath(f))
1374 ui.warn(_("%s does not exist!\n") % uipath(f))
1375 rejected.append(f)
1375 rejected.append(f)
1376 continue
1376 continue
1377 limit = ui.configbytes('ui', 'large-file-limit')
1377 limit = ui.configbytes('ui', 'large-file-limit')
1378 if limit != 0 and st.st_size > limit:
1378 if limit != 0 and st.st_size > limit:
1379 ui.warn(_("%s: up to %d MB of RAM may be required "
1379 ui.warn(_("%s: up to %d MB of RAM may be required "
1380 "to manage this file\n"
1380 "to manage this file\n"
1381 "(use 'hg revert %s' to cancel the "
1381 "(use 'hg revert %s' to cancel the "
1382 "pending addition)\n")
1382 "pending addition)\n")
1383 % (f, 3 * st.st_size // 1000000, uipath(f)))
1383 % (f, 3 * st.st_size // 1000000, uipath(f)))
1384 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
1384 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
1385 ui.warn(_("%s not added: only files and symlinks "
1385 ui.warn(_("%s not added: only files and symlinks "
1386 "supported currently\n") % uipath(f))
1386 "supported currently\n") % uipath(f))
1387 rejected.append(f)
1387 rejected.append(f)
1388 elif ds[f] in 'amn':
1388 elif ds[f] in 'amn':
1389 ui.warn(_("%s already tracked!\n") % uipath(f))
1389 ui.warn(_("%s already tracked!\n") % uipath(f))
1390 elif ds[f] == 'r':
1390 elif ds[f] == 'r':
1391 ds.normallookup(f)
1391 ds.normallookup(f)
1392 else:
1392 else:
1393 ds.add(f)
1393 ds.add(f)
1394 return rejected
1394 return rejected
1395
1395
1396 def forget(self, files, prefix=""):
1396 def forget(self, files, prefix=""):
1397 with self._repo.wlock():
1397 with self._repo.wlock():
1398 ds = self._repo.dirstate
1398 ds = self._repo.dirstate
1399 uipath = lambda f: ds.pathto(pathutil.join(prefix, f))
1399 uipath = lambda f: ds.pathto(pathutil.join(prefix, f))
1400 rejected = []
1400 rejected = []
1401 for f in files:
1401 for f in files:
1402 if f not in ds:
1402 if f not in ds:
1403 self._repo.ui.warn(_("%s not tracked!\n") % uipath(f))
1403 self._repo.ui.warn(_("%s not tracked!\n") % uipath(f))
1404 rejected.append(f)
1404 rejected.append(f)
1405 elif ds[f] != 'a':
1405 elif ds[f] != 'a':
1406 ds.remove(f)
1406 ds.remove(f)
1407 else:
1407 else:
1408 ds.drop(f)
1408 ds.drop(f)
1409 return rejected
1409 return rejected
1410
1410
1411 def copy(self, source, dest):
1411 def copy(self, source, dest):
1412 try:
1412 try:
1413 st = self._repo.wvfs.lstat(dest)
1413 st = self._repo.wvfs.lstat(dest)
1414 except OSError as err:
1414 except OSError as err:
1415 if err.errno != errno.ENOENT:
1415 if err.errno != errno.ENOENT:
1416 raise
1416 raise
1417 self._repo.ui.warn(_("%s does not exist!\n")
1417 self._repo.ui.warn(_("%s does not exist!\n")
1418 % self._repo.dirstate.pathto(dest))
1418 % self._repo.dirstate.pathto(dest))
1419 return
1419 return
1420 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
1420 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
1421 self._repo.ui.warn(_("copy failed: %s is not a file or a "
1421 self._repo.ui.warn(_("copy failed: %s is not a file or a "
1422 "symbolic link\n")
1422 "symbolic link\n")
1423 % self._repo.dirstate.pathto(dest))
1423 % self._repo.dirstate.pathto(dest))
1424 else:
1424 else:
1425 with self._repo.wlock():
1425 with self._repo.wlock():
1426 ds = self._repo.dirstate
1426 ds = self._repo.dirstate
1427 if ds[dest] in '?':
1427 if ds[dest] in '?':
1428 ds.add(dest)
1428 ds.add(dest)
1429 elif ds[dest] in 'r':
1429 elif ds[dest] in 'r':
1430 ds.normallookup(dest)
1430 ds.normallookup(dest)
1431 ds.copy(source, dest)
1431 ds.copy(source, dest)
1432
1432
1433 def match(self, pats=None, include=None, exclude=None, default='glob',
1433 def match(self, pats=None, include=None, exclude=None, default='glob',
1434 listsubrepos=False, badfn=None):
1434 listsubrepos=False, badfn=None):
1435 r = self._repo
1435 r = self._repo
1436
1436
1437 # Only a case insensitive filesystem needs magic to translate user input
1437 # Only a case insensitive filesystem needs magic to translate user input
1438 # to actual case in the filesystem.
1438 # to actual case in the filesystem.
1439 icasefs = not util.fscasesensitive(r.root)
1439 icasefs = not util.fscasesensitive(r.root)
1440 return matchmod.match(r.root, r.getcwd(), pats, include, exclude,
1440 return matchmod.match(r.root, r.getcwd(), pats, include, exclude,
1441 default, auditor=r.auditor, ctx=self,
1441 default, auditor=r.auditor, ctx=self,
1442 listsubrepos=listsubrepos, badfn=badfn,
1442 listsubrepos=listsubrepos, badfn=badfn,
1443 icasefs=icasefs)
1443 icasefs=icasefs)
1444
1444
1445 def _filtersuspectsymlink(self, files):
1445 def _filtersuspectsymlink(self, files):
1446 if not files or self._repo.dirstate._checklink:
1446 if not files or self._repo.dirstate._checklink:
1447 return files
1447 return files
1448
1448
1449 # Symlink placeholders may get non-symlink-like contents
1449 # Symlink placeholders may get non-symlink-like contents
1450 # via user error or dereferencing by NFS or Samba servers,
1450 # via user error or dereferencing by NFS or Samba servers,
1451 # so we filter out any placeholders that don't look like a
1451 # so we filter out any placeholders that don't look like a
1452 # symlink
1452 # symlink
1453 sane = []
1453 sane = []
1454 for f in files:
1454 for f in files:
1455 if self.flags(f) == 'l':
1455 if self.flags(f) == 'l':
1456 d = self[f].data()
1456 d = self[f].data()
1457 if (d == '' or len(d) >= 1024 or '\n' in d
1457 if (d == '' or len(d) >= 1024 or '\n' in d
1458 or stringutil.binary(d)):
1458 or stringutil.binary(d)):
1459 self._repo.ui.debug('ignoring suspect symlink placeholder'
1459 self._repo.ui.debug('ignoring suspect symlink placeholder'
1460 ' "%s"\n' % f)
1460 ' "%s"\n' % f)
1461 continue
1461 continue
1462 sane.append(f)
1462 sane.append(f)
1463 return sane
1463 return sane
1464
1464
1465 def _checklookup(self, files):
1465 def _checklookup(self, files):
1466 # check for any possibly clean files
1466 # check for any possibly clean files
1467 if not files:
1467 if not files:
1468 return [], [], []
1468 return [], [], []
1469
1469
1470 modified = []
1470 modified = []
1471 deleted = []
1471 deleted = []
1472 fixup = []
1472 fixup = []
1473 pctx = self._parents[0]
1473 pctx = self._parents[0]
1474 # do a full compare of any files that might have changed
1474 # do a full compare of any files that might have changed
1475 for f in sorted(files):
1475 for f in sorted(files):
1476 try:
1476 try:
1477 # This will return True for a file that got replaced by a
1477 # This will return True for a file that got replaced by a
1478 # directory in the interim, but fixing that is pretty hard.
1478 # directory in the interim, but fixing that is pretty hard.
1479 if (f not in pctx or self.flags(f) != pctx.flags(f)
1479 if (f not in pctx or self.flags(f) != pctx.flags(f)
1480 or pctx[f].cmp(self[f])):
1480 or pctx[f].cmp(self[f])):
1481 modified.append(f)
1481 modified.append(f)
1482 else:
1482 else:
1483 fixup.append(f)
1483 fixup.append(f)
1484 except (IOError, OSError):
1484 except (IOError, OSError):
1485 # A file become inaccessible in between? Mark it as deleted,
1485 # A file become inaccessible in between? Mark it as deleted,
1486 # matching dirstate behavior (issue5584).
1486 # matching dirstate behavior (issue5584).
1487 # The dirstate has more complex behavior around whether a
1487 # The dirstate has more complex behavior around whether a
1488 # missing file matches a directory, etc, but we don't need to
1488 # missing file matches a directory, etc, but we don't need to
1489 # bother with that: if f has made it to this point, we're sure
1489 # bother with that: if f has made it to this point, we're sure
1490 # it's in the dirstate.
1490 # it's in the dirstate.
1491 deleted.append(f)
1491 deleted.append(f)
1492
1492
1493 return modified, deleted, fixup
1493 return modified, deleted, fixup
1494
1494
1495 def _poststatusfixup(self, status, fixup):
1495 def _poststatusfixup(self, status, fixup):
1496 """update dirstate for files that are actually clean"""
1496 """update dirstate for files that are actually clean"""
1497 poststatus = self._repo.postdsstatus()
1497 poststatus = self._repo.postdsstatus()
1498 if fixup or poststatus:
1498 if fixup or poststatus:
1499 try:
1499 try:
1500 oldid = self._repo.dirstate.identity()
1500 oldid = self._repo.dirstate.identity()
1501
1501
1502 # updating the dirstate is optional
1502 # updating the dirstate is optional
1503 # so we don't wait on the lock
1503 # so we don't wait on the lock
1504 # wlock can invalidate the dirstate, so cache normal _after_
1504 # wlock can invalidate the dirstate, so cache normal _after_
1505 # taking the lock
1505 # taking the lock
1506 with self._repo.wlock(False):
1506 with self._repo.wlock(False):
1507 if self._repo.dirstate.identity() == oldid:
1507 if self._repo.dirstate.identity() == oldid:
1508 if fixup:
1508 if fixup:
1509 normal = self._repo.dirstate.normal
1509 normal = self._repo.dirstate.normal
1510 for f in fixup:
1510 for f in fixup:
1511 normal(f)
1511 normal(f)
1512 # write changes out explicitly, because nesting
1512 # write changes out explicitly, because nesting
1513 # wlock at runtime may prevent 'wlock.release()'
1513 # wlock at runtime may prevent 'wlock.release()'
1514 # after this block from doing so for subsequent
1514 # after this block from doing so for subsequent
1515 # changing files
1515 # changing files
1516 tr = self._repo.currenttransaction()
1516 tr = self._repo.currenttransaction()
1517 self._repo.dirstate.write(tr)
1517 self._repo.dirstate.write(tr)
1518
1518
1519 if poststatus:
1519 if poststatus:
1520 for ps in poststatus:
1520 for ps in poststatus:
1521 ps(self, status)
1521 ps(self, status)
1522 else:
1522 else:
1523 # in this case, writing changes out breaks
1523 # in this case, writing changes out breaks
1524 # consistency, because .hg/dirstate was
1524 # consistency, because .hg/dirstate was
1525 # already changed simultaneously after last
1525 # already changed simultaneously after last
1526 # caching (see also issue5584 for detail)
1526 # caching (see also issue5584 for detail)
1527 self._repo.ui.debug('skip updating dirstate: '
1527 self._repo.ui.debug('skip updating dirstate: '
1528 'identity mismatch\n')
1528 'identity mismatch\n')
1529 except error.LockError:
1529 except error.LockError:
1530 pass
1530 pass
1531 finally:
1531 finally:
1532 # Even if the wlock couldn't be grabbed, clear out the list.
1532 # Even if the wlock couldn't be grabbed, clear out the list.
1533 self._repo.clearpostdsstatus()
1533 self._repo.clearpostdsstatus()
1534
1534
1535 def _dirstatestatus(self, match, ignored=False, clean=False, unknown=False):
1535 def _dirstatestatus(self, match, ignored=False, clean=False, unknown=False):
1536 '''Gets the status from the dirstate -- internal use only.'''
1536 '''Gets the status from the dirstate -- internal use only.'''
1537 subrepos = []
1537 subrepos = []
1538 if '.hgsub' in self:
1538 if '.hgsub' in self:
1539 subrepos = sorted(self.substate)
1539 subrepos = sorted(self.substate)
1540 cmp, s = self._repo.dirstate.status(match, subrepos, ignored=ignored,
1540 cmp, s = self._repo.dirstate.status(match, subrepos, ignored=ignored,
1541 clean=clean, unknown=unknown)
1541 clean=clean, unknown=unknown)
1542
1542
1543 # check for any possibly clean files
1543 # check for any possibly clean files
1544 fixup = []
1544 fixup = []
1545 if cmp:
1545 if cmp:
1546 modified2, deleted2, fixup = self._checklookup(cmp)
1546 modified2, deleted2, fixup = self._checklookup(cmp)
1547 s.modified.extend(modified2)
1547 s.modified.extend(modified2)
1548 s.deleted.extend(deleted2)
1548 s.deleted.extend(deleted2)
1549
1549
1550 if fixup and clean:
1550 if fixup and clean:
1551 s.clean.extend(fixup)
1551 s.clean.extend(fixup)
1552
1552
1553 self._poststatusfixup(s, fixup)
1553 self._poststatusfixup(s, fixup)
1554
1554
1555 if match.always():
1555 if match.always():
1556 # cache for performance
1556 # cache for performance
1557 if s.unknown or s.ignored or s.clean:
1557 if s.unknown or s.ignored or s.clean:
1558 # "_status" is cached with list*=False in the normal route
1558 # "_status" is cached with list*=False in the normal route
1559 self._status = scmutil.status(s.modified, s.added, s.removed,
1559 self._status = scmutil.status(s.modified, s.added, s.removed,
1560 s.deleted, [], [], [])
1560 s.deleted, [], [], [])
1561 else:
1561 else:
1562 self._status = s
1562 self._status = s
1563
1563
1564 return s
1564 return s
1565
1565
1566 @propertycache
1566 @propertycache
1567 def _manifest(self):
1567 def _manifest(self):
1568 """generate a manifest corresponding to the values in self._status
1568 """generate a manifest corresponding to the values in self._status
1569
1569
1570 This reuse the file nodeid from parent, but we use special node
1570 This reuse the file nodeid from parent, but we use special node
1571 identifiers for added and modified files. This is used by manifests
1571 identifiers for added and modified files. This is used by manifests
1572 merge to see that files are different and by update logic to avoid
1572 merge to see that files are different and by update logic to avoid
1573 deleting newly added files.
1573 deleting newly added files.
1574 """
1574 """
1575 return self._buildstatusmanifest(self._status)
1575 return self._buildstatusmanifest(self._status)
1576
1576
1577 def _buildstatusmanifest(self, status):
1577 def _buildstatusmanifest(self, status):
1578 """Builds a manifest that includes the given status results."""
1578 """Builds a manifest that includes the given status results."""
1579 parents = self.parents()
1579 parents = self.parents()
1580
1580
1581 man = parents[0].manifest().copy()
1581 man = parents[0].manifest().copy()
1582
1582
1583 ff = self._flagfunc
1583 ff = self._flagfunc
1584 for i, l in ((addednodeid, status.added),
1584 for i, l in ((addednodeid, status.added),
1585 (modifiednodeid, status.modified)):
1585 (modifiednodeid, status.modified)):
1586 for f in l:
1586 for f in l:
1587 man[f] = i
1587 man[f] = i
1588 try:
1588 try:
1589 man.setflag(f, ff(f))
1589 man.setflag(f, ff(f))
1590 except OSError:
1590 except OSError:
1591 pass
1591 pass
1592
1592
1593 for f in status.deleted + status.removed:
1593 for f in status.deleted + status.removed:
1594 if f in man:
1594 if f in man:
1595 del man[f]
1595 del man[f]
1596
1596
1597 return man
1597 return man
1598
1598
1599 def _buildstatus(self, other, s, match, listignored, listclean,
1599 def _buildstatus(self, other, s, match, listignored, listclean,
1600 listunknown):
1600 listunknown):
1601 """build a status with respect to another context
1601 """build a status with respect to another context
1602
1602
1603 This includes logic for maintaining the fast path of status when
1603 This includes logic for maintaining the fast path of status when
1604 comparing the working directory against its parent, which is to skip
1604 comparing the working directory against its parent, which is to skip
1605 building a new manifest if self (working directory) is not comparing
1605 building a new manifest if self (working directory) is not comparing
1606 against its parent (repo['.']).
1606 against its parent (repo['.']).
1607 """
1607 """
1608 s = self._dirstatestatus(match, listignored, listclean, listunknown)
1608 s = self._dirstatestatus(match, listignored, listclean, listunknown)
1609 # Filter out symlinks that, in the case of FAT32 and NTFS filesystems,
1609 # Filter out symlinks that, in the case of FAT32 and NTFS filesystems,
1610 # might have accidentally ended up with the entire contents of the file
1610 # might have accidentally ended up with the entire contents of the file
1611 # they are supposed to be linking to.
1611 # they are supposed to be linking to.
1612 s.modified[:] = self._filtersuspectsymlink(s.modified)
1612 s.modified[:] = self._filtersuspectsymlink(s.modified)
1613 if other != self._repo['.']:
1613 if other != self._repo['.']:
1614 s = super(workingctx, self)._buildstatus(other, s, match,
1614 s = super(workingctx, self)._buildstatus(other, s, match,
1615 listignored, listclean,
1615 listignored, listclean,
1616 listunknown)
1616 listunknown)
1617 return s
1617 return s
1618
1618
1619 def _matchstatus(self, other, match):
1619 def _matchstatus(self, other, match):
1620 """override the match method with a filter for directory patterns
1620 """override the match method with a filter for directory patterns
1621
1621
1622 We use inheritance to customize the match.bad method only in cases of
1622 We use inheritance to customize the match.bad method only in cases of
1623 workingctx since it belongs only to the working directory when
1623 workingctx since it belongs only to the working directory when
1624 comparing against the parent changeset.
1624 comparing against the parent changeset.
1625
1625
1626 If we aren't comparing against the working directory's parent, then we
1626 If we aren't comparing against the working directory's parent, then we
1627 just use the default match object sent to us.
1627 just use the default match object sent to us.
1628 """
1628 """
1629 if other != self._repo['.']:
1629 if other != self._repo['.']:
1630 def bad(f, msg):
1630 def bad(f, msg):
1631 # 'f' may be a directory pattern from 'match.files()',
1631 # 'f' may be a directory pattern from 'match.files()',
1632 # so 'f not in ctx1' is not enough
1632 # so 'f not in ctx1' is not enough
1633 if f not in other and not other.hasdir(f):
1633 if f not in other and not other.hasdir(f):
1634 self._repo.ui.warn('%s: %s\n' %
1634 self._repo.ui.warn('%s: %s\n' %
1635 (self._repo.dirstate.pathto(f), msg))
1635 (self._repo.dirstate.pathto(f), msg))
1636 match.bad = bad
1636 match.bad = bad
1637 return match
1637 return match
1638
1638
1639 def markcommitted(self, node):
1639 def markcommitted(self, node):
1640 super(workingctx, self).markcommitted(node)
1640 super(workingctx, self).markcommitted(node)
1641
1641
1642 sparse.aftercommit(self._repo, node)
1642 sparse.aftercommit(self._repo, node)
1643
1643
1644 class committablefilectx(basefilectx):
1644 class committablefilectx(basefilectx):
1645 """A committablefilectx provides common functionality for a file context
1645 """A committablefilectx provides common functionality for a file context
1646 that wants the ability to commit, e.g. workingfilectx or memfilectx."""
1646 that wants the ability to commit, e.g. workingfilectx or memfilectx."""
1647 def __init__(self, repo, path, filelog=None, ctx=None):
1647 def __init__(self, repo, path, filelog=None, ctx=None):
1648 self._repo = repo
1648 self._repo = repo
1649 self._path = path
1649 self._path = path
1650 self._changeid = None
1650 self._changeid = None
1651 self._filerev = self._filenode = None
1651 self._filerev = self._filenode = None
1652
1652
1653 if filelog is not None:
1653 if filelog is not None:
1654 self._filelog = filelog
1654 self._filelog = filelog
1655 if ctx:
1655 if ctx:
1656 self._changectx = ctx
1656 self._changectx = ctx
1657
1657
1658 def __nonzero__(self):
1658 def __nonzero__(self):
1659 return True
1659 return True
1660
1660
1661 __bool__ = __nonzero__
1661 __bool__ = __nonzero__
1662
1662
1663 def linkrev(self):
1663 def linkrev(self):
1664 # linked to self._changectx no matter if file is modified or not
1664 # linked to self._changectx no matter if file is modified or not
1665 return self.rev()
1665 return self.rev()
1666
1666
1667 def renamed(self):
1667 def renamed(self):
1668 path = self.copysource()
1668 path = self.copysource()
1669 if not path:
1669 if not path:
1670 return None
1670 return None
1671 return path, self._changectx._parents[0]._manifest.get(path, nullid)
1671 return path, self._changectx._parents[0]._manifest.get(path, nullid)
1672
1672
1673 def parents(self):
1673 def parents(self):
1674 '''return parent filectxs, following copies if necessary'''
1674 '''return parent filectxs, following copies if necessary'''
1675 def filenode(ctx, path):
1675 def filenode(ctx, path):
1676 return ctx._manifest.get(path, nullid)
1676 return ctx._manifest.get(path, nullid)
1677
1677
1678 path = self._path
1678 path = self._path
1679 fl = self._filelog
1679 fl = self._filelog
1680 pcl = self._changectx._parents
1680 pcl = self._changectx._parents
1681 renamed = self.renamed()
1681 renamed = self.renamed()
1682
1682
1683 if renamed:
1683 if renamed:
1684 pl = [renamed + (None,)]
1684 pl = [renamed + (None,)]
1685 else:
1685 else:
1686 pl = [(path, filenode(pcl[0], path), fl)]
1686 pl = [(path, filenode(pcl[0], path), fl)]
1687
1687
1688 for pc in pcl[1:]:
1688 for pc in pcl[1:]:
1689 pl.append((path, filenode(pc, path), fl))
1689 pl.append((path, filenode(pc, path), fl))
1690
1690
1691 return [self._parentfilectx(p, fileid=n, filelog=l)
1691 return [self._parentfilectx(p, fileid=n, filelog=l)
1692 for p, n, l in pl if n != nullid]
1692 for p, n, l in pl if n != nullid]
1693
1693
1694 def children(self):
1694 def children(self):
1695 return []
1695 return []
1696
1696
1697 class workingfilectx(committablefilectx):
1697 class workingfilectx(committablefilectx):
1698 """A workingfilectx object makes access to data related to a particular
1698 """A workingfilectx object makes access to data related to a particular
1699 file in the working directory convenient."""
1699 file in the working directory convenient."""
1700 def __init__(self, repo, path, filelog=None, workingctx=None):
1700 def __init__(self, repo, path, filelog=None, workingctx=None):
1701 super(workingfilectx, self).__init__(repo, path, filelog, workingctx)
1701 super(workingfilectx, self).__init__(repo, path, filelog, workingctx)
1702
1702
1703 @propertycache
1703 @propertycache
1704 def _changectx(self):
1704 def _changectx(self):
1705 return workingctx(self._repo)
1705 return workingctx(self._repo)
1706
1706
1707 def data(self):
1707 def data(self):
1708 return self._repo.wread(self._path)
1708 return self._repo.wread(self._path)
1709 def copysource(self):
1709 def copysource(self):
1710 return self._repo.dirstate.copied(self._path)
1710 return self._repo.dirstate.copied(self._path)
1711
1711
1712 def size(self):
1712 def size(self):
1713 return self._repo.wvfs.lstat(self._path).st_size
1713 return self._repo.wvfs.lstat(self._path).st_size
1714 def date(self):
1714 def date(self):
1715 t, tz = self._changectx.date()
1715 t, tz = self._changectx.date()
1716 try:
1716 try:
1717 return (self._repo.wvfs.lstat(self._path)[stat.ST_MTIME], tz)
1717 return (self._repo.wvfs.lstat(self._path)[stat.ST_MTIME], tz)
1718 except OSError as err:
1718 except OSError as err:
1719 if err.errno != errno.ENOENT:
1719 if err.errno != errno.ENOENT:
1720 raise
1720 raise
1721 return (t, tz)
1721 return (t, tz)
1722
1722
1723 def exists(self):
1723 def exists(self):
1724 return self._repo.wvfs.exists(self._path)
1724 return self._repo.wvfs.exists(self._path)
1725
1725
1726 def lexists(self):
1726 def lexists(self):
1727 return self._repo.wvfs.lexists(self._path)
1727 return self._repo.wvfs.lexists(self._path)
1728
1728
1729 def audit(self):
1729 def audit(self):
1730 return self._repo.wvfs.audit(self._path)
1730 return self._repo.wvfs.audit(self._path)
1731
1731
1732 def cmp(self, fctx):
1732 def cmp(self, fctx):
1733 """compare with other file context
1733 """compare with other file context
1734
1734
1735 returns True if different than fctx.
1735 returns True if different than fctx.
1736 """
1736 """
1737 # fctx should be a filectx (not a workingfilectx)
1737 # fctx should be a filectx (not a workingfilectx)
1738 # invert comparison to reuse the same code path
1738 # invert comparison to reuse the same code path
1739 return fctx.cmp(self)
1739 return fctx.cmp(self)
1740
1740
1741 def remove(self, ignoremissing=False):
1741 def remove(self, ignoremissing=False):
1742 """wraps unlink for a repo's working directory"""
1742 """wraps unlink for a repo's working directory"""
1743 rmdir = self._repo.ui.configbool('experimental', 'removeemptydirs')
1743 rmdir = self._repo.ui.configbool('experimental', 'removeemptydirs')
1744 self._repo.wvfs.unlinkpath(self._path, ignoremissing=ignoremissing,
1744 self._repo.wvfs.unlinkpath(self._path, ignoremissing=ignoremissing,
1745 rmdir=rmdir)
1745 rmdir=rmdir)
1746
1746
1747 def write(self, data, flags, backgroundclose=False, **kwargs):
1747 def write(self, data, flags, backgroundclose=False, **kwargs):
1748 """wraps repo.wwrite"""
1748 """wraps repo.wwrite"""
1749 self._repo.wwrite(self._path, data, flags,
1749 self._repo.wwrite(self._path, data, flags,
1750 backgroundclose=backgroundclose,
1750 backgroundclose=backgroundclose,
1751 **kwargs)
1751 **kwargs)
1752
1752
1753 def markcopied(self, src):
1753 def markcopied(self, src):
1754 """marks this file a copy of `src`"""
1754 """marks this file a copy of `src`"""
1755 if self._repo.dirstate[self._path] in "nma":
1755 if self._repo.dirstate[self._path] in "nma":
1756 self._repo.dirstate.copy(src, self._path)
1756 self._repo.dirstate.copy(src, self._path)
1757
1757
1758 def clearunknown(self):
1758 def clearunknown(self):
1759 """Removes conflicting items in the working directory so that
1759 """Removes conflicting items in the working directory so that
1760 ``write()`` can be called successfully.
1760 ``write()`` can be called successfully.
1761 """
1761 """
1762 wvfs = self._repo.wvfs
1762 wvfs = self._repo.wvfs
1763 f = self._path
1763 f = self._path
1764 wvfs.audit(f)
1764 wvfs.audit(f)
1765 if self._repo.ui.configbool('experimental', 'merge.checkpathconflicts'):
1765 if self._repo.ui.configbool('experimental', 'merge.checkpathconflicts'):
1766 # remove files under the directory as they should already be
1766 # remove files under the directory as they should already be
1767 # warned and backed up
1767 # warned and backed up
1768 if wvfs.isdir(f) and not wvfs.islink(f):
1768 if wvfs.isdir(f) and not wvfs.islink(f):
1769 wvfs.rmtree(f, forcibly=True)
1769 wvfs.rmtree(f, forcibly=True)
1770 for p in reversed(list(util.finddirs(f))):
1770 for p in reversed(list(util.finddirs(f))):
1771 if wvfs.isfileorlink(p):
1771 if wvfs.isfileorlink(p):
1772 wvfs.unlink(p)
1772 wvfs.unlink(p)
1773 break
1773 break
1774 else:
1774 else:
1775 # don't remove files if path conflicts are not processed
1775 # don't remove files if path conflicts are not processed
1776 if wvfs.isdir(f) and not wvfs.islink(f):
1776 if wvfs.isdir(f) and not wvfs.islink(f):
1777 wvfs.removedirs(f)
1777 wvfs.removedirs(f)
1778
1778
1779 def setflags(self, l, x):
1779 def setflags(self, l, x):
1780 self._repo.wvfs.setflags(self._path, l, x)
1780 self._repo.wvfs.setflags(self._path, l, x)
1781
1781
1782 class overlayworkingctx(committablectx):
1782 class overlayworkingctx(committablectx):
1783 """Wraps another mutable context with a write-back cache that can be
1783 """Wraps another mutable context with a write-back cache that can be
1784 converted into a commit context.
1784 converted into a commit context.
1785
1785
1786 self._cache[path] maps to a dict with keys: {
1786 self._cache[path] maps to a dict with keys: {
1787 'exists': bool?
1787 'exists': bool?
1788 'date': date?
1788 'date': date?
1789 'data': str?
1789 'data': str?
1790 'flags': str?
1790 'flags': str?
1791 'copied': str? (path or None)
1791 'copied': str? (path or None)
1792 }
1792 }
1793 If `exists` is True, `flags` must be non-None and 'date' is non-None. If it
1793 If `exists` is True, `flags` must be non-None and 'date' is non-None. If it
1794 is `False`, the file was deleted.
1794 is `False`, the file was deleted.
1795 """
1795 """
1796
1796
1797 def __init__(self, repo):
1797 def __init__(self, repo):
1798 super(overlayworkingctx, self).__init__(repo)
1798 super(overlayworkingctx, self).__init__(repo)
1799 self.clean()
1799 self.clean()
1800
1800
1801 def setbase(self, wrappedctx):
1801 def setbase(self, wrappedctx):
1802 self._wrappedctx = wrappedctx
1802 self._wrappedctx = wrappedctx
1803 self._parents = [wrappedctx]
1803 self._parents = [wrappedctx]
1804 # Drop old manifest cache as it is now out of date.
1804 # Drop old manifest cache as it is now out of date.
1805 # This is necessary when, e.g., rebasing several nodes with one
1805 # This is necessary when, e.g., rebasing several nodes with one
1806 # ``overlayworkingctx`` (e.g. with --collapse).
1806 # ``overlayworkingctx`` (e.g. with --collapse).
1807 util.clearcachedproperty(self, '_manifest')
1807 util.clearcachedproperty(self, '_manifest')
1808
1808
1809 def data(self, path):
1809 def data(self, path):
1810 if self.isdirty(path):
1810 if self.isdirty(path):
1811 if self._cache[path]['exists']:
1811 if self._cache[path]['exists']:
1812 if self._cache[path]['data']:
1812 if self._cache[path]['data']:
1813 return self._cache[path]['data']
1813 return self._cache[path]['data']
1814 else:
1814 else:
1815 # Must fallback here, too, because we only set flags.
1815 # Must fallback here, too, because we only set flags.
1816 return self._wrappedctx[path].data()
1816 return self._wrappedctx[path].data()
1817 else:
1817 else:
1818 raise error.ProgrammingError("No such file or directory: %s" %
1818 raise error.ProgrammingError("No such file or directory: %s" %
1819 path)
1819 path)
1820 else:
1820 else:
1821 return self._wrappedctx[path].data()
1821 return self._wrappedctx[path].data()
1822
1822
1823 @propertycache
1823 @propertycache
1824 def _manifest(self):
1824 def _manifest(self):
1825 parents = self.parents()
1825 parents = self.parents()
1826 man = parents[0].manifest().copy()
1826 man = parents[0].manifest().copy()
1827
1827
1828 flag = self._flagfunc
1828 flag = self._flagfunc
1829 for path in self.added():
1829 for path in self.added():
1830 man[path] = addednodeid
1830 man[path] = addednodeid
1831 man.setflag(path, flag(path))
1831 man.setflag(path, flag(path))
1832 for path in self.modified():
1832 for path in self.modified():
1833 man[path] = modifiednodeid
1833 man[path] = modifiednodeid
1834 man.setflag(path, flag(path))
1834 man.setflag(path, flag(path))
1835 for path in self.removed():
1835 for path in self.removed():
1836 del man[path]
1836 del man[path]
1837 return man
1837 return man
1838
1838
1839 @propertycache
1839 @propertycache
1840 def _flagfunc(self):
1840 def _flagfunc(self):
1841 def f(path):
1841 def f(path):
1842 return self._cache[path]['flags']
1842 return self._cache[path]['flags']
1843 return f
1843 return f
1844
1844
1845 def files(self):
1845 def files(self):
1846 return sorted(self.added() + self.modified() + self.removed())
1846 return sorted(self.added() + self.modified() + self.removed())
1847
1847
1848 def modified(self):
1848 def modified(self):
1849 return [f for f in self._cache.keys() if self._cache[f]['exists'] and
1849 return [f for f in self._cache.keys() if self._cache[f]['exists'] and
1850 self._existsinparent(f)]
1850 self._existsinparent(f)]
1851
1851
1852 def added(self):
1852 def added(self):
1853 return [f for f in self._cache.keys() if self._cache[f]['exists'] and
1853 return [f for f in self._cache.keys() if self._cache[f]['exists'] and
1854 not self._existsinparent(f)]
1854 not self._existsinparent(f)]
1855
1855
1856 def removed(self):
1856 def removed(self):
1857 return [f for f in self._cache.keys() if
1857 return [f for f in self._cache.keys() if
1858 not self._cache[f]['exists'] and self._existsinparent(f)]
1858 not self._cache[f]['exists'] and self._existsinparent(f)]
1859
1859
1860 def p1copies(self):
1860 def p1copies(self):
1861 copies = self._repo._wrappedctx.p1copies().copy()
1861 copies = self._repo._wrappedctx.p1copies().copy()
1862 narrowmatch = self._repo.narrowmatch()
1862 narrowmatch = self._repo.narrowmatch()
1863 for f in self._cache.keys():
1863 for f in self._cache.keys():
1864 if not narrowmatch(f):
1864 if not narrowmatch(f):
1865 continue
1865 continue
1866 copies.pop(f, None) # delete if it exists
1866 copies.pop(f, None) # delete if it exists
1867 source = self._cache[f]['copied']
1867 source = self._cache[f]['copied']
1868 if source:
1868 if source:
1869 copies[f] = source
1869 copies[f] = source
1870 return copies
1870 return copies
1871
1871
1872 def p2copies(self):
1872 def p2copies(self):
1873 copies = self._repo._wrappedctx.p2copies().copy()
1873 copies = self._repo._wrappedctx.p2copies().copy()
1874 narrowmatch = self._repo.narrowmatch()
1874 narrowmatch = self._repo.narrowmatch()
1875 for f in self._cache.keys():
1875 for f in self._cache.keys():
1876 if not narrowmatch(f):
1876 if not narrowmatch(f):
1877 continue
1877 continue
1878 copies.pop(f, None) # delete if it exists
1878 copies.pop(f, None) # delete if it exists
1879 source = self._cache[f]['copied']
1879 source = self._cache[f]['copied']
1880 if source:
1880 if source:
1881 copies[f] = source
1881 copies[f] = source
1882 return copies
1882 return copies
1883
1883
1884 def isinmemory(self):
1884 def isinmemory(self):
1885 return True
1885 return True
1886
1886
1887 def filedate(self, path):
1887 def filedate(self, path):
1888 if self.isdirty(path):
1888 if self.isdirty(path):
1889 return self._cache[path]['date']
1889 return self._cache[path]['date']
1890 else:
1890 else:
1891 return self._wrappedctx[path].date()
1891 return self._wrappedctx[path].date()
1892
1892
1893 def markcopied(self, path, origin):
1893 def markcopied(self, path, origin):
1894 self._markdirty(path, exists=True, date=self.filedate(path),
1894 self._markdirty(path, exists=True, date=self.filedate(path),
1895 flags=self.flags(path), copied=origin)
1895 flags=self.flags(path), copied=origin)
1896
1896
1897 def copydata(self, path):
1897 def copydata(self, path):
1898 if self.isdirty(path):
1898 if self.isdirty(path):
1899 return self._cache[path]['copied']
1899 return self._cache[path]['copied']
1900 else:
1900 else:
1901 raise error.ProgrammingError('copydata() called on clean context')
1901 raise error.ProgrammingError('copydata() called on clean context')
1902
1902
1903 def flags(self, path):
1903 def flags(self, path):
1904 if self.isdirty(path):
1904 if self.isdirty(path):
1905 if self._cache[path]['exists']:
1905 if self._cache[path]['exists']:
1906 return self._cache[path]['flags']
1906 return self._cache[path]['flags']
1907 else:
1907 else:
1908 raise error.ProgrammingError("No such file or directory: %s" %
1908 raise error.ProgrammingError("No such file or directory: %s" %
1909 self._path)
1909 self._path)
1910 else:
1910 else:
1911 return self._wrappedctx[path].flags()
1911 return self._wrappedctx[path].flags()
1912
1912
1913 def __contains__(self, key):
1913 def __contains__(self, key):
1914 if key in self._cache:
1914 if key in self._cache:
1915 return self._cache[key]['exists']
1915 return self._cache[key]['exists']
1916 return key in self.p1()
1916 return key in self.p1()
1917
1917
1918 def _existsinparent(self, path):
1918 def _existsinparent(self, path):
1919 try:
1919 try:
1920 # ``commitctx` raises a ``ManifestLookupError`` if a path does not
1920 # ``commitctx` raises a ``ManifestLookupError`` if a path does not
1921 # exist, unlike ``workingctx``, which returns a ``workingfilectx``
1921 # exist, unlike ``workingctx``, which returns a ``workingfilectx``
1922 # with an ``exists()`` function.
1922 # with an ``exists()`` function.
1923 self._wrappedctx[path]
1923 self._wrappedctx[path]
1924 return True
1924 return True
1925 except error.ManifestLookupError:
1925 except error.ManifestLookupError:
1926 return False
1926 return False
1927
1927
1928 def _auditconflicts(self, path):
1928 def _auditconflicts(self, path):
1929 """Replicates conflict checks done by wvfs.write().
1929 """Replicates conflict checks done by wvfs.write().
1930
1930
1931 Since we never write to the filesystem and never call `applyupdates` in
1931 Since we never write to the filesystem and never call `applyupdates` in
1932 IMM, we'll never check that a path is actually writable -- e.g., because
1932 IMM, we'll never check that a path is actually writable -- e.g., because
1933 it adds `a/foo`, but `a` is actually a file in the other commit.
1933 it adds `a/foo`, but `a` is actually a file in the other commit.
1934 """
1934 """
1935 def fail(path, component):
1935 def fail(path, component):
1936 # p1() is the base and we're receiving "writes" for p2()'s
1936 # p1() is the base and we're receiving "writes" for p2()'s
1937 # files.
1937 # files.
1938 if 'l' in self.p1()[component].flags():
1938 if 'l' in self.p1()[component].flags():
1939 raise error.Abort("error: %s conflicts with symlink %s "
1939 raise error.Abort("error: %s conflicts with symlink %s "
1940 "in %d." % (path, component,
1940 "in %d." % (path, component,
1941 self.p1().rev()))
1941 self.p1().rev()))
1942 else:
1942 else:
1943 raise error.Abort("error: '%s' conflicts with file '%s' in "
1943 raise error.Abort("error: '%s' conflicts with file '%s' in "
1944 "%d." % (path, component,
1944 "%d." % (path, component,
1945 self.p1().rev()))
1945 self.p1().rev()))
1946
1946
1947 # Test that each new directory to be created to write this path from p2
1947 # Test that each new directory to be created to write this path from p2
1948 # is not a file in p1.
1948 # is not a file in p1.
1949 components = path.split('/')
1949 components = path.split('/')
1950 for i in pycompat.xrange(len(components)):
1950 for i in pycompat.xrange(len(components)):
1951 component = "/".join(components[0:i])
1951 component = "/".join(components[0:i])
1952 if component in self:
1952 if component in self:
1953 fail(path, component)
1953 fail(path, component)
1954
1954
1955 # Test the other direction -- that this path from p2 isn't a directory
1955 # Test the other direction -- that this path from p2 isn't a directory
1956 # in p1 (test that p1 doesn't have any paths matching `path/*`).
1956 # in p1 (test that p1 doesn't have any paths matching `path/*`).
1957 match = self.match(include=[path + '/'], default=b'path')
1957 match = self.match(include=[path + '/'], default=b'path')
1958 matches = self.p1().manifest().matches(match)
1958 matches = self.p1().manifest().matches(match)
1959 mfiles = matches.keys()
1959 mfiles = matches.keys()
1960 if len(mfiles) > 0:
1960 if len(mfiles) > 0:
1961 if len(mfiles) == 1 and mfiles[0] == path:
1961 if len(mfiles) == 1 and mfiles[0] == path:
1962 return
1962 return
1963 # omit the files which are deleted in current IMM wctx
1963 # omit the files which are deleted in current IMM wctx
1964 mfiles = [m for m in mfiles if m in self]
1964 mfiles = [m for m in mfiles if m in self]
1965 if not mfiles:
1965 if not mfiles:
1966 return
1966 return
1967 raise error.Abort("error: file '%s' cannot be written because "
1967 raise error.Abort("error: file '%s' cannot be written because "
1968 " '%s/' is a folder in %s (containing %d "
1968 " '%s/' is a directory in %s (containing %d "
1969 "entries: %s)"
1969 "entries: %s)"
1970 % (path, path, self.p1(), len(mfiles),
1970 % (path, path, self.p1(), len(mfiles),
1971 ', '.join(mfiles)))
1971 ', '.join(mfiles)))
1972
1972
1973 def write(self, path, data, flags='', **kwargs):
1973 def write(self, path, data, flags='', **kwargs):
1974 if data is None:
1974 if data is None:
1975 raise error.ProgrammingError("data must be non-None")
1975 raise error.ProgrammingError("data must be non-None")
1976 self._auditconflicts(path)
1976 self._auditconflicts(path)
1977 self._markdirty(path, exists=True, data=data, date=dateutil.makedate(),
1977 self._markdirty(path, exists=True, data=data, date=dateutil.makedate(),
1978 flags=flags)
1978 flags=flags)
1979
1979
1980 def setflags(self, path, l, x):
1980 def setflags(self, path, l, x):
1981 flag = ''
1981 flag = ''
1982 if l:
1982 if l:
1983 flag = 'l'
1983 flag = 'l'
1984 elif x:
1984 elif x:
1985 flag = 'x'
1985 flag = 'x'
1986 self._markdirty(path, exists=True, date=dateutil.makedate(),
1986 self._markdirty(path, exists=True, date=dateutil.makedate(),
1987 flags=flag)
1987 flags=flag)
1988
1988
1989 def remove(self, path):
1989 def remove(self, path):
1990 self._markdirty(path, exists=False)
1990 self._markdirty(path, exists=False)
1991
1991
1992 def exists(self, path):
1992 def exists(self, path):
1993 """exists behaves like `lexists`, but needs to follow symlinks and
1993 """exists behaves like `lexists`, but needs to follow symlinks and
1994 return False if they are broken.
1994 return False if they are broken.
1995 """
1995 """
1996 if self.isdirty(path):
1996 if self.isdirty(path):
1997 # If this path exists and is a symlink, "follow" it by calling
1997 # If this path exists and is a symlink, "follow" it by calling
1998 # exists on the destination path.
1998 # exists on the destination path.
1999 if (self._cache[path]['exists'] and
1999 if (self._cache[path]['exists'] and
2000 'l' in self._cache[path]['flags']):
2000 'l' in self._cache[path]['flags']):
2001 return self.exists(self._cache[path]['data'].strip())
2001 return self.exists(self._cache[path]['data'].strip())
2002 else:
2002 else:
2003 return self._cache[path]['exists']
2003 return self._cache[path]['exists']
2004
2004
2005 return self._existsinparent(path)
2005 return self._existsinparent(path)
2006
2006
2007 def lexists(self, path):
2007 def lexists(self, path):
2008 """lexists returns True if the path exists"""
2008 """lexists returns True if the path exists"""
2009 if self.isdirty(path):
2009 if self.isdirty(path):
2010 return self._cache[path]['exists']
2010 return self._cache[path]['exists']
2011
2011
2012 return self._existsinparent(path)
2012 return self._existsinparent(path)
2013
2013
2014 def size(self, path):
2014 def size(self, path):
2015 if self.isdirty(path):
2015 if self.isdirty(path):
2016 if self._cache[path]['exists']:
2016 if self._cache[path]['exists']:
2017 return len(self._cache[path]['data'])
2017 return len(self._cache[path]['data'])
2018 else:
2018 else:
2019 raise error.ProgrammingError("No such file or directory: %s" %
2019 raise error.ProgrammingError("No such file or directory: %s" %
2020 self._path)
2020 self._path)
2021 return self._wrappedctx[path].size()
2021 return self._wrappedctx[path].size()
2022
2022
2023 def tomemctx(self, text, branch=None, extra=None, date=None, parents=None,
2023 def tomemctx(self, text, branch=None, extra=None, date=None, parents=None,
2024 user=None, editor=None):
2024 user=None, editor=None):
2025 """Converts this ``overlayworkingctx`` into a ``memctx`` ready to be
2025 """Converts this ``overlayworkingctx`` into a ``memctx`` ready to be
2026 committed.
2026 committed.
2027
2027
2028 ``text`` is the commit message.
2028 ``text`` is the commit message.
2029 ``parents`` (optional) are rev numbers.
2029 ``parents`` (optional) are rev numbers.
2030 """
2030 """
2031 # Default parents to the wrapped contexts' if not passed.
2031 # Default parents to the wrapped contexts' if not passed.
2032 if parents is None:
2032 if parents is None:
2033 parents = self._wrappedctx.parents()
2033 parents = self._wrappedctx.parents()
2034 if len(parents) == 1:
2034 if len(parents) == 1:
2035 parents = (parents[0], None)
2035 parents = (parents[0], None)
2036
2036
2037 # ``parents`` is passed as rev numbers; convert to ``commitctxs``.
2037 # ``parents`` is passed as rev numbers; convert to ``commitctxs``.
2038 if parents[1] is None:
2038 if parents[1] is None:
2039 parents = (self._repo[parents[0]], None)
2039 parents = (self._repo[parents[0]], None)
2040 else:
2040 else:
2041 parents = (self._repo[parents[0]], self._repo[parents[1]])
2041 parents = (self._repo[parents[0]], self._repo[parents[1]])
2042
2042
2043 files = self._cache.keys()
2043 files = self._cache.keys()
2044 def getfile(repo, memctx, path):
2044 def getfile(repo, memctx, path):
2045 if self._cache[path]['exists']:
2045 if self._cache[path]['exists']:
2046 return memfilectx(repo, memctx, path,
2046 return memfilectx(repo, memctx, path,
2047 self._cache[path]['data'],
2047 self._cache[path]['data'],
2048 'l' in self._cache[path]['flags'],
2048 'l' in self._cache[path]['flags'],
2049 'x' in self._cache[path]['flags'],
2049 'x' in self._cache[path]['flags'],
2050 self._cache[path]['copied'])
2050 self._cache[path]['copied'])
2051 else:
2051 else:
2052 # Returning None, but including the path in `files`, is
2052 # Returning None, but including the path in `files`, is
2053 # necessary for memctx to register a deletion.
2053 # necessary for memctx to register a deletion.
2054 return None
2054 return None
2055 return memctx(self._repo, parents, text, files, getfile, date=date,
2055 return memctx(self._repo, parents, text, files, getfile, date=date,
2056 extra=extra, user=user, branch=branch, editor=editor)
2056 extra=extra, user=user, branch=branch, editor=editor)
2057
2057
2058 def isdirty(self, path):
2058 def isdirty(self, path):
2059 return path in self._cache
2059 return path in self._cache
2060
2060
2061 def isempty(self):
2061 def isempty(self):
2062 # We need to discard any keys that are actually clean before the empty
2062 # We need to discard any keys that are actually clean before the empty
2063 # commit check.
2063 # commit check.
2064 self._compact()
2064 self._compact()
2065 return len(self._cache) == 0
2065 return len(self._cache) == 0
2066
2066
2067 def clean(self):
2067 def clean(self):
2068 self._cache = {}
2068 self._cache = {}
2069
2069
2070 def _compact(self):
2070 def _compact(self):
2071 """Removes keys from the cache that are actually clean, by comparing
2071 """Removes keys from the cache that are actually clean, by comparing
2072 them with the underlying context.
2072 them with the underlying context.
2073
2073
2074 This can occur during the merge process, e.g. by passing --tool :local
2074 This can occur during the merge process, e.g. by passing --tool :local
2075 to resolve a conflict.
2075 to resolve a conflict.
2076 """
2076 """
2077 keys = []
2077 keys = []
2078 # This won't be perfect, but can help performance significantly when
2078 # This won't be perfect, but can help performance significantly when
2079 # using things like remotefilelog.
2079 # using things like remotefilelog.
2080 scmutil.prefetchfiles(
2080 scmutil.prefetchfiles(
2081 self.repo(), [self.p1().rev()],
2081 self.repo(), [self.p1().rev()],
2082 scmutil.matchfiles(self.repo(), self._cache.keys()))
2082 scmutil.matchfiles(self.repo(), self._cache.keys()))
2083
2083
2084 for path in self._cache.keys():
2084 for path in self._cache.keys():
2085 cache = self._cache[path]
2085 cache = self._cache[path]
2086 try:
2086 try:
2087 underlying = self._wrappedctx[path]
2087 underlying = self._wrappedctx[path]
2088 if (underlying.data() == cache['data'] and
2088 if (underlying.data() == cache['data'] and
2089 underlying.flags() == cache['flags']):
2089 underlying.flags() == cache['flags']):
2090 keys.append(path)
2090 keys.append(path)
2091 except error.ManifestLookupError:
2091 except error.ManifestLookupError:
2092 # Path not in the underlying manifest (created).
2092 # Path not in the underlying manifest (created).
2093 continue
2093 continue
2094
2094
2095 for path in keys:
2095 for path in keys:
2096 del self._cache[path]
2096 del self._cache[path]
2097 return keys
2097 return keys
2098
2098
2099 def _markdirty(self, path, exists, data=None, date=None, flags='',
2099 def _markdirty(self, path, exists, data=None, date=None, flags='',
2100 copied=None):
2100 copied=None):
2101 # data not provided, let's see if we already have some; if not, let's
2101 # data not provided, let's see if we already have some; if not, let's
2102 # grab it from our underlying context, so that we always have data if
2102 # grab it from our underlying context, so that we always have data if
2103 # the file is marked as existing.
2103 # the file is marked as existing.
2104 if exists and data is None:
2104 if exists and data is None:
2105 oldentry = self._cache.get(path) or {}
2105 oldentry = self._cache.get(path) or {}
2106 data = oldentry.get('data') or self._wrappedctx[path].data()
2106 data = oldentry.get('data') or self._wrappedctx[path].data()
2107
2107
2108 self._cache[path] = {
2108 self._cache[path] = {
2109 'exists': exists,
2109 'exists': exists,
2110 'data': data,
2110 'data': data,
2111 'date': date,
2111 'date': date,
2112 'flags': flags,
2112 'flags': flags,
2113 'copied': copied,
2113 'copied': copied,
2114 }
2114 }
2115
2115
2116 def filectx(self, path, filelog=None):
2116 def filectx(self, path, filelog=None):
2117 return overlayworkingfilectx(self._repo, path, parent=self,
2117 return overlayworkingfilectx(self._repo, path, parent=self,
2118 filelog=filelog)
2118 filelog=filelog)
2119
2119
2120 class overlayworkingfilectx(committablefilectx):
2120 class overlayworkingfilectx(committablefilectx):
2121 """Wrap a ``workingfilectx`` but intercepts all writes into an in-memory
2121 """Wrap a ``workingfilectx`` but intercepts all writes into an in-memory
2122 cache, which can be flushed through later by calling ``flush()``."""
2122 cache, which can be flushed through later by calling ``flush()``."""
2123
2123
2124 def __init__(self, repo, path, filelog=None, parent=None):
2124 def __init__(self, repo, path, filelog=None, parent=None):
2125 super(overlayworkingfilectx, self).__init__(repo, path, filelog,
2125 super(overlayworkingfilectx, self).__init__(repo, path, filelog,
2126 parent)
2126 parent)
2127 self._repo = repo
2127 self._repo = repo
2128 self._parent = parent
2128 self._parent = parent
2129 self._path = path
2129 self._path = path
2130
2130
2131 def cmp(self, fctx):
2131 def cmp(self, fctx):
2132 return self.data() != fctx.data()
2132 return self.data() != fctx.data()
2133
2133
2134 def changectx(self):
2134 def changectx(self):
2135 return self._parent
2135 return self._parent
2136
2136
2137 def data(self):
2137 def data(self):
2138 return self._parent.data(self._path)
2138 return self._parent.data(self._path)
2139
2139
2140 def date(self):
2140 def date(self):
2141 return self._parent.filedate(self._path)
2141 return self._parent.filedate(self._path)
2142
2142
2143 def exists(self):
2143 def exists(self):
2144 return self.lexists()
2144 return self.lexists()
2145
2145
2146 def lexists(self):
2146 def lexists(self):
2147 return self._parent.exists(self._path)
2147 return self._parent.exists(self._path)
2148
2148
2149 def copysource(self):
2149 def copysource(self):
2150 return self._parent.copydata(self._path)
2150 return self._parent.copydata(self._path)
2151
2151
2152 def size(self):
2152 def size(self):
2153 return self._parent.size(self._path)
2153 return self._parent.size(self._path)
2154
2154
2155 def markcopied(self, origin):
2155 def markcopied(self, origin):
2156 self._parent.markcopied(self._path, origin)
2156 self._parent.markcopied(self._path, origin)
2157
2157
2158 def audit(self):
2158 def audit(self):
2159 pass
2159 pass
2160
2160
2161 def flags(self):
2161 def flags(self):
2162 return self._parent.flags(self._path)
2162 return self._parent.flags(self._path)
2163
2163
2164 def setflags(self, islink, isexec):
2164 def setflags(self, islink, isexec):
2165 return self._parent.setflags(self._path, islink, isexec)
2165 return self._parent.setflags(self._path, islink, isexec)
2166
2166
2167 def write(self, data, flags, backgroundclose=False, **kwargs):
2167 def write(self, data, flags, backgroundclose=False, **kwargs):
2168 return self._parent.write(self._path, data, flags, **kwargs)
2168 return self._parent.write(self._path, data, flags, **kwargs)
2169
2169
2170 def remove(self, ignoremissing=False):
2170 def remove(self, ignoremissing=False):
2171 return self._parent.remove(self._path)
2171 return self._parent.remove(self._path)
2172
2172
2173 def clearunknown(self):
2173 def clearunknown(self):
2174 pass
2174 pass
2175
2175
2176 class workingcommitctx(workingctx):
2176 class workingcommitctx(workingctx):
2177 """A workingcommitctx object makes access to data related to
2177 """A workingcommitctx object makes access to data related to
2178 the revision being committed convenient.
2178 the revision being committed convenient.
2179
2179
2180 This hides changes in the working directory, if they aren't
2180 This hides changes in the working directory, if they aren't
2181 committed in this context.
2181 committed in this context.
2182 """
2182 """
2183 def __init__(self, repo, changes,
2183 def __init__(self, repo, changes,
2184 text="", user=None, date=None, extra=None):
2184 text="", user=None, date=None, extra=None):
2185 super(workingcommitctx, self).__init__(repo, text, user, date, extra,
2185 super(workingcommitctx, self).__init__(repo, text, user, date, extra,
2186 changes)
2186 changes)
2187
2187
2188 def _dirstatestatus(self, match, ignored=False, clean=False, unknown=False):
2188 def _dirstatestatus(self, match, ignored=False, clean=False, unknown=False):
2189 """Return matched files only in ``self._status``
2189 """Return matched files only in ``self._status``
2190
2190
2191 Uncommitted files appear "clean" via this context, even if
2191 Uncommitted files appear "clean" via this context, even if
2192 they aren't actually so in the working directory.
2192 they aren't actually so in the working directory.
2193 """
2193 """
2194 if clean:
2194 if clean:
2195 clean = [f for f in self._manifest if f not in self._changedset]
2195 clean = [f for f in self._manifest if f not in self._changedset]
2196 else:
2196 else:
2197 clean = []
2197 clean = []
2198 return scmutil.status([f for f in self._status.modified if match(f)],
2198 return scmutil.status([f for f in self._status.modified if match(f)],
2199 [f for f in self._status.added if match(f)],
2199 [f for f in self._status.added if match(f)],
2200 [f for f in self._status.removed if match(f)],
2200 [f for f in self._status.removed if match(f)],
2201 [], [], [], clean)
2201 [], [], [], clean)
2202
2202
2203 @propertycache
2203 @propertycache
2204 def _changedset(self):
2204 def _changedset(self):
2205 """Return the set of files changed in this context
2205 """Return the set of files changed in this context
2206 """
2206 """
2207 changed = set(self._status.modified)
2207 changed = set(self._status.modified)
2208 changed.update(self._status.added)
2208 changed.update(self._status.added)
2209 changed.update(self._status.removed)
2209 changed.update(self._status.removed)
2210 return changed
2210 return changed
2211
2211
2212 def makecachingfilectxfn(func):
2212 def makecachingfilectxfn(func):
2213 """Create a filectxfn that caches based on the path.
2213 """Create a filectxfn that caches based on the path.
2214
2214
2215 We can't use util.cachefunc because it uses all arguments as the cache
2215 We can't use util.cachefunc because it uses all arguments as the cache
2216 key and this creates a cycle since the arguments include the repo and
2216 key and this creates a cycle since the arguments include the repo and
2217 memctx.
2217 memctx.
2218 """
2218 """
2219 cache = {}
2219 cache = {}
2220
2220
2221 def getfilectx(repo, memctx, path):
2221 def getfilectx(repo, memctx, path):
2222 if path not in cache:
2222 if path not in cache:
2223 cache[path] = func(repo, memctx, path)
2223 cache[path] = func(repo, memctx, path)
2224 return cache[path]
2224 return cache[path]
2225
2225
2226 return getfilectx
2226 return getfilectx
2227
2227
2228 def memfilefromctx(ctx):
2228 def memfilefromctx(ctx):
2229 """Given a context return a memfilectx for ctx[path]
2229 """Given a context return a memfilectx for ctx[path]
2230
2230
2231 This is a convenience method for building a memctx based on another
2231 This is a convenience method for building a memctx based on another
2232 context.
2232 context.
2233 """
2233 """
2234 def getfilectx(repo, memctx, path):
2234 def getfilectx(repo, memctx, path):
2235 fctx = ctx[path]
2235 fctx = ctx[path]
2236 copysource = fctx.copysource()
2236 copysource = fctx.copysource()
2237 return memfilectx(repo, memctx, path, fctx.data(),
2237 return memfilectx(repo, memctx, path, fctx.data(),
2238 islink=fctx.islink(), isexec=fctx.isexec(),
2238 islink=fctx.islink(), isexec=fctx.isexec(),
2239 copysource=copysource)
2239 copysource=copysource)
2240
2240
2241 return getfilectx
2241 return getfilectx
2242
2242
2243 def memfilefrompatch(patchstore):
2243 def memfilefrompatch(patchstore):
2244 """Given a patch (e.g. patchstore object) return a memfilectx
2244 """Given a patch (e.g. patchstore object) return a memfilectx
2245
2245
2246 This is a convenience method for building a memctx based on a patchstore.
2246 This is a convenience method for building a memctx based on a patchstore.
2247 """
2247 """
2248 def getfilectx(repo, memctx, path):
2248 def getfilectx(repo, memctx, path):
2249 data, mode, copysource = patchstore.getfile(path)
2249 data, mode, copysource = patchstore.getfile(path)
2250 if data is None:
2250 if data is None:
2251 return None
2251 return None
2252 islink, isexec = mode
2252 islink, isexec = mode
2253 return memfilectx(repo, memctx, path, data, islink=islink,
2253 return memfilectx(repo, memctx, path, data, islink=islink,
2254 isexec=isexec, copysource=copysource)
2254 isexec=isexec, copysource=copysource)
2255
2255
2256 return getfilectx
2256 return getfilectx
2257
2257
2258 class memctx(committablectx):
2258 class memctx(committablectx):
2259 """Use memctx to perform in-memory commits via localrepo.commitctx().
2259 """Use memctx to perform in-memory commits via localrepo.commitctx().
2260
2260
2261 Revision information is supplied at initialization time while
2261 Revision information is supplied at initialization time while
2262 related files data and is made available through a callback
2262 related files data and is made available through a callback
2263 mechanism. 'repo' is the current localrepo, 'parents' is a
2263 mechanism. 'repo' is the current localrepo, 'parents' is a
2264 sequence of two parent revisions identifiers (pass None for every
2264 sequence of two parent revisions identifiers (pass None for every
2265 missing parent), 'text' is the commit message and 'files' lists
2265 missing parent), 'text' is the commit message and 'files' lists
2266 names of files touched by the revision (normalized and relative to
2266 names of files touched by the revision (normalized and relative to
2267 repository root).
2267 repository root).
2268
2268
2269 filectxfn(repo, memctx, path) is a callable receiving the
2269 filectxfn(repo, memctx, path) is a callable receiving the
2270 repository, the current memctx object and the normalized path of
2270 repository, the current memctx object and the normalized path of
2271 requested file, relative to repository root. It is fired by the
2271 requested file, relative to repository root. It is fired by the
2272 commit function for every file in 'files', but calls order is
2272 commit function for every file in 'files', but calls order is
2273 undefined. If the file is available in the revision being
2273 undefined. If the file is available in the revision being
2274 committed (updated or added), filectxfn returns a memfilectx
2274 committed (updated or added), filectxfn returns a memfilectx
2275 object. If the file was removed, filectxfn return None for recent
2275 object. If the file was removed, filectxfn return None for recent
2276 Mercurial. Moved files are represented by marking the source file
2276 Mercurial. Moved files are represented by marking the source file
2277 removed and the new file added with copy information (see
2277 removed and the new file added with copy information (see
2278 memfilectx).
2278 memfilectx).
2279
2279
2280 user receives the committer name and defaults to current
2280 user receives the committer name and defaults to current
2281 repository username, date is the commit date in any format
2281 repository username, date is the commit date in any format
2282 supported by dateutil.parsedate() and defaults to current date, extra
2282 supported by dateutil.parsedate() and defaults to current date, extra
2283 is a dictionary of metadata or is left empty.
2283 is a dictionary of metadata or is left empty.
2284 """
2284 """
2285
2285
2286 # Mercurial <= 3.1 expects the filectxfn to raise IOError for missing files.
2286 # Mercurial <= 3.1 expects the filectxfn to raise IOError for missing files.
2287 # Extensions that need to retain compatibility across Mercurial 3.1 can use
2287 # Extensions that need to retain compatibility across Mercurial 3.1 can use
2288 # this field to determine what to do in filectxfn.
2288 # this field to determine what to do in filectxfn.
2289 _returnnoneformissingfiles = True
2289 _returnnoneformissingfiles = True
2290
2290
2291 def __init__(self, repo, parents, text, files, filectxfn, user=None,
2291 def __init__(self, repo, parents, text, files, filectxfn, user=None,
2292 date=None, extra=None, branch=None, editor=False):
2292 date=None, extra=None, branch=None, editor=False):
2293 super(memctx, self).__init__(repo, text, user, date, extra)
2293 super(memctx, self).__init__(repo, text, user, date, extra)
2294 self._rev = None
2294 self._rev = None
2295 self._node = None
2295 self._node = None
2296 parents = [(p or nullid) for p in parents]
2296 parents = [(p or nullid) for p in parents]
2297 p1, p2 = parents
2297 p1, p2 = parents
2298 self._parents = [self._repo[p] for p in (p1, p2)]
2298 self._parents = [self._repo[p] for p in (p1, p2)]
2299 files = sorted(set(files))
2299 files = sorted(set(files))
2300 self._files = files
2300 self._files = files
2301 if branch is not None:
2301 if branch is not None:
2302 self._extra['branch'] = encoding.fromlocal(branch)
2302 self._extra['branch'] = encoding.fromlocal(branch)
2303 self.substate = {}
2303 self.substate = {}
2304
2304
2305 if isinstance(filectxfn, patch.filestore):
2305 if isinstance(filectxfn, patch.filestore):
2306 filectxfn = memfilefrompatch(filectxfn)
2306 filectxfn = memfilefrompatch(filectxfn)
2307 elif not callable(filectxfn):
2307 elif not callable(filectxfn):
2308 # if store is not callable, wrap it in a function
2308 # if store is not callable, wrap it in a function
2309 filectxfn = memfilefromctx(filectxfn)
2309 filectxfn = memfilefromctx(filectxfn)
2310
2310
2311 # memoizing increases performance for e.g. vcs convert scenarios.
2311 # memoizing increases performance for e.g. vcs convert scenarios.
2312 self._filectxfn = makecachingfilectxfn(filectxfn)
2312 self._filectxfn = makecachingfilectxfn(filectxfn)
2313
2313
2314 if editor:
2314 if editor:
2315 self._text = editor(self._repo, self, [])
2315 self._text = editor(self._repo, self, [])
2316 self._repo.savecommitmessage(self._text)
2316 self._repo.savecommitmessage(self._text)
2317
2317
2318 def filectx(self, path, filelog=None):
2318 def filectx(self, path, filelog=None):
2319 """get a file context from the working directory
2319 """get a file context from the working directory
2320
2320
2321 Returns None if file doesn't exist and should be removed."""
2321 Returns None if file doesn't exist and should be removed."""
2322 return self._filectxfn(self._repo, self, path)
2322 return self._filectxfn(self._repo, self, path)
2323
2323
2324 def commit(self):
2324 def commit(self):
2325 """commit context to the repo"""
2325 """commit context to the repo"""
2326 return self._repo.commitctx(self)
2326 return self._repo.commitctx(self)
2327
2327
2328 @propertycache
2328 @propertycache
2329 def _manifest(self):
2329 def _manifest(self):
2330 """generate a manifest based on the return values of filectxfn"""
2330 """generate a manifest based on the return values of filectxfn"""
2331
2331
2332 # keep this simple for now; just worry about p1
2332 # keep this simple for now; just worry about p1
2333 pctx = self._parents[0]
2333 pctx = self._parents[0]
2334 man = pctx.manifest().copy()
2334 man = pctx.manifest().copy()
2335
2335
2336 for f in self._status.modified:
2336 for f in self._status.modified:
2337 man[f] = modifiednodeid
2337 man[f] = modifiednodeid
2338
2338
2339 for f in self._status.added:
2339 for f in self._status.added:
2340 man[f] = addednodeid
2340 man[f] = addednodeid
2341
2341
2342 for f in self._status.removed:
2342 for f in self._status.removed:
2343 if f in man:
2343 if f in man:
2344 del man[f]
2344 del man[f]
2345
2345
2346 return man
2346 return man
2347
2347
2348 @propertycache
2348 @propertycache
2349 def _status(self):
2349 def _status(self):
2350 """Calculate exact status from ``files`` specified at construction
2350 """Calculate exact status from ``files`` specified at construction
2351 """
2351 """
2352 man1 = self.p1().manifest()
2352 man1 = self.p1().manifest()
2353 p2 = self._parents[1]
2353 p2 = self._parents[1]
2354 # "1 < len(self._parents)" can't be used for checking
2354 # "1 < len(self._parents)" can't be used for checking
2355 # existence of the 2nd parent, because "memctx._parents" is
2355 # existence of the 2nd parent, because "memctx._parents" is
2356 # explicitly initialized by the list, of which length is 2.
2356 # explicitly initialized by the list, of which length is 2.
2357 if p2.node() != nullid:
2357 if p2.node() != nullid:
2358 man2 = p2.manifest()
2358 man2 = p2.manifest()
2359 managing = lambda f: f in man1 or f in man2
2359 managing = lambda f: f in man1 or f in man2
2360 else:
2360 else:
2361 managing = lambda f: f in man1
2361 managing = lambda f: f in man1
2362
2362
2363 modified, added, removed = [], [], []
2363 modified, added, removed = [], [], []
2364 for f in self._files:
2364 for f in self._files:
2365 if not managing(f):
2365 if not managing(f):
2366 added.append(f)
2366 added.append(f)
2367 elif self[f]:
2367 elif self[f]:
2368 modified.append(f)
2368 modified.append(f)
2369 else:
2369 else:
2370 removed.append(f)
2370 removed.append(f)
2371
2371
2372 return scmutil.status(modified, added, removed, [], [], [], [])
2372 return scmutil.status(modified, added, removed, [], [], [], [])
2373
2373
2374 class memfilectx(committablefilectx):
2374 class memfilectx(committablefilectx):
2375 """memfilectx represents an in-memory file to commit.
2375 """memfilectx represents an in-memory file to commit.
2376
2376
2377 See memctx and committablefilectx for more details.
2377 See memctx and committablefilectx for more details.
2378 """
2378 """
2379 def __init__(self, repo, changectx, path, data, islink=False,
2379 def __init__(self, repo, changectx, path, data, islink=False,
2380 isexec=False, copysource=None):
2380 isexec=False, copysource=None):
2381 """
2381 """
2382 path is the normalized file path relative to repository root.
2382 path is the normalized file path relative to repository root.
2383 data is the file content as a string.
2383 data is the file content as a string.
2384 islink is True if the file is a symbolic link.
2384 islink is True if the file is a symbolic link.
2385 isexec is True if the file is executable.
2385 isexec is True if the file is executable.
2386 copied is the source file path if current file was copied in the
2386 copied is the source file path if current file was copied in the
2387 revision being committed, or None."""
2387 revision being committed, or None."""
2388 super(memfilectx, self).__init__(repo, path, None, changectx)
2388 super(memfilectx, self).__init__(repo, path, None, changectx)
2389 self._data = data
2389 self._data = data
2390 if islink:
2390 if islink:
2391 self._flags = 'l'
2391 self._flags = 'l'
2392 elif isexec:
2392 elif isexec:
2393 self._flags = 'x'
2393 self._flags = 'x'
2394 else:
2394 else:
2395 self._flags = ''
2395 self._flags = ''
2396 self._copysource = copysource
2396 self._copysource = copysource
2397
2397
2398 def copysource(self):
2398 def copysource(self):
2399 return self._copysource
2399 return self._copysource
2400
2400
2401 def cmp(self, fctx):
2401 def cmp(self, fctx):
2402 return self.data() != fctx.data()
2402 return self.data() != fctx.data()
2403
2403
2404 def data(self):
2404 def data(self):
2405 return self._data
2405 return self._data
2406
2406
2407 def remove(self, ignoremissing=False):
2407 def remove(self, ignoremissing=False):
2408 """wraps unlink for a repo's working directory"""
2408 """wraps unlink for a repo's working directory"""
2409 # need to figure out what to do here
2409 # need to figure out what to do here
2410 del self._changectx[self._path]
2410 del self._changectx[self._path]
2411
2411
2412 def write(self, data, flags, **kwargs):
2412 def write(self, data, flags, **kwargs):
2413 """wraps repo.wwrite"""
2413 """wraps repo.wwrite"""
2414 self._data = data
2414 self._data = data
2415
2415
2416
2416
2417 class metadataonlyctx(committablectx):
2417 class metadataonlyctx(committablectx):
2418 """Like memctx but it's reusing the manifest of different commit.
2418 """Like memctx but it's reusing the manifest of different commit.
2419 Intended to be used by lightweight operations that are creating
2419 Intended to be used by lightweight operations that are creating
2420 metadata-only changes.
2420 metadata-only changes.
2421
2421
2422 Revision information is supplied at initialization time. 'repo' is the
2422 Revision information is supplied at initialization time. 'repo' is the
2423 current localrepo, 'ctx' is original revision which manifest we're reuisng
2423 current localrepo, 'ctx' is original revision which manifest we're reuisng
2424 'parents' is a sequence of two parent revisions identifiers (pass None for
2424 'parents' is a sequence of two parent revisions identifiers (pass None for
2425 every missing parent), 'text' is the commit.
2425 every missing parent), 'text' is the commit.
2426
2426
2427 user receives the committer name and defaults to current repository
2427 user receives the committer name and defaults to current repository
2428 username, date is the commit date in any format supported by
2428 username, date is the commit date in any format supported by
2429 dateutil.parsedate() and defaults to current date, extra is a dictionary of
2429 dateutil.parsedate() and defaults to current date, extra is a dictionary of
2430 metadata or is left empty.
2430 metadata or is left empty.
2431 """
2431 """
2432 def __init__(self, repo, originalctx, parents=None, text=None, user=None,
2432 def __init__(self, repo, originalctx, parents=None, text=None, user=None,
2433 date=None, extra=None, editor=False):
2433 date=None, extra=None, editor=False):
2434 if text is None:
2434 if text is None:
2435 text = originalctx.description()
2435 text = originalctx.description()
2436 super(metadataonlyctx, self).__init__(repo, text, user, date, extra)
2436 super(metadataonlyctx, self).__init__(repo, text, user, date, extra)
2437 self._rev = None
2437 self._rev = None
2438 self._node = None
2438 self._node = None
2439 self._originalctx = originalctx
2439 self._originalctx = originalctx
2440 self._manifestnode = originalctx.manifestnode()
2440 self._manifestnode = originalctx.manifestnode()
2441 if parents is None:
2441 if parents is None:
2442 parents = originalctx.parents()
2442 parents = originalctx.parents()
2443 else:
2443 else:
2444 parents = [repo[p] for p in parents if p is not None]
2444 parents = [repo[p] for p in parents if p is not None]
2445 parents = parents[:]
2445 parents = parents[:]
2446 while len(parents) < 2:
2446 while len(parents) < 2:
2447 parents.append(repo[nullid])
2447 parents.append(repo[nullid])
2448 p1, p2 = self._parents = parents
2448 p1, p2 = self._parents = parents
2449
2449
2450 # sanity check to ensure that the reused manifest parents are
2450 # sanity check to ensure that the reused manifest parents are
2451 # manifests of our commit parents
2451 # manifests of our commit parents
2452 mp1, mp2 = self.manifestctx().parents
2452 mp1, mp2 = self.manifestctx().parents
2453 if p1 != nullid and p1.manifestnode() != mp1:
2453 if p1 != nullid and p1.manifestnode() != mp1:
2454 raise RuntimeError(r"can't reuse the manifest: its p1 "
2454 raise RuntimeError(r"can't reuse the manifest: its p1 "
2455 r"doesn't match the new ctx p1")
2455 r"doesn't match the new ctx p1")
2456 if p2 != nullid and p2.manifestnode() != mp2:
2456 if p2 != nullid and p2.manifestnode() != mp2:
2457 raise RuntimeError(r"can't reuse the manifest: "
2457 raise RuntimeError(r"can't reuse the manifest: "
2458 r"its p2 doesn't match the new ctx p2")
2458 r"its p2 doesn't match the new ctx p2")
2459
2459
2460 self._files = originalctx.files()
2460 self._files = originalctx.files()
2461 self.substate = {}
2461 self.substate = {}
2462
2462
2463 if editor:
2463 if editor:
2464 self._text = editor(self._repo, self, [])
2464 self._text = editor(self._repo, self, [])
2465 self._repo.savecommitmessage(self._text)
2465 self._repo.savecommitmessage(self._text)
2466
2466
2467 def manifestnode(self):
2467 def manifestnode(self):
2468 return self._manifestnode
2468 return self._manifestnode
2469
2469
2470 @property
2470 @property
2471 def _manifestctx(self):
2471 def _manifestctx(self):
2472 return self._repo.manifestlog[self._manifestnode]
2472 return self._repo.manifestlog[self._manifestnode]
2473
2473
2474 def filectx(self, path, filelog=None):
2474 def filectx(self, path, filelog=None):
2475 return self._originalctx.filectx(path, filelog=filelog)
2475 return self._originalctx.filectx(path, filelog=filelog)
2476
2476
2477 def commit(self):
2477 def commit(self):
2478 """commit context to the repo"""
2478 """commit context to the repo"""
2479 return self._repo.commitctx(self)
2479 return self._repo.commitctx(self)
2480
2480
2481 @property
2481 @property
2482 def _manifest(self):
2482 def _manifest(self):
2483 return self._originalctx.manifest()
2483 return self._originalctx.manifest()
2484
2484
2485 @propertycache
2485 @propertycache
2486 def _status(self):
2486 def _status(self):
2487 """Calculate exact status from ``files`` specified in the ``origctx``
2487 """Calculate exact status from ``files`` specified in the ``origctx``
2488 and parents manifests.
2488 and parents manifests.
2489 """
2489 """
2490 man1 = self.p1().manifest()
2490 man1 = self.p1().manifest()
2491 p2 = self._parents[1]
2491 p2 = self._parents[1]
2492 # "1 < len(self._parents)" can't be used for checking
2492 # "1 < len(self._parents)" can't be used for checking
2493 # existence of the 2nd parent, because "metadataonlyctx._parents" is
2493 # existence of the 2nd parent, because "metadataonlyctx._parents" is
2494 # explicitly initialized by the list, of which length is 2.
2494 # explicitly initialized by the list, of which length is 2.
2495 if p2.node() != nullid:
2495 if p2.node() != nullid:
2496 man2 = p2.manifest()
2496 man2 = p2.manifest()
2497 managing = lambda f: f in man1 or f in man2
2497 managing = lambda f: f in man1 or f in man2
2498 else:
2498 else:
2499 managing = lambda f: f in man1
2499 managing = lambda f: f in man1
2500
2500
2501 modified, added, removed = [], [], []
2501 modified, added, removed = [], [], []
2502 for f in self._files:
2502 for f in self._files:
2503 if not managing(f):
2503 if not managing(f):
2504 added.append(f)
2504 added.append(f)
2505 elif f in self:
2505 elif f in self:
2506 modified.append(f)
2506 modified.append(f)
2507 else:
2507 else:
2508 removed.append(f)
2508 removed.append(f)
2509
2509
2510 return scmutil.status(modified, added, removed, [], [], [], [])
2510 return scmutil.status(modified, added, removed, [], [], [], [])
2511
2511
2512 class arbitraryfilectx(object):
2512 class arbitraryfilectx(object):
2513 """Allows you to use filectx-like functions on a file in an arbitrary
2513 """Allows you to use filectx-like functions on a file in an arbitrary
2514 location on disk, possibly not in the working directory.
2514 location on disk, possibly not in the working directory.
2515 """
2515 """
2516 def __init__(self, path, repo=None):
2516 def __init__(self, path, repo=None):
2517 # Repo is optional because contrib/simplemerge uses this class.
2517 # Repo is optional because contrib/simplemerge uses this class.
2518 self._repo = repo
2518 self._repo = repo
2519 self._path = path
2519 self._path = path
2520
2520
2521 def cmp(self, fctx):
2521 def cmp(self, fctx):
2522 # filecmp follows symlinks whereas `cmp` should not, so skip the fast
2522 # filecmp follows symlinks whereas `cmp` should not, so skip the fast
2523 # path if either side is a symlink.
2523 # path if either side is a symlink.
2524 symlinks = ('l' in self.flags() or 'l' in fctx.flags())
2524 symlinks = ('l' in self.flags() or 'l' in fctx.flags())
2525 if not symlinks and isinstance(fctx, workingfilectx) and self._repo:
2525 if not symlinks and isinstance(fctx, workingfilectx) and self._repo:
2526 # Add a fast-path for merge if both sides are disk-backed.
2526 # Add a fast-path for merge if both sides are disk-backed.
2527 # Note that filecmp uses the opposite return values (True if same)
2527 # Note that filecmp uses the opposite return values (True if same)
2528 # from our cmp functions (True if different).
2528 # from our cmp functions (True if different).
2529 return not filecmp.cmp(self.path(), self._repo.wjoin(fctx.path()))
2529 return not filecmp.cmp(self.path(), self._repo.wjoin(fctx.path()))
2530 return self.data() != fctx.data()
2530 return self.data() != fctx.data()
2531
2531
2532 def path(self):
2532 def path(self):
2533 return self._path
2533 return self._path
2534
2534
2535 def flags(self):
2535 def flags(self):
2536 return ''
2536 return ''
2537
2537
2538 def data(self):
2538 def data(self):
2539 return util.readfile(self._path)
2539 return util.readfile(self._path)
2540
2540
2541 def decodeddata(self):
2541 def decodeddata(self):
2542 with open(self._path, "rb") as f:
2542 with open(self._path, "rb") as f:
2543 return f.read()
2543 return f.read()
2544
2544
2545 def remove(self):
2545 def remove(self):
2546 util.unlink(self._path)
2546 util.unlink(self._path)
2547
2547
2548 def write(self, data, flags, **kwargs):
2548 def write(self, data, flags, **kwargs):
2549 assert not flags
2549 assert not flags
2550 with open(self._path, "wb") as f:
2550 with open(self._path, "wb") as f:
2551 f.write(data)
2551 f.write(data)
@@ -1,3467 +1,3467 b''
1 # debugcommands.py - command processing for debug* commands
1 # debugcommands.py - command processing for debug* commands
2 #
2 #
3 # Copyright 2005-2016 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2016 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 codecs
10 import codecs
11 import collections
11 import collections
12 import difflib
12 import difflib
13 import errno
13 import errno
14 import operator
14 import operator
15 import os
15 import os
16 import random
16 import random
17 import re
17 import re
18 import socket
18 import socket
19 import ssl
19 import ssl
20 import stat
20 import stat
21 import string
21 import string
22 import subprocess
22 import subprocess
23 import sys
23 import sys
24 import time
24 import time
25
25
26 from .i18n import _
26 from .i18n import _
27 from .node import (
27 from .node import (
28 bin,
28 bin,
29 hex,
29 hex,
30 nullhex,
30 nullhex,
31 nullid,
31 nullid,
32 nullrev,
32 nullrev,
33 short,
33 short,
34 )
34 )
35 from . import (
35 from . import (
36 bundle2,
36 bundle2,
37 changegroup,
37 changegroup,
38 cmdutil,
38 cmdutil,
39 color,
39 color,
40 context,
40 context,
41 copies,
41 copies,
42 dagparser,
42 dagparser,
43 encoding,
43 encoding,
44 error,
44 error,
45 exchange,
45 exchange,
46 extensions,
46 extensions,
47 filemerge,
47 filemerge,
48 filesetlang,
48 filesetlang,
49 formatter,
49 formatter,
50 hg,
50 hg,
51 httppeer,
51 httppeer,
52 localrepo,
52 localrepo,
53 lock as lockmod,
53 lock as lockmod,
54 logcmdutil,
54 logcmdutil,
55 merge as mergemod,
55 merge as mergemod,
56 obsolete,
56 obsolete,
57 obsutil,
57 obsutil,
58 phases,
58 phases,
59 policy,
59 policy,
60 pvec,
60 pvec,
61 pycompat,
61 pycompat,
62 registrar,
62 registrar,
63 repair,
63 repair,
64 revlog,
64 revlog,
65 revset,
65 revset,
66 revsetlang,
66 revsetlang,
67 scmutil,
67 scmutil,
68 setdiscovery,
68 setdiscovery,
69 simplemerge,
69 simplemerge,
70 sshpeer,
70 sshpeer,
71 sslutil,
71 sslutil,
72 streamclone,
72 streamclone,
73 templater,
73 templater,
74 treediscovery,
74 treediscovery,
75 upgrade,
75 upgrade,
76 url as urlmod,
76 url as urlmod,
77 util,
77 util,
78 vfs as vfsmod,
78 vfs as vfsmod,
79 wireprotoframing,
79 wireprotoframing,
80 wireprotoserver,
80 wireprotoserver,
81 wireprotov2peer,
81 wireprotov2peer,
82 )
82 )
83 from .utils import (
83 from .utils import (
84 cborutil,
84 cborutil,
85 compression,
85 compression,
86 dateutil,
86 dateutil,
87 procutil,
87 procutil,
88 stringutil,
88 stringutil,
89 )
89 )
90
90
91 from .revlogutils import (
91 from .revlogutils import (
92 deltas as deltautil
92 deltas as deltautil
93 )
93 )
94
94
95 release = lockmod.release
95 release = lockmod.release
96
96
97 command = registrar.command()
97 command = registrar.command()
98
98
99 @command('debugancestor', [], _('[INDEX] REV1 REV2'), optionalrepo=True)
99 @command('debugancestor', [], _('[INDEX] REV1 REV2'), optionalrepo=True)
100 def debugancestor(ui, repo, *args):
100 def debugancestor(ui, repo, *args):
101 """find the ancestor revision of two revisions in a given index"""
101 """find the ancestor revision of two revisions in a given index"""
102 if len(args) == 3:
102 if len(args) == 3:
103 index, rev1, rev2 = args
103 index, rev1, rev2 = args
104 r = revlog.revlog(vfsmod.vfs(encoding.getcwd(), audit=False), index)
104 r = revlog.revlog(vfsmod.vfs(encoding.getcwd(), audit=False), index)
105 lookup = r.lookup
105 lookup = r.lookup
106 elif len(args) == 2:
106 elif len(args) == 2:
107 if not repo:
107 if not repo:
108 raise error.Abort(_('there is no Mercurial repository here '
108 raise error.Abort(_('there is no Mercurial repository here '
109 '(.hg not found)'))
109 '(.hg not found)'))
110 rev1, rev2 = args
110 rev1, rev2 = args
111 r = repo.changelog
111 r = repo.changelog
112 lookup = repo.lookup
112 lookup = repo.lookup
113 else:
113 else:
114 raise error.Abort(_('either two or three arguments required'))
114 raise error.Abort(_('either two or three arguments required'))
115 a = r.ancestor(lookup(rev1), lookup(rev2))
115 a = r.ancestor(lookup(rev1), lookup(rev2))
116 ui.write('%d:%s\n' % (r.rev(a), hex(a)))
116 ui.write('%d:%s\n' % (r.rev(a), hex(a)))
117
117
118 @command('debugapplystreamclonebundle', [], 'FILE')
118 @command('debugapplystreamclonebundle', [], 'FILE')
119 def debugapplystreamclonebundle(ui, repo, fname):
119 def debugapplystreamclonebundle(ui, repo, fname):
120 """apply a stream clone bundle file"""
120 """apply a stream clone bundle file"""
121 f = hg.openpath(ui, fname)
121 f = hg.openpath(ui, fname)
122 gen = exchange.readbundle(ui, f, fname)
122 gen = exchange.readbundle(ui, f, fname)
123 gen.apply(repo)
123 gen.apply(repo)
124
124
125 @command('debugbuilddag',
125 @command('debugbuilddag',
126 [('m', 'mergeable-file', None, _('add single file mergeable changes')),
126 [('m', 'mergeable-file', None, _('add single file mergeable changes')),
127 ('o', 'overwritten-file', None, _('add single file all revs overwrite')),
127 ('o', 'overwritten-file', None, _('add single file all revs overwrite')),
128 ('n', 'new-file', None, _('add new file at each rev'))],
128 ('n', 'new-file', None, _('add new file at each rev'))],
129 _('[OPTION]... [TEXT]'))
129 _('[OPTION]... [TEXT]'))
130 def debugbuilddag(ui, repo, text=None,
130 def debugbuilddag(ui, repo, text=None,
131 mergeable_file=False,
131 mergeable_file=False,
132 overwritten_file=False,
132 overwritten_file=False,
133 new_file=False):
133 new_file=False):
134 """builds a repo with a given DAG from scratch in the current empty repo
134 """builds a repo with a given DAG from scratch in the current empty repo
135
135
136 The description of the DAG is read from stdin if not given on the
136 The description of the DAG is read from stdin if not given on the
137 command line.
137 command line.
138
138
139 Elements:
139 Elements:
140
140
141 - "+n" is a linear run of n nodes based on the current default parent
141 - "+n" is a linear run of n nodes based on the current default parent
142 - "." is a single node based on the current default parent
142 - "." is a single node based on the current default parent
143 - "$" resets the default parent to null (implied at the start);
143 - "$" resets the default parent to null (implied at the start);
144 otherwise the default parent is always the last node created
144 otherwise the default parent is always the last node created
145 - "<p" sets the default parent to the backref p
145 - "<p" sets the default parent to the backref p
146 - "*p" is a fork at parent p, which is a backref
146 - "*p" is a fork at parent p, which is a backref
147 - "*p1/p2" is a merge of parents p1 and p2, which are backrefs
147 - "*p1/p2" is a merge of parents p1 and p2, which are backrefs
148 - "/p2" is a merge of the preceding node and p2
148 - "/p2" is a merge of the preceding node and p2
149 - ":tag" defines a local tag for the preceding node
149 - ":tag" defines a local tag for the preceding node
150 - "@branch" sets the named branch for subsequent nodes
150 - "@branch" sets the named branch for subsequent nodes
151 - "#...\\n" is a comment up to the end of the line
151 - "#...\\n" is a comment up to the end of the line
152
152
153 Whitespace between the above elements is ignored.
153 Whitespace between the above elements is ignored.
154
154
155 A backref is either
155 A backref is either
156
156
157 - a number n, which references the node curr-n, where curr is the current
157 - a number n, which references the node curr-n, where curr is the current
158 node, or
158 node, or
159 - the name of a local tag you placed earlier using ":tag", or
159 - the name of a local tag you placed earlier using ":tag", or
160 - empty to denote the default parent.
160 - empty to denote the default parent.
161
161
162 All string valued-elements are either strictly alphanumeric, or must
162 All string valued-elements are either strictly alphanumeric, or must
163 be enclosed in double quotes ("..."), with "\\" as escape character.
163 be enclosed in double quotes ("..."), with "\\" as escape character.
164 """
164 """
165
165
166 if text is None:
166 if text is None:
167 ui.status(_("reading DAG from stdin\n"))
167 ui.status(_("reading DAG from stdin\n"))
168 text = ui.fin.read()
168 text = ui.fin.read()
169
169
170 cl = repo.changelog
170 cl = repo.changelog
171 if len(cl) > 0:
171 if len(cl) > 0:
172 raise error.Abort(_('repository is not empty'))
172 raise error.Abort(_('repository is not empty'))
173
173
174 # determine number of revs in DAG
174 # determine number of revs in DAG
175 total = 0
175 total = 0
176 for type, data in dagparser.parsedag(text):
176 for type, data in dagparser.parsedag(text):
177 if type == 'n':
177 if type == 'n':
178 total += 1
178 total += 1
179
179
180 if mergeable_file:
180 if mergeable_file:
181 linesperrev = 2
181 linesperrev = 2
182 # make a file with k lines per rev
182 # make a file with k lines per rev
183 initialmergedlines = ['%d' % i
183 initialmergedlines = ['%d' % i
184 for i in pycompat.xrange(0, total * linesperrev)]
184 for i in pycompat.xrange(0, total * linesperrev)]
185 initialmergedlines.append("")
185 initialmergedlines.append("")
186
186
187 tags = []
187 tags = []
188 progress = ui.makeprogress(_('building'), unit=_('revisions'),
188 progress = ui.makeprogress(_('building'), unit=_('revisions'),
189 total=total)
189 total=total)
190 with progress, repo.wlock(), repo.lock(), repo.transaction("builddag"):
190 with progress, repo.wlock(), repo.lock(), repo.transaction("builddag"):
191 at = -1
191 at = -1
192 atbranch = 'default'
192 atbranch = 'default'
193 nodeids = []
193 nodeids = []
194 id = 0
194 id = 0
195 progress.update(id)
195 progress.update(id)
196 for type, data in dagparser.parsedag(text):
196 for type, data in dagparser.parsedag(text):
197 if type == 'n':
197 if type == 'n':
198 ui.note(('node %s\n' % pycompat.bytestr(data)))
198 ui.note(('node %s\n' % pycompat.bytestr(data)))
199 id, ps = data
199 id, ps = data
200
200
201 files = []
201 files = []
202 filecontent = {}
202 filecontent = {}
203
203
204 p2 = None
204 p2 = None
205 if mergeable_file:
205 if mergeable_file:
206 fn = "mf"
206 fn = "mf"
207 p1 = repo[ps[0]]
207 p1 = repo[ps[0]]
208 if len(ps) > 1:
208 if len(ps) > 1:
209 p2 = repo[ps[1]]
209 p2 = repo[ps[1]]
210 pa = p1.ancestor(p2)
210 pa = p1.ancestor(p2)
211 base, local, other = [x[fn].data() for x in (pa, p1,
211 base, local, other = [x[fn].data() for x in (pa, p1,
212 p2)]
212 p2)]
213 m3 = simplemerge.Merge3Text(base, local, other)
213 m3 = simplemerge.Merge3Text(base, local, other)
214 ml = [l.strip() for l in m3.merge_lines()]
214 ml = [l.strip() for l in m3.merge_lines()]
215 ml.append("")
215 ml.append("")
216 elif at > 0:
216 elif at > 0:
217 ml = p1[fn].data().split("\n")
217 ml = p1[fn].data().split("\n")
218 else:
218 else:
219 ml = initialmergedlines
219 ml = initialmergedlines
220 ml[id * linesperrev] += " r%i" % id
220 ml[id * linesperrev] += " r%i" % id
221 mergedtext = "\n".join(ml)
221 mergedtext = "\n".join(ml)
222 files.append(fn)
222 files.append(fn)
223 filecontent[fn] = mergedtext
223 filecontent[fn] = mergedtext
224
224
225 if overwritten_file:
225 if overwritten_file:
226 fn = "of"
226 fn = "of"
227 files.append(fn)
227 files.append(fn)
228 filecontent[fn] = "r%i\n" % id
228 filecontent[fn] = "r%i\n" % id
229
229
230 if new_file:
230 if new_file:
231 fn = "nf%i" % id
231 fn = "nf%i" % id
232 files.append(fn)
232 files.append(fn)
233 filecontent[fn] = "r%i\n" % id
233 filecontent[fn] = "r%i\n" % id
234 if len(ps) > 1:
234 if len(ps) > 1:
235 if not p2:
235 if not p2:
236 p2 = repo[ps[1]]
236 p2 = repo[ps[1]]
237 for fn in p2:
237 for fn in p2:
238 if fn.startswith("nf"):
238 if fn.startswith("nf"):
239 files.append(fn)
239 files.append(fn)
240 filecontent[fn] = p2[fn].data()
240 filecontent[fn] = p2[fn].data()
241
241
242 def fctxfn(repo, cx, path):
242 def fctxfn(repo, cx, path):
243 if path in filecontent:
243 if path in filecontent:
244 return context.memfilectx(repo, cx, path,
244 return context.memfilectx(repo, cx, path,
245 filecontent[path])
245 filecontent[path])
246 return None
246 return None
247
247
248 if len(ps) == 0 or ps[0] < 0:
248 if len(ps) == 0 or ps[0] < 0:
249 pars = [None, None]
249 pars = [None, None]
250 elif len(ps) == 1:
250 elif len(ps) == 1:
251 pars = [nodeids[ps[0]], None]
251 pars = [nodeids[ps[0]], None]
252 else:
252 else:
253 pars = [nodeids[p] for p in ps]
253 pars = [nodeids[p] for p in ps]
254 cx = context.memctx(repo, pars, "r%i" % id, files, fctxfn,
254 cx = context.memctx(repo, pars, "r%i" % id, files, fctxfn,
255 date=(id, 0),
255 date=(id, 0),
256 user="debugbuilddag",
256 user="debugbuilddag",
257 extra={'branch': atbranch})
257 extra={'branch': atbranch})
258 nodeid = repo.commitctx(cx)
258 nodeid = repo.commitctx(cx)
259 nodeids.append(nodeid)
259 nodeids.append(nodeid)
260 at = id
260 at = id
261 elif type == 'l':
261 elif type == 'l':
262 id, name = data
262 id, name = data
263 ui.note(('tag %s\n' % name))
263 ui.note(('tag %s\n' % name))
264 tags.append("%s %s\n" % (hex(repo.changelog.node(id)), name))
264 tags.append("%s %s\n" % (hex(repo.changelog.node(id)), name))
265 elif type == 'a':
265 elif type == 'a':
266 ui.note(('branch %s\n' % data))
266 ui.note(('branch %s\n' % data))
267 atbranch = data
267 atbranch = data
268 progress.update(id)
268 progress.update(id)
269
269
270 if tags:
270 if tags:
271 repo.vfs.write("localtags", "".join(tags))
271 repo.vfs.write("localtags", "".join(tags))
272
272
273 def _debugchangegroup(ui, gen, all=None, indent=0, **opts):
273 def _debugchangegroup(ui, gen, all=None, indent=0, **opts):
274 indent_string = ' ' * indent
274 indent_string = ' ' * indent
275 if all:
275 if all:
276 ui.write(("%sformat: id, p1, p2, cset, delta base, len(delta)\n")
276 ui.write(("%sformat: id, p1, p2, cset, delta base, len(delta)\n")
277 % indent_string)
277 % indent_string)
278
278
279 def showchunks(named):
279 def showchunks(named):
280 ui.write("\n%s%s\n" % (indent_string, named))
280 ui.write("\n%s%s\n" % (indent_string, named))
281 for deltadata in gen.deltaiter():
281 for deltadata in gen.deltaiter():
282 node, p1, p2, cs, deltabase, delta, flags = deltadata
282 node, p1, p2, cs, deltabase, delta, flags = deltadata
283 ui.write("%s%s %s %s %s %s %d\n" %
283 ui.write("%s%s %s %s %s %s %d\n" %
284 (indent_string, hex(node), hex(p1), hex(p2),
284 (indent_string, hex(node), hex(p1), hex(p2),
285 hex(cs), hex(deltabase), len(delta)))
285 hex(cs), hex(deltabase), len(delta)))
286
286
287 chunkdata = gen.changelogheader()
287 chunkdata = gen.changelogheader()
288 showchunks("changelog")
288 showchunks("changelog")
289 chunkdata = gen.manifestheader()
289 chunkdata = gen.manifestheader()
290 showchunks("manifest")
290 showchunks("manifest")
291 for chunkdata in iter(gen.filelogheader, {}):
291 for chunkdata in iter(gen.filelogheader, {}):
292 fname = chunkdata['filename']
292 fname = chunkdata['filename']
293 showchunks(fname)
293 showchunks(fname)
294 else:
294 else:
295 if isinstance(gen, bundle2.unbundle20):
295 if isinstance(gen, bundle2.unbundle20):
296 raise error.Abort(_('use debugbundle2 for this file'))
296 raise error.Abort(_('use debugbundle2 for this file'))
297 chunkdata = gen.changelogheader()
297 chunkdata = gen.changelogheader()
298 for deltadata in gen.deltaiter():
298 for deltadata in gen.deltaiter():
299 node, p1, p2, cs, deltabase, delta, flags = deltadata
299 node, p1, p2, cs, deltabase, delta, flags = deltadata
300 ui.write("%s%s\n" % (indent_string, hex(node)))
300 ui.write("%s%s\n" % (indent_string, hex(node)))
301
301
302 def _debugobsmarkers(ui, part, indent=0, **opts):
302 def _debugobsmarkers(ui, part, indent=0, **opts):
303 """display version and markers contained in 'data'"""
303 """display version and markers contained in 'data'"""
304 opts = pycompat.byteskwargs(opts)
304 opts = pycompat.byteskwargs(opts)
305 data = part.read()
305 data = part.read()
306 indent_string = ' ' * indent
306 indent_string = ' ' * indent
307 try:
307 try:
308 version, markers = obsolete._readmarkers(data)
308 version, markers = obsolete._readmarkers(data)
309 except error.UnknownVersion as exc:
309 except error.UnknownVersion as exc:
310 msg = "%sunsupported version: %s (%d bytes)\n"
310 msg = "%sunsupported version: %s (%d bytes)\n"
311 msg %= indent_string, exc.version, len(data)
311 msg %= indent_string, exc.version, len(data)
312 ui.write(msg)
312 ui.write(msg)
313 else:
313 else:
314 msg = "%sversion: %d (%d bytes)\n"
314 msg = "%sversion: %d (%d bytes)\n"
315 msg %= indent_string, version, len(data)
315 msg %= indent_string, version, len(data)
316 ui.write(msg)
316 ui.write(msg)
317 fm = ui.formatter('debugobsolete', opts)
317 fm = ui.formatter('debugobsolete', opts)
318 for rawmarker in sorted(markers):
318 for rawmarker in sorted(markers):
319 m = obsutil.marker(None, rawmarker)
319 m = obsutil.marker(None, rawmarker)
320 fm.startitem()
320 fm.startitem()
321 fm.plain(indent_string)
321 fm.plain(indent_string)
322 cmdutil.showmarker(fm, m)
322 cmdutil.showmarker(fm, m)
323 fm.end()
323 fm.end()
324
324
325 def _debugphaseheads(ui, data, indent=0):
325 def _debugphaseheads(ui, data, indent=0):
326 """display version and markers contained in 'data'"""
326 """display version and markers contained in 'data'"""
327 indent_string = ' ' * indent
327 indent_string = ' ' * indent
328 headsbyphase = phases.binarydecode(data)
328 headsbyphase = phases.binarydecode(data)
329 for phase in phases.allphases:
329 for phase in phases.allphases:
330 for head in headsbyphase[phase]:
330 for head in headsbyphase[phase]:
331 ui.write(indent_string)
331 ui.write(indent_string)
332 ui.write('%s %s\n' % (hex(head), phases.phasenames[phase]))
332 ui.write('%s %s\n' % (hex(head), phases.phasenames[phase]))
333
333
334 def _quasirepr(thing):
334 def _quasirepr(thing):
335 if isinstance(thing, (dict, util.sortdict, collections.OrderedDict)):
335 if isinstance(thing, (dict, util.sortdict, collections.OrderedDict)):
336 return '{%s}' % (
336 return '{%s}' % (
337 b', '.join(b'%s: %s' % (k, thing[k]) for k in sorted(thing)))
337 b', '.join(b'%s: %s' % (k, thing[k]) for k in sorted(thing)))
338 return pycompat.bytestr(repr(thing))
338 return pycompat.bytestr(repr(thing))
339
339
340 def _debugbundle2(ui, gen, all=None, **opts):
340 def _debugbundle2(ui, gen, all=None, **opts):
341 """lists the contents of a bundle2"""
341 """lists the contents of a bundle2"""
342 if not isinstance(gen, bundle2.unbundle20):
342 if not isinstance(gen, bundle2.unbundle20):
343 raise error.Abort(_('not a bundle2 file'))
343 raise error.Abort(_('not a bundle2 file'))
344 ui.write(('Stream params: %s\n' % _quasirepr(gen.params)))
344 ui.write(('Stream params: %s\n' % _quasirepr(gen.params)))
345 parttypes = opts.get(r'part_type', [])
345 parttypes = opts.get(r'part_type', [])
346 for part in gen.iterparts():
346 for part in gen.iterparts():
347 if parttypes and part.type not in parttypes:
347 if parttypes and part.type not in parttypes:
348 continue
348 continue
349 msg = '%s -- %s (mandatory: %r)\n'
349 msg = '%s -- %s (mandatory: %r)\n'
350 ui.write((msg % (part.type, _quasirepr(part.params), part.mandatory)))
350 ui.write((msg % (part.type, _quasirepr(part.params), part.mandatory)))
351 if part.type == 'changegroup':
351 if part.type == 'changegroup':
352 version = part.params.get('version', '01')
352 version = part.params.get('version', '01')
353 cg = changegroup.getunbundler(version, part, 'UN')
353 cg = changegroup.getunbundler(version, part, 'UN')
354 if not ui.quiet:
354 if not ui.quiet:
355 _debugchangegroup(ui, cg, all=all, indent=4, **opts)
355 _debugchangegroup(ui, cg, all=all, indent=4, **opts)
356 if part.type == 'obsmarkers':
356 if part.type == 'obsmarkers':
357 if not ui.quiet:
357 if not ui.quiet:
358 _debugobsmarkers(ui, part, indent=4, **opts)
358 _debugobsmarkers(ui, part, indent=4, **opts)
359 if part.type == 'phase-heads':
359 if part.type == 'phase-heads':
360 if not ui.quiet:
360 if not ui.quiet:
361 _debugphaseheads(ui, part, indent=4)
361 _debugphaseheads(ui, part, indent=4)
362
362
363 @command('debugbundle',
363 @command('debugbundle',
364 [('a', 'all', None, _('show all details')),
364 [('a', 'all', None, _('show all details')),
365 ('', 'part-type', [], _('show only the named part type')),
365 ('', 'part-type', [], _('show only the named part type')),
366 ('', 'spec', None, _('print the bundlespec of the bundle'))],
366 ('', 'spec', None, _('print the bundlespec of the bundle'))],
367 _('FILE'),
367 _('FILE'),
368 norepo=True)
368 norepo=True)
369 def debugbundle(ui, bundlepath, all=None, spec=None, **opts):
369 def debugbundle(ui, bundlepath, all=None, spec=None, **opts):
370 """lists the contents of a bundle"""
370 """lists the contents of a bundle"""
371 with hg.openpath(ui, bundlepath) as f:
371 with hg.openpath(ui, bundlepath) as f:
372 if spec:
372 if spec:
373 spec = exchange.getbundlespec(ui, f)
373 spec = exchange.getbundlespec(ui, f)
374 ui.write('%s\n' % spec)
374 ui.write('%s\n' % spec)
375 return
375 return
376
376
377 gen = exchange.readbundle(ui, f, bundlepath)
377 gen = exchange.readbundle(ui, f, bundlepath)
378 if isinstance(gen, bundle2.unbundle20):
378 if isinstance(gen, bundle2.unbundle20):
379 return _debugbundle2(ui, gen, all=all, **opts)
379 return _debugbundle2(ui, gen, all=all, **opts)
380 _debugchangegroup(ui, gen, all=all, **opts)
380 _debugchangegroup(ui, gen, all=all, **opts)
381
381
382 @command('debugcapabilities',
382 @command('debugcapabilities',
383 [], _('PATH'),
383 [], _('PATH'),
384 norepo=True)
384 norepo=True)
385 def debugcapabilities(ui, path, **opts):
385 def debugcapabilities(ui, path, **opts):
386 """lists the capabilities of a remote peer"""
386 """lists the capabilities of a remote peer"""
387 opts = pycompat.byteskwargs(opts)
387 opts = pycompat.byteskwargs(opts)
388 peer = hg.peer(ui, opts, path)
388 peer = hg.peer(ui, opts, path)
389 caps = peer.capabilities()
389 caps = peer.capabilities()
390 ui.write(('Main capabilities:\n'))
390 ui.write(('Main capabilities:\n'))
391 for c in sorted(caps):
391 for c in sorted(caps):
392 ui.write((' %s\n') % c)
392 ui.write((' %s\n') % c)
393 b2caps = bundle2.bundle2caps(peer)
393 b2caps = bundle2.bundle2caps(peer)
394 if b2caps:
394 if b2caps:
395 ui.write(('Bundle2 capabilities:\n'))
395 ui.write(('Bundle2 capabilities:\n'))
396 for key, values in sorted(b2caps.iteritems()):
396 for key, values in sorted(b2caps.iteritems()):
397 ui.write((' %s\n') % key)
397 ui.write((' %s\n') % key)
398 for v in values:
398 for v in values:
399 ui.write((' %s\n') % v)
399 ui.write((' %s\n') % v)
400
400
401 @command('debugcheckstate', [], '')
401 @command('debugcheckstate', [], '')
402 def debugcheckstate(ui, repo):
402 def debugcheckstate(ui, repo):
403 """validate the correctness of the current dirstate"""
403 """validate the correctness of the current dirstate"""
404 parent1, parent2 = repo.dirstate.parents()
404 parent1, parent2 = repo.dirstate.parents()
405 m1 = repo[parent1].manifest()
405 m1 = repo[parent1].manifest()
406 m2 = repo[parent2].manifest()
406 m2 = repo[parent2].manifest()
407 errors = 0
407 errors = 0
408 for f in repo.dirstate:
408 for f in repo.dirstate:
409 state = repo.dirstate[f]
409 state = repo.dirstate[f]
410 if state in "nr" and f not in m1:
410 if state in "nr" and f not in m1:
411 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
411 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
412 errors += 1
412 errors += 1
413 if state in "a" and f in m1:
413 if state in "a" and f in m1:
414 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
414 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
415 errors += 1
415 errors += 1
416 if state in "m" and f not in m1 and f not in m2:
416 if state in "m" and f not in m1 and f not in m2:
417 ui.warn(_("%s in state %s, but not in either manifest\n") %
417 ui.warn(_("%s in state %s, but not in either manifest\n") %
418 (f, state))
418 (f, state))
419 errors += 1
419 errors += 1
420 for f in m1:
420 for f in m1:
421 state = repo.dirstate[f]
421 state = repo.dirstate[f]
422 if state not in "nrm":
422 if state not in "nrm":
423 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
423 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
424 errors += 1
424 errors += 1
425 if errors:
425 if errors:
426 error = _(".hg/dirstate inconsistent with current parent's manifest")
426 error = _(".hg/dirstate inconsistent with current parent's manifest")
427 raise error.Abort(error)
427 raise error.Abort(error)
428
428
429 @command('debugcolor',
429 @command('debugcolor',
430 [('', 'style', None, _('show all configured styles'))],
430 [('', 'style', None, _('show all configured styles'))],
431 'hg debugcolor')
431 'hg debugcolor')
432 def debugcolor(ui, repo, **opts):
432 def debugcolor(ui, repo, **opts):
433 """show available color, effects or style"""
433 """show available color, effects or style"""
434 ui.write(('color mode: %s\n') % stringutil.pprint(ui._colormode))
434 ui.write(('color mode: %s\n') % stringutil.pprint(ui._colormode))
435 if opts.get(r'style'):
435 if opts.get(r'style'):
436 return _debugdisplaystyle(ui)
436 return _debugdisplaystyle(ui)
437 else:
437 else:
438 return _debugdisplaycolor(ui)
438 return _debugdisplaycolor(ui)
439
439
440 def _debugdisplaycolor(ui):
440 def _debugdisplaycolor(ui):
441 ui = ui.copy()
441 ui = ui.copy()
442 ui._styles.clear()
442 ui._styles.clear()
443 for effect in color._activeeffects(ui).keys():
443 for effect in color._activeeffects(ui).keys():
444 ui._styles[effect] = effect
444 ui._styles[effect] = effect
445 if ui._terminfoparams:
445 if ui._terminfoparams:
446 for k, v in ui.configitems('color'):
446 for k, v in ui.configitems('color'):
447 if k.startswith('color.'):
447 if k.startswith('color.'):
448 ui._styles[k] = k[6:]
448 ui._styles[k] = k[6:]
449 elif k.startswith('terminfo.'):
449 elif k.startswith('terminfo.'):
450 ui._styles[k] = k[9:]
450 ui._styles[k] = k[9:]
451 ui.write(_('available colors:\n'))
451 ui.write(_('available colors:\n'))
452 # sort label with a '_' after the other to group '_background' entry.
452 # sort label with a '_' after the other to group '_background' entry.
453 items = sorted(ui._styles.items(),
453 items = sorted(ui._styles.items(),
454 key=lambda i: ('_' in i[0], i[0], i[1]))
454 key=lambda i: ('_' in i[0], i[0], i[1]))
455 for colorname, label in items:
455 for colorname, label in items:
456 ui.write(('%s\n') % colorname, label=label)
456 ui.write(('%s\n') % colorname, label=label)
457
457
458 def _debugdisplaystyle(ui):
458 def _debugdisplaystyle(ui):
459 ui.write(_('available style:\n'))
459 ui.write(_('available style:\n'))
460 if not ui._styles:
460 if not ui._styles:
461 return
461 return
462 width = max(len(s) for s in ui._styles)
462 width = max(len(s) for s in ui._styles)
463 for label, effects in sorted(ui._styles.items()):
463 for label, effects in sorted(ui._styles.items()):
464 ui.write('%s' % label, label=label)
464 ui.write('%s' % label, label=label)
465 if effects:
465 if effects:
466 # 50
466 # 50
467 ui.write(': ')
467 ui.write(': ')
468 ui.write(' ' * (max(0, width - len(label))))
468 ui.write(' ' * (max(0, width - len(label))))
469 ui.write(', '.join(ui.label(e, e) for e in effects.split()))
469 ui.write(', '.join(ui.label(e, e) for e in effects.split()))
470 ui.write('\n')
470 ui.write('\n')
471
471
472 @command('debugcreatestreamclonebundle', [], 'FILE')
472 @command('debugcreatestreamclonebundle', [], 'FILE')
473 def debugcreatestreamclonebundle(ui, repo, fname):
473 def debugcreatestreamclonebundle(ui, repo, fname):
474 """create a stream clone bundle file
474 """create a stream clone bundle file
475
475
476 Stream bundles are special bundles that are essentially archives of
476 Stream bundles are special bundles that are essentially archives of
477 revlog files. They are commonly used for cloning very quickly.
477 revlog files. They are commonly used for cloning very quickly.
478 """
478 """
479 # TODO we may want to turn this into an abort when this functionality
479 # TODO we may want to turn this into an abort when this functionality
480 # is moved into `hg bundle`.
480 # is moved into `hg bundle`.
481 if phases.hassecret(repo):
481 if phases.hassecret(repo):
482 ui.warn(_('(warning: stream clone bundle will contain secret '
482 ui.warn(_('(warning: stream clone bundle will contain secret '
483 'revisions)\n'))
483 'revisions)\n'))
484
484
485 requirements, gen = streamclone.generatebundlev1(repo)
485 requirements, gen = streamclone.generatebundlev1(repo)
486 changegroup.writechunks(ui, gen, fname)
486 changegroup.writechunks(ui, gen, fname)
487
487
488 ui.write(_('bundle requirements: %s\n') % ', '.join(sorted(requirements)))
488 ui.write(_('bundle requirements: %s\n') % ', '.join(sorted(requirements)))
489
489
490 @command('debugdag',
490 @command('debugdag',
491 [('t', 'tags', None, _('use tags as labels')),
491 [('t', 'tags', None, _('use tags as labels')),
492 ('b', 'branches', None, _('annotate with branch names')),
492 ('b', 'branches', None, _('annotate with branch names')),
493 ('', 'dots', None, _('use dots for runs')),
493 ('', 'dots', None, _('use dots for runs')),
494 ('s', 'spaces', None, _('separate elements by spaces'))],
494 ('s', 'spaces', None, _('separate elements by spaces'))],
495 _('[OPTION]... [FILE [REV]...]'),
495 _('[OPTION]... [FILE [REV]...]'),
496 optionalrepo=True)
496 optionalrepo=True)
497 def debugdag(ui, repo, file_=None, *revs, **opts):
497 def debugdag(ui, repo, file_=None, *revs, **opts):
498 """format the changelog or an index DAG as a concise textual description
498 """format the changelog or an index DAG as a concise textual description
499
499
500 If you pass a revlog index, the revlog's DAG is emitted. If you list
500 If you pass a revlog index, the revlog's DAG is emitted. If you list
501 revision numbers, they get labeled in the output as rN.
501 revision numbers, they get labeled in the output as rN.
502
502
503 Otherwise, the changelog DAG of the current repo is emitted.
503 Otherwise, the changelog DAG of the current repo is emitted.
504 """
504 """
505 spaces = opts.get(r'spaces')
505 spaces = opts.get(r'spaces')
506 dots = opts.get(r'dots')
506 dots = opts.get(r'dots')
507 if file_:
507 if file_:
508 rlog = revlog.revlog(vfsmod.vfs(encoding.getcwd(), audit=False),
508 rlog = revlog.revlog(vfsmod.vfs(encoding.getcwd(), audit=False),
509 file_)
509 file_)
510 revs = set((int(r) for r in revs))
510 revs = set((int(r) for r in revs))
511 def events():
511 def events():
512 for r in rlog:
512 for r in rlog:
513 yield 'n', (r, list(p for p in rlog.parentrevs(r)
513 yield 'n', (r, list(p for p in rlog.parentrevs(r)
514 if p != -1))
514 if p != -1))
515 if r in revs:
515 if r in revs:
516 yield 'l', (r, "r%i" % r)
516 yield 'l', (r, "r%i" % r)
517 elif repo:
517 elif repo:
518 cl = repo.changelog
518 cl = repo.changelog
519 tags = opts.get(r'tags')
519 tags = opts.get(r'tags')
520 branches = opts.get(r'branches')
520 branches = opts.get(r'branches')
521 if tags:
521 if tags:
522 labels = {}
522 labels = {}
523 for l, n in repo.tags().items():
523 for l, n in repo.tags().items():
524 labels.setdefault(cl.rev(n), []).append(l)
524 labels.setdefault(cl.rev(n), []).append(l)
525 def events():
525 def events():
526 b = "default"
526 b = "default"
527 for r in cl:
527 for r in cl:
528 if branches:
528 if branches:
529 newb = cl.read(cl.node(r))[5]['branch']
529 newb = cl.read(cl.node(r))[5]['branch']
530 if newb != b:
530 if newb != b:
531 yield 'a', newb
531 yield 'a', newb
532 b = newb
532 b = newb
533 yield 'n', (r, list(p for p in cl.parentrevs(r)
533 yield 'n', (r, list(p for p in cl.parentrevs(r)
534 if p != -1))
534 if p != -1))
535 if tags:
535 if tags:
536 ls = labels.get(r)
536 ls = labels.get(r)
537 if ls:
537 if ls:
538 for l in ls:
538 for l in ls:
539 yield 'l', (r, l)
539 yield 'l', (r, l)
540 else:
540 else:
541 raise error.Abort(_('need repo for changelog dag'))
541 raise error.Abort(_('need repo for changelog dag'))
542
542
543 for line in dagparser.dagtextlines(events(),
543 for line in dagparser.dagtextlines(events(),
544 addspaces=spaces,
544 addspaces=spaces,
545 wraplabels=True,
545 wraplabels=True,
546 wrapannotations=True,
546 wrapannotations=True,
547 wrapnonlinear=dots,
547 wrapnonlinear=dots,
548 usedots=dots,
548 usedots=dots,
549 maxlinewidth=70):
549 maxlinewidth=70):
550 ui.write(line)
550 ui.write(line)
551 ui.write("\n")
551 ui.write("\n")
552
552
553 @command('debugdata', cmdutil.debugrevlogopts, _('-c|-m|FILE REV'))
553 @command('debugdata', cmdutil.debugrevlogopts, _('-c|-m|FILE REV'))
554 def debugdata(ui, repo, file_, rev=None, **opts):
554 def debugdata(ui, repo, file_, rev=None, **opts):
555 """dump the contents of a data file revision"""
555 """dump the contents of a data file revision"""
556 opts = pycompat.byteskwargs(opts)
556 opts = pycompat.byteskwargs(opts)
557 if opts.get('changelog') or opts.get('manifest') or opts.get('dir'):
557 if opts.get('changelog') or opts.get('manifest') or opts.get('dir'):
558 if rev is not None:
558 if rev is not None:
559 raise error.CommandError('debugdata', _('invalid arguments'))
559 raise error.CommandError('debugdata', _('invalid arguments'))
560 file_, rev = None, file_
560 file_, rev = None, file_
561 elif rev is None:
561 elif rev is None:
562 raise error.CommandError('debugdata', _('invalid arguments'))
562 raise error.CommandError('debugdata', _('invalid arguments'))
563 r = cmdutil.openstorage(repo, 'debugdata', file_, opts)
563 r = cmdutil.openstorage(repo, 'debugdata', file_, opts)
564 try:
564 try:
565 ui.write(r.revision(r.lookup(rev), raw=True))
565 ui.write(r.revision(r.lookup(rev), raw=True))
566 except KeyError:
566 except KeyError:
567 raise error.Abort(_('invalid revision identifier %s') % rev)
567 raise error.Abort(_('invalid revision identifier %s') % rev)
568
568
569 @command('debugdate',
569 @command('debugdate',
570 [('e', 'extended', None, _('try extended date formats'))],
570 [('e', 'extended', None, _('try extended date formats'))],
571 _('[-e] DATE [RANGE]'),
571 _('[-e] DATE [RANGE]'),
572 norepo=True, optionalrepo=True)
572 norepo=True, optionalrepo=True)
573 def debugdate(ui, date, range=None, **opts):
573 def debugdate(ui, date, range=None, **opts):
574 """parse and display a date"""
574 """parse and display a date"""
575 if opts[r"extended"]:
575 if opts[r"extended"]:
576 d = dateutil.parsedate(date, util.extendeddateformats)
576 d = dateutil.parsedate(date, util.extendeddateformats)
577 else:
577 else:
578 d = dateutil.parsedate(date)
578 d = dateutil.parsedate(date)
579 ui.write(("internal: %d %d\n") % d)
579 ui.write(("internal: %d %d\n") % d)
580 ui.write(("standard: %s\n") % dateutil.datestr(d))
580 ui.write(("standard: %s\n") % dateutil.datestr(d))
581 if range:
581 if range:
582 m = dateutil.matchdate(range)
582 m = dateutil.matchdate(range)
583 ui.write(("match: %s\n") % m(d[0]))
583 ui.write(("match: %s\n") % m(d[0]))
584
584
585 @command('debugdeltachain',
585 @command('debugdeltachain',
586 cmdutil.debugrevlogopts + cmdutil.formatteropts,
586 cmdutil.debugrevlogopts + cmdutil.formatteropts,
587 _('-c|-m|FILE'),
587 _('-c|-m|FILE'),
588 optionalrepo=True)
588 optionalrepo=True)
589 def debugdeltachain(ui, repo, file_=None, **opts):
589 def debugdeltachain(ui, repo, file_=None, **opts):
590 """dump information about delta chains in a revlog
590 """dump information about delta chains in a revlog
591
591
592 Output can be templatized. Available template keywords are:
592 Output can be templatized. Available template keywords are:
593
593
594 :``rev``: revision number
594 :``rev``: revision number
595 :``chainid``: delta chain identifier (numbered by unique base)
595 :``chainid``: delta chain identifier (numbered by unique base)
596 :``chainlen``: delta chain length to this revision
596 :``chainlen``: delta chain length to this revision
597 :``prevrev``: previous revision in delta chain
597 :``prevrev``: previous revision in delta chain
598 :``deltatype``: role of delta / how it was computed
598 :``deltatype``: role of delta / how it was computed
599 :``compsize``: compressed size of revision
599 :``compsize``: compressed size of revision
600 :``uncompsize``: uncompressed size of revision
600 :``uncompsize``: uncompressed size of revision
601 :``chainsize``: total size of compressed revisions in chain
601 :``chainsize``: total size of compressed revisions in chain
602 :``chainratio``: total chain size divided by uncompressed revision size
602 :``chainratio``: total chain size divided by uncompressed revision size
603 (new delta chains typically start at ratio 2.00)
603 (new delta chains typically start at ratio 2.00)
604 :``lindist``: linear distance from base revision in delta chain to end
604 :``lindist``: linear distance from base revision in delta chain to end
605 of this revision
605 of this revision
606 :``extradist``: total size of revisions not part of this delta chain from
606 :``extradist``: total size of revisions not part of this delta chain from
607 base of delta chain to end of this revision; a measurement
607 base of delta chain to end of this revision; a measurement
608 of how much extra data we need to read/seek across to read
608 of how much extra data we need to read/seek across to read
609 the delta chain for this revision
609 the delta chain for this revision
610 :``extraratio``: extradist divided by chainsize; another representation of
610 :``extraratio``: extradist divided by chainsize; another representation of
611 how much unrelated data is needed to load this delta chain
611 how much unrelated data is needed to load this delta chain
612
612
613 If the repository is configured to use the sparse read, additional keywords
613 If the repository is configured to use the sparse read, additional keywords
614 are available:
614 are available:
615
615
616 :``readsize``: total size of data read from the disk for a revision
616 :``readsize``: total size of data read from the disk for a revision
617 (sum of the sizes of all the blocks)
617 (sum of the sizes of all the blocks)
618 :``largestblock``: size of the largest block of data read from the disk
618 :``largestblock``: size of the largest block of data read from the disk
619 :``readdensity``: density of useful bytes in the data read from the disk
619 :``readdensity``: density of useful bytes in the data read from the disk
620 :``srchunks``: in how many data hunks the whole revision would be read
620 :``srchunks``: in how many data hunks the whole revision would be read
621
621
622 The sparse read can be enabled with experimental.sparse-read = True
622 The sparse read can be enabled with experimental.sparse-read = True
623 """
623 """
624 opts = pycompat.byteskwargs(opts)
624 opts = pycompat.byteskwargs(opts)
625 r = cmdutil.openrevlog(repo, 'debugdeltachain', file_, opts)
625 r = cmdutil.openrevlog(repo, 'debugdeltachain', file_, opts)
626 index = r.index
626 index = r.index
627 start = r.start
627 start = r.start
628 length = r.length
628 length = r.length
629 generaldelta = r.version & revlog.FLAG_GENERALDELTA
629 generaldelta = r.version & revlog.FLAG_GENERALDELTA
630 withsparseread = getattr(r, '_withsparseread', False)
630 withsparseread = getattr(r, '_withsparseread', False)
631
631
632 def revinfo(rev):
632 def revinfo(rev):
633 e = index[rev]
633 e = index[rev]
634 compsize = e[1]
634 compsize = e[1]
635 uncompsize = e[2]
635 uncompsize = e[2]
636 chainsize = 0
636 chainsize = 0
637
637
638 if generaldelta:
638 if generaldelta:
639 if e[3] == e[5]:
639 if e[3] == e[5]:
640 deltatype = 'p1'
640 deltatype = 'p1'
641 elif e[3] == e[6]:
641 elif e[3] == e[6]:
642 deltatype = 'p2'
642 deltatype = 'p2'
643 elif e[3] == rev - 1:
643 elif e[3] == rev - 1:
644 deltatype = 'prev'
644 deltatype = 'prev'
645 elif e[3] == rev:
645 elif e[3] == rev:
646 deltatype = 'base'
646 deltatype = 'base'
647 else:
647 else:
648 deltatype = 'other'
648 deltatype = 'other'
649 else:
649 else:
650 if e[3] == rev:
650 if e[3] == rev:
651 deltatype = 'base'
651 deltatype = 'base'
652 else:
652 else:
653 deltatype = 'prev'
653 deltatype = 'prev'
654
654
655 chain = r._deltachain(rev)[0]
655 chain = r._deltachain(rev)[0]
656 for iterrev in chain:
656 for iterrev in chain:
657 e = index[iterrev]
657 e = index[iterrev]
658 chainsize += e[1]
658 chainsize += e[1]
659
659
660 return compsize, uncompsize, deltatype, chain, chainsize
660 return compsize, uncompsize, deltatype, chain, chainsize
661
661
662 fm = ui.formatter('debugdeltachain', opts)
662 fm = ui.formatter('debugdeltachain', opts)
663
663
664 fm.plain(' rev chain# chainlen prev delta '
664 fm.plain(' rev chain# chainlen prev delta '
665 'size rawsize chainsize ratio lindist extradist '
665 'size rawsize chainsize ratio lindist extradist '
666 'extraratio')
666 'extraratio')
667 if withsparseread:
667 if withsparseread:
668 fm.plain(' readsize largestblk rddensity srchunks')
668 fm.plain(' readsize largestblk rddensity srchunks')
669 fm.plain('\n')
669 fm.plain('\n')
670
670
671 chainbases = {}
671 chainbases = {}
672 for rev in r:
672 for rev in r:
673 comp, uncomp, deltatype, chain, chainsize = revinfo(rev)
673 comp, uncomp, deltatype, chain, chainsize = revinfo(rev)
674 chainbase = chain[0]
674 chainbase = chain[0]
675 chainid = chainbases.setdefault(chainbase, len(chainbases) + 1)
675 chainid = chainbases.setdefault(chainbase, len(chainbases) + 1)
676 basestart = start(chainbase)
676 basestart = start(chainbase)
677 revstart = start(rev)
677 revstart = start(rev)
678 lineardist = revstart + comp - basestart
678 lineardist = revstart + comp - basestart
679 extradist = lineardist - chainsize
679 extradist = lineardist - chainsize
680 try:
680 try:
681 prevrev = chain[-2]
681 prevrev = chain[-2]
682 except IndexError:
682 except IndexError:
683 prevrev = -1
683 prevrev = -1
684
684
685 if uncomp != 0:
685 if uncomp != 0:
686 chainratio = float(chainsize) / float(uncomp)
686 chainratio = float(chainsize) / float(uncomp)
687 else:
687 else:
688 chainratio = chainsize
688 chainratio = chainsize
689
689
690 if chainsize != 0:
690 if chainsize != 0:
691 extraratio = float(extradist) / float(chainsize)
691 extraratio = float(extradist) / float(chainsize)
692 else:
692 else:
693 extraratio = extradist
693 extraratio = extradist
694
694
695 fm.startitem()
695 fm.startitem()
696 fm.write('rev chainid chainlen prevrev deltatype compsize '
696 fm.write('rev chainid chainlen prevrev deltatype compsize '
697 'uncompsize chainsize chainratio lindist extradist '
697 'uncompsize chainsize chainratio lindist extradist '
698 'extraratio',
698 'extraratio',
699 '%7d %7d %8d %8d %7s %10d %10d %10d %9.5f %9d %9d %10.5f',
699 '%7d %7d %8d %8d %7s %10d %10d %10d %9.5f %9d %9d %10.5f',
700 rev, chainid, len(chain), prevrev, deltatype, comp,
700 rev, chainid, len(chain), prevrev, deltatype, comp,
701 uncomp, chainsize, chainratio, lineardist, extradist,
701 uncomp, chainsize, chainratio, lineardist, extradist,
702 extraratio,
702 extraratio,
703 rev=rev, chainid=chainid, chainlen=len(chain),
703 rev=rev, chainid=chainid, chainlen=len(chain),
704 prevrev=prevrev, deltatype=deltatype, compsize=comp,
704 prevrev=prevrev, deltatype=deltatype, compsize=comp,
705 uncompsize=uncomp, chainsize=chainsize,
705 uncompsize=uncomp, chainsize=chainsize,
706 chainratio=chainratio, lindist=lineardist,
706 chainratio=chainratio, lindist=lineardist,
707 extradist=extradist, extraratio=extraratio)
707 extradist=extradist, extraratio=extraratio)
708 if withsparseread:
708 if withsparseread:
709 readsize = 0
709 readsize = 0
710 largestblock = 0
710 largestblock = 0
711 srchunks = 0
711 srchunks = 0
712
712
713 for revschunk in deltautil.slicechunk(r, chain):
713 for revschunk in deltautil.slicechunk(r, chain):
714 srchunks += 1
714 srchunks += 1
715 blkend = start(revschunk[-1]) + length(revschunk[-1])
715 blkend = start(revschunk[-1]) + length(revschunk[-1])
716 blksize = blkend - start(revschunk[0])
716 blksize = blkend - start(revschunk[0])
717
717
718 readsize += blksize
718 readsize += blksize
719 if largestblock < blksize:
719 if largestblock < blksize:
720 largestblock = blksize
720 largestblock = blksize
721
721
722 if readsize:
722 if readsize:
723 readdensity = float(chainsize) / float(readsize)
723 readdensity = float(chainsize) / float(readsize)
724 else:
724 else:
725 readdensity = 1
725 readdensity = 1
726
726
727 fm.write('readsize largestblock readdensity srchunks',
727 fm.write('readsize largestblock readdensity srchunks',
728 ' %10d %10d %9.5f %8d',
728 ' %10d %10d %9.5f %8d',
729 readsize, largestblock, readdensity, srchunks,
729 readsize, largestblock, readdensity, srchunks,
730 readsize=readsize, largestblock=largestblock,
730 readsize=readsize, largestblock=largestblock,
731 readdensity=readdensity, srchunks=srchunks)
731 readdensity=readdensity, srchunks=srchunks)
732
732
733 fm.plain('\n')
733 fm.plain('\n')
734
734
735 fm.end()
735 fm.end()
736
736
737 @command('debugdirstate|debugstate',
737 @command('debugdirstate|debugstate',
738 [('', 'nodates', None, _('do not display the saved mtime (DEPRECATED)')),
738 [('', 'nodates', None, _('do not display the saved mtime (DEPRECATED)')),
739 ('', 'dates', True, _('display the saved mtime')),
739 ('', 'dates', True, _('display the saved mtime')),
740 ('', 'datesort', None, _('sort by saved mtime'))],
740 ('', 'datesort', None, _('sort by saved mtime'))],
741 _('[OPTION]...'))
741 _('[OPTION]...'))
742 def debugstate(ui, repo, **opts):
742 def debugstate(ui, repo, **opts):
743 """show the contents of the current dirstate"""
743 """show the contents of the current dirstate"""
744
744
745 nodates = not opts[r'dates']
745 nodates = not opts[r'dates']
746 if opts.get(r'nodates') is not None:
746 if opts.get(r'nodates') is not None:
747 nodates = True
747 nodates = True
748 datesort = opts.get(r'datesort')
748 datesort = opts.get(r'datesort')
749
749
750 if datesort:
750 if datesort:
751 keyfunc = lambda x: (x[1][3], x[0]) # sort by mtime, then by filename
751 keyfunc = lambda x: (x[1][3], x[0]) # sort by mtime, then by filename
752 else:
752 else:
753 keyfunc = None # sort by filename
753 keyfunc = None # sort by filename
754 for file_, ent in sorted(repo.dirstate._map.iteritems(), key=keyfunc):
754 for file_, ent in sorted(repo.dirstate._map.iteritems(), key=keyfunc):
755 if ent[3] == -1:
755 if ent[3] == -1:
756 timestr = 'unset '
756 timestr = 'unset '
757 elif nodates:
757 elif nodates:
758 timestr = 'set '
758 timestr = 'set '
759 else:
759 else:
760 timestr = time.strftime(r"%Y-%m-%d %H:%M:%S ",
760 timestr = time.strftime(r"%Y-%m-%d %H:%M:%S ",
761 time.localtime(ent[3]))
761 time.localtime(ent[3]))
762 timestr = encoding.strtolocal(timestr)
762 timestr = encoding.strtolocal(timestr)
763 if ent[1] & 0o20000:
763 if ent[1] & 0o20000:
764 mode = 'lnk'
764 mode = 'lnk'
765 else:
765 else:
766 mode = '%3o' % (ent[1] & 0o777 & ~util.umask)
766 mode = '%3o' % (ent[1] & 0o777 & ~util.umask)
767 ui.write("%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
767 ui.write("%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
768 for f in repo.dirstate.copies():
768 for f in repo.dirstate.copies():
769 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
769 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
770
770
771 @command('debugdiscovery',
771 @command('debugdiscovery',
772 [('', 'old', None, _('use old-style discovery')),
772 [('', 'old', None, _('use old-style discovery')),
773 ('', 'nonheads', None,
773 ('', 'nonheads', None,
774 _('use old-style discovery with non-heads included')),
774 _('use old-style discovery with non-heads included')),
775 ('', 'rev', [], 'restrict discovery to this set of revs'),
775 ('', 'rev', [], 'restrict discovery to this set of revs'),
776 ('', 'seed', '12323', 'specify the random seed use for discovery'),
776 ('', 'seed', '12323', 'specify the random seed use for discovery'),
777 ] + cmdutil.remoteopts,
777 ] + cmdutil.remoteopts,
778 _('[--rev REV] [OTHER]'))
778 _('[--rev REV] [OTHER]'))
779 def debugdiscovery(ui, repo, remoteurl="default", **opts):
779 def debugdiscovery(ui, repo, remoteurl="default", **opts):
780 """runs the changeset discovery protocol in isolation"""
780 """runs the changeset discovery protocol in isolation"""
781 opts = pycompat.byteskwargs(opts)
781 opts = pycompat.byteskwargs(opts)
782 remoteurl, branches = hg.parseurl(ui.expandpath(remoteurl))
782 remoteurl, branches = hg.parseurl(ui.expandpath(remoteurl))
783 remote = hg.peer(repo, opts, remoteurl)
783 remote = hg.peer(repo, opts, remoteurl)
784 ui.status(_('comparing with %s\n') % util.hidepassword(remoteurl))
784 ui.status(_('comparing with %s\n') % util.hidepassword(remoteurl))
785
785
786 # make sure tests are repeatable
786 # make sure tests are repeatable
787 random.seed(int(opts['seed']))
787 random.seed(int(opts['seed']))
788
788
789
789
790
790
791 if opts.get('old'):
791 if opts.get('old'):
792 def doit(pushedrevs, remoteheads, remote=remote):
792 def doit(pushedrevs, remoteheads, remote=remote):
793 if not util.safehasattr(remote, 'branches'):
793 if not util.safehasattr(remote, 'branches'):
794 # enable in-client legacy support
794 # enable in-client legacy support
795 remote = localrepo.locallegacypeer(remote.local())
795 remote = localrepo.locallegacypeer(remote.local())
796 common, _in, hds = treediscovery.findcommonincoming(repo, remote,
796 common, _in, hds = treediscovery.findcommonincoming(repo, remote,
797 force=True)
797 force=True)
798 common = set(common)
798 common = set(common)
799 if not opts.get('nonheads'):
799 if not opts.get('nonheads'):
800 ui.write(("unpruned common: %s\n") %
800 ui.write(("unpruned common: %s\n") %
801 " ".join(sorted(short(n) for n in common)))
801 " ".join(sorted(short(n) for n in common)))
802
802
803 clnode = repo.changelog.node
803 clnode = repo.changelog.node
804 common = repo.revs('heads(::%ln)', common)
804 common = repo.revs('heads(::%ln)', common)
805 common = {clnode(r) for r in common}
805 common = {clnode(r) for r in common}
806 return common, hds
806 return common, hds
807 else:
807 else:
808 def doit(pushedrevs, remoteheads, remote=remote):
808 def doit(pushedrevs, remoteheads, remote=remote):
809 nodes = None
809 nodes = None
810 if pushedrevs:
810 if pushedrevs:
811 revs = scmutil.revrange(repo, pushedrevs)
811 revs = scmutil.revrange(repo, pushedrevs)
812 nodes = [repo[r].node() for r in revs]
812 nodes = [repo[r].node() for r in revs]
813 common, any, hds = setdiscovery.findcommonheads(ui, repo, remote,
813 common, any, hds = setdiscovery.findcommonheads(ui, repo, remote,
814 ancestorsof=nodes)
814 ancestorsof=nodes)
815 return common, hds
815 return common, hds
816
816
817 remoterevs, _checkout = hg.addbranchrevs(repo, remote, branches, revs=None)
817 remoterevs, _checkout = hg.addbranchrevs(repo, remote, branches, revs=None)
818 localrevs = opts['rev']
818 localrevs = opts['rev']
819 with util.timedcm('debug-discovery') as t:
819 with util.timedcm('debug-discovery') as t:
820 common, hds = doit(localrevs, remoterevs)
820 common, hds = doit(localrevs, remoterevs)
821
821
822 # compute all statistics
822 # compute all statistics
823 common = set(common)
823 common = set(common)
824 rheads = set(hds)
824 rheads = set(hds)
825 lheads = set(repo.heads())
825 lheads = set(repo.heads())
826
826
827 data = {}
827 data = {}
828 data['elapsed'] = t.elapsed
828 data['elapsed'] = t.elapsed
829 data['nb-common'] = len(common)
829 data['nb-common'] = len(common)
830 data['nb-common-local'] = len(common & lheads)
830 data['nb-common-local'] = len(common & lheads)
831 data['nb-common-remote'] = len(common & rheads)
831 data['nb-common-remote'] = len(common & rheads)
832 data['nb-local'] = len(lheads)
832 data['nb-local'] = len(lheads)
833 data['nb-local-missing'] = data['nb-local'] - data['nb-common-local']
833 data['nb-local-missing'] = data['nb-local'] - data['nb-common-local']
834 data['nb-remote'] = len(rheads)
834 data['nb-remote'] = len(rheads)
835 data['nb-remote-unknown'] = data['nb-remote'] - data['nb-common-remote']
835 data['nb-remote-unknown'] = data['nb-remote'] - data['nb-common-remote']
836 data['nb-revs'] = len(repo.revs('all()'))
836 data['nb-revs'] = len(repo.revs('all()'))
837 data['nb-revs-common'] = len(repo.revs('::%ln', common))
837 data['nb-revs-common'] = len(repo.revs('::%ln', common))
838 data['nb-revs-missing'] = data['nb-revs'] - data['nb-revs-common']
838 data['nb-revs-missing'] = data['nb-revs'] - data['nb-revs-common']
839
839
840 # display discovery summary
840 # display discovery summary
841 ui.write(("elapsed time: %(elapsed)f seconds\n") % data)
841 ui.write(("elapsed time: %(elapsed)f seconds\n") % data)
842 ui.write(("heads summary:\n"))
842 ui.write(("heads summary:\n"))
843 ui.write((" total common heads: %(nb-common)9d\n") % data)
843 ui.write((" total common heads: %(nb-common)9d\n") % data)
844 ui.write((" also local heads: %(nb-common-local)9d\n") % data)
844 ui.write((" also local heads: %(nb-common-local)9d\n") % data)
845 ui.write((" also remote heads: %(nb-common-remote)9d\n") % data)
845 ui.write((" also remote heads: %(nb-common-remote)9d\n") % data)
846 ui.write((" local heads: %(nb-local)9d\n") % data)
846 ui.write((" local heads: %(nb-local)9d\n") % data)
847 ui.write((" common: %(nb-common-local)9d\n") % data)
847 ui.write((" common: %(nb-common-local)9d\n") % data)
848 ui.write((" missing: %(nb-local-missing)9d\n") % data)
848 ui.write((" missing: %(nb-local-missing)9d\n") % data)
849 ui.write((" remote heads: %(nb-remote)9d\n") % data)
849 ui.write((" remote heads: %(nb-remote)9d\n") % data)
850 ui.write((" common: %(nb-common-remote)9d\n") % data)
850 ui.write((" common: %(nb-common-remote)9d\n") % data)
851 ui.write((" unknown: %(nb-remote-unknown)9d\n") % data)
851 ui.write((" unknown: %(nb-remote-unknown)9d\n") % data)
852 ui.write(("local changesets: %(nb-revs)9d\n") % data)
852 ui.write(("local changesets: %(nb-revs)9d\n") % data)
853 ui.write((" common: %(nb-revs-common)9d\n") % data)
853 ui.write((" common: %(nb-revs-common)9d\n") % data)
854 ui.write((" missing: %(nb-revs-missing)9d\n") % data)
854 ui.write((" missing: %(nb-revs-missing)9d\n") % data)
855
855
856 if ui.verbose:
856 if ui.verbose:
857 ui.write(("common heads: %s\n") %
857 ui.write(("common heads: %s\n") %
858 " ".join(sorted(short(n) for n in common)))
858 " ".join(sorted(short(n) for n in common)))
859
859
860 _chunksize = 4 << 10
860 _chunksize = 4 << 10
861
861
862 @command('debugdownload',
862 @command('debugdownload',
863 [
863 [
864 ('o', 'output', '', _('path')),
864 ('o', 'output', '', _('path')),
865 ],
865 ],
866 optionalrepo=True)
866 optionalrepo=True)
867 def debugdownload(ui, repo, url, output=None, **opts):
867 def debugdownload(ui, repo, url, output=None, **opts):
868 """download a resource using Mercurial logic and config
868 """download a resource using Mercurial logic and config
869 """
869 """
870 fh = urlmod.open(ui, url, output)
870 fh = urlmod.open(ui, url, output)
871
871
872 dest = ui
872 dest = ui
873 if output:
873 if output:
874 dest = open(output, "wb", _chunksize)
874 dest = open(output, "wb", _chunksize)
875 try:
875 try:
876 data = fh.read(_chunksize)
876 data = fh.read(_chunksize)
877 while data:
877 while data:
878 dest.write(data)
878 dest.write(data)
879 data = fh.read(_chunksize)
879 data = fh.read(_chunksize)
880 finally:
880 finally:
881 if output:
881 if output:
882 dest.close()
882 dest.close()
883
883
884 @command('debugextensions', cmdutil.formatteropts, [], optionalrepo=True)
884 @command('debugextensions', cmdutil.formatteropts, [], optionalrepo=True)
885 def debugextensions(ui, repo, **opts):
885 def debugextensions(ui, repo, **opts):
886 '''show information about active extensions'''
886 '''show information about active extensions'''
887 opts = pycompat.byteskwargs(opts)
887 opts = pycompat.byteskwargs(opts)
888 exts = extensions.extensions(ui)
888 exts = extensions.extensions(ui)
889 hgver = util.version()
889 hgver = util.version()
890 fm = ui.formatter('debugextensions', opts)
890 fm = ui.formatter('debugextensions', opts)
891 for extname, extmod in sorted(exts, key=operator.itemgetter(0)):
891 for extname, extmod in sorted(exts, key=operator.itemgetter(0)):
892 isinternal = extensions.ismoduleinternal(extmod)
892 isinternal = extensions.ismoduleinternal(extmod)
893 extsource = pycompat.fsencode(extmod.__file__)
893 extsource = pycompat.fsencode(extmod.__file__)
894 if isinternal:
894 if isinternal:
895 exttestedwith = [] # never expose magic string to users
895 exttestedwith = [] # never expose magic string to users
896 else:
896 else:
897 exttestedwith = getattr(extmod, 'testedwith', '').split()
897 exttestedwith = getattr(extmod, 'testedwith', '').split()
898 extbuglink = getattr(extmod, 'buglink', None)
898 extbuglink = getattr(extmod, 'buglink', None)
899
899
900 fm.startitem()
900 fm.startitem()
901
901
902 if ui.quiet or ui.verbose:
902 if ui.quiet or ui.verbose:
903 fm.write('name', '%s\n', extname)
903 fm.write('name', '%s\n', extname)
904 else:
904 else:
905 fm.write('name', '%s', extname)
905 fm.write('name', '%s', extname)
906 if isinternal or hgver in exttestedwith:
906 if isinternal or hgver in exttestedwith:
907 fm.plain('\n')
907 fm.plain('\n')
908 elif not exttestedwith:
908 elif not exttestedwith:
909 fm.plain(_(' (untested!)\n'))
909 fm.plain(_(' (untested!)\n'))
910 else:
910 else:
911 lasttestedversion = exttestedwith[-1]
911 lasttestedversion = exttestedwith[-1]
912 fm.plain(' (%s!)\n' % lasttestedversion)
912 fm.plain(' (%s!)\n' % lasttestedversion)
913
913
914 fm.condwrite(ui.verbose and extsource, 'source',
914 fm.condwrite(ui.verbose and extsource, 'source',
915 _(' location: %s\n'), extsource or "")
915 _(' location: %s\n'), extsource or "")
916
916
917 if ui.verbose:
917 if ui.verbose:
918 fm.plain(_(' bundled: %s\n') % ['no', 'yes'][isinternal])
918 fm.plain(_(' bundled: %s\n') % ['no', 'yes'][isinternal])
919 fm.data(bundled=isinternal)
919 fm.data(bundled=isinternal)
920
920
921 fm.condwrite(ui.verbose and exttestedwith, 'testedwith',
921 fm.condwrite(ui.verbose and exttestedwith, 'testedwith',
922 _(' tested with: %s\n'),
922 _(' tested with: %s\n'),
923 fm.formatlist(exttestedwith, name='ver'))
923 fm.formatlist(exttestedwith, name='ver'))
924
924
925 fm.condwrite(ui.verbose and extbuglink, 'buglink',
925 fm.condwrite(ui.verbose and extbuglink, 'buglink',
926 _(' bug reporting: %s\n'), extbuglink or "")
926 _(' bug reporting: %s\n'), extbuglink or "")
927
927
928 fm.end()
928 fm.end()
929
929
930 @command('debugfileset',
930 @command('debugfileset',
931 [('r', 'rev', '', _('apply the filespec on this revision'), _('REV')),
931 [('r', 'rev', '', _('apply the filespec on this revision'), _('REV')),
932 ('', 'all-files', False,
932 ('', 'all-files', False,
933 _('test files from all revisions and working directory')),
933 _('test files from all revisions and working directory')),
934 ('s', 'show-matcher', None,
934 ('s', 'show-matcher', None,
935 _('print internal representation of matcher')),
935 _('print internal representation of matcher')),
936 ('p', 'show-stage', [],
936 ('p', 'show-stage', [],
937 _('print parsed tree at the given stage'), _('NAME'))],
937 _('print parsed tree at the given stage'), _('NAME'))],
938 _('[-r REV] [--all-files] [OPTION]... FILESPEC'))
938 _('[-r REV] [--all-files] [OPTION]... FILESPEC'))
939 def debugfileset(ui, repo, expr, **opts):
939 def debugfileset(ui, repo, expr, **opts):
940 '''parse and apply a fileset specification'''
940 '''parse and apply a fileset specification'''
941 from . import fileset
941 from . import fileset
942 fileset.symbols # force import of fileset so we have predicates to optimize
942 fileset.symbols # force import of fileset so we have predicates to optimize
943 opts = pycompat.byteskwargs(opts)
943 opts = pycompat.byteskwargs(opts)
944 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
944 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
945
945
946 stages = [
946 stages = [
947 ('parsed', pycompat.identity),
947 ('parsed', pycompat.identity),
948 ('analyzed', filesetlang.analyze),
948 ('analyzed', filesetlang.analyze),
949 ('optimized', filesetlang.optimize),
949 ('optimized', filesetlang.optimize),
950 ]
950 ]
951 stagenames = set(n for n, f in stages)
951 stagenames = set(n for n, f in stages)
952
952
953 showalways = set()
953 showalways = set()
954 if ui.verbose and not opts['show_stage']:
954 if ui.verbose and not opts['show_stage']:
955 # show parsed tree by --verbose (deprecated)
955 # show parsed tree by --verbose (deprecated)
956 showalways.add('parsed')
956 showalways.add('parsed')
957 if opts['show_stage'] == ['all']:
957 if opts['show_stage'] == ['all']:
958 showalways.update(stagenames)
958 showalways.update(stagenames)
959 else:
959 else:
960 for n in opts['show_stage']:
960 for n in opts['show_stage']:
961 if n not in stagenames:
961 if n not in stagenames:
962 raise error.Abort(_('invalid stage name: %s') % n)
962 raise error.Abort(_('invalid stage name: %s') % n)
963 showalways.update(opts['show_stage'])
963 showalways.update(opts['show_stage'])
964
964
965 tree = filesetlang.parse(expr)
965 tree = filesetlang.parse(expr)
966 for n, f in stages:
966 for n, f in stages:
967 tree = f(tree)
967 tree = f(tree)
968 if n in showalways:
968 if n in showalways:
969 if opts['show_stage'] or n != 'parsed':
969 if opts['show_stage'] or n != 'parsed':
970 ui.write(("* %s:\n") % n)
970 ui.write(("* %s:\n") % n)
971 ui.write(filesetlang.prettyformat(tree), "\n")
971 ui.write(filesetlang.prettyformat(tree), "\n")
972
972
973 files = set()
973 files = set()
974 if opts['all_files']:
974 if opts['all_files']:
975 for r in repo:
975 for r in repo:
976 c = repo[r]
976 c = repo[r]
977 files.update(c.files())
977 files.update(c.files())
978 files.update(c.substate)
978 files.update(c.substate)
979 if opts['all_files'] or ctx.rev() is None:
979 if opts['all_files'] or ctx.rev() is None:
980 wctx = repo[None]
980 wctx = repo[None]
981 files.update(repo.dirstate.walk(scmutil.matchall(repo),
981 files.update(repo.dirstate.walk(scmutil.matchall(repo),
982 subrepos=list(wctx.substate),
982 subrepos=list(wctx.substate),
983 unknown=True, ignored=True))
983 unknown=True, ignored=True))
984 files.update(wctx.substate)
984 files.update(wctx.substate)
985 else:
985 else:
986 files.update(ctx.files())
986 files.update(ctx.files())
987 files.update(ctx.substate)
987 files.update(ctx.substate)
988
988
989 m = ctx.matchfileset(expr)
989 m = ctx.matchfileset(expr)
990 if opts['show_matcher'] or (opts['show_matcher'] is None and ui.verbose):
990 if opts['show_matcher'] or (opts['show_matcher'] is None and ui.verbose):
991 ui.write(('* matcher:\n'), stringutil.prettyrepr(m), '\n')
991 ui.write(('* matcher:\n'), stringutil.prettyrepr(m), '\n')
992 for f in sorted(files):
992 for f in sorted(files):
993 if not m(f):
993 if not m(f):
994 continue
994 continue
995 ui.write("%s\n" % f)
995 ui.write("%s\n" % f)
996
996
997 @command('debugformat',
997 @command('debugformat',
998 [] + cmdutil.formatteropts)
998 [] + cmdutil.formatteropts)
999 def debugformat(ui, repo, **opts):
999 def debugformat(ui, repo, **opts):
1000 """display format information about the current repository
1000 """display format information about the current repository
1001
1001
1002 Use --verbose to get extra information about current config value and
1002 Use --verbose to get extra information about current config value and
1003 Mercurial default."""
1003 Mercurial default."""
1004 opts = pycompat.byteskwargs(opts)
1004 opts = pycompat.byteskwargs(opts)
1005 maxvariantlength = max(len(fv.name) for fv in upgrade.allformatvariant)
1005 maxvariantlength = max(len(fv.name) for fv in upgrade.allformatvariant)
1006 maxvariantlength = max(len('format-variant'), maxvariantlength)
1006 maxvariantlength = max(len('format-variant'), maxvariantlength)
1007
1007
1008 def makeformatname(name):
1008 def makeformatname(name):
1009 return '%s:' + (' ' * (maxvariantlength - len(name)))
1009 return '%s:' + (' ' * (maxvariantlength - len(name)))
1010
1010
1011 fm = ui.formatter('debugformat', opts)
1011 fm = ui.formatter('debugformat', opts)
1012 if fm.isplain():
1012 if fm.isplain():
1013 def formatvalue(value):
1013 def formatvalue(value):
1014 if util.safehasattr(value, 'startswith'):
1014 if util.safehasattr(value, 'startswith'):
1015 return value
1015 return value
1016 if value:
1016 if value:
1017 return 'yes'
1017 return 'yes'
1018 else:
1018 else:
1019 return 'no'
1019 return 'no'
1020 else:
1020 else:
1021 formatvalue = pycompat.identity
1021 formatvalue = pycompat.identity
1022
1022
1023 fm.plain('format-variant')
1023 fm.plain('format-variant')
1024 fm.plain(' ' * (maxvariantlength - len('format-variant')))
1024 fm.plain(' ' * (maxvariantlength - len('format-variant')))
1025 fm.plain(' repo')
1025 fm.plain(' repo')
1026 if ui.verbose:
1026 if ui.verbose:
1027 fm.plain(' config default')
1027 fm.plain(' config default')
1028 fm.plain('\n')
1028 fm.plain('\n')
1029 for fv in upgrade.allformatvariant:
1029 for fv in upgrade.allformatvariant:
1030 fm.startitem()
1030 fm.startitem()
1031 repovalue = fv.fromrepo(repo)
1031 repovalue = fv.fromrepo(repo)
1032 configvalue = fv.fromconfig(repo)
1032 configvalue = fv.fromconfig(repo)
1033
1033
1034 if repovalue != configvalue:
1034 if repovalue != configvalue:
1035 namelabel = 'formatvariant.name.mismatchconfig'
1035 namelabel = 'formatvariant.name.mismatchconfig'
1036 repolabel = 'formatvariant.repo.mismatchconfig'
1036 repolabel = 'formatvariant.repo.mismatchconfig'
1037 elif repovalue != fv.default:
1037 elif repovalue != fv.default:
1038 namelabel = 'formatvariant.name.mismatchdefault'
1038 namelabel = 'formatvariant.name.mismatchdefault'
1039 repolabel = 'formatvariant.repo.mismatchdefault'
1039 repolabel = 'formatvariant.repo.mismatchdefault'
1040 else:
1040 else:
1041 namelabel = 'formatvariant.name.uptodate'
1041 namelabel = 'formatvariant.name.uptodate'
1042 repolabel = 'formatvariant.repo.uptodate'
1042 repolabel = 'formatvariant.repo.uptodate'
1043
1043
1044 fm.write('name', makeformatname(fv.name), fv.name,
1044 fm.write('name', makeformatname(fv.name), fv.name,
1045 label=namelabel)
1045 label=namelabel)
1046 fm.write('repo', ' %3s', formatvalue(repovalue),
1046 fm.write('repo', ' %3s', formatvalue(repovalue),
1047 label=repolabel)
1047 label=repolabel)
1048 if fv.default != configvalue:
1048 if fv.default != configvalue:
1049 configlabel = 'formatvariant.config.special'
1049 configlabel = 'formatvariant.config.special'
1050 else:
1050 else:
1051 configlabel = 'formatvariant.config.default'
1051 configlabel = 'formatvariant.config.default'
1052 fm.condwrite(ui.verbose, 'config', ' %6s', formatvalue(configvalue),
1052 fm.condwrite(ui.verbose, 'config', ' %6s', formatvalue(configvalue),
1053 label=configlabel)
1053 label=configlabel)
1054 fm.condwrite(ui.verbose, 'default', ' %7s', formatvalue(fv.default),
1054 fm.condwrite(ui.verbose, 'default', ' %7s', formatvalue(fv.default),
1055 label='formatvariant.default')
1055 label='formatvariant.default')
1056 fm.plain('\n')
1056 fm.plain('\n')
1057 fm.end()
1057 fm.end()
1058
1058
1059 @command('debugfsinfo', [], _('[PATH]'), norepo=True)
1059 @command('debugfsinfo', [], _('[PATH]'), norepo=True)
1060 def debugfsinfo(ui, path="."):
1060 def debugfsinfo(ui, path="."):
1061 """show information detected about current filesystem"""
1061 """show information detected about current filesystem"""
1062 ui.write(('path: %s\n') % path)
1062 ui.write(('path: %s\n') % path)
1063 ui.write(('mounted on: %s\n') % (util.getfsmountpoint(path) or '(unknown)'))
1063 ui.write(('mounted on: %s\n') % (util.getfsmountpoint(path) or '(unknown)'))
1064 ui.write(('exec: %s\n') % (util.checkexec(path) and 'yes' or 'no'))
1064 ui.write(('exec: %s\n') % (util.checkexec(path) and 'yes' or 'no'))
1065 ui.write(('fstype: %s\n') % (util.getfstype(path) or '(unknown)'))
1065 ui.write(('fstype: %s\n') % (util.getfstype(path) or '(unknown)'))
1066 ui.write(('symlink: %s\n') % (util.checklink(path) and 'yes' or 'no'))
1066 ui.write(('symlink: %s\n') % (util.checklink(path) and 'yes' or 'no'))
1067 ui.write(('hardlink: %s\n') % (util.checknlink(path) and 'yes' or 'no'))
1067 ui.write(('hardlink: %s\n') % (util.checknlink(path) and 'yes' or 'no'))
1068 casesensitive = '(unknown)'
1068 casesensitive = '(unknown)'
1069 try:
1069 try:
1070 with pycompat.namedtempfile(prefix='.debugfsinfo', dir=path) as f:
1070 with pycompat.namedtempfile(prefix='.debugfsinfo', dir=path) as f:
1071 casesensitive = util.fscasesensitive(f.name) and 'yes' or 'no'
1071 casesensitive = util.fscasesensitive(f.name) and 'yes' or 'no'
1072 except OSError:
1072 except OSError:
1073 pass
1073 pass
1074 ui.write(('case-sensitive: %s\n') % casesensitive)
1074 ui.write(('case-sensitive: %s\n') % casesensitive)
1075
1075
1076 @command('debuggetbundle',
1076 @command('debuggetbundle',
1077 [('H', 'head', [], _('id of head node'), _('ID')),
1077 [('H', 'head', [], _('id of head node'), _('ID')),
1078 ('C', 'common', [], _('id of common node'), _('ID')),
1078 ('C', 'common', [], _('id of common node'), _('ID')),
1079 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE'))],
1079 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE'))],
1080 _('REPO FILE [-H|-C ID]...'),
1080 _('REPO FILE [-H|-C ID]...'),
1081 norepo=True)
1081 norepo=True)
1082 def debuggetbundle(ui, repopath, bundlepath, head=None, common=None, **opts):
1082 def debuggetbundle(ui, repopath, bundlepath, head=None, common=None, **opts):
1083 """retrieves a bundle from a repo
1083 """retrieves a bundle from a repo
1084
1084
1085 Every ID must be a full-length hex node id string. Saves the bundle to the
1085 Every ID must be a full-length hex node id string. Saves the bundle to the
1086 given file.
1086 given file.
1087 """
1087 """
1088 opts = pycompat.byteskwargs(opts)
1088 opts = pycompat.byteskwargs(opts)
1089 repo = hg.peer(ui, opts, repopath)
1089 repo = hg.peer(ui, opts, repopath)
1090 if not repo.capable('getbundle'):
1090 if not repo.capable('getbundle'):
1091 raise error.Abort("getbundle() not supported by target repository")
1091 raise error.Abort("getbundle() not supported by target repository")
1092 args = {}
1092 args = {}
1093 if common:
1093 if common:
1094 args[r'common'] = [bin(s) for s in common]
1094 args[r'common'] = [bin(s) for s in common]
1095 if head:
1095 if head:
1096 args[r'heads'] = [bin(s) for s in head]
1096 args[r'heads'] = [bin(s) for s in head]
1097 # TODO: get desired bundlecaps from command line.
1097 # TODO: get desired bundlecaps from command line.
1098 args[r'bundlecaps'] = None
1098 args[r'bundlecaps'] = None
1099 bundle = repo.getbundle('debug', **args)
1099 bundle = repo.getbundle('debug', **args)
1100
1100
1101 bundletype = opts.get('type', 'bzip2').lower()
1101 bundletype = opts.get('type', 'bzip2').lower()
1102 btypes = {'none': 'HG10UN',
1102 btypes = {'none': 'HG10UN',
1103 'bzip2': 'HG10BZ',
1103 'bzip2': 'HG10BZ',
1104 'gzip': 'HG10GZ',
1104 'gzip': 'HG10GZ',
1105 'bundle2': 'HG20'}
1105 'bundle2': 'HG20'}
1106 bundletype = btypes.get(bundletype)
1106 bundletype = btypes.get(bundletype)
1107 if bundletype not in bundle2.bundletypes:
1107 if bundletype not in bundle2.bundletypes:
1108 raise error.Abort(_('unknown bundle type specified with --type'))
1108 raise error.Abort(_('unknown bundle type specified with --type'))
1109 bundle2.writebundle(ui, bundle, bundlepath, bundletype)
1109 bundle2.writebundle(ui, bundle, bundlepath, bundletype)
1110
1110
1111 @command('debugignore', [], '[FILE]')
1111 @command('debugignore', [], '[FILE]')
1112 def debugignore(ui, repo, *files, **opts):
1112 def debugignore(ui, repo, *files, **opts):
1113 """display the combined ignore pattern and information about ignored files
1113 """display the combined ignore pattern and information about ignored files
1114
1114
1115 With no argument display the combined ignore pattern.
1115 With no argument display the combined ignore pattern.
1116
1116
1117 Given space separated file names, shows if the given file is ignored and
1117 Given space separated file names, shows if the given file is ignored and
1118 if so, show the ignore rule (file and line number) that matched it.
1118 if so, show the ignore rule (file and line number) that matched it.
1119 """
1119 """
1120 ignore = repo.dirstate._ignore
1120 ignore = repo.dirstate._ignore
1121 if not files:
1121 if not files:
1122 # Show all the patterns
1122 # Show all the patterns
1123 ui.write("%s\n" % pycompat.byterepr(ignore))
1123 ui.write("%s\n" % pycompat.byterepr(ignore))
1124 else:
1124 else:
1125 m = scmutil.match(repo[None], pats=files)
1125 m = scmutil.match(repo[None], pats=files)
1126 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
1126 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
1127 for f in m.files():
1127 for f in m.files():
1128 nf = util.normpath(f)
1128 nf = util.normpath(f)
1129 ignored = None
1129 ignored = None
1130 ignoredata = None
1130 ignoredata = None
1131 if nf != '.':
1131 if nf != '.':
1132 if ignore(nf):
1132 if ignore(nf):
1133 ignored = nf
1133 ignored = nf
1134 ignoredata = repo.dirstate._ignorefileandline(nf)
1134 ignoredata = repo.dirstate._ignorefileandline(nf)
1135 else:
1135 else:
1136 for p in util.finddirs(nf):
1136 for p in util.finddirs(nf):
1137 if ignore(p):
1137 if ignore(p):
1138 ignored = p
1138 ignored = p
1139 ignoredata = repo.dirstate._ignorefileandline(p)
1139 ignoredata = repo.dirstate._ignorefileandline(p)
1140 break
1140 break
1141 if ignored:
1141 if ignored:
1142 if ignored == nf:
1142 if ignored == nf:
1143 ui.write(_("%s is ignored\n") % uipathfn(f))
1143 ui.write(_("%s is ignored\n") % uipathfn(f))
1144 else:
1144 else:
1145 ui.write(_("%s is ignored because of "
1145 ui.write(_("%s is ignored because of "
1146 "containing folder %s\n")
1146 "containing directory %s\n")
1147 % (uipathfn(f), ignored))
1147 % (uipathfn(f), ignored))
1148 ignorefile, lineno, line = ignoredata
1148 ignorefile, lineno, line = ignoredata
1149 ui.write(_("(ignore rule in %s, line %d: '%s')\n")
1149 ui.write(_("(ignore rule in %s, line %d: '%s')\n")
1150 % (ignorefile, lineno, line))
1150 % (ignorefile, lineno, line))
1151 else:
1151 else:
1152 ui.write(_("%s is not ignored\n") % uipathfn(f))
1152 ui.write(_("%s is not ignored\n") % uipathfn(f))
1153
1153
1154 @command('debugindex', cmdutil.debugrevlogopts + cmdutil.formatteropts,
1154 @command('debugindex', cmdutil.debugrevlogopts + cmdutil.formatteropts,
1155 _('-c|-m|FILE'))
1155 _('-c|-m|FILE'))
1156 def debugindex(ui, repo, file_=None, **opts):
1156 def debugindex(ui, repo, file_=None, **opts):
1157 """dump index data for a storage primitive"""
1157 """dump index data for a storage primitive"""
1158 opts = pycompat.byteskwargs(opts)
1158 opts = pycompat.byteskwargs(opts)
1159 store = cmdutil.openstorage(repo, 'debugindex', file_, opts)
1159 store = cmdutil.openstorage(repo, 'debugindex', file_, opts)
1160
1160
1161 if ui.debugflag:
1161 if ui.debugflag:
1162 shortfn = hex
1162 shortfn = hex
1163 else:
1163 else:
1164 shortfn = short
1164 shortfn = short
1165
1165
1166 idlen = 12
1166 idlen = 12
1167 for i in store:
1167 for i in store:
1168 idlen = len(shortfn(store.node(i)))
1168 idlen = len(shortfn(store.node(i)))
1169 break
1169 break
1170
1170
1171 fm = ui.formatter('debugindex', opts)
1171 fm = ui.formatter('debugindex', opts)
1172 fm.plain(b' rev linkrev %s %s p2\n' % (
1172 fm.plain(b' rev linkrev %s %s p2\n' % (
1173 b'nodeid'.ljust(idlen),
1173 b'nodeid'.ljust(idlen),
1174 b'p1'.ljust(idlen)))
1174 b'p1'.ljust(idlen)))
1175
1175
1176 for rev in store:
1176 for rev in store:
1177 node = store.node(rev)
1177 node = store.node(rev)
1178 parents = store.parents(node)
1178 parents = store.parents(node)
1179
1179
1180 fm.startitem()
1180 fm.startitem()
1181 fm.write(b'rev', b'%6d ', rev)
1181 fm.write(b'rev', b'%6d ', rev)
1182 fm.write(b'linkrev', '%7d ', store.linkrev(rev))
1182 fm.write(b'linkrev', '%7d ', store.linkrev(rev))
1183 fm.write(b'node', '%s ', shortfn(node))
1183 fm.write(b'node', '%s ', shortfn(node))
1184 fm.write(b'p1', '%s ', shortfn(parents[0]))
1184 fm.write(b'p1', '%s ', shortfn(parents[0]))
1185 fm.write(b'p2', '%s', shortfn(parents[1]))
1185 fm.write(b'p2', '%s', shortfn(parents[1]))
1186 fm.plain(b'\n')
1186 fm.plain(b'\n')
1187
1187
1188 fm.end()
1188 fm.end()
1189
1189
1190 @command('debugindexdot', cmdutil.debugrevlogopts,
1190 @command('debugindexdot', cmdutil.debugrevlogopts,
1191 _('-c|-m|FILE'), optionalrepo=True)
1191 _('-c|-m|FILE'), optionalrepo=True)
1192 def debugindexdot(ui, repo, file_=None, **opts):
1192 def debugindexdot(ui, repo, file_=None, **opts):
1193 """dump an index DAG as a graphviz dot file"""
1193 """dump an index DAG as a graphviz dot file"""
1194 opts = pycompat.byteskwargs(opts)
1194 opts = pycompat.byteskwargs(opts)
1195 r = cmdutil.openstorage(repo, 'debugindexdot', file_, opts)
1195 r = cmdutil.openstorage(repo, 'debugindexdot', file_, opts)
1196 ui.write(("digraph G {\n"))
1196 ui.write(("digraph G {\n"))
1197 for i in r:
1197 for i in r:
1198 node = r.node(i)
1198 node = r.node(i)
1199 pp = r.parents(node)
1199 pp = r.parents(node)
1200 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
1200 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
1201 if pp[1] != nullid:
1201 if pp[1] != nullid:
1202 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
1202 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
1203 ui.write("}\n")
1203 ui.write("}\n")
1204
1204
1205 @command('debugindexstats', [])
1205 @command('debugindexstats', [])
1206 def debugindexstats(ui, repo):
1206 def debugindexstats(ui, repo):
1207 """show stats related to the changelog index"""
1207 """show stats related to the changelog index"""
1208 repo.changelog.shortest(nullid, 1)
1208 repo.changelog.shortest(nullid, 1)
1209 index = repo.changelog.index
1209 index = repo.changelog.index
1210 if not util.safehasattr(index, 'stats'):
1210 if not util.safehasattr(index, 'stats'):
1211 raise error.Abort(_('debugindexstats only works with native code'))
1211 raise error.Abort(_('debugindexstats only works with native code'))
1212 for k, v in sorted(index.stats().items()):
1212 for k, v in sorted(index.stats().items()):
1213 ui.write('%s: %d\n' % (k, v))
1213 ui.write('%s: %d\n' % (k, v))
1214
1214
1215 @command('debuginstall', [] + cmdutil.formatteropts, '', norepo=True)
1215 @command('debuginstall', [] + cmdutil.formatteropts, '', norepo=True)
1216 def debuginstall(ui, **opts):
1216 def debuginstall(ui, **opts):
1217 '''test Mercurial installation
1217 '''test Mercurial installation
1218
1218
1219 Returns 0 on success.
1219 Returns 0 on success.
1220 '''
1220 '''
1221 opts = pycompat.byteskwargs(opts)
1221 opts = pycompat.byteskwargs(opts)
1222
1222
1223 problems = 0
1223 problems = 0
1224
1224
1225 fm = ui.formatter('debuginstall', opts)
1225 fm = ui.formatter('debuginstall', opts)
1226 fm.startitem()
1226 fm.startitem()
1227
1227
1228 # encoding
1228 # encoding
1229 fm.write('encoding', _("checking encoding (%s)...\n"), encoding.encoding)
1229 fm.write('encoding', _("checking encoding (%s)...\n"), encoding.encoding)
1230 err = None
1230 err = None
1231 try:
1231 try:
1232 codecs.lookup(pycompat.sysstr(encoding.encoding))
1232 codecs.lookup(pycompat.sysstr(encoding.encoding))
1233 except LookupError as inst:
1233 except LookupError as inst:
1234 err = stringutil.forcebytestr(inst)
1234 err = stringutil.forcebytestr(inst)
1235 problems += 1
1235 problems += 1
1236 fm.condwrite(err, 'encodingerror', _(" %s\n"
1236 fm.condwrite(err, 'encodingerror', _(" %s\n"
1237 " (check that your locale is properly set)\n"), err)
1237 " (check that your locale is properly set)\n"), err)
1238
1238
1239 # Python
1239 # Python
1240 fm.write('pythonexe', _("checking Python executable (%s)\n"),
1240 fm.write('pythonexe', _("checking Python executable (%s)\n"),
1241 pycompat.sysexecutable)
1241 pycompat.sysexecutable)
1242 fm.write('pythonver', _("checking Python version (%s)\n"),
1242 fm.write('pythonver', _("checking Python version (%s)\n"),
1243 ("%d.%d.%d" % sys.version_info[:3]))
1243 ("%d.%d.%d" % sys.version_info[:3]))
1244 fm.write('pythonlib', _("checking Python lib (%s)...\n"),
1244 fm.write('pythonlib', _("checking Python lib (%s)...\n"),
1245 os.path.dirname(pycompat.fsencode(os.__file__)))
1245 os.path.dirname(pycompat.fsencode(os.__file__)))
1246
1246
1247 security = set(sslutil.supportedprotocols)
1247 security = set(sslutil.supportedprotocols)
1248 if sslutil.hassni:
1248 if sslutil.hassni:
1249 security.add('sni')
1249 security.add('sni')
1250
1250
1251 fm.write('pythonsecurity', _("checking Python security support (%s)\n"),
1251 fm.write('pythonsecurity', _("checking Python security support (%s)\n"),
1252 fm.formatlist(sorted(security), name='protocol',
1252 fm.formatlist(sorted(security), name='protocol',
1253 fmt='%s', sep=','))
1253 fmt='%s', sep=','))
1254
1254
1255 # These are warnings, not errors. So don't increment problem count. This
1255 # These are warnings, not errors. So don't increment problem count. This
1256 # may change in the future.
1256 # may change in the future.
1257 if 'tls1.2' not in security:
1257 if 'tls1.2' not in security:
1258 fm.plain(_(' TLS 1.2 not supported by Python install; '
1258 fm.plain(_(' TLS 1.2 not supported by Python install; '
1259 'network connections lack modern security\n'))
1259 'network connections lack modern security\n'))
1260 if 'sni' not in security:
1260 if 'sni' not in security:
1261 fm.plain(_(' SNI not supported by Python install; may have '
1261 fm.plain(_(' SNI not supported by Python install; may have '
1262 'connectivity issues with some servers\n'))
1262 'connectivity issues with some servers\n'))
1263
1263
1264 # TODO print CA cert info
1264 # TODO print CA cert info
1265
1265
1266 # hg version
1266 # hg version
1267 hgver = util.version()
1267 hgver = util.version()
1268 fm.write('hgver', _("checking Mercurial version (%s)\n"),
1268 fm.write('hgver', _("checking Mercurial version (%s)\n"),
1269 hgver.split('+')[0])
1269 hgver.split('+')[0])
1270 fm.write('hgverextra', _("checking Mercurial custom build (%s)\n"),
1270 fm.write('hgverextra', _("checking Mercurial custom build (%s)\n"),
1271 '+'.join(hgver.split('+')[1:]))
1271 '+'.join(hgver.split('+')[1:]))
1272
1272
1273 # compiled modules
1273 # compiled modules
1274 fm.write('hgmodulepolicy', _("checking module policy (%s)\n"),
1274 fm.write('hgmodulepolicy', _("checking module policy (%s)\n"),
1275 policy.policy)
1275 policy.policy)
1276 fm.write('hgmodules', _("checking installed modules (%s)...\n"),
1276 fm.write('hgmodules', _("checking installed modules (%s)...\n"),
1277 os.path.dirname(pycompat.fsencode(__file__)))
1277 os.path.dirname(pycompat.fsencode(__file__)))
1278
1278
1279 if policy.policy in ('c', 'allow'):
1279 if policy.policy in ('c', 'allow'):
1280 err = None
1280 err = None
1281 try:
1281 try:
1282 from .cext import (
1282 from .cext import (
1283 base85,
1283 base85,
1284 bdiff,
1284 bdiff,
1285 mpatch,
1285 mpatch,
1286 osutil,
1286 osutil,
1287 )
1287 )
1288 dir(bdiff), dir(mpatch), dir(base85), dir(osutil) # quiet pyflakes
1288 dir(bdiff), dir(mpatch), dir(base85), dir(osutil) # quiet pyflakes
1289 except Exception as inst:
1289 except Exception as inst:
1290 err = stringutil.forcebytestr(inst)
1290 err = stringutil.forcebytestr(inst)
1291 problems += 1
1291 problems += 1
1292 fm.condwrite(err, 'extensionserror', " %s\n", err)
1292 fm.condwrite(err, 'extensionserror', " %s\n", err)
1293
1293
1294 compengines = util.compengines._engines.values()
1294 compengines = util.compengines._engines.values()
1295 fm.write('compengines', _('checking registered compression engines (%s)\n'),
1295 fm.write('compengines', _('checking registered compression engines (%s)\n'),
1296 fm.formatlist(sorted(e.name() for e in compengines),
1296 fm.formatlist(sorted(e.name() for e in compengines),
1297 name='compengine', fmt='%s', sep=', '))
1297 name='compengine', fmt='%s', sep=', '))
1298 fm.write('compenginesavail', _('checking available compression engines '
1298 fm.write('compenginesavail', _('checking available compression engines '
1299 '(%s)\n'),
1299 '(%s)\n'),
1300 fm.formatlist(sorted(e.name() for e in compengines
1300 fm.formatlist(sorted(e.name() for e in compengines
1301 if e.available()),
1301 if e.available()),
1302 name='compengine', fmt='%s', sep=', '))
1302 name='compengine', fmt='%s', sep=', '))
1303 wirecompengines = compression.compengines.supportedwireengines(
1303 wirecompengines = compression.compengines.supportedwireengines(
1304 compression.SERVERROLE)
1304 compression.SERVERROLE)
1305 fm.write('compenginesserver', _('checking available compression engines '
1305 fm.write('compenginesserver', _('checking available compression engines '
1306 'for wire protocol (%s)\n'),
1306 'for wire protocol (%s)\n'),
1307 fm.formatlist([e.name() for e in wirecompengines
1307 fm.formatlist([e.name() for e in wirecompengines
1308 if e.wireprotosupport()],
1308 if e.wireprotosupport()],
1309 name='compengine', fmt='%s', sep=', '))
1309 name='compengine', fmt='%s', sep=', '))
1310 re2 = 'missing'
1310 re2 = 'missing'
1311 if util._re2:
1311 if util._re2:
1312 re2 = 'available'
1312 re2 = 'available'
1313 fm.plain(_('checking "re2" regexp engine (%s)\n') % re2)
1313 fm.plain(_('checking "re2" regexp engine (%s)\n') % re2)
1314 fm.data(re2=bool(util._re2))
1314 fm.data(re2=bool(util._re2))
1315
1315
1316 # templates
1316 # templates
1317 p = templater.templatepaths()
1317 p = templater.templatepaths()
1318 fm.write('templatedirs', 'checking templates (%s)...\n', ' '.join(p))
1318 fm.write('templatedirs', 'checking templates (%s)...\n', ' '.join(p))
1319 fm.condwrite(not p, '', _(" no template directories found\n"))
1319 fm.condwrite(not p, '', _(" no template directories found\n"))
1320 if p:
1320 if p:
1321 m = templater.templatepath("map-cmdline.default")
1321 m = templater.templatepath("map-cmdline.default")
1322 if m:
1322 if m:
1323 # template found, check if it is working
1323 # template found, check if it is working
1324 err = None
1324 err = None
1325 try:
1325 try:
1326 templater.templater.frommapfile(m)
1326 templater.templater.frommapfile(m)
1327 except Exception as inst:
1327 except Exception as inst:
1328 err = stringutil.forcebytestr(inst)
1328 err = stringutil.forcebytestr(inst)
1329 p = None
1329 p = None
1330 fm.condwrite(err, 'defaulttemplateerror', " %s\n", err)
1330 fm.condwrite(err, 'defaulttemplateerror', " %s\n", err)
1331 else:
1331 else:
1332 p = None
1332 p = None
1333 fm.condwrite(p, 'defaulttemplate',
1333 fm.condwrite(p, 'defaulttemplate',
1334 _("checking default template (%s)\n"), m)
1334 _("checking default template (%s)\n"), m)
1335 fm.condwrite(not m, 'defaulttemplatenotfound',
1335 fm.condwrite(not m, 'defaulttemplatenotfound',
1336 _(" template '%s' not found\n"), "default")
1336 _(" template '%s' not found\n"), "default")
1337 if not p:
1337 if not p:
1338 problems += 1
1338 problems += 1
1339 fm.condwrite(not p, '',
1339 fm.condwrite(not p, '',
1340 _(" (templates seem to have been installed incorrectly)\n"))
1340 _(" (templates seem to have been installed incorrectly)\n"))
1341
1341
1342 # editor
1342 # editor
1343 editor = ui.geteditor()
1343 editor = ui.geteditor()
1344 editor = util.expandpath(editor)
1344 editor = util.expandpath(editor)
1345 editorbin = procutil.shellsplit(editor)[0]
1345 editorbin = procutil.shellsplit(editor)[0]
1346 fm.write('editor', _("checking commit editor... (%s)\n"), editorbin)
1346 fm.write('editor', _("checking commit editor... (%s)\n"), editorbin)
1347 cmdpath = procutil.findexe(editorbin)
1347 cmdpath = procutil.findexe(editorbin)
1348 fm.condwrite(not cmdpath and editor == 'vi', 'vinotfound',
1348 fm.condwrite(not cmdpath and editor == 'vi', 'vinotfound',
1349 _(" No commit editor set and can't find %s in PATH\n"
1349 _(" No commit editor set and can't find %s in PATH\n"
1350 " (specify a commit editor in your configuration"
1350 " (specify a commit editor in your configuration"
1351 " file)\n"), not cmdpath and editor == 'vi' and editorbin)
1351 " file)\n"), not cmdpath and editor == 'vi' and editorbin)
1352 fm.condwrite(not cmdpath and editor != 'vi', 'editornotfound',
1352 fm.condwrite(not cmdpath and editor != 'vi', 'editornotfound',
1353 _(" Can't find editor '%s' in PATH\n"
1353 _(" Can't find editor '%s' in PATH\n"
1354 " (specify a commit editor in your configuration"
1354 " (specify a commit editor in your configuration"
1355 " file)\n"), not cmdpath and editorbin)
1355 " file)\n"), not cmdpath and editorbin)
1356 if not cmdpath and editor != 'vi':
1356 if not cmdpath and editor != 'vi':
1357 problems += 1
1357 problems += 1
1358
1358
1359 # check username
1359 # check username
1360 username = None
1360 username = None
1361 err = None
1361 err = None
1362 try:
1362 try:
1363 username = ui.username()
1363 username = ui.username()
1364 except error.Abort as e:
1364 except error.Abort as e:
1365 err = stringutil.forcebytestr(e)
1365 err = stringutil.forcebytestr(e)
1366 problems += 1
1366 problems += 1
1367
1367
1368 fm.condwrite(username, 'username', _("checking username (%s)\n"), username)
1368 fm.condwrite(username, 'username', _("checking username (%s)\n"), username)
1369 fm.condwrite(err, 'usernameerror', _("checking username...\n %s\n"
1369 fm.condwrite(err, 'usernameerror', _("checking username...\n %s\n"
1370 " (specify a username in your configuration file)\n"), err)
1370 " (specify a username in your configuration file)\n"), err)
1371
1371
1372 fm.condwrite(not problems, '',
1372 fm.condwrite(not problems, '',
1373 _("no problems detected\n"))
1373 _("no problems detected\n"))
1374 if not problems:
1374 if not problems:
1375 fm.data(problems=problems)
1375 fm.data(problems=problems)
1376 fm.condwrite(problems, 'problems',
1376 fm.condwrite(problems, 'problems',
1377 _("%d problems detected,"
1377 _("%d problems detected,"
1378 " please check your install!\n"), problems)
1378 " please check your install!\n"), problems)
1379 fm.end()
1379 fm.end()
1380
1380
1381 return problems
1381 return problems
1382
1382
1383 @command('debugknown', [], _('REPO ID...'), norepo=True)
1383 @command('debugknown', [], _('REPO ID...'), norepo=True)
1384 def debugknown(ui, repopath, *ids, **opts):
1384 def debugknown(ui, repopath, *ids, **opts):
1385 """test whether node ids are known to a repo
1385 """test whether node ids are known to a repo
1386
1386
1387 Every ID must be a full-length hex node id string. Returns a list of 0s
1387 Every ID must be a full-length hex node id string. Returns a list of 0s
1388 and 1s indicating unknown/known.
1388 and 1s indicating unknown/known.
1389 """
1389 """
1390 opts = pycompat.byteskwargs(opts)
1390 opts = pycompat.byteskwargs(opts)
1391 repo = hg.peer(ui, opts, repopath)
1391 repo = hg.peer(ui, opts, repopath)
1392 if not repo.capable('known'):
1392 if not repo.capable('known'):
1393 raise error.Abort("known() not supported by target repository")
1393 raise error.Abort("known() not supported by target repository")
1394 flags = repo.known([bin(s) for s in ids])
1394 flags = repo.known([bin(s) for s in ids])
1395 ui.write("%s\n" % ("".join([f and "1" or "0" for f in flags])))
1395 ui.write("%s\n" % ("".join([f and "1" or "0" for f in flags])))
1396
1396
1397 @command('debuglabelcomplete', [], _('LABEL...'))
1397 @command('debuglabelcomplete', [], _('LABEL...'))
1398 def debuglabelcomplete(ui, repo, *args):
1398 def debuglabelcomplete(ui, repo, *args):
1399 '''backwards compatibility with old bash completion scripts (DEPRECATED)'''
1399 '''backwards compatibility with old bash completion scripts (DEPRECATED)'''
1400 debugnamecomplete(ui, repo, *args)
1400 debugnamecomplete(ui, repo, *args)
1401
1401
1402 @command('debuglocks',
1402 @command('debuglocks',
1403 [('L', 'force-lock', None, _('free the store lock (DANGEROUS)')),
1403 [('L', 'force-lock', None, _('free the store lock (DANGEROUS)')),
1404 ('W', 'force-wlock', None,
1404 ('W', 'force-wlock', None,
1405 _('free the working state lock (DANGEROUS)')),
1405 _('free the working state lock (DANGEROUS)')),
1406 ('s', 'set-lock', None, _('set the store lock until stopped')),
1406 ('s', 'set-lock', None, _('set the store lock until stopped')),
1407 ('S', 'set-wlock', None,
1407 ('S', 'set-wlock', None,
1408 _('set the working state lock until stopped'))],
1408 _('set the working state lock until stopped'))],
1409 _('[OPTION]...'))
1409 _('[OPTION]...'))
1410 def debuglocks(ui, repo, **opts):
1410 def debuglocks(ui, repo, **opts):
1411 """show or modify state of locks
1411 """show or modify state of locks
1412
1412
1413 By default, this command will show which locks are held. This
1413 By default, this command will show which locks are held. This
1414 includes the user and process holding the lock, the amount of time
1414 includes the user and process holding the lock, the amount of time
1415 the lock has been held, and the machine name where the process is
1415 the lock has been held, and the machine name where the process is
1416 running if it's not local.
1416 running if it's not local.
1417
1417
1418 Locks protect the integrity of Mercurial's data, so should be
1418 Locks protect the integrity of Mercurial's data, so should be
1419 treated with care. System crashes or other interruptions may cause
1419 treated with care. System crashes or other interruptions may cause
1420 locks to not be properly released, though Mercurial will usually
1420 locks to not be properly released, though Mercurial will usually
1421 detect and remove such stale locks automatically.
1421 detect and remove such stale locks automatically.
1422
1422
1423 However, detecting stale locks may not always be possible (for
1423 However, detecting stale locks may not always be possible (for
1424 instance, on a shared filesystem). Removing locks may also be
1424 instance, on a shared filesystem). Removing locks may also be
1425 blocked by filesystem permissions.
1425 blocked by filesystem permissions.
1426
1426
1427 Setting a lock will prevent other commands from changing the data.
1427 Setting a lock will prevent other commands from changing the data.
1428 The command will wait until an interruption (SIGINT, SIGTERM, ...) occurs.
1428 The command will wait until an interruption (SIGINT, SIGTERM, ...) occurs.
1429 The set locks are removed when the command exits.
1429 The set locks are removed when the command exits.
1430
1430
1431 Returns 0 if no locks are held.
1431 Returns 0 if no locks are held.
1432
1432
1433 """
1433 """
1434
1434
1435 if opts.get(r'force_lock'):
1435 if opts.get(r'force_lock'):
1436 repo.svfs.unlink('lock')
1436 repo.svfs.unlink('lock')
1437 if opts.get(r'force_wlock'):
1437 if opts.get(r'force_wlock'):
1438 repo.vfs.unlink('wlock')
1438 repo.vfs.unlink('wlock')
1439 if opts.get(r'force_lock') or opts.get(r'force_wlock'):
1439 if opts.get(r'force_lock') or opts.get(r'force_wlock'):
1440 return 0
1440 return 0
1441
1441
1442 locks = []
1442 locks = []
1443 try:
1443 try:
1444 if opts.get(r'set_wlock'):
1444 if opts.get(r'set_wlock'):
1445 try:
1445 try:
1446 locks.append(repo.wlock(False))
1446 locks.append(repo.wlock(False))
1447 except error.LockHeld:
1447 except error.LockHeld:
1448 raise error.Abort(_('wlock is already held'))
1448 raise error.Abort(_('wlock is already held'))
1449 if opts.get(r'set_lock'):
1449 if opts.get(r'set_lock'):
1450 try:
1450 try:
1451 locks.append(repo.lock(False))
1451 locks.append(repo.lock(False))
1452 except error.LockHeld:
1452 except error.LockHeld:
1453 raise error.Abort(_('lock is already held'))
1453 raise error.Abort(_('lock is already held'))
1454 if len(locks):
1454 if len(locks):
1455 ui.promptchoice(_("ready to release the lock (y)? $$ &Yes"))
1455 ui.promptchoice(_("ready to release the lock (y)? $$ &Yes"))
1456 return 0
1456 return 0
1457 finally:
1457 finally:
1458 release(*locks)
1458 release(*locks)
1459
1459
1460 now = time.time()
1460 now = time.time()
1461 held = 0
1461 held = 0
1462
1462
1463 def report(vfs, name, method):
1463 def report(vfs, name, method):
1464 # this causes stale locks to get reaped for more accurate reporting
1464 # this causes stale locks to get reaped for more accurate reporting
1465 try:
1465 try:
1466 l = method(False)
1466 l = method(False)
1467 except error.LockHeld:
1467 except error.LockHeld:
1468 l = None
1468 l = None
1469
1469
1470 if l:
1470 if l:
1471 l.release()
1471 l.release()
1472 else:
1472 else:
1473 try:
1473 try:
1474 st = vfs.lstat(name)
1474 st = vfs.lstat(name)
1475 age = now - st[stat.ST_MTIME]
1475 age = now - st[stat.ST_MTIME]
1476 user = util.username(st.st_uid)
1476 user = util.username(st.st_uid)
1477 locker = vfs.readlock(name)
1477 locker = vfs.readlock(name)
1478 if ":" in locker:
1478 if ":" in locker:
1479 host, pid = locker.split(':')
1479 host, pid = locker.split(':')
1480 if host == socket.gethostname():
1480 if host == socket.gethostname():
1481 locker = 'user %s, process %s' % (user or b'None', pid)
1481 locker = 'user %s, process %s' % (user or b'None', pid)
1482 else:
1482 else:
1483 locker = ('user %s, process %s, host %s'
1483 locker = ('user %s, process %s, host %s'
1484 % (user or b'None', pid, host))
1484 % (user or b'None', pid, host))
1485 ui.write(("%-6s %s (%ds)\n") % (name + ":", locker, age))
1485 ui.write(("%-6s %s (%ds)\n") % (name + ":", locker, age))
1486 return 1
1486 return 1
1487 except OSError as e:
1487 except OSError as e:
1488 if e.errno != errno.ENOENT:
1488 if e.errno != errno.ENOENT:
1489 raise
1489 raise
1490
1490
1491 ui.write(("%-6s free\n") % (name + ":"))
1491 ui.write(("%-6s free\n") % (name + ":"))
1492 return 0
1492 return 0
1493
1493
1494 held += report(repo.svfs, "lock", repo.lock)
1494 held += report(repo.svfs, "lock", repo.lock)
1495 held += report(repo.vfs, "wlock", repo.wlock)
1495 held += report(repo.vfs, "wlock", repo.wlock)
1496
1496
1497 return held
1497 return held
1498
1498
1499 @command('debugmanifestfulltextcache', [
1499 @command('debugmanifestfulltextcache', [
1500 ('', 'clear', False, _('clear the cache')),
1500 ('', 'clear', False, _('clear the cache')),
1501 ('a', 'add', [], _('add the given manifest nodes to the cache'),
1501 ('a', 'add', [], _('add the given manifest nodes to the cache'),
1502 _('NODE'))
1502 _('NODE'))
1503 ], '')
1503 ], '')
1504 def debugmanifestfulltextcache(ui, repo, add=(), **opts):
1504 def debugmanifestfulltextcache(ui, repo, add=(), **opts):
1505 """show, clear or amend the contents of the manifest fulltext cache"""
1505 """show, clear or amend the contents of the manifest fulltext cache"""
1506
1506
1507 def getcache():
1507 def getcache():
1508 r = repo.manifestlog.getstorage(b'')
1508 r = repo.manifestlog.getstorage(b'')
1509 try:
1509 try:
1510 return r._fulltextcache
1510 return r._fulltextcache
1511 except AttributeError:
1511 except AttributeError:
1512 msg = _("Current revlog implementation doesn't appear to have a "
1512 msg = _("Current revlog implementation doesn't appear to have a "
1513 "manifest fulltext cache\n")
1513 "manifest fulltext cache\n")
1514 raise error.Abort(msg)
1514 raise error.Abort(msg)
1515
1515
1516 if opts.get(r'clear'):
1516 if opts.get(r'clear'):
1517 with repo.wlock():
1517 with repo.wlock():
1518 cache = getcache()
1518 cache = getcache()
1519 cache.clear(clear_persisted_data=True)
1519 cache.clear(clear_persisted_data=True)
1520 return
1520 return
1521
1521
1522 if add:
1522 if add:
1523 with repo.wlock():
1523 with repo.wlock():
1524 m = repo.manifestlog
1524 m = repo.manifestlog
1525 store = m.getstorage(b'')
1525 store = m.getstorage(b'')
1526 for n in add:
1526 for n in add:
1527 try:
1527 try:
1528 manifest = m[store.lookup(n)]
1528 manifest = m[store.lookup(n)]
1529 except error.LookupError as e:
1529 except error.LookupError as e:
1530 raise error.Abort(e, hint="Check your manifest node id")
1530 raise error.Abort(e, hint="Check your manifest node id")
1531 manifest.read() # stores revisision in cache too
1531 manifest.read() # stores revisision in cache too
1532 return
1532 return
1533
1533
1534 cache = getcache()
1534 cache = getcache()
1535 if not len(cache):
1535 if not len(cache):
1536 ui.write(_('cache empty\n'))
1536 ui.write(_('cache empty\n'))
1537 else:
1537 else:
1538 ui.write(
1538 ui.write(
1539 _('cache contains %d manifest entries, in order of most to '
1539 _('cache contains %d manifest entries, in order of most to '
1540 'least recent:\n') % (len(cache),))
1540 'least recent:\n') % (len(cache),))
1541 totalsize = 0
1541 totalsize = 0
1542 for nodeid in cache:
1542 for nodeid in cache:
1543 # Use cache.get to not update the LRU order
1543 # Use cache.get to not update the LRU order
1544 data = cache.peek(nodeid)
1544 data = cache.peek(nodeid)
1545 size = len(data)
1545 size = len(data)
1546 totalsize += size + 24 # 20 bytes nodeid, 4 bytes size
1546 totalsize += size + 24 # 20 bytes nodeid, 4 bytes size
1547 ui.write(_('id: %s, size %s\n') % (
1547 ui.write(_('id: %s, size %s\n') % (
1548 hex(nodeid), util.bytecount(size)))
1548 hex(nodeid), util.bytecount(size)))
1549 ondisk = cache._opener.stat('manifestfulltextcache').st_size
1549 ondisk = cache._opener.stat('manifestfulltextcache').st_size
1550 ui.write(
1550 ui.write(
1551 _('total cache data size %s, on-disk %s\n') % (
1551 _('total cache data size %s, on-disk %s\n') % (
1552 util.bytecount(totalsize), util.bytecount(ondisk))
1552 util.bytecount(totalsize), util.bytecount(ondisk))
1553 )
1553 )
1554
1554
1555 @command('debugmergestate', [], '')
1555 @command('debugmergestate', [], '')
1556 def debugmergestate(ui, repo, *args):
1556 def debugmergestate(ui, repo, *args):
1557 """print merge state
1557 """print merge state
1558
1558
1559 Use --verbose to print out information about whether v1 or v2 merge state
1559 Use --verbose to print out information about whether v1 or v2 merge state
1560 was chosen."""
1560 was chosen."""
1561 def _hashornull(h):
1561 def _hashornull(h):
1562 if h == nullhex:
1562 if h == nullhex:
1563 return 'null'
1563 return 'null'
1564 else:
1564 else:
1565 return h
1565 return h
1566
1566
1567 def printrecords(version):
1567 def printrecords(version):
1568 ui.write(('* version %d records\n') % version)
1568 ui.write(('* version %d records\n') % version)
1569 if version == 1:
1569 if version == 1:
1570 records = v1records
1570 records = v1records
1571 else:
1571 else:
1572 records = v2records
1572 records = v2records
1573
1573
1574 for rtype, record in records:
1574 for rtype, record in records:
1575 # pretty print some record types
1575 # pretty print some record types
1576 if rtype == 'L':
1576 if rtype == 'L':
1577 ui.write(('local: %s\n') % record)
1577 ui.write(('local: %s\n') % record)
1578 elif rtype == 'O':
1578 elif rtype == 'O':
1579 ui.write(('other: %s\n') % record)
1579 ui.write(('other: %s\n') % record)
1580 elif rtype == 'm':
1580 elif rtype == 'm':
1581 driver, mdstate = record.split('\0', 1)
1581 driver, mdstate = record.split('\0', 1)
1582 ui.write(('merge driver: %s (state "%s")\n')
1582 ui.write(('merge driver: %s (state "%s")\n')
1583 % (driver, mdstate))
1583 % (driver, mdstate))
1584 elif rtype in 'FDC':
1584 elif rtype in 'FDC':
1585 r = record.split('\0')
1585 r = record.split('\0')
1586 f, state, hash, lfile, afile, anode, ofile = r[0:7]
1586 f, state, hash, lfile, afile, anode, ofile = r[0:7]
1587 if version == 1:
1587 if version == 1:
1588 onode = 'not stored in v1 format'
1588 onode = 'not stored in v1 format'
1589 flags = r[7]
1589 flags = r[7]
1590 else:
1590 else:
1591 onode, flags = r[7:9]
1591 onode, flags = r[7:9]
1592 ui.write(('file: %s (record type "%s", state "%s", hash %s)\n')
1592 ui.write(('file: %s (record type "%s", state "%s", hash %s)\n')
1593 % (f, rtype, state, _hashornull(hash)))
1593 % (f, rtype, state, _hashornull(hash)))
1594 ui.write((' local path: %s (flags "%s")\n') % (lfile, flags))
1594 ui.write((' local path: %s (flags "%s")\n') % (lfile, flags))
1595 ui.write((' ancestor path: %s (node %s)\n')
1595 ui.write((' ancestor path: %s (node %s)\n')
1596 % (afile, _hashornull(anode)))
1596 % (afile, _hashornull(anode)))
1597 ui.write((' other path: %s (node %s)\n')
1597 ui.write((' other path: %s (node %s)\n')
1598 % (ofile, _hashornull(onode)))
1598 % (ofile, _hashornull(onode)))
1599 elif rtype == 'f':
1599 elif rtype == 'f':
1600 filename, rawextras = record.split('\0', 1)
1600 filename, rawextras = record.split('\0', 1)
1601 extras = rawextras.split('\0')
1601 extras = rawextras.split('\0')
1602 i = 0
1602 i = 0
1603 extrastrings = []
1603 extrastrings = []
1604 while i < len(extras):
1604 while i < len(extras):
1605 extrastrings.append('%s = %s' % (extras[i], extras[i + 1]))
1605 extrastrings.append('%s = %s' % (extras[i], extras[i + 1]))
1606 i += 2
1606 i += 2
1607
1607
1608 ui.write(('file extras: %s (%s)\n')
1608 ui.write(('file extras: %s (%s)\n')
1609 % (filename, ', '.join(extrastrings)))
1609 % (filename, ', '.join(extrastrings)))
1610 elif rtype == 'l':
1610 elif rtype == 'l':
1611 labels = record.split('\0', 2)
1611 labels = record.split('\0', 2)
1612 labels = [l for l in labels if len(l) > 0]
1612 labels = [l for l in labels if len(l) > 0]
1613 ui.write(('labels:\n'))
1613 ui.write(('labels:\n'))
1614 ui.write((' local: %s\n' % labels[0]))
1614 ui.write((' local: %s\n' % labels[0]))
1615 ui.write((' other: %s\n' % labels[1]))
1615 ui.write((' other: %s\n' % labels[1]))
1616 if len(labels) > 2:
1616 if len(labels) > 2:
1617 ui.write((' base: %s\n' % labels[2]))
1617 ui.write((' base: %s\n' % labels[2]))
1618 else:
1618 else:
1619 ui.write(('unrecognized entry: %s\t%s\n')
1619 ui.write(('unrecognized entry: %s\t%s\n')
1620 % (rtype, record.replace('\0', '\t')))
1620 % (rtype, record.replace('\0', '\t')))
1621
1621
1622 # Avoid mergestate.read() since it may raise an exception for unsupported
1622 # Avoid mergestate.read() since it may raise an exception for unsupported
1623 # merge state records. We shouldn't be doing this, but this is OK since this
1623 # merge state records. We shouldn't be doing this, but this is OK since this
1624 # command is pretty low-level.
1624 # command is pretty low-level.
1625 ms = mergemod.mergestate(repo)
1625 ms = mergemod.mergestate(repo)
1626
1626
1627 # sort so that reasonable information is on top
1627 # sort so that reasonable information is on top
1628 v1records = ms._readrecordsv1()
1628 v1records = ms._readrecordsv1()
1629 v2records = ms._readrecordsv2()
1629 v2records = ms._readrecordsv2()
1630 order = 'LOml'
1630 order = 'LOml'
1631 def key(r):
1631 def key(r):
1632 idx = order.find(r[0])
1632 idx = order.find(r[0])
1633 if idx == -1:
1633 if idx == -1:
1634 return (1, r[1])
1634 return (1, r[1])
1635 else:
1635 else:
1636 return (0, idx)
1636 return (0, idx)
1637 v1records.sort(key=key)
1637 v1records.sort(key=key)
1638 v2records.sort(key=key)
1638 v2records.sort(key=key)
1639
1639
1640 if not v1records and not v2records:
1640 if not v1records and not v2records:
1641 ui.write(('no merge state found\n'))
1641 ui.write(('no merge state found\n'))
1642 elif not v2records:
1642 elif not v2records:
1643 ui.note(('no version 2 merge state\n'))
1643 ui.note(('no version 2 merge state\n'))
1644 printrecords(1)
1644 printrecords(1)
1645 elif ms._v1v2match(v1records, v2records):
1645 elif ms._v1v2match(v1records, v2records):
1646 ui.note(('v1 and v2 states match: using v2\n'))
1646 ui.note(('v1 and v2 states match: using v2\n'))
1647 printrecords(2)
1647 printrecords(2)
1648 else:
1648 else:
1649 ui.note(('v1 and v2 states mismatch: using v1\n'))
1649 ui.note(('v1 and v2 states mismatch: using v1\n'))
1650 printrecords(1)
1650 printrecords(1)
1651 if ui.verbose:
1651 if ui.verbose:
1652 printrecords(2)
1652 printrecords(2)
1653
1653
1654 @command('debugnamecomplete', [], _('NAME...'))
1654 @command('debugnamecomplete', [], _('NAME...'))
1655 def debugnamecomplete(ui, repo, *args):
1655 def debugnamecomplete(ui, repo, *args):
1656 '''complete "names" - tags, open branch names, bookmark names'''
1656 '''complete "names" - tags, open branch names, bookmark names'''
1657
1657
1658 names = set()
1658 names = set()
1659 # since we previously only listed open branches, we will handle that
1659 # since we previously only listed open branches, we will handle that
1660 # specially (after this for loop)
1660 # specially (after this for loop)
1661 for name, ns in repo.names.iteritems():
1661 for name, ns in repo.names.iteritems():
1662 if name != 'branches':
1662 if name != 'branches':
1663 names.update(ns.listnames(repo))
1663 names.update(ns.listnames(repo))
1664 names.update(tag for (tag, heads, tip, closed)
1664 names.update(tag for (tag, heads, tip, closed)
1665 in repo.branchmap().iterbranches() if not closed)
1665 in repo.branchmap().iterbranches() if not closed)
1666 completions = set()
1666 completions = set()
1667 if not args:
1667 if not args:
1668 args = ['']
1668 args = ['']
1669 for a in args:
1669 for a in args:
1670 completions.update(n for n in names if n.startswith(a))
1670 completions.update(n for n in names if n.startswith(a))
1671 ui.write('\n'.join(sorted(completions)))
1671 ui.write('\n'.join(sorted(completions)))
1672 ui.write('\n')
1672 ui.write('\n')
1673
1673
1674 @command('debugobsolete',
1674 @command('debugobsolete',
1675 [('', 'flags', 0, _('markers flag')),
1675 [('', 'flags', 0, _('markers flag')),
1676 ('', 'record-parents', False,
1676 ('', 'record-parents', False,
1677 _('record parent information for the precursor')),
1677 _('record parent information for the precursor')),
1678 ('r', 'rev', [], _('display markers relevant to REV')),
1678 ('r', 'rev', [], _('display markers relevant to REV')),
1679 ('', 'exclusive', False, _('restrict display to markers only '
1679 ('', 'exclusive', False, _('restrict display to markers only '
1680 'relevant to REV')),
1680 'relevant to REV')),
1681 ('', 'index', False, _('display index of the marker')),
1681 ('', 'index', False, _('display index of the marker')),
1682 ('', 'delete', [], _('delete markers specified by indices')),
1682 ('', 'delete', [], _('delete markers specified by indices')),
1683 ] + cmdutil.commitopts2 + cmdutil.formatteropts,
1683 ] + cmdutil.commitopts2 + cmdutil.formatteropts,
1684 _('[OBSOLETED [REPLACEMENT ...]]'))
1684 _('[OBSOLETED [REPLACEMENT ...]]'))
1685 def debugobsolete(ui, repo, precursor=None, *successors, **opts):
1685 def debugobsolete(ui, repo, precursor=None, *successors, **opts):
1686 """create arbitrary obsolete marker
1686 """create arbitrary obsolete marker
1687
1687
1688 With no arguments, displays the list of obsolescence markers."""
1688 With no arguments, displays the list of obsolescence markers."""
1689
1689
1690 opts = pycompat.byteskwargs(opts)
1690 opts = pycompat.byteskwargs(opts)
1691
1691
1692 def parsenodeid(s):
1692 def parsenodeid(s):
1693 try:
1693 try:
1694 # We do not use revsingle/revrange functions here to accept
1694 # We do not use revsingle/revrange functions here to accept
1695 # arbitrary node identifiers, possibly not present in the
1695 # arbitrary node identifiers, possibly not present in the
1696 # local repository.
1696 # local repository.
1697 n = bin(s)
1697 n = bin(s)
1698 if len(n) != len(nullid):
1698 if len(n) != len(nullid):
1699 raise TypeError()
1699 raise TypeError()
1700 return n
1700 return n
1701 except TypeError:
1701 except TypeError:
1702 raise error.Abort('changeset references must be full hexadecimal '
1702 raise error.Abort('changeset references must be full hexadecimal '
1703 'node identifiers')
1703 'node identifiers')
1704
1704
1705 if opts.get('delete'):
1705 if opts.get('delete'):
1706 indices = []
1706 indices = []
1707 for v in opts.get('delete'):
1707 for v in opts.get('delete'):
1708 try:
1708 try:
1709 indices.append(int(v))
1709 indices.append(int(v))
1710 except ValueError:
1710 except ValueError:
1711 raise error.Abort(_('invalid index value: %r') % v,
1711 raise error.Abort(_('invalid index value: %r') % v,
1712 hint=_('use integers for indices'))
1712 hint=_('use integers for indices'))
1713
1713
1714 if repo.currenttransaction():
1714 if repo.currenttransaction():
1715 raise error.Abort(_('cannot delete obsmarkers in the middle '
1715 raise error.Abort(_('cannot delete obsmarkers in the middle '
1716 'of transaction.'))
1716 'of transaction.'))
1717
1717
1718 with repo.lock():
1718 with repo.lock():
1719 n = repair.deleteobsmarkers(repo.obsstore, indices)
1719 n = repair.deleteobsmarkers(repo.obsstore, indices)
1720 ui.write(_('deleted %i obsolescence markers\n') % n)
1720 ui.write(_('deleted %i obsolescence markers\n') % n)
1721
1721
1722 return
1722 return
1723
1723
1724 if precursor is not None:
1724 if precursor is not None:
1725 if opts['rev']:
1725 if opts['rev']:
1726 raise error.Abort('cannot select revision when creating marker')
1726 raise error.Abort('cannot select revision when creating marker')
1727 metadata = {}
1727 metadata = {}
1728 metadata['user'] = encoding.fromlocal(opts['user'] or ui.username())
1728 metadata['user'] = encoding.fromlocal(opts['user'] or ui.username())
1729 succs = tuple(parsenodeid(succ) for succ in successors)
1729 succs = tuple(parsenodeid(succ) for succ in successors)
1730 l = repo.lock()
1730 l = repo.lock()
1731 try:
1731 try:
1732 tr = repo.transaction('debugobsolete')
1732 tr = repo.transaction('debugobsolete')
1733 try:
1733 try:
1734 date = opts.get('date')
1734 date = opts.get('date')
1735 if date:
1735 if date:
1736 date = dateutil.parsedate(date)
1736 date = dateutil.parsedate(date)
1737 else:
1737 else:
1738 date = None
1738 date = None
1739 prec = parsenodeid(precursor)
1739 prec = parsenodeid(precursor)
1740 parents = None
1740 parents = None
1741 if opts['record_parents']:
1741 if opts['record_parents']:
1742 if prec not in repo.unfiltered():
1742 if prec not in repo.unfiltered():
1743 raise error.Abort('cannot used --record-parents on '
1743 raise error.Abort('cannot used --record-parents on '
1744 'unknown changesets')
1744 'unknown changesets')
1745 parents = repo.unfiltered()[prec].parents()
1745 parents = repo.unfiltered()[prec].parents()
1746 parents = tuple(p.node() for p in parents)
1746 parents = tuple(p.node() for p in parents)
1747 repo.obsstore.create(tr, prec, succs, opts['flags'],
1747 repo.obsstore.create(tr, prec, succs, opts['flags'],
1748 parents=parents, date=date,
1748 parents=parents, date=date,
1749 metadata=metadata, ui=ui)
1749 metadata=metadata, ui=ui)
1750 tr.close()
1750 tr.close()
1751 except ValueError as exc:
1751 except ValueError as exc:
1752 raise error.Abort(_('bad obsmarker input: %s') %
1752 raise error.Abort(_('bad obsmarker input: %s') %
1753 pycompat.bytestr(exc))
1753 pycompat.bytestr(exc))
1754 finally:
1754 finally:
1755 tr.release()
1755 tr.release()
1756 finally:
1756 finally:
1757 l.release()
1757 l.release()
1758 else:
1758 else:
1759 if opts['rev']:
1759 if opts['rev']:
1760 revs = scmutil.revrange(repo, opts['rev'])
1760 revs = scmutil.revrange(repo, opts['rev'])
1761 nodes = [repo[r].node() for r in revs]
1761 nodes = [repo[r].node() for r in revs]
1762 markers = list(obsutil.getmarkers(repo, nodes=nodes,
1762 markers = list(obsutil.getmarkers(repo, nodes=nodes,
1763 exclusive=opts['exclusive']))
1763 exclusive=opts['exclusive']))
1764 markers.sort(key=lambda x: x._data)
1764 markers.sort(key=lambda x: x._data)
1765 else:
1765 else:
1766 markers = obsutil.getmarkers(repo)
1766 markers = obsutil.getmarkers(repo)
1767
1767
1768 markerstoiter = markers
1768 markerstoiter = markers
1769 isrelevant = lambda m: True
1769 isrelevant = lambda m: True
1770 if opts.get('rev') and opts.get('index'):
1770 if opts.get('rev') and opts.get('index'):
1771 markerstoiter = obsutil.getmarkers(repo)
1771 markerstoiter = obsutil.getmarkers(repo)
1772 markerset = set(markers)
1772 markerset = set(markers)
1773 isrelevant = lambda m: m in markerset
1773 isrelevant = lambda m: m in markerset
1774
1774
1775 fm = ui.formatter('debugobsolete', opts)
1775 fm = ui.formatter('debugobsolete', opts)
1776 for i, m in enumerate(markerstoiter):
1776 for i, m in enumerate(markerstoiter):
1777 if not isrelevant(m):
1777 if not isrelevant(m):
1778 # marker can be irrelevant when we're iterating over a set
1778 # marker can be irrelevant when we're iterating over a set
1779 # of markers (markerstoiter) which is bigger than the set
1779 # of markers (markerstoiter) which is bigger than the set
1780 # of markers we want to display (markers)
1780 # of markers we want to display (markers)
1781 # this can happen if both --index and --rev options are
1781 # this can happen if both --index and --rev options are
1782 # provided and thus we need to iterate over all of the markers
1782 # provided and thus we need to iterate over all of the markers
1783 # to get the correct indices, but only display the ones that
1783 # to get the correct indices, but only display the ones that
1784 # are relevant to --rev value
1784 # are relevant to --rev value
1785 continue
1785 continue
1786 fm.startitem()
1786 fm.startitem()
1787 ind = i if opts.get('index') else None
1787 ind = i if opts.get('index') else None
1788 cmdutil.showmarker(fm, m, index=ind)
1788 cmdutil.showmarker(fm, m, index=ind)
1789 fm.end()
1789 fm.end()
1790
1790
1791 @command('debugp1copies',
1791 @command('debugp1copies',
1792 [('r', 'rev', '', _('revision to debug'), _('REV'))],
1792 [('r', 'rev', '', _('revision to debug'), _('REV'))],
1793 _('[-r REV]'))
1793 _('[-r REV]'))
1794 def debugp1copies(ui, repo, **opts):
1794 def debugp1copies(ui, repo, **opts):
1795 """dump copy information compared to p1"""
1795 """dump copy information compared to p1"""
1796
1796
1797 opts = pycompat.byteskwargs(opts)
1797 opts = pycompat.byteskwargs(opts)
1798 ctx = scmutil.revsingle(repo, opts.get('rev'), default=None)
1798 ctx = scmutil.revsingle(repo, opts.get('rev'), default=None)
1799 for dst, src in ctx.p1copies().items():
1799 for dst, src in ctx.p1copies().items():
1800 ui.write('%s -> %s\n' % (src, dst))
1800 ui.write('%s -> %s\n' % (src, dst))
1801
1801
1802 @command('debugp2copies',
1802 @command('debugp2copies',
1803 [('r', 'rev', '', _('revision to debug'), _('REV'))],
1803 [('r', 'rev', '', _('revision to debug'), _('REV'))],
1804 _('[-r REV]'))
1804 _('[-r REV]'))
1805 def debugp1copies(ui, repo, **opts):
1805 def debugp1copies(ui, repo, **opts):
1806 """dump copy information compared to p2"""
1806 """dump copy information compared to p2"""
1807
1807
1808 opts = pycompat.byteskwargs(opts)
1808 opts = pycompat.byteskwargs(opts)
1809 ctx = scmutil.revsingle(repo, opts.get('rev'), default=None)
1809 ctx = scmutil.revsingle(repo, opts.get('rev'), default=None)
1810 for dst, src in ctx.p2copies().items():
1810 for dst, src in ctx.p2copies().items():
1811 ui.write('%s -> %s\n' % (src, dst))
1811 ui.write('%s -> %s\n' % (src, dst))
1812
1812
1813 @command('debugpathcomplete',
1813 @command('debugpathcomplete',
1814 [('f', 'full', None, _('complete an entire path')),
1814 [('f', 'full', None, _('complete an entire path')),
1815 ('n', 'normal', None, _('show only normal files')),
1815 ('n', 'normal', None, _('show only normal files')),
1816 ('a', 'added', None, _('show only added files')),
1816 ('a', 'added', None, _('show only added files')),
1817 ('r', 'removed', None, _('show only removed files'))],
1817 ('r', 'removed', None, _('show only removed files'))],
1818 _('FILESPEC...'))
1818 _('FILESPEC...'))
1819 def debugpathcomplete(ui, repo, *specs, **opts):
1819 def debugpathcomplete(ui, repo, *specs, **opts):
1820 '''complete part or all of a tracked path
1820 '''complete part or all of a tracked path
1821
1821
1822 This command supports shells that offer path name completion. It
1822 This command supports shells that offer path name completion. It
1823 currently completes only files already known to the dirstate.
1823 currently completes only files already known to the dirstate.
1824
1824
1825 Completion extends only to the next path segment unless
1825 Completion extends only to the next path segment unless
1826 --full is specified, in which case entire paths are used.'''
1826 --full is specified, in which case entire paths are used.'''
1827
1827
1828 def complete(path, acceptable):
1828 def complete(path, acceptable):
1829 dirstate = repo.dirstate
1829 dirstate = repo.dirstate
1830 spec = os.path.normpath(os.path.join(encoding.getcwd(), path))
1830 spec = os.path.normpath(os.path.join(encoding.getcwd(), path))
1831 rootdir = repo.root + pycompat.ossep
1831 rootdir = repo.root + pycompat.ossep
1832 if spec != repo.root and not spec.startswith(rootdir):
1832 if spec != repo.root and not spec.startswith(rootdir):
1833 return [], []
1833 return [], []
1834 if os.path.isdir(spec):
1834 if os.path.isdir(spec):
1835 spec += '/'
1835 spec += '/'
1836 spec = spec[len(rootdir):]
1836 spec = spec[len(rootdir):]
1837 fixpaths = pycompat.ossep != '/'
1837 fixpaths = pycompat.ossep != '/'
1838 if fixpaths:
1838 if fixpaths:
1839 spec = spec.replace(pycompat.ossep, '/')
1839 spec = spec.replace(pycompat.ossep, '/')
1840 speclen = len(spec)
1840 speclen = len(spec)
1841 fullpaths = opts[r'full']
1841 fullpaths = opts[r'full']
1842 files, dirs = set(), set()
1842 files, dirs = set(), set()
1843 adddir, addfile = dirs.add, files.add
1843 adddir, addfile = dirs.add, files.add
1844 for f, st in dirstate.iteritems():
1844 for f, st in dirstate.iteritems():
1845 if f.startswith(spec) and st[0] in acceptable:
1845 if f.startswith(spec) and st[0] in acceptable:
1846 if fixpaths:
1846 if fixpaths:
1847 f = f.replace('/', pycompat.ossep)
1847 f = f.replace('/', pycompat.ossep)
1848 if fullpaths:
1848 if fullpaths:
1849 addfile(f)
1849 addfile(f)
1850 continue
1850 continue
1851 s = f.find(pycompat.ossep, speclen)
1851 s = f.find(pycompat.ossep, speclen)
1852 if s >= 0:
1852 if s >= 0:
1853 adddir(f[:s])
1853 adddir(f[:s])
1854 else:
1854 else:
1855 addfile(f)
1855 addfile(f)
1856 return files, dirs
1856 return files, dirs
1857
1857
1858 acceptable = ''
1858 acceptable = ''
1859 if opts[r'normal']:
1859 if opts[r'normal']:
1860 acceptable += 'nm'
1860 acceptable += 'nm'
1861 if opts[r'added']:
1861 if opts[r'added']:
1862 acceptable += 'a'
1862 acceptable += 'a'
1863 if opts[r'removed']:
1863 if opts[r'removed']:
1864 acceptable += 'r'
1864 acceptable += 'r'
1865 cwd = repo.getcwd()
1865 cwd = repo.getcwd()
1866 if not specs:
1866 if not specs:
1867 specs = ['.']
1867 specs = ['.']
1868
1868
1869 files, dirs = set(), set()
1869 files, dirs = set(), set()
1870 for spec in specs:
1870 for spec in specs:
1871 f, d = complete(spec, acceptable or 'nmar')
1871 f, d = complete(spec, acceptable or 'nmar')
1872 files.update(f)
1872 files.update(f)
1873 dirs.update(d)
1873 dirs.update(d)
1874 files.update(dirs)
1874 files.update(dirs)
1875 ui.write('\n'.join(repo.pathto(p, cwd) for p in sorted(files)))
1875 ui.write('\n'.join(repo.pathto(p, cwd) for p in sorted(files)))
1876 ui.write('\n')
1876 ui.write('\n')
1877
1877
1878 @command('debugpathcopies',
1878 @command('debugpathcopies',
1879 cmdutil.walkopts,
1879 cmdutil.walkopts,
1880 'hg debugpathcopies REV1 REV2 [FILE]',
1880 'hg debugpathcopies REV1 REV2 [FILE]',
1881 inferrepo=True)
1881 inferrepo=True)
1882 def debugpathcopies(ui, repo, rev1, rev2, *pats, **opts):
1882 def debugpathcopies(ui, repo, rev1, rev2, *pats, **opts):
1883 """show copies between two revisions"""
1883 """show copies between two revisions"""
1884 ctx1 = scmutil.revsingle(repo, rev1)
1884 ctx1 = scmutil.revsingle(repo, rev1)
1885 ctx2 = scmutil.revsingle(repo, rev2)
1885 ctx2 = scmutil.revsingle(repo, rev2)
1886 m = scmutil.match(ctx1, pats, opts)
1886 m = scmutil.match(ctx1, pats, opts)
1887 for dst, src in sorted(copies.pathcopies(ctx1, ctx2, m).items()):
1887 for dst, src in sorted(copies.pathcopies(ctx1, ctx2, m).items()):
1888 ui.write('%s -> %s\n' % (src, dst))
1888 ui.write('%s -> %s\n' % (src, dst))
1889
1889
1890 @command('debugpeer', [], _('PATH'), norepo=True)
1890 @command('debugpeer', [], _('PATH'), norepo=True)
1891 def debugpeer(ui, path):
1891 def debugpeer(ui, path):
1892 """establish a connection to a peer repository"""
1892 """establish a connection to a peer repository"""
1893 # Always enable peer request logging. Requires --debug to display
1893 # Always enable peer request logging. Requires --debug to display
1894 # though.
1894 # though.
1895 overrides = {
1895 overrides = {
1896 ('devel', 'debug.peer-request'): True,
1896 ('devel', 'debug.peer-request'): True,
1897 }
1897 }
1898
1898
1899 with ui.configoverride(overrides):
1899 with ui.configoverride(overrides):
1900 peer = hg.peer(ui, {}, path)
1900 peer = hg.peer(ui, {}, path)
1901
1901
1902 local = peer.local() is not None
1902 local = peer.local() is not None
1903 canpush = peer.canpush()
1903 canpush = peer.canpush()
1904
1904
1905 ui.write(_('url: %s\n') % peer.url())
1905 ui.write(_('url: %s\n') % peer.url())
1906 ui.write(_('local: %s\n') % (_('yes') if local else _('no')))
1906 ui.write(_('local: %s\n') % (_('yes') if local else _('no')))
1907 ui.write(_('pushable: %s\n') % (_('yes') if canpush else _('no')))
1907 ui.write(_('pushable: %s\n') % (_('yes') if canpush else _('no')))
1908
1908
1909 @command('debugpickmergetool',
1909 @command('debugpickmergetool',
1910 [('r', 'rev', '', _('check for files in this revision'), _('REV')),
1910 [('r', 'rev', '', _('check for files in this revision'), _('REV')),
1911 ('', 'changedelete', None, _('emulate merging change and delete')),
1911 ('', 'changedelete', None, _('emulate merging change and delete')),
1912 ] + cmdutil.walkopts + cmdutil.mergetoolopts,
1912 ] + cmdutil.walkopts + cmdutil.mergetoolopts,
1913 _('[PATTERN]...'),
1913 _('[PATTERN]...'),
1914 inferrepo=True)
1914 inferrepo=True)
1915 def debugpickmergetool(ui, repo, *pats, **opts):
1915 def debugpickmergetool(ui, repo, *pats, **opts):
1916 """examine which merge tool is chosen for specified file
1916 """examine which merge tool is chosen for specified file
1917
1917
1918 As described in :hg:`help merge-tools`, Mercurial examines
1918 As described in :hg:`help merge-tools`, Mercurial examines
1919 configurations below in this order to decide which merge tool is
1919 configurations below in this order to decide which merge tool is
1920 chosen for specified file.
1920 chosen for specified file.
1921
1921
1922 1. ``--tool`` option
1922 1. ``--tool`` option
1923 2. ``HGMERGE`` environment variable
1923 2. ``HGMERGE`` environment variable
1924 3. configurations in ``merge-patterns`` section
1924 3. configurations in ``merge-patterns`` section
1925 4. configuration of ``ui.merge``
1925 4. configuration of ``ui.merge``
1926 5. configurations in ``merge-tools`` section
1926 5. configurations in ``merge-tools`` section
1927 6. ``hgmerge`` tool (for historical reason only)
1927 6. ``hgmerge`` tool (for historical reason only)
1928 7. default tool for fallback (``:merge`` or ``:prompt``)
1928 7. default tool for fallback (``:merge`` or ``:prompt``)
1929
1929
1930 This command writes out examination result in the style below::
1930 This command writes out examination result in the style below::
1931
1931
1932 FILE = MERGETOOL
1932 FILE = MERGETOOL
1933
1933
1934 By default, all files known in the first parent context of the
1934 By default, all files known in the first parent context of the
1935 working directory are examined. Use file patterns and/or -I/-X
1935 working directory are examined. Use file patterns and/or -I/-X
1936 options to limit target files. -r/--rev is also useful to examine
1936 options to limit target files. -r/--rev is also useful to examine
1937 files in another context without actual updating to it.
1937 files in another context without actual updating to it.
1938
1938
1939 With --debug, this command shows warning messages while matching
1939 With --debug, this command shows warning messages while matching
1940 against ``merge-patterns`` and so on, too. It is recommended to
1940 against ``merge-patterns`` and so on, too. It is recommended to
1941 use this option with explicit file patterns and/or -I/-X options,
1941 use this option with explicit file patterns and/or -I/-X options,
1942 because this option increases amount of output per file according
1942 because this option increases amount of output per file according
1943 to configurations in hgrc.
1943 to configurations in hgrc.
1944
1944
1945 With -v/--verbose, this command shows configurations below at
1945 With -v/--verbose, this command shows configurations below at
1946 first (only if specified).
1946 first (only if specified).
1947
1947
1948 - ``--tool`` option
1948 - ``--tool`` option
1949 - ``HGMERGE`` environment variable
1949 - ``HGMERGE`` environment variable
1950 - configuration of ``ui.merge``
1950 - configuration of ``ui.merge``
1951
1951
1952 If merge tool is chosen before matching against
1952 If merge tool is chosen before matching against
1953 ``merge-patterns``, this command can't show any helpful
1953 ``merge-patterns``, this command can't show any helpful
1954 information, even with --debug. In such case, information above is
1954 information, even with --debug. In such case, information above is
1955 useful to know why a merge tool is chosen.
1955 useful to know why a merge tool is chosen.
1956 """
1956 """
1957 opts = pycompat.byteskwargs(opts)
1957 opts = pycompat.byteskwargs(opts)
1958 overrides = {}
1958 overrides = {}
1959 if opts['tool']:
1959 if opts['tool']:
1960 overrides[('ui', 'forcemerge')] = opts['tool']
1960 overrides[('ui', 'forcemerge')] = opts['tool']
1961 ui.note(('with --tool %r\n') % (pycompat.bytestr(opts['tool'])))
1961 ui.note(('with --tool %r\n') % (pycompat.bytestr(opts['tool'])))
1962
1962
1963 with ui.configoverride(overrides, 'debugmergepatterns'):
1963 with ui.configoverride(overrides, 'debugmergepatterns'):
1964 hgmerge = encoding.environ.get("HGMERGE")
1964 hgmerge = encoding.environ.get("HGMERGE")
1965 if hgmerge is not None:
1965 if hgmerge is not None:
1966 ui.note(('with HGMERGE=%r\n') % (pycompat.bytestr(hgmerge)))
1966 ui.note(('with HGMERGE=%r\n') % (pycompat.bytestr(hgmerge)))
1967 uimerge = ui.config("ui", "merge")
1967 uimerge = ui.config("ui", "merge")
1968 if uimerge:
1968 if uimerge:
1969 ui.note(('with ui.merge=%r\n') % (pycompat.bytestr(uimerge)))
1969 ui.note(('with ui.merge=%r\n') % (pycompat.bytestr(uimerge)))
1970
1970
1971 ctx = scmutil.revsingle(repo, opts.get('rev'))
1971 ctx = scmutil.revsingle(repo, opts.get('rev'))
1972 m = scmutil.match(ctx, pats, opts)
1972 m = scmutil.match(ctx, pats, opts)
1973 changedelete = opts['changedelete']
1973 changedelete = opts['changedelete']
1974 for path in ctx.walk(m):
1974 for path in ctx.walk(m):
1975 fctx = ctx[path]
1975 fctx = ctx[path]
1976 try:
1976 try:
1977 if not ui.debugflag:
1977 if not ui.debugflag:
1978 ui.pushbuffer(error=True)
1978 ui.pushbuffer(error=True)
1979 tool, toolpath = filemerge._picktool(repo, ui, path,
1979 tool, toolpath = filemerge._picktool(repo, ui, path,
1980 fctx.isbinary(),
1980 fctx.isbinary(),
1981 'l' in fctx.flags(),
1981 'l' in fctx.flags(),
1982 changedelete)
1982 changedelete)
1983 finally:
1983 finally:
1984 if not ui.debugflag:
1984 if not ui.debugflag:
1985 ui.popbuffer()
1985 ui.popbuffer()
1986 ui.write(('%s = %s\n') % (path, tool))
1986 ui.write(('%s = %s\n') % (path, tool))
1987
1987
1988 @command('debugpushkey', [], _('REPO NAMESPACE [KEY OLD NEW]'), norepo=True)
1988 @command('debugpushkey', [], _('REPO NAMESPACE [KEY OLD NEW]'), norepo=True)
1989 def debugpushkey(ui, repopath, namespace, *keyinfo, **opts):
1989 def debugpushkey(ui, repopath, namespace, *keyinfo, **opts):
1990 '''access the pushkey key/value protocol
1990 '''access the pushkey key/value protocol
1991
1991
1992 With two args, list the keys in the given namespace.
1992 With two args, list the keys in the given namespace.
1993
1993
1994 With five args, set a key to new if it currently is set to old.
1994 With five args, set a key to new if it currently is set to old.
1995 Reports success or failure.
1995 Reports success or failure.
1996 '''
1996 '''
1997
1997
1998 target = hg.peer(ui, {}, repopath)
1998 target = hg.peer(ui, {}, repopath)
1999 if keyinfo:
1999 if keyinfo:
2000 key, old, new = keyinfo
2000 key, old, new = keyinfo
2001 with target.commandexecutor() as e:
2001 with target.commandexecutor() as e:
2002 r = e.callcommand('pushkey', {
2002 r = e.callcommand('pushkey', {
2003 'namespace': namespace,
2003 'namespace': namespace,
2004 'key': key,
2004 'key': key,
2005 'old': old,
2005 'old': old,
2006 'new': new,
2006 'new': new,
2007 }).result()
2007 }).result()
2008
2008
2009 ui.status(pycompat.bytestr(r) + '\n')
2009 ui.status(pycompat.bytestr(r) + '\n')
2010 return not r
2010 return not r
2011 else:
2011 else:
2012 for k, v in sorted(target.listkeys(namespace).iteritems()):
2012 for k, v in sorted(target.listkeys(namespace).iteritems()):
2013 ui.write("%s\t%s\n" % (stringutil.escapestr(k),
2013 ui.write("%s\t%s\n" % (stringutil.escapestr(k),
2014 stringutil.escapestr(v)))
2014 stringutil.escapestr(v)))
2015
2015
2016 @command('debugpvec', [], _('A B'))
2016 @command('debugpvec', [], _('A B'))
2017 def debugpvec(ui, repo, a, b=None):
2017 def debugpvec(ui, repo, a, b=None):
2018 ca = scmutil.revsingle(repo, a)
2018 ca = scmutil.revsingle(repo, a)
2019 cb = scmutil.revsingle(repo, b)
2019 cb = scmutil.revsingle(repo, b)
2020 pa = pvec.ctxpvec(ca)
2020 pa = pvec.ctxpvec(ca)
2021 pb = pvec.ctxpvec(cb)
2021 pb = pvec.ctxpvec(cb)
2022 if pa == pb:
2022 if pa == pb:
2023 rel = "="
2023 rel = "="
2024 elif pa > pb:
2024 elif pa > pb:
2025 rel = ">"
2025 rel = ">"
2026 elif pa < pb:
2026 elif pa < pb:
2027 rel = "<"
2027 rel = "<"
2028 elif pa | pb:
2028 elif pa | pb:
2029 rel = "|"
2029 rel = "|"
2030 ui.write(_("a: %s\n") % pa)
2030 ui.write(_("a: %s\n") % pa)
2031 ui.write(_("b: %s\n") % pb)
2031 ui.write(_("b: %s\n") % pb)
2032 ui.write(_("depth(a): %d depth(b): %d\n") % (pa._depth, pb._depth))
2032 ui.write(_("depth(a): %d depth(b): %d\n") % (pa._depth, pb._depth))
2033 ui.write(_("delta: %d hdist: %d distance: %d relation: %s\n") %
2033 ui.write(_("delta: %d hdist: %d distance: %d relation: %s\n") %
2034 (abs(pa._depth - pb._depth), pvec._hamming(pa._vec, pb._vec),
2034 (abs(pa._depth - pb._depth), pvec._hamming(pa._vec, pb._vec),
2035 pa.distance(pb), rel))
2035 pa.distance(pb), rel))
2036
2036
2037 @command('debugrebuilddirstate|debugrebuildstate',
2037 @command('debugrebuilddirstate|debugrebuildstate',
2038 [('r', 'rev', '', _('revision to rebuild to'), _('REV')),
2038 [('r', 'rev', '', _('revision to rebuild to'), _('REV')),
2039 ('', 'minimal', None, _('only rebuild files that are inconsistent with '
2039 ('', 'minimal', None, _('only rebuild files that are inconsistent with '
2040 'the working copy parent')),
2040 'the working copy parent')),
2041 ],
2041 ],
2042 _('[-r REV]'))
2042 _('[-r REV]'))
2043 def debugrebuilddirstate(ui, repo, rev, **opts):
2043 def debugrebuilddirstate(ui, repo, rev, **opts):
2044 """rebuild the dirstate as it would look like for the given revision
2044 """rebuild the dirstate as it would look like for the given revision
2045
2045
2046 If no revision is specified the first current parent will be used.
2046 If no revision is specified the first current parent will be used.
2047
2047
2048 The dirstate will be set to the files of the given revision.
2048 The dirstate will be set to the files of the given revision.
2049 The actual working directory content or existing dirstate
2049 The actual working directory content or existing dirstate
2050 information such as adds or removes is not considered.
2050 information such as adds or removes is not considered.
2051
2051
2052 ``minimal`` will only rebuild the dirstate status for files that claim to be
2052 ``minimal`` will only rebuild the dirstate status for files that claim to be
2053 tracked but are not in the parent manifest, or that exist in the parent
2053 tracked but are not in the parent manifest, or that exist in the parent
2054 manifest but are not in the dirstate. It will not change adds, removes, or
2054 manifest but are not in the dirstate. It will not change adds, removes, or
2055 modified files that are in the working copy parent.
2055 modified files that are in the working copy parent.
2056
2056
2057 One use of this command is to make the next :hg:`status` invocation
2057 One use of this command is to make the next :hg:`status` invocation
2058 check the actual file content.
2058 check the actual file content.
2059 """
2059 """
2060 ctx = scmutil.revsingle(repo, rev)
2060 ctx = scmutil.revsingle(repo, rev)
2061 with repo.wlock():
2061 with repo.wlock():
2062 dirstate = repo.dirstate
2062 dirstate = repo.dirstate
2063 changedfiles = None
2063 changedfiles = None
2064 # See command doc for what minimal does.
2064 # See command doc for what minimal does.
2065 if opts.get(r'minimal'):
2065 if opts.get(r'minimal'):
2066 manifestfiles = set(ctx.manifest().keys())
2066 manifestfiles = set(ctx.manifest().keys())
2067 dirstatefiles = set(dirstate)
2067 dirstatefiles = set(dirstate)
2068 manifestonly = manifestfiles - dirstatefiles
2068 manifestonly = manifestfiles - dirstatefiles
2069 dsonly = dirstatefiles - manifestfiles
2069 dsonly = dirstatefiles - manifestfiles
2070 dsnotadded = set(f for f in dsonly if dirstate[f] != 'a')
2070 dsnotadded = set(f for f in dsonly if dirstate[f] != 'a')
2071 changedfiles = manifestonly | dsnotadded
2071 changedfiles = manifestonly | dsnotadded
2072
2072
2073 dirstate.rebuild(ctx.node(), ctx.manifest(), changedfiles)
2073 dirstate.rebuild(ctx.node(), ctx.manifest(), changedfiles)
2074
2074
2075 @command('debugrebuildfncache', [], '')
2075 @command('debugrebuildfncache', [], '')
2076 def debugrebuildfncache(ui, repo):
2076 def debugrebuildfncache(ui, repo):
2077 """rebuild the fncache file"""
2077 """rebuild the fncache file"""
2078 repair.rebuildfncache(ui, repo)
2078 repair.rebuildfncache(ui, repo)
2079
2079
2080 @command('debugrename',
2080 @command('debugrename',
2081 [('r', 'rev', '', _('revision to debug'), _('REV'))],
2081 [('r', 'rev', '', _('revision to debug'), _('REV'))],
2082 _('[-r REV] [FILE]...'))
2082 _('[-r REV] [FILE]...'))
2083 def debugrename(ui, repo, *pats, **opts):
2083 def debugrename(ui, repo, *pats, **opts):
2084 """dump rename information"""
2084 """dump rename information"""
2085
2085
2086 opts = pycompat.byteskwargs(opts)
2086 opts = pycompat.byteskwargs(opts)
2087 ctx = scmutil.revsingle(repo, opts.get('rev'))
2087 ctx = scmutil.revsingle(repo, opts.get('rev'))
2088 m = scmutil.match(ctx, pats, opts)
2088 m = scmutil.match(ctx, pats, opts)
2089 for abs in ctx.walk(m):
2089 for abs in ctx.walk(m):
2090 fctx = ctx[abs]
2090 fctx = ctx[abs]
2091 o = fctx.filelog().renamed(fctx.filenode())
2091 o = fctx.filelog().renamed(fctx.filenode())
2092 rel = repo.pathto(abs)
2092 rel = repo.pathto(abs)
2093 if o:
2093 if o:
2094 ui.write(_("%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
2094 ui.write(_("%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
2095 else:
2095 else:
2096 ui.write(_("%s not renamed\n") % rel)
2096 ui.write(_("%s not renamed\n") % rel)
2097
2097
2098 @command('debugrevlog', cmdutil.debugrevlogopts +
2098 @command('debugrevlog', cmdutil.debugrevlogopts +
2099 [('d', 'dump', False, _('dump index data'))],
2099 [('d', 'dump', False, _('dump index data'))],
2100 _('-c|-m|FILE'),
2100 _('-c|-m|FILE'),
2101 optionalrepo=True)
2101 optionalrepo=True)
2102 def debugrevlog(ui, repo, file_=None, **opts):
2102 def debugrevlog(ui, repo, file_=None, **opts):
2103 """show data and statistics about a revlog"""
2103 """show data and statistics about a revlog"""
2104 opts = pycompat.byteskwargs(opts)
2104 opts = pycompat.byteskwargs(opts)
2105 r = cmdutil.openrevlog(repo, 'debugrevlog', file_, opts)
2105 r = cmdutil.openrevlog(repo, 'debugrevlog', file_, opts)
2106
2106
2107 if opts.get("dump"):
2107 if opts.get("dump"):
2108 numrevs = len(r)
2108 numrevs = len(r)
2109 ui.write(("# rev p1rev p2rev start end deltastart base p1 p2"
2109 ui.write(("# rev p1rev p2rev start end deltastart base p1 p2"
2110 " rawsize totalsize compression heads chainlen\n"))
2110 " rawsize totalsize compression heads chainlen\n"))
2111 ts = 0
2111 ts = 0
2112 heads = set()
2112 heads = set()
2113
2113
2114 for rev in pycompat.xrange(numrevs):
2114 for rev in pycompat.xrange(numrevs):
2115 dbase = r.deltaparent(rev)
2115 dbase = r.deltaparent(rev)
2116 if dbase == -1:
2116 if dbase == -1:
2117 dbase = rev
2117 dbase = rev
2118 cbase = r.chainbase(rev)
2118 cbase = r.chainbase(rev)
2119 clen = r.chainlen(rev)
2119 clen = r.chainlen(rev)
2120 p1, p2 = r.parentrevs(rev)
2120 p1, p2 = r.parentrevs(rev)
2121 rs = r.rawsize(rev)
2121 rs = r.rawsize(rev)
2122 ts = ts + rs
2122 ts = ts + rs
2123 heads -= set(r.parentrevs(rev))
2123 heads -= set(r.parentrevs(rev))
2124 heads.add(rev)
2124 heads.add(rev)
2125 try:
2125 try:
2126 compression = ts / r.end(rev)
2126 compression = ts / r.end(rev)
2127 except ZeroDivisionError:
2127 except ZeroDivisionError:
2128 compression = 0
2128 compression = 0
2129 ui.write("%5d %5d %5d %5d %5d %10d %4d %4d %4d %7d %9d "
2129 ui.write("%5d %5d %5d %5d %5d %10d %4d %4d %4d %7d %9d "
2130 "%11d %5d %8d\n" %
2130 "%11d %5d %8d\n" %
2131 (rev, p1, p2, r.start(rev), r.end(rev),
2131 (rev, p1, p2, r.start(rev), r.end(rev),
2132 r.start(dbase), r.start(cbase),
2132 r.start(dbase), r.start(cbase),
2133 r.start(p1), r.start(p2),
2133 r.start(p1), r.start(p2),
2134 rs, ts, compression, len(heads), clen))
2134 rs, ts, compression, len(heads), clen))
2135 return 0
2135 return 0
2136
2136
2137 v = r.version
2137 v = r.version
2138 format = v & 0xFFFF
2138 format = v & 0xFFFF
2139 flags = []
2139 flags = []
2140 gdelta = False
2140 gdelta = False
2141 if v & revlog.FLAG_INLINE_DATA:
2141 if v & revlog.FLAG_INLINE_DATA:
2142 flags.append('inline')
2142 flags.append('inline')
2143 if v & revlog.FLAG_GENERALDELTA:
2143 if v & revlog.FLAG_GENERALDELTA:
2144 gdelta = True
2144 gdelta = True
2145 flags.append('generaldelta')
2145 flags.append('generaldelta')
2146 if not flags:
2146 if not flags:
2147 flags = ['(none)']
2147 flags = ['(none)']
2148
2148
2149 ### tracks merge vs single parent
2149 ### tracks merge vs single parent
2150 nummerges = 0
2150 nummerges = 0
2151
2151
2152 ### tracks ways the "delta" are build
2152 ### tracks ways the "delta" are build
2153 # nodelta
2153 # nodelta
2154 numempty = 0
2154 numempty = 0
2155 numemptytext = 0
2155 numemptytext = 0
2156 numemptydelta = 0
2156 numemptydelta = 0
2157 # full file content
2157 # full file content
2158 numfull = 0
2158 numfull = 0
2159 # intermediate snapshot against a prior snapshot
2159 # intermediate snapshot against a prior snapshot
2160 numsemi = 0
2160 numsemi = 0
2161 # snapshot count per depth
2161 # snapshot count per depth
2162 numsnapdepth = collections.defaultdict(lambda: 0)
2162 numsnapdepth = collections.defaultdict(lambda: 0)
2163 # delta against previous revision
2163 # delta against previous revision
2164 numprev = 0
2164 numprev = 0
2165 # delta against first or second parent (not prev)
2165 # delta against first or second parent (not prev)
2166 nump1 = 0
2166 nump1 = 0
2167 nump2 = 0
2167 nump2 = 0
2168 # delta against neither prev nor parents
2168 # delta against neither prev nor parents
2169 numother = 0
2169 numother = 0
2170 # delta against prev that are also first or second parent
2170 # delta against prev that are also first or second parent
2171 # (details of `numprev`)
2171 # (details of `numprev`)
2172 nump1prev = 0
2172 nump1prev = 0
2173 nump2prev = 0
2173 nump2prev = 0
2174
2174
2175 # data about delta chain of each revs
2175 # data about delta chain of each revs
2176 chainlengths = []
2176 chainlengths = []
2177 chainbases = []
2177 chainbases = []
2178 chainspans = []
2178 chainspans = []
2179
2179
2180 # data about each revision
2180 # data about each revision
2181 datasize = [None, 0, 0]
2181 datasize = [None, 0, 0]
2182 fullsize = [None, 0, 0]
2182 fullsize = [None, 0, 0]
2183 semisize = [None, 0, 0]
2183 semisize = [None, 0, 0]
2184 # snapshot count per depth
2184 # snapshot count per depth
2185 snapsizedepth = collections.defaultdict(lambda: [None, 0, 0])
2185 snapsizedepth = collections.defaultdict(lambda: [None, 0, 0])
2186 deltasize = [None, 0, 0]
2186 deltasize = [None, 0, 0]
2187 chunktypecounts = {}
2187 chunktypecounts = {}
2188 chunktypesizes = {}
2188 chunktypesizes = {}
2189
2189
2190 def addsize(size, l):
2190 def addsize(size, l):
2191 if l[0] is None or size < l[0]:
2191 if l[0] is None or size < l[0]:
2192 l[0] = size
2192 l[0] = size
2193 if size > l[1]:
2193 if size > l[1]:
2194 l[1] = size
2194 l[1] = size
2195 l[2] += size
2195 l[2] += size
2196
2196
2197 numrevs = len(r)
2197 numrevs = len(r)
2198 for rev in pycompat.xrange(numrevs):
2198 for rev in pycompat.xrange(numrevs):
2199 p1, p2 = r.parentrevs(rev)
2199 p1, p2 = r.parentrevs(rev)
2200 delta = r.deltaparent(rev)
2200 delta = r.deltaparent(rev)
2201 if format > 0:
2201 if format > 0:
2202 addsize(r.rawsize(rev), datasize)
2202 addsize(r.rawsize(rev), datasize)
2203 if p2 != nullrev:
2203 if p2 != nullrev:
2204 nummerges += 1
2204 nummerges += 1
2205 size = r.length(rev)
2205 size = r.length(rev)
2206 if delta == nullrev:
2206 if delta == nullrev:
2207 chainlengths.append(0)
2207 chainlengths.append(0)
2208 chainbases.append(r.start(rev))
2208 chainbases.append(r.start(rev))
2209 chainspans.append(size)
2209 chainspans.append(size)
2210 if size == 0:
2210 if size == 0:
2211 numempty += 1
2211 numempty += 1
2212 numemptytext += 1
2212 numemptytext += 1
2213 else:
2213 else:
2214 numfull += 1
2214 numfull += 1
2215 numsnapdepth[0] += 1
2215 numsnapdepth[0] += 1
2216 addsize(size, fullsize)
2216 addsize(size, fullsize)
2217 addsize(size, snapsizedepth[0])
2217 addsize(size, snapsizedepth[0])
2218 else:
2218 else:
2219 chainlengths.append(chainlengths[delta] + 1)
2219 chainlengths.append(chainlengths[delta] + 1)
2220 baseaddr = chainbases[delta]
2220 baseaddr = chainbases[delta]
2221 revaddr = r.start(rev)
2221 revaddr = r.start(rev)
2222 chainbases.append(baseaddr)
2222 chainbases.append(baseaddr)
2223 chainspans.append((revaddr - baseaddr) + size)
2223 chainspans.append((revaddr - baseaddr) + size)
2224 if size == 0:
2224 if size == 0:
2225 numempty += 1
2225 numempty += 1
2226 numemptydelta += 1
2226 numemptydelta += 1
2227 elif r.issnapshot(rev):
2227 elif r.issnapshot(rev):
2228 addsize(size, semisize)
2228 addsize(size, semisize)
2229 numsemi += 1
2229 numsemi += 1
2230 depth = r.snapshotdepth(rev)
2230 depth = r.snapshotdepth(rev)
2231 numsnapdepth[depth] += 1
2231 numsnapdepth[depth] += 1
2232 addsize(size, snapsizedepth[depth])
2232 addsize(size, snapsizedepth[depth])
2233 else:
2233 else:
2234 addsize(size, deltasize)
2234 addsize(size, deltasize)
2235 if delta == rev - 1:
2235 if delta == rev - 1:
2236 numprev += 1
2236 numprev += 1
2237 if delta == p1:
2237 if delta == p1:
2238 nump1prev += 1
2238 nump1prev += 1
2239 elif delta == p2:
2239 elif delta == p2:
2240 nump2prev += 1
2240 nump2prev += 1
2241 elif delta == p1:
2241 elif delta == p1:
2242 nump1 += 1
2242 nump1 += 1
2243 elif delta == p2:
2243 elif delta == p2:
2244 nump2 += 1
2244 nump2 += 1
2245 elif delta != nullrev:
2245 elif delta != nullrev:
2246 numother += 1
2246 numother += 1
2247
2247
2248 # Obtain data on the raw chunks in the revlog.
2248 # Obtain data on the raw chunks in the revlog.
2249 if util.safehasattr(r, '_getsegmentforrevs'):
2249 if util.safehasattr(r, '_getsegmentforrevs'):
2250 segment = r._getsegmentforrevs(rev, rev)[1]
2250 segment = r._getsegmentforrevs(rev, rev)[1]
2251 else:
2251 else:
2252 segment = r._revlog._getsegmentforrevs(rev, rev)[1]
2252 segment = r._revlog._getsegmentforrevs(rev, rev)[1]
2253 if segment:
2253 if segment:
2254 chunktype = bytes(segment[0:1])
2254 chunktype = bytes(segment[0:1])
2255 else:
2255 else:
2256 chunktype = 'empty'
2256 chunktype = 'empty'
2257
2257
2258 if chunktype not in chunktypecounts:
2258 if chunktype not in chunktypecounts:
2259 chunktypecounts[chunktype] = 0
2259 chunktypecounts[chunktype] = 0
2260 chunktypesizes[chunktype] = 0
2260 chunktypesizes[chunktype] = 0
2261
2261
2262 chunktypecounts[chunktype] += 1
2262 chunktypecounts[chunktype] += 1
2263 chunktypesizes[chunktype] += size
2263 chunktypesizes[chunktype] += size
2264
2264
2265 # Adjust size min value for empty cases
2265 # Adjust size min value for empty cases
2266 for size in (datasize, fullsize, semisize, deltasize):
2266 for size in (datasize, fullsize, semisize, deltasize):
2267 if size[0] is None:
2267 if size[0] is None:
2268 size[0] = 0
2268 size[0] = 0
2269
2269
2270 numdeltas = numrevs - numfull - numempty - numsemi
2270 numdeltas = numrevs - numfull - numempty - numsemi
2271 numoprev = numprev - nump1prev - nump2prev
2271 numoprev = numprev - nump1prev - nump2prev
2272 totalrawsize = datasize[2]
2272 totalrawsize = datasize[2]
2273 datasize[2] /= numrevs
2273 datasize[2] /= numrevs
2274 fulltotal = fullsize[2]
2274 fulltotal = fullsize[2]
2275 fullsize[2] /= numfull
2275 fullsize[2] /= numfull
2276 semitotal = semisize[2]
2276 semitotal = semisize[2]
2277 snaptotal = {}
2277 snaptotal = {}
2278 if numsemi > 0:
2278 if numsemi > 0:
2279 semisize[2] /= numsemi
2279 semisize[2] /= numsemi
2280 for depth in snapsizedepth:
2280 for depth in snapsizedepth:
2281 snaptotal[depth] = snapsizedepth[depth][2]
2281 snaptotal[depth] = snapsizedepth[depth][2]
2282 snapsizedepth[depth][2] /= numsnapdepth[depth]
2282 snapsizedepth[depth][2] /= numsnapdepth[depth]
2283
2283
2284 deltatotal = deltasize[2]
2284 deltatotal = deltasize[2]
2285 if numdeltas > 0:
2285 if numdeltas > 0:
2286 deltasize[2] /= numdeltas
2286 deltasize[2] /= numdeltas
2287 totalsize = fulltotal + semitotal + deltatotal
2287 totalsize = fulltotal + semitotal + deltatotal
2288 avgchainlen = sum(chainlengths) / numrevs
2288 avgchainlen = sum(chainlengths) / numrevs
2289 maxchainlen = max(chainlengths)
2289 maxchainlen = max(chainlengths)
2290 maxchainspan = max(chainspans)
2290 maxchainspan = max(chainspans)
2291 compratio = 1
2291 compratio = 1
2292 if totalsize:
2292 if totalsize:
2293 compratio = totalrawsize / totalsize
2293 compratio = totalrawsize / totalsize
2294
2294
2295 basedfmtstr = '%%%dd\n'
2295 basedfmtstr = '%%%dd\n'
2296 basepcfmtstr = '%%%dd %s(%%5.2f%%%%)\n'
2296 basepcfmtstr = '%%%dd %s(%%5.2f%%%%)\n'
2297
2297
2298 def dfmtstr(max):
2298 def dfmtstr(max):
2299 return basedfmtstr % len(str(max))
2299 return basedfmtstr % len(str(max))
2300 def pcfmtstr(max, padding=0):
2300 def pcfmtstr(max, padding=0):
2301 return basepcfmtstr % (len(str(max)), ' ' * padding)
2301 return basepcfmtstr % (len(str(max)), ' ' * padding)
2302
2302
2303 def pcfmt(value, total):
2303 def pcfmt(value, total):
2304 if total:
2304 if total:
2305 return (value, 100 * float(value) / total)
2305 return (value, 100 * float(value) / total)
2306 else:
2306 else:
2307 return value, 100.0
2307 return value, 100.0
2308
2308
2309 ui.write(('format : %d\n') % format)
2309 ui.write(('format : %d\n') % format)
2310 ui.write(('flags : %s\n') % ', '.join(flags))
2310 ui.write(('flags : %s\n') % ', '.join(flags))
2311
2311
2312 ui.write('\n')
2312 ui.write('\n')
2313 fmt = pcfmtstr(totalsize)
2313 fmt = pcfmtstr(totalsize)
2314 fmt2 = dfmtstr(totalsize)
2314 fmt2 = dfmtstr(totalsize)
2315 ui.write(('revisions : ') + fmt2 % numrevs)
2315 ui.write(('revisions : ') + fmt2 % numrevs)
2316 ui.write((' merges : ') + fmt % pcfmt(nummerges, numrevs))
2316 ui.write((' merges : ') + fmt % pcfmt(nummerges, numrevs))
2317 ui.write((' normal : ') + fmt % pcfmt(numrevs - nummerges, numrevs))
2317 ui.write((' normal : ') + fmt % pcfmt(numrevs - nummerges, numrevs))
2318 ui.write(('revisions : ') + fmt2 % numrevs)
2318 ui.write(('revisions : ') + fmt2 % numrevs)
2319 ui.write((' empty : ') + fmt % pcfmt(numempty, numrevs))
2319 ui.write((' empty : ') + fmt % pcfmt(numempty, numrevs))
2320 ui.write((' text : ')
2320 ui.write((' text : ')
2321 + fmt % pcfmt(numemptytext, numemptytext + numemptydelta))
2321 + fmt % pcfmt(numemptytext, numemptytext + numemptydelta))
2322 ui.write((' delta : ')
2322 ui.write((' delta : ')
2323 + fmt % pcfmt(numemptydelta, numemptytext + numemptydelta))
2323 + fmt % pcfmt(numemptydelta, numemptytext + numemptydelta))
2324 ui.write((' snapshot : ') + fmt % pcfmt(numfull + numsemi, numrevs))
2324 ui.write((' snapshot : ') + fmt % pcfmt(numfull + numsemi, numrevs))
2325 for depth in sorted(numsnapdepth):
2325 for depth in sorted(numsnapdepth):
2326 ui.write((' lvl-%-3d : ' % depth)
2326 ui.write((' lvl-%-3d : ' % depth)
2327 + fmt % pcfmt(numsnapdepth[depth], numrevs))
2327 + fmt % pcfmt(numsnapdepth[depth], numrevs))
2328 ui.write((' deltas : ') + fmt % pcfmt(numdeltas, numrevs))
2328 ui.write((' deltas : ') + fmt % pcfmt(numdeltas, numrevs))
2329 ui.write(('revision size : ') + fmt2 % totalsize)
2329 ui.write(('revision size : ') + fmt2 % totalsize)
2330 ui.write((' snapshot : ')
2330 ui.write((' snapshot : ')
2331 + fmt % pcfmt(fulltotal + semitotal, totalsize))
2331 + fmt % pcfmt(fulltotal + semitotal, totalsize))
2332 for depth in sorted(numsnapdepth):
2332 for depth in sorted(numsnapdepth):
2333 ui.write((' lvl-%-3d : ' % depth)
2333 ui.write((' lvl-%-3d : ' % depth)
2334 + fmt % pcfmt(snaptotal[depth], totalsize))
2334 + fmt % pcfmt(snaptotal[depth], totalsize))
2335 ui.write((' deltas : ') + fmt % pcfmt(deltatotal, totalsize))
2335 ui.write((' deltas : ') + fmt % pcfmt(deltatotal, totalsize))
2336
2336
2337 def fmtchunktype(chunktype):
2337 def fmtchunktype(chunktype):
2338 if chunktype == 'empty':
2338 if chunktype == 'empty':
2339 return ' %s : ' % chunktype
2339 return ' %s : ' % chunktype
2340 elif chunktype in pycompat.bytestr(string.ascii_letters):
2340 elif chunktype in pycompat.bytestr(string.ascii_letters):
2341 return ' 0x%s (%s) : ' % (hex(chunktype), chunktype)
2341 return ' 0x%s (%s) : ' % (hex(chunktype), chunktype)
2342 else:
2342 else:
2343 return ' 0x%s : ' % hex(chunktype)
2343 return ' 0x%s : ' % hex(chunktype)
2344
2344
2345 ui.write('\n')
2345 ui.write('\n')
2346 ui.write(('chunks : ') + fmt2 % numrevs)
2346 ui.write(('chunks : ') + fmt2 % numrevs)
2347 for chunktype in sorted(chunktypecounts):
2347 for chunktype in sorted(chunktypecounts):
2348 ui.write(fmtchunktype(chunktype))
2348 ui.write(fmtchunktype(chunktype))
2349 ui.write(fmt % pcfmt(chunktypecounts[chunktype], numrevs))
2349 ui.write(fmt % pcfmt(chunktypecounts[chunktype], numrevs))
2350 ui.write(('chunks size : ') + fmt2 % totalsize)
2350 ui.write(('chunks size : ') + fmt2 % totalsize)
2351 for chunktype in sorted(chunktypecounts):
2351 for chunktype in sorted(chunktypecounts):
2352 ui.write(fmtchunktype(chunktype))
2352 ui.write(fmtchunktype(chunktype))
2353 ui.write(fmt % pcfmt(chunktypesizes[chunktype], totalsize))
2353 ui.write(fmt % pcfmt(chunktypesizes[chunktype], totalsize))
2354
2354
2355 ui.write('\n')
2355 ui.write('\n')
2356 fmt = dfmtstr(max(avgchainlen, maxchainlen, maxchainspan, compratio))
2356 fmt = dfmtstr(max(avgchainlen, maxchainlen, maxchainspan, compratio))
2357 ui.write(('avg chain length : ') + fmt % avgchainlen)
2357 ui.write(('avg chain length : ') + fmt % avgchainlen)
2358 ui.write(('max chain length : ') + fmt % maxchainlen)
2358 ui.write(('max chain length : ') + fmt % maxchainlen)
2359 ui.write(('max chain reach : ') + fmt % maxchainspan)
2359 ui.write(('max chain reach : ') + fmt % maxchainspan)
2360 ui.write(('compression ratio : ') + fmt % compratio)
2360 ui.write(('compression ratio : ') + fmt % compratio)
2361
2361
2362 if format > 0:
2362 if format > 0:
2363 ui.write('\n')
2363 ui.write('\n')
2364 ui.write(('uncompressed data size (min/max/avg) : %d / %d / %d\n')
2364 ui.write(('uncompressed data size (min/max/avg) : %d / %d / %d\n')
2365 % tuple(datasize))
2365 % tuple(datasize))
2366 ui.write(('full revision size (min/max/avg) : %d / %d / %d\n')
2366 ui.write(('full revision size (min/max/avg) : %d / %d / %d\n')
2367 % tuple(fullsize))
2367 % tuple(fullsize))
2368 ui.write(('inter-snapshot size (min/max/avg) : %d / %d / %d\n')
2368 ui.write(('inter-snapshot size (min/max/avg) : %d / %d / %d\n')
2369 % tuple(semisize))
2369 % tuple(semisize))
2370 for depth in sorted(snapsizedepth):
2370 for depth in sorted(snapsizedepth):
2371 if depth == 0:
2371 if depth == 0:
2372 continue
2372 continue
2373 ui.write((' level-%-3d (min/max/avg) : %d / %d / %d\n')
2373 ui.write((' level-%-3d (min/max/avg) : %d / %d / %d\n')
2374 % ((depth,) + tuple(snapsizedepth[depth])))
2374 % ((depth,) + tuple(snapsizedepth[depth])))
2375 ui.write(('delta size (min/max/avg) : %d / %d / %d\n')
2375 ui.write(('delta size (min/max/avg) : %d / %d / %d\n')
2376 % tuple(deltasize))
2376 % tuple(deltasize))
2377
2377
2378 if numdeltas > 0:
2378 if numdeltas > 0:
2379 ui.write('\n')
2379 ui.write('\n')
2380 fmt = pcfmtstr(numdeltas)
2380 fmt = pcfmtstr(numdeltas)
2381 fmt2 = pcfmtstr(numdeltas, 4)
2381 fmt2 = pcfmtstr(numdeltas, 4)
2382 ui.write(('deltas against prev : ') + fmt % pcfmt(numprev, numdeltas))
2382 ui.write(('deltas against prev : ') + fmt % pcfmt(numprev, numdeltas))
2383 if numprev > 0:
2383 if numprev > 0:
2384 ui.write((' where prev = p1 : ') + fmt2 % pcfmt(nump1prev,
2384 ui.write((' where prev = p1 : ') + fmt2 % pcfmt(nump1prev,
2385 numprev))
2385 numprev))
2386 ui.write((' where prev = p2 : ') + fmt2 % pcfmt(nump2prev,
2386 ui.write((' where prev = p2 : ') + fmt2 % pcfmt(nump2prev,
2387 numprev))
2387 numprev))
2388 ui.write((' other : ') + fmt2 % pcfmt(numoprev,
2388 ui.write((' other : ') + fmt2 % pcfmt(numoprev,
2389 numprev))
2389 numprev))
2390 if gdelta:
2390 if gdelta:
2391 ui.write(('deltas against p1 : ')
2391 ui.write(('deltas against p1 : ')
2392 + fmt % pcfmt(nump1, numdeltas))
2392 + fmt % pcfmt(nump1, numdeltas))
2393 ui.write(('deltas against p2 : ')
2393 ui.write(('deltas against p2 : ')
2394 + fmt % pcfmt(nump2, numdeltas))
2394 + fmt % pcfmt(nump2, numdeltas))
2395 ui.write(('deltas against other : ') + fmt % pcfmt(numother,
2395 ui.write(('deltas against other : ') + fmt % pcfmt(numother,
2396 numdeltas))
2396 numdeltas))
2397
2397
2398 @command('debugrevlogindex', cmdutil.debugrevlogopts +
2398 @command('debugrevlogindex', cmdutil.debugrevlogopts +
2399 [('f', 'format', 0, _('revlog format'), _('FORMAT'))],
2399 [('f', 'format', 0, _('revlog format'), _('FORMAT'))],
2400 _('[-f FORMAT] -c|-m|FILE'),
2400 _('[-f FORMAT] -c|-m|FILE'),
2401 optionalrepo=True)
2401 optionalrepo=True)
2402 def debugrevlogindex(ui, repo, file_=None, **opts):
2402 def debugrevlogindex(ui, repo, file_=None, **opts):
2403 """dump the contents of a revlog index"""
2403 """dump the contents of a revlog index"""
2404 opts = pycompat.byteskwargs(opts)
2404 opts = pycompat.byteskwargs(opts)
2405 r = cmdutil.openrevlog(repo, 'debugrevlogindex', file_, opts)
2405 r = cmdutil.openrevlog(repo, 'debugrevlogindex', file_, opts)
2406 format = opts.get('format', 0)
2406 format = opts.get('format', 0)
2407 if format not in (0, 1):
2407 if format not in (0, 1):
2408 raise error.Abort(_("unknown format %d") % format)
2408 raise error.Abort(_("unknown format %d") % format)
2409
2409
2410 if ui.debugflag:
2410 if ui.debugflag:
2411 shortfn = hex
2411 shortfn = hex
2412 else:
2412 else:
2413 shortfn = short
2413 shortfn = short
2414
2414
2415 # There might not be anything in r, so have a sane default
2415 # There might not be anything in r, so have a sane default
2416 idlen = 12
2416 idlen = 12
2417 for i in r:
2417 for i in r:
2418 idlen = len(shortfn(r.node(i)))
2418 idlen = len(shortfn(r.node(i)))
2419 break
2419 break
2420
2420
2421 if format == 0:
2421 if format == 0:
2422 if ui.verbose:
2422 if ui.verbose:
2423 ui.write((" rev offset length linkrev"
2423 ui.write((" rev offset length linkrev"
2424 " %s %s p2\n") % ("nodeid".ljust(idlen),
2424 " %s %s p2\n") % ("nodeid".ljust(idlen),
2425 "p1".ljust(idlen)))
2425 "p1".ljust(idlen)))
2426 else:
2426 else:
2427 ui.write((" rev linkrev %s %s p2\n") % (
2427 ui.write((" rev linkrev %s %s p2\n") % (
2428 "nodeid".ljust(idlen), "p1".ljust(idlen)))
2428 "nodeid".ljust(idlen), "p1".ljust(idlen)))
2429 elif format == 1:
2429 elif format == 1:
2430 if ui.verbose:
2430 if ui.verbose:
2431 ui.write((" rev flag offset length size link p1"
2431 ui.write((" rev flag offset length size link p1"
2432 " p2 %s\n") % "nodeid".rjust(idlen))
2432 " p2 %s\n") % "nodeid".rjust(idlen))
2433 else:
2433 else:
2434 ui.write((" rev flag size link p1 p2 %s\n") %
2434 ui.write((" rev flag size link p1 p2 %s\n") %
2435 "nodeid".rjust(idlen))
2435 "nodeid".rjust(idlen))
2436
2436
2437 for i in r:
2437 for i in r:
2438 node = r.node(i)
2438 node = r.node(i)
2439 if format == 0:
2439 if format == 0:
2440 try:
2440 try:
2441 pp = r.parents(node)
2441 pp = r.parents(node)
2442 except Exception:
2442 except Exception:
2443 pp = [nullid, nullid]
2443 pp = [nullid, nullid]
2444 if ui.verbose:
2444 if ui.verbose:
2445 ui.write("% 6d % 9d % 7d % 7d %s %s %s\n" % (
2445 ui.write("% 6d % 9d % 7d % 7d %s %s %s\n" % (
2446 i, r.start(i), r.length(i), r.linkrev(i),
2446 i, r.start(i), r.length(i), r.linkrev(i),
2447 shortfn(node), shortfn(pp[0]), shortfn(pp[1])))
2447 shortfn(node), shortfn(pp[0]), shortfn(pp[1])))
2448 else:
2448 else:
2449 ui.write("% 6d % 7d %s %s %s\n" % (
2449 ui.write("% 6d % 7d %s %s %s\n" % (
2450 i, r.linkrev(i), shortfn(node), shortfn(pp[0]),
2450 i, r.linkrev(i), shortfn(node), shortfn(pp[0]),
2451 shortfn(pp[1])))
2451 shortfn(pp[1])))
2452 elif format == 1:
2452 elif format == 1:
2453 pr = r.parentrevs(i)
2453 pr = r.parentrevs(i)
2454 if ui.verbose:
2454 if ui.verbose:
2455 ui.write("% 6d %04x % 8d % 8d % 8d % 6d % 6d % 6d %s\n" % (
2455 ui.write("% 6d %04x % 8d % 8d % 8d % 6d % 6d % 6d %s\n" % (
2456 i, r.flags(i), r.start(i), r.length(i), r.rawsize(i),
2456 i, r.flags(i), r.start(i), r.length(i), r.rawsize(i),
2457 r.linkrev(i), pr[0], pr[1], shortfn(node)))
2457 r.linkrev(i), pr[0], pr[1], shortfn(node)))
2458 else:
2458 else:
2459 ui.write("% 6d %04x % 8d % 6d % 6d % 6d %s\n" % (
2459 ui.write("% 6d %04x % 8d % 6d % 6d % 6d %s\n" % (
2460 i, r.flags(i), r.rawsize(i), r.linkrev(i), pr[0], pr[1],
2460 i, r.flags(i), r.rawsize(i), r.linkrev(i), pr[0], pr[1],
2461 shortfn(node)))
2461 shortfn(node)))
2462
2462
2463 @command('debugrevspec',
2463 @command('debugrevspec',
2464 [('', 'optimize', None,
2464 [('', 'optimize', None,
2465 _('print parsed tree after optimizing (DEPRECATED)')),
2465 _('print parsed tree after optimizing (DEPRECATED)')),
2466 ('', 'show-revs', True, _('print list of result revisions (default)')),
2466 ('', 'show-revs', True, _('print list of result revisions (default)')),
2467 ('s', 'show-set', None, _('print internal representation of result set')),
2467 ('s', 'show-set', None, _('print internal representation of result set')),
2468 ('p', 'show-stage', [],
2468 ('p', 'show-stage', [],
2469 _('print parsed tree at the given stage'), _('NAME')),
2469 _('print parsed tree at the given stage'), _('NAME')),
2470 ('', 'no-optimized', False, _('evaluate tree without optimization')),
2470 ('', 'no-optimized', False, _('evaluate tree without optimization')),
2471 ('', 'verify-optimized', False, _('verify optimized result')),
2471 ('', 'verify-optimized', False, _('verify optimized result')),
2472 ],
2472 ],
2473 ('REVSPEC'))
2473 ('REVSPEC'))
2474 def debugrevspec(ui, repo, expr, **opts):
2474 def debugrevspec(ui, repo, expr, **opts):
2475 """parse and apply a revision specification
2475 """parse and apply a revision specification
2476
2476
2477 Use -p/--show-stage option to print the parsed tree at the given stages.
2477 Use -p/--show-stage option to print the parsed tree at the given stages.
2478 Use -p all to print tree at every stage.
2478 Use -p all to print tree at every stage.
2479
2479
2480 Use --no-show-revs option with -s or -p to print only the set
2480 Use --no-show-revs option with -s or -p to print only the set
2481 representation or the parsed tree respectively.
2481 representation or the parsed tree respectively.
2482
2482
2483 Use --verify-optimized to compare the optimized result with the unoptimized
2483 Use --verify-optimized to compare the optimized result with the unoptimized
2484 one. Returns 1 if the optimized result differs.
2484 one. Returns 1 if the optimized result differs.
2485 """
2485 """
2486 opts = pycompat.byteskwargs(opts)
2486 opts = pycompat.byteskwargs(opts)
2487 aliases = ui.configitems('revsetalias')
2487 aliases = ui.configitems('revsetalias')
2488 stages = [
2488 stages = [
2489 ('parsed', lambda tree: tree),
2489 ('parsed', lambda tree: tree),
2490 ('expanded', lambda tree: revsetlang.expandaliases(tree, aliases,
2490 ('expanded', lambda tree: revsetlang.expandaliases(tree, aliases,
2491 ui.warn)),
2491 ui.warn)),
2492 ('concatenated', revsetlang.foldconcat),
2492 ('concatenated', revsetlang.foldconcat),
2493 ('analyzed', revsetlang.analyze),
2493 ('analyzed', revsetlang.analyze),
2494 ('optimized', revsetlang.optimize),
2494 ('optimized', revsetlang.optimize),
2495 ]
2495 ]
2496 if opts['no_optimized']:
2496 if opts['no_optimized']:
2497 stages = stages[:-1]
2497 stages = stages[:-1]
2498 if opts['verify_optimized'] and opts['no_optimized']:
2498 if opts['verify_optimized'] and opts['no_optimized']:
2499 raise error.Abort(_('cannot use --verify-optimized with '
2499 raise error.Abort(_('cannot use --verify-optimized with '
2500 '--no-optimized'))
2500 '--no-optimized'))
2501 stagenames = set(n for n, f in stages)
2501 stagenames = set(n for n, f in stages)
2502
2502
2503 showalways = set()
2503 showalways = set()
2504 showchanged = set()
2504 showchanged = set()
2505 if ui.verbose and not opts['show_stage']:
2505 if ui.verbose and not opts['show_stage']:
2506 # show parsed tree by --verbose (deprecated)
2506 # show parsed tree by --verbose (deprecated)
2507 showalways.add('parsed')
2507 showalways.add('parsed')
2508 showchanged.update(['expanded', 'concatenated'])
2508 showchanged.update(['expanded', 'concatenated'])
2509 if opts['optimize']:
2509 if opts['optimize']:
2510 showalways.add('optimized')
2510 showalways.add('optimized')
2511 if opts['show_stage'] and opts['optimize']:
2511 if opts['show_stage'] and opts['optimize']:
2512 raise error.Abort(_('cannot use --optimize with --show-stage'))
2512 raise error.Abort(_('cannot use --optimize with --show-stage'))
2513 if opts['show_stage'] == ['all']:
2513 if opts['show_stage'] == ['all']:
2514 showalways.update(stagenames)
2514 showalways.update(stagenames)
2515 else:
2515 else:
2516 for n in opts['show_stage']:
2516 for n in opts['show_stage']:
2517 if n not in stagenames:
2517 if n not in stagenames:
2518 raise error.Abort(_('invalid stage name: %s') % n)
2518 raise error.Abort(_('invalid stage name: %s') % n)
2519 showalways.update(opts['show_stage'])
2519 showalways.update(opts['show_stage'])
2520
2520
2521 treebystage = {}
2521 treebystage = {}
2522 printedtree = None
2522 printedtree = None
2523 tree = revsetlang.parse(expr, lookup=revset.lookupfn(repo))
2523 tree = revsetlang.parse(expr, lookup=revset.lookupfn(repo))
2524 for n, f in stages:
2524 for n, f in stages:
2525 treebystage[n] = tree = f(tree)
2525 treebystage[n] = tree = f(tree)
2526 if n in showalways or (n in showchanged and tree != printedtree):
2526 if n in showalways or (n in showchanged and tree != printedtree):
2527 if opts['show_stage'] or n != 'parsed':
2527 if opts['show_stage'] or n != 'parsed':
2528 ui.write(("* %s:\n") % n)
2528 ui.write(("* %s:\n") % n)
2529 ui.write(revsetlang.prettyformat(tree), "\n")
2529 ui.write(revsetlang.prettyformat(tree), "\n")
2530 printedtree = tree
2530 printedtree = tree
2531
2531
2532 if opts['verify_optimized']:
2532 if opts['verify_optimized']:
2533 arevs = revset.makematcher(treebystage['analyzed'])(repo)
2533 arevs = revset.makematcher(treebystage['analyzed'])(repo)
2534 brevs = revset.makematcher(treebystage['optimized'])(repo)
2534 brevs = revset.makematcher(treebystage['optimized'])(repo)
2535 if opts['show_set'] or (opts['show_set'] is None and ui.verbose):
2535 if opts['show_set'] or (opts['show_set'] is None and ui.verbose):
2536 ui.write(("* analyzed set:\n"), stringutil.prettyrepr(arevs), "\n")
2536 ui.write(("* analyzed set:\n"), stringutil.prettyrepr(arevs), "\n")
2537 ui.write(("* optimized set:\n"), stringutil.prettyrepr(brevs), "\n")
2537 ui.write(("* optimized set:\n"), stringutil.prettyrepr(brevs), "\n")
2538 arevs = list(arevs)
2538 arevs = list(arevs)
2539 brevs = list(brevs)
2539 brevs = list(brevs)
2540 if arevs == brevs:
2540 if arevs == brevs:
2541 return 0
2541 return 0
2542 ui.write(('--- analyzed\n'), label='diff.file_a')
2542 ui.write(('--- analyzed\n'), label='diff.file_a')
2543 ui.write(('+++ optimized\n'), label='diff.file_b')
2543 ui.write(('+++ optimized\n'), label='diff.file_b')
2544 sm = difflib.SequenceMatcher(None, arevs, brevs)
2544 sm = difflib.SequenceMatcher(None, arevs, brevs)
2545 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
2545 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
2546 if tag in (r'delete', r'replace'):
2546 if tag in (r'delete', r'replace'):
2547 for c in arevs[alo:ahi]:
2547 for c in arevs[alo:ahi]:
2548 ui.write('-%d\n' % c, label='diff.deleted')
2548 ui.write('-%d\n' % c, label='diff.deleted')
2549 if tag in (r'insert', r'replace'):
2549 if tag in (r'insert', r'replace'):
2550 for c in brevs[blo:bhi]:
2550 for c in brevs[blo:bhi]:
2551 ui.write('+%d\n' % c, label='diff.inserted')
2551 ui.write('+%d\n' % c, label='diff.inserted')
2552 if tag == r'equal':
2552 if tag == r'equal':
2553 for c in arevs[alo:ahi]:
2553 for c in arevs[alo:ahi]:
2554 ui.write(' %d\n' % c)
2554 ui.write(' %d\n' % c)
2555 return 1
2555 return 1
2556
2556
2557 func = revset.makematcher(tree)
2557 func = revset.makematcher(tree)
2558 revs = func(repo)
2558 revs = func(repo)
2559 if opts['show_set'] or (opts['show_set'] is None and ui.verbose):
2559 if opts['show_set'] or (opts['show_set'] is None and ui.verbose):
2560 ui.write(("* set:\n"), stringutil.prettyrepr(revs), "\n")
2560 ui.write(("* set:\n"), stringutil.prettyrepr(revs), "\n")
2561 if not opts['show_revs']:
2561 if not opts['show_revs']:
2562 return
2562 return
2563 for c in revs:
2563 for c in revs:
2564 ui.write("%d\n" % c)
2564 ui.write("%d\n" % c)
2565
2565
2566 @command('debugserve', [
2566 @command('debugserve', [
2567 ('', 'sshstdio', False, _('run an SSH server bound to process handles')),
2567 ('', 'sshstdio', False, _('run an SSH server bound to process handles')),
2568 ('', 'logiofd', '', _('file descriptor to log server I/O to')),
2568 ('', 'logiofd', '', _('file descriptor to log server I/O to')),
2569 ('', 'logiofile', '', _('file to log server I/O to')),
2569 ('', 'logiofile', '', _('file to log server I/O to')),
2570 ], '')
2570 ], '')
2571 def debugserve(ui, repo, **opts):
2571 def debugserve(ui, repo, **opts):
2572 """run a server with advanced settings
2572 """run a server with advanced settings
2573
2573
2574 This command is similar to :hg:`serve`. It exists partially as a
2574 This command is similar to :hg:`serve`. It exists partially as a
2575 workaround to the fact that ``hg serve --stdio`` must have specific
2575 workaround to the fact that ``hg serve --stdio`` must have specific
2576 arguments for security reasons.
2576 arguments for security reasons.
2577 """
2577 """
2578 opts = pycompat.byteskwargs(opts)
2578 opts = pycompat.byteskwargs(opts)
2579
2579
2580 if not opts['sshstdio']:
2580 if not opts['sshstdio']:
2581 raise error.Abort(_('only --sshstdio is currently supported'))
2581 raise error.Abort(_('only --sshstdio is currently supported'))
2582
2582
2583 logfh = None
2583 logfh = None
2584
2584
2585 if opts['logiofd'] and opts['logiofile']:
2585 if opts['logiofd'] and opts['logiofile']:
2586 raise error.Abort(_('cannot use both --logiofd and --logiofile'))
2586 raise error.Abort(_('cannot use both --logiofd and --logiofile'))
2587
2587
2588 if opts['logiofd']:
2588 if opts['logiofd']:
2589 # Line buffered because output is line based.
2589 # Line buffered because output is line based.
2590 try:
2590 try:
2591 logfh = os.fdopen(int(opts['logiofd']), r'ab', 1)
2591 logfh = os.fdopen(int(opts['logiofd']), r'ab', 1)
2592 except OSError as e:
2592 except OSError as e:
2593 if e.errno != errno.ESPIPE:
2593 if e.errno != errno.ESPIPE:
2594 raise
2594 raise
2595 # can't seek a pipe, so `ab` mode fails on py3
2595 # can't seek a pipe, so `ab` mode fails on py3
2596 logfh = os.fdopen(int(opts['logiofd']), r'wb', 1)
2596 logfh = os.fdopen(int(opts['logiofd']), r'wb', 1)
2597 elif opts['logiofile']:
2597 elif opts['logiofile']:
2598 logfh = open(opts['logiofile'], 'ab', 1)
2598 logfh = open(opts['logiofile'], 'ab', 1)
2599
2599
2600 s = wireprotoserver.sshserver(ui, repo, logfh=logfh)
2600 s = wireprotoserver.sshserver(ui, repo, logfh=logfh)
2601 s.serve_forever()
2601 s.serve_forever()
2602
2602
2603 @command('debugsetparents', [], _('REV1 [REV2]'))
2603 @command('debugsetparents', [], _('REV1 [REV2]'))
2604 def debugsetparents(ui, repo, rev1, rev2=None):
2604 def debugsetparents(ui, repo, rev1, rev2=None):
2605 """manually set the parents of the current working directory
2605 """manually set the parents of the current working directory
2606
2606
2607 This is useful for writing repository conversion tools, but should
2607 This is useful for writing repository conversion tools, but should
2608 be used with care. For example, neither the working directory nor the
2608 be used with care. For example, neither the working directory nor the
2609 dirstate is updated, so file status may be incorrect after running this
2609 dirstate is updated, so file status may be incorrect after running this
2610 command.
2610 command.
2611
2611
2612 Returns 0 on success.
2612 Returns 0 on success.
2613 """
2613 """
2614
2614
2615 node1 = scmutil.revsingle(repo, rev1).node()
2615 node1 = scmutil.revsingle(repo, rev1).node()
2616 node2 = scmutil.revsingle(repo, rev2, 'null').node()
2616 node2 = scmutil.revsingle(repo, rev2, 'null').node()
2617
2617
2618 with repo.wlock():
2618 with repo.wlock():
2619 repo.setparents(node1, node2)
2619 repo.setparents(node1, node2)
2620
2620
2621 @command('debugssl', [], '[SOURCE]', optionalrepo=True)
2621 @command('debugssl', [], '[SOURCE]', optionalrepo=True)
2622 def debugssl(ui, repo, source=None, **opts):
2622 def debugssl(ui, repo, source=None, **opts):
2623 '''test a secure connection to a server
2623 '''test a secure connection to a server
2624
2624
2625 This builds the certificate chain for the server on Windows, installing the
2625 This builds the certificate chain for the server on Windows, installing the
2626 missing intermediates and trusted root via Windows Update if necessary. It
2626 missing intermediates and trusted root via Windows Update if necessary. It
2627 does nothing on other platforms.
2627 does nothing on other platforms.
2628
2628
2629 If SOURCE is omitted, the 'default' path will be used. If a URL is given,
2629 If SOURCE is omitted, the 'default' path will be used. If a URL is given,
2630 that server is used. See :hg:`help urls` for more information.
2630 that server is used. See :hg:`help urls` for more information.
2631
2631
2632 If the update succeeds, retry the original operation. Otherwise, the cause
2632 If the update succeeds, retry the original operation. Otherwise, the cause
2633 of the SSL error is likely another issue.
2633 of the SSL error is likely another issue.
2634 '''
2634 '''
2635 if not pycompat.iswindows:
2635 if not pycompat.iswindows:
2636 raise error.Abort(_('certificate chain building is only possible on '
2636 raise error.Abort(_('certificate chain building is only possible on '
2637 'Windows'))
2637 'Windows'))
2638
2638
2639 if not source:
2639 if not source:
2640 if not repo:
2640 if not repo:
2641 raise error.Abort(_("there is no Mercurial repository here, and no "
2641 raise error.Abort(_("there is no Mercurial repository here, and no "
2642 "server specified"))
2642 "server specified"))
2643 source = "default"
2643 source = "default"
2644
2644
2645 source, branches = hg.parseurl(ui.expandpath(source))
2645 source, branches = hg.parseurl(ui.expandpath(source))
2646 url = util.url(source)
2646 url = util.url(source)
2647
2647
2648 defaultport = {'https': 443, 'ssh': 22}
2648 defaultport = {'https': 443, 'ssh': 22}
2649 if url.scheme in defaultport:
2649 if url.scheme in defaultport:
2650 try:
2650 try:
2651 addr = (url.host, int(url.port or defaultport[url.scheme]))
2651 addr = (url.host, int(url.port or defaultport[url.scheme]))
2652 except ValueError:
2652 except ValueError:
2653 raise error.Abort(_("malformed port number in URL"))
2653 raise error.Abort(_("malformed port number in URL"))
2654 else:
2654 else:
2655 raise error.Abort(_("only https and ssh connections are supported"))
2655 raise error.Abort(_("only https and ssh connections are supported"))
2656
2656
2657 from . import win32
2657 from . import win32
2658
2658
2659 s = ssl.wrap_socket(socket.socket(), ssl_version=ssl.PROTOCOL_TLS,
2659 s = ssl.wrap_socket(socket.socket(), ssl_version=ssl.PROTOCOL_TLS,
2660 cert_reqs=ssl.CERT_NONE, ca_certs=None)
2660 cert_reqs=ssl.CERT_NONE, ca_certs=None)
2661
2661
2662 try:
2662 try:
2663 s.connect(addr)
2663 s.connect(addr)
2664 cert = s.getpeercert(True)
2664 cert = s.getpeercert(True)
2665
2665
2666 ui.status(_('checking the certificate chain for %s\n') % url.host)
2666 ui.status(_('checking the certificate chain for %s\n') % url.host)
2667
2667
2668 complete = win32.checkcertificatechain(cert, build=False)
2668 complete = win32.checkcertificatechain(cert, build=False)
2669
2669
2670 if not complete:
2670 if not complete:
2671 ui.status(_('certificate chain is incomplete, updating... '))
2671 ui.status(_('certificate chain is incomplete, updating... '))
2672
2672
2673 if not win32.checkcertificatechain(cert):
2673 if not win32.checkcertificatechain(cert):
2674 ui.status(_('failed.\n'))
2674 ui.status(_('failed.\n'))
2675 else:
2675 else:
2676 ui.status(_('done.\n'))
2676 ui.status(_('done.\n'))
2677 else:
2677 else:
2678 ui.status(_('full certificate chain is available\n'))
2678 ui.status(_('full certificate chain is available\n'))
2679 finally:
2679 finally:
2680 s.close()
2680 s.close()
2681
2681
2682 @command('debugsub',
2682 @command('debugsub',
2683 [('r', 'rev', '',
2683 [('r', 'rev', '',
2684 _('revision to check'), _('REV'))],
2684 _('revision to check'), _('REV'))],
2685 _('[-r REV] [REV]'))
2685 _('[-r REV] [REV]'))
2686 def debugsub(ui, repo, rev=None):
2686 def debugsub(ui, repo, rev=None):
2687 ctx = scmutil.revsingle(repo, rev, None)
2687 ctx = scmutil.revsingle(repo, rev, None)
2688 for k, v in sorted(ctx.substate.items()):
2688 for k, v in sorted(ctx.substate.items()):
2689 ui.write(('path %s\n') % k)
2689 ui.write(('path %s\n') % k)
2690 ui.write((' source %s\n') % v[0])
2690 ui.write((' source %s\n') % v[0])
2691 ui.write((' revision %s\n') % v[1])
2691 ui.write((' revision %s\n') % v[1])
2692
2692
2693 @command('debugsuccessorssets',
2693 @command('debugsuccessorssets',
2694 [('', 'closest', False, _('return closest successors sets only'))],
2694 [('', 'closest', False, _('return closest successors sets only'))],
2695 _('[REV]'))
2695 _('[REV]'))
2696 def debugsuccessorssets(ui, repo, *revs, **opts):
2696 def debugsuccessorssets(ui, repo, *revs, **opts):
2697 """show set of successors for revision
2697 """show set of successors for revision
2698
2698
2699 A successors set of changeset A is a consistent group of revisions that
2699 A successors set of changeset A is a consistent group of revisions that
2700 succeed A. It contains non-obsolete changesets only unless closests
2700 succeed A. It contains non-obsolete changesets only unless closests
2701 successors set is set.
2701 successors set is set.
2702
2702
2703 In most cases a changeset A has a single successors set containing a single
2703 In most cases a changeset A has a single successors set containing a single
2704 successor (changeset A replaced by A').
2704 successor (changeset A replaced by A').
2705
2705
2706 A changeset that is made obsolete with no successors are called "pruned".
2706 A changeset that is made obsolete with no successors are called "pruned".
2707 Such changesets have no successors sets at all.
2707 Such changesets have no successors sets at all.
2708
2708
2709 A changeset that has been "split" will have a successors set containing
2709 A changeset that has been "split" will have a successors set containing
2710 more than one successor.
2710 more than one successor.
2711
2711
2712 A changeset that has been rewritten in multiple different ways is called
2712 A changeset that has been rewritten in multiple different ways is called
2713 "divergent". Such changesets have multiple successor sets (each of which
2713 "divergent". Such changesets have multiple successor sets (each of which
2714 may also be split, i.e. have multiple successors).
2714 may also be split, i.e. have multiple successors).
2715
2715
2716 Results are displayed as follows::
2716 Results are displayed as follows::
2717
2717
2718 <rev1>
2718 <rev1>
2719 <successors-1A>
2719 <successors-1A>
2720 <rev2>
2720 <rev2>
2721 <successors-2A>
2721 <successors-2A>
2722 <successors-2B1> <successors-2B2> <successors-2B3>
2722 <successors-2B1> <successors-2B2> <successors-2B3>
2723
2723
2724 Here rev2 has two possible (i.e. divergent) successors sets. The first
2724 Here rev2 has two possible (i.e. divergent) successors sets. The first
2725 holds one element, whereas the second holds three (i.e. the changeset has
2725 holds one element, whereas the second holds three (i.e. the changeset has
2726 been split).
2726 been split).
2727 """
2727 """
2728 # passed to successorssets caching computation from one call to another
2728 # passed to successorssets caching computation from one call to another
2729 cache = {}
2729 cache = {}
2730 ctx2str = bytes
2730 ctx2str = bytes
2731 node2str = short
2731 node2str = short
2732 for rev in scmutil.revrange(repo, revs):
2732 for rev in scmutil.revrange(repo, revs):
2733 ctx = repo[rev]
2733 ctx = repo[rev]
2734 ui.write('%s\n'% ctx2str(ctx))
2734 ui.write('%s\n'% ctx2str(ctx))
2735 for succsset in obsutil.successorssets(repo, ctx.node(),
2735 for succsset in obsutil.successorssets(repo, ctx.node(),
2736 closest=opts[r'closest'],
2736 closest=opts[r'closest'],
2737 cache=cache):
2737 cache=cache):
2738 if succsset:
2738 if succsset:
2739 ui.write(' ')
2739 ui.write(' ')
2740 ui.write(node2str(succsset[0]))
2740 ui.write(node2str(succsset[0]))
2741 for node in succsset[1:]:
2741 for node in succsset[1:]:
2742 ui.write(' ')
2742 ui.write(' ')
2743 ui.write(node2str(node))
2743 ui.write(node2str(node))
2744 ui.write('\n')
2744 ui.write('\n')
2745
2745
2746 @command('debugtemplate',
2746 @command('debugtemplate',
2747 [('r', 'rev', [], _('apply template on changesets'), _('REV')),
2747 [('r', 'rev', [], _('apply template on changesets'), _('REV')),
2748 ('D', 'define', [], _('define template keyword'), _('KEY=VALUE'))],
2748 ('D', 'define', [], _('define template keyword'), _('KEY=VALUE'))],
2749 _('[-r REV]... [-D KEY=VALUE]... TEMPLATE'),
2749 _('[-r REV]... [-D KEY=VALUE]... TEMPLATE'),
2750 optionalrepo=True)
2750 optionalrepo=True)
2751 def debugtemplate(ui, repo, tmpl, **opts):
2751 def debugtemplate(ui, repo, tmpl, **opts):
2752 """parse and apply a template
2752 """parse and apply a template
2753
2753
2754 If -r/--rev is given, the template is processed as a log template and
2754 If -r/--rev is given, the template is processed as a log template and
2755 applied to the given changesets. Otherwise, it is processed as a generic
2755 applied to the given changesets. Otherwise, it is processed as a generic
2756 template.
2756 template.
2757
2757
2758 Use --verbose to print the parsed tree.
2758 Use --verbose to print the parsed tree.
2759 """
2759 """
2760 revs = None
2760 revs = None
2761 if opts[r'rev']:
2761 if opts[r'rev']:
2762 if repo is None:
2762 if repo is None:
2763 raise error.RepoError(_('there is no Mercurial repository here '
2763 raise error.RepoError(_('there is no Mercurial repository here '
2764 '(.hg not found)'))
2764 '(.hg not found)'))
2765 revs = scmutil.revrange(repo, opts[r'rev'])
2765 revs = scmutil.revrange(repo, opts[r'rev'])
2766
2766
2767 props = {}
2767 props = {}
2768 for d in opts[r'define']:
2768 for d in opts[r'define']:
2769 try:
2769 try:
2770 k, v = (e.strip() for e in d.split('=', 1))
2770 k, v = (e.strip() for e in d.split('=', 1))
2771 if not k or k == 'ui':
2771 if not k or k == 'ui':
2772 raise ValueError
2772 raise ValueError
2773 props[k] = v
2773 props[k] = v
2774 except ValueError:
2774 except ValueError:
2775 raise error.Abort(_('malformed keyword definition: %s') % d)
2775 raise error.Abort(_('malformed keyword definition: %s') % d)
2776
2776
2777 if ui.verbose:
2777 if ui.verbose:
2778 aliases = ui.configitems('templatealias')
2778 aliases = ui.configitems('templatealias')
2779 tree = templater.parse(tmpl)
2779 tree = templater.parse(tmpl)
2780 ui.note(templater.prettyformat(tree), '\n')
2780 ui.note(templater.prettyformat(tree), '\n')
2781 newtree = templater.expandaliases(tree, aliases)
2781 newtree = templater.expandaliases(tree, aliases)
2782 if newtree != tree:
2782 if newtree != tree:
2783 ui.note(("* expanded:\n"), templater.prettyformat(newtree), '\n')
2783 ui.note(("* expanded:\n"), templater.prettyformat(newtree), '\n')
2784
2784
2785 if revs is None:
2785 if revs is None:
2786 tres = formatter.templateresources(ui, repo)
2786 tres = formatter.templateresources(ui, repo)
2787 t = formatter.maketemplater(ui, tmpl, resources=tres)
2787 t = formatter.maketemplater(ui, tmpl, resources=tres)
2788 if ui.verbose:
2788 if ui.verbose:
2789 kwds, funcs = t.symbolsuseddefault()
2789 kwds, funcs = t.symbolsuseddefault()
2790 ui.write(("* keywords: %s\n") % ', '.join(sorted(kwds)))
2790 ui.write(("* keywords: %s\n") % ', '.join(sorted(kwds)))
2791 ui.write(("* functions: %s\n") % ', '.join(sorted(funcs)))
2791 ui.write(("* functions: %s\n") % ', '.join(sorted(funcs)))
2792 ui.write(t.renderdefault(props))
2792 ui.write(t.renderdefault(props))
2793 else:
2793 else:
2794 displayer = logcmdutil.maketemplater(ui, repo, tmpl)
2794 displayer = logcmdutil.maketemplater(ui, repo, tmpl)
2795 if ui.verbose:
2795 if ui.verbose:
2796 kwds, funcs = displayer.t.symbolsuseddefault()
2796 kwds, funcs = displayer.t.symbolsuseddefault()
2797 ui.write(("* keywords: %s\n") % ', '.join(sorted(kwds)))
2797 ui.write(("* keywords: %s\n") % ', '.join(sorted(kwds)))
2798 ui.write(("* functions: %s\n") % ', '.join(sorted(funcs)))
2798 ui.write(("* functions: %s\n") % ', '.join(sorted(funcs)))
2799 for r in revs:
2799 for r in revs:
2800 displayer.show(repo[r], **pycompat.strkwargs(props))
2800 displayer.show(repo[r], **pycompat.strkwargs(props))
2801 displayer.close()
2801 displayer.close()
2802
2802
2803 @command('debuguigetpass', [
2803 @command('debuguigetpass', [
2804 ('p', 'prompt', '', _('prompt text'), _('TEXT')),
2804 ('p', 'prompt', '', _('prompt text'), _('TEXT')),
2805 ], _('[-p TEXT]'), norepo=True)
2805 ], _('[-p TEXT]'), norepo=True)
2806 def debuguigetpass(ui, prompt=''):
2806 def debuguigetpass(ui, prompt=''):
2807 """show prompt to type password"""
2807 """show prompt to type password"""
2808 r = ui.getpass(prompt)
2808 r = ui.getpass(prompt)
2809 ui.write(('respose: %s\n') % r)
2809 ui.write(('respose: %s\n') % r)
2810
2810
2811 @command('debuguiprompt', [
2811 @command('debuguiprompt', [
2812 ('p', 'prompt', '', _('prompt text'), _('TEXT')),
2812 ('p', 'prompt', '', _('prompt text'), _('TEXT')),
2813 ], _('[-p TEXT]'), norepo=True)
2813 ], _('[-p TEXT]'), norepo=True)
2814 def debuguiprompt(ui, prompt=''):
2814 def debuguiprompt(ui, prompt=''):
2815 """show plain prompt"""
2815 """show plain prompt"""
2816 r = ui.prompt(prompt)
2816 r = ui.prompt(prompt)
2817 ui.write(('response: %s\n') % r)
2817 ui.write(('response: %s\n') % r)
2818
2818
2819 @command('debugupdatecaches', [])
2819 @command('debugupdatecaches', [])
2820 def debugupdatecaches(ui, repo, *pats, **opts):
2820 def debugupdatecaches(ui, repo, *pats, **opts):
2821 """warm all known caches in the repository"""
2821 """warm all known caches in the repository"""
2822 with repo.wlock(), repo.lock():
2822 with repo.wlock(), repo.lock():
2823 repo.updatecaches(full=True)
2823 repo.updatecaches(full=True)
2824
2824
2825 @command('debugupgraderepo', [
2825 @command('debugupgraderepo', [
2826 ('o', 'optimize', [], _('extra optimization to perform'), _('NAME')),
2826 ('o', 'optimize', [], _('extra optimization to perform'), _('NAME')),
2827 ('', 'run', False, _('performs an upgrade')),
2827 ('', 'run', False, _('performs an upgrade')),
2828 ('', 'backup', True, _('keep the old repository content around')),
2828 ('', 'backup', True, _('keep the old repository content around')),
2829 ])
2829 ])
2830 def debugupgraderepo(ui, repo, run=False, optimize=None, backup=True):
2830 def debugupgraderepo(ui, repo, run=False, optimize=None, backup=True):
2831 """upgrade a repository to use different features
2831 """upgrade a repository to use different features
2832
2832
2833 If no arguments are specified, the repository is evaluated for upgrade
2833 If no arguments are specified, the repository is evaluated for upgrade
2834 and a list of problems and potential optimizations is printed.
2834 and a list of problems and potential optimizations is printed.
2835
2835
2836 With ``--run``, a repository upgrade is performed. Behavior of the upgrade
2836 With ``--run``, a repository upgrade is performed. Behavior of the upgrade
2837 can be influenced via additional arguments. More details will be provided
2837 can be influenced via additional arguments. More details will be provided
2838 by the command output when run without ``--run``.
2838 by the command output when run without ``--run``.
2839
2839
2840 During the upgrade, the repository will be locked and no writes will be
2840 During the upgrade, the repository will be locked and no writes will be
2841 allowed.
2841 allowed.
2842
2842
2843 At the end of the upgrade, the repository may not be readable while new
2843 At the end of the upgrade, the repository may not be readable while new
2844 repository data is swapped in. This window will be as long as it takes to
2844 repository data is swapped in. This window will be as long as it takes to
2845 rename some directories inside the ``.hg`` directory. On most machines, this
2845 rename some directories inside the ``.hg`` directory. On most machines, this
2846 should complete almost instantaneously and the chances of a consumer being
2846 should complete almost instantaneously and the chances of a consumer being
2847 unable to access the repository should be low.
2847 unable to access the repository should be low.
2848 """
2848 """
2849 return upgrade.upgraderepo(ui, repo, run=run, optimize=optimize,
2849 return upgrade.upgraderepo(ui, repo, run=run, optimize=optimize,
2850 backup=backup)
2850 backup=backup)
2851
2851
2852 @command('debugwalk', cmdutil.walkopts, _('[OPTION]... [FILE]...'),
2852 @command('debugwalk', cmdutil.walkopts, _('[OPTION]... [FILE]...'),
2853 inferrepo=True)
2853 inferrepo=True)
2854 def debugwalk(ui, repo, *pats, **opts):
2854 def debugwalk(ui, repo, *pats, **opts):
2855 """show how files match on given patterns"""
2855 """show how files match on given patterns"""
2856 opts = pycompat.byteskwargs(opts)
2856 opts = pycompat.byteskwargs(opts)
2857 m = scmutil.match(repo[None], pats, opts)
2857 m = scmutil.match(repo[None], pats, opts)
2858 if ui.verbose:
2858 if ui.verbose:
2859 ui.write(('* matcher:\n'), stringutil.prettyrepr(m), '\n')
2859 ui.write(('* matcher:\n'), stringutil.prettyrepr(m), '\n')
2860 items = list(repo[None].walk(m))
2860 items = list(repo[None].walk(m))
2861 if not items:
2861 if not items:
2862 return
2862 return
2863 f = lambda fn: fn
2863 f = lambda fn: fn
2864 if ui.configbool('ui', 'slash') and pycompat.ossep != '/':
2864 if ui.configbool('ui', 'slash') and pycompat.ossep != '/':
2865 f = lambda fn: util.normpath(fn)
2865 f = lambda fn: util.normpath(fn)
2866 fmt = 'f %%-%ds %%-%ds %%s' % (
2866 fmt = 'f %%-%ds %%-%ds %%s' % (
2867 max([len(abs) for abs in items]),
2867 max([len(abs) for abs in items]),
2868 max([len(repo.pathto(abs)) for abs in items]))
2868 max([len(repo.pathto(abs)) for abs in items]))
2869 for abs in items:
2869 for abs in items:
2870 line = fmt % (abs, f(repo.pathto(abs)), m.exact(abs) and 'exact' or '')
2870 line = fmt % (abs, f(repo.pathto(abs)), m.exact(abs) and 'exact' or '')
2871 ui.write("%s\n" % line.rstrip())
2871 ui.write("%s\n" % line.rstrip())
2872
2872
2873 @command('debugwhyunstable', [], _('REV'))
2873 @command('debugwhyunstable', [], _('REV'))
2874 def debugwhyunstable(ui, repo, rev):
2874 def debugwhyunstable(ui, repo, rev):
2875 """explain instabilities of a changeset"""
2875 """explain instabilities of a changeset"""
2876 for entry in obsutil.whyunstable(repo, scmutil.revsingle(repo, rev)):
2876 for entry in obsutil.whyunstable(repo, scmutil.revsingle(repo, rev)):
2877 dnodes = ''
2877 dnodes = ''
2878 if entry.get('divergentnodes'):
2878 if entry.get('divergentnodes'):
2879 dnodes = ' '.join('%s (%s)' % (ctx.hex(), ctx.phasestr())
2879 dnodes = ' '.join('%s (%s)' % (ctx.hex(), ctx.phasestr())
2880 for ctx in entry['divergentnodes']) + ' '
2880 for ctx in entry['divergentnodes']) + ' '
2881 ui.write('%s: %s%s %s\n' % (entry['instability'], dnodes,
2881 ui.write('%s: %s%s %s\n' % (entry['instability'], dnodes,
2882 entry['reason'], entry['node']))
2882 entry['reason'], entry['node']))
2883
2883
2884 @command('debugwireargs',
2884 @command('debugwireargs',
2885 [('', 'three', '', 'three'),
2885 [('', 'three', '', 'three'),
2886 ('', 'four', '', 'four'),
2886 ('', 'four', '', 'four'),
2887 ('', 'five', '', 'five'),
2887 ('', 'five', '', 'five'),
2888 ] + cmdutil.remoteopts,
2888 ] + cmdutil.remoteopts,
2889 _('REPO [OPTIONS]... [ONE [TWO]]'),
2889 _('REPO [OPTIONS]... [ONE [TWO]]'),
2890 norepo=True)
2890 norepo=True)
2891 def debugwireargs(ui, repopath, *vals, **opts):
2891 def debugwireargs(ui, repopath, *vals, **opts):
2892 opts = pycompat.byteskwargs(opts)
2892 opts = pycompat.byteskwargs(opts)
2893 repo = hg.peer(ui, opts, repopath)
2893 repo = hg.peer(ui, opts, repopath)
2894 for opt in cmdutil.remoteopts:
2894 for opt in cmdutil.remoteopts:
2895 del opts[opt[1]]
2895 del opts[opt[1]]
2896 args = {}
2896 args = {}
2897 for k, v in opts.iteritems():
2897 for k, v in opts.iteritems():
2898 if v:
2898 if v:
2899 args[k] = v
2899 args[k] = v
2900 args = pycompat.strkwargs(args)
2900 args = pycompat.strkwargs(args)
2901 # run twice to check that we don't mess up the stream for the next command
2901 # run twice to check that we don't mess up the stream for the next command
2902 res1 = repo.debugwireargs(*vals, **args)
2902 res1 = repo.debugwireargs(*vals, **args)
2903 res2 = repo.debugwireargs(*vals, **args)
2903 res2 = repo.debugwireargs(*vals, **args)
2904 ui.write("%s\n" % res1)
2904 ui.write("%s\n" % res1)
2905 if res1 != res2:
2905 if res1 != res2:
2906 ui.warn("%s\n" % res2)
2906 ui.warn("%s\n" % res2)
2907
2907
2908 def _parsewirelangblocks(fh):
2908 def _parsewirelangblocks(fh):
2909 activeaction = None
2909 activeaction = None
2910 blocklines = []
2910 blocklines = []
2911 lastindent = 0
2911 lastindent = 0
2912
2912
2913 for line in fh:
2913 for line in fh:
2914 line = line.rstrip()
2914 line = line.rstrip()
2915 if not line:
2915 if not line:
2916 continue
2916 continue
2917
2917
2918 if line.startswith(b'#'):
2918 if line.startswith(b'#'):
2919 continue
2919 continue
2920
2920
2921 if not line.startswith(b' '):
2921 if not line.startswith(b' '):
2922 # New block. Flush previous one.
2922 # New block. Flush previous one.
2923 if activeaction:
2923 if activeaction:
2924 yield activeaction, blocklines
2924 yield activeaction, blocklines
2925
2925
2926 activeaction = line
2926 activeaction = line
2927 blocklines = []
2927 blocklines = []
2928 lastindent = 0
2928 lastindent = 0
2929 continue
2929 continue
2930
2930
2931 # Else we start with an indent.
2931 # Else we start with an indent.
2932
2932
2933 if not activeaction:
2933 if not activeaction:
2934 raise error.Abort(_('indented line outside of block'))
2934 raise error.Abort(_('indented line outside of block'))
2935
2935
2936 indent = len(line) - len(line.lstrip())
2936 indent = len(line) - len(line.lstrip())
2937
2937
2938 # If this line is indented more than the last line, concatenate it.
2938 # If this line is indented more than the last line, concatenate it.
2939 if indent > lastindent and blocklines:
2939 if indent > lastindent and blocklines:
2940 blocklines[-1] += line.lstrip()
2940 blocklines[-1] += line.lstrip()
2941 else:
2941 else:
2942 blocklines.append(line)
2942 blocklines.append(line)
2943 lastindent = indent
2943 lastindent = indent
2944
2944
2945 # Flush last block.
2945 # Flush last block.
2946 if activeaction:
2946 if activeaction:
2947 yield activeaction, blocklines
2947 yield activeaction, blocklines
2948
2948
2949 @command('debugwireproto',
2949 @command('debugwireproto',
2950 [
2950 [
2951 ('', 'localssh', False, _('start an SSH server for this repo')),
2951 ('', 'localssh', False, _('start an SSH server for this repo')),
2952 ('', 'peer', '', _('construct a specific version of the peer')),
2952 ('', 'peer', '', _('construct a specific version of the peer')),
2953 ('', 'noreadstderr', False, _('do not read from stderr of the remote')),
2953 ('', 'noreadstderr', False, _('do not read from stderr of the remote')),
2954 ('', 'nologhandshake', False,
2954 ('', 'nologhandshake', False,
2955 _('do not log I/O related to the peer handshake')),
2955 _('do not log I/O related to the peer handshake')),
2956 ] + cmdutil.remoteopts,
2956 ] + cmdutil.remoteopts,
2957 _('[PATH]'),
2957 _('[PATH]'),
2958 optionalrepo=True)
2958 optionalrepo=True)
2959 def debugwireproto(ui, repo, path=None, **opts):
2959 def debugwireproto(ui, repo, path=None, **opts):
2960 """send wire protocol commands to a server
2960 """send wire protocol commands to a server
2961
2961
2962 This command can be used to issue wire protocol commands to remote
2962 This command can be used to issue wire protocol commands to remote
2963 peers and to debug the raw data being exchanged.
2963 peers and to debug the raw data being exchanged.
2964
2964
2965 ``--localssh`` will start an SSH server against the current repository
2965 ``--localssh`` will start an SSH server against the current repository
2966 and connect to that. By default, the connection will perform a handshake
2966 and connect to that. By default, the connection will perform a handshake
2967 and establish an appropriate peer instance.
2967 and establish an appropriate peer instance.
2968
2968
2969 ``--peer`` can be used to bypass the handshake protocol and construct a
2969 ``--peer`` can be used to bypass the handshake protocol and construct a
2970 peer instance using the specified class type. Valid values are ``raw``,
2970 peer instance using the specified class type. Valid values are ``raw``,
2971 ``http2``, ``ssh1``, and ``ssh2``. ``raw`` instances only allow sending
2971 ``http2``, ``ssh1``, and ``ssh2``. ``raw`` instances only allow sending
2972 raw data payloads and don't support higher-level command actions.
2972 raw data payloads and don't support higher-level command actions.
2973
2973
2974 ``--noreadstderr`` can be used to disable automatic reading from stderr
2974 ``--noreadstderr`` can be used to disable automatic reading from stderr
2975 of the peer (for SSH connections only). Disabling automatic reading of
2975 of the peer (for SSH connections only). Disabling automatic reading of
2976 stderr is useful for making output more deterministic.
2976 stderr is useful for making output more deterministic.
2977
2977
2978 Commands are issued via a mini language which is specified via stdin.
2978 Commands are issued via a mini language which is specified via stdin.
2979 The language consists of individual actions to perform. An action is
2979 The language consists of individual actions to perform. An action is
2980 defined by a block. A block is defined as a line with no leading
2980 defined by a block. A block is defined as a line with no leading
2981 space followed by 0 or more lines with leading space. Blocks are
2981 space followed by 0 or more lines with leading space. Blocks are
2982 effectively a high-level command with additional metadata.
2982 effectively a high-level command with additional metadata.
2983
2983
2984 Lines beginning with ``#`` are ignored.
2984 Lines beginning with ``#`` are ignored.
2985
2985
2986 The following sections denote available actions.
2986 The following sections denote available actions.
2987
2987
2988 raw
2988 raw
2989 ---
2989 ---
2990
2990
2991 Send raw data to the server.
2991 Send raw data to the server.
2992
2992
2993 The block payload contains the raw data to send as one atomic send
2993 The block payload contains the raw data to send as one atomic send
2994 operation. The data may not actually be delivered in a single system
2994 operation. The data may not actually be delivered in a single system
2995 call: it depends on the abilities of the transport being used.
2995 call: it depends on the abilities of the transport being used.
2996
2996
2997 Each line in the block is de-indented and concatenated. Then, that
2997 Each line in the block is de-indented and concatenated. Then, that
2998 value is evaluated as a Python b'' literal. This allows the use of
2998 value is evaluated as a Python b'' literal. This allows the use of
2999 backslash escaping, etc.
2999 backslash escaping, etc.
3000
3000
3001 raw+
3001 raw+
3002 ----
3002 ----
3003
3003
3004 Behaves like ``raw`` except flushes output afterwards.
3004 Behaves like ``raw`` except flushes output afterwards.
3005
3005
3006 command <X>
3006 command <X>
3007 -----------
3007 -----------
3008
3008
3009 Send a request to run a named command, whose name follows the ``command``
3009 Send a request to run a named command, whose name follows the ``command``
3010 string.
3010 string.
3011
3011
3012 Arguments to the command are defined as lines in this block. The format of
3012 Arguments to the command are defined as lines in this block. The format of
3013 each line is ``<key> <value>``. e.g.::
3013 each line is ``<key> <value>``. e.g.::
3014
3014
3015 command listkeys
3015 command listkeys
3016 namespace bookmarks
3016 namespace bookmarks
3017
3017
3018 If the value begins with ``eval:``, it will be interpreted as a Python
3018 If the value begins with ``eval:``, it will be interpreted as a Python
3019 literal expression. Otherwise values are interpreted as Python b'' literals.
3019 literal expression. Otherwise values are interpreted as Python b'' literals.
3020 This allows sending complex types and encoding special byte sequences via
3020 This allows sending complex types and encoding special byte sequences via
3021 backslash escaping.
3021 backslash escaping.
3022
3022
3023 The following arguments have special meaning:
3023 The following arguments have special meaning:
3024
3024
3025 ``PUSHFILE``
3025 ``PUSHFILE``
3026 When defined, the *push* mechanism of the peer will be used instead
3026 When defined, the *push* mechanism of the peer will be used instead
3027 of the static request-response mechanism and the content of the
3027 of the static request-response mechanism and the content of the
3028 file specified in the value of this argument will be sent as the
3028 file specified in the value of this argument will be sent as the
3029 command payload.
3029 command payload.
3030
3030
3031 This can be used to submit a local bundle file to the remote.
3031 This can be used to submit a local bundle file to the remote.
3032
3032
3033 batchbegin
3033 batchbegin
3034 ----------
3034 ----------
3035
3035
3036 Instruct the peer to begin a batched send.
3036 Instruct the peer to begin a batched send.
3037
3037
3038 All ``command`` blocks are queued for execution until the next
3038 All ``command`` blocks are queued for execution until the next
3039 ``batchsubmit`` block.
3039 ``batchsubmit`` block.
3040
3040
3041 batchsubmit
3041 batchsubmit
3042 -----------
3042 -----------
3043
3043
3044 Submit previously queued ``command`` blocks as a batch request.
3044 Submit previously queued ``command`` blocks as a batch request.
3045
3045
3046 This action MUST be paired with a ``batchbegin`` action.
3046 This action MUST be paired with a ``batchbegin`` action.
3047
3047
3048 httprequest <method> <path>
3048 httprequest <method> <path>
3049 ---------------------------
3049 ---------------------------
3050
3050
3051 (HTTP peer only)
3051 (HTTP peer only)
3052
3052
3053 Send an HTTP request to the peer.
3053 Send an HTTP request to the peer.
3054
3054
3055 The HTTP request line follows the ``httprequest`` action. e.g. ``GET /foo``.
3055 The HTTP request line follows the ``httprequest`` action. e.g. ``GET /foo``.
3056
3056
3057 Arguments of the form ``<key>: <value>`` are interpreted as HTTP request
3057 Arguments of the form ``<key>: <value>`` are interpreted as HTTP request
3058 headers to add to the request. e.g. ``Accept: foo``.
3058 headers to add to the request. e.g. ``Accept: foo``.
3059
3059
3060 The following arguments are special:
3060 The following arguments are special:
3061
3061
3062 ``BODYFILE``
3062 ``BODYFILE``
3063 The content of the file defined as the value to this argument will be
3063 The content of the file defined as the value to this argument will be
3064 transferred verbatim as the HTTP request body.
3064 transferred verbatim as the HTTP request body.
3065
3065
3066 ``frame <type> <flags> <payload>``
3066 ``frame <type> <flags> <payload>``
3067 Send a unified protocol frame as part of the request body.
3067 Send a unified protocol frame as part of the request body.
3068
3068
3069 All frames will be collected and sent as the body to the HTTP
3069 All frames will be collected and sent as the body to the HTTP
3070 request.
3070 request.
3071
3071
3072 close
3072 close
3073 -----
3073 -----
3074
3074
3075 Close the connection to the server.
3075 Close the connection to the server.
3076
3076
3077 flush
3077 flush
3078 -----
3078 -----
3079
3079
3080 Flush data written to the server.
3080 Flush data written to the server.
3081
3081
3082 readavailable
3082 readavailable
3083 -------------
3083 -------------
3084
3084
3085 Close the write end of the connection and read all available data from
3085 Close the write end of the connection and read all available data from
3086 the server.
3086 the server.
3087
3087
3088 If the connection to the server encompasses multiple pipes, we poll both
3088 If the connection to the server encompasses multiple pipes, we poll both
3089 pipes and read available data.
3089 pipes and read available data.
3090
3090
3091 readline
3091 readline
3092 --------
3092 --------
3093
3093
3094 Read a line of output from the server. If there are multiple output
3094 Read a line of output from the server. If there are multiple output
3095 pipes, reads only the main pipe.
3095 pipes, reads only the main pipe.
3096
3096
3097 ereadline
3097 ereadline
3098 ---------
3098 ---------
3099
3099
3100 Like ``readline``, but read from the stderr pipe, if available.
3100 Like ``readline``, but read from the stderr pipe, if available.
3101
3101
3102 read <X>
3102 read <X>
3103 --------
3103 --------
3104
3104
3105 ``read()`` N bytes from the server's main output pipe.
3105 ``read()`` N bytes from the server's main output pipe.
3106
3106
3107 eread <X>
3107 eread <X>
3108 ---------
3108 ---------
3109
3109
3110 ``read()`` N bytes from the server's stderr pipe, if available.
3110 ``read()`` N bytes from the server's stderr pipe, if available.
3111
3111
3112 Specifying Unified Frame-Based Protocol Frames
3112 Specifying Unified Frame-Based Protocol Frames
3113 ----------------------------------------------
3113 ----------------------------------------------
3114
3114
3115 It is possible to emit a *Unified Frame-Based Protocol* by using special
3115 It is possible to emit a *Unified Frame-Based Protocol* by using special
3116 syntax.
3116 syntax.
3117
3117
3118 A frame is composed as a type, flags, and payload. These can be parsed
3118 A frame is composed as a type, flags, and payload. These can be parsed
3119 from a string of the form:
3119 from a string of the form:
3120
3120
3121 <request-id> <stream-id> <stream-flags> <type> <flags> <payload>
3121 <request-id> <stream-id> <stream-flags> <type> <flags> <payload>
3122
3122
3123 ``request-id`` and ``stream-id`` are integers defining the request and
3123 ``request-id`` and ``stream-id`` are integers defining the request and
3124 stream identifiers.
3124 stream identifiers.
3125
3125
3126 ``type`` can be an integer value for the frame type or the string name
3126 ``type`` can be an integer value for the frame type or the string name
3127 of the type. The strings are defined in ``wireprotoframing.py``. e.g.
3127 of the type. The strings are defined in ``wireprotoframing.py``. e.g.
3128 ``command-name``.
3128 ``command-name``.
3129
3129
3130 ``stream-flags`` and ``flags`` are a ``|`` delimited list of flag
3130 ``stream-flags`` and ``flags`` are a ``|`` delimited list of flag
3131 components. Each component (and there can be just one) can be an integer
3131 components. Each component (and there can be just one) can be an integer
3132 or a flag name for stream flags or frame flags, respectively. Values are
3132 or a flag name for stream flags or frame flags, respectively. Values are
3133 resolved to integers and then bitwise OR'd together.
3133 resolved to integers and then bitwise OR'd together.
3134
3134
3135 ``payload`` represents the raw frame payload. If it begins with
3135 ``payload`` represents the raw frame payload. If it begins with
3136 ``cbor:``, the following string is evaluated as Python code and the
3136 ``cbor:``, the following string is evaluated as Python code and the
3137 resulting object is fed into a CBOR encoder. Otherwise it is interpreted
3137 resulting object is fed into a CBOR encoder. Otherwise it is interpreted
3138 as a Python byte string literal.
3138 as a Python byte string literal.
3139 """
3139 """
3140 opts = pycompat.byteskwargs(opts)
3140 opts = pycompat.byteskwargs(opts)
3141
3141
3142 if opts['localssh'] and not repo:
3142 if opts['localssh'] and not repo:
3143 raise error.Abort(_('--localssh requires a repository'))
3143 raise error.Abort(_('--localssh requires a repository'))
3144
3144
3145 if opts['peer'] and opts['peer'] not in ('raw', 'http2', 'ssh1', 'ssh2'):
3145 if opts['peer'] and opts['peer'] not in ('raw', 'http2', 'ssh1', 'ssh2'):
3146 raise error.Abort(_('invalid value for --peer'),
3146 raise error.Abort(_('invalid value for --peer'),
3147 hint=_('valid values are "raw", "ssh1", and "ssh2"'))
3147 hint=_('valid values are "raw", "ssh1", and "ssh2"'))
3148
3148
3149 if path and opts['localssh']:
3149 if path and opts['localssh']:
3150 raise error.Abort(_('cannot specify --localssh with an explicit '
3150 raise error.Abort(_('cannot specify --localssh with an explicit '
3151 'path'))
3151 'path'))
3152
3152
3153 if ui.interactive():
3153 if ui.interactive():
3154 ui.write(_('(waiting for commands on stdin)\n'))
3154 ui.write(_('(waiting for commands on stdin)\n'))
3155
3155
3156 blocks = list(_parsewirelangblocks(ui.fin))
3156 blocks = list(_parsewirelangblocks(ui.fin))
3157
3157
3158 proc = None
3158 proc = None
3159 stdin = None
3159 stdin = None
3160 stdout = None
3160 stdout = None
3161 stderr = None
3161 stderr = None
3162 opener = None
3162 opener = None
3163
3163
3164 if opts['localssh']:
3164 if opts['localssh']:
3165 # We start the SSH server in its own process so there is process
3165 # We start the SSH server in its own process so there is process
3166 # separation. This prevents a whole class of potential bugs around
3166 # separation. This prevents a whole class of potential bugs around
3167 # shared state from interfering with server operation.
3167 # shared state from interfering with server operation.
3168 args = procutil.hgcmd() + [
3168 args = procutil.hgcmd() + [
3169 '-R', repo.root,
3169 '-R', repo.root,
3170 'debugserve', '--sshstdio',
3170 'debugserve', '--sshstdio',
3171 ]
3171 ]
3172 proc = subprocess.Popen(pycompat.rapply(procutil.tonativestr, args),
3172 proc = subprocess.Popen(pycompat.rapply(procutil.tonativestr, args),
3173 stdin=subprocess.PIPE,
3173 stdin=subprocess.PIPE,
3174 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
3174 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
3175 bufsize=0)
3175 bufsize=0)
3176
3176
3177 stdin = proc.stdin
3177 stdin = proc.stdin
3178 stdout = proc.stdout
3178 stdout = proc.stdout
3179 stderr = proc.stderr
3179 stderr = proc.stderr
3180
3180
3181 # We turn the pipes into observers so we can log I/O.
3181 # We turn the pipes into observers so we can log I/O.
3182 if ui.verbose or opts['peer'] == 'raw':
3182 if ui.verbose or opts['peer'] == 'raw':
3183 stdin = util.makeloggingfileobject(ui, proc.stdin, b'i',
3183 stdin = util.makeloggingfileobject(ui, proc.stdin, b'i',
3184 logdata=True)
3184 logdata=True)
3185 stdout = util.makeloggingfileobject(ui, proc.stdout, b'o',
3185 stdout = util.makeloggingfileobject(ui, proc.stdout, b'o',
3186 logdata=True)
3186 logdata=True)
3187 stderr = util.makeloggingfileobject(ui, proc.stderr, b'e',
3187 stderr = util.makeloggingfileobject(ui, proc.stderr, b'e',
3188 logdata=True)
3188 logdata=True)
3189
3189
3190 # --localssh also implies the peer connection settings.
3190 # --localssh also implies the peer connection settings.
3191
3191
3192 url = 'ssh://localserver'
3192 url = 'ssh://localserver'
3193 autoreadstderr = not opts['noreadstderr']
3193 autoreadstderr = not opts['noreadstderr']
3194
3194
3195 if opts['peer'] == 'ssh1':
3195 if opts['peer'] == 'ssh1':
3196 ui.write(_('creating ssh peer for wire protocol version 1\n'))
3196 ui.write(_('creating ssh peer for wire protocol version 1\n'))
3197 peer = sshpeer.sshv1peer(ui, url, proc, stdin, stdout, stderr,
3197 peer = sshpeer.sshv1peer(ui, url, proc, stdin, stdout, stderr,
3198 None, autoreadstderr=autoreadstderr)
3198 None, autoreadstderr=autoreadstderr)
3199 elif opts['peer'] == 'ssh2':
3199 elif opts['peer'] == 'ssh2':
3200 ui.write(_('creating ssh peer for wire protocol version 2\n'))
3200 ui.write(_('creating ssh peer for wire protocol version 2\n'))
3201 peer = sshpeer.sshv2peer(ui, url, proc, stdin, stdout, stderr,
3201 peer = sshpeer.sshv2peer(ui, url, proc, stdin, stdout, stderr,
3202 None, autoreadstderr=autoreadstderr)
3202 None, autoreadstderr=autoreadstderr)
3203 elif opts['peer'] == 'raw':
3203 elif opts['peer'] == 'raw':
3204 ui.write(_('using raw connection to peer\n'))
3204 ui.write(_('using raw connection to peer\n'))
3205 peer = None
3205 peer = None
3206 else:
3206 else:
3207 ui.write(_('creating ssh peer from handshake results\n'))
3207 ui.write(_('creating ssh peer from handshake results\n'))
3208 peer = sshpeer.makepeer(ui, url, proc, stdin, stdout, stderr,
3208 peer = sshpeer.makepeer(ui, url, proc, stdin, stdout, stderr,
3209 autoreadstderr=autoreadstderr)
3209 autoreadstderr=autoreadstderr)
3210
3210
3211 elif path:
3211 elif path:
3212 # We bypass hg.peer() so we can proxy the sockets.
3212 # We bypass hg.peer() so we can proxy the sockets.
3213 # TODO consider not doing this because we skip
3213 # TODO consider not doing this because we skip
3214 # ``hg.wirepeersetupfuncs`` and potentially other useful functionality.
3214 # ``hg.wirepeersetupfuncs`` and potentially other useful functionality.
3215 u = util.url(path)
3215 u = util.url(path)
3216 if u.scheme != 'http':
3216 if u.scheme != 'http':
3217 raise error.Abort(_('only http:// paths are currently supported'))
3217 raise error.Abort(_('only http:// paths are currently supported'))
3218
3218
3219 url, authinfo = u.authinfo()
3219 url, authinfo = u.authinfo()
3220 openerargs = {
3220 openerargs = {
3221 r'useragent': b'Mercurial debugwireproto',
3221 r'useragent': b'Mercurial debugwireproto',
3222 }
3222 }
3223
3223
3224 # Turn pipes/sockets into observers so we can log I/O.
3224 # Turn pipes/sockets into observers so we can log I/O.
3225 if ui.verbose:
3225 if ui.verbose:
3226 openerargs.update({
3226 openerargs.update({
3227 r'loggingfh': ui,
3227 r'loggingfh': ui,
3228 r'loggingname': b's',
3228 r'loggingname': b's',
3229 r'loggingopts': {
3229 r'loggingopts': {
3230 r'logdata': True,
3230 r'logdata': True,
3231 r'logdataapis': False,
3231 r'logdataapis': False,
3232 },
3232 },
3233 })
3233 })
3234
3234
3235 if ui.debugflag:
3235 if ui.debugflag:
3236 openerargs[r'loggingopts'][r'logdataapis'] = True
3236 openerargs[r'loggingopts'][r'logdataapis'] = True
3237
3237
3238 # Don't send default headers when in raw mode. This allows us to
3238 # Don't send default headers when in raw mode. This allows us to
3239 # bypass most of the behavior of our URL handling code so we can
3239 # bypass most of the behavior of our URL handling code so we can
3240 # have near complete control over what's sent on the wire.
3240 # have near complete control over what's sent on the wire.
3241 if opts['peer'] == 'raw':
3241 if opts['peer'] == 'raw':
3242 openerargs[r'sendaccept'] = False
3242 openerargs[r'sendaccept'] = False
3243
3243
3244 opener = urlmod.opener(ui, authinfo, **openerargs)
3244 opener = urlmod.opener(ui, authinfo, **openerargs)
3245
3245
3246 if opts['peer'] == 'http2':
3246 if opts['peer'] == 'http2':
3247 ui.write(_('creating http peer for wire protocol version 2\n'))
3247 ui.write(_('creating http peer for wire protocol version 2\n'))
3248 # We go through makepeer() because we need an API descriptor for
3248 # We go through makepeer() because we need an API descriptor for
3249 # the peer instance to be useful.
3249 # the peer instance to be useful.
3250 with ui.configoverride({
3250 with ui.configoverride({
3251 ('experimental', 'httppeer.advertise-v2'): True}):
3251 ('experimental', 'httppeer.advertise-v2'): True}):
3252 if opts['nologhandshake']:
3252 if opts['nologhandshake']:
3253 ui.pushbuffer()
3253 ui.pushbuffer()
3254
3254
3255 peer = httppeer.makepeer(ui, path, opener=opener)
3255 peer = httppeer.makepeer(ui, path, opener=opener)
3256
3256
3257 if opts['nologhandshake']:
3257 if opts['nologhandshake']:
3258 ui.popbuffer()
3258 ui.popbuffer()
3259
3259
3260 if not isinstance(peer, httppeer.httpv2peer):
3260 if not isinstance(peer, httppeer.httpv2peer):
3261 raise error.Abort(_('could not instantiate HTTP peer for '
3261 raise error.Abort(_('could not instantiate HTTP peer for '
3262 'wire protocol version 2'),
3262 'wire protocol version 2'),
3263 hint=_('the server may not have the feature '
3263 hint=_('the server may not have the feature '
3264 'enabled or is not allowing this '
3264 'enabled or is not allowing this '
3265 'client version'))
3265 'client version'))
3266
3266
3267 elif opts['peer'] == 'raw':
3267 elif opts['peer'] == 'raw':
3268 ui.write(_('using raw connection to peer\n'))
3268 ui.write(_('using raw connection to peer\n'))
3269 peer = None
3269 peer = None
3270 elif opts['peer']:
3270 elif opts['peer']:
3271 raise error.Abort(_('--peer %s not supported with HTTP peers') %
3271 raise error.Abort(_('--peer %s not supported with HTTP peers') %
3272 opts['peer'])
3272 opts['peer'])
3273 else:
3273 else:
3274 peer = httppeer.makepeer(ui, path, opener=opener)
3274 peer = httppeer.makepeer(ui, path, opener=opener)
3275
3275
3276 # We /could/ populate stdin/stdout with sock.makefile()...
3276 # We /could/ populate stdin/stdout with sock.makefile()...
3277 else:
3277 else:
3278 raise error.Abort(_('unsupported connection configuration'))
3278 raise error.Abort(_('unsupported connection configuration'))
3279
3279
3280 batchedcommands = None
3280 batchedcommands = None
3281
3281
3282 # Now perform actions based on the parsed wire language instructions.
3282 # Now perform actions based on the parsed wire language instructions.
3283 for action, lines in blocks:
3283 for action, lines in blocks:
3284 if action in ('raw', 'raw+'):
3284 if action in ('raw', 'raw+'):
3285 if not stdin:
3285 if not stdin:
3286 raise error.Abort(_('cannot call raw/raw+ on this peer'))
3286 raise error.Abort(_('cannot call raw/raw+ on this peer'))
3287
3287
3288 # Concatenate the data together.
3288 # Concatenate the data together.
3289 data = ''.join(l.lstrip() for l in lines)
3289 data = ''.join(l.lstrip() for l in lines)
3290 data = stringutil.unescapestr(data)
3290 data = stringutil.unescapestr(data)
3291 stdin.write(data)
3291 stdin.write(data)
3292
3292
3293 if action == 'raw+':
3293 if action == 'raw+':
3294 stdin.flush()
3294 stdin.flush()
3295 elif action == 'flush':
3295 elif action == 'flush':
3296 if not stdin:
3296 if not stdin:
3297 raise error.Abort(_('cannot call flush on this peer'))
3297 raise error.Abort(_('cannot call flush on this peer'))
3298 stdin.flush()
3298 stdin.flush()
3299 elif action.startswith('command'):
3299 elif action.startswith('command'):
3300 if not peer:
3300 if not peer:
3301 raise error.Abort(_('cannot send commands unless peer instance '
3301 raise error.Abort(_('cannot send commands unless peer instance '
3302 'is available'))
3302 'is available'))
3303
3303
3304 command = action.split(' ', 1)[1]
3304 command = action.split(' ', 1)[1]
3305
3305
3306 args = {}
3306 args = {}
3307 for line in lines:
3307 for line in lines:
3308 # We need to allow empty values.
3308 # We need to allow empty values.
3309 fields = line.lstrip().split(' ', 1)
3309 fields = line.lstrip().split(' ', 1)
3310 if len(fields) == 1:
3310 if len(fields) == 1:
3311 key = fields[0]
3311 key = fields[0]
3312 value = ''
3312 value = ''
3313 else:
3313 else:
3314 key, value = fields
3314 key, value = fields
3315
3315
3316 if value.startswith('eval:'):
3316 if value.startswith('eval:'):
3317 value = stringutil.evalpythonliteral(value[5:])
3317 value = stringutil.evalpythonliteral(value[5:])
3318 else:
3318 else:
3319 value = stringutil.unescapestr(value)
3319 value = stringutil.unescapestr(value)
3320
3320
3321 args[key] = value
3321 args[key] = value
3322
3322
3323 if batchedcommands is not None:
3323 if batchedcommands is not None:
3324 batchedcommands.append((command, args))
3324 batchedcommands.append((command, args))
3325 continue
3325 continue
3326
3326
3327 ui.status(_('sending %s command\n') % command)
3327 ui.status(_('sending %s command\n') % command)
3328
3328
3329 if 'PUSHFILE' in args:
3329 if 'PUSHFILE' in args:
3330 with open(args['PUSHFILE'], r'rb') as fh:
3330 with open(args['PUSHFILE'], r'rb') as fh:
3331 del args['PUSHFILE']
3331 del args['PUSHFILE']
3332 res, output = peer._callpush(command, fh,
3332 res, output = peer._callpush(command, fh,
3333 **pycompat.strkwargs(args))
3333 **pycompat.strkwargs(args))
3334 ui.status(_('result: %s\n') % stringutil.escapestr(res))
3334 ui.status(_('result: %s\n') % stringutil.escapestr(res))
3335 ui.status(_('remote output: %s\n') %
3335 ui.status(_('remote output: %s\n') %
3336 stringutil.escapestr(output))
3336 stringutil.escapestr(output))
3337 else:
3337 else:
3338 with peer.commandexecutor() as e:
3338 with peer.commandexecutor() as e:
3339 res = e.callcommand(command, args).result()
3339 res = e.callcommand(command, args).result()
3340
3340
3341 if isinstance(res, wireprotov2peer.commandresponse):
3341 if isinstance(res, wireprotov2peer.commandresponse):
3342 val = res.objects()
3342 val = res.objects()
3343 ui.status(_('response: %s\n') %
3343 ui.status(_('response: %s\n') %
3344 stringutil.pprint(val, bprefix=True, indent=2))
3344 stringutil.pprint(val, bprefix=True, indent=2))
3345 else:
3345 else:
3346 ui.status(_('response: %s\n') %
3346 ui.status(_('response: %s\n') %
3347 stringutil.pprint(res, bprefix=True, indent=2))
3347 stringutil.pprint(res, bprefix=True, indent=2))
3348
3348
3349 elif action == 'batchbegin':
3349 elif action == 'batchbegin':
3350 if batchedcommands is not None:
3350 if batchedcommands is not None:
3351 raise error.Abort(_('nested batchbegin not allowed'))
3351 raise error.Abort(_('nested batchbegin not allowed'))
3352
3352
3353 batchedcommands = []
3353 batchedcommands = []
3354 elif action == 'batchsubmit':
3354 elif action == 'batchsubmit':
3355 # There is a batching API we could go through. But it would be
3355 # There is a batching API we could go through. But it would be
3356 # difficult to normalize requests into function calls. It is easier
3356 # difficult to normalize requests into function calls. It is easier
3357 # to bypass this layer and normalize to commands + args.
3357 # to bypass this layer and normalize to commands + args.
3358 ui.status(_('sending batch with %d sub-commands\n') %
3358 ui.status(_('sending batch with %d sub-commands\n') %
3359 len(batchedcommands))
3359 len(batchedcommands))
3360 for i, chunk in enumerate(peer._submitbatch(batchedcommands)):
3360 for i, chunk in enumerate(peer._submitbatch(batchedcommands)):
3361 ui.status(_('response #%d: %s\n') %
3361 ui.status(_('response #%d: %s\n') %
3362 (i, stringutil.escapestr(chunk)))
3362 (i, stringutil.escapestr(chunk)))
3363
3363
3364 batchedcommands = None
3364 batchedcommands = None
3365
3365
3366 elif action.startswith('httprequest '):
3366 elif action.startswith('httprequest '):
3367 if not opener:
3367 if not opener:
3368 raise error.Abort(_('cannot use httprequest without an HTTP '
3368 raise error.Abort(_('cannot use httprequest without an HTTP '
3369 'peer'))
3369 'peer'))
3370
3370
3371 request = action.split(' ', 2)
3371 request = action.split(' ', 2)
3372 if len(request) != 3:
3372 if len(request) != 3:
3373 raise error.Abort(_('invalid httprequest: expected format is '
3373 raise error.Abort(_('invalid httprequest: expected format is '
3374 '"httprequest <method> <path>'))
3374 '"httprequest <method> <path>'))
3375
3375
3376 method, httppath = request[1:]
3376 method, httppath = request[1:]
3377 headers = {}
3377 headers = {}
3378 body = None
3378 body = None
3379 frames = []
3379 frames = []
3380 for line in lines:
3380 for line in lines:
3381 line = line.lstrip()
3381 line = line.lstrip()
3382 m = re.match(b'^([a-zA-Z0-9_-]+): (.*)$', line)
3382 m = re.match(b'^([a-zA-Z0-9_-]+): (.*)$', line)
3383 if m:
3383 if m:
3384 # Headers need to use native strings.
3384 # Headers need to use native strings.
3385 key = pycompat.strurl(m.group(1))
3385 key = pycompat.strurl(m.group(1))
3386 value = pycompat.strurl(m.group(2))
3386 value = pycompat.strurl(m.group(2))
3387 headers[key] = value
3387 headers[key] = value
3388 continue
3388 continue
3389
3389
3390 if line.startswith(b'BODYFILE '):
3390 if line.startswith(b'BODYFILE '):
3391 with open(line.split(b' ', 1), 'rb') as fh:
3391 with open(line.split(b' ', 1), 'rb') as fh:
3392 body = fh.read()
3392 body = fh.read()
3393 elif line.startswith(b'frame '):
3393 elif line.startswith(b'frame '):
3394 frame = wireprotoframing.makeframefromhumanstring(
3394 frame = wireprotoframing.makeframefromhumanstring(
3395 line[len(b'frame '):])
3395 line[len(b'frame '):])
3396
3396
3397 frames.append(frame)
3397 frames.append(frame)
3398 else:
3398 else:
3399 raise error.Abort(_('unknown argument to httprequest: %s') %
3399 raise error.Abort(_('unknown argument to httprequest: %s') %
3400 line)
3400 line)
3401
3401
3402 url = path + httppath
3402 url = path + httppath
3403
3403
3404 if frames:
3404 if frames:
3405 body = b''.join(bytes(f) for f in frames)
3405 body = b''.join(bytes(f) for f in frames)
3406
3406
3407 req = urlmod.urlreq.request(pycompat.strurl(url), body, headers)
3407 req = urlmod.urlreq.request(pycompat.strurl(url), body, headers)
3408
3408
3409 # urllib.Request insists on using has_data() as a proxy for
3409 # urllib.Request insists on using has_data() as a proxy for
3410 # determining the request method. Override that to use our
3410 # determining the request method. Override that to use our
3411 # explicitly requested method.
3411 # explicitly requested method.
3412 req.get_method = lambda: pycompat.sysstr(method)
3412 req.get_method = lambda: pycompat.sysstr(method)
3413
3413
3414 try:
3414 try:
3415 res = opener.open(req)
3415 res = opener.open(req)
3416 body = res.read()
3416 body = res.read()
3417 except util.urlerr.urlerror as e:
3417 except util.urlerr.urlerror as e:
3418 # read() method must be called, but only exists in Python 2
3418 # read() method must be called, but only exists in Python 2
3419 getattr(e, 'read', lambda: None)()
3419 getattr(e, 'read', lambda: None)()
3420 continue
3420 continue
3421
3421
3422 ct = res.headers.get(r'Content-Type')
3422 ct = res.headers.get(r'Content-Type')
3423 if ct == r'application/mercurial-cbor':
3423 if ct == r'application/mercurial-cbor':
3424 ui.write(_('cbor> %s\n') %
3424 ui.write(_('cbor> %s\n') %
3425 stringutil.pprint(cborutil.decodeall(body),
3425 stringutil.pprint(cborutil.decodeall(body),
3426 bprefix=True,
3426 bprefix=True,
3427 indent=2))
3427 indent=2))
3428
3428
3429 elif action == 'close':
3429 elif action == 'close':
3430 peer.close()
3430 peer.close()
3431 elif action == 'readavailable':
3431 elif action == 'readavailable':
3432 if not stdout or not stderr:
3432 if not stdout or not stderr:
3433 raise error.Abort(_('readavailable not available on this peer'))
3433 raise error.Abort(_('readavailable not available on this peer'))
3434
3434
3435 stdin.close()
3435 stdin.close()
3436 stdout.read()
3436 stdout.read()
3437 stderr.read()
3437 stderr.read()
3438
3438
3439 elif action == 'readline':
3439 elif action == 'readline':
3440 if not stdout:
3440 if not stdout:
3441 raise error.Abort(_('readline not available on this peer'))
3441 raise error.Abort(_('readline not available on this peer'))
3442 stdout.readline()
3442 stdout.readline()
3443 elif action == 'ereadline':
3443 elif action == 'ereadline':
3444 if not stderr:
3444 if not stderr:
3445 raise error.Abort(_('ereadline not available on this peer'))
3445 raise error.Abort(_('ereadline not available on this peer'))
3446 stderr.readline()
3446 stderr.readline()
3447 elif action.startswith('read '):
3447 elif action.startswith('read '):
3448 count = int(action.split(' ', 1)[1])
3448 count = int(action.split(' ', 1)[1])
3449 if not stdout:
3449 if not stdout:
3450 raise error.Abort(_('read not available on this peer'))
3450 raise error.Abort(_('read not available on this peer'))
3451 stdout.read(count)
3451 stdout.read(count)
3452 elif action.startswith('eread '):
3452 elif action.startswith('eread '):
3453 count = int(action.split(' ', 1)[1])
3453 count = int(action.split(' ', 1)[1])
3454 if not stderr:
3454 if not stderr:
3455 raise error.Abort(_('eread not available on this peer'))
3455 raise error.Abort(_('eread not available on this peer'))
3456 stderr.read(count)
3456 stderr.read(count)
3457 else:
3457 else:
3458 raise error.Abort(_('unknown action: %s') % action)
3458 raise error.Abort(_('unknown action: %s') % action)
3459
3459
3460 if batchedcommands is not None:
3460 if batchedcommands is not None:
3461 raise error.Abort(_('unclosed "batchbegin" request'))
3461 raise error.Abort(_('unclosed "batchbegin" request'))
3462
3462
3463 if peer:
3463 if peer:
3464 peer.close()
3464 peer.close()
3465
3465
3466 if proc:
3466 if proc:
3467 proc.kill()
3467 proc.kill()
@@ -1,762 +1,762 b''
1 #require symlink execbit
1 #require symlink execbit
2 $ cat << EOF >> $HGRCPATH
2 $ cat << EOF >> $HGRCPATH
3 > [phases]
3 > [phases]
4 > publish=False
4 > publish=False
5 > [extensions]
5 > [extensions]
6 > amend=
6 > amend=
7 > rebase=
7 > rebase=
8 > debugdrawdag=$TESTDIR/drawdag.py
8 > debugdrawdag=$TESTDIR/drawdag.py
9 > strip=
9 > strip=
10 > [rebase]
10 > [rebase]
11 > experimental.inmemory=1
11 > experimental.inmemory=1
12 > [diff]
12 > [diff]
13 > git=1
13 > git=1
14 > [alias]
14 > [alias]
15 > tglog = log -G --template "{rev}: {node|short} '{desc}'\n"
15 > tglog = log -G --template "{rev}: {node|short} '{desc}'\n"
16 > EOF
16 > EOF
17
17
18 Rebase a simple DAG:
18 Rebase a simple DAG:
19 $ hg init repo1
19 $ hg init repo1
20 $ cd repo1
20 $ cd repo1
21 $ hg debugdrawdag <<'EOS'
21 $ hg debugdrawdag <<'EOS'
22 > c b
22 > c b
23 > |/
23 > |/
24 > d
24 > d
25 > |
25 > |
26 > a
26 > a
27 > EOS
27 > EOS
28 $ hg up -C a
28 $ hg up -C a
29 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
29 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
30 $ hg tglog
30 $ hg tglog
31 o 3: 814f6bd05178 'c'
31 o 3: 814f6bd05178 'c'
32 |
32 |
33 | o 2: db0e82a16a62 'b'
33 | o 2: db0e82a16a62 'b'
34 |/
34 |/
35 o 1: 02952614a83d 'd'
35 o 1: 02952614a83d 'd'
36 |
36 |
37 @ 0: b173517d0057 'a'
37 @ 0: b173517d0057 'a'
38
38
39 $ hg cat -r 3 c
39 $ hg cat -r 3 c
40 c (no-eol)
40 c (no-eol)
41 $ hg cat -r 2 b
41 $ hg cat -r 2 b
42 b (no-eol)
42 b (no-eol)
43 $ hg rebase --debug -r b -d c | grep rebasing
43 $ hg rebase --debug -r b -d c | grep rebasing
44 rebasing in-memory
44 rebasing in-memory
45 rebasing 2:db0e82a16a62 "b" (b)
45 rebasing 2:db0e82a16a62 "b" (b)
46 $ hg tglog
46 $ hg tglog
47 o 3: ca58782ad1e4 'b'
47 o 3: ca58782ad1e4 'b'
48 |
48 |
49 o 2: 814f6bd05178 'c'
49 o 2: 814f6bd05178 'c'
50 |
50 |
51 o 1: 02952614a83d 'd'
51 o 1: 02952614a83d 'd'
52 |
52 |
53 @ 0: b173517d0057 'a'
53 @ 0: b173517d0057 'a'
54
54
55 $ hg cat -r 3 b
55 $ hg cat -r 3 b
56 b (no-eol)
56 b (no-eol)
57 $ hg cat -r 2 c
57 $ hg cat -r 2 c
58 c (no-eol)
58 c (no-eol)
59 $ cd ..
59 $ cd ..
60
60
61 Case 2:
61 Case 2:
62 $ hg init repo2
62 $ hg init repo2
63 $ cd repo2
63 $ cd repo2
64 $ hg debugdrawdag <<'EOS'
64 $ hg debugdrawdag <<'EOS'
65 > c b
65 > c b
66 > |/
66 > |/
67 > d
67 > d
68 > |
68 > |
69 > a
69 > a
70 > EOS
70 > EOS
71
71
72 Add a symlink and executable file:
72 Add a symlink and executable file:
73 $ hg up -C c
73 $ hg up -C c
74 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
74 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
75 $ ln -s somefile e
75 $ ln -s somefile e
76 $ echo f > f
76 $ echo f > f
77 $ chmod +x f
77 $ chmod +x f
78 $ hg add e f
78 $ hg add e f
79 $ hg amend -q
79 $ hg amend -q
80 $ hg up -Cq a
80 $ hg up -Cq a
81
81
82 Write files to the working copy, and ensure they're still there after the rebase
82 Write files to the working copy, and ensure they're still there after the rebase
83 $ echo "abc" > a
83 $ echo "abc" > a
84 $ ln -s def b
84 $ ln -s def b
85 $ echo "ghi" > c
85 $ echo "ghi" > c
86 $ echo "jkl" > d
86 $ echo "jkl" > d
87 $ echo "mno" > e
87 $ echo "mno" > e
88 $ hg tglog
88 $ hg tglog
89 o 3: f56b71190a8f 'c'
89 o 3: f56b71190a8f 'c'
90 |
90 |
91 | o 2: db0e82a16a62 'b'
91 | o 2: db0e82a16a62 'b'
92 |/
92 |/
93 o 1: 02952614a83d 'd'
93 o 1: 02952614a83d 'd'
94 |
94 |
95 @ 0: b173517d0057 'a'
95 @ 0: b173517d0057 'a'
96
96
97 $ hg cat -r 3 c
97 $ hg cat -r 3 c
98 c (no-eol)
98 c (no-eol)
99 $ hg cat -r 2 b
99 $ hg cat -r 2 b
100 b (no-eol)
100 b (no-eol)
101 $ hg cat -r 3 e
101 $ hg cat -r 3 e
102 somefile (no-eol)
102 somefile (no-eol)
103 $ hg rebase --debug -s b -d a | grep rebasing
103 $ hg rebase --debug -s b -d a | grep rebasing
104 rebasing in-memory
104 rebasing in-memory
105 rebasing 2:db0e82a16a62 "b" (b)
105 rebasing 2:db0e82a16a62 "b" (b)
106 $ hg tglog
106 $ hg tglog
107 o 3: fc055c3b4d33 'b'
107 o 3: fc055c3b4d33 'b'
108 |
108 |
109 | o 2: f56b71190a8f 'c'
109 | o 2: f56b71190a8f 'c'
110 | |
110 | |
111 | o 1: 02952614a83d 'd'
111 | o 1: 02952614a83d 'd'
112 |/
112 |/
113 @ 0: b173517d0057 'a'
113 @ 0: b173517d0057 'a'
114
114
115 $ hg cat -r 2 c
115 $ hg cat -r 2 c
116 c (no-eol)
116 c (no-eol)
117 $ hg cat -r 3 b
117 $ hg cat -r 3 b
118 b (no-eol)
118 b (no-eol)
119 $ hg rebase --debug -s 1 -d 3 | grep rebasing
119 $ hg rebase --debug -s 1 -d 3 | grep rebasing
120 rebasing in-memory
120 rebasing in-memory
121 rebasing 1:02952614a83d "d" (d)
121 rebasing 1:02952614a83d "d" (d)
122 rebasing 2:f56b71190a8f "c"
122 rebasing 2:f56b71190a8f "c"
123 $ hg tglog
123 $ hg tglog
124 o 3: 753feb6fd12a 'c'
124 o 3: 753feb6fd12a 'c'
125 |
125 |
126 o 2: 09c044d2cb43 'd'
126 o 2: 09c044d2cb43 'd'
127 |
127 |
128 o 1: fc055c3b4d33 'b'
128 o 1: fc055c3b4d33 'b'
129 |
129 |
130 @ 0: b173517d0057 'a'
130 @ 0: b173517d0057 'a'
131
131
132 Ensure working copy files are still there:
132 Ensure working copy files are still there:
133 $ cat a
133 $ cat a
134 abc
134 abc
135 $ readlink.py b
135 $ readlink.py b
136 b -> def
136 b -> def
137 $ cat e
137 $ cat e
138 mno
138 mno
139
139
140 Ensure symlink and executable files were rebased properly:
140 Ensure symlink and executable files were rebased properly:
141 $ hg up -Cq 3
141 $ hg up -Cq 3
142 $ readlink.py e
142 $ readlink.py e
143 e -> somefile
143 e -> somefile
144 $ ls -l f | cut -c -10
144 $ ls -l f | cut -c -10
145 -rwxr-xr-x
145 -rwxr-xr-x
146
146
147 Rebase the working copy parent
147 Rebase the working copy parent
148 $ hg up -C 3
148 $ hg up -C 3
149 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
149 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
150 $ hg rebase -r 3 -d 0 --debug | grep rebasing
150 $ hg rebase -r 3 -d 0 --debug | grep rebasing
151 rebasing in-memory
151 rebasing in-memory
152 rebasing 3:753feb6fd12a "c" (tip)
152 rebasing 3:753feb6fd12a "c" (tip)
153 $ hg tglog
153 $ hg tglog
154 @ 3: 844a7de3e617 'c'
154 @ 3: 844a7de3e617 'c'
155 |
155 |
156 | o 2: 09c044d2cb43 'd'
156 | o 2: 09c044d2cb43 'd'
157 | |
157 | |
158 | o 1: fc055c3b4d33 'b'
158 | o 1: fc055c3b4d33 'b'
159 |/
159 |/
160 o 0: b173517d0057 'a'
160 o 0: b173517d0057 'a'
161
161
162
162
163 Test reporting of path conflicts
163 Test reporting of path conflicts
164
164
165 $ hg rm a
165 $ hg rm a
166 $ mkdir a
166 $ mkdir a
167 $ touch a/a
167 $ touch a/a
168 $ hg ci -Am "a/a"
168 $ hg ci -Am "a/a"
169 adding a/a
169 adding a/a
170 $ hg tglog
170 $ hg tglog
171 @ 4: daf7dfc139cb 'a/a'
171 @ 4: daf7dfc139cb 'a/a'
172 |
172 |
173 o 3: 844a7de3e617 'c'
173 o 3: 844a7de3e617 'c'
174 |
174 |
175 | o 2: 09c044d2cb43 'd'
175 | o 2: 09c044d2cb43 'd'
176 | |
176 | |
177 | o 1: fc055c3b4d33 'b'
177 | o 1: fc055c3b4d33 'b'
178 |/
178 |/
179 o 0: b173517d0057 'a'
179 o 0: b173517d0057 'a'
180
180
181 $ hg rebase -r . -d 2
181 $ hg rebase -r . -d 2
182 rebasing 4:daf7dfc139cb "a/a" (tip)
182 rebasing 4:daf7dfc139cb "a/a" (tip)
183 saved backup bundle to $TESTTMP/repo2/.hg/strip-backup/daf7dfc139cb-fdbfcf4f-rebase.hg
183 saved backup bundle to $TESTTMP/repo2/.hg/strip-backup/daf7dfc139cb-fdbfcf4f-rebase.hg
184
184
185 $ hg tglog
185 $ hg tglog
186 @ 4: c6ad37a4f250 'a/a'
186 @ 4: c6ad37a4f250 'a/a'
187 |
187 |
188 | o 3: 844a7de3e617 'c'
188 | o 3: 844a7de3e617 'c'
189 | |
189 | |
190 o | 2: 09c044d2cb43 'd'
190 o | 2: 09c044d2cb43 'd'
191 | |
191 | |
192 o | 1: fc055c3b4d33 'b'
192 o | 1: fc055c3b4d33 'b'
193 |/
193 |/
194 o 0: b173517d0057 'a'
194 o 0: b173517d0057 'a'
195
195
196 $ echo foo > foo
196 $ echo foo > foo
197 $ hg ci -Aqm "added foo"
197 $ hg ci -Aqm "added foo"
198 $ hg up '.^'
198 $ hg up '.^'
199 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
199 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
200 $ echo bar > bar
200 $ echo bar > bar
201 $ hg ci -Aqm "added bar"
201 $ hg ci -Aqm "added bar"
202 $ hg rm a/a
202 $ hg rm a/a
203 $ echo a > a
203 $ echo a > a
204 $ hg ci -Aqm "added a back!"
204 $ hg ci -Aqm "added a back!"
205 $ hg tglog
205 $ hg tglog
206 @ 7: 855e9797387e 'added a back!'
206 @ 7: 855e9797387e 'added a back!'
207 |
207 |
208 o 6: d14530e5e3e6 'added bar'
208 o 6: d14530e5e3e6 'added bar'
209 |
209 |
210 | o 5: 9b94b9373deb 'added foo'
210 | o 5: 9b94b9373deb 'added foo'
211 |/
211 |/
212 o 4: c6ad37a4f250 'a/a'
212 o 4: c6ad37a4f250 'a/a'
213 |
213 |
214 | o 3: 844a7de3e617 'c'
214 | o 3: 844a7de3e617 'c'
215 | |
215 | |
216 o | 2: 09c044d2cb43 'd'
216 o | 2: 09c044d2cb43 'd'
217 | |
217 | |
218 o | 1: fc055c3b4d33 'b'
218 o | 1: fc055c3b4d33 'b'
219 |/
219 |/
220 o 0: b173517d0057 'a'
220 o 0: b173517d0057 'a'
221
221
222 $ hg rebase -r . -d 5
222 $ hg rebase -r . -d 5
223 rebasing 7:855e9797387e "added a back!" (tip)
223 rebasing 7:855e9797387e "added a back!" (tip)
224 saved backup bundle to $TESTTMP/repo2/.hg/strip-backup/855e9797387e-81ee4c5d-rebase.hg
224 saved backup bundle to $TESTTMP/repo2/.hg/strip-backup/855e9797387e-81ee4c5d-rebase.hg
225
225
226 $ hg tglog
226 $ hg tglog
227 @ 7: bb3f02be2688 'added a back!'
227 @ 7: bb3f02be2688 'added a back!'
228 |
228 |
229 | o 6: d14530e5e3e6 'added bar'
229 | o 6: d14530e5e3e6 'added bar'
230 | |
230 | |
231 o | 5: 9b94b9373deb 'added foo'
231 o | 5: 9b94b9373deb 'added foo'
232 |/
232 |/
233 o 4: c6ad37a4f250 'a/a'
233 o 4: c6ad37a4f250 'a/a'
234 |
234 |
235 | o 3: 844a7de3e617 'c'
235 | o 3: 844a7de3e617 'c'
236 | |
236 | |
237 o | 2: 09c044d2cb43 'd'
237 o | 2: 09c044d2cb43 'd'
238 | |
238 | |
239 o | 1: fc055c3b4d33 'b'
239 o | 1: fc055c3b4d33 'b'
240 |/
240 |/
241 o 0: b173517d0057 'a'
241 o 0: b173517d0057 'a'
242
242
243 $ mkdir -p c/subdir
243 $ mkdir -p c/subdir
244 $ echo c > c/subdir/file.txt
244 $ echo c > c/subdir/file.txt
245 $ hg add c/subdir/file.txt
245 $ hg add c/subdir/file.txt
246 $ hg ci -m 'c/subdir/file.txt'
246 $ hg ci -m 'c/subdir/file.txt'
247 $ hg rebase -r . -d 3 -n
247 $ hg rebase -r . -d 3 -n
248 starting dry-run rebase; repository will not be changed
248 starting dry-run rebase; repository will not be changed
249 rebasing 8:e147e6e3c490 "c/subdir/file.txt" (tip)
249 rebasing 8:e147e6e3c490 "c/subdir/file.txt" (tip)
250 abort: error: 'c/subdir/file.txt' conflicts with file 'c' in 3.
250 abort: error: 'c/subdir/file.txt' conflicts with file 'c' in 3.
251 [255]
251 [255]
252 $ hg rebase -r 3 -d . -n
252 $ hg rebase -r 3 -d . -n
253 starting dry-run rebase; repository will not be changed
253 starting dry-run rebase; repository will not be changed
254 rebasing 3:844a7de3e617 "c"
254 rebasing 3:844a7de3e617 "c"
255 abort: error: file 'c' cannot be written because 'c/' is a folder in e147e6e3c490 (containing 1 entries: c/subdir/file.txt)
255 abort: error: file 'c' cannot be written because 'c/' is a directory in e147e6e3c490 (containing 1 entries: c/subdir/file.txt)
256 [255]
256 [255]
257
257
258 $ cd ..
258 $ cd ..
259
259
260 Test path auditing (issue5818)
260 Test path auditing (issue5818)
261
261
262 $ mkdir lib_
262 $ mkdir lib_
263 $ ln -s lib_ lib
263 $ ln -s lib_ lib
264 $ hg init repo
264 $ hg init repo
265 $ cd repo
265 $ cd repo
266 $ mkdir -p ".$TESTTMP/lib"
266 $ mkdir -p ".$TESTTMP/lib"
267 $ touch ".$TESTTMP/lib/a"
267 $ touch ".$TESTTMP/lib/a"
268 $ hg add ".$TESTTMP/lib/a"
268 $ hg add ".$TESTTMP/lib/a"
269 $ hg ci -m 'a'
269 $ hg ci -m 'a'
270
270
271 $ touch ".$TESTTMP/lib/b"
271 $ touch ".$TESTTMP/lib/b"
272 $ hg add ".$TESTTMP/lib/b"
272 $ hg add ".$TESTTMP/lib/b"
273 $ hg ci -m 'b'
273 $ hg ci -m 'b'
274
274
275 $ hg up -q '.^'
275 $ hg up -q '.^'
276 $ touch ".$TESTTMP/lib/c"
276 $ touch ".$TESTTMP/lib/c"
277 $ hg add ".$TESTTMP/lib/c"
277 $ hg add ".$TESTTMP/lib/c"
278 $ hg ci -m 'c'
278 $ hg ci -m 'c'
279 created new head
279 created new head
280 $ hg rebase -s 1 -d .
280 $ hg rebase -s 1 -d .
281 rebasing 1:* "b" (glob)
281 rebasing 1:* "b" (glob)
282 saved backup bundle to $TESTTMP/repo/.hg/strip-backup/*-rebase.hg (glob)
282 saved backup bundle to $TESTTMP/repo/.hg/strip-backup/*-rebase.hg (glob)
283 $ cd ..
283 $ cd ..
284
284
285 Test dry-run rebasing
285 Test dry-run rebasing
286
286
287 $ hg init repo3
287 $ hg init repo3
288 $ cd repo3
288 $ cd repo3
289 $ echo a>a
289 $ echo a>a
290 $ hg ci -Aqma
290 $ hg ci -Aqma
291 $ echo b>b
291 $ echo b>b
292 $ hg ci -Aqmb
292 $ hg ci -Aqmb
293 $ echo c>c
293 $ echo c>c
294 $ hg ci -Aqmc
294 $ hg ci -Aqmc
295 $ echo d>d
295 $ echo d>d
296 $ hg ci -Aqmd
296 $ hg ci -Aqmd
297 $ echo e>e
297 $ echo e>e
298 $ hg ci -Aqme
298 $ hg ci -Aqme
299
299
300 $ hg up 1 -q
300 $ hg up 1 -q
301 $ echo f>f
301 $ echo f>f
302 $ hg ci -Amf
302 $ hg ci -Amf
303 adding f
303 adding f
304 created new head
304 created new head
305 $ echo g>g
305 $ echo g>g
306 $ hg ci -Aqmg
306 $ hg ci -Aqmg
307 $ hg log -G --template "{rev}:{short(node)} {person(author)}\n{firstline(desc)} {topic}\n\n"
307 $ hg log -G --template "{rev}:{short(node)} {person(author)}\n{firstline(desc)} {topic}\n\n"
308 @ 6:baf10c5166d4 test
308 @ 6:baf10c5166d4 test
309 | g
309 | g
310 |
310 |
311 o 5:6343ca3eff20 test
311 o 5:6343ca3eff20 test
312 | f
312 | f
313 |
313 |
314 | o 4:e860deea161a test
314 | o 4:e860deea161a test
315 | | e
315 | | e
316 | |
316 | |
317 | o 3:055a42cdd887 test
317 | o 3:055a42cdd887 test
318 | | d
318 | | d
319 | |
319 | |
320 | o 2:177f92b77385 test
320 | o 2:177f92b77385 test
321 |/ c
321 |/ c
322 |
322 |
323 o 1:d2ae7f538514 test
323 o 1:d2ae7f538514 test
324 | b
324 | b
325 |
325 |
326 o 0:cb9a9f314b8b test
326 o 0:cb9a9f314b8b test
327 a
327 a
328
328
329 Make sure it throws error while passing --continue or --abort with --dry-run
329 Make sure it throws error while passing --continue or --abort with --dry-run
330 $ hg rebase -s 2 -d 6 -n --continue
330 $ hg rebase -s 2 -d 6 -n --continue
331 abort: cannot specify both --dry-run and --continue
331 abort: cannot specify both --dry-run and --continue
332 [255]
332 [255]
333 $ hg rebase -s 2 -d 6 -n --abort
333 $ hg rebase -s 2 -d 6 -n --abort
334 abort: cannot specify both --dry-run and --abort
334 abort: cannot specify both --dry-run and --abort
335 [255]
335 [255]
336
336
337 Check dryrun gives correct results when there is no conflict in rebasing
337 Check dryrun gives correct results when there is no conflict in rebasing
338 $ hg rebase -s 2 -d 6 -n
338 $ hg rebase -s 2 -d 6 -n
339 starting dry-run rebase; repository will not be changed
339 starting dry-run rebase; repository will not be changed
340 rebasing 2:177f92b77385 "c"
340 rebasing 2:177f92b77385 "c"
341 rebasing 3:055a42cdd887 "d"
341 rebasing 3:055a42cdd887 "d"
342 rebasing 4:e860deea161a "e"
342 rebasing 4:e860deea161a "e"
343 dry-run rebase completed successfully; run without -n/--dry-run to perform this rebase
343 dry-run rebase completed successfully; run without -n/--dry-run to perform this rebase
344
344
345 $ hg diff
345 $ hg diff
346 $ hg status
346 $ hg status
347
347
348 $ hg log -G --template "{rev}:{short(node)} {person(author)}\n{firstline(desc)} {topic}\n\n"
348 $ hg log -G --template "{rev}:{short(node)} {person(author)}\n{firstline(desc)} {topic}\n\n"
349 @ 6:baf10c5166d4 test
349 @ 6:baf10c5166d4 test
350 | g
350 | g
351 |
351 |
352 o 5:6343ca3eff20 test
352 o 5:6343ca3eff20 test
353 | f
353 | f
354 |
354 |
355 | o 4:e860deea161a test
355 | o 4:e860deea161a test
356 | | e
356 | | e
357 | |
357 | |
358 | o 3:055a42cdd887 test
358 | o 3:055a42cdd887 test
359 | | d
359 | | d
360 | |
360 | |
361 | o 2:177f92b77385 test
361 | o 2:177f92b77385 test
362 |/ c
362 |/ c
363 |
363 |
364 o 1:d2ae7f538514 test
364 o 1:d2ae7f538514 test
365 | b
365 | b
366 |
366 |
367 o 0:cb9a9f314b8b test
367 o 0:cb9a9f314b8b test
368 a
368 a
369
369
370 Check dryrun working with --collapse when there is no conflict
370 Check dryrun working with --collapse when there is no conflict
371 $ hg rebase -s 2 -d 6 -n --collapse
371 $ hg rebase -s 2 -d 6 -n --collapse
372 starting dry-run rebase; repository will not be changed
372 starting dry-run rebase; repository will not be changed
373 rebasing 2:177f92b77385 "c"
373 rebasing 2:177f92b77385 "c"
374 rebasing 3:055a42cdd887 "d"
374 rebasing 3:055a42cdd887 "d"
375 rebasing 4:e860deea161a "e"
375 rebasing 4:e860deea161a "e"
376 dry-run rebase completed successfully; run without -n/--dry-run to perform this rebase
376 dry-run rebase completed successfully; run without -n/--dry-run to perform this rebase
377
377
378 Check dryrun gives correct results when there is conflict in rebasing
378 Check dryrun gives correct results when there is conflict in rebasing
379 Make a conflict:
379 Make a conflict:
380 $ hg up 6 -q
380 $ hg up 6 -q
381 $ echo conflict>e
381 $ echo conflict>e
382 $ hg ci -Aqm "conflict with e"
382 $ hg ci -Aqm "conflict with e"
383 $ hg log -G --template "{rev}:{short(node)} {person(author)}\n{firstline(desc)} {topic}\n\n"
383 $ hg log -G --template "{rev}:{short(node)} {person(author)}\n{firstline(desc)} {topic}\n\n"
384 @ 7:d2c195b28050 test
384 @ 7:d2c195b28050 test
385 | conflict with e
385 | conflict with e
386 |
386 |
387 o 6:baf10c5166d4 test
387 o 6:baf10c5166d4 test
388 | g
388 | g
389 |
389 |
390 o 5:6343ca3eff20 test
390 o 5:6343ca3eff20 test
391 | f
391 | f
392 |
392 |
393 | o 4:e860deea161a test
393 | o 4:e860deea161a test
394 | | e
394 | | e
395 | |
395 | |
396 | o 3:055a42cdd887 test
396 | o 3:055a42cdd887 test
397 | | d
397 | | d
398 | |
398 | |
399 | o 2:177f92b77385 test
399 | o 2:177f92b77385 test
400 |/ c
400 |/ c
401 |
401 |
402 o 1:d2ae7f538514 test
402 o 1:d2ae7f538514 test
403 | b
403 | b
404 |
404 |
405 o 0:cb9a9f314b8b test
405 o 0:cb9a9f314b8b test
406 a
406 a
407
407
408 $ hg rebase -s 2 -d 7 -n
408 $ hg rebase -s 2 -d 7 -n
409 starting dry-run rebase; repository will not be changed
409 starting dry-run rebase; repository will not be changed
410 rebasing 2:177f92b77385 "c"
410 rebasing 2:177f92b77385 "c"
411 rebasing 3:055a42cdd887 "d"
411 rebasing 3:055a42cdd887 "d"
412 rebasing 4:e860deea161a "e"
412 rebasing 4:e860deea161a "e"
413 merging e
413 merging e
414 transaction abort!
414 transaction abort!
415 rollback completed
415 rollback completed
416 hit a merge conflict
416 hit a merge conflict
417 [1]
417 [1]
418 $ hg diff
418 $ hg diff
419 $ hg status
419 $ hg status
420 $ hg log -G --template "{rev}:{short(node)} {person(author)}\n{firstline(desc)} {topic}\n\n"
420 $ hg log -G --template "{rev}:{short(node)} {person(author)}\n{firstline(desc)} {topic}\n\n"
421 @ 7:d2c195b28050 test
421 @ 7:d2c195b28050 test
422 | conflict with e
422 | conflict with e
423 |
423 |
424 o 6:baf10c5166d4 test
424 o 6:baf10c5166d4 test
425 | g
425 | g
426 |
426 |
427 o 5:6343ca3eff20 test
427 o 5:6343ca3eff20 test
428 | f
428 | f
429 |
429 |
430 | o 4:e860deea161a test
430 | o 4:e860deea161a test
431 | | e
431 | | e
432 | |
432 | |
433 | o 3:055a42cdd887 test
433 | o 3:055a42cdd887 test
434 | | d
434 | | d
435 | |
435 | |
436 | o 2:177f92b77385 test
436 | o 2:177f92b77385 test
437 |/ c
437 |/ c
438 |
438 |
439 o 1:d2ae7f538514 test
439 o 1:d2ae7f538514 test
440 | b
440 | b
441 |
441 |
442 o 0:cb9a9f314b8b test
442 o 0:cb9a9f314b8b test
443 a
443 a
444
444
445 Check dryrun working with --collapse when there is conflicts
445 Check dryrun working with --collapse when there is conflicts
446 $ hg rebase -s 2 -d 7 -n --collapse
446 $ hg rebase -s 2 -d 7 -n --collapse
447 starting dry-run rebase; repository will not be changed
447 starting dry-run rebase; repository will not be changed
448 rebasing 2:177f92b77385 "c"
448 rebasing 2:177f92b77385 "c"
449 rebasing 3:055a42cdd887 "d"
449 rebasing 3:055a42cdd887 "d"
450 rebasing 4:e860deea161a "e"
450 rebasing 4:e860deea161a "e"
451 merging e
451 merging e
452 hit a merge conflict
452 hit a merge conflict
453 [1]
453 [1]
454
454
455 In-memory rebase that fails due to merge conflicts
455 In-memory rebase that fails due to merge conflicts
456
456
457 $ hg rebase -s 2 -d 7
457 $ hg rebase -s 2 -d 7
458 rebasing 2:177f92b77385 "c"
458 rebasing 2:177f92b77385 "c"
459 rebasing 3:055a42cdd887 "d"
459 rebasing 3:055a42cdd887 "d"
460 rebasing 4:e860deea161a "e"
460 rebasing 4:e860deea161a "e"
461 merging e
461 merging e
462 transaction abort!
462 transaction abort!
463 rollback completed
463 rollback completed
464 hit merge conflicts; re-running rebase without in-memory merge
464 hit merge conflicts; re-running rebase without in-memory merge
465 rebasing 2:177f92b77385 "c"
465 rebasing 2:177f92b77385 "c"
466 rebasing 3:055a42cdd887 "d"
466 rebasing 3:055a42cdd887 "d"
467 rebasing 4:e860deea161a "e"
467 rebasing 4:e860deea161a "e"
468 merging e
468 merging e
469 warning: conflicts while merging e! (edit, then use 'hg resolve --mark')
469 warning: conflicts while merging e! (edit, then use 'hg resolve --mark')
470 unresolved conflicts (see hg resolve, then hg rebase --continue)
470 unresolved conflicts (see hg resolve, then hg rebase --continue)
471 [1]
471 [1]
472 $ hg rebase --abort
472 $ hg rebase --abort
473 saved backup bundle to $TESTTMP/repo3/.hg/strip-backup/c1e524d4287c-f91f82e1-backup.hg
473 saved backup bundle to $TESTTMP/repo3/.hg/strip-backup/c1e524d4287c-f91f82e1-backup.hg
474 rebase aborted
474 rebase aborted
475
475
476 Retrying without in-memory merge won't lose working copy changes
476 Retrying without in-memory merge won't lose working copy changes
477 $ cd ..
477 $ cd ..
478 $ hg clone repo3 repo3-dirty -q
478 $ hg clone repo3 repo3-dirty -q
479 $ cd repo3-dirty
479 $ cd repo3-dirty
480 $ echo dirty > a
480 $ echo dirty > a
481 $ hg rebase -s 2 -d 7
481 $ hg rebase -s 2 -d 7
482 rebasing 2:177f92b77385 "c"
482 rebasing 2:177f92b77385 "c"
483 rebasing 3:055a42cdd887 "d"
483 rebasing 3:055a42cdd887 "d"
484 rebasing 4:e860deea161a "e"
484 rebasing 4:e860deea161a "e"
485 merging e
485 merging e
486 transaction abort!
486 transaction abort!
487 rollback completed
487 rollback completed
488 hit merge conflicts; re-running rebase without in-memory merge
488 hit merge conflicts; re-running rebase without in-memory merge
489 abort: uncommitted changes
489 abort: uncommitted changes
490 [255]
490 [255]
491 $ cat a
491 $ cat a
492 dirty
492 dirty
493
493
494 Retrying without in-memory merge won't lose merge state
494 Retrying without in-memory merge won't lose merge state
495 $ cd ..
495 $ cd ..
496 $ hg clone repo3 repo3-merge-state -q
496 $ hg clone repo3 repo3-merge-state -q
497 $ cd repo3-merge-state
497 $ cd repo3-merge-state
498 $ hg merge 4
498 $ hg merge 4
499 merging e
499 merging e
500 warning: conflicts while merging e! (edit, then use 'hg resolve --mark')
500 warning: conflicts while merging e! (edit, then use 'hg resolve --mark')
501 2 files updated, 0 files merged, 0 files removed, 1 files unresolved
501 2 files updated, 0 files merged, 0 files removed, 1 files unresolved
502 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
502 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
503 [1]
503 [1]
504 $ hg resolve -l
504 $ hg resolve -l
505 U e
505 U e
506 $ hg rebase -s 2 -d 7
506 $ hg rebase -s 2 -d 7
507 rebasing 2:177f92b77385 "c"
507 rebasing 2:177f92b77385 "c"
508 abort: outstanding merge conflicts
508 abort: outstanding merge conflicts
509 [255]
509 [255]
510 $ hg resolve -l
510 $ hg resolve -l
511 U e
511 U e
512
512
513 ==========================
513 ==========================
514 Test for --confirm option|
514 Test for --confirm option|
515 ==========================
515 ==========================
516 $ cd ..
516 $ cd ..
517 $ hg clone repo3 repo4 -q
517 $ hg clone repo3 repo4 -q
518 $ cd repo4
518 $ cd repo4
519 $ hg strip 7 -q
519 $ hg strip 7 -q
520 $ hg log -G --template "{rev}:{short(node)} {person(author)}\n{firstline(desc)} {topic}\n\n"
520 $ hg log -G --template "{rev}:{short(node)} {person(author)}\n{firstline(desc)} {topic}\n\n"
521 @ 6:baf10c5166d4 test
521 @ 6:baf10c5166d4 test
522 | g
522 | g
523 |
523 |
524 o 5:6343ca3eff20 test
524 o 5:6343ca3eff20 test
525 | f
525 | f
526 |
526 |
527 | o 4:e860deea161a test
527 | o 4:e860deea161a test
528 | | e
528 | | e
529 | |
529 | |
530 | o 3:055a42cdd887 test
530 | o 3:055a42cdd887 test
531 | | d
531 | | d
532 | |
532 | |
533 | o 2:177f92b77385 test
533 | o 2:177f92b77385 test
534 |/ c
534 |/ c
535 |
535 |
536 o 1:d2ae7f538514 test
536 o 1:d2ae7f538514 test
537 | b
537 | b
538 |
538 |
539 o 0:cb9a9f314b8b test
539 o 0:cb9a9f314b8b test
540 a
540 a
541
541
542 Check it gives error when both --dryrun and --confirm is used:
542 Check it gives error when both --dryrun and --confirm is used:
543 $ hg rebase -s 2 -d . --confirm --dry-run
543 $ hg rebase -s 2 -d . --confirm --dry-run
544 abort: cannot specify both --confirm and --dry-run
544 abort: cannot specify both --confirm and --dry-run
545 [255]
545 [255]
546 $ hg rebase -s 2 -d . --confirm --abort
546 $ hg rebase -s 2 -d . --confirm --abort
547 abort: cannot specify both --confirm and --abort
547 abort: cannot specify both --confirm and --abort
548 [255]
548 [255]
549 $ hg rebase -s 2 -d . --confirm --continue
549 $ hg rebase -s 2 -d . --confirm --continue
550 abort: cannot specify both --confirm and --continue
550 abort: cannot specify both --confirm and --continue
551 [255]
551 [255]
552
552
553 Test --confirm option when there are no conflicts:
553 Test --confirm option when there are no conflicts:
554 $ hg rebase -s 2 -d . --keep --config ui.interactive=True --confirm << EOF
554 $ hg rebase -s 2 -d . --keep --config ui.interactive=True --confirm << EOF
555 > n
555 > n
556 > EOF
556 > EOF
557 starting in-memory rebase
557 starting in-memory rebase
558 rebasing 2:177f92b77385 "c"
558 rebasing 2:177f92b77385 "c"
559 rebasing 3:055a42cdd887 "d"
559 rebasing 3:055a42cdd887 "d"
560 rebasing 4:e860deea161a "e"
560 rebasing 4:e860deea161a "e"
561 rebase completed successfully
561 rebase completed successfully
562 apply changes (yn)? n
562 apply changes (yn)? n
563 $ hg log -G --template "{rev}:{short(node)} {person(author)}\n{firstline(desc)} {topic}\n\n"
563 $ hg log -G --template "{rev}:{short(node)} {person(author)}\n{firstline(desc)} {topic}\n\n"
564 @ 6:baf10c5166d4 test
564 @ 6:baf10c5166d4 test
565 | g
565 | g
566 |
566 |
567 o 5:6343ca3eff20 test
567 o 5:6343ca3eff20 test
568 | f
568 | f
569 |
569 |
570 | o 4:e860deea161a test
570 | o 4:e860deea161a test
571 | | e
571 | | e
572 | |
572 | |
573 | o 3:055a42cdd887 test
573 | o 3:055a42cdd887 test
574 | | d
574 | | d
575 | |
575 | |
576 | o 2:177f92b77385 test
576 | o 2:177f92b77385 test
577 |/ c
577 |/ c
578 |
578 |
579 o 1:d2ae7f538514 test
579 o 1:d2ae7f538514 test
580 | b
580 | b
581 |
581 |
582 o 0:cb9a9f314b8b test
582 o 0:cb9a9f314b8b test
583 a
583 a
584
584
585 $ hg rebase -s 2 -d . --keep --config ui.interactive=True --confirm << EOF
585 $ hg rebase -s 2 -d . --keep --config ui.interactive=True --confirm << EOF
586 > y
586 > y
587 > EOF
587 > EOF
588 starting in-memory rebase
588 starting in-memory rebase
589 rebasing 2:177f92b77385 "c"
589 rebasing 2:177f92b77385 "c"
590 rebasing 3:055a42cdd887 "d"
590 rebasing 3:055a42cdd887 "d"
591 rebasing 4:e860deea161a "e"
591 rebasing 4:e860deea161a "e"
592 rebase completed successfully
592 rebase completed successfully
593 apply changes (yn)? y
593 apply changes (yn)? y
594 $ hg log -G --template "{rev}:{short(node)} {person(author)}\n{firstline(desc)} {topic}\n\n"
594 $ hg log -G --template "{rev}:{short(node)} {person(author)}\n{firstline(desc)} {topic}\n\n"
595 o 9:9fd28f55f6dc test
595 o 9:9fd28f55f6dc test
596 | e
596 | e
597 |
597 |
598 o 8:12cbf031f469 test
598 o 8:12cbf031f469 test
599 | d
599 | d
600 |
600 |
601 o 7:c83b1da5b1ae test
601 o 7:c83b1da5b1ae test
602 | c
602 | c
603 |
603 |
604 @ 6:baf10c5166d4 test
604 @ 6:baf10c5166d4 test
605 | g
605 | g
606 |
606 |
607 o 5:6343ca3eff20 test
607 o 5:6343ca3eff20 test
608 | f
608 | f
609 |
609 |
610 | o 4:e860deea161a test
610 | o 4:e860deea161a test
611 | | e
611 | | e
612 | |
612 | |
613 | o 3:055a42cdd887 test
613 | o 3:055a42cdd887 test
614 | | d
614 | | d
615 | |
615 | |
616 | o 2:177f92b77385 test
616 | o 2:177f92b77385 test
617 |/ c
617 |/ c
618 |
618 |
619 o 1:d2ae7f538514 test
619 o 1:d2ae7f538514 test
620 | b
620 | b
621 |
621 |
622 o 0:cb9a9f314b8b test
622 o 0:cb9a9f314b8b test
623 a
623 a
624
624
625 Test --confirm option when there is a conflict
625 Test --confirm option when there is a conflict
626 $ hg up tip -q
626 $ hg up tip -q
627 $ echo ee>e
627 $ echo ee>e
628 $ hg ci --amend -m "conflict with e" -q
628 $ hg ci --amend -m "conflict with e" -q
629 $ hg log -G --template "{rev}:{short(node)} {person(author)}\n{firstline(desc)} {topic}\n\n"
629 $ hg log -G --template "{rev}:{short(node)} {person(author)}\n{firstline(desc)} {topic}\n\n"
630 @ 9:906d72f66a59 test
630 @ 9:906d72f66a59 test
631 | conflict with e
631 | conflict with e
632 |
632 |
633 o 8:12cbf031f469 test
633 o 8:12cbf031f469 test
634 | d
634 | d
635 |
635 |
636 o 7:c83b1da5b1ae test
636 o 7:c83b1da5b1ae test
637 | c
637 | c
638 |
638 |
639 o 6:baf10c5166d4 test
639 o 6:baf10c5166d4 test
640 | g
640 | g
641 |
641 |
642 o 5:6343ca3eff20 test
642 o 5:6343ca3eff20 test
643 | f
643 | f
644 |
644 |
645 | o 4:e860deea161a test
645 | o 4:e860deea161a test
646 | | e
646 | | e
647 | |
647 | |
648 | o 3:055a42cdd887 test
648 | o 3:055a42cdd887 test
649 | | d
649 | | d
650 | |
650 | |
651 | o 2:177f92b77385 test
651 | o 2:177f92b77385 test
652 |/ c
652 |/ c
653 |
653 |
654 o 1:d2ae7f538514 test
654 o 1:d2ae7f538514 test
655 | b
655 | b
656 |
656 |
657 o 0:cb9a9f314b8b test
657 o 0:cb9a9f314b8b test
658 a
658 a
659
659
660 $ hg rebase -s 4 -d . --keep --confirm
660 $ hg rebase -s 4 -d . --keep --confirm
661 starting in-memory rebase
661 starting in-memory rebase
662 rebasing 4:e860deea161a "e"
662 rebasing 4:e860deea161a "e"
663 merging e
663 merging e
664 hit a merge conflict
664 hit a merge conflict
665 [1]
665 [1]
666 $ hg log -G --template "{rev}:{short(node)} {person(author)}\n{firstline(desc)} {topic}\n\n"
666 $ hg log -G --template "{rev}:{short(node)} {person(author)}\n{firstline(desc)} {topic}\n\n"
667 @ 9:906d72f66a59 test
667 @ 9:906d72f66a59 test
668 | conflict with e
668 | conflict with e
669 |
669 |
670 o 8:12cbf031f469 test
670 o 8:12cbf031f469 test
671 | d
671 | d
672 |
672 |
673 o 7:c83b1da5b1ae test
673 o 7:c83b1da5b1ae test
674 | c
674 | c
675 |
675 |
676 o 6:baf10c5166d4 test
676 o 6:baf10c5166d4 test
677 | g
677 | g
678 |
678 |
679 o 5:6343ca3eff20 test
679 o 5:6343ca3eff20 test
680 | f
680 | f
681 |
681 |
682 | o 4:e860deea161a test
682 | o 4:e860deea161a test
683 | | e
683 | | e
684 | |
684 | |
685 | o 3:055a42cdd887 test
685 | o 3:055a42cdd887 test
686 | | d
686 | | d
687 | |
687 | |
688 | o 2:177f92b77385 test
688 | o 2:177f92b77385 test
689 |/ c
689 |/ c
690 |
690 |
691 o 1:d2ae7f538514 test
691 o 1:d2ae7f538514 test
692 | b
692 | b
693 |
693 |
694 o 0:cb9a9f314b8b test
694 o 0:cb9a9f314b8b test
695 a
695 a
696
696
697 Test a metadata-only in-memory merge
697 Test a metadata-only in-memory merge
698 $ cd $TESTTMP
698 $ cd $TESTTMP
699 $ hg init no_exception
699 $ hg init no_exception
700 $ cd no_exception
700 $ cd no_exception
701 # Produce the following graph:
701 # Produce the following graph:
702 # o 'add +x to foo.txt'
702 # o 'add +x to foo.txt'
703 # | o r1 (adds bar.txt, just for something to rebase to)
703 # | o r1 (adds bar.txt, just for something to rebase to)
704 # |/
704 # |/
705 # o r0 (adds foo.txt, no +x)
705 # o r0 (adds foo.txt, no +x)
706 $ echo hi > foo.txt
706 $ echo hi > foo.txt
707 $ hg ci -qAm r0
707 $ hg ci -qAm r0
708 $ echo hi > bar.txt
708 $ echo hi > bar.txt
709 $ hg ci -qAm r1
709 $ hg ci -qAm r1
710 $ hg co -qr ".^"
710 $ hg co -qr ".^"
711 $ chmod +x foo.txt
711 $ chmod +x foo.txt
712 $ hg ci -qAm 'add +x to foo.txt'
712 $ hg ci -qAm 'add +x to foo.txt'
713 issue5960: this was raising an AttributeError exception
713 issue5960: this was raising an AttributeError exception
714 $ hg rebase -r . -d 1
714 $ hg rebase -r . -d 1
715 rebasing 2:539b93e77479 "add +x to foo.txt" (tip)
715 rebasing 2:539b93e77479 "add +x to foo.txt" (tip)
716 saved backup bundle to $TESTTMP/no_exception/.hg/strip-backup/*.hg (glob)
716 saved backup bundle to $TESTTMP/no_exception/.hg/strip-backup/*.hg (glob)
717 $ hg diff -c tip
717 $ hg diff -c tip
718 diff --git a/foo.txt b/foo.txt
718 diff --git a/foo.txt b/foo.txt
719 old mode 100644
719 old mode 100644
720 new mode 100755
720 new mode 100755
721
721
722 Test rebasing a commit with copy information, but no content changes
722 Test rebasing a commit with copy information, but no content changes
723
723
724 $ cd ..
724 $ cd ..
725 $ hg clone -q repo1 merge-and-rename
725 $ hg clone -q repo1 merge-and-rename
726 $ cd merge-and-rename
726 $ cd merge-and-rename
727 $ cat << EOF >> .hg/hgrc
727 $ cat << EOF >> .hg/hgrc
728 > [experimental]
728 > [experimental]
729 > evolution.createmarkers=True
729 > evolution.createmarkers=True
730 > evolution.allowunstable=True
730 > evolution.allowunstable=True
731 > EOF
731 > EOF
732 $ hg co -q 1
732 $ hg co -q 1
733 $ hg mv d e
733 $ hg mv d e
734 $ hg ci -qm 'rename d to e'
734 $ hg ci -qm 'rename d to e'
735 $ hg co -q 3
735 $ hg co -q 3
736 $ hg merge -q 4
736 $ hg merge -q 4
737 $ hg ci -m 'merge'
737 $ hg ci -m 'merge'
738 $ hg co -q 2
738 $ hg co -q 2
739 $ mv d e
739 $ mv d e
740 $ hg addremove -qs 0
740 $ hg addremove -qs 0
741 $ hg ci -qm 'untracked rename of d to e'
741 $ hg ci -qm 'untracked rename of d to e'
742 $ hg debugobsolete -q `hg log -T '{node}' -r 4` `hg log -T '{node}' -r .`
742 $ hg debugobsolete -q `hg log -T '{node}' -r 4` `hg log -T '{node}' -r .`
743 1 new orphan changesets
743 1 new orphan changesets
744 $ hg tglog
744 $ hg tglog
745 @ 6: 676538af172d 'untracked rename of d to e'
745 @ 6: 676538af172d 'untracked rename of d to e'
746 |
746 |
747 | * 5: 71cb43376053 'merge'
747 | * 5: 71cb43376053 'merge'
748 | |\
748 | |\
749 | | x 4: 2c8b5dad7956 'rename d to e'
749 | | x 4: 2c8b5dad7956 'rename d to e'
750 | | |
750 | | |
751 | o | 3: ca58782ad1e4 'b'
751 | o | 3: ca58782ad1e4 'b'
752 |/ /
752 |/ /
753 o / 2: 814f6bd05178 'c'
753 o / 2: 814f6bd05178 'c'
754 |/
754 |/
755 o 1: 02952614a83d 'd'
755 o 1: 02952614a83d 'd'
756 |
756 |
757 o 0: b173517d0057 'a'
757 o 0: b173517d0057 'a'
758
758
759 $ hg rebase -b 5 -d tip
759 $ hg rebase -b 5 -d tip
760 rebasing 3:ca58782ad1e4 "b"
760 rebasing 3:ca58782ad1e4 "b"
761 rebasing 5:71cb43376053 "merge"
761 rebasing 5:71cb43376053 "merge"
762 note: not rebasing 5:71cb43376053 "merge", its destination already has all its changes
762 note: not rebasing 5:71cb43376053 "merge", its destination already has all its changes
General Comments 0
You need to be logged in to leave comments. Login now