##// END OF EJS Templates
status: revert + flag-change == modified...
Martin von Zweigbergk -
r27668:369c8f94 default
parent child Browse files
Show More
@@ -1,1969 +1,1971 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 os
11 import os
12 import re
12 import re
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 bin,
17 bin,
18 hex,
18 hex,
19 nullid,
19 nullid,
20 nullrev,
20 nullrev,
21 short,
21 short,
22 wdirid,
22 wdirid,
23 )
23 )
24 from . import (
24 from . import (
25 encoding,
25 encoding,
26 error,
26 error,
27 fileset,
27 fileset,
28 match as matchmod,
28 match as matchmod,
29 mdiff,
29 mdiff,
30 obsolete as obsmod,
30 obsolete as obsmod,
31 patch,
31 patch,
32 phases,
32 phases,
33 repoview,
33 repoview,
34 revlog,
34 revlog,
35 scmutil,
35 scmutil,
36 subrepo,
36 subrepo,
37 util,
37 util,
38 )
38 )
39
39
40 propertycache = util.propertycache
40 propertycache = util.propertycache
41
41
42 # Phony node value to stand-in for new files in some uses of
42 # Phony node value to stand-in for new files in some uses of
43 # manifests. Manifests support 21-byte hashes for nodes which are
43 # manifests. Manifests support 21-byte hashes for nodes which are
44 # dirty in the working copy.
44 # dirty in the working copy.
45 _newnode = '!' * 21
45 _newnode = '!' * 21
46
46
47 nonascii = re.compile(r'[^\x21-\x7f]').search
47 nonascii = re.compile(r'[^\x21-\x7f]').search
48
48
49 class basectx(object):
49 class basectx(object):
50 """A basectx object represents the common logic for its children:
50 """A basectx object represents the common logic for its children:
51 changectx: read-only context that is already present in the repo,
51 changectx: read-only context that is already present in the repo,
52 workingctx: a context that represents the working directory and can
52 workingctx: a context that represents the working directory and can
53 be committed,
53 be committed,
54 memctx: a context that represents changes in-memory and can also
54 memctx: a context that represents changes in-memory and can also
55 be committed."""
55 be committed."""
56 def __new__(cls, repo, changeid='', *args, **kwargs):
56 def __new__(cls, repo, changeid='', *args, **kwargs):
57 if isinstance(changeid, basectx):
57 if isinstance(changeid, basectx):
58 return changeid
58 return changeid
59
59
60 o = super(basectx, cls).__new__(cls)
60 o = super(basectx, cls).__new__(cls)
61
61
62 o._repo = repo
62 o._repo = repo
63 o._rev = nullrev
63 o._rev = nullrev
64 o._node = nullid
64 o._node = nullid
65
65
66 return o
66 return o
67
67
68 def __str__(self):
68 def __str__(self):
69 return short(self.node())
69 return short(self.node())
70
70
71 def __int__(self):
71 def __int__(self):
72 return self.rev()
72 return self.rev()
73
73
74 def __repr__(self):
74 def __repr__(self):
75 return "<%s %s>" % (type(self).__name__, str(self))
75 return "<%s %s>" % (type(self).__name__, str(self))
76
76
77 def __eq__(self, other):
77 def __eq__(self, other):
78 try:
78 try:
79 return type(self) == type(other) and self._rev == other._rev
79 return type(self) == type(other) and self._rev == other._rev
80 except AttributeError:
80 except AttributeError:
81 return False
81 return False
82
82
83 def __ne__(self, other):
83 def __ne__(self, other):
84 return not (self == other)
84 return not (self == other)
85
85
86 def __contains__(self, key):
86 def __contains__(self, key):
87 return key in self._manifest
87 return key in self._manifest
88
88
89 def __getitem__(self, key):
89 def __getitem__(self, key):
90 return self.filectx(key)
90 return self.filectx(key)
91
91
92 def __iter__(self):
92 def __iter__(self):
93 return iter(self._manifest)
93 return iter(self._manifest)
94
94
95 def _manifestmatches(self, match, s):
95 def _manifestmatches(self, match, s):
96 """generate a new manifest filtered by the match argument
96 """generate a new manifest filtered by the match argument
97
97
98 This method is for internal use only and mainly exists to provide an
98 This method is for internal use only and mainly exists to provide an
99 object oriented way for other contexts to customize the manifest
99 object oriented way for other contexts to customize the manifest
100 generation.
100 generation.
101 """
101 """
102 return self.manifest().matches(match)
102 return self.manifest().matches(match)
103
103
104 def _matchstatus(self, other, match):
104 def _matchstatus(self, other, match):
105 """return match.always if match is none
105 """return match.always if match is none
106
106
107 This internal method provides a way for child objects to override the
107 This internal method provides a way for child objects to override the
108 match operator.
108 match operator.
109 """
109 """
110 return match or matchmod.always(self._repo.root, self._repo.getcwd())
110 return match or matchmod.always(self._repo.root, self._repo.getcwd())
111
111
112 def _buildstatus(self, other, s, match, listignored, listclean,
112 def _buildstatus(self, other, s, match, listignored, listclean,
113 listunknown):
113 listunknown):
114 """build a status with respect to another context"""
114 """build a status with respect to another context"""
115 # Load earliest manifest first for caching reasons. More specifically,
115 # Load earliest manifest first for caching reasons. More specifically,
116 # if you have revisions 1000 and 1001, 1001 is probably stored as a
116 # if you have revisions 1000 and 1001, 1001 is probably stored as a
117 # delta against 1000. Thus, if you read 1000 first, we'll reconstruct
117 # delta against 1000. Thus, if you read 1000 first, we'll reconstruct
118 # 1000 and cache it so that when you read 1001, we just need to apply a
118 # 1000 and cache it so that when you read 1001, we just need to apply a
119 # delta to what's in the cache. So that's one full reconstruction + one
119 # delta to what's in the cache. So that's one full reconstruction + one
120 # delta application.
120 # delta application.
121 if self.rev() is not None and self.rev() < other.rev():
121 if self.rev() is not None and self.rev() < other.rev():
122 self.manifest()
122 self.manifest()
123 mf1 = other._manifestmatches(match, s)
123 mf1 = other._manifestmatches(match, s)
124 mf2 = self._manifestmatches(match, s)
124 mf2 = self._manifestmatches(match, s)
125
125
126 modified, added = [], []
126 modified, added = [], []
127 removed = []
127 removed = []
128 clean = []
128 clean = []
129 deleted, unknown, ignored = s.deleted, s.unknown, s.ignored
129 deleted, unknown, ignored = s.deleted, s.unknown, s.ignored
130 deletedset = set(deleted)
130 deletedset = set(deleted)
131 d = mf1.diff(mf2, clean=listclean)
131 d = mf1.diff(mf2, clean=listclean)
132 for fn, value in d.iteritems():
132 for fn, value in d.iteritems():
133 if fn in deletedset:
133 if fn in deletedset:
134 continue
134 continue
135 if value is None:
135 if value is None:
136 clean.append(fn)
136 clean.append(fn)
137 continue
137 continue
138 (node1, flag1), (node2, flag2) = value
138 (node1, flag1), (node2, flag2) = value
139 if node1 is None:
139 if node1 is None:
140 added.append(fn)
140 added.append(fn)
141 elif node2 is None:
141 elif node2 is None:
142 removed.append(fn)
142 removed.append(fn)
143 elif node2 != _newnode:
143 elif node2 != _newnode:
144 # The file was not a new file in mf2, so an entry
144 # The file was not a new file in mf2, so an entry
145 # from diff is really a difference.
145 # from diff is really a difference.
146 modified.append(fn)
146 modified.append(fn)
147 elif flag1 != flag2:
148 modified.append(fn)
147 elif self[fn].cmp(other[fn]):
149 elif self[fn].cmp(other[fn]):
148 # node2 was newnode, but the working file doesn't
150 # node2 was newnode, but the working file doesn't
149 # match the one in mf1.
151 # match the one in mf1.
150 modified.append(fn)
152 modified.append(fn)
151 else:
153 else:
152 clean.append(fn)
154 clean.append(fn)
153
155
154 if removed:
156 if removed:
155 # need to filter files if they are already reported as removed
157 # need to filter files if they are already reported as removed
156 unknown = [fn for fn in unknown if fn not in mf1]
158 unknown = [fn for fn in unknown if fn not in mf1]
157 ignored = [fn for fn in ignored if fn not in mf1]
159 ignored = [fn for fn in ignored if fn not in mf1]
158 # if they're deleted, don't report them as removed
160 # if they're deleted, don't report them as removed
159 removed = [fn for fn in removed if fn not in deletedset]
161 removed = [fn for fn in removed if fn not in deletedset]
160
162
161 return scmutil.status(modified, added, removed, deleted, unknown,
163 return scmutil.status(modified, added, removed, deleted, unknown,
162 ignored, clean)
164 ignored, clean)
163
165
164 @propertycache
166 @propertycache
165 def substate(self):
167 def substate(self):
166 return subrepo.state(self, self._repo.ui)
168 return subrepo.state(self, self._repo.ui)
167
169
168 def subrev(self, subpath):
170 def subrev(self, subpath):
169 return self.substate[subpath][1]
171 return self.substate[subpath][1]
170
172
171 def rev(self):
173 def rev(self):
172 return self._rev
174 return self._rev
173 def node(self):
175 def node(self):
174 return self._node
176 return self._node
175 def hex(self):
177 def hex(self):
176 return hex(self.node())
178 return hex(self.node())
177 def manifest(self):
179 def manifest(self):
178 return self._manifest
180 return self._manifest
179 def repo(self):
181 def repo(self):
180 return self._repo
182 return self._repo
181 def phasestr(self):
183 def phasestr(self):
182 return phases.phasenames[self.phase()]
184 return phases.phasenames[self.phase()]
183 def mutable(self):
185 def mutable(self):
184 return self.phase() > phases.public
186 return self.phase() > phases.public
185
187
186 def getfileset(self, expr):
188 def getfileset(self, expr):
187 return fileset.getfileset(self, expr)
189 return fileset.getfileset(self, expr)
188
190
189 def obsolete(self):
191 def obsolete(self):
190 """True if the changeset is obsolete"""
192 """True if the changeset is obsolete"""
191 return self.rev() in obsmod.getrevs(self._repo, 'obsolete')
193 return self.rev() in obsmod.getrevs(self._repo, 'obsolete')
192
194
193 def extinct(self):
195 def extinct(self):
194 """True if the changeset is extinct"""
196 """True if the changeset is extinct"""
195 return self.rev() in obsmod.getrevs(self._repo, 'extinct')
197 return self.rev() in obsmod.getrevs(self._repo, 'extinct')
196
198
197 def unstable(self):
199 def unstable(self):
198 """True if the changeset is not obsolete but it's ancestor are"""
200 """True if the changeset is not obsolete but it's ancestor are"""
199 return self.rev() in obsmod.getrevs(self._repo, 'unstable')
201 return self.rev() in obsmod.getrevs(self._repo, 'unstable')
200
202
201 def bumped(self):
203 def bumped(self):
202 """True if the changeset try to be a successor of a public changeset
204 """True if the changeset try to be a successor of a public changeset
203
205
204 Only non-public and non-obsolete changesets may be bumped.
206 Only non-public and non-obsolete changesets may be bumped.
205 """
207 """
206 return self.rev() in obsmod.getrevs(self._repo, 'bumped')
208 return self.rev() in obsmod.getrevs(self._repo, 'bumped')
207
209
208 def divergent(self):
210 def divergent(self):
209 """Is a successors of a changeset with multiple possible successors set
211 """Is a successors of a changeset with multiple possible successors set
210
212
211 Only non-public and non-obsolete changesets may be divergent.
213 Only non-public and non-obsolete changesets may be divergent.
212 """
214 """
213 return self.rev() in obsmod.getrevs(self._repo, 'divergent')
215 return self.rev() in obsmod.getrevs(self._repo, 'divergent')
214
216
215 def troubled(self):
217 def troubled(self):
216 """True if the changeset is either unstable, bumped or divergent"""
218 """True if the changeset is either unstable, bumped or divergent"""
217 return self.unstable() or self.bumped() or self.divergent()
219 return self.unstable() or self.bumped() or self.divergent()
218
220
219 def troubles(self):
221 def troubles(self):
220 """return the list of troubles affecting this changesets.
222 """return the list of troubles affecting this changesets.
221
223
222 Troubles are returned as strings. possible values are:
224 Troubles are returned as strings. possible values are:
223 - unstable,
225 - unstable,
224 - bumped,
226 - bumped,
225 - divergent.
227 - divergent.
226 """
228 """
227 troubles = []
229 troubles = []
228 if self.unstable():
230 if self.unstable():
229 troubles.append('unstable')
231 troubles.append('unstable')
230 if self.bumped():
232 if self.bumped():
231 troubles.append('bumped')
233 troubles.append('bumped')
232 if self.divergent():
234 if self.divergent():
233 troubles.append('divergent')
235 troubles.append('divergent')
234 return troubles
236 return troubles
235
237
236 def parents(self):
238 def parents(self):
237 """return contexts for each parent changeset"""
239 """return contexts for each parent changeset"""
238 return self._parents
240 return self._parents
239
241
240 def p1(self):
242 def p1(self):
241 return self._parents[0]
243 return self._parents[0]
242
244
243 def p2(self):
245 def p2(self):
244 parents = self._parents
246 parents = self._parents
245 if len(parents) == 2:
247 if len(parents) == 2:
246 return parents[1]
248 return parents[1]
247 return changectx(self._repo, nullrev)
249 return changectx(self._repo, nullrev)
248
250
249 def _fileinfo(self, path):
251 def _fileinfo(self, path):
250 if '_manifest' in self.__dict__:
252 if '_manifest' in self.__dict__:
251 try:
253 try:
252 return self._manifest[path], self._manifest.flags(path)
254 return self._manifest[path], self._manifest.flags(path)
253 except KeyError:
255 except KeyError:
254 raise error.ManifestLookupError(self._node, path,
256 raise error.ManifestLookupError(self._node, path,
255 _('not found in manifest'))
257 _('not found in manifest'))
256 if '_manifestdelta' in self.__dict__ or path in self.files():
258 if '_manifestdelta' in self.__dict__ or path in self.files():
257 if path in self._manifestdelta:
259 if path in self._manifestdelta:
258 return (self._manifestdelta[path],
260 return (self._manifestdelta[path],
259 self._manifestdelta.flags(path))
261 self._manifestdelta.flags(path))
260 node, flag = self._repo.manifest.find(self._changeset[0], path)
262 node, flag = self._repo.manifest.find(self._changeset[0], path)
261 if not node:
263 if not node:
262 raise error.ManifestLookupError(self._node, path,
264 raise error.ManifestLookupError(self._node, path,
263 _('not found in manifest'))
265 _('not found in manifest'))
264
266
265 return node, flag
267 return node, flag
266
268
267 def filenode(self, path):
269 def filenode(self, path):
268 return self._fileinfo(path)[0]
270 return self._fileinfo(path)[0]
269
271
270 def flags(self, path):
272 def flags(self, path):
271 try:
273 try:
272 return self._fileinfo(path)[1]
274 return self._fileinfo(path)[1]
273 except error.LookupError:
275 except error.LookupError:
274 return ''
276 return ''
275
277
276 def sub(self, path):
278 def sub(self, path):
277 '''return a subrepo for the stored revision of path, never wdir()'''
279 '''return a subrepo for the stored revision of path, never wdir()'''
278 return subrepo.subrepo(self, path)
280 return subrepo.subrepo(self, path)
279
281
280 def nullsub(self, path, pctx):
282 def nullsub(self, path, pctx):
281 return subrepo.nullsubrepo(self, path, pctx)
283 return subrepo.nullsubrepo(self, path, pctx)
282
284
283 def workingsub(self, path):
285 def workingsub(self, path):
284 '''return a subrepo for the stored revision, or wdir if this is a wdir
286 '''return a subrepo for the stored revision, or wdir if this is a wdir
285 context.
287 context.
286 '''
288 '''
287 return subrepo.subrepo(self, path, allowwdir=True)
289 return subrepo.subrepo(self, path, allowwdir=True)
288
290
289 def match(self, pats=[], include=None, exclude=None, default='glob',
291 def match(self, pats=[], include=None, exclude=None, default='glob',
290 listsubrepos=False, badfn=None):
292 listsubrepos=False, badfn=None):
291 r = self._repo
293 r = self._repo
292 return matchmod.match(r.root, r.getcwd(), pats,
294 return matchmod.match(r.root, r.getcwd(), pats,
293 include, exclude, default,
295 include, exclude, default,
294 auditor=r.nofsauditor, ctx=self,
296 auditor=r.nofsauditor, ctx=self,
295 listsubrepos=listsubrepos, badfn=badfn)
297 listsubrepos=listsubrepos, badfn=badfn)
296
298
297 def diff(self, ctx2=None, match=None, **opts):
299 def diff(self, ctx2=None, match=None, **opts):
298 """Returns a diff generator for the given contexts and matcher"""
300 """Returns a diff generator for the given contexts and matcher"""
299 if ctx2 is None:
301 if ctx2 is None:
300 ctx2 = self.p1()
302 ctx2 = self.p1()
301 if ctx2 is not None:
303 if ctx2 is not None:
302 ctx2 = self._repo[ctx2]
304 ctx2 = self._repo[ctx2]
303 diffopts = patch.diffopts(self._repo.ui, opts)
305 diffopts = patch.diffopts(self._repo.ui, opts)
304 return patch.diff(self._repo, ctx2, self, match=match, opts=diffopts)
306 return patch.diff(self._repo, ctx2, self, match=match, opts=diffopts)
305
307
306 def dirs(self):
308 def dirs(self):
307 return self._manifest.dirs()
309 return self._manifest.dirs()
308
310
309 def hasdir(self, dir):
311 def hasdir(self, dir):
310 return self._manifest.hasdir(dir)
312 return self._manifest.hasdir(dir)
311
313
312 def dirty(self, missing=False, merge=True, branch=True):
314 def dirty(self, missing=False, merge=True, branch=True):
313 return False
315 return False
314
316
315 def status(self, other=None, match=None, listignored=False,
317 def status(self, other=None, match=None, listignored=False,
316 listclean=False, listunknown=False, listsubrepos=False):
318 listclean=False, listunknown=False, listsubrepos=False):
317 """return status of files between two nodes or node and working
319 """return status of files between two nodes or node and working
318 directory.
320 directory.
319
321
320 If other is None, compare this node with working directory.
322 If other is None, compare this node with working directory.
321
323
322 returns (modified, added, removed, deleted, unknown, ignored, clean)
324 returns (modified, added, removed, deleted, unknown, ignored, clean)
323 """
325 """
324
326
325 ctx1 = self
327 ctx1 = self
326 ctx2 = self._repo[other]
328 ctx2 = self._repo[other]
327
329
328 # This next code block is, admittedly, fragile logic that tests for
330 # 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
331 # 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
332 # the fast (and common) code path of comparing the working directory
331 # with its first parent.
333 # with its first parent.
332 #
334 #
333 # What we're aiming for here is the ability to call:
335 # What we're aiming for here is the ability to call:
334 #
336 #
335 # workingctx.status(parentctx)
337 # workingctx.status(parentctx)
336 #
338 #
337 # If we always built the manifest for each context and compared those,
339 # 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
340 # then we'd be done. But the special case of the above call means we
339 # just copy the manifest of the parent.
341 # just copy the manifest of the parent.
340 reversed = False
342 reversed = False
341 if (not isinstance(ctx1, changectx)
343 if (not isinstance(ctx1, changectx)
342 and isinstance(ctx2, changectx)):
344 and isinstance(ctx2, changectx)):
343 reversed = True
345 reversed = True
344 ctx1, ctx2 = ctx2, ctx1
346 ctx1, ctx2 = ctx2, ctx1
345
347
346 match = ctx2._matchstatus(ctx1, match)
348 match = ctx2._matchstatus(ctx1, match)
347 r = scmutil.status([], [], [], [], [], [], [])
349 r = scmutil.status([], [], [], [], [], [], [])
348 r = ctx2._buildstatus(ctx1, r, match, listignored, listclean,
350 r = ctx2._buildstatus(ctx1, r, match, listignored, listclean,
349 listunknown)
351 listunknown)
350
352
351 if reversed:
353 if reversed:
352 # Reverse added and removed. Clear deleted, unknown and ignored as
354 # Reverse added and removed. Clear deleted, unknown and ignored as
353 # these make no sense to reverse.
355 # these make no sense to reverse.
354 r = scmutil.status(r.modified, r.removed, r.added, [], [], [],
356 r = scmutil.status(r.modified, r.removed, r.added, [], [], [],
355 r.clean)
357 r.clean)
356
358
357 if listsubrepos:
359 if listsubrepos:
358 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
360 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
359 try:
361 try:
360 rev2 = ctx2.subrev(subpath)
362 rev2 = ctx2.subrev(subpath)
361 except KeyError:
363 except KeyError:
362 # A subrepo that existed in node1 was deleted between
364 # A subrepo that existed in node1 was deleted between
363 # node1 and node2 (inclusive). Thus, ctx2's substate
365 # node1 and node2 (inclusive). Thus, ctx2's substate
364 # won't contain that subpath. The best we can do ignore it.
366 # won't contain that subpath. The best we can do ignore it.
365 rev2 = None
367 rev2 = None
366 submatch = matchmod.narrowmatcher(subpath, match)
368 submatch = matchmod.narrowmatcher(subpath, match)
367 s = sub.status(rev2, match=submatch, ignored=listignored,
369 s = sub.status(rev2, match=submatch, ignored=listignored,
368 clean=listclean, unknown=listunknown,
370 clean=listclean, unknown=listunknown,
369 listsubrepos=True)
371 listsubrepos=True)
370 for rfiles, sfiles in zip(r, s):
372 for rfiles, sfiles in zip(r, s):
371 rfiles.extend("%s/%s" % (subpath, f) for f in sfiles)
373 rfiles.extend("%s/%s" % (subpath, f) for f in sfiles)
372
374
373 for l in r:
375 for l in r:
374 l.sort()
376 l.sort()
375
377
376 return r
378 return r
377
379
378
380
379 def makememctx(repo, parents, text, user, date, branch, files, store,
381 def makememctx(repo, parents, text, user, date, branch, files, store,
380 editor=None, extra=None):
382 editor=None, extra=None):
381 def getfilectx(repo, memctx, path):
383 def getfilectx(repo, memctx, path):
382 data, mode, copied = store.getfile(path)
384 data, mode, copied = store.getfile(path)
383 if data is None:
385 if data is None:
384 return None
386 return None
385 islink, isexec = mode
387 islink, isexec = mode
386 return memfilectx(repo, path, data, islink=islink, isexec=isexec,
388 return memfilectx(repo, path, data, islink=islink, isexec=isexec,
387 copied=copied, memctx=memctx)
389 copied=copied, memctx=memctx)
388 if extra is None:
390 if extra is None:
389 extra = {}
391 extra = {}
390 if branch:
392 if branch:
391 extra['branch'] = encoding.fromlocal(branch)
393 extra['branch'] = encoding.fromlocal(branch)
392 ctx = memctx(repo, parents, text, files, getfilectx, user,
394 ctx = memctx(repo, parents, text, files, getfilectx, user,
393 date, extra, editor)
395 date, extra, editor)
394 return ctx
396 return ctx
395
397
396 class changectx(basectx):
398 class changectx(basectx):
397 """A changecontext object makes access to data related to a particular
399 """A changecontext object makes access to data related to a particular
398 changeset convenient. It represents a read-only context already present in
400 changeset convenient. It represents a read-only context already present in
399 the repo."""
401 the repo."""
400 def __init__(self, repo, changeid=''):
402 def __init__(self, repo, changeid=''):
401 """changeid is a revision number, node, or tag"""
403 """changeid is a revision number, node, or tag"""
402
404
403 # since basectx.__new__ already took care of copying the object, we
405 # since basectx.__new__ already took care of copying the object, we
404 # don't need to do anything in __init__, so we just exit here
406 # don't need to do anything in __init__, so we just exit here
405 if isinstance(changeid, basectx):
407 if isinstance(changeid, basectx):
406 return
408 return
407
409
408 if changeid == '':
410 if changeid == '':
409 changeid = '.'
411 changeid = '.'
410 self._repo = repo
412 self._repo = repo
411
413
412 try:
414 try:
413 if isinstance(changeid, int):
415 if isinstance(changeid, int):
414 self._node = repo.changelog.node(changeid)
416 self._node = repo.changelog.node(changeid)
415 self._rev = changeid
417 self._rev = changeid
416 return
418 return
417 if isinstance(changeid, long):
419 if isinstance(changeid, long):
418 changeid = str(changeid)
420 changeid = str(changeid)
419 if changeid == 'null':
421 if changeid == 'null':
420 self._node = nullid
422 self._node = nullid
421 self._rev = nullrev
423 self._rev = nullrev
422 return
424 return
423 if changeid == 'tip':
425 if changeid == 'tip':
424 self._node = repo.changelog.tip()
426 self._node = repo.changelog.tip()
425 self._rev = repo.changelog.rev(self._node)
427 self._rev = repo.changelog.rev(self._node)
426 return
428 return
427 if changeid == '.' or changeid == repo.dirstate.p1():
429 if changeid == '.' or changeid == repo.dirstate.p1():
428 # this is a hack to delay/avoid loading obsmarkers
430 # this is a hack to delay/avoid loading obsmarkers
429 # when we know that '.' won't be hidden
431 # when we know that '.' won't be hidden
430 self._node = repo.dirstate.p1()
432 self._node = repo.dirstate.p1()
431 self._rev = repo.unfiltered().changelog.rev(self._node)
433 self._rev = repo.unfiltered().changelog.rev(self._node)
432 return
434 return
433 if len(changeid) == 20:
435 if len(changeid) == 20:
434 try:
436 try:
435 self._node = changeid
437 self._node = changeid
436 self._rev = repo.changelog.rev(changeid)
438 self._rev = repo.changelog.rev(changeid)
437 return
439 return
438 except error.FilteredRepoLookupError:
440 except error.FilteredRepoLookupError:
439 raise
441 raise
440 except LookupError:
442 except LookupError:
441 pass
443 pass
442
444
443 try:
445 try:
444 r = int(changeid)
446 r = int(changeid)
445 if str(r) != changeid:
447 if str(r) != changeid:
446 raise ValueError
448 raise ValueError
447 l = len(repo.changelog)
449 l = len(repo.changelog)
448 if r < 0:
450 if r < 0:
449 r += l
451 r += l
450 if r < 0 or r >= l:
452 if r < 0 or r >= l:
451 raise ValueError
453 raise ValueError
452 self._rev = r
454 self._rev = r
453 self._node = repo.changelog.node(r)
455 self._node = repo.changelog.node(r)
454 return
456 return
455 except error.FilteredIndexError:
457 except error.FilteredIndexError:
456 raise
458 raise
457 except (ValueError, OverflowError, IndexError):
459 except (ValueError, OverflowError, IndexError):
458 pass
460 pass
459
461
460 if len(changeid) == 40:
462 if len(changeid) == 40:
461 try:
463 try:
462 self._node = bin(changeid)
464 self._node = bin(changeid)
463 self._rev = repo.changelog.rev(self._node)
465 self._rev = repo.changelog.rev(self._node)
464 return
466 return
465 except error.FilteredLookupError:
467 except error.FilteredLookupError:
466 raise
468 raise
467 except (TypeError, LookupError):
469 except (TypeError, LookupError):
468 pass
470 pass
469
471
470 # lookup bookmarks through the name interface
472 # lookup bookmarks through the name interface
471 try:
473 try:
472 self._node = repo.names.singlenode(repo, changeid)
474 self._node = repo.names.singlenode(repo, changeid)
473 self._rev = repo.changelog.rev(self._node)
475 self._rev = repo.changelog.rev(self._node)
474 return
476 return
475 except KeyError:
477 except KeyError:
476 pass
478 pass
477 except error.FilteredRepoLookupError:
479 except error.FilteredRepoLookupError:
478 raise
480 raise
479 except error.RepoLookupError:
481 except error.RepoLookupError:
480 pass
482 pass
481
483
482 self._node = repo.unfiltered().changelog._partialmatch(changeid)
484 self._node = repo.unfiltered().changelog._partialmatch(changeid)
483 if self._node is not None:
485 if self._node is not None:
484 self._rev = repo.changelog.rev(self._node)
486 self._rev = repo.changelog.rev(self._node)
485 return
487 return
486
488
487 # lookup failed
489 # lookup failed
488 # check if it might have come from damaged dirstate
490 # check if it might have come from damaged dirstate
489 #
491 #
490 # XXX we could avoid the unfiltered if we had a recognizable
492 # XXX we could avoid the unfiltered if we had a recognizable
491 # exception for filtered changeset access
493 # exception for filtered changeset access
492 if changeid in repo.unfiltered().dirstate.parents():
494 if changeid in repo.unfiltered().dirstate.parents():
493 msg = _("working directory has unknown parent '%s'!")
495 msg = _("working directory has unknown parent '%s'!")
494 raise error.Abort(msg % short(changeid))
496 raise error.Abort(msg % short(changeid))
495 try:
497 try:
496 if len(changeid) == 20 and nonascii(changeid):
498 if len(changeid) == 20 and nonascii(changeid):
497 changeid = hex(changeid)
499 changeid = hex(changeid)
498 except TypeError:
500 except TypeError:
499 pass
501 pass
500 except (error.FilteredIndexError, error.FilteredLookupError,
502 except (error.FilteredIndexError, error.FilteredLookupError,
501 error.FilteredRepoLookupError):
503 error.FilteredRepoLookupError):
502 if repo.filtername.startswith('visible'):
504 if repo.filtername.startswith('visible'):
503 msg = _("hidden revision '%s'") % changeid
505 msg = _("hidden revision '%s'") % changeid
504 hint = _('use --hidden to access hidden revisions')
506 hint = _('use --hidden to access hidden revisions')
505 raise error.FilteredRepoLookupError(msg, hint=hint)
507 raise error.FilteredRepoLookupError(msg, hint=hint)
506 msg = _("filtered revision '%s' (not in '%s' subset)")
508 msg = _("filtered revision '%s' (not in '%s' subset)")
507 msg %= (changeid, repo.filtername)
509 msg %= (changeid, repo.filtername)
508 raise error.FilteredRepoLookupError(msg)
510 raise error.FilteredRepoLookupError(msg)
509 except IndexError:
511 except IndexError:
510 pass
512 pass
511 raise error.RepoLookupError(
513 raise error.RepoLookupError(
512 _("unknown revision '%s'") % changeid)
514 _("unknown revision '%s'") % changeid)
513
515
514 def __hash__(self):
516 def __hash__(self):
515 try:
517 try:
516 return hash(self._rev)
518 return hash(self._rev)
517 except AttributeError:
519 except AttributeError:
518 return id(self)
520 return id(self)
519
521
520 def __nonzero__(self):
522 def __nonzero__(self):
521 return self._rev != nullrev
523 return self._rev != nullrev
522
524
523 @propertycache
525 @propertycache
524 def _changeset(self):
526 def _changeset(self):
525 return self._repo.changelog.read(self.rev())
527 return self._repo.changelog.read(self.rev())
526
528
527 @propertycache
529 @propertycache
528 def _manifest(self):
530 def _manifest(self):
529 return self._repo.manifest.read(self._changeset[0])
531 return self._repo.manifest.read(self._changeset[0])
530
532
531 @propertycache
533 @propertycache
532 def _manifestdelta(self):
534 def _manifestdelta(self):
533 return self._repo.manifest.readdelta(self._changeset[0])
535 return self._repo.manifest.readdelta(self._changeset[0])
534
536
535 @propertycache
537 @propertycache
536 def _parents(self):
538 def _parents(self):
537 repo = self._repo
539 repo = self._repo
538 p1, p2 = repo.changelog.parentrevs(self._rev)
540 p1, p2 = repo.changelog.parentrevs(self._rev)
539 if p2 == nullrev:
541 if p2 == nullrev:
540 return [changectx(repo, p1)]
542 return [changectx(repo, p1)]
541 return [changectx(repo, p1), changectx(repo, p2)]
543 return [changectx(repo, p1), changectx(repo, p2)]
542
544
543 def changeset(self):
545 def changeset(self):
544 return self._changeset
546 return self._changeset
545 def manifestnode(self):
547 def manifestnode(self):
546 return self._changeset[0]
548 return self._changeset[0]
547
549
548 def user(self):
550 def user(self):
549 return self._changeset[1]
551 return self._changeset[1]
550 def date(self):
552 def date(self):
551 return self._changeset[2]
553 return self._changeset[2]
552 def files(self):
554 def files(self):
553 return self._changeset[3]
555 return self._changeset[3]
554 def description(self):
556 def description(self):
555 return self._changeset[4]
557 return self._changeset[4]
556 def branch(self):
558 def branch(self):
557 return encoding.tolocal(self._changeset[5].get("branch"))
559 return encoding.tolocal(self._changeset[5].get("branch"))
558 def closesbranch(self):
560 def closesbranch(self):
559 return 'close' in self._changeset[5]
561 return 'close' in self._changeset[5]
560 def extra(self):
562 def extra(self):
561 return self._changeset[5]
563 return self._changeset[5]
562 def tags(self):
564 def tags(self):
563 return self._repo.nodetags(self._node)
565 return self._repo.nodetags(self._node)
564 def bookmarks(self):
566 def bookmarks(self):
565 return self._repo.nodebookmarks(self._node)
567 return self._repo.nodebookmarks(self._node)
566 def phase(self):
568 def phase(self):
567 return self._repo._phasecache.phase(self._repo, self._rev)
569 return self._repo._phasecache.phase(self._repo, self._rev)
568 def hidden(self):
570 def hidden(self):
569 return self._rev in repoview.filterrevs(self._repo, 'visible')
571 return self._rev in repoview.filterrevs(self._repo, 'visible')
570
572
571 def children(self):
573 def children(self):
572 """return contexts for each child changeset"""
574 """return contexts for each child changeset"""
573 c = self._repo.changelog.children(self._node)
575 c = self._repo.changelog.children(self._node)
574 return [changectx(self._repo, x) for x in c]
576 return [changectx(self._repo, x) for x in c]
575
577
576 def ancestors(self):
578 def ancestors(self):
577 for a in self._repo.changelog.ancestors([self._rev]):
579 for a in self._repo.changelog.ancestors([self._rev]):
578 yield changectx(self._repo, a)
580 yield changectx(self._repo, a)
579
581
580 def descendants(self):
582 def descendants(self):
581 for d in self._repo.changelog.descendants([self._rev]):
583 for d in self._repo.changelog.descendants([self._rev]):
582 yield changectx(self._repo, d)
584 yield changectx(self._repo, d)
583
585
584 def filectx(self, path, fileid=None, filelog=None):
586 def filectx(self, path, fileid=None, filelog=None):
585 """get a file context from this changeset"""
587 """get a file context from this changeset"""
586 if fileid is None:
588 if fileid is None:
587 fileid = self.filenode(path)
589 fileid = self.filenode(path)
588 return filectx(self._repo, path, fileid=fileid,
590 return filectx(self._repo, path, fileid=fileid,
589 changectx=self, filelog=filelog)
591 changectx=self, filelog=filelog)
590
592
591 def ancestor(self, c2, warn=False):
593 def ancestor(self, c2, warn=False):
592 """return the "best" ancestor context of self and c2
594 """return the "best" ancestor context of self and c2
593
595
594 If there are multiple candidates, it will show a message and check
596 If there are multiple candidates, it will show a message and check
595 merge.preferancestor configuration before falling back to the
597 merge.preferancestor configuration before falling back to the
596 revlog ancestor."""
598 revlog ancestor."""
597 # deal with workingctxs
599 # deal with workingctxs
598 n2 = c2._node
600 n2 = c2._node
599 if n2 is None:
601 if n2 is None:
600 n2 = c2._parents[0]._node
602 n2 = c2._parents[0]._node
601 cahs = self._repo.changelog.commonancestorsheads(self._node, n2)
603 cahs = self._repo.changelog.commonancestorsheads(self._node, n2)
602 if not cahs:
604 if not cahs:
603 anc = nullid
605 anc = nullid
604 elif len(cahs) == 1:
606 elif len(cahs) == 1:
605 anc = cahs[0]
607 anc = cahs[0]
606 else:
608 else:
607 # experimental config: merge.preferancestor
609 # experimental config: merge.preferancestor
608 for r in self._repo.ui.configlist('merge', 'preferancestor', ['*']):
610 for r in self._repo.ui.configlist('merge', 'preferancestor', ['*']):
609 try:
611 try:
610 ctx = changectx(self._repo, r)
612 ctx = changectx(self._repo, r)
611 except error.RepoLookupError:
613 except error.RepoLookupError:
612 continue
614 continue
613 anc = ctx.node()
615 anc = ctx.node()
614 if anc in cahs:
616 if anc in cahs:
615 break
617 break
616 else:
618 else:
617 anc = self._repo.changelog.ancestor(self._node, n2)
619 anc = self._repo.changelog.ancestor(self._node, n2)
618 if warn:
620 if warn:
619 self._repo.ui.status(
621 self._repo.ui.status(
620 (_("note: using %s as ancestor of %s and %s\n") %
622 (_("note: using %s as ancestor of %s and %s\n") %
621 (short(anc), short(self._node), short(n2))) +
623 (short(anc), short(self._node), short(n2))) +
622 ''.join(_(" alternatively, use --config "
624 ''.join(_(" alternatively, use --config "
623 "merge.preferancestor=%s\n") %
625 "merge.preferancestor=%s\n") %
624 short(n) for n in sorted(cahs) if n != anc))
626 short(n) for n in sorted(cahs) if n != anc))
625 return changectx(self._repo, anc)
627 return changectx(self._repo, anc)
626
628
627 def descendant(self, other):
629 def descendant(self, other):
628 """True if other is descendant of this changeset"""
630 """True if other is descendant of this changeset"""
629 return self._repo.changelog.descendant(self._rev, other._rev)
631 return self._repo.changelog.descendant(self._rev, other._rev)
630
632
631 def walk(self, match):
633 def walk(self, match):
632 '''Generates matching file names.'''
634 '''Generates matching file names.'''
633
635
634 # Wrap match.bad method to have message with nodeid
636 # Wrap match.bad method to have message with nodeid
635 def bad(fn, msg):
637 def bad(fn, msg):
636 # The manifest doesn't know about subrepos, so don't complain about
638 # The manifest doesn't know about subrepos, so don't complain about
637 # paths into valid subrepos.
639 # paths into valid subrepos.
638 if any(fn == s or fn.startswith(s + '/')
640 if any(fn == s or fn.startswith(s + '/')
639 for s in self.substate):
641 for s in self.substate):
640 return
642 return
641 match.bad(fn, _('no such file in rev %s') % self)
643 match.bad(fn, _('no such file in rev %s') % self)
642
644
643 m = matchmod.badmatch(match, bad)
645 m = matchmod.badmatch(match, bad)
644 return self._manifest.walk(m)
646 return self._manifest.walk(m)
645
647
646 def matches(self, match):
648 def matches(self, match):
647 return self.walk(match)
649 return self.walk(match)
648
650
649 class basefilectx(object):
651 class basefilectx(object):
650 """A filecontext object represents the common logic for its children:
652 """A filecontext object represents the common logic for its children:
651 filectx: read-only access to a filerevision that is already present
653 filectx: read-only access to a filerevision that is already present
652 in the repo,
654 in the repo,
653 workingfilectx: a filecontext that represents files from the working
655 workingfilectx: a filecontext that represents files from the working
654 directory,
656 directory,
655 memfilectx: a filecontext that represents files in-memory."""
657 memfilectx: a filecontext that represents files in-memory."""
656 def __new__(cls, repo, path, *args, **kwargs):
658 def __new__(cls, repo, path, *args, **kwargs):
657 return super(basefilectx, cls).__new__(cls)
659 return super(basefilectx, cls).__new__(cls)
658
660
659 @propertycache
661 @propertycache
660 def _filelog(self):
662 def _filelog(self):
661 return self._repo.file(self._path)
663 return self._repo.file(self._path)
662
664
663 @propertycache
665 @propertycache
664 def _changeid(self):
666 def _changeid(self):
665 if '_changeid' in self.__dict__:
667 if '_changeid' in self.__dict__:
666 return self._changeid
668 return self._changeid
667 elif '_changectx' in self.__dict__:
669 elif '_changectx' in self.__dict__:
668 return self._changectx.rev()
670 return self._changectx.rev()
669 elif '_descendantrev' in self.__dict__:
671 elif '_descendantrev' in self.__dict__:
670 # this file context was created from a revision with a known
672 # this file context was created from a revision with a known
671 # descendant, we can (lazily) correct for linkrev aliases
673 # descendant, we can (lazily) correct for linkrev aliases
672 return self._adjustlinkrev(self._path, self._filelog,
674 return self._adjustlinkrev(self._path, self._filelog,
673 self._filenode, self._descendantrev)
675 self._filenode, self._descendantrev)
674 else:
676 else:
675 return self._filelog.linkrev(self._filerev)
677 return self._filelog.linkrev(self._filerev)
676
678
677 @propertycache
679 @propertycache
678 def _filenode(self):
680 def _filenode(self):
679 if '_fileid' in self.__dict__:
681 if '_fileid' in self.__dict__:
680 return self._filelog.lookup(self._fileid)
682 return self._filelog.lookup(self._fileid)
681 else:
683 else:
682 return self._changectx.filenode(self._path)
684 return self._changectx.filenode(self._path)
683
685
684 @propertycache
686 @propertycache
685 def _filerev(self):
687 def _filerev(self):
686 return self._filelog.rev(self._filenode)
688 return self._filelog.rev(self._filenode)
687
689
688 @propertycache
690 @propertycache
689 def _repopath(self):
691 def _repopath(self):
690 return self._path
692 return self._path
691
693
692 def __nonzero__(self):
694 def __nonzero__(self):
693 try:
695 try:
694 self._filenode
696 self._filenode
695 return True
697 return True
696 except error.LookupError:
698 except error.LookupError:
697 # file is missing
699 # file is missing
698 return False
700 return False
699
701
700 def __str__(self):
702 def __str__(self):
701 return "%s@%s" % (self.path(), self._changectx)
703 return "%s@%s" % (self.path(), self._changectx)
702
704
703 def __repr__(self):
705 def __repr__(self):
704 return "<%s %s>" % (type(self).__name__, str(self))
706 return "<%s %s>" % (type(self).__name__, str(self))
705
707
706 def __hash__(self):
708 def __hash__(self):
707 try:
709 try:
708 return hash((self._path, self._filenode))
710 return hash((self._path, self._filenode))
709 except AttributeError:
711 except AttributeError:
710 return id(self)
712 return id(self)
711
713
712 def __eq__(self, other):
714 def __eq__(self, other):
713 try:
715 try:
714 return (type(self) == type(other) and self._path == other._path
716 return (type(self) == type(other) and self._path == other._path
715 and self._filenode == other._filenode)
717 and self._filenode == other._filenode)
716 except AttributeError:
718 except AttributeError:
717 return False
719 return False
718
720
719 def __ne__(self, other):
721 def __ne__(self, other):
720 return not (self == other)
722 return not (self == other)
721
723
722 def filerev(self):
724 def filerev(self):
723 return self._filerev
725 return self._filerev
724 def filenode(self):
726 def filenode(self):
725 return self._filenode
727 return self._filenode
726 def flags(self):
728 def flags(self):
727 return self._changectx.flags(self._path)
729 return self._changectx.flags(self._path)
728 def filelog(self):
730 def filelog(self):
729 return self._filelog
731 return self._filelog
730 def rev(self):
732 def rev(self):
731 return self._changeid
733 return self._changeid
732 def linkrev(self):
734 def linkrev(self):
733 return self._filelog.linkrev(self._filerev)
735 return self._filelog.linkrev(self._filerev)
734 def node(self):
736 def node(self):
735 return self._changectx.node()
737 return self._changectx.node()
736 def hex(self):
738 def hex(self):
737 return self._changectx.hex()
739 return self._changectx.hex()
738 def user(self):
740 def user(self):
739 return self._changectx.user()
741 return self._changectx.user()
740 def date(self):
742 def date(self):
741 return self._changectx.date()
743 return self._changectx.date()
742 def files(self):
744 def files(self):
743 return self._changectx.files()
745 return self._changectx.files()
744 def description(self):
746 def description(self):
745 return self._changectx.description()
747 return self._changectx.description()
746 def branch(self):
748 def branch(self):
747 return self._changectx.branch()
749 return self._changectx.branch()
748 def extra(self):
750 def extra(self):
749 return self._changectx.extra()
751 return self._changectx.extra()
750 def phase(self):
752 def phase(self):
751 return self._changectx.phase()
753 return self._changectx.phase()
752 def phasestr(self):
754 def phasestr(self):
753 return self._changectx.phasestr()
755 return self._changectx.phasestr()
754 def manifest(self):
756 def manifest(self):
755 return self._changectx.manifest()
757 return self._changectx.manifest()
756 def changectx(self):
758 def changectx(self):
757 return self._changectx
759 return self._changectx
758 def repo(self):
760 def repo(self):
759 return self._repo
761 return self._repo
760
762
761 def path(self):
763 def path(self):
762 return self._path
764 return self._path
763
765
764 def isbinary(self):
766 def isbinary(self):
765 try:
767 try:
766 return util.binary(self.data())
768 return util.binary(self.data())
767 except IOError:
769 except IOError:
768 return False
770 return False
769 def isexec(self):
771 def isexec(self):
770 return 'x' in self.flags()
772 return 'x' in self.flags()
771 def islink(self):
773 def islink(self):
772 return 'l' in self.flags()
774 return 'l' in self.flags()
773
775
774 def isabsent(self):
776 def isabsent(self):
775 """whether this filectx represents a file not in self._changectx
777 """whether this filectx represents a file not in self._changectx
776
778
777 This is mainly for merge code to detect change/delete conflicts. This is
779 This is mainly for merge code to detect change/delete conflicts. This is
778 expected to be True for all subclasses of basectx."""
780 expected to be True for all subclasses of basectx."""
779 return False
781 return False
780
782
781 _customcmp = False
783 _customcmp = False
782 def cmp(self, fctx):
784 def cmp(self, fctx):
783 """compare with other file context
785 """compare with other file context
784
786
785 returns True if different than fctx.
787 returns True if different than fctx.
786 """
788 """
787 if fctx._customcmp:
789 if fctx._customcmp:
788 return fctx.cmp(self)
790 return fctx.cmp(self)
789
791
790 if (fctx._filerev is None
792 if (fctx._filerev is None
791 and (self._repo._encodefilterpats
793 and (self._repo._encodefilterpats
792 # if file data starts with '\1\n', empty metadata block is
794 # if file data starts with '\1\n', empty metadata block is
793 # prepended, which adds 4 bytes to filelog.size().
795 # prepended, which adds 4 bytes to filelog.size().
794 or self.size() - 4 == fctx.size())
796 or self.size() - 4 == fctx.size())
795 or self.size() == fctx.size()):
797 or self.size() == fctx.size()):
796 return self._filelog.cmp(self._filenode, fctx.data())
798 return self._filelog.cmp(self._filenode, fctx.data())
797
799
798 return True
800 return True
799
801
800 def _adjustlinkrev(self, path, filelog, fnode, srcrev, inclusive=False):
802 def _adjustlinkrev(self, path, filelog, fnode, srcrev, inclusive=False):
801 """return the first ancestor of <srcrev> introducing <fnode>
803 """return the first ancestor of <srcrev> introducing <fnode>
802
804
803 If the linkrev of the file revision does not point to an ancestor of
805 If the linkrev of the file revision does not point to an ancestor of
804 srcrev, we'll walk down the ancestors until we find one introducing
806 srcrev, we'll walk down the ancestors until we find one introducing
805 this file revision.
807 this file revision.
806
808
807 :repo: a localrepository object (used to access changelog and manifest)
809 :repo: a localrepository object (used to access changelog and manifest)
808 :path: the file path
810 :path: the file path
809 :fnode: the nodeid of the file revision
811 :fnode: the nodeid of the file revision
810 :filelog: the filelog of this path
812 :filelog: the filelog of this path
811 :srcrev: the changeset revision we search ancestors from
813 :srcrev: the changeset revision we search ancestors from
812 :inclusive: if true, the src revision will also be checked
814 :inclusive: if true, the src revision will also be checked
813 """
815 """
814 repo = self._repo
816 repo = self._repo
815 cl = repo.unfiltered().changelog
817 cl = repo.unfiltered().changelog
816 ma = repo.manifest
818 ma = repo.manifest
817 # fetch the linkrev
819 # fetch the linkrev
818 fr = filelog.rev(fnode)
820 fr = filelog.rev(fnode)
819 lkr = filelog.linkrev(fr)
821 lkr = filelog.linkrev(fr)
820 # hack to reuse ancestor computation when searching for renames
822 # hack to reuse ancestor computation when searching for renames
821 memberanc = getattr(self, '_ancestrycontext', None)
823 memberanc = getattr(self, '_ancestrycontext', None)
822 iteranc = None
824 iteranc = None
823 if srcrev is None:
825 if srcrev is None:
824 # wctx case, used by workingfilectx during mergecopy
826 # wctx case, used by workingfilectx during mergecopy
825 revs = [p.rev() for p in self._repo[None].parents()]
827 revs = [p.rev() for p in self._repo[None].parents()]
826 inclusive = True # we skipped the real (revless) source
828 inclusive = True # we skipped the real (revless) source
827 else:
829 else:
828 revs = [srcrev]
830 revs = [srcrev]
829 if memberanc is None:
831 if memberanc is None:
830 memberanc = iteranc = cl.ancestors(revs, lkr,
832 memberanc = iteranc = cl.ancestors(revs, lkr,
831 inclusive=inclusive)
833 inclusive=inclusive)
832 # check if this linkrev is an ancestor of srcrev
834 # check if this linkrev is an ancestor of srcrev
833 if lkr not in memberanc:
835 if lkr not in memberanc:
834 if iteranc is None:
836 if iteranc is None:
835 iteranc = cl.ancestors(revs, lkr, inclusive=inclusive)
837 iteranc = cl.ancestors(revs, lkr, inclusive=inclusive)
836 for a in iteranc:
838 for a in iteranc:
837 ac = cl.read(a) # get changeset data (we avoid object creation)
839 ac = cl.read(a) # get changeset data (we avoid object creation)
838 if path in ac[3]: # checking the 'files' field.
840 if path in ac[3]: # checking the 'files' field.
839 # The file has been touched, check if the content is
841 # The file has been touched, check if the content is
840 # similar to the one we search for.
842 # similar to the one we search for.
841 if fnode == ma.readfast(ac[0]).get(path):
843 if fnode == ma.readfast(ac[0]).get(path):
842 return a
844 return a
843 # In theory, we should never get out of that loop without a result.
845 # In theory, we should never get out of that loop without a result.
844 # But if manifest uses a buggy file revision (not children of the
846 # But if manifest uses a buggy file revision (not children of the
845 # one it replaces) we could. Such a buggy situation will likely
847 # one it replaces) we could. Such a buggy situation will likely
846 # result is crash somewhere else at to some point.
848 # result is crash somewhere else at to some point.
847 return lkr
849 return lkr
848
850
849 def introrev(self):
851 def introrev(self):
850 """return the rev of the changeset which introduced this file revision
852 """return the rev of the changeset which introduced this file revision
851
853
852 This method is different from linkrev because it take into account the
854 This method is different from linkrev because it take into account the
853 changeset the filectx was created from. It ensures the returned
855 changeset the filectx was created from. It ensures the returned
854 revision is one of its ancestors. This prevents bugs from
856 revision is one of its ancestors. This prevents bugs from
855 'linkrev-shadowing' when a file revision is used by multiple
857 'linkrev-shadowing' when a file revision is used by multiple
856 changesets.
858 changesets.
857 """
859 """
858 lkr = self.linkrev()
860 lkr = self.linkrev()
859 attrs = vars(self)
861 attrs = vars(self)
860 noctx = not ('_changeid' in attrs or '_changectx' in attrs)
862 noctx = not ('_changeid' in attrs or '_changectx' in attrs)
861 if noctx or self.rev() == lkr:
863 if noctx or self.rev() == lkr:
862 return self.linkrev()
864 return self.linkrev()
863 return self._adjustlinkrev(self._path, self._filelog, self._filenode,
865 return self._adjustlinkrev(self._path, self._filelog, self._filenode,
864 self.rev(), inclusive=True)
866 self.rev(), inclusive=True)
865
867
866 def _parentfilectx(self, path, fileid, filelog):
868 def _parentfilectx(self, path, fileid, filelog):
867 """create parent filectx keeping ancestry info for _adjustlinkrev()"""
869 """create parent filectx keeping ancestry info for _adjustlinkrev()"""
868 fctx = filectx(self._repo, path, fileid=fileid, filelog=filelog)
870 fctx = filectx(self._repo, path, fileid=fileid, filelog=filelog)
869 if '_changeid' in vars(self) or '_changectx' in vars(self):
871 if '_changeid' in vars(self) or '_changectx' in vars(self):
870 # If self is associated with a changeset (probably explicitly
872 # If self is associated with a changeset (probably explicitly
871 # fed), ensure the created filectx is associated with a
873 # fed), ensure the created filectx is associated with a
872 # changeset that is an ancestor of self.changectx.
874 # changeset that is an ancestor of self.changectx.
873 # This lets us later use _adjustlinkrev to get a correct link.
875 # This lets us later use _adjustlinkrev to get a correct link.
874 fctx._descendantrev = self.rev()
876 fctx._descendantrev = self.rev()
875 fctx._ancestrycontext = getattr(self, '_ancestrycontext', None)
877 fctx._ancestrycontext = getattr(self, '_ancestrycontext', None)
876 elif '_descendantrev' in vars(self):
878 elif '_descendantrev' in vars(self):
877 # Otherwise propagate _descendantrev if we have one associated.
879 # Otherwise propagate _descendantrev if we have one associated.
878 fctx._descendantrev = self._descendantrev
880 fctx._descendantrev = self._descendantrev
879 fctx._ancestrycontext = getattr(self, '_ancestrycontext', None)
881 fctx._ancestrycontext = getattr(self, '_ancestrycontext', None)
880 return fctx
882 return fctx
881
883
882 def parents(self):
884 def parents(self):
883 _path = self._path
885 _path = self._path
884 fl = self._filelog
886 fl = self._filelog
885 parents = self._filelog.parents(self._filenode)
887 parents = self._filelog.parents(self._filenode)
886 pl = [(_path, node, fl) for node in parents if node != nullid]
888 pl = [(_path, node, fl) for node in parents if node != nullid]
887
889
888 r = fl.renamed(self._filenode)
890 r = fl.renamed(self._filenode)
889 if r:
891 if r:
890 # - In the simple rename case, both parent are nullid, pl is empty.
892 # - In the simple rename case, both parent are nullid, pl is empty.
891 # - In case of merge, only one of the parent is null id and should
893 # - In case of merge, only one of the parent is null id and should
892 # be replaced with the rename information. This parent is -always-
894 # be replaced with the rename information. This parent is -always-
893 # the first one.
895 # the first one.
894 #
896 #
895 # As null id have always been filtered out in the previous list
897 # As null id have always been filtered out in the previous list
896 # comprehension, inserting to 0 will always result in "replacing
898 # comprehension, inserting to 0 will always result in "replacing
897 # first nullid parent with rename information.
899 # first nullid parent with rename information.
898 pl.insert(0, (r[0], r[1], self._repo.file(r[0])))
900 pl.insert(0, (r[0], r[1], self._repo.file(r[0])))
899
901
900 return [self._parentfilectx(path, fnode, l) for path, fnode, l in pl]
902 return [self._parentfilectx(path, fnode, l) for path, fnode, l in pl]
901
903
902 def p1(self):
904 def p1(self):
903 return self.parents()[0]
905 return self.parents()[0]
904
906
905 def p2(self):
907 def p2(self):
906 p = self.parents()
908 p = self.parents()
907 if len(p) == 2:
909 if len(p) == 2:
908 return p[1]
910 return p[1]
909 return filectx(self._repo, self._path, fileid=-1, filelog=self._filelog)
911 return filectx(self._repo, self._path, fileid=-1, filelog=self._filelog)
910
912
911 def annotate(self, follow=False, linenumber=None, diffopts=None):
913 def annotate(self, follow=False, linenumber=None, diffopts=None):
912 '''returns a list of tuples of (ctx, line) for each line
914 '''returns a list of tuples of (ctx, line) for each line
913 in the file, where ctx is the filectx of the node where
915 in the file, where ctx is the filectx of the node where
914 that line was last changed.
916 that line was last changed.
915 This returns tuples of ((ctx, linenumber), line) for each line,
917 This returns tuples of ((ctx, linenumber), line) for each line,
916 if "linenumber" parameter is NOT "None".
918 if "linenumber" parameter is NOT "None".
917 In such tuples, linenumber means one at the first appearance
919 In such tuples, linenumber means one at the first appearance
918 in the managed file.
920 in the managed file.
919 To reduce annotation cost,
921 To reduce annotation cost,
920 this returns fixed value(False is used) as linenumber,
922 this returns fixed value(False is used) as linenumber,
921 if "linenumber" parameter is "False".'''
923 if "linenumber" parameter is "False".'''
922
924
923 if linenumber is None:
925 if linenumber is None:
924 def decorate(text, rev):
926 def decorate(text, rev):
925 return ([rev] * len(text.splitlines()), text)
927 return ([rev] * len(text.splitlines()), text)
926 elif linenumber:
928 elif linenumber:
927 def decorate(text, rev):
929 def decorate(text, rev):
928 size = len(text.splitlines())
930 size = len(text.splitlines())
929 return ([(rev, i) for i in xrange(1, size + 1)], text)
931 return ([(rev, i) for i in xrange(1, size + 1)], text)
930 else:
932 else:
931 def decorate(text, rev):
933 def decorate(text, rev):
932 return ([(rev, False)] * len(text.splitlines()), text)
934 return ([(rev, False)] * len(text.splitlines()), text)
933
935
934 def pair(parent, child):
936 def pair(parent, child):
935 blocks = mdiff.allblocks(parent[1], child[1], opts=diffopts,
937 blocks = mdiff.allblocks(parent[1], child[1], opts=diffopts,
936 refine=True)
938 refine=True)
937 for (a1, a2, b1, b2), t in blocks:
939 for (a1, a2, b1, b2), t in blocks:
938 # Changed blocks ('!') or blocks made only of blank lines ('~')
940 # Changed blocks ('!') or blocks made only of blank lines ('~')
939 # belong to the child.
941 # belong to the child.
940 if t == '=':
942 if t == '=':
941 child[0][b1:b2] = parent[0][a1:a2]
943 child[0][b1:b2] = parent[0][a1:a2]
942 return child
944 return child
943
945
944 getlog = util.lrucachefunc(lambda x: self._repo.file(x))
946 getlog = util.lrucachefunc(lambda x: self._repo.file(x))
945
947
946 def parents(f):
948 def parents(f):
947 # Cut _descendantrev here to mitigate the penalty of lazy linkrev
949 # Cut _descendantrev here to mitigate the penalty of lazy linkrev
948 # adjustment. Otherwise, p._adjustlinkrev() would walk changelog
950 # adjustment. Otherwise, p._adjustlinkrev() would walk changelog
949 # from the topmost introrev (= srcrev) down to p.linkrev() if it
951 # from the topmost introrev (= srcrev) down to p.linkrev() if it
950 # isn't an ancestor of the srcrev.
952 # isn't an ancestor of the srcrev.
951 f._changeid
953 f._changeid
952 pl = f.parents()
954 pl = f.parents()
953
955
954 # Don't return renamed parents if we aren't following.
956 # Don't return renamed parents if we aren't following.
955 if not follow:
957 if not follow:
956 pl = [p for p in pl if p.path() == f.path()]
958 pl = [p for p in pl if p.path() == f.path()]
957
959
958 # renamed filectx won't have a filelog yet, so set it
960 # renamed filectx won't have a filelog yet, so set it
959 # from the cache to save time
961 # from the cache to save time
960 for p in pl:
962 for p in pl:
961 if not '_filelog' in p.__dict__:
963 if not '_filelog' in p.__dict__:
962 p._filelog = getlog(p.path())
964 p._filelog = getlog(p.path())
963
965
964 return pl
966 return pl
965
967
966 # use linkrev to find the first changeset where self appeared
968 # use linkrev to find the first changeset where self appeared
967 base = self
969 base = self
968 introrev = self.introrev()
970 introrev = self.introrev()
969 if self.rev() != introrev:
971 if self.rev() != introrev:
970 base = self.filectx(self.filenode(), changeid=introrev)
972 base = self.filectx(self.filenode(), changeid=introrev)
971 if getattr(base, '_ancestrycontext', None) is None:
973 if getattr(base, '_ancestrycontext', None) is None:
972 cl = self._repo.changelog
974 cl = self._repo.changelog
973 if introrev is None:
975 if introrev is None:
974 # wctx is not inclusive, but works because _ancestrycontext
976 # wctx is not inclusive, but works because _ancestrycontext
975 # is used to test filelog revisions
977 # is used to test filelog revisions
976 ac = cl.ancestors([p.rev() for p in base.parents()],
978 ac = cl.ancestors([p.rev() for p in base.parents()],
977 inclusive=True)
979 inclusive=True)
978 else:
980 else:
979 ac = cl.ancestors([introrev], inclusive=True)
981 ac = cl.ancestors([introrev], inclusive=True)
980 base._ancestrycontext = ac
982 base._ancestrycontext = ac
981
983
982 # This algorithm would prefer to be recursive, but Python is a
984 # This algorithm would prefer to be recursive, but Python is a
983 # bit recursion-hostile. Instead we do an iterative
985 # bit recursion-hostile. Instead we do an iterative
984 # depth-first search.
986 # depth-first search.
985
987
986 visit = [base]
988 visit = [base]
987 hist = {}
989 hist = {}
988 pcache = {}
990 pcache = {}
989 needed = {base: 1}
991 needed = {base: 1}
990 while visit:
992 while visit:
991 f = visit[-1]
993 f = visit[-1]
992 pcached = f in pcache
994 pcached = f in pcache
993 if not pcached:
995 if not pcached:
994 pcache[f] = parents(f)
996 pcache[f] = parents(f)
995
997
996 ready = True
998 ready = True
997 pl = pcache[f]
999 pl = pcache[f]
998 for p in pl:
1000 for p in pl:
999 if p not in hist:
1001 if p not in hist:
1000 ready = False
1002 ready = False
1001 visit.append(p)
1003 visit.append(p)
1002 if not pcached:
1004 if not pcached:
1003 needed[p] = needed.get(p, 0) + 1
1005 needed[p] = needed.get(p, 0) + 1
1004 if ready:
1006 if ready:
1005 visit.pop()
1007 visit.pop()
1006 reusable = f in hist
1008 reusable = f in hist
1007 if reusable:
1009 if reusable:
1008 curr = hist[f]
1010 curr = hist[f]
1009 else:
1011 else:
1010 curr = decorate(f.data(), f)
1012 curr = decorate(f.data(), f)
1011 for p in pl:
1013 for p in pl:
1012 if not reusable:
1014 if not reusable:
1013 curr = pair(hist[p], curr)
1015 curr = pair(hist[p], curr)
1014 if needed[p] == 1:
1016 if needed[p] == 1:
1015 del hist[p]
1017 del hist[p]
1016 del needed[p]
1018 del needed[p]
1017 else:
1019 else:
1018 needed[p] -= 1
1020 needed[p] -= 1
1019
1021
1020 hist[f] = curr
1022 hist[f] = curr
1021 pcache[f] = []
1023 pcache[f] = []
1022
1024
1023 return zip(hist[base][0], hist[base][1].splitlines(True))
1025 return zip(hist[base][0], hist[base][1].splitlines(True))
1024
1026
1025 def ancestors(self, followfirst=False):
1027 def ancestors(self, followfirst=False):
1026 visit = {}
1028 visit = {}
1027 c = self
1029 c = self
1028 if followfirst:
1030 if followfirst:
1029 cut = 1
1031 cut = 1
1030 else:
1032 else:
1031 cut = None
1033 cut = None
1032
1034
1033 while True:
1035 while True:
1034 for parent in c.parents()[:cut]:
1036 for parent in c.parents()[:cut]:
1035 visit[(parent.linkrev(), parent.filenode())] = parent
1037 visit[(parent.linkrev(), parent.filenode())] = parent
1036 if not visit:
1038 if not visit:
1037 break
1039 break
1038 c = visit.pop(max(visit))
1040 c = visit.pop(max(visit))
1039 yield c
1041 yield c
1040
1042
1041 class filectx(basefilectx):
1043 class filectx(basefilectx):
1042 """A filecontext object makes access to data related to a particular
1044 """A filecontext object makes access to data related to a particular
1043 filerevision convenient."""
1045 filerevision convenient."""
1044 def __init__(self, repo, path, changeid=None, fileid=None,
1046 def __init__(self, repo, path, changeid=None, fileid=None,
1045 filelog=None, changectx=None):
1047 filelog=None, changectx=None):
1046 """changeid can be a changeset revision, node, or tag.
1048 """changeid can be a changeset revision, node, or tag.
1047 fileid can be a file revision or node."""
1049 fileid can be a file revision or node."""
1048 self._repo = repo
1050 self._repo = repo
1049 self._path = path
1051 self._path = path
1050
1052
1051 assert (changeid is not None
1053 assert (changeid is not None
1052 or fileid is not None
1054 or fileid is not None
1053 or changectx is not None), \
1055 or changectx is not None), \
1054 ("bad args: changeid=%r, fileid=%r, changectx=%r"
1056 ("bad args: changeid=%r, fileid=%r, changectx=%r"
1055 % (changeid, fileid, changectx))
1057 % (changeid, fileid, changectx))
1056
1058
1057 if filelog is not None:
1059 if filelog is not None:
1058 self._filelog = filelog
1060 self._filelog = filelog
1059
1061
1060 if changeid is not None:
1062 if changeid is not None:
1061 self._changeid = changeid
1063 self._changeid = changeid
1062 if changectx is not None:
1064 if changectx is not None:
1063 self._changectx = changectx
1065 self._changectx = changectx
1064 if fileid is not None:
1066 if fileid is not None:
1065 self._fileid = fileid
1067 self._fileid = fileid
1066
1068
1067 @propertycache
1069 @propertycache
1068 def _changectx(self):
1070 def _changectx(self):
1069 try:
1071 try:
1070 return changectx(self._repo, self._changeid)
1072 return changectx(self._repo, self._changeid)
1071 except error.FilteredRepoLookupError:
1073 except error.FilteredRepoLookupError:
1072 # Linkrev may point to any revision in the repository. When the
1074 # Linkrev may point to any revision in the repository. When the
1073 # repository is filtered this may lead to `filectx` trying to build
1075 # repository is filtered this may lead to `filectx` trying to build
1074 # `changectx` for filtered revision. In such case we fallback to
1076 # `changectx` for filtered revision. In such case we fallback to
1075 # creating `changectx` on the unfiltered version of the reposition.
1077 # creating `changectx` on the unfiltered version of the reposition.
1076 # This fallback should not be an issue because `changectx` from
1078 # This fallback should not be an issue because `changectx` from
1077 # `filectx` are not used in complex operations that care about
1079 # `filectx` are not used in complex operations that care about
1078 # filtering.
1080 # filtering.
1079 #
1081 #
1080 # This fallback is a cheap and dirty fix that prevent several
1082 # This fallback is a cheap and dirty fix that prevent several
1081 # crashes. It does not ensure the behavior is correct. However the
1083 # crashes. It does not ensure the behavior is correct. However the
1082 # behavior was not correct before filtering either and "incorrect
1084 # behavior was not correct before filtering either and "incorrect
1083 # behavior" is seen as better as "crash"
1085 # behavior" is seen as better as "crash"
1084 #
1086 #
1085 # Linkrevs have several serious troubles with filtering that are
1087 # Linkrevs have several serious troubles with filtering that are
1086 # complicated to solve. Proper handling of the issue here should be
1088 # complicated to solve. Proper handling of the issue here should be
1087 # considered when solving linkrev issue are on the table.
1089 # considered when solving linkrev issue are on the table.
1088 return changectx(self._repo.unfiltered(), self._changeid)
1090 return changectx(self._repo.unfiltered(), self._changeid)
1089
1091
1090 def filectx(self, fileid, changeid=None):
1092 def filectx(self, fileid, changeid=None):
1091 '''opens an arbitrary revision of the file without
1093 '''opens an arbitrary revision of the file without
1092 opening a new filelog'''
1094 opening a new filelog'''
1093 return filectx(self._repo, self._path, fileid=fileid,
1095 return filectx(self._repo, self._path, fileid=fileid,
1094 filelog=self._filelog, changeid=changeid)
1096 filelog=self._filelog, changeid=changeid)
1095
1097
1096 def data(self):
1098 def data(self):
1097 try:
1099 try:
1098 return self._filelog.read(self._filenode)
1100 return self._filelog.read(self._filenode)
1099 except error.CensoredNodeError:
1101 except error.CensoredNodeError:
1100 if self._repo.ui.config("censor", "policy", "abort") == "ignore":
1102 if self._repo.ui.config("censor", "policy", "abort") == "ignore":
1101 return ""
1103 return ""
1102 raise error.Abort(_("censored node: %s") % short(self._filenode),
1104 raise error.Abort(_("censored node: %s") % short(self._filenode),
1103 hint=_("set censor.policy to ignore errors"))
1105 hint=_("set censor.policy to ignore errors"))
1104
1106
1105 def size(self):
1107 def size(self):
1106 return self._filelog.size(self._filerev)
1108 return self._filelog.size(self._filerev)
1107
1109
1108 def renamed(self):
1110 def renamed(self):
1109 """check if file was actually renamed in this changeset revision
1111 """check if file was actually renamed in this changeset revision
1110
1112
1111 If rename logged in file revision, we report copy for changeset only
1113 If rename logged in file revision, we report copy for changeset only
1112 if file revisions linkrev points back to the changeset in question
1114 if file revisions linkrev points back to the changeset in question
1113 or both changeset parents contain different file revisions.
1115 or both changeset parents contain different file revisions.
1114 """
1116 """
1115
1117
1116 renamed = self._filelog.renamed(self._filenode)
1118 renamed = self._filelog.renamed(self._filenode)
1117 if not renamed:
1119 if not renamed:
1118 return renamed
1120 return renamed
1119
1121
1120 if self.rev() == self.linkrev():
1122 if self.rev() == self.linkrev():
1121 return renamed
1123 return renamed
1122
1124
1123 name = self.path()
1125 name = self.path()
1124 fnode = self._filenode
1126 fnode = self._filenode
1125 for p in self._changectx.parents():
1127 for p in self._changectx.parents():
1126 try:
1128 try:
1127 if fnode == p.filenode(name):
1129 if fnode == p.filenode(name):
1128 return None
1130 return None
1129 except error.LookupError:
1131 except error.LookupError:
1130 pass
1132 pass
1131 return renamed
1133 return renamed
1132
1134
1133 def children(self):
1135 def children(self):
1134 # hard for renames
1136 # hard for renames
1135 c = self._filelog.children(self._filenode)
1137 c = self._filelog.children(self._filenode)
1136 return [filectx(self._repo, self._path, fileid=x,
1138 return [filectx(self._repo, self._path, fileid=x,
1137 filelog=self._filelog) for x in c]
1139 filelog=self._filelog) for x in c]
1138
1140
1139 class committablectx(basectx):
1141 class committablectx(basectx):
1140 """A committablectx object provides common functionality for a context that
1142 """A committablectx object provides common functionality for a context that
1141 wants the ability to commit, e.g. workingctx or memctx."""
1143 wants the ability to commit, e.g. workingctx or memctx."""
1142 def __init__(self, repo, text="", user=None, date=None, extra=None,
1144 def __init__(self, repo, text="", user=None, date=None, extra=None,
1143 changes=None):
1145 changes=None):
1144 self._repo = repo
1146 self._repo = repo
1145 self._rev = None
1147 self._rev = None
1146 self._node = None
1148 self._node = None
1147 self._text = text
1149 self._text = text
1148 if date:
1150 if date:
1149 self._date = util.parsedate(date)
1151 self._date = util.parsedate(date)
1150 if user:
1152 if user:
1151 self._user = user
1153 self._user = user
1152 if changes:
1154 if changes:
1153 self._status = changes
1155 self._status = changes
1154
1156
1155 self._extra = {}
1157 self._extra = {}
1156 if extra:
1158 if extra:
1157 self._extra = extra.copy()
1159 self._extra = extra.copy()
1158 if 'branch' not in self._extra:
1160 if 'branch' not in self._extra:
1159 try:
1161 try:
1160 branch = encoding.fromlocal(self._repo.dirstate.branch())
1162 branch = encoding.fromlocal(self._repo.dirstate.branch())
1161 except UnicodeDecodeError:
1163 except UnicodeDecodeError:
1162 raise error.Abort(_('branch name not in UTF-8!'))
1164 raise error.Abort(_('branch name not in UTF-8!'))
1163 self._extra['branch'] = branch
1165 self._extra['branch'] = branch
1164 if self._extra['branch'] == '':
1166 if self._extra['branch'] == '':
1165 self._extra['branch'] = 'default'
1167 self._extra['branch'] = 'default'
1166
1168
1167 def __str__(self):
1169 def __str__(self):
1168 return str(self._parents[0]) + "+"
1170 return str(self._parents[0]) + "+"
1169
1171
1170 def __nonzero__(self):
1172 def __nonzero__(self):
1171 return True
1173 return True
1172
1174
1173 def _buildflagfunc(self):
1175 def _buildflagfunc(self):
1174 # Create a fallback function for getting file flags when the
1176 # Create a fallback function for getting file flags when the
1175 # filesystem doesn't support them
1177 # filesystem doesn't support them
1176
1178
1177 copiesget = self._repo.dirstate.copies().get
1179 copiesget = self._repo.dirstate.copies().get
1178 parents = self.parents()
1180 parents = self.parents()
1179 if len(parents) < 2:
1181 if len(parents) < 2:
1180 # when we have one parent, it's easy: copy from parent
1182 # when we have one parent, it's easy: copy from parent
1181 man = parents[0].manifest()
1183 man = parents[0].manifest()
1182 def func(f):
1184 def func(f):
1183 f = copiesget(f, f)
1185 f = copiesget(f, f)
1184 return man.flags(f)
1186 return man.flags(f)
1185 else:
1187 else:
1186 # merges are tricky: we try to reconstruct the unstored
1188 # merges are tricky: we try to reconstruct the unstored
1187 # result from the merge (issue1802)
1189 # result from the merge (issue1802)
1188 p1, p2 = parents
1190 p1, p2 = parents
1189 pa = p1.ancestor(p2)
1191 pa = p1.ancestor(p2)
1190 m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest()
1192 m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest()
1191
1193
1192 def func(f):
1194 def func(f):
1193 f = copiesget(f, f) # may be wrong for merges with copies
1195 f = copiesget(f, f) # may be wrong for merges with copies
1194 fl1, fl2, fla = m1.flags(f), m2.flags(f), ma.flags(f)
1196 fl1, fl2, fla = m1.flags(f), m2.flags(f), ma.flags(f)
1195 if fl1 == fl2:
1197 if fl1 == fl2:
1196 return fl1
1198 return fl1
1197 if fl1 == fla:
1199 if fl1 == fla:
1198 return fl2
1200 return fl2
1199 if fl2 == fla:
1201 if fl2 == fla:
1200 return fl1
1202 return fl1
1201 return '' # punt for conflicts
1203 return '' # punt for conflicts
1202
1204
1203 return func
1205 return func
1204
1206
1205 @propertycache
1207 @propertycache
1206 def _flagfunc(self):
1208 def _flagfunc(self):
1207 return self._repo.dirstate.flagfunc(self._buildflagfunc)
1209 return self._repo.dirstate.flagfunc(self._buildflagfunc)
1208
1210
1209 @propertycache
1211 @propertycache
1210 def _manifest(self):
1212 def _manifest(self):
1211 """generate a manifest corresponding to the values in self._status
1213 """generate a manifest corresponding to the values in self._status
1212
1214
1213 This reuse the file nodeid from parent, but we append an extra letter
1215 This reuse the file nodeid from parent, but we append an extra letter
1214 when modified. Modified files get an extra 'm' while added files get
1216 when modified. Modified files get an extra 'm' while added files get
1215 an extra 'a'. This is used by manifests merge to see that files
1217 an extra 'a'. This is used by manifests merge to see that files
1216 are different and by update logic to avoid deleting newly added files.
1218 are different and by update logic to avoid deleting newly added files.
1217 """
1219 """
1218 parents = self.parents()
1220 parents = self.parents()
1219
1221
1220 man1 = parents[0].manifest()
1222 man1 = parents[0].manifest()
1221 man = man1.copy()
1223 man = man1.copy()
1222 if len(parents) > 1:
1224 if len(parents) > 1:
1223 man2 = self.p2().manifest()
1225 man2 = self.p2().manifest()
1224 def getman(f):
1226 def getman(f):
1225 if f in man1:
1227 if f in man1:
1226 return man1
1228 return man1
1227 return man2
1229 return man2
1228 else:
1230 else:
1229 getman = lambda f: man1
1231 getman = lambda f: man1
1230
1232
1231 copied = self._repo.dirstate.copies()
1233 copied = self._repo.dirstate.copies()
1232 ff = self._flagfunc
1234 ff = self._flagfunc
1233 for i, l in (("a", self._status.added), ("m", self._status.modified)):
1235 for i, l in (("a", self._status.added), ("m", self._status.modified)):
1234 for f in l:
1236 for f in l:
1235 orig = copied.get(f, f)
1237 orig = copied.get(f, f)
1236 man[f] = getman(orig).get(orig, nullid) + i
1238 man[f] = getman(orig).get(orig, nullid) + i
1237 try:
1239 try:
1238 man.setflag(f, ff(f))
1240 man.setflag(f, ff(f))
1239 except OSError:
1241 except OSError:
1240 pass
1242 pass
1241
1243
1242 for f in self._status.deleted + self._status.removed:
1244 for f in self._status.deleted + self._status.removed:
1243 if f in man:
1245 if f in man:
1244 del man[f]
1246 del man[f]
1245
1247
1246 return man
1248 return man
1247
1249
1248 @propertycache
1250 @propertycache
1249 def _status(self):
1251 def _status(self):
1250 return self._repo.status()
1252 return self._repo.status()
1251
1253
1252 @propertycache
1254 @propertycache
1253 def _user(self):
1255 def _user(self):
1254 return self._repo.ui.username()
1256 return self._repo.ui.username()
1255
1257
1256 @propertycache
1258 @propertycache
1257 def _date(self):
1259 def _date(self):
1258 return util.makedate()
1260 return util.makedate()
1259
1261
1260 def subrev(self, subpath):
1262 def subrev(self, subpath):
1261 return None
1263 return None
1262
1264
1263 def manifestnode(self):
1265 def manifestnode(self):
1264 return None
1266 return None
1265 def user(self):
1267 def user(self):
1266 return self._user or self._repo.ui.username()
1268 return self._user or self._repo.ui.username()
1267 def date(self):
1269 def date(self):
1268 return self._date
1270 return self._date
1269 def description(self):
1271 def description(self):
1270 return self._text
1272 return self._text
1271 def files(self):
1273 def files(self):
1272 return sorted(self._status.modified + self._status.added +
1274 return sorted(self._status.modified + self._status.added +
1273 self._status.removed)
1275 self._status.removed)
1274
1276
1275 def modified(self):
1277 def modified(self):
1276 return self._status.modified
1278 return self._status.modified
1277 def added(self):
1279 def added(self):
1278 return self._status.added
1280 return self._status.added
1279 def removed(self):
1281 def removed(self):
1280 return self._status.removed
1282 return self._status.removed
1281 def deleted(self):
1283 def deleted(self):
1282 return self._status.deleted
1284 return self._status.deleted
1283 def branch(self):
1285 def branch(self):
1284 return encoding.tolocal(self._extra['branch'])
1286 return encoding.tolocal(self._extra['branch'])
1285 def closesbranch(self):
1287 def closesbranch(self):
1286 return 'close' in self._extra
1288 return 'close' in self._extra
1287 def extra(self):
1289 def extra(self):
1288 return self._extra
1290 return self._extra
1289
1291
1290 def tags(self):
1292 def tags(self):
1291 return []
1293 return []
1292
1294
1293 def bookmarks(self):
1295 def bookmarks(self):
1294 b = []
1296 b = []
1295 for p in self.parents():
1297 for p in self.parents():
1296 b.extend(p.bookmarks())
1298 b.extend(p.bookmarks())
1297 return b
1299 return b
1298
1300
1299 def phase(self):
1301 def phase(self):
1300 phase = phases.draft # default phase to draft
1302 phase = phases.draft # default phase to draft
1301 for p in self.parents():
1303 for p in self.parents():
1302 phase = max(phase, p.phase())
1304 phase = max(phase, p.phase())
1303 return phase
1305 return phase
1304
1306
1305 def hidden(self):
1307 def hidden(self):
1306 return False
1308 return False
1307
1309
1308 def children(self):
1310 def children(self):
1309 return []
1311 return []
1310
1312
1311 def flags(self, path):
1313 def flags(self, path):
1312 if '_manifest' in self.__dict__:
1314 if '_manifest' in self.__dict__:
1313 try:
1315 try:
1314 return self._manifest.flags(path)
1316 return self._manifest.flags(path)
1315 except KeyError:
1317 except KeyError:
1316 return ''
1318 return ''
1317
1319
1318 try:
1320 try:
1319 return self._flagfunc(path)
1321 return self._flagfunc(path)
1320 except OSError:
1322 except OSError:
1321 return ''
1323 return ''
1322
1324
1323 def ancestor(self, c2):
1325 def ancestor(self, c2):
1324 """return the "best" ancestor context of self and c2"""
1326 """return the "best" ancestor context of self and c2"""
1325 return self._parents[0].ancestor(c2) # punt on two parents for now
1327 return self._parents[0].ancestor(c2) # punt on two parents for now
1326
1328
1327 def walk(self, match):
1329 def walk(self, match):
1328 '''Generates matching file names.'''
1330 '''Generates matching file names.'''
1329 return sorted(self._repo.dirstate.walk(match, sorted(self.substate),
1331 return sorted(self._repo.dirstate.walk(match, sorted(self.substate),
1330 True, False))
1332 True, False))
1331
1333
1332 def matches(self, match):
1334 def matches(self, match):
1333 return sorted(self._repo.dirstate.matches(match))
1335 return sorted(self._repo.dirstate.matches(match))
1334
1336
1335 def ancestors(self):
1337 def ancestors(self):
1336 for p in self._parents:
1338 for p in self._parents:
1337 yield p
1339 yield p
1338 for a in self._repo.changelog.ancestors(
1340 for a in self._repo.changelog.ancestors(
1339 [p.rev() for p in self._parents]):
1341 [p.rev() for p in self._parents]):
1340 yield changectx(self._repo, a)
1342 yield changectx(self._repo, a)
1341
1343
1342 def markcommitted(self, node):
1344 def markcommitted(self, node):
1343 """Perform post-commit cleanup necessary after committing this ctx
1345 """Perform post-commit cleanup necessary after committing this ctx
1344
1346
1345 Specifically, this updates backing stores this working context
1347 Specifically, this updates backing stores this working context
1346 wraps to reflect the fact that the changes reflected by this
1348 wraps to reflect the fact that the changes reflected by this
1347 workingctx have been committed. For example, it marks
1349 workingctx have been committed. For example, it marks
1348 modified and added files as normal in the dirstate.
1350 modified and added files as normal in the dirstate.
1349
1351
1350 """
1352 """
1351
1353
1352 self._repo.dirstate.beginparentchange()
1354 self._repo.dirstate.beginparentchange()
1353 for f in self.modified() + self.added():
1355 for f in self.modified() + self.added():
1354 self._repo.dirstate.normal(f)
1356 self._repo.dirstate.normal(f)
1355 for f in self.removed():
1357 for f in self.removed():
1356 self._repo.dirstate.drop(f)
1358 self._repo.dirstate.drop(f)
1357 self._repo.dirstate.setparents(node)
1359 self._repo.dirstate.setparents(node)
1358 self._repo.dirstate.endparentchange()
1360 self._repo.dirstate.endparentchange()
1359
1361
1360 # write changes out explicitly, because nesting wlock at
1362 # write changes out explicitly, because nesting wlock at
1361 # runtime may prevent 'wlock.release()' in 'repo.commit()'
1363 # runtime may prevent 'wlock.release()' in 'repo.commit()'
1362 # from immediately doing so for subsequent changing files
1364 # from immediately doing so for subsequent changing files
1363 self._repo.dirstate.write(self._repo.currenttransaction())
1365 self._repo.dirstate.write(self._repo.currenttransaction())
1364
1366
1365 class workingctx(committablectx):
1367 class workingctx(committablectx):
1366 """A workingctx object makes access to data related to
1368 """A workingctx object makes access to data related to
1367 the current working directory convenient.
1369 the current working directory convenient.
1368 date - any valid date string or (unixtime, offset), or None.
1370 date - any valid date string or (unixtime, offset), or None.
1369 user - username string, or None.
1371 user - username string, or None.
1370 extra - a dictionary of extra values, or None.
1372 extra - a dictionary of extra values, or None.
1371 changes - a list of file lists as returned by localrepo.status()
1373 changes - a list of file lists as returned by localrepo.status()
1372 or None to use the repository status.
1374 or None to use the repository status.
1373 """
1375 """
1374 def __init__(self, repo, text="", user=None, date=None, extra=None,
1376 def __init__(self, repo, text="", user=None, date=None, extra=None,
1375 changes=None):
1377 changes=None):
1376 super(workingctx, self).__init__(repo, text, user, date, extra, changes)
1378 super(workingctx, self).__init__(repo, text, user, date, extra, changes)
1377
1379
1378 def __iter__(self):
1380 def __iter__(self):
1379 d = self._repo.dirstate
1381 d = self._repo.dirstate
1380 for f in d:
1382 for f in d:
1381 if d[f] != 'r':
1383 if d[f] != 'r':
1382 yield f
1384 yield f
1383
1385
1384 def __contains__(self, key):
1386 def __contains__(self, key):
1385 return self._repo.dirstate[key] not in "?r"
1387 return self._repo.dirstate[key] not in "?r"
1386
1388
1387 def hex(self):
1389 def hex(self):
1388 return hex(wdirid)
1390 return hex(wdirid)
1389
1391
1390 @propertycache
1392 @propertycache
1391 def _parents(self):
1393 def _parents(self):
1392 p = self._repo.dirstate.parents()
1394 p = self._repo.dirstate.parents()
1393 if p[1] == nullid:
1395 if p[1] == nullid:
1394 p = p[:-1]
1396 p = p[:-1]
1395 return [changectx(self._repo, x) for x in p]
1397 return [changectx(self._repo, x) for x in p]
1396
1398
1397 def filectx(self, path, filelog=None):
1399 def filectx(self, path, filelog=None):
1398 """get a file context from the working directory"""
1400 """get a file context from the working directory"""
1399 return workingfilectx(self._repo, path, workingctx=self,
1401 return workingfilectx(self._repo, path, workingctx=self,
1400 filelog=filelog)
1402 filelog=filelog)
1401
1403
1402 def dirty(self, missing=False, merge=True, branch=True):
1404 def dirty(self, missing=False, merge=True, branch=True):
1403 "check whether a working directory is modified"
1405 "check whether a working directory is modified"
1404 # check subrepos first
1406 # check subrepos first
1405 for s in sorted(self.substate):
1407 for s in sorted(self.substate):
1406 if self.sub(s).dirty():
1408 if self.sub(s).dirty():
1407 return True
1409 return True
1408 # check current working dir
1410 # check current working dir
1409 return ((merge and self.p2()) or
1411 return ((merge and self.p2()) or
1410 (branch and self.branch() != self.p1().branch()) or
1412 (branch and self.branch() != self.p1().branch()) or
1411 self.modified() or self.added() or self.removed() or
1413 self.modified() or self.added() or self.removed() or
1412 (missing and self.deleted()))
1414 (missing and self.deleted()))
1413
1415
1414 def add(self, list, prefix=""):
1416 def add(self, list, prefix=""):
1415 join = lambda f: os.path.join(prefix, f)
1417 join = lambda f: os.path.join(prefix, f)
1416 wlock = self._repo.wlock()
1418 wlock = self._repo.wlock()
1417 ui, ds = self._repo.ui, self._repo.dirstate
1419 ui, ds = self._repo.ui, self._repo.dirstate
1418 try:
1420 try:
1419 rejected = []
1421 rejected = []
1420 lstat = self._repo.wvfs.lstat
1422 lstat = self._repo.wvfs.lstat
1421 for f in list:
1423 for f in list:
1422 scmutil.checkportable(ui, join(f))
1424 scmutil.checkportable(ui, join(f))
1423 try:
1425 try:
1424 st = lstat(f)
1426 st = lstat(f)
1425 except OSError:
1427 except OSError:
1426 ui.warn(_("%s does not exist!\n") % join(f))
1428 ui.warn(_("%s does not exist!\n") % join(f))
1427 rejected.append(f)
1429 rejected.append(f)
1428 continue
1430 continue
1429 if st.st_size > 10000000:
1431 if st.st_size > 10000000:
1430 ui.warn(_("%s: up to %d MB of RAM may be required "
1432 ui.warn(_("%s: up to %d MB of RAM may be required "
1431 "to manage this file\n"
1433 "to manage this file\n"
1432 "(use 'hg revert %s' to cancel the "
1434 "(use 'hg revert %s' to cancel the "
1433 "pending addition)\n")
1435 "pending addition)\n")
1434 % (f, 3 * st.st_size // 1000000, join(f)))
1436 % (f, 3 * st.st_size // 1000000, join(f)))
1435 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
1437 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
1436 ui.warn(_("%s not added: only files and symlinks "
1438 ui.warn(_("%s not added: only files and symlinks "
1437 "supported currently\n") % join(f))
1439 "supported currently\n") % join(f))
1438 rejected.append(f)
1440 rejected.append(f)
1439 elif ds[f] in 'amn':
1441 elif ds[f] in 'amn':
1440 ui.warn(_("%s already tracked!\n") % join(f))
1442 ui.warn(_("%s already tracked!\n") % join(f))
1441 elif ds[f] == 'r':
1443 elif ds[f] == 'r':
1442 ds.normallookup(f)
1444 ds.normallookup(f)
1443 else:
1445 else:
1444 ds.add(f)
1446 ds.add(f)
1445 return rejected
1447 return rejected
1446 finally:
1448 finally:
1447 wlock.release()
1449 wlock.release()
1448
1450
1449 def forget(self, files, prefix=""):
1451 def forget(self, files, prefix=""):
1450 join = lambda f: os.path.join(prefix, f)
1452 join = lambda f: os.path.join(prefix, f)
1451 wlock = self._repo.wlock()
1453 wlock = self._repo.wlock()
1452 try:
1454 try:
1453 rejected = []
1455 rejected = []
1454 for f in files:
1456 for f in files:
1455 if f not in self._repo.dirstate:
1457 if f not in self._repo.dirstate:
1456 self._repo.ui.warn(_("%s not tracked!\n") % join(f))
1458 self._repo.ui.warn(_("%s not tracked!\n") % join(f))
1457 rejected.append(f)
1459 rejected.append(f)
1458 elif self._repo.dirstate[f] != 'a':
1460 elif self._repo.dirstate[f] != 'a':
1459 self._repo.dirstate.remove(f)
1461 self._repo.dirstate.remove(f)
1460 else:
1462 else:
1461 self._repo.dirstate.drop(f)
1463 self._repo.dirstate.drop(f)
1462 return rejected
1464 return rejected
1463 finally:
1465 finally:
1464 wlock.release()
1466 wlock.release()
1465
1467
1466 def undelete(self, list):
1468 def undelete(self, list):
1467 pctxs = self.parents()
1469 pctxs = self.parents()
1468 wlock = self._repo.wlock()
1470 wlock = self._repo.wlock()
1469 try:
1471 try:
1470 for f in list:
1472 for f in list:
1471 if self._repo.dirstate[f] != 'r':
1473 if self._repo.dirstate[f] != 'r':
1472 self._repo.ui.warn(_("%s not removed!\n") % f)
1474 self._repo.ui.warn(_("%s not removed!\n") % f)
1473 else:
1475 else:
1474 fctx = f in pctxs[0] and pctxs[0][f] or pctxs[1][f]
1476 fctx = f in pctxs[0] and pctxs[0][f] or pctxs[1][f]
1475 t = fctx.data()
1477 t = fctx.data()
1476 self._repo.wwrite(f, t, fctx.flags())
1478 self._repo.wwrite(f, t, fctx.flags())
1477 self._repo.dirstate.normal(f)
1479 self._repo.dirstate.normal(f)
1478 finally:
1480 finally:
1479 wlock.release()
1481 wlock.release()
1480
1482
1481 def copy(self, source, dest):
1483 def copy(self, source, dest):
1482 try:
1484 try:
1483 st = self._repo.wvfs.lstat(dest)
1485 st = self._repo.wvfs.lstat(dest)
1484 except OSError as err:
1486 except OSError as err:
1485 if err.errno != errno.ENOENT:
1487 if err.errno != errno.ENOENT:
1486 raise
1488 raise
1487 self._repo.ui.warn(_("%s does not exist!\n") % dest)
1489 self._repo.ui.warn(_("%s does not exist!\n") % dest)
1488 return
1490 return
1489 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
1491 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
1490 self._repo.ui.warn(_("copy failed: %s is not a file or a "
1492 self._repo.ui.warn(_("copy failed: %s is not a file or a "
1491 "symbolic link\n") % dest)
1493 "symbolic link\n") % dest)
1492 else:
1494 else:
1493 wlock = self._repo.wlock()
1495 wlock = self._repo.wlock()
1494 try:
1496 try:
1495 if self._repo.dirstate[dest] in '?':
1497 if self._repo.dirstate[dest] in '?':
1496 self._repo.dirstate.add(dest)
1498 self._repo.dirstate.add(dest)
1497 elif self._repo.dirstate[dest] in 'r':
1499 elif self._repo.dirstate[dest] in 'r':
1498 self._repo.dirstate.normallookup(dest)
1500 self._repo.dirstate.normallookup(dest)
1499 self._repo.dirstate.copy(source, dest)
1501 self._repo.dirstate.copy(source, dest)
1500 finally:
1502 finally:
1501 wlock.release()
1503 wlock.release()
1502
1504
1503 def match(self, pats=[], include=None, exclude=None, default='glob',
1505 def match(self, pats=[], include=None, exclude=None, default='glob',
1504 listsubrepos=False, badfn=None):
1506 listsubrepos=False, badfn=None):
1505 r = self._repo
1507 r = self._repo
1506
1508
1507 # Only a case insensitive filesystem needs magic to translate user input
1509 # Only a case insensitive filesystem needs magic to translate user input
1508 # to actual case in the filesystem.
1510 # to actual case in the filesystem.
1509 if not util.checkcase(r.root):
1511 if not util.checkcase(r.root):
1510 return matchmod.icasefsmatcher(r.root, r.getcwd(), pats, include,
1512 return matchmod.icasefsmatcher(r.root, r.getcwd(), pats, include,
1511 exclude, default, r.auditor, self,
1513 exclude, default, r.auditor, self,
1512 listsubrepos=listsubrepos,
1514 listsubrepos=listsubrepos,
1513 badfn=badfn)
1515 badfn=badfn)
1514 return matchmod.match(r.root, r.getcwd(), pats,
1516 return matchmod.match(r.root, r.getcwd(), pats,
1515 include, exclude, default,
1517 include, exclude, default,
1516 auditor=r.auditor, ctx=self,
1518 auditor=r.auditor, ctx=self,
1517 listsubrepos=listsubrepos, badfn=badfn)
1519 listsubrepos=listsubrepos, badfn=badfn)
1518
1520
1519 def _filtersuspectsymlink(self, files):
1521 def _filtersuspectsymlink(self, files):
1520 if not files or self._repo.dirstate._checklink:
1522 if not files or self._repo.dirstate._checklink:
1521 return files
1523 return files
1522
1524
1523 # Symlink placeholders may get non-symlink-like contents
1525 # Symlink placeholders may get non-symlink-like contents
1524 # via user error or dereferencing by NFS or Samba servers,
1526 # via user error or dereferencing by NFS or Samba servers,
1525 # so we filter out any placeholders that don't look like a
1527 # so we filter out any placeholders that don't look like a
1526 # symlink
1528 # symlink
1527 sane = []
1529 sane = []
1528 for f in files:
1530 for f in files:
1529 if self.flags(f) == 'l':
1531 if self.flags(f) == 'l':
1530 d = self[f].data()
1532 d = self[f].data()
1531 if d == '' or len(d) >= 1024 or '\n' in d or util.binary(d):
1533 if d == '' or len(d) >= 1024 or '\n' in d or util.binary(d):
1532 self._repo.ui.debug('ignoring suspect symlink placeholder'
1534 self._repo.ui.debug('ignoring suspect symlink placeholder'
1533 ' "%s"\n' % f)
1535 ' "%s"\n' % f)
1534 continue
1536 continue
1535 sane.append(f)
1537 sane.append(f)
1536 return sane
1538 return sane
1537
1539
1538 def _checklookup(self, files):
1540 def _checklookup(self, files):
1539 # check for any possibly clean files
1541 # check for any possibly clean files
1540 if not files:
1542 if not files:
1541 return [], []
1543 return [], []
1542
1544
1543 modified = []
1545 modified = []
1544 fixup = []
1546 fixup = []
1545 pctx = self._parents[0]
1547 pctx = self._parents[0]
1546 # do a full compare of any files that might have changed
1548 # do a full compare of any files that might have changed
1547 for f in sorted(files):
1549 for f in sorted(files):
1548 if (f not in pctx or self.flags(f) != pctx.flags(f)
1550 if (f not in pctx or self.flags(f) != pctx.flags(f)
1549 or pctx[f].cmp(self[f])):
1551 or pctx[f].cmp(self[f])):
1550 modified.append(f)
1552 modified.append(f)
1551 else:
1553 else:
1552 fixup.append(f)
1554 fixup.append(f)
1553
1555
1554 # update dirstate for files that are actually clean
1556 # update dirstate for files that are actually clean
1555 if fixup:
1557 if fixup:
1556 try:
1558 try:
1557 # updating the dirstate is optional
1559 # updating the dirstate is optional
1558 # so we don't wait on the lock
1560 # so we don't wait on the lock
1559 # wlock can invalidate the dirstate, so cache normal _after_
1561 # wlock can invalidate the dirstate, so cache normal _after_
1560 # taking the lock
1562 # taking the lock
1561 wlock = self._repo.wlock(False)
1563 wlock = self._repo.wlock(False)
1562 normal = self._repo.dirstate.normal
1564 normal = self._repo.dirstate.normal
1563 try:
1565 try:
1564 for f in fixup:
1566 for f in fixup:
1565 normal(f)
1567 normal(f)
1566 # write changes out explicitly, because nesting
1568 # write changes out explicitly, because nesting
1567 # wlock at runtime may prevent 'wlock.release()'
1569 # wlock at runtime may prevent 'wlock.release()'
1568 # below from doing so for subsequent changing files
1570 # below from doing so for subsequent changing files
1569 self._repo.dirstate.write(self._repo.currenttransaction())
1571 self._repo.dirstate.write(self._repo.currenttransaction())
1570 finally:
1572 finally:
1571 wlock.release()
1573 wlock.release()
1572 except error.LockError:
1574 except error.LockError:
1573 pass
1575 pass
1574 return modified, fixup
1576 return modified, fixup
1575
1577
1576 def _manifestmatches(self, match, s):
1578 def _manifestmatches(self, match, s):
1577 """Slow path for workingctx
1579 """Slow path for workingctx
1578
1580
1579 The fast path is when we compare the working directory to its parent
1581 The fast path is when we compare the working directory to its parent
1580 which means this function is comparing with a non-parent; therefore we
1582 which means this function is comparing with a non-parent; therefore we
1581 need to build a manifest and return what matches.
1583 need to build a manifest and return what matches.
1582 """
1584 """
1583 mf = self._repo['.']._manifestmatches(match, s)
1585 mf = self._repo['.']._manifestmatches(match, s)
1584 for f in s.modified + s.added:
1586 for f in s.modified + s.added:
1585 mf[f] = _newnode
1587 mf[f] = _newnode
1586 mf.setflag(f, self.flags(f))
1588 mf.setflag(f, self.flags(f))
1587 for f in s.removed:
1589 for f in s.removed:
1588 if f in mf:
1590 if f in mf:
1589 del mf[f]
1591 del mf[f]
1590 return mf
1592 return mf
1591
1593
1592 def _dirstatestatus(self, match=None, ignored=False, clean=False,
1594 def _dirstatestatus(self, match=None, ignored=False, clean=False,
1593 unknown=False):
1595 unknown=False):
1594 '''Gets the status from the dirstate -- internal use only.'''
1596 '''Gets the status from the dirstate -- internal use only.'''
1595 listignored, listclean, listunknown = ignored, clean, unknown
1597 listignored, listclean, listunknown = ignored, clean, unknown
1596 match = match or matchmod.always(self._repo.root, self._repo.getcwd())
1598 match = match or matchmod.always(self._repo.root, self._repo.getcwd())
1597 subrepos = []
1599 subrepos = []
1598 if '.hgsub' in self:
1600 if '.hgsub' in self:
1599 subrepos = sorted(self.substate)
1601 subrepos = sorted(self.substate)
1600 cmp, s = self._repo.dirstate.status(match, subrepos, listignored,
1602 cmp, s = self._repo.dirstate.status(match, subrepos, listignored,
1601 listclean, listunknown)
1603 listclean, listunknown)
1602
1604
1603 # check for any possibly clean files
1605 # check for any possibly clean files
1604 if cmp:
1606 if cmp:
1605 modified2, fixup = self._checklookup(cmp)
1607 modified2, fixup = self._checklookup(cmp)
1606 s.modified.extend(modified2)
1608 s.modified.extend(modified2)
1607
1609
1608 # update dirstate for files that are actually clean
1610 # update dirstate for files that are actually clean
1609 if fixup and listclean:
1611 if fixup and listclean:
1610 s.clean.extend(fixup)
1612 s.clean.extend(fixup)
1611
1613
1612 if match.always():
1614 if match.always():
1613 # cache for performance
1615 # cache for performance
1614 if s.unknown or s.ignored or s.clean:
1616 if s.unknown or s.ignored or s.clean:
1615 # "_status" is cached with list*=False in the normal route
1617 # "_status" is cached with list*=False in the normal route
1616 self._status = scmutil.status(s.modified, s.added, s.removed,
1618 self._status = scmutil.status(s.modified, s.added, s.removed,
1617 s.deleted, [], [], [])
1619 s.deleted, [], [], [])
1618 else:
1620 else:
1619 self._status = s
1621 self._status = s
1620
1622
1621 return s
1623 return s
1622
1624
1623 def _buildstatus(self, other, s, match, listignored, listclean,
1625 def _buildstatus(self, other, s, match, listignored, listclean,
1624 listunknown):
1626 listunknown):
1625 """build a status with respect to another context
1627 """build a status with respect to another context
1626
1628
1627 This includes logic for maintaining the fast path of status when
1629 This includes logic for maintaining the fast path of status when
1628 comparing the working directory against its parent, which is to skip
1630 comparing the working directory against its parent, which is to skip
1629 building a new manifest if self (working directory) is not comparing
1631 building a new manifest if self (working directory) is not comparing
1630 against its parent (repo['.']).
1632 against its parent (repo['.']).
1631 """
1633 """
1632 s = self._dirstatestatus(match, listignored, listclean, listunknown)
1634 s = self._dirstatestatus(match, listignored, listclean, listunknown)
1633 # Filter out symlinks that, in the case of FAT32 and NTFS filesystems,
1635 # Filter out symlinks that, in the case of FAT32 and NTFS filesystems,
1634 # might have accidentally ended up with the entire contents of the file
1636 # might have accidentally ended up with the entire contents of the file
1635 # they are supposed to be linking to.
1637 # they are supposed to be linking to.
1636 s.modified[:] = self._filtersuspectsymlink(s.modified)
1638 s.modified[:] = self._filtersuspectsymlink(s.modified)
1637 if other != self._repo['.']:
1639 if other != self._repo['.']:
1638 s = super(workingctx, self)._buildstatus(other, s, match,
1640 s = super(workingctx, self)._buildstatus(other, s, match,
1639 listignored, listclean,
1641 listignored, listclean,
1640 listunknown)
1642 listunknown)
1641 return s
1643 return s
1642
1644
1643 def _matchstatus(self, other, match):
1645 def _matchstatus(self, other, match):
1644 """override the match method with a filter for directory patterns
1646 """override the match method with a filter for directory patterns
1645
1647
1646 We use inheritance to customize the match.bad method only in cases of
1648 We use inheritance to customize the match.bad method only in cases of
1647 workingctx since it belongs only to the working directory when
1649 workingctx since it belongs only to the working directory when
1648 comparing against the parent changeset.
1650 comparing against the parent changeset.
1649
1651
1650 If we aren't comparing against the working directory's parent, then we
1652 If we aren't comparing against the working directory's parent, then we
1651 just use the default match object sent to us.
1653 just use the default match object sent to us.
1652 """
1654 """
1653 superself = super(workingctx, self)
1655 superself = super(workingctx, self)
1654 match = superself._matchstatus(other, match)
1656 match = superself._matchstatus(other, match)
1655 if other != self._repo['.']:
1657 if other != self._repo['.']:
1656 def bad(f, msg):
1658 def bad(f, msg):
1657 # 'f' may be a directory pattern from 'match.files()',
1659 # 'f' may be a directory pattern from 'match.files()',
1658 # so 'f not in ctx1' is not enough
1660 # so 'f not in ctx1' is not enough
1659 if f not in other and not other.hasdir(f):
1661 if f not in other and not other.hasdir(f):
1660 self._repo.ui.warn('%s: %s\n' %
1662 self._repo.ui.warn('%s: %s\n' %
1661 (self._repo.dirstate.pathto(f), msg))
1663 (self._repo.dirstate.pathto(f), msg))
1662 match.bad = bad
1664 match.bad = bad
1663 return match
1665 return match
1664
1666
1665 class committablefilectx(basefilectx):
1667 class committablefilectx(basefilectx):
1666 """A committablefilectx provides common functionality for a file context
1668 """A committablefilectx provides common functionality for a file context
1667 that wants the ability to commit, e.g. workingfilectx or memfilectx."""
1669 that wants the ability to commit, e.g. workingfilectx or memfilectx."""
1668 def __init__(self, repo, path, filelog=None, ctx=None):
1670 def __init__(self, repo, path, filelog=None, ctx=None):
1669 self._repo = repo
1671 self._repo = repo
1670 self._path = path
1672 self._path = path
1671 self._changeid = None
1673 self._changeid = None
1672 self._filerev = self._filenode = None
1674 self._filerev = self._filenode = None
1673
1675
1674 if filelog is not None:
1676 if filelog is not None:
1675 self._filelog = filelog
1677 self._filelog = filelog
1676 if ctx:
1678 if ctx:
1677 self._changectx = ctx
1679 self._changectx = ctx
1678
1680
1679 def __nonzero__(self):
1681 def __nonzero__(self):
1680 return True
1682 return True
1681
1683
1682 def linkrev(self):
1684 def linkrev(self):
1683 # linked to self._changectx no matter if file is modified or not
1685 # linked to self._changectx no matter if file is modified or not
1684 return self.rev()
1686 return self.rev()
1685
1687
1686 def parents(self):
1688 def parents(self):
1687 '''return parent filectxs, following copies if necessary'''
1689 '''return parent filectxs, following copies if necessary'''
1688 def filenode(ctx, path):
1690 def filenode(ctx, path):
1689 return ctx._manifest.get(path, nullid)
1691 return ctx._manifest.get(path, nullid)
1690
1692
1691 path = self._path
1693 path = self._path
1692 fl = self._filelog
1694 fl = self._filelog
1693 pcl = self._changectx._parents
1695 pcl = self._changectx._parents
1694 renamed = self.renamed()
1696 renamed = self.renamed()
1695
1697
1696 if renamed:
1698 if renamed:
1697 pl = [renamed + (None,)]
1699 pl = [renamed + (None,)]
1698 else:
1700 else:
1699 pl = [(path, filenode(pcl[0], path), fl)]
1701 pl = [(path, filenode(pcl[0], path), fl)]
1700
1702
1701 for pc in pcl[1:]:
1703 for pc in pcl[1:]:
1702 pl.append((path, filenode(pc, path), fl))
1704 pl.append((path, filenode(pc, path), fl))
1703
1705
1704 return [self._parentfilectx(p, fileid=n, filelog=l)
1706 return [self._parentfilectx(p, fileid=n, filelog=l)
1705 for p, n, l in pl if n != nullid]
1707 for p, n, l in pl if n != nullid]
1706
1708
1707 def children(self):
1709 def children(self):
1708 return []
1710 return []
1709
1711
1710 class workingfilectx(committablefilectx):
1712 class workingfilectx(committablefilectx):
1711 """A workingfilectx object makes access to data related to a particular
1713 """A workingfilectx object makes access to data related to a particular
1712 file in the working directory convenient."""
1714 file in the working directory convenient."""
1713 def __init__(self, repo, path, filelog=None, workingctx=None):
1715 def __init__(self, repo, path, filelog=None, workingctx=None):
1714 super(workingfilectx, self).__init__(repo, path, filelog, workingctx)
1716 super(workingfilectx, self).__init__(repo, path, filelog, workingctx)
1715
1717
1716 @propertycache
1718 @propertycache
1717 def _changectx(self):
1719 def _changectx(self):
1718 return workingctx(self._repo)
1720 return workingctx(self._repo)
1719
1721
1720 def data(self):
1722 def data(self):
1721 return self._repo.wread(self._path)
1723 return self._repo.wread(self._path)
1722 def renamed(self):
1724 def renamed(self):
1723 rp = self._repo.dirstate.copied(self._path)
1725 rp = self._repo.dirstate.copied(self._path)
1724 if not rp:
1726 if not rp:
1725 return None
1727 return None
1726 return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
1728 return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
1727
1729
1728 def size(self):
1730 def size(self):
1729 return self._repo.wvfs.lstat(self._path).st_size
1731 return self._repo.wvfs.lstat(self._path).st_size
1730 def date(self):
1732 def date(self):
1731 t, tz = self._changectx.date()
1733 t, tz = self._changectx.date()
1732 try:
1734 try:
1733 return (self._repo.wvfs.lstat(self._path).st_mtime, tz)
1735 return (self._repo.wvfs.lstat(self._path).st_mtime, tz)
1734 except OSError as err:
1736 except OSError as err:
1735 if err.errno != errno.ENOENT:
1737 if err.errno != errno.ENOENT:
1736 raise
1738 raise
1737 return (t, tz)
1739 return (t, tz)
1738
1740
1739 def cmp(self, fctx):
1741 def cmp(self, fctx):
1740 """compare with other file context
1742 """compare with other file context
1741
1743
1742 returns True if different than fctx.
1744 returns True if different than fctx.
1743 """
1745 """
1744 # fctx should be a filectx (not a workingfilectx)
1746 # fctx should be a filectx (not a workingfilectx)
1745 # invert comparison to reuse the same code path
1747 # invert comparison to reuse the same code path
1746 return fctx.cmp(self)
1748 return fctx.cmp(self)
1747
1749
1748 def remove(self, ignoremissing=False):
1750 def remove(self, ignoremissing=False):
1749 """wraps unlink for a repo's working directory"""
1751 """wraps unlink for a repo's working directory"""
1750 util.unlinkpath(self._repo.wjoin(self._path), ignoremissing)
1752 util.unlinkpath(self._repo.wjoin(self._path), ignoremissing)
1751
1753
1752 def write(self, data, flags):
1754 def write(self, data, flags):
1753 """wraps repo.wwrite"""
1755 """wraps repo.wwrite"""
1754 self._repo.wwrite(self._path, data, flags)
1756 self._repo.wwrite(self._path, data, flags)
1755
1757
1756 class workingcommitctx(workingctx):
1758 class workingcommitctx(workingctx):
1757 """A workingcommitctx object makes access to data related to
1759 """A workingcommitctx object makes access to data related to
1758 the revision being committed convenient.
1760 the revision being committed convenient.
1759
1761
1760 This hides changes in the working directory, if they aren't
1762 This hides changes in the working directory, if they aren't
1761 committed in this context.
1763 committed in this context.
1762 """
1764 """
1763 def __init__(self, repo, changes,
1765 def __init__(self, repo, changes,
1764 text="", user=None, date=None, extra=None):
1766 text="", user=None, date=None, extra=None):
1765 super(workingctx, self).__init__(repo, text, user, date, extra,
1767 super(workingctx, self).__init__(repo, text, user, date, extra,
1766 changes)
1768 changes)
1767
1769
1768 def _dirstatestatus(self, match=None, ignored=False, clean=False,
1770 def _dirstatestatus(self, match=None, ignored=False, clean=False,
1769 unknown=False):
1771 unknown=False):
1770 """Return matched files only in ``self._status``
1772 """Return matched files only in ``self._status``
1771
1773
1772 Uncommitted files appear "clean" via this context, even if
1774 Uncommitted files appear "clean" via this context, even if
1773 they aren't actually so in the working directory.
1775 they aren't actually so in the working directory.
1774 """
1776 """
1775 match = match or matchmod.always(self._repo.root, self._repo.getcwd())
1777 match = match or matchmod.always(self._repo.root, self._repo.getcwd())
1776 if clean:
1778 if clean:
1777 clean = [f for f in self._manifest if f not in self._changedset]
1779 clean = [f for f in self._manifest if f not in self._changedset]
1778 else:
1780 else:
1779 clean = []
1781 clean = []
1780 return scmutil.status([f for f in self._status.modified if match(f)],
1782 return scmutil.status([f for f in self._status.modified if match(f)],
1781 [f for f in self._status.added if match(f)],
1783 [f for f in self._status.added if match(f)],
1782 [f for f in self._status.removed if match(f)],
1784 [f for f in self._status.removed if match(f)],
1783 [], [], [], clean)
1785 [], [], [], clean)
1784
1786
1785 @propertycache
1787 @propertycache
1786 def _changedset(self):
1788 def _changedset(self):
1787 """Return the set of files changed in this context
1789 """Return the set of files changed in this context
1788 """
1790 """
1789 changed = set(self._status.modified)
1791 changed = set(self._status.modified)
1790 changed.update(self._status.added)
1792 changed.update(self._status.added)
1791 changed.update(self._status.removed)
1793 changed.update(self._status.removed)
1792 return changed
1794 return changed
1793
1795
1794 class memctx(committablectx):
1796 class memctx(committablectx):
1795 """Use memctx to perform in-memory commits via localrepo.commitctx().
1797 """Use memctx to perform in-memory commits via localrepo.commitctx().
1796
1798
1797 Revision information is supplied at initialization time while
1799 Revision information is supplied at initialization time while
1798 related files data and is made available through a callback
1800 related files data and is made available through a callback
1799 mechanism. 'repo' is the current localrepo, 'parents' is a
1801 mechanism. 'repo' is the current localrepo, 'parents' is a
1800 sequence of two parent revisions identifiers (pass None for every
1802 sequence of two parent revisions identifiers (pass None for every
1801 missing parent), 'text' is the commit message and 'files' lists
1803 missing parent), 'text' is the commit message and 'files' lists
1802 names of files touched by the revision (normalized and relative to
1804 names of files touched by the revision (normalized and relative to
1803 repository root).
1805 repository root).
1804
1806
1805 filectxfn(repo, memctx, path) is a callable receiving the
1807 filectxfn(repo, memctx, path) is a callable receiving the
1806 repository, the current memctx object and the normalized path of
1808 repository, the current memctx object and the normalized path of
1807 requested file, relative to repository root. It is fired by the
1809 requested file, relative to repository root. It is fired by the
1808 commit function for every file in 'files', but calls order is
1810 commit function for every file in 'files', but calls order is
1809 undefined. If the file is available in the revision being
1811 undefined. If the file is available in the revision being
1810 committed (updated or added), filectxfn returns a memfilectx
1812 committed (updated or added), filectxfn returns a memfilectx
1811 object. If the file was removed, filectxfn raises an
1813 object. If the file was removed, filectxfn raises an
1812 IOError. Moved files are represented by marking the source file
1814 IOError. Moved files are represented by marking the source file
1813 removed and the new file added with copy information (see
1815 removed and the new file added with copy information (see
1814 memfilectx).
1816 memfilectx).
1815
1817
1816 user receives the committer name and defaults to current
1818 user receives the committer name and defaults to current
1817 repository username, date is the commit date in any format
1819 repository username, date is the commit date in any format
1818 supported by util.parsedate() and defaults to current date, extra
1820 supported by util.parsedate() and defaults to current date, extra
1819 is a dictionary of metadata or is left empty.
1821 is a dictionary of metadata or is left empty.
1820 """
1822 """
1821
1823
1822 # Mercurial <= 3.1 expects the filectxfn to raise IOError for missing files.
1824 # Mercurial <= 3.1 expects the filectxfn to raise IOError for missing files.
1823 # Extensions that need to retain compatibility across Mercurial 3.1 can use
1825 # Extensions that need to retain compatibility across Mercurial 3.1 can use
1824 # this field to determine what to do in filectxfn.
1826 # this field to determine what to do in filectxfn.
1825 _returnnoneformissingfiles = True
1827 _returnnoneformissingfiles = True
1826
1828
1827 def __init__(self, repo, parents, text, files, filectxfn, user=None,
1829 def __init__(self, repo, parents, text, files, filectxfn, user=None,
1828 date=None, extra=None, editor=False):
1830 date=None, extra=None, editor=False):
1829 super(memctx, self).__init__(repo, text, user, date, extra)
1831 super(memctx, self).__init__(repo, text, user, date, extra)
1830 self._rev = None
1832 self._rev = None
1831 self._node = None
1833 self._node = None
1832 parents = [(p or nullid) for p in parents]
1834 parents = [(p or nullid) for p in parents]
1833 p1, p2 = parents
1835 p1, p2 = parents
1834 self._parents = [changectx(self._repo, p) for p in (p1, p2)]
1836 self._parents = [changectx(self._repo, p) for p in (p1, p2)]
1835 files = sorted(set(files))
1837 files = sorted(set(files))
1836 self._files = files
1838 self._files = files
1837 self.substate = {}
1839 self.substate = {}
1838
1840
1839 # if store is not callable, wrap it in a function
1841 # if store is not callable, wrap it in a function
1840 if not callable(filectxfn):
1842 if not callable(filectxfn):
1841 def getfilectx(repo, memctx, path):
1843 def getfilectx(repo, memctx, path):
1842 fctx = filectxfn[path]
1844 fctx = filectxfn[path]
1843 # this is weird but apparently we only keep track of one parent
1845 # this is weird but apparently we only keep track of one parent
1844 # (why not only store that instead of a tuple?)
1846 # (why not only store that instead of a tuple?)
1845 copied = fctx.renamed()
1847 copied = fctx.renamed()
1846 if copied:
1848 if copied:
1847 copied = copied[0]
1849 copied = copied[0]
1848 return memfilectx(repo, path, fctx.data(),
1850 return memfilectx(repo, path, fctx.data(),
1849 islink=fctx.islink(), isexec=fctx.isexec(),
1851 islink=fctx.islink(), isexec=fctx.isexec(),
1850 copied=copied, memctx=memctx)
1852 copied=copied, memctx=memctx)
1851 self._filectxfn = getfilectx
1853 self._filectxfn = getfilectx
1852 else:
1854 else:
1853 # "util.cachefunc" reduces invocation of possibly expensive
1855 # "util.cachefunc" reduces invocation of possibly expensive
1854 # "filectxfn" for performance (e.g. converting from another VCS)
1856 # "filectxfn" for performance (e.g. converting from another VCS)
1855 self._filectxfn = util.cachefunc(filectxfn)
1857 self._filectxfn = util.cachefunc(filectxfn)
1856
1858
1857 if extra:
1859 if extra:
1858 self._extra = extra.copy()
1860 self._extra = extra.copy()
1859 else:
1861 else:
1860 self._extra = {}
1862 self._extra = {}
1861
1863
1862 if self._extra.get('branch', '') == '':
1864 if self._extra.get('branch', '') == '':
1863 self._extra['branch'] = 'default'
1865 self._extra['branch'] = 'default'
1864
1866
1865 if editor:
1867 if editor:
1866 self._text = editor(self._repo, self, [])
1868 self._text = editor(self._repo, self, [])
1867 self._repo.savecommitmessage(self._text)
1869 self._repo.savecommitmessage(self._text)
1868
1870
1869 def filectx(self, path, filelog=None):
1871 def filectx(self, path, filelog=None):
1870 """get a file context from the working directory
1872 """get a file context from the working directory
1871
1873
1872 Returns None if file doesn't exist and should be removed."""
1874 Returns None if file doesn't exist and should be removed."""
1873 return self._filectxfn(self._repo, self, path)
1875 return self._filectxfn(self._repo, self, path)
1874
1876
1875 def commit(self):
1877 def commit(self):
1876 """commit context to the repo"""
1878 """commit context to the repo"""
1877 return self._repo.commitctx(self)
1879 return self._repo.commitctx(self)
1878
1880
1879 @propertycache
1881 @propertycache
1880 def _manifest(self):
1882 def _manifest(self):
1881 """generate a manifest based on the return values of filectxfn"""
1883 """generate a manifest based on the return values of filectxfn"""
1882
1884
1883 # keep this simple for now; just worry about p1
1885 # keep this simple for now; just worry about p1
1884 pctx = self._parents[0]
1886 pctx = self._parents[0]
1885 man = pctx.manifest().copy()
1887 man = pctx.manifest().copy()
1886
1888
1887 for f in self._status.modified:
1889 for f in self._status.modified:
1888 p1node = nullid
1890 p1node = nullid
1889 p2node = nullid
1891 p2node = nullid
1890 p = pctx[f].parents() # if file isn't in pctx, check p2?
1892 p = pctx[f].parents() # if file isn't in pctx, check p2?
1891 if len(p) > 0:
1893 if len(p) > 0:
1892 p1node = p[0].node()
1894 p1node = p[0].node()
1893 if len(p) > 1:
1895 if len(p) > 1:
1894 p2node = p[1].node()
1896 p2node = p[1].node()
1895 man[f] = revlog.hash(self[f].data(), p1node, p2node)
1897 man[f] = revlog.hash(self[f].data(), p1node, p2node)
1896
1898
1897 for f in self._status.added:
1899 for f in self._status.added:
1898 man[f] = revlog.hash(self[f].data(), nullid, nullid)
1900 man[f] = revlog.hash(self[f].data(), nullid, nullid)
1899
1901
1900 for f in self._status.removed:
1902 for f in self._status.removed:
1901 if f in man:
1903 if f in man:
1902 del man[f]
1904 del man[f]
1903
1905
1904 return man
1906 return man
1905
1907
1906 @propertycache
1908 @propertycache
1907 def _status(self):
1909 def _status(self):
1908 """Calculate exact status from ``files`` specified at construction
1910 """Calculate exact status from ``files`` specified at construction
1909 """
1911 """
1910 man1 = self.p1().manifest()
1912 man1 = self.p1().manifest()
1911 p2 = self._parents[1]
1913 p2 = self._parents[1]
1912 # "1 < len(self._parents)" can't be used for checking
1914 # "1 < len(self._parents)" can't be used for checking
1913 # existence of the 2nd parent, because "memctx._parents" is
1915 # existence of the 2nd parent, because "memctx._parents" is
1914 # explicitly initialized by the list, of which length is 2.
1916 # explicitly initialized by the list, of which length is 2.
1915 if p2.node() != nullid:
1917 if p2.node() != nullid:
1916 man2 = p2.manifest()
1918 man2 = p2.manifest()
1917 managing = lambda f: f in man1 or f in man2
1919 managing = lambda f: f in man1 or f in man2
1918 else:
1920 else:
1919 managing = lambda f: f in man1
1921 managing = lambda f: f in man1
1920
1922
1921 modified, added, removed = [], [], []
1923 modified, added, removed = [], [], []
1922 for f in self._files:
1924 for f in self._files:
1923 if not managing(f):
1925 if not managing(f):
1924 added.append(f)
1926 added.append(f)
1925 elif self[f]:
1927 elif self[f]:
1926 modified.append(f)
1928 modified.append(f)
1927 else:
1929 else:
1928 removed.append(f)
1930 removed.append(f)
1929
1931
1930 return scmutil.status(modified, added, removed, [], [], [], [])
1932 return scmutil.status(modified, added, removed, [], [], [], [])
1931
1933
1932 class memfilectx(committablefilectx):
1934 class memfilectx(committablefilectx):
1933 """memfilectx represents an in-memory file to commit.
1935 """memfilectx represents an in-memory file to commit.
1934
1936
1935 See memctx and committablefilectx for more details.
1937 See memctx and committablefilectx for more details.
1936 """
1938 """
1937 def __init__(self, repo, path, data, islink=False,
1939 def __init__(self, repo, path, data, islink=False,
1938 isexec=False, copied=None, memctx=None):
1940 isexec=False, copied=None, memctx=None):
1939 """
1941 """
1940 path is the normalized file path relative to repository root.
1942 path is the normalized file path relative to repository root.
1941 data is the file content as a string.
1943 data is the file content as a string.
1942 islink is True if the file is a symbolic link.
1944 islink is True if the file is a symbolic link.
1943 isexec is True if the file is executable.
1945 isexec is True if the file is executable.
1944 copied is the source file path if current file was copied in the
1946 copied is the source file path if current file was copied in the
1945 revision being committed, or None."""
1947 revision being committed, or None."""
1946 super(memfilectx, self).__init__(repo, path, None, memctx)
1948 super(memfilectx, self).__init__(repo, path, None, memctx)
1947 self._data = data
1949 self._data = data
1948 self._flags = (islink and 'l' or '') + (isexec and 'x' or '')
1950 self._flags = (islink and 'l' or '') + (isexec and 'x' or '')
1949 self._copied = None
1951 self._copied = None
1950 if copied:
1952 if copied:
1951 self._copied = (copied, nullid)
1953 self._copied = (copied, nullid)
1952
1954
1953 def data(self):
1955 def data(self):
1954 return self._data
1956 return self._data
1955 def size(self):
1957 def size(self):
1956 return len(self.data())
1958 return len(self.data())
1957 def flags(self):
1959 def flags(self):
1958 return self._flags
1960 return self._flags
1959 def renamed(self):
1961 def renamed(self):
1960 return self._copied
1962 return self._copied
1961
1963
1962 def remove(self, ignoremissing=False):
1964 def remove(self, ignoremissing=False):
1963 """wraps unlink for a repo's working directory"""
1965 """wraps unlink for a repo's working directory"""
1964 # need to figure out what to do here
1966 # need to figure out what to do here
1965 del self._changectx[self._path]
1967 del self._changectx[self._path]
1966
1968
1967 def write(self, data, flags):
1969 def write(self, data, flags):
1968 """wraps repo.wwrite"""
1970 """wraps repo.wwrite"""
1969 self._data = data
1971 self._data = data
@@ -1,485 +1,513 b''
1 $ hg init repo1
1 $ hg init repo1
2 $ cd repo1
2 $ cd repo1
3 $ mkdir a b a/1 b/1 b/2
3 $ mkdir a b a/1 b/1 b/2
4 $ touch in_root a/in_a b/in_b a/1/in_a_1 b/1/in_b_1 b/2/in_b_2
4 $ touch in_root a/in_a b/in_b a/1/in_a_1 b/1/in_b_1 b/2/in_b_2
5
5
6 hg status in repo root:
6 hg status in repo root:
7
7
8 $ hg status
8 $ hg status
9 ? a/1/in_a_1
9 ? a/1/in_a_1
10 ? a/in_a
10 ? a/in_a
11 ? b/1/in_b_1
11 ? b/1/in_b_1
12 ? b/2/in_b_2
12 ? b/2/in_b_2
13 ? b/in_b
13 ? b/in_b
14 ? in_root
14 ? in_root
15
15
16 hg status . in repo root:
16 hg status . in repo root:
17
17
18 $ hg status .
18 $ hg status .
19 ? a/1/in_a_1
19 ? a/1/in_a_1
20 ? a/in_a
20 ? a/in_a
21 ? b/1/in_b_1
21 ? b/1/in_b_1
22 ? b/2/in_b_2
22 ? b/2/in_b_2
23 ? b/in_b
23 ? b/in_b
24 ? in_root
24 ? in_root
25
25
26 $ hg status --cwd a
26 $ hg status --cwd a
27 ? a/1/in_a_1
27 ? a/1/in_a_1
28 ? a/in_a
28 ? a/in_a
29 ? b/1/in_b_1
29 ? b/1/in_b_1
30 ? b/2/in_b_2
30 ? b/2/in_b_2
31 ? b/in_b
31 ? b/in_b
32 ? in_root
32 ? in_root
33 $ hg status --cwd a .
33 $ hg status --cwd a .
34 ? 1/in_a_1
34 ? 1/in_a_1
35 ? in_a
35 ? in_a
36 $ hg status --cwd a ..
36 $ hg status --cwd a ..
37 ? 1/in_a_1
37 ? 1/in_a_1
38 ? in_a
38 ? in_a
39 ? ../b/1/in_b_1
39 ? ../b/1/in_b_1
40 ? ../b/2/in_b_2
40 ? ../b/2/in_b_2
41 ? ../b/in_b
41 ? ../b/in_b
42 ? ../in_root
42 ? ../in_root
43
43
44 $ hg status --cwd b
44 $ hg status --cwd b
45 ? a/1/in_a_1
45 ? a/1/in_a_1
46 ? a/in_a
46 ? a/in_a
47 ? b/1/in_b_1
47 ? b/1/in_b_1
48 ? b/2/in_b_2
48 ? b/2/in_b_2
49 ? b/in_b
49 ? b/in_b
50 ? in_root
50 ? in_root
51 $ hg status --cwd b .
51 $ hg status --cwd b .
52 ? 1/in_b_1
52 ? 1/in_b_1
53 ? 2/in_b_2
53 ? 2/in_b_2
54 ? in_b
54 ? in_b
55 $ hg status --cwd b ..
55 $ hg status --cwd b ..
56 ? ../a/1/in_a_1
56 ? ../a/1/in_a_1
57 ? ../a/in_a
57 ? ../a/in_a
58 ? 1/in_b_1
58 ? 1/in_b_1
59 ? 2/in_b_2
59 ? 2/in_b_2
60 ? in_b
60 ? in_b
61 ? ../in_root
61 ? ../in_root
62
62
63 $ hg status --cwd a/1
63 $ hg status --cwd a/1
64 ? a/1/in_a_1
64 ? a/1/in_a_1
65 ? a/in_a
65 ? a/in_a
66 ? b/1/in_b_1
66 ? b/1/in_b_1
67 ? b/2/in_b_2
67 ? b/2/in_b_2
68 ? b/in_b
68 ? b/in_b
69 ? in_root
69 ? in_root
70 $ hg status --cwd a/1 .
70 $ hg status --cwd a/1 .
71 ? in_a_1
71 ? in_a_1
72 $ hg status --cwd a/1 ..
72 $ hg status --cwd a/1 ..
73 ? in_a_1
73 ? in_a_1
74 ? ../in_a
74 ? ../in_a
75
75
76 $ hg status --cwd b/1
76 $ hg status --cwd b/1
77 ? a/1/in_a_1
77 ? a/1/in_a_1
78 ? a/in_a
78 ? a/in_a
79 ? b/1/in_b_1
79 ? b/1/in_b_1
80 ? b/2/in_b_2
80 ? b/2/in_b_2
81 ? b/in_b
81 ? b/in_b
82 ? in_root
82 ? in_root
83 $ hg status --cwd b/1 .
83 $ hg status --cwd b/1 .
84 ? in_b_1
84 ? in_b_1
85 $ hg status --cwd b/1 ..
85 $ hg status --cwd b/1 ..
86 ? in_b_1
86 ? in_b_1
87 ? ../2/in_b_2
87 ? ../2/in_b_2
88 ? ../in_b
88 ? ../in_b
89
89
90 $ hg status --cwd b/2
90 $ hg status --cwd b/2
91 ? a/1/in_a_1
91 ? a/1/in_a_1
92 ? a/in_a
92 ? a/in_a
93 ? b/1/in_b_1
93 ? b/1/in_b_1
94 ? b/2/in_b_2
94 ? b/2/in_b_2
95 ? b/in_b
95 ? b/in_b
96 ? in_root
96 ? in_root
97 $ hg status --cwd b/2 .
97 $ hg status --cwd b/2 .
98 ? in_b_2
98 ? in_b_2
99 $ hg status --cwd b/2 ..
99 $ hg status --cwd b/2 ..
100 ? ../1/in_b_1
100 ? ../1/in_b_1
101 ? in_b_2
101 ? in_b_2
102 ? ../in_b
102 ? ../in_b
103
103
104 combining patterns with root and patterns without a root works
104 combining patterns with root and patterns without a root works
105
105
106 $ hg st a/in_a re:.*b$
106 $ hg st a/in_a re:.*b$
107 ? a/in_a
107 ? a/in_a
108 ? b/in_b
108 ? b/in_b
109
109
110 $ cd ..
110 $ cd ..
111
111
112 $ hg init repo2
112 $ hg init repo2
113 $ cd repo2
113 $ cd repo2
114 $ touch modified removed deleted ignored
114 $ touch modified removed deleted ignored
115 $ echo "^ignored$" > .hgignore
115 $ echo "^ignored$" > .hgignore
116 $ hg ci -A -m 'initial checkin'
116 $ hg ci -A -m 'initial checkin'
117 adding .hgignore
117 adding .hgignore
118 adding deleted
118 adding deleted
119 adding modified
119 adding modified
120 adding removed
120 adding removed
121 $ touch modified added unknown ignored
121 $ touch modified added unknown ignored
122 $ hg add added
122 $ hg add added
123 $ hg remove removed
123 $ hg remove removed
124 $ rm deleted
124 $ rm deleted
125
125
126 hg status:
126 hg status:
127
127
128 $ hg status
128 $ hg status
129 A added
129 A added
130 R removed
130 R removed
131 ! deleted
131 ! deleted
132 ? unknown
132 ? unknown
133
133
134 hg status modified added removed deleted unknown never-existed ignored:
134 hg status modified added removed deleted unknown never-existed ignored:
135
135
136 $ hg status modified added removed deleted unknown never-existed ignored
136 $ hg status modified added removed deleted unknown never-existed ignored
137 never-existed: * (glob)
137 never-existed: * (glob)
138 A added
138 A added
139 R removed
139 R removed
140 ! deleted
140 ! deleted
141 ? unknown
141 ? unknown
142
142
143 $ hg copy modified copied
143 $ hg copy modified copied
144
144
145 hg status -C:
145 hg status -C:
146
146
147 $ hg status -C
147 $ hg status -C
148 A added
148 A added
149 A copied
149 A copied
150 modified
150 modified
151 R removed
151 R removed
152 ! deleted
152 ! deleted
153 ? unknown
153 ? unknown
154
154
155 hg status -A:
155 hg status -A:
156
156
157 $ hg status -A
157 $ hg status -A
158 A added
158 A added
159 A copied
159 A copied
160 modified
160 modified
161 R removed
161 R removed
162 ! deleted
162 ! deleted
163 ? unknown
163 ? unknown
164 I ignored
164 I ignored
165 C .hgignore
165 C .hgignore
166 C modified
166 C modified
167
167
168 $ hg status -A -Tjson
168 $ hg status -A -Tjson
169 [
169 [
170 {
170 {
171 "path": "added",
171 "path": "added",
172 "status": "A"
172 "status": "A"
173 },
173 },
174 {
174 {
175 "copy": "modified",
175 "copy": "modified",
176 "path": "copied",
176 "path": "copied",
177 "status": "A"
177 "status": "A"
178 },
178 },
179 {
179 {
180 "path": "removed",
180 "path": "removed",
181 "status": "R"
181 "status": "R"
182 },
182 },
183 {
183 {
184 "path": "deleted",
184 "path": "deleted",
185 "status": "!"
185 "status": "!"
186 },
186 },
187 {
187 {
188 "path": "unknown",
188 "path": "unknown",
189 "status": "?"
189 "status": "?"
190 },
190 },
191 {
191 {
192 "path": "ignored",
192 "path": "ignored",
193 "status": "I"
193 "status": "I"
194 },
194 },
195 {
195 {
196 "path": ".hgignore",
196 "path": ".hgignore",
197 "status": "C"
197 "status": "C"
198 },
198 },
199 {
199 {
200 "path": "modified",
200 "path": "modified",
201 "status": "C"
201 "status": "C"
202 }
202 }
203 ]
203 ]
204
204
205 $ hg status -A -Tpickle > pickle
205 $ hg status -A -Tpickle > pickle
206 >>> import pickle
206 >>> import pickle
207 >>> print sorted((x['status'], x['path']) for x in pickle.load(open("pickle")))
207 >>> print sorted((x['status'], x['path']) for x in pickle.load(open("pickle")))
208 [('!', 'deleted'), ('?', 'pickle'), ('?', 'unknown'), ('A', 'added'), ('A', 'copied'), ('C', '.hgignore'), ('C', 'modified'), ('I', 'ignored'), ('R', 'removed')]
208 [('!', 'deleted'), ('?', 'pickle'), ('?', 'unknown'), ('A', 'added'), ('A', 'copied'), ('C', '.hgignore'), ('C', 'modified'), ('I', 'ignored'), ('R', 'removed')]
209 $ rm pickle
209 $ rm pickle
210
210
211 $ echo "^ignoreddir$" > .hgignore
211 $ echo "^ignoreddir$" > .hgignore
212 $ mkdir ignoreddir
212 $ mkdir ignoreddir
213 $ touch ignoreddir/file
213 $ touch ignoreddir/file
214
214
215 Test templater support:
215 Test templater support:
216
216
217 $ hg status -AT "[{status}]\t{if(copy, '{copy} -> ')}{path}\n"
217 $ hg status -AT "[{status}]\t{if(copy, '{copy} -> ')}{path}\n"
218 [M] .hgignore
218 [M] .hgignore
219 [A] added
219 [A] added
220 [A] modified -> copied
220 [A] modified -> copied
221 [R] removed
221 [R] removed
222 [!] deleted
222 [!] deleted
223 [?] ignored
223 [?] ignored
224 [?] unknown
224 [?] unknown
225 [I] ignoreddir/file
225 [I] ignoreddir/file
226 [C] modified
226 [C] modified
227 $ hg status -AT default
227 $ hg status -AT default
228 M .hgignore
228 M .hgignore
229 A added
229 A added
230 A copied
230 A copied
231 modified
231 modified
232 R removed
232 R removed
233 ! deleted
233 ! deleted
234 ? ignored
234 ? ignored
235 ? unknown
235 ? unknown
236 I ignoreddir/file
236 I ignoreddir/file
237 C modified
237 C modified
238 $ hg status -T compact
238 $ hg status -T compact
239 abort: "status" not in template map
239 abort: "status" not in template map
240 [255]
240 [255]
241
241
242 hg status ignoreddir/file:
242 hg status ignoreddir/file:
243
243
244 $ hg status ignoreddir/file
244 $ hg status ignoreddir/file
245
245
246 hg status -i ignoreddir/file:
246 hg status -i ignoreddir/file:
247
247
248 $ hg status -i ignoreddir/file
248 $ hg status -i ignoreddir/file
249 I ignoreddir/file
249 I ignoreddir/file
250 $ cd ..
250 $ cd ..
251
251
252 Check 'status -q' and some combinations
252 Check 'status -q' and some combinations
253
253
254 $ hg init repo3
254 $ hg init repo3
255 $ cd repo3
255 $ cd repo3
256 $ touch modified removed deleted ignored
256 $ touch modified removed deleted ignored
257 $ echo "^ignored$" > .hgignore
257 $ echo "^ignored$" > .hgignore
258 $ hg commit -A -m 'initial checkin'
258 $ hg commit -A -m 'initial checkin'
259 adding .hgignore
259 adding .hgignore
260 adding deleted
260 adding deleted
261 adding modified
261 adding modified
262 adding removed
262 adding removed
263 $ touch added unknown ignored
263 $ touch added unknown ignored
264 $ hg add added
264 $ hg add added
265 $ echo "test" >> modified
265 $ echo "test" >> modified
266 $ hg remove removed
266 $ hg remove removed
267 $ rm deleted
267 $ rm deleted
268 $ hg copy modified copied
268 $ hg copy modified copied
269
269
270 Specify working directory revision explicitly, that should be the same as
270 Specify working directory revision explicitly, that should be the same as
271 "hg status"
271 "hg status"
272
272
273 $ hg status --change "wdir()"
273 $ hg status --change "wdir()"
274 M modified
274 M modified
275 A added
275 A added
276 A copied
276 A copied
277 R removed
277 R removed
278 ! deleted
278 ! deleted
279 ? unknown
279 ? unknown
280
280
281 Run status with 2 different flags.
281 Run status with 2 different flags.
282 Check if result is the same or different.
282 Check if result is the same or different.
283 If result is not as expected, raise error
283 If result is not as expected, raise error
284
284
285 $ assert() {
285 $ assert() {
286 > hg status $1 > ../a
286 > hg status $1 > ../a
287 > hg status $2 > ../b
287 > hg status $2 > ../b
288 > if diff ../a ../b > /dev/null; then
288 > if diff ../a ../b > /dev/null; then
289 > out=0
289 > out=0
290 > else
290 > else
291 > out=1
291 > out=1
292 > fi
292 > fi
293 > if [ $3 -eq 0 ]; then
293 > if [ $3 -eq 0 ]; then
294 > df="same"
294 > df="same"
295 > else
295 > else
296 > df="different"
296 > df="different"
297 > fi
297 > fi
298 > if [ $out -ne $3 ]; then
298 > if [ $out -ne $3 ]; then
299 > echo "Error on $1 and $2, should be $df."
299 > echo "Error on $1 and $2, should be $df."
300 > fi
300 > fi
301 > }
301 > }
302
302
303 Assert flag1 flag2 [0-same | 1-different]
303 Assert flag1 flag2 [0-same | 1-different]
304
304
305 $ assert "-q" "-mard" 0
305 $ assert "-q" "-mard" 0
306 $ assert "-A" "-marduicC" 0
306 $ assert "-A" "-marduicC" 0
307 $ assert "-qA" "-mardcC" 0
307 $ assert "-qA" "-mardcC" 0
308 $ assert "-qAui" "-A" 0
308 $ assert "-qAui" "-A" 0
309 $ assert "-qAu" "-marducC" 0
309 $ assert "-qAu" "-marducC" 0
310 $ assert "-qAi" "-mardicC" 0
310 $ assert "-qAi" "-mardicC" 0
311 $ assert "-qu" "-u" 0
311 $ assert "-qu" "-u" 0
312 $ assert "-q" "-u" 1
312 $ assert "-q" "-u" 1
313 $ assert "-m" "-a" 1
313 $ assert "-m" "-a" 1
314 $ assert "-r" "-d" 1
314 $ assert "-r" "-d" 1
315 $ cd ..
315 $ cd ..
316
316
317 $ hg init repo4
317 $ hg init repo4
318 $ cd repo4
318 $ cd repo4
319 $ touch modified removed deleted
319 $ touch modified removed deleted
320 $ hg ci -q -A -m 'initial checkin'
320 $ hg ci -q -A -m 'initial checkin'
321 $ touch added unknown
321 $ touch added unknown
322 $ hg add added
322 $ hg add added
323 $ hg remove removed
323 $ hg remove removed
324 $ rm deleted
324 $ rm deleted
325 $ echo x > modified
325 $ echo x > modified
326 $ hg copy modified copied
326 $ hg copy modified copied
327 $ hg ci -m 'test checkin' -d "1000001 0"
327 $ hg ci -m 'test checkin' -d "1000001 0"
328 $ rm *
328 $ rm *
329 $ touch unrelated
329 $ touch unrelated
330 $ hg ci -q -A -m 'unrelated checkin' -d "1000002 0"
330 $ hg ci -q -A -m 'unrelated checkin' -d "1000002 0"
331
331
332 hg status --change 1:
332 hg status --change 1:
333
333
334 $ hg status --change 1
334 $ hg status --change 1
335 M modified
335 M modified
336 A added
336 A added
337 A copied
337 A copied
338 R removed
338 R removed
339
339
340 hg status --change 1 unrelated:
340 hg status --change 1 unrelated:
341
341
342 $ hg status --change 1 unrelated
342 $ hg status --change 1 unrelated
343
343
344 hg status -C --change 1 added modified copied removed deleted:
344 hg status -C --change 1 added modified copied removed deleted:
345
345
346 $ hg status -C --change 1 added modified copied removed deleted
346 $ hg status -C --change 1 added modified copied removed deleted
347 M modified
347 M modified
348 A added
348 A added
349 A copied
349 A copied
350 modified
350 modified
351 R removed
351 R removed
352
352
353 hg status -A --change 1 and revset:
353 hg status -A --change 1 and revset:
354
354
355 $ hg status -A --change '1|1'
355 $ hg status -A --change '1|1'
356 M modified
356 M modified
357 A added
357 A added
358 A copied
358 A copied
359 modified
359 modified
360 R removed
360 R removed
361 C deleted
361 C deleted
362
362
363 $ cd ..
363 $ cd ..
364
364
365 hg status with --rev and reverted changes:
366
367 $ hg init reverted-changes-repo
368 $ cd reverted-changes-repo
369 $ echo a > file
370 $ hg add file
371 $ hg ci -m a
372 $ echo b > file
373 $ hg ci -m b
374
375 reverted file should appear clean
376
377 $ hg revert -r 0 .
378 reverting file
379 $ hg status -A --rev 0
380 C file
381
382 #if execbit
383 reverted file with changed flag should appear modified
384
385 $ chmod +x file
386 $ hg status -A --rev 0
387 M file
388
389 #endif
390
391 $ cd ..
392
365 hg status of binary file starting with '\1\n', a separator for metadata:
393 hg status of binary file starting with '\1\n', a separator for metadata:
366
394
367 $ hg init repo5
395 $ hg init repo5
368 $ cd repo5
396 $ cd repo5
369 >>> open("010a", "wb").write("\1\nfoo")
397 >>> open("010a", "wb").write("\1\nfoo")
370 $ hg ci -q -A -m 'initial checkin'
398 $ hg ci -q -A -m 'initial checkin'
371 $ hg status -A
399 $ hg status -A
372 C 010a
400 C 010a
373
401
374 >>> open("010a", "wb").write("\1\nbar")
402 >>> open("010a", "wb").write("\1\nbar")
375 $ hg status -A
403 $ hg status -A
376 M 010a
404 M 010a
377 $ hg ci -q -m 'modify 010a'
405 $ hg ci -q -m 'modify 010a'
378 $ hg status -A --rev 0:1
406 $ hg status -A --rev 0:1
379 M 010a
407 M 010a
380
408
381 $ touch empty
409 $ touch empty
382 $ hg ci -q -A -m 'add another file'
410 $ hg ci -q -A -m 'add another file'
383 $ hg status -A --rev 1:2 010a
411 $ hg status -A --rev 1:2 010a
384 C 010a
412 C 010a
385
413
386 $ cd ..
414 $ cd ..
387
415
388 test "hg status" with "directory pattern" which matches against files
416 test "hg status" with "directory pattern" which matches against files
389 only known on target revision.
417 only known on target revision.
390
418
391 $ hg init repo6
419 $ hg init repo6
392 $ cd repo6
420 $ cd repo6
393
421
394 $ echo a > a.txt
422 $ echo a > a.txt
395 $ hg add a.txt
423 $ hg add a.txt
396 $ hg commit -m '#0'
424 $ hg commit -m '#0'
397 $ mkdir -p 1/2/3/4/5
425 $ mkdir -p 1/2/3/4/5
398 $ echo b > 1/2/3/4/5/b.txt
426 $ echo b > 1/2/3/4/5/b.txt
399 $ hg add 1/2/3/4/5/b.txt
427 $ hg add 1/2/3/4/5/b.txt
400 $ hg commit -m '#1'
428 $ hg commit -m '#1'
401
429
402 $ hg update -C 0 > /dev/null
430 $ hg update -C 0 > /dev/null
403 $ hg status -A
431 $ hg status -A
404 C a.txt
432 C a.txt
405
433
406 the directory matching against specified pattern should be removed,
434 the directory matching against specified pattern should be removed,
407 because directory existence prevents 'dirstate.walk()' from showing
435 because directory existence prevents 'dirstate.walk()' from showing
408 warning message about such pattern.
436 warning message about such pattern.
409
437
410 $ test ! -d 1
438 $ test ! -d 1
411 $ hg status -A --rev 1 1/2/3/4/5/b.txt
439 $ hg status -A --rev 1 1/2/3/4/5/b.txt
412 R 1/2/3/4/5/b.txt
440 R 1/2/3/4/5/b.txt
413 $ hg status -A --rev 1 1/2/3/4/5
441 $ hg status -A --rev 1 1/2/3/4/5
414 R 1/2/3/4/5/b.txt
442 R 1/2/3/4/5/b.txt
415 $ hg status -A --rev 1 1/2/3
443 $ hg status -A --rev 1 1/2/3
416 R 1/2/3/4/5/b.txt
444 R 1/2/3/4/5/b.txt
417 $ hg status -A --rev 1 1
445 $ hg status -A --rev 1 1
418 R 1/2/3/4/5/b.txt
446 R 1/2/3/4/5/b.txt
419
447
420 $ hg status --config ui.formatdebug=True --rev 1 1
448 $ hg status --config ui.formatdebug=True --rev 1 1
421 status = [
449 status = [
422 {*'path': '1/2/3/4/5/b.txt'*}, (glob)
450 {*'path': '1/2/3/4/5/b.txt'*}, (glob)
423 ]
451 ]
424
452
425 #if windows
453 #if windows
426 $ hg --config ui.slash=false status -A --rev 1 1
454 $ hg --config ui.slash=false status -A --rev 1 1
427 R 1\2\3\4\5\b.txt
455 R 1\2\3\4\5\b.txt
428 #endif
456 #endif
429
457
430 $ cd ..
458 $ cd ..
431
459
432 Status after move overwriting a file (issue4458)
460 Status after move overwriting a file (issue4458)
433 =================================================
461 =================================================
434
462
435
463
436 $ hg init issue4458
464 $ hg init issue4458
437 $ cd issue4458
465 $ cd issue4458
438 $ echo a > a
466 $ echo a > a
439 $ echo b > b
467 $ echo b > b
440 $ hg commit -Am base
468 $ hg commit -Am base
441 adding a
469 adding a
442 adding b
470 adding b
443
471
444
472
445 with --force
473 with --force
446
474
447 $ hg mv b --force a
475 $ hg mv b --force a
448 $ hg st --copies
476 $ hg st --copies
449 M a
477 M a
450 b
478 b
451 R b
479 R b
452 $ hg revert --all
480 $ hg revert --all
453 reverting a
481 reverting a
454 undeleting b
482 undeleting b
455 $ rm *.orig
483 $ rm *.orig
456
484
457 without force
485 without force
458
486
459 $ hg rm a
487 $ hg rm a
460 $ hg st --copies
488 $ hg st --copies
461 R a
489 R a
462 $ hg mv b a
490 $ hg mv b a
463 $ hg st --copies
491 $ hg st --copies
464 M a
492 M a
465 b
493 b
466 R b
494 R b
467
495
468 using ui.statuscopies setting
496 using ui.statuscopies setting
469 $ hg st --config ui.statuscopies=true
497 $ hg st --config ui.statuscopies=true
470 M a
498 M a
471 b
499 b
472 R b
500 R b
473 $ hg st --config ui.statuscopies=false
501 $ hg st --config ui.statuscopies=false
474 M a
502 M a
475 R b
503 R b
476
504
477 Other "bug" highlight, the revision status does not report the copy information.
505 Other "bug" highlight, the revision status does not report the copy information.
478 This is buggy behavior.
506 This is buggy behavior.
479
507
480 $ hg commit -m 'blah'
508 $ hg commit -m 'blah'
481 $ hg st --copies --change .
509 $ hg st --copies --change .
482 M a
510 M a
483 R b
511 R b
484
512
485 $ cd ..
513 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now