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