##// END OF EJS Templates
basectx: copy localrepo.status method...
Sean Farley -
r21594:9e456782 default
parent child Browse files
Show More
@@ -1,1617 +1,1677 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 node import nullid, nullrev, short, hex, bin
8 from node import nullid, nullrev, short, hex, bin
9 from i18n import _
9 from i18n import _
10 import mdiff, error, util, scmutil, subrepo, patch, encoding, phases
10 import mdiff, error, util, scmutil, subrepo, patch, encoding, phases
11 import match as matchmod
11 import match as matchmod
12 import os, errno, stat
12 import os, errno, stat
13 import obsolete as obsmod
13 import obsolete as obsmod
14 import repoview
14 import repoview
15 import fileset
15 import fileset
16
16
17 propertycache = util.propertycache
17 propertycache = util.propertycache
18
18
19 class basectx(object):
19 class basectx(object):
20 """A basectx object represents the common logic for its children:
20 """A basectx object represents the common logic for its children:
21 changectx: read-only context that is already present in the repo,
21 changectx: read-only context that is already present in the repo,
22 workingctx: a context that represents the working directory and can
22 workingctx: a context that represents the working directory and can
23 be committed,
23 be committed,
24 memctx: a context that represents changes in-memory and can also
24 memctx: a context that represents changes in-memory and can also
25 be committed."""
25 be committed."""
26 def __new__(cls, repo, changeid='', *args, **kwargs):
26 def __new__(cls, repo, changeid='', *args, **kwargs):
27 if isinstance(changeid, basectx):
27 if isinstance(changeid, basectx):
28 return changeid
28 return changeid
29
29
30 o = super(basectx, cls).__new__(cls)
30 o = super(basectx, cls).__new__(cls)
31
31
32 o._repo = repo
32 o._repo = repo
33 o._rev = nullrev
33 o._rev = nullrev
34 o._node = nullid
34 o._node = nullid
35
35
36 return o
36 return o
37
37
38 def __str__(self):
38 def __str__(self):
39 return short(self.node())
39 return short(self.node())
40
40
41 def __int__(self):
41 def __int__(self):
42 return self.rev()
42 return self.rev()
43
43
44 def __repr__(self):
44 def __repr__(self):
45 return "<%s %s>" % (type(self).__name__, str(self))
45 return "<%s %s>" % (type(self).__name__, str(self))
46
46
47 def __eq__(self, other):
47 def __eq__(self, other):
48 try:
48 try:
49 return type(self) == type(other) and self._rev == other._rev
49 return type(self) == type(other) and self._rev == other._rev
50 except AttributeError:
50 except AttributeError:
51 return False
51 return False
52
52
53 def __ne__(self, other):
53 def __ne__(self, other):
54 return not (self == other)
54 return not (self == other)
55
55
56 def __contains__(self, key):
56 def __contains__(self, key):
57 return key in self._manifest
57 return key in self._manifest
58
58
59 def __getitem__(self, key):
59 def __getitem__(self, key):
60 return self.filectx(key)
60 return self.filectx(key)
61
61
62 def __iter__(self):
62 def __iter__(self):
63 for f in sorted(self._manifest):
63 for f in sorted(self._manifest):
64 yield f
64 yield f
65
65
66 def _manifestmatches(self, match, s):
66 def _manifestmatches(self, match, s):
67 """generate a new manifest filtered by the match argument
67 """generate a new manifest filtered by the match argument
68
68
69 This method is for internal use only and mainly exists to provide an
69 This method is for internal use only and mainly exists to provide an
70 object oriented way for other contexts to customize the manifest
70 object oriented way for other contexts to customize the manifest
71 generation.
71 generation.
72 """
72 """
73 mf = self.manifest().copy()
73 mf = self.manifest().copy()
74 if match.always():
74 if match.always():
75 return mf
75 return mf
76 for fn in mf.keys():
76 for fn in mf.keys():
77 if not match(fn):
77 if not match(fn):
78 del mf[fn]
78 del mf[fn]
79 return mf
79 return mf
80
80
81 def _matchstatus(self, other, s, match, listignored, listclean,
81 def _matchstatus(self, other, s, match, listignored, listclean,
82 listunknown):
82 listunknown):
83 """return match.always if match is none
83 """return match.always if match is none
84
84
85 This internal method provides a way for child objects to override the
85 This internal method provides a way for child objects to override the
86 match operator.
86 match operator.
87 """
87 """
88 return match or matchmod.always(self._repo.root, self._repo.getcwd())
88 return match or matchmod.always(self._repo.root, self._repo.getcwd())
89
89
90 def _prestatus(self, other, s, match, listignored, listclean, listunknown):
90 def _prestatus(self, other, s, match, listignored, listclean, listunknown):
91 """provide a hook to allow child objects to preprocess status results
91 """provide a hook to allow child objects to preprocess status results
92
92
93 For example, this allows other contexts, such as workingctx, to query
93 For example, this allows other contexts, such as workingctx, to query
94 the dirstate before comparing the manifests.
94 the dirstate before comparing the manifests.
95 """
95 """
96 # load earliest manifest first for caching reasons
96 # load earliest manifest first for caching reasons
97 if self.rev() < other.rev():
97 if self.rev() < other.rev():
98 self.manifest()
98 self.manifest()
99 return s
99 return s
100
100
101 def _poststatus(self, other, s, match, listignored, listclean, listunknown):
101 def _poststatus(self, other, s, match, listignored, listclean, listunknown):
102 """provide a hook to allow child objects to postprocess status results
102 """provide a hook to allow child objects to postprocess status results
103
103
104 For example, this allows other contexts, such as workingctx, to filter
104 For example, this allows other contexts, such as workingctx, to filter
105 suspect symlinks in the case of FAT32 and NTFS filesytems.
105 suspect symlinks in the case of FAT32 and NTFS filesytems.
106 """
106 """
107 return s
107 return s
108
108
109 def _buildstatus(self, other, s, match, listignored, listclean,
109 def _buildstatus(self, other, s, match, listignored, listclean,
110 listunknown):
110 listunknown):
111 """build a status with respect to another context"""
111 """build a status with respect to another context"""
112 mf1 = other._manifestmatches(match, s)
112 mf1 = other._manifestmatches(match, s)
113 mf2 = self._manifestmatches(match, s)
113 mf2 = self._manifestmatches(match, s)
114
114
115 modified, added, clean = [], [], []
115 modified, added, clean = [], [], []
116 deleted, unknown, ignored = s[3], [], []
116 deleted, unknown, ignored = s[3], [], []
117 withflags = mf1.withflags() | mf2.withflags()
117 withflags = mf1.withflags() | mf2.withflags()
118 for fn, mf2node in mf2.iteritems():
118 for fn, mf2node in mf2.iteritems():
119 if fn in mf1:
119 if fn in mf1:
120 if (fn not in deleted and
120 if (fn not in deleted and
121 ((fn in withflags and mf1.flags(fn) != mf2.flags(fn)) or
121 ((fn in withflags and mf1.flags(fn) != mf2.flags(fn)) or
122 (mf1[fn] != mf2node and
122 (mf1[fn] != mf2node and
123 (mf2node or self[fn].cmp(other[fn]))))):
123 (mf2node or self[fn].cmp(other[fn]))))):
124 modified.append(fn)
124 modified.append(fn)
125 elif listclean:
125 elif listclean:
126 clean.append(fn)
126 clean.append(fn)
127 del mf1[fn]
127 del mf1[fn]
128 elif fn not in deleted:
128 elif fn not in deleted:
129 added.append(fn)
129 added.append(fn)
130 removed = mf1.keys()
130 removed = mf1.keys()
131
131
132 return [modified, added, removed, deleted, unknown, ignored, clean]
132 return [modified, added, removed, deleted, unknown, ignored, clean]
133
133
134 @propertycache
134 @propertycache
135 def substate(self):
135 def substate(self):
136 return subrepo.state(self, self._repo.ui)
136 return subrepo.state(self, self._repo.ui)
137
137
138 def subrev(self, subpath):
138 def subrev(self, subpath):
139 return self.substate[subpath][1]
139 return self.substate[subpath][1]
140
140
141 def rev(self):
141 def rev(self):
142 return self._rev
142 return self._rev
143 def node(self):
143 def node(self):
144 return self._node
144 return self._node
145 def hex(self):
145 def hex(self):
146 return hex(self.node())
146 return hex(self.node())
147 def manifest(self):
147 def manifest(self):
148 return self._manifest
148 return self._manifest
149 def phasestr(self):
149 def phasestr(self):
150 return phases.phasenames[self.phase()]
150 return phases.phasenames[self.phase()]
151 def mutable(self):
151 def mutable(self):
152 return self.phase() > phases.public
152 return self.phase() > phases.public
153
153
154 def getfileset(self, expr):
154 def getfileset(self, expr):
155 return fileset.getfileset(self, expr)
155 return fileset.getfileset(self, expr)
156
156
157 def obsolete(self):
157 def obsolete(self):
158 """True if the changeset is obsolete"""
158 """True if the changeset is obsolete"""
159 return self.rev() in obsmod.getrevs(self._repo, 'obsolete')
159 return self.rev() in obsmod.getrevs(self._repo, 'obsolete')
160
160
161 def extinct(self):
161 def extinct(self):
162 """True if the changeset is extinct"""
162 """True if the changeset is extinct"""
163 return self.rev() in obsmod.getrevs(self._repo, 'extinct')
163 return self.rev() in obsmod.getrevs(self._repo, 'extinct')
164
164
165 def unstable(self):
165 def unstable(self):
166 """True if the changeset is not obsolete but it's ancestor are"""
166 """True if the changeset is not obsolete but it's ancestor are"""
167 return self.rev() in obsmod.getrevs(self._repo, 'unstable')
167 return self.rev() in obsmod.getrevs(self._repo, 'unstable')
168
168
169 def bumped(self):
169 def bumped(self):
170 """True if the changeset try to be a successor of a public changeset
170 """True if the changeset try to be a successor of a public changeset
171
171
172 Only non-public and non-obsolete changesets may be bumped.
172 Only non-public and non-obsolete changesets may be bumped.
173 """
173 """
174 return self.rev() in obsmod.getrevs(self._repo, 'bumped')
174 return self.rev() in obsmod.getrevs(self._repo, 'bumped')
175
175
176 def divergent(self):
176 def divergent(self):
177 """Is a successors of a changeset with multiple possible successors set
177 """Is a successors of a changeset with multiple possible successors set
178
178
179 Only non-public and non-obsolete changesets may be divergent.
179 Only non-public and non-obsolete changesets may be divergent.
180 """
180 """
181 return self.rev() in obsmod.getrevs(self._repo, 'divergent')
181 return self.rev() in obsmod.getrevs(self._repo, 'divergent')
182
182
183 def troubled(self):
183 def troubled(self):
184 """True if the changeset is either unstable, bumped or divergent"""
184 """True if the changeset is either unstable, bumped or divergent"""
185 return self.unstable() or self.bumped() or self.divergent()
185 return self.unstable() or self.bumped() or self.divergent()
186
186
187 def troubles(self):
187 def troubles(self):
188 """return the list of troubles affecting this changesets.
188 """return the list of troubles affecting this changesets.
189
189
190 Troubles are returned as strings. possible values are:
190 Troubles are returned as strings. possible values are:
191 - unstable,
191 - unstable,
192 - bumped,
192 - bumped,
193 - divergent.
193 - divergent.
194 """
194 """
195 troubles = []
195 troubles = []
196 if self.unstable():
196 if self.unstable():
197 troubles.append('unstable')
197 troubles.append('unstable')
198 if self.bumped():
198 if self.bumped():
199 troubles.append('bumped')
199 troubles.append('bumped')
200 if self.divergent():
200 if self.divergent():
201 troubles.append('divergent')
201 troubles.append('divergent')
202 return troubles
202 return troubles
203
203
204 def parents(self):
204 def parents(self):
205 """return contexts for each parent changeset"""
205 """return contexts for each parent changeset"""
206 return self._parents
206 return self._parents
207
207
208 def p1(self):
208 def p1(self):
209 return self._parents[0]
209 return self._parents[0]
210
210
211 def p2(self):
211 def p2(self):
212 if len(self._parents) == 2:
212 if len(self._parents) == 2:
213 return self._parents[1]
213 return self._parents[1]
214 return changectx(self._repo, -1)
214 return changectx(self._repo, -1)
215
215
216 def _fileinfo(self, path):
216 def _fileinfo(self, path):
217 if '_manifest' in self.__dict__:
217 if '_manifest' in self.__dict__:
218 try:
218 try:
219 return self._manifest[path], self._manifest.flags(path)
219 return self._manifest[path], self._manifest.flags(path)
220 except KeyError:
220 except KeyError:
221 raise error.ManifestLookupError(self._node, path,
221 raise error.ManifestLookupError(self._node, path,
222 _('not found in manifest'))
222 _('not found in manifest'))
223 if '_manifestdelta' in self.__dict__ or path in self.files():
223 if '_manifestdelta' in self.__dict__ or path in self.files():
224 if path in self._manifestdelta:
224 if path in self._manifestdelta:
225 return (self._manifestdelta[path],
225 return (self._manifestdelta[path],
226 self._manifestdelta.flags(path))
226 self._manifestdelta.flags(path))
227 node, flag = self._repo.manifest.find(self._changeset[0], path)
227 node, flag = self._repo.manifest.find(self._changeset[0], path)
228 if not node:
228 if not node:
229 raise error.ManifestLookupError(self._node, path,
229 raise error.ManifestLookupError(self._node, path,
230 _('not found in manifest'))
230 _('not found in manifest'))
231
231
232 return node, flag
232 return node, flag
233
233
234 def filenode(self, path):
234 def filenode(self, path):
235 return self._fileinfo(path)[0]
235 return self._fileinfo(path)[0]
236
236
237 def flags(self, path):
237 def flags(self, path):
238 try:
238 try:
239 return self._fileinfo(path)[1]
239 return self._fileinfo(path)[1]
240 except error.LookupError:
240 except error.LookupError:
241 return ''
241 return ''
242
242
243 def sub(self, path):
243 def sub(self, path):
244 return subrepo.subrepo(self, path)
244 return subrepo.subrepo(self, path)
245
245
246 def match(self, pats=[], include=None, exclude=None, default='glob'):
246 def match(self, pats=[], include=None, exclude=None, default='glob'):
247 r = self._repo
247 r = self._repo
248 return matchmod.match(r.root, r.getcwd(), pats,
248 return matchmod.match(r.root, r.getcwd(), pats,
249 include, exclude, default,
249 include, exclude, default,
250 auditor=r.auditor, ctx=self)
250 auditor=r.auditor, ctx=self)
251
251
252 def diff(self, ctx2=None, match=None, **opts):
252 def diff(self, ctx2=None, match=None, **opts):
253 """Returns a diff generator for the given contexts and matcher"""
253 """Returns a diff generator for the given contexts and matcher"""
254 if ctx2 is None:
254 if ctx2 is None:
255 ctx2 = self.p1()
255 ctx2 = self.p1()
256 if ctx2 is not None:
256 if ctx2 is not None:
257 ctx2 = self._repo[ctx2]
257 ctx2 = self._repo[ctx2]
258 diffopts = patch.diffopts(self._repo.ui, opts)
258 diffopts = patch.diffopts(self._repo.ui, opts)
259 return patch.diff(self._repo, ctx2.node(), self.node(),
259 return patch.diff(self._repo, ctx2.node(), self.node(),
260 match=match, opts=diffopts)
260 match=match, opts=diffopts)
261
261
262 @propertycache
262 @propertycache
263 def _dirs(self):
263 def _dirs(self):
264 return scmutil.dirs(self._manifest)
264 return scmutil.dirs(self._manifest)
265
265
266 def dirs(self):
266 def dirs(self):
267 return self._dirs
267 return self._dirs
268
268
269 def dirty(self):
269 def dirty(self):
270 return False
270 return False
271
271
272 def status(self, other=None, match=None, listignored=False,
273 listclean=False, listunknown=False, listsubrepos=False):
274 """return status of files between two nodes or node and working
275 directory.
276
277 If other is None, compare this node with working directory.
278 """
279
280 ctx1 = self
281 ctx2 = self._repo[other]
282
283 # This next code block is, admittedly, fragile logic that tests for
284 # reversing the contexts and wouldn't need to exist if it weren't for
285 # the fast (and common) code path of comparing the working directory
286 # with its first parent.
287 #
288 # What we're aiming for here is the ability to call:
289 #
290 # workingctx.status(parentctx)
291 #
292 # If we always built the manifest for each context and compared those,
293 # then we'd be done. But the special case of the above call means we
294 # just copy the manifest of the parent.
295 reversed = False
296 if (not isinstance(ctx1, changectx)
297 and isinstance(ctx2, changectx)):
298 reversed = True
299 ctx1, ctx2 = ctx2, ctx1
300
301 r = [[], [], [], [], [], [], []]
302 match = ctx2._matchstatus(ctx1, r, match, listignored, listclean,
303 listunknown)
304 r = ctx2._prestatus(ctx1, r, match, listignored, listclean, listunknown)
305 r = ctx2._buildstatus(ctx1, r, match, listignored, listclean,
306 listunknown)
307 r = ctx2._poststatus(ctx1, r, match, listignored, listclean,
308 listunknown)
309
310 if reversed:
311 r[1], r[2], r[3], r[4] = r[2], r[1], r[4], r[3]
312
313 if listsubrepos:
314 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
315 rev2 = ctx2.subrev(subpath)
316 try:
317 submatch = matchmod.narrowmatcher(subpath, match)
318 s = sub.status(rev2, match=submatch, ignored=listignored,
319 clean=listclean, unknown=listunknown,
320 listsubrepos=True)
321 for rfiles, sfiles in zip(r, s):
322 rfiles.extend("%s/%s" % (subpath, f) for f in sfiles)
323 except error.LookupError:
324 self._repo.ui.status(_("skipping missing "
325 "subrepository: %s\n") % subpath)
326
327 for l in r:
328 l.sort()
329 return r
330
331
272 def makememctx(repo, parents, text, user, date, branch, files, store,
332 def makememctx(repo, parents, text, user, date, branch, files, store,
273 editor=None):
333 editor=None):
274 def getfilectx(repo, memctx, path):
334 def getfilectx(repo, memctx, path):
275 data, (islink, isexec), copied = store.getfile(path)
335 data, (islink, isexec), copied = store.getfile(path)
276 return memfilectx(path, data, islink=islink, isexec=isexec,
336 return memfilectx(path, data, islink=islink, isexec=isexec,
277 copied=copied)
337 copied=copied)
278 extra = {}
338 extra = {}
279 if branch:
339 if branch:
280 extra['branch'] = encoding.fromlocal(branch)
340 extra['branch'] = encoding.fromlocal(branch)
281 ctx = memctx(repo, parents, text, files, getfilectx, user,
341 ctx = memctx(repo, parents, text, files, getfilectx, user,
282 date, extra, editor)
342 date, extra, editor)
283 return ctx
343 return ctx
284
344
285 class changectx(basectx):
345 class changectx(basectx):
286 """A changecontext object makes access to data related to a particular
346 """A changecontext object makes access to data related to a particular
287 changeset convenient. It represents a read-only context already present in
347 changeset convenient. It represents a read-only context already present in
288 the repo."""
348 the repo."""
289 def __init__(self, repo, changeid=''):
349 def __init__(self, repo, changeid=''):
290 """changeid is a revision number, node, or tag"""
350 """changeid is a revision number, node, or tag"""
291
351
292 # since basectx.__new__ already took care of copying the object, we
352 # since basectx.__new__ already took care of copying the object, we
293 # don't need to do anything in __init__, so we just exit here
353 # don't need to do anything in __init__, so we just exit here
294 if isinstance(changeid, basectx):
354 if isinstance(changeid, basectx):
295 return
355 return
296
356
297 if changeid == '':
357 if changeid == '':
298 changeid = '.'
358 changeid = '.'
299 self._repo = repo
359 self._repo = repo
300
360
301 if isinstance(changeid, int):
361 if isinstance(changeid, int):
302 try:
362 try:
303 self._node = repo.changelog.node(changeid)
363 self._node = repo.changelog.node(changeid)
304 except IndexError:
364 except IndexError:
305 raise error.RepoLookupError(
365 raise error.RepoLookupError(
306 _("unknown revision '%s'") % changeid)
366 _("unknown revision '%s'") % changeid)
307 self._rev = changeid
367 self._rev = changeid
308 return
368 return
309 if isinstance(changeid, long):
369 if isinstance(changeid, long):
310 changeid = str(changeid)
370 changeid = str(changeid)
311 if changeid == '.':
371 if changeid == '.':
312 self._node = repo.dirstate.p1()
372 self._node = repo.dirstate.p1()
313 self._rev = repo.changelog.rev(self._node)
373 self._rev = repo.changelog.rev(self._node)
314 return
374 return
315 if changeid == 'null':
375 if changeid == 'null':
316 self._node = nullid
376 self._node = nullid
317 self._rev = nullrev
377 self._rev = nullrev
318 return
378 return
319 if changeid == 'tip':
379 if changeid == 'tip':
320 self._node = repo.changelog.tip()
380 self._node = repo.changelog.tip()
321 self._rev = repo.changelog.rev(self._node)
381 self._rev = repo.changelog.rev(self._node)
322 return
382 return
323 if len(changeid) == 20:
383 if len(changeid) == 20:
324 try:
384 try:
325 self._node = changeid
385 self._node = changeid
326 self._rev = repo.changelog.rev(changeid)
386 self._rev = repo.changelog.rev(changeid)
327 return
387 return
328 except LookupError:
388 except LookupError:
329 pass
389 pass
330
390
331 try:
391 try:
332 r = int(changeid)
392 r = int(changeid)
333 if str(r) != changeid:
393 if str(r) != changeid:
334 raise ValueError
394 raise ValueError
335 l = len(repo.changelog)
395 l = len(repo.changelog)
336 if r < 0:
396 if r < 0:
337 r += l
397 r += l
338 if r < 0 or r >= l:
398 if r < 0 or r >= l:
339 raise ValueError
399 raise ValueError
340 self._rev = r
400 self._rev = r
341 self._node = repo.changelog.node(r)
401 self._node = repo.changelog.node(r)
342 return
402 return
343 except (ValueError, OverflowError, IndexError):
403 except (ValueError, OverflowError, IndexError):
344 pass
404 pass
345
405
346 if len(changeid) == 40:
406 if len(changeid) == 40:
347 try:
407 try:
348 self._node = bin(changeid)
408 self._node = bin(changeid)
349 self._rev = repo.changelog.rev(self._node)
409 self._rev = repo.changelog.rev(self._node)
350 return
410 return
351 except (TypeError, LookupError):
411 except (TypeError, LookupError):
352 pass
412 pass
353
413
354 if changeid in repo._bookmarks:
414 if changeid in repo._bookmarks:
355 self._node = repo._bookmarks[changeid]
415 self._node = repo._bookmarks[changeid]
356 self._rev = repo.changelog.rev(self._node)
416 self._rev = repo.changelog.rev(self._node)
357 return
417 return
358 if changeid in repo._tagscache.tags:
418 if changeid in repo._tagscache.tags:
359 self._node = repo._tagscache.tags[changeid]
419 self._node = repo._tagscache.tags[changeid]
360 self._rev = repo.changelog.rev(self._node)
420 self._rev = repo.changelog.rev(self._node)
361 return
421 return
362 try:
422 try:
363 self._node = repo.branchtip(changeid)
423 self._node = repo.branchtip(changeid)
364 self._rev = repo.changelog.rev(self._node)
424 self._rev = repo.changelog.rev(self._node)
365 return
425 return
366 except error.RepoLookupError:
426 except error.RepoLookupError:
367 pass
427 pass
368
428
369 self._node = repo.changelog._partialmatch(changeid)
429 self._node = repo.changelog._partialmatch(changeid)
370 if self._node is not None:
430 if self._node is not None:
371 self._rev = repo.changelog.rev(self._node)
431 self._rev = repo.changelog.rev(self._node)
372 return
432 return
373
433
374 # lookup failed
434 # lookup failed
375 # check if it might have come from damaged dirstate
435 # check if it might have come from damaged dirstate
376 #
436 #
377 # XXX we could avoid the unfiltered if we had a recognizable exception
437 # XXX we could avoid the unfiltered if we had a recognizable exception
378 # for filtered changeset access
438 # for filtered changeset access
379 if changeid in repo.unfiltered().dirstate.parents():
439 if changeid in repo.unfiltered().dirstate.parents():
380 raise error.Abort(_("working directory has unknown parent '%s'!")
440 raise error.Abort(_("working directory has unknown parent '%s'!")
381 % short(changeid))
441 % short(changeid))
382 try:
442 try:
383 if len(changeid) == 20:
443 if len(changeid) == 20:
384 changeid = hex(changeid)
444 changeid = hex(changeid)
385 except TypeError:
445 except TypeError:
386 pass
446 pass
387 raise error.RepoLookupError(
447 raise error.RepoLookupError(
388 _("unknown revision '%s'") % changeid)
448 _("unknown revision '%s'") % changeid)
389
449
390 def __hash__(self):
450 def __hash__(self):
391 try:
451 try:
392 return hash(self._rev)
452 return hash(self._rev)
393 except AttributeError:
453 except AttributeError:
394 return id(self)
454 return id(self)
395
455
396 def __nonzero__(self):
456 def __nonzero__(self):
397 return self._rev != nullrev
457 return self._rev != nullrev
398
458
399 @propertycache
459 @propertycache
400 def _changeset(self):
460 def _changeset(self):
401 return self._repo.changelog.read(self.rev())
461 return self._repo.changelog.read(self.rev())
402
462
403 @propertycache
463 @propertycache
404 def _manifest(self):
464 def _manifest(self):
405 return self._repo.manifest.read(self._changeset[0])
465 return self._repo.manifest.read(self._changeset[0])
406
466
407 @propertycache
467 @propertycache
408 def _manifestdelta(self):
468 def _manifestdelta(self):
409 return self._repo.manifest.readdelta(self._changeset[0])
469 return self._repo.manifest.readdelta(self._changeset[0])
410
470
411 @propertycache
471 @propertycache
412 def _parents(self):
472 def _parents(self):
413 p = self._repo.changelog.parentrevs(self._rev)
473 p = self._repo.changelog.parentrevs(self._rev)
414 if p[1] == nullrev:
474 if p[1] == nullrev:
415 p = p[:-1]
475 p = p[:-1]
416 return [changectx(self._repo, x) for x in p]
476 return [changectx(self._repo, x) for x in p]
417
477
418 def changeset(self):
478 def changeset(self):
419 return self._changeset
479 return self._changeset
420 def manifestnode(self):
480 def manifestnode(self):
421 return self._changeset[0]
481 return self._changeset[0]
422
482
423 def user(self):
483 def user(self):
424 return self._changeset[1]
484 return self._changeset[1]
425 def date(self):
485 def date(self):
426 return self._changeset[2]
486 return self._changeset[2]
427 def files(self):
487 def files(self):
428 return self._changeset[3]
488 return self._changeset[3]
429 def description(self):
489 def description(self):
430 return self._changeset[4]
490 return self._changeset[4]
431 def branch(self):
491 def branch(self):
432 return encoding.tolocal(self._changeset[5].get("branch"))
492 return encoding.tolocal(self._changeset[5].get("branch"))
433 def closesbranch(self):
493 def closesbranch(self):
434 return 'close' in self._changeset[5]
494 return 'close' in self._changeset[5]
435 def extra(self):
495 def extra(self):
436 return self._changeset[5]
496 return self._changeset[5]
437 def tags(self):
497 def tags(self):
438 return self._repo.nodetags(self._node)
498 return self._repo.nodetags(self._node)
439 def bookmarks(self):
499 def bookmarks(self):
440 return self._repo.nodebookmarks(self._node)
500 return self._repo.nodebookmarks(self._node)
441 def phase(self):
501 def phase(self):
442 return self._repo._phasecache.phase(self._repo, self._rev)
502 return self._repo._phasecache.phase(self._repo, self._rev)
443 def hidden(self):
503 def hidden(self):
444 return self._rev in repoview.filterrevs(self._repo, 'visible')
504 return self._rev in repoview.filterrevs(self._repo, 'visible')
445
505
446 def children(self):
506 def children(self):
447 """return contexts for each child changeset"""
507 """return contexts for each child changeset"""
448 c = self._repo.changelog.children(self._node)
508 c = self._repo.changelog.children(self._node)
449 return [changectx(self._repo, x) for x in c]
509 return [changectx(self._repo, x) for x in c]
450
510
451 def ancestors(self):
511 def ancestors(self):
452 for a in self._repo.changelog.ancestors([self._rev]):
512 for a in self._repo.changelog.ancestors([self._rev]):
453 yield changectx(self._repo, a)
513 yield changectx(self._repo, a)
454
514
455 def descendants(self):
515 def descendants(self):
456 for d in self._repo.changelog.descendants([self._rev]):
516 for d in self._repo.changelog.descendants([self._rev]):
457 yield changectx(self._repo, d)
517 yield changectx(self._repo, d)
458
518
459 def filectx(self, path, fileid=None, filelog=None):
519 def filectx(self, path, fileid=None, filelog=None):
460 """get a file context from this changeset"""
520 """get a file context from this changeset"""
461 if fileid is None:
521 if fileid is None:
462 fileid = self.filenode(path)
522 fileid = self.filenode(path)
463 return filectx(self._repo, path, fileid=fileid,
523 return filectx(self._repo, path, fileid=fileid,
464 changectx=self, filelog=filelog)
524 changectx=self, filelog=filelog)
465
525
466 def ancestor(self, c2, warn=False):
526 def ancestor(self, c2, warn=False):
467 """
527 """
468 return the "best" ancestor context of self and c2
528 return the "best" ancestor context of self and c2
469 """
529 """
470 # deal with workingctxs
530 # deal with workingctxs
471 n2 = c2._node
531 n2 = c2._node
472 if n2 is None:
532 if n2 is None:
473 n2 = c2._parents[0]._node
533 n2 = c2._parents[0]._node
474 cahs = self._repo.changelog.commonancestorsheads(self._node, n2)
534 cahs = self._repo.changelog.commonancestorsheads(self._node, n2)
475 if not cahs:
535 if not cahs:
476 anc = nullid
536 anc = nullid
477 elif len(cahs) == 1:
537 elif len(cahs) == 1:
478 anc = cahs[0]
538 anc = cahs[0]
479 else:
539 else:
480 for r in self._repo.ui.configlist('merge', 'preferancestor'):
540 for r in self._repo.ui.configlist('merge', 'preferancestor'):
481 ctx = changectx(self._repo, r)
541 ctx = changectx(self._repo, r)
482 anc = ctx.node()
542 anc = ctx.node()
483 if anc in cahs:
543 if anc in cahs:
484 break
544 break
485 else:
545 else:
486 anc = self._repo.changelog.ancestor(self._node, n2)
546 anc = self._repo.changelog.ancestor(self._node, n2)
487 if warn:
547 if warn:
488 self._repo.ui.status(
548 self._repo.ui.status(
489 (_("note: using %s as ancestor of %s and %s\n") %
549 (_("note: using %s as ancestor of %s and %s\n") %
490 (short(anc), short(self._node), short(n2))) +
550 (short(anc), short(self._node), short(n2))) +
491 ''.join(_(" alternatively, use --config "
551 ''.join(_(" alternatively, use --config "
492 "merge.preferancestor=%s\n") %
552 "merge.preferancestor=%s\n") %
493 short(n) for n in sorted(cahs) if n != anc))
553 short(n) for n in sorted(cahs) if n != anc))
494 return changectx(self._repo, anc)
554 return changectx(self._repo, anc)
495
555
496 def descendant(self, other):
556 def descendant(self, other):
497 """True if other is descendant of this changeset"""
557 """True if other is descendant of this changeset"""
498 return self._repo.changelog.descendant(self._rev, other._rev)
558 return self._repo.changelog.descendant(self._rev, other._rev)
499
559
500 def walk(self, match):
560 def walk(self, match):
501 fset = set(match.files())
561 fset = set(match.files())
502 # for dirstate.walk, files=['.'] means "walk the whole tree".
562 # for dirstate.walk, files=['.'] means "walk the whole tree".
503 # follow that here, too
563 # follow that here, too
504 fset.discard('.')
564 fset.discard('.')
505
565
506 # avoid the entire walk if we're only looking for specific files
566 # avoid the entire walk if we're only looking for specific files
507 if fset and not match.anypats():
567 if fset and not match.anypats():
508 if util.all([fn in self for fn in fset]):
568 if util.all([fn in self for fn in fset]):
509 for fn in sorted(fset):
569 for fn in sorted(fset):
510 if match(fn):
570 if match(fn):
511 yield fn
571 yield fn
512 raise StopIteration
572 raise StopIteration
513
573
514 for fn in self:
574 for fn in self:
515 if fn in fset:
575 if fn in fset:
516 # specified pattern is the exact name
576 # specified pattern is the exact name
517 fset.remove(fn)
577 fset.remove(fn)
518 if match(fn):
578 if match(fn):
519 yield fn
579 yield fn
520 for fn in sorted(fset):
580 for fn in sorted(fset):
521 if fn in self._dirs:
581 if fn in self._dirs:
522 # specified pattern is a directory
582 # specified pattern is a directory
523 continue
583 continue
524 match.bad(fn, _('no such file in rev %s') % self)
584 match.bad(fn, _('no such file in rev %s') % self)
525
585
526 class basefilectx(object):
586 class basefilectx(object):
527 """A filecontext object represents the common logic for its children:
587 """A filecontext object represents the common logic for its children:
528 filectx: read-only access to a filerevision that is already present
588 filectx: read-only access to a filerevision that is already present
529 in the repo,
589 in the repo,
530 workingfilectx: a filecontext that represents files from the working
590 workingfilectx: a filecontext that represents files from the working
531 directory,
591 directory,
532 memfilectx: a filecontext that represents files in-memory."""
592 memfilectx: a filecontext that represents files in-memory."""
533 def __new__(cls, repo, path, *args, **kwargs):
593 def __new__(cls, repo, path, *args, **kwargs):
534 return super(basefilectx, cls).__new__(cls)
594 return super(basefilectx, cls).__new__(cls)
535
595
536 @propertycache
596 @propertycache
537 def _filelog(self):
597 def _filelog(self):
538 return self._repo.file(self._path)
598 return self._repo.file(self._path)
539
599
540 @propertycache
600 @propertycache
541 def _changeid(self):
601 def _changeid(self):
542 if '_changeid' in self.__dict__:
602 if '_changeid' in self.__dict__:
543 return self._changeid
603 return self._changeid
544 elif '_changectx' in self.__dict__:
604 elif '_changectx' in self.__dict__:
545 return self._changectx.rev()
605 return self._changectx.rev()
546 else:
606 else:
547 return self._filelog.linkrev(self._filerev)
607 return self._filelog.linkrev(self._filerev)
548
608
549 @propertycache
609 @propertycache
550 def _filenode(self):
610 def _filenode(self):
551 if '_fileid' in self.__dict__:
611 if '_fileid' in self.__dict__:
552 return self._filelog.lookup(self._fileid)
612 return self._filelog.lookup(self._fileid)
553 else:
613 else:
554 return self._changectx.filenode(self._path)
614 return self._changectx.filenode(self._path)
555
615
556 @propertycache
616 @propertycache
557 def _filerev(self):
617 def _filerev(self):
558 return self._filelog.rev(self._filenode)
618 return self._filelog.rev(self._filenode)
559
619
560 @propertycache
620 @propertycache
561 def _repopath(self):
621 def _repopath(self):
562 return self._path
622 return self._path
563
623
564 def __nonzero__(self):
624 def __nonzero__(self):
565 try:
625 try:
566 self._filenode
626 self._filenode
567 return True
627 return True
568 except error.LookupError:
628 except error.LookupError:
569 # file is missing
629 # file is missing
570 return False
630 return False
571
631
572 def __str__(self):
632 def __str__(self):
573 return "%s@%s" % (self.path(), self._changectx)
633 return "%s@%s" % (self.path(), self._changectx)
574
634
575 def __repr__(self):
635 def __repr__(self):
576 return "<%s %s>" % (type(self).__name__, str(self))
636 return "<%s %s>" % (type(self).__name__, str(self))
577
637
578 def __hash__(self):
638 def __hash__(self):
579 try:
639 try:
580 return hash((self._path, self._filenode))
640 return hash((self._path, self._filenode))
581 except AttributeError:
641 except AttributeError:
582 return id(self)
642 return id(self)
583
643
584 def __eq__(self, other):
644 def __eq__(self, other):
585 try:
645 try:
586 return (type(self) == type(other) and self._path == other._path
646 return (type(self) == type(other) and self._path == other._path
587 and self._filenode == other._filenode)
647 and self._filenode == other._filenode)
588 except AttributeError:
648 except AttributeError:
589 return False
649 return False
590
650
591 def __ne__(self, other):
651 def __ne__(self, other):
592 return not (self == other)
652 return not (self == other)
593
653
594 def filerev(self):
654 def filerev(self):
595 return self._filerev
655 return self._filerev
596 def filenode(self):
656 def filenode(self):
597 return self._filenode
657 return self._filenode
598 def flags(self):
658 def flags(self):
599 return self._changectx.flags(self._path)
659 return self._changectx.flags(self._path)
600 def filelog(self):
660 def filelog(self):
601 return self._filelog
661 return self._filelog
602 def rev(self):
662 def rev(self):
603 return self._changeid
663 return self._changeid
604 def linkrev(self):
664 def linkrev(self):
605 return self._filelog.linkrev(self._filerev)
665 return self._filelog.linkrev(self._filerev)
606 def node(self):
666 def node(self):
607 return self._changectx.node()
667 return self._changectx.node()
608 def hex(self):
668 def hex(self):
609 return self._changectx.hex()
669 return self._changectx.hex()
610 def user(self):
670 def user(self):
611 return self._changectx.user()
671 return self._changectx.user()
612 def date(self):
672 def date(self):
613 return self._changectx.date()
673 return self._changectx.date()
614 def files(self):
674 def files(self):
615 return self._changectx.files()
675 return self._changectx.files()
616 def description(self):
676 def description(self):
617 return self._changectx.description()
677 return self._changectx.description()
618 def branch(self):
678 def branch(self):
619 return self._changectx.branch()
679 return self._changectx.branch()
620 def extra(self):
680 def extra(self):
621 return self._changectx.extra()
681 return self._changectx.extra()
622 def phase(self):
682 def phase(self):
623 return self._changectx.phase()
683 return self._changectx.phase()
624 def phasestr(self):
684 def phasestr(self):
625 return self._changectx.phasestr()
685 return self._changectx.phasestr()
626 def manifest(self):
686 def manifest(self):
627 return self._changectx.manifest()
687 return self._changectx.manifest()
628 def changectx(self):
688 def changectx(self):
629 return self._changectx
689 return self._changectx
630
690
631 def path(self):
691 def path(self):
632 return self._path
692 return self._path
633
693
634 def isbinary(self):
694 def isbinary(self):
635 try:
695 try:
636 return util.binary(self.data())
696 return util.binary(self.data())
637 except IOError:
697 except IOError:
638 return False
698 return False
639
699
640 def cmp(self, fctx):
700 def cmp(self, fctx):
641 """compare with other file context
701 """compare with other file context
642
702
643 returns True if different than fctx.
703 returns True if different than fctx.
644 """
704 """
645 if (fctx._filerev is None
705 if (fctx._filerev is None
646 and (self._repo._encodefilterpats
706 and (self._repo._encodefilterpats
647 # if file data starts with '\1\n', empty metadata block is
707 # if file data starts with '\1\n', empty metadata block is
648 # prepended, which adds 4 bytes to filelog.size().
708 # prepended, which adds 4 bytes to filelog.size().
649 or self.size() - 4 == fctx.size())
709 or self.size() - 4 == fctx.size())
650 or self.size() == fctx.size()):
710 or self.size() == fctx.size()):
651 return self._filelog.cmp(self._filenode, fctx.data())
711 return self._filelog.cmp(self._filenode, fctx.data())
652
712
653 return True
713 return True
654
714
655 def parents(self):
715 def parents(self):
656 p = self._path
716 p = self._path
657 fl = self._filelog
717 fl = self._filelog
658 pl = [(p, n, fl) for n in self._filelog.parents(self._filenode)]
718 pl = [(p, n, fl) for n in self._filelog.parents(self._filenode)]
659
719
660 r = self._filelog.renamed(self._filenode)
720 r = self._filelog.renamed(self._filenode)
661 if r:
721 if r:
662 pl[0] = (r[0], r[1], None)
722 pl[0] = (r[0], r[1], None)
663
723
664 return [filectx(self._repo, p, fileid=n, filelog=l)
724 return [filectx(self._repo, p, fileid=n, filelog=l)
665 for p, n, l in pl if n != nullid]
725 for p, n, l in pl if n != nullid]
666
726
667 def p1(self):
727 def p1(self):
668 return self.parents()[0]
728 return self.parents()[0]
669
729
670 def p2(self):
730 def p2(self):
671 p = self.parents()
731 p = self.parents()
672 if len(p) == 2:
732 if len(p) == 2:
673 return p[1]
733 return p[1]
674 return filectx(self._repo, self._path, fileid=-1, filelog=self._filelog)
734 return filectx(self._repo, self._path, fileid=-1, filelog=self._filelog)
675
735
676 def annotate(self, follow=False, linenumber=None, diffopts=None):
736 def annotate(self, follow=False, linenumber=None, diffopts=None):
677 '''returns a list of tuples of (ctx, line) for each line
737 '''returns a list of tuples of (ctx, line) for each line
678 in the file, where ctx is the filectx of the node where
738 in the file, where ctx is the filectx of the node where
679 that line was last changed.
739 that line was last changed.
680 This returns tuples of ((ctx, linenumber), line) for each line,
740 This returns tuples of ((ctx, linenumber), line) for each line,
681 if "linenumber" parameter is NOT "None".
741 if "linenumber" parameter is NOT "None".
682 In such tuples, linenumber means one at the first appearance
742 In such tuples, linenumber means one at the first appearance
683 in the managed file.
743 in the managed file.
684 To reduce annotation cost,
744 To reduce annotation cost,
685 this returns fixed value(False is used) as linenumber,
745 this returns fixed value(False is used) as linenumber,
686 if "linenumber" parameter is "False".'''
746 if "linenumber" parameter is "False".'''
687
747
688 def decorate_compat(text, rev):
748 def decorate_compat(text, rev):
689 return ([rev] * len(text.splitlines()), text)
749 return ([rev] * len(text.splitlines()), text)
690
750
691 def without_linenumber(text, rev):
751 def without_linenumber(text, rev):
692 return ([(rev, False)] * len(text.splitlines()), text)
752 return ([(rev, False)] * len(text.splitlines()), text)
693
753
694 def with_linenumber(text, rev):
754 def with_linenumber(text, rev):
695 size = len(text.splitlines())
755 size = len(text.splitlines())
696 return ([(rev, i) for i in xrange(1, size + 1)], text)
756 return ([(rev, i) for i in xrange(1, size + 1)], text)
697
757
698 decorate = (((linenumber is None) and decorate_compat) or
758 decorate = (((linenumber is None) and decorate_compat) or
699 (linenumber and with_linenumber) or
759 (linenumber and with_linenumber) or
700 without_linenumber)
760 without_linenumber)
701
761
702 def pair(parent, child):
762 def pair(parent, child):
703 blocks = mdiff.allblocks(parent[1], child[1], opts=diffopts,
763 blocks = mdiff.allblocks(parent[1], child[1], opts=diffopts,
704 refine=True)
764 refine=True)
705 for (a1, a2, b1, b2), t in blocks:
765 for (a1, a2, b1, b2), t in blocks:
706 # Changed blocks ('!') or blocks made only of blank lines ('~')
766 # Changed blocks ('!') or blocks made only of blank lines ('~')
707 # belong to the child.
767 # belong to the child.
708 if t == '=':
768 if t == '=':
709 child[0][b1:b2] = parent[0][a1:a2]
769 child[0][b1:b2] = parent[0][a1:a2]
710 return child
770 return child
711
771
712 getlog = util.lrucachefunc(lambda x: self._repo.file(x))
772 getlog = util.lrucachefunc(lambda x: self._repo.file(x))
713
773
714 def parents(f):
774 def parents(f):
715 pl = f.parents()
775 pl = f.parents()
716
776
717 # Don't return renamed parents if we aren't following.
777 # Don't return renamed parents if we aren't following.
718 if not follow:
778 if not follow:
719 pl = [p for p in pl if p.path() == f.path()]
779 pl = [p for p in pl if p.path() == f.path()]
720
780
721 # renamed filectx won't have a filelog yet, so set it
781 # renamed filectx won't have a filelog yet, so set it
722 # from the cache to save time
782 # from the cache to save time
723 for p in pl:
783 for p in pl:
724 if not '_filelog' in p.__dict__:
784 if not '_filelog' in p.__dict__:
725 p._filelog = getlog(p.path())
785 p._filelog = getlog(p.path())
726
786
727 return pl
787 return pl
728
788
729 # use linkrev to find the first changeset where self appeared
789 # use linkrev to find the first changeset where self appeared
730 if self.rev() != self.linkrev():
790 if self.rev() != self.linkrev():
731 base = self.filectx(self.filenode())
791 base = self.filectx(self.filenode())
732 else:
792 else:
733 base = self
793 base = self
734
794
735 # This algorithm would prefer to be recursive, but Python is a
795 # This algorithm would prefer to be recursive, but Python is a
736 # bit recursion-hostile. Instead we do an iterative
796 # bit recursion-hostile. Instead we do an iterative
737 # depth-first search.
797 # depth-first search.
738
798
739 visit = [base]
799 visit = [base]
740 hist = {}
800 hist = {}
741 pcache = {}
801 pcache = {}
742 needed = {base: 1}
802 needed = {base: 1}
743 while visit:
803 while visit:
744 f = visit[-1]
804 f = visit[-1]
745 pcached = f in pcache
805 pcached = f in pcache
746 if not pcached:
806 if not pcached:
747 pcache[f] = parents(f)
807 pcache[f] = parents(f)
748
808
749 ready = True
809 ready = True
750 pl = pcache[f]
810 pl = pcache[f]
751 for p in pl:
811 for p in pl:
752 if p not in hist:
812 if p not in hist:
753 ready = False
813 ready = False
754 visit.append(p)
814 visit.append(p)
755 if not pcached:
815 if not pcached:
756 needed[p] = needed.get(p, 0) + 1
816 needed[p] = needed.get(p, 0) + 1
757 if ready:
817 if ready:
758 visit.pop()
818 visit.pop()
759 reusable = f in hist
819 reusable = f in hist
760 if reusable:
820 if reusable:
761 curr = hist[f]
821 curr = hist[f]
762 else:
822 else:
763 curr = decorate(f.data(), f)
823 curr = decorate(f.data(), f)
764 for p in pl:
824 for p in pl:
765 if not reusable:
825 if not reusable:
766 curr = pair(hist[p], curr)
826 curr = pair(hist[p], curr)
767 if needed[p] == 1:
827 if needed[p] == 1:
768 del hist[p]
828 del hist[p]
769 del needed[p]
829 del needed[p]
770 else:
830 else:
771 needed[p] -= 1
831 needed[p] -= 1
772
832
773 hist[f] = curr
833 hist[f] = curr
774 pcache[f] = []
834 pcache[f] = []
775
835
776 return zip(hist[base][0], hist[base][1].splitlines(True))
836 return zip(hist[base][0], hist[base][1].splitlines(True))
777
837
778 def ancestors(self, followfirst=False):
838 def ancestors(self, followfirst=False):
779 visit = {}
839 visit = {}
780 c = self
840 c = self
781 cut = followfirst and 1 or None
841 cut = followfirst and 1 or None
782 while True:
842 while True:
783 for parent in c.parents()[:cut]:
843 for parent in c.parents()[:cut]:
784 visit[(parent.rev(), parent.node())] = parent
844 visit[(parent.rev(), parent.node())] = parent
785 if not visit:
845 if not visit:
786 break
846 break
787 c = visit.pop(max(visit))
847 c = visit.pop(max(visit))
788 yield c
848 yield c
789
849
790 class filectx(basefilectx):
850 class filectx(basefilectx):
791 """A filecontext object makes access to data related to a particular
851 """A filecontext object makes access to data related to a particular
792 filerevision convenient."""
852 filerevision convenient."""
793 def __init__(self, repo, path, changeid=None, fileid=None,
853 def __init__(self, repo, path, changeid=None, fileid=None,
794 filelog=None, changectx=None):
854 filelog=None, changectx=None):
795 """changeid can be a changeset revision, node, or tag.
855 """changeid can be a changeset revision, node, or tag.
796 fileid can be a file revision or node."""
856 fileid can be a file revision or node."""
797 self._repo = repo
857 self._repo = repo
798 self._path = path
858 self._path = path
799
859
800 assert (changeid is not None
860 assert (changeid is not None
801 or fileid is not None
861 or fileid is not None
802 or changectx is not None), \
862 or changectx is not None), \
803 ("bad args: changeid=%r, fileid=%r, changectx=%r"
863 ("bad args: changeid=%r, fileid=%r, changectx=%r"
804 % (changeid, fileid, changectx))
864 % (changeid, fileid, changectx))
805
865
806 if filelog is not None:
866 if filelog is not None:
807 self._filelog = filelog
867 self._filelog = filelog
808
868
809 if changeid is not None:
869 if changeid is not None:
810 self._changeid = changeid
870 self._changeid = changeid
811 if changectx is not None:
871 if changectx is not None:
812 self._changectx = changectx
872 self._changectx = changectx
813 if fileid is not None:
873 if fileid is not None:
814 self._fileid = fileid
874 self._fileid = fileid
815
875
816 @propertycache
876 @propertycache
817 def _changectx(self):
877 def _changectx(self):
818 try:
878 try:
819 return changectx(self._repo, self._changeid)
879 return changectx(self._repo, self._changeid)
820 except error.RepoLookupError:
880 except error.RepoLookupError:
821 # Linkrev may point to any revision in the repository. When the
881 # Linkrev may point to any revision in the repository. When the
822 # repository is filtered this may lead to `filectx` trying to build
882 # repository is filtered this may lead to `filectx` trying to build
823 # `changectx` for filtered revision. In such case we fallback to
883 # `changectx` for filtered revision. In such case we fallback to
824 # creating `changectx` on the unfiltered version of the reposition.
884 # creating `changectx` on the unfiltered version of the reposition.
825 # This fallback should not be an issue because `changectx` from
885 # This fallback should not be an issue because `changectx` from
826 # `filectx` are not used in complex operations that care about
886 # `filectx` are not used in complex operations that care about
827 # filtering.
887 # filtering.
828 #
888 #
829 # This fallback is a cheap and dirty fix that prevent several
889 # This fallback is a cheap and dirty fix that prevent several
830 # crashes. It does not ensure the behavior is correct. However the
890 # crashes. It does not ensure the behavior is correct. However the
831 # behavior was not correct before filtering either and "incorrect
891 # behavior was not correct before filtering either and "incorrect
832 # behavior" is seen as better as "crash"
892 # behavior" is seen as better as "crash"
833 #
893 #
834 # Linkrevs have several serious troubles with filtering that are
894 # Linkrevs have several serious troubles with filtering that are
835 # complicated to solve. Proper handling of the issue here should be
895 # complicated to solve. Proper handling of the issue here should be
836 # considered when solving linkrev issue are on the table.
896 # considered when solving linkrev issue are on the table.
837 return changectx(self._repo.unfiltered(), self._changeid)
897 return changectx(self._repo.unfiltered(), self._changeid)
838
898
839 def filectx(self, fileid):
899 def filectx(self, fileid):
840 '''opens an arbitrary revision of the file without
900 '''opens an arbitrary revision of the file without
841 opening a new filelog'''
901 opening a new filelog'''
842 return filectx(self._repo, self._path, fileid=fileid,
902 return filectx(self._repo, self._path, fileid=fileid,
843 filelog=self._filelog)
903 filelog=self._filelog)
844
904
845 def data(self):
905 def data(self):
846 return self._filelog.read(self._filenode)
906 return self._filelog.read(self._filenode)
847 def size(self):
907 def size(self):
848 return self._filelog.size(self._filerev)
908 return self._filelog.size(self._filerev)
849
909
850 def renamed(self):
910 def renamed(self):
851 """check if file was actually renamed in this changeset revision
911 """check if file was actually renamed in this changeset revision
852
912
853 If rename logged in file revision, we report copy for changeset only
913 If rename logged in file revision, we report copy for changeset only
854 if file revisions linkrev points back to the changeset in question
914 if file revisions linkrev points back to the changeset in question
855 or both changeset parents contain different file revisions.
915 or both changeset parents contain different file revisions.
856 """
916 """
857
917
858 renamed = self._filelog.renamed(self._filenode)
918 renamed = self._filelog.renamed(self._filenode)
859 if not renamed:
919 if not renamed:
860 return renamed
920 return renamed
861
921
862 if self.rev() == self.linkrev():
922 if self.rev() == self.linkrev():
863 return renamed
923 return renamed
864
924
865 name = self.path()
925 name = self.path()
866 fnode = self._filenode
926 fnode = self._filenode
867 for p in self._changectx.parents():
927 for p in self._changectx.parents():
868 try:
928 try:
869 if fnode == p.filenode(name):
929 if fnode == p.filenode(name):
870 return None
930 return None
871 except error.LookupError:
931 except error.LookupError:
872 pass
932 pass
873 return renamed
933 return renamed
874
934
875 def children(self):
935 def children(self):
876 # hard for renames
936 # hard for renames
877 c = self._filelog.children(self._filenode)
937 c = self._filelog.children(self._filenode)
878 return [filectx(self._repo, self._path, fileid=x,
938 return [filectx(self._repo, self._path, fileid=x,
879 filelog=self._filelog) for x in c]
939 filelog=self._filelog) for x in c]
880
940
881 class committablectx(basectx):
941 class committablectx(basectx):
882 """A committablectx object provides common functionality for a context that
942 """A committablectx object provides common functionality for a context that
883 wants the ability to commit, e.g. workingctx or memctx."""
943 wants the ability to commit, e.g. workingctx or memctx."""
884 def __init__(self, repo, text="", user=None, date=None, extra=None,
944 def __init__(self, repo, text="", user=None, date=None, extra=None,
885 changes=None):
945 changes=None):
886 self._repo = repo
946 self._repo = repo
887 self._rev = None
947 self._rev = None
888 self._node = None
948 self._node = None
889 self._text = text
949 self._text = text
890 if date:
950 if date:
891 self._date = util.parsedate(date)
951 self._date = util.parsedate(date)
892 if user:
952 if user:
893 self._user = user
953 self._user = user
894 if changes:
954 if changes:
895 self._status = changes
955 self._status = changes
896
956
897 self._extra = {}
957 self._extra = {}
898 if extra:
958 if extra:
899 self._extra = extra.copy()
959 self._extra = extra.copy()
900 if 'branch' not in self._extra:
960 if 'branch' not in self._extra:
901 try:
961 try:
902 branch = encoding.fromlocal(self._repo.dirstate.branch())
962 branch = encoding.fromlocal(self._repo.dirstate.branch())
903 except UnicodeDecodeError:
963 except UnicodeDecodeError:
904 raise util.Abort(_('branch name not in UTF-8!'))
964 raise util.Abort(_('branch name not in UTF-8!'))
905 self._extra['branch'] = branch
965 self._extra['branch'] = branch
906 if self._extra['branch'] == '':
966 if self._extra['branch'] == '':
907 self._extra['branch'] = 'default'
967 self._extra['branch'] = 'default'
908
968
909 def __str__(self):
969 def __str__(self):
910 return str(self._parents[0]) + "+"
970 return str(self._parents[0]) + "+"
911
971
912 def __nonzero__(self):
972 def __nonzero__(self):
913 return True
973 return True
914
974
915 def __contains__(self, key):
975 def __contains__(self, key):
916 return self._repo.dirstate[key] not in "?r"
976 return self._repo.dirstate[key] not in "?r"
917
977
918 def _buildflagfunc(self):
978 def _buildflagfunc(self):
919 # Create a fallback function for getting file flags when the
979 # Create a fallback function for getting file flags when the
920 # filesystem doesn't support them
980 # filesystem doesn't support them
921
981
922 copiesget = self._repo.dirstate.copies().get
982 copiesget = self._repo.dirstate.copies().get
923
983
924 if len(self._parents) < 2:
984 if len(self._parents) < 2:
925 # when we have one parent, it's easy: copy from parent
985 # when we have one parent, it's easy: copy from parent
926 man = self._parents[0].manifest()
986 man = self._parents[0].manifest()
927 def func(f):
987 def func(f):
928 f = copiesget(f, f)
988 f = copiesget(f, f)
929 return man.flags(f)
989 return man.flags(f)
930 else:
990 else:
931 # merges are tricky: we try to reconstruct the unstored
991 # merges are tricky: we try to reconstruct the unstored
932 # result from the merge (issue1802)
992 # result from the merge (issue1802)
933 p1, p2 = self._parents
993 p1, p2 = self._parents
934 pa = p1.ancestor(p2)
994 pa = p1.ancestor(p2)
935 m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest()
995 m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest()
936
996
937 def func(f):
997 def func(f):
938 f = copiesget(f, f) # may be wrong for merges with copies
998 f = copiesget(f, f) # may be wrong for merges with copies
939 fl1, fl2, fla = m1.flags(f), m2.flags(f), ma.flags(f)
999 fl1, fl2, fla = m1.flags(f), m2.flags(f), ma.flags(f)
940 if fl1 == fl2:
1000 if fl1 == fl2:
941 return fl1
1001 return fl1
942 if fl1 == fla:
1002 if fl1 == fla:
943 return fl2
1003 return fl2
944 if fl2 == fla:
1004 if fl2 == fla:
945 return fl1
1005 return fl1
946 return '' # punt for conflicts
1006 return '' # punt for conflicts
947
1007
948 return func
1008 return func
949
1009
950 @propertycache
1010 @propertycache
951 def _flagfunc(self):
1011 def _flagfunc(self):
952 return self._repo.dirstate.flagfunc(self._buildflagfunc)
1012 return self._repo.dirstate.flagfunc(self._buildflagfunc)
953
1013
954 @propertycache
1014 @propertycache
955 def _manifest(self):
1015 def _manifest(self):
956 """generate a manifest corresponding to the working directory"""
1016 """generate a manifest corresponding to the working directory"""
957
1017
958 man = self._parents[0].manifest().copy()
1018 man = self._parents[0].manifest().copy()
959 if len(self._parents) > 1:
1019 if len(self._parents) > 1:
960 man2 = self.p2().manifest()
1020 man2 = self.p2().manifest()
961 def getman(f):
1021 def getman(f):
962 if f in man:
1022 if f in man:
963 return man
1023 return man
964 return man2
1024 return man2
965 else:
1025 else:
966 getman = lambda f: man
1026 getman = lambda f: man
967
1027
968 copied = self._repo.dirstate.copies()
1028 copied = self._repo.dirstate.copies()
969 ff = self._flagfunc
1029 ff = self._flagfunc
970 modified, added, removed, deleted = self._status[:4]
1030 modified, added, removed, deleted = self._status[:4]
971 for i, l in (("a", added), ("m", modified)):
1031 for i, l in (("a", added), ("m", modified)):
972 for f in l:
1032 for f in l:
973 orig = copied.get(f, f)
1033 orig = copied.get(f, f)
974 man[f] = getman(orig).get(orig, nullid) + i
1034 man[f] = getman(orig).get(orig, nullid) + i
975 try:
1035 try:
976 man.set(f, ff(f))
1036 man.set(f, ff(f))
977 except OSError:
1037 except OSError:
978 pass
1038 pass
979
1039
980 for f in deleted + removed:
1040 for f in deleted + removed:
981 if f in man:
1041 if f in man:
982 del man[f]
1042 del man[f]
983
1043
984 return man
1044 return man
985
1045
986 @propertycache
1046 @propertycache
987 def _status(self):
1047 def _status(self):
988 return self._repo.status()
1048 return self._repo.status()
989
1049
990 @propertycache
1050 @propertycache
991 def _user(self):
1051 def _user(self):
992 return self._repo.ui.username()
1052 return self._repo.ui.username()
993
1053
994 @propertycache
1054 @propertycache
995 def _date(self):
1055 def _date(self):
996 return util.makedate()
1056 return util.makedate()
997
1057
998 def subrev(self, subpath):
1058 def subrev(self, subpath):
999 return None
1059 return None
1000
1060
1001 def user(self):
1061 def user(self):
1002 return self._user or self._repo.ui.username()
1062 return self._user or self._repo.ui.username()
1003 def date(self):
1063 def date(self):
1004 return self._date
1064 return self._date
1005 def description(self):
1065 def description(self):
1006 return self._text
1066 return self._text
1007 def files(self):
1067 def files(self):
1008 return sorted(self._status[0] + self._status[1] + self._status[2])
1068 return sorted(self._status[0] + self._status[1] + self._status[2])
1009
1069
1010 def modified(self):
1070 def modified(self):
1011 return self._status[0]
1071 return self._status[0]
1012 def added(self):
1072 def added(self):
1013 return self._status[1]
1073 return self._status[1]
1014 def removed(self):
1074 def removed(self):
1015 return self._status[2]
1075 return self._status[2]
1016 def deleted(self):
1076 def deleted(self):
1017 return self._status[3]
1077 return self._status[3]
1018 def unknown(self):
1078 def unknown(self):
1019 return self._status[4]
1079 return self._status[4]
1020 def ignored(self):
1080 def ignored(self):
1021 return self._status[5]
1081 return self._status[5]
1022 def clean(self):
1082 def clean(self):
1023 return self._status[6]
1083 return self._status[6]
1024 def branch(self):
1084 def branch(self):
1025 return encoding.tolocal(self._extra['branch'])
1085 return encoding.tolocal(self._extra['branch'])
1026 def closesbranch(self):
1086 def closesbranch(self):
1027 return 'close' in self._extra
1087 return 'close' in self._extra
1028 def extra(self):
1088 def extra(self):
1029 return self._extra
1089 return self._extra
1030
1090
1031 def tags(self):
1091 def tags(self):
1032 t = []
1092 t = []
1033 for p in self.parents():
1093 for p in self.parents():
1034 t.extend(p.tags())
1094 t.extend(p.tags())
1035 return t
1095 return t
1036
1096
1037 def bookmarks(self):
1097 def bookmarks(self):
1038 b = []
1098 b = []
1039 for p in self.parents():
1099 for p in self.parents():
1040 b.extend(p.bookmarks())
1100 b.extend(p.bookmarks())
1041 return b
1101 return b
1042
1102
1043 def phase(self):
1103 def phase(self):
1044 phase = phases.draft # default phase to draft
1104 phase = phases.draft # default phase to draft
1045 for p in self.parents():
1105 for p in self.parents():
1046 phase = max(phase, p.phase())
1106 phase = max(phase, p.phase())
1047 return phase
1107 return phase
1048
1108
1049 def hidden(self):
1109 def hidden(self):
1050 return False
1110 return False
1051
1111
1052 def children(self):
1112 def children(self):
1053 return []
1113 return []
1054
1114
1055 def flags(self, path):
1115 def flags(self, path):
1056 if '_manifest' in self.__dict__:
1116 if '_manifest' in self.__dict__:
1057 try:
1117 try:
1058 return self._manifest.flags(path)
1118 return self._manifest.flags(path)
1059 except KeyError:
1119 except KeyError:
1060 return ''
1120 return ''
1061
1121
1062 try:
1122 try:
1063 return self._flagfunc(path)
1123 return self._flagfunc(path)
1064 except OSError:
1124 except OSError:
1065 return ''
1125 return ''
1066
1126
1067 def ancestor(self, c2):
1127 def ancestor(self, c2):
1068 """return the ancestor context of self and c2"""
1128 """return the ancestor context of self and c2"""
1069 return self._parents[0].ancestor(c2) # punt on two parents for now
1129 return self._parents[0].ancestor(c2) # punt on two parents for now
1070
1130
1071 def walk(self, match):
1131 def walk(self, match):
1072 return sorted(self._repo.dirstate.walk(match, sorted(self.substate),
1132 return sorted(self._repo.dirstate.walk(match, sorted(self.substate),
1073 True, False))
1133 True, False))
1074
1134
1075 def ancestors(self):
1135 def ancestors(self):
1076 for a in self._repo.changelog.ancestors(
1136 for a in self._repo.changelog.ancestors(
1077 [p.rev() for p in self._parents]):
1137 [p.rev() for p in self._parents]):
1078 yield changectx(self._repo, a)
1138 yield changectx(self._repo, a)
1079
1139
1080 def markcommitted(self, node):
1140 def markcommitted(self, node):
1081 """Perform post-commit cleanup necessary after committing this ctx
1141 """Perform post-commit cleanup necessary after committing this ctx
1082
1142
1083 Specifically, this updates backing stores this working context
1143 Specifically, this updates backing stores this working context
1084 wraps to reflect the fact that the changes reflected by this
1144 wraps to reflect the fact that the changes reflected by this
1085 workingctx have been committed. For example, it marks
1145 workingctx have been committed. For example, it marks
1086 modified and added files as normal in the dirstate.
1146 modified and added files as normal in the dirstate.
1087
1147
1088 """
1148 """
1089
1149
1090 for f in self.modified() + self.added():
1150 for f in self.modified() + self.added():
1091 self._repo.dirstate.normal(f)
1151 self._repo.dirstate.normal(f)
1092 for f in self.removed():
1152 for f in self.removed():
1093 self._repo.dirstate.drop(f)
1153 self._repo.dirstate.drop(f)
1094 self._repo.dirstate.setparents(node)
1154 self._repo.dirstate.setparents(node)
1095
1155
1096 def dirs(self):
1156 def dirs(self):
1097 return self._repo.dirstate.dirs()
1157 return self._repo.dirstate.dirs()
1098
1158
1099 class workingctx(committablectx):
1159 class workingctx(committablectx):
1100 """A workingctx object makes access to data related to
1160 """A workingctx object makes access to data related to
1101 the current working directory convenient.
1161 the current working directory convenient.
1102 date - any valid date string or (unixtime, offset), or None.
1162 date - any valid date string or (unixtime, offset), or None.
1103 user - username string, or None.
1163 user - username string, or None.
1104 extra - a dictionary of extra values, or None.
1164 extra - a dictionary of extra values, or None.
1105 changes - a list of file lists as returned by localrepo.status()
1165 changes - a list of file lists as returned by localrepo.status()
1106 or None to use the repository status.
1166 or None to use the repository status.
1107 """
1167 """
1108 def __init__(self, repo, text="", user=None, date=None, extra=None,
1168 def __init__(self, repo, text="", user=None, date=None, extra=None,
1109 changes=None):
1169 changes=None):
1110 super(workingctx, self).__init__(repo, text, user, date, extra, changes)
1170 super(workingctx, self).__init__(repo, text, user, date, extra, changes)
1111
1171
1112 def __iter__(self):
1172 def __iter__(self):
1113 d = self._repo.dirstate
1173 d = self._repo.dirstate
1114 for f in d:
1174 for f in d:
1115 if d[f] != 'r':
1175 if d[f] != 'r':
1116 yield f
1176 yield f
1117
1177
1118 @propertycache
1178 @propertycache
1119 def _parents(self):
1179 def _parents(self):
1120 p = self._repo.dirstate.parents()
1180 p = self._repo.dirstate.parents()
1121 if p[1] == nullid:
1181 if p[1] == nullid:
1122 p = p[:-1]
1182 p = p[:-1]
1123 return [changectx(self._repo, x) for x in p]
1183 return [changectx(self._repo, x) for x in p]
1124
1184
1125 def filectx(self, path, filelog=None):
1185 def filectx(self, path, filelog=None):
1126 """get a file context from the working directory"""
1186 """get a file context from the working directory"""
1127 return workingfilectx(self._repo, path, workingctx=self,
1187 return workingfilectx(self._repo, path, workingctx=self,
1128 filelog=filelog)
1188 filelog=filelog)
1129
1189
1130 def dirty(self, missing=False, merge=True, branch=True):
1190 def dirty(self, missing=False, merge=True, branch=True):
1131 "check whether a working directory is modified"
1191 "check whether a working directory is modified"
1132 # check subrepos first
1192 # check subrepos first
1133 for s in sorted(self.substate):
1193 for s in sorted(self.substate):
1134 if self.sub(s).dirty():
1194 if self.sub(s).dirty():
1135 return True
1195 return True
1136 # check current working dir
1196 # check current working dir
1137 return ((merge and self.p2()) or
1197 return ((merge and self.p2()) or
1138 (branch and self.branch() != self.p1().branch()) or
1198 (branch and self.branch() != self.p1().branch()) or
1139 self.modified() or self.added() or self.removed() or
1199 self.modified() or self.added() or self.removed() or
1140 (missing and self.deleted()))
1200 (missing and self.deleted()))
1141
1201
1142 def add(self, list, prefix=""):
1202 def add(self, list, prefix=""):
1143 join = lambda f: os.path.join(prefix, f)
1203 join = lambda f: os.path.join(prefix, f)
1144 wlock = self._repo.wlock()
1204 wlock = self._repo.wlock()
1145 ui, ds = self._repo.ui, self._repo.dirstate
1205 ui, ds = self._repo.ui, self._repo.dirstate
1146 try:
1206 try:
1147 rejected = []
1207 rejected = []
1148 lstat = self._repo.wvfs.lstat
1208 lstat = self._repo.wvfs.lstat
1149 for f in list:
1209 for f in list:
1150 scmutil.checkportable(ui, join(f))
1210 scmutil.checkportable(ui, join(f))
1151 try:
1211 try:
1152 st = lstat(f)
1212 st = lstat(f)
1153 except OSError:
1213 except OSError:
1154 ui.warn(_("%s does not exist!\n") % join(f))
1214 ui.warn(_("%s does not exist!\n") % join(f))
1155 rejected.append(f)
1215 rejected.append(f)
1156 continue
1216 continue
1157 if st.st_size > 10000000:
1217 if st.st_size > 10000000:
1158 ui.warn(_("%s: up to %d MB of RAM may be required "
1218 ui.warn(_("%s: up to %d MB of RAM may be required "
1159 "to manage this file\n"
1219 "to manage this file\n"
1160 "(use 'hg revert %s' to cancel the "
1220 "(use 'hg revert %s' to cancel the "
1161 "pending addition)\n")
1221 "pending addition)\n")
1162 % (f, 3 * st.st_size // 1000000, join(f)))
1222 % (f, 3 * st.st_size // 1000000, join(f)))
1163 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
1223 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
1164 ui.warn(_("%s not added: only files and symlinks "
1224 ui.warn(_("%s not added: only files and symlinks "
1165 "supported currently\n") % join(f))
1225 "supported currently\n") % join(f))
1166 rejected.append(f)
1226 rejected.append(f)
1167 elif ds[f] in 'amn':
1227 elif ds[f] in 'amn':
1168 ui.warn(_("%s already tracked!\n") % join(f))
1228 ui.warn(_("%s already tracked!\n") % join(f))
1169 elif ds[f] == 'r':
1229 elif ds[f] == 'r':
1170 ds.normallookup(f)
1230 ds.normallookup(f)
1171 else:
1231 else:
1172 ds.add(f)
1232 ds.add(f)
1173 return rejected
1233 return rejected
1174 finally:
1234 finally:
1175 wlock.release()
1235 wlock.release()
1176
1236
1177 def forget(self, files, prefix=""):
1237 def forget(self, files, prefix=""):
1178 join = lambda f: os.path.join(prefix, f)
1238 join = lambda f: os.path.join(prefix, f)
1179 wlock = self._repo.wlock()
1239 wlock = self._repo.wlock()
1180 try:
1240 try:
1181 rejected = []
1241 rejected = []
1182 for f in files:
1242 for f in files:
1183 if f not in self._repo.dirstate:
1243 if f not in self._repo.dirstate:
1184 self._repo.ui.warn(_("%s not tracked!\n") % join(f))
1244 self._repo.ui.warn(_("%s not tracked!\n") % join(f))
1185 rejected.append(f)
1245 rejected.append(f)
1186 elif self._repo.dirstate[f] != 'a':
1246 elif self._repo.dirstate[f] != 'a':
1187 self._repo.dirstate.remove(f)
1247 self._repo.dirstate.remove(f)
1188 else:
1248 else:
1189 self._repo.dirstate.drop(f)
1249 self._repo.dirstate.drop(f)
1190 return rejected
1250 return rejected
1191 finally:
1251 finally:
1192 wlock.release()
1252 wlock.release()
1193
1253
1194 def undelete(self, list):
1254 def undelete(self, list):
1195 pctxs = self.parents()
1255 pctxs = self.parents()
1196 wlock = self._repo.wlock()
1256 wlock = self._repo.wlock()
1197 try:
1257 try:
1198 for f in list:
1258 for f in list:
1199 if self._repo.dirstate[f] != 'r':
1259 if self._repo.dirstate[f] != 'r':
1200 self._repo.ui.warn(_("%s not removed!\n") % f)
1260 self._repo.ui.warn(_("%s not removed!\n") % f)
1201 else:
1261 else:
1202 fctx = f in pctxs[0] and pctxs[0][f] or pctxs[1][f]
1262 fctx = f in pctxs[0] and pctxs[0][f] or pctxs[1][f]
1203 t = fctx.data()
1263 t = fctx.data()
1204 self._repo.wwrite(f, t, fctx.flags())
1264 self._repo.wwrite(f, t, fctx.flags())
1205 self._repo.dirstate.normal(f)
1265 self._repo.dirstate.normal(f)
1206 finally:
1266 finally:
1207 wlock.release()
1267 wlock.release()
1208
1268
1209 def copy(self, source, dest):
1269 def copy(self, source, dest):
1210 try:
1270 try:
1211 st = self._repo.wvfs.lstat(dest)
1271 st = self._repo.wvfs.lstat(dest)
1212 except OSError, err:
1272 except OSError, err:
1213 if err.errno != errno.ENOENT:
1273 if err.errno != errno.ENOENT:
1214 raise
1274 raise
1215 self._repo.ui.warn(_("%s does not exist!\n") % dest)
1275 self._repo.ui.warn(_("%s does not exist!\n") % dest)
1216 return
1276 return
1217 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
1277 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
1218 self._repo.ui.warn(_("copy failed: %s is not a file or a "
1278 self._repo.ui.warn(_("copy failed: %s is not a file or a "
1219 "symbolic link\n") % dest)
1279 "symbolic link\n") % dest)
1220 else:
1280 else:
1221 wlock = self._repo.wlock()
1281 wlock = self._repo.wlock()
1222 try:
1282 try:
1223 if self._repo.dirstate[dest] in '?r':
1283 if self._repo.dirstate[dest] in '?r':
1224 self._repo.dirstate.add(dest)
1284 self._repo.dirstate.add(dest)
1225 self._repo.dirstate.copy(source, dest)
1285 self._repo.dirstate.copy(source, dest)
1226 finally:
1286 finally:
1227 wlock.release()
1287 wlock.release()
1228
1288
1229 def _filtersuspectsymlink(self, files):
1289 def _filtersuspectsymlink(self, files):
1230 if not files or self._repo.dirstate._checklink:
1290 if not files or self._repo.dirstate._checklink:
1231 return files
1291 return files
1232
1292
1233 # Symlink placeholders may get non-symlink-like contents
1293 # Symlink placeholders may get non-symlink-like contents
1234 # via user error or dereferencing by NFS or Samba servers,
1294 # via user error or dereferencing by NFS or Samba servers,
1235 # so we filter out any placeholders that don't look like a
1295 # so we filter out any placeholders that don't look like a
1236 # symlink
1296 # symlink
1237 sane = []
1297 sane = []
1238 for f in files:
1298 for f in files:
1239 if self.flags(f) == 'l':
1299 if self.flags(f) == 'l':
1240 d = self[f].data()
1300 d = self[f].data()
1241 if d == '' or len(d) >= 1024 or '\n' in d or util.binary(d):
1301 if d == '' or len(d) >= 1024 or '\n' in d or util.binary(d):
1242 self._repo.ui.debug('ignoring suspect symlink placeholder'
1302 self._repo.ui.debug('ignoring suspect symlink placeholder'
1243 ' "%s"\n' % f)
1303 ' "%s"\n' % f)
1244 continue
1304 continue
1245 sane.append(f)
1305 sane.append(f)
1246 return sane
1306 return sane
1247
1307
1248 def _checklookup(self, files):
1308 def _checklookup(self, files):
1249 # check for any possibly clean files
1309 # check for any possibly clean files
1250 if not files:
1310 if not files:
1251 return [], []
1311 return [], []
1252
1312
1253 modified = []
1313 modified = []
1254 fixup = []
1314 fixup = []
1255 pctx = self._parents[0]
1315 pctx = self._parents[0]
1256 # do a full compare of any files that might have changed
1316 # do a full compare of any files that might have changed
1257 for f in sorted(files):
1317 for f in sorted(files):
1258 if (f not in pctx or self.flags(f) != pctx.flags(f)
1318 if (f not in pctx or self.flags(f) != pctx.flags(f)
1259 or pctx[f].cmp(self[f])):
1319 or pctx[f].cmp(self[f])):
1260 modified.append(f)
1320 modified.append(f)
1261 else:
1321 else:
1262 fixup.append(f)
1322 fixup.append(f)
1263
1323
1264 # update dirstate for files that are actually clean
1324 # update dirstate for files that are actually clean
1265 if fixup:
1325 if fixup:
1266 try:
1326 try:
1267 # updating the dirstate is optional
1327 # updating the dirstate is optional
1268 # so we don't wait on the lock
1328 # so we don't wait on the lock
1269 normal = self._repo.dirstate.normal
1329 normal = self._repo.dirstate.normal
1270 wlock = self._repo.wlock(False)
1330 wlock = self._repo.wlock(False)
1271 try:
1331 try:
1272 for f in fixup:
1332 for f in fixup:
1273 normal(f)
1333 normal(f)
1274 finally:
1334 finally:
1275 wlock.release()
1335 wlock.release()
1276 except error.LockError:
1336 except error.LockError:
1277 pass
1337 pass
1278 return modified, fixup
1338 return modified, fixup
1279
1339
1280 def _manifestmatches(self, match, s):
1340 def _manifestmatches(self, match, s):
1281 """Slow path for workingctx
1341 """Slow path for workingctx
1282
1342
1283 The fast path is when we compare the working directory to its parent
1343 The fast path is when we compare the working directory to its parent
1284 which means this function is comparing with a non-parent; therefore we
1344 which means this function is comparing with a non-parent; therefore we
1285 need to build a manifest and return what matches.
1345 need to build a manifest and return what matches.
1286 """
1346 """
1287 mf = self._repo['.']._manifestmatches(match, s)
1347 mf = self._repo['.']._manifestmatches(match, s)
1288 modified, added, removed = s[0:3]
1348 modified, added, removed = s[0:3]
1289 for f in modified + added:
1349 for f in modified + added:
1290 mf[f] = None
1350 mf[f] = None
1291 mf.set(f, self.flags(f))
1351 mf.set(f, self.flags(f))
1292 for f in removed:
1352 for f in removed:
1293 if f in mf:
1353 if f in mf:
1294 del mf[f]
1354 del mf[f]
1295 return mf
1355 return mf
1296
1356
1297 def _prestatus(self, other, s, match, listignored, listclean, listunknown):
1357 def _prestatus(self, other, s, match, listignored, listclean, listunknown):
1298 """override the parent hook with a dirstate query
1358 """override the parent hook with a dirstate query
1299
1359
1300 We use this prestatus hook to populate the status with information from
1360 We use this prestatus hook to populate the status with information from
1301 the dirstate.
1361 the dirstate.
1302 """
1362 """
1303 # doesn't need to call super; if that changes, be aware that super
1363 # doesn't need to call super; if that changes, be aware that super
1304 # calls self.manifest which would slow down the common case of calling
1364 # calls self.manifest which would slow down the common case of calling
1305 # status against a workingctx's parent
1365 # status against a workingctx's parent
1306 return self._dirstatestatus(match, listignored, listclean, listunknown)
1366 return self._dirstatestatus(match, listignored, listclean, listunknown)
1307
1367
1308 def _poststatus(self, other, s, match, listignored, listclean, listunknown):
1368 def _poststatus(self, other, s, match, listignored, listclean, listunknown):
1309 """override the parent hook with a filter for suspect symlinks
1369 """override the parent hook with a filter for suspect symlinks
1310
1370
1311 We use this poststatus hook to filter out symlinks that might have
1371 We use this poststatus hook to filter out symlinks that might have
1312 accidentally ended up with the entire contents of the file they are
1372 accidentally ended up with the entire contents of the file they are
1313 susposed to be linking to.
1373 susposed to be linking to.
1314 """
1374 """
1315 s[0] = self._filtersuspectsymlink(s[0])
1375 s[0] = self._filtersuspectsymlink(s[0])
1316 self._status = s
1376 self._status = s
1317 return s
1377 return s
1318
1378
1319 def _dirstatestatus(self, match=None, ignored=False, clean=False,
1379 def _dirstatestatus(self, match=None, ignored=False, clean=False,
1320 unknown=False):
1380 unknown=False):
1321 '''Gets the status from the dirstate -- internal use only.'''
1381 '''Gets the status from the dirstate -- internal use only.'''
1322 listignored, listclean, listunknown = ignored, clean, unknown
1382 listignored, listclean, listunknown = ignored, clean, unknown
1323 match = match or matchmod.always(self._repo.root, self._repo.getcwd())
1383 match = match or matchmod.always(self._repo.root, self._repo.getcwd())
1324 subrepos = []
1384 subrepos = []
1325 if '.hgsub' in self:
1385 if '.hgsub' in self:
1326 subrepos = sorted(self.substate)
1386 subrepos = sorted(self.substate)
1327 s = self._repo.dirstate.status(match, subrepos, listignored,
1387 s = self._repo.dirstate.status(match, subrepos, listignored,
1328 listclean, listunknown)
1388 listclean, listunknown)
1329 cmp, modified, added, removed, deleted, unknown, ignored, clean = s
1389 cmp, modified, added, removed, deleted, unknown, ignored, clean = s
1330
1390
1331 # check for any possibly clean files
1391 # check for any possibly clean files
1332 if cmp:
1392 if cmp:
1333 modified2, fixup = self._checklookup(cmp)
1393 modified2, fixup = self._checklookup(cmp)
1334 modified += modified2
1394 modified += modified2
1335
1395
1336 # update dirstate for files that are actually clean
1396 # update dirstate for files that are actually clean
1337 if fixup and listclean:
1397 if fixup and listclean:
1338 clean += fixup
1398 clean += fixup
1339
1399
1340 return [modified, added, removed, deleted, unknown, ignored, clean]
1400 return [modified, added, removed, deleted, unknown, ignored, clean]
1341
1401
1342 def _buildstatus(self, other, s, match, listignored, listclean,
1402 def _buildstatus(self, other, s, match, listignored, listclean,
1343 listunknown):
1403 listunknown):
1344 """build a status with respect to another context
1404 """build a status with respect to another context
1345
1405
1346 This includes logic for maintaining the fast path of status when
1406 This includes logic for maintaining the fast path of status when
1347 comparing the working directory against its parent, which is to skip
1407 comparing the working directory against its parent, which is to skip
1348 building a new manifest if self (working directory) is not comparing
1408 building a new manifest if self (working directory) is not comparing
1349 against its parent (repo['.']).
1409 against its parent (repo['.']).
1350 """
1410 """
1351 if other != self._repo['.']:
1411 if other != self._repo['.']:
1352 s = super(workingctx, self)._buildstatus(other, s, match,
1412 s = super(workingctx, self)._buildstatus(other, s, match,
1353 listignored, listclean,
1413 listignored, listclean,
1354 listunknown)
1414 listunknown)
1355 return s
1415 return s
1356
1416
1357 def _matchstatus(self, other, s, match, listignored, listclean,
1417 def _matchstatus(self, other, s, match, listignored, listclean,
1358 listunknown):
1418 listunknown):
1359 """override the match method with a filter for directory patterns
1419 """override the match method with a filter for directory patterns
1360
1420
1361 We use inheritance to customize the match.bad method only in cases of
1421 We use inheritance to customize the match.bad method only in cases of
1362 workingctx since it belongs only to the working directory when
1422 workingctx since it belongs only to the working directory when
1363 comparing against the parent changeset.
1423 comparing against the parent changeset.
1364
1424
1365 If we aren't comparing against the working directory's parent, then we
1425 If we aren't comparing against the working directory's parent, then we
1366 just use the default match object sent to us.
1426 just use the default match object sent to us.
1367 """
1427 """
1368 superself = super(workingctx, self)
1428 superself = super(workingctx, self)
1369 match = superself._matchstatus(other, s, match, listignored, listclean,
1429 match = superself._matchstatus(other, s, match, listignored, listclean,
1370 listunknown)
1430 listunknown)
1371 if other != self._repo['.']:
1431 if other != self._repo['.']:
1372 def bad(f, msg):
1432 def bad(f, msg):
1373 # 'f' may be a directory pattern from 'match.files()',
1433 # 'f' may be a directory pattern from 'match.files()',
1374 # so 'f not in ctx1' is not enough
1434 # so 'f not in ctx1' is not enough
1375 if f not in other and f not in other.dirs():
1435 if f not in other and f not in other.dirs():
1376 self._repo.ui.warn('%s: %s\n' %
1436 self._repo.ui.warn('%s: %s\n' %
1377 (self._repo.dirstate.pathto(f), msg))
1437 (self._repo.dirstate.pathto(f), msg))
1378 match.bad = bad
1438 match.bad = bad
1379 return match
1439 return match
1380
1440
1381 def status(self, ignored=False, clean=False, unknown=False, match=None):
1441 def status(self, ignored=False, clean=False, unknown=False, match=None):
1382 """Explicit status query
1442 """Explicit status query
1383 Unless this method is used to query the working copy status, the
1443 Unless this method is used to query the working copy status, the
1384 _status property will implicitly read the status using its default
1444 _status property will implicitly read the status using its default
1385 arguments."""
1445 arguments."""
1386 listignored, listclean, listunknown = ignored, clean, unknown
1446 listignored, listclean, listunknown = ignored, clean, unknown
1387 s = self._dirstatestatus(match=match, ignored=listignored,
1447 s = self._dirstatestatus(match=match, ignored=listignored,
1388 clean=listclean, unknown=listunknown)
1448 clean=listclean, unknown=listunknown)
1389
1449
1390 s[0] = self._filtersuspectsymlink(s[0])
1450 s[0] = self._filtersuspectsymlink(s[0])
1391 self._status = s
1451 self._status = s
1392 return s
1452 return s
1393
1453
1394
1454
1395 class committablefilectx(basefilectx):
1455 class committablefilectx(basefilectx):
1396 """A committablefilectx provides common functionality for a file context
1456 """A committablefilectx provides common functionality for a file context
1397 that wants the ability to commit, e.g. workingfilectx or memfilectx."""
1457 that wants the ability to commit, e.g. workingfilectx or memfilectx."""
1398 def __init__(self, repo, path, filelog=None, ctx=None):
1458 def __init__(self, repo, path, filelog=None, ctx=None):
1399 self._repo = repo
1459 self._repo = repo
1400 self._path = path
1460 self._path = path
1401 self._changeid = None
1461 self._changeid = None
1402 self._filerev = self._filenode = None
1462 self._filerev = self._filenode = None
1403
1463
1404 if filelog is not None:
1464 if filelog is not None:
1405 self._filelog = filelog
1465 self._filelog = filelog
1406 if ctx:
1466 if ctx:
1407 self._changectx = ctx
1467 self._changectx = ctx
1408
1468
1409 def __nonzero__(self):
1469 def __nonzero__(self):
1410 return True
1470 return True
1411
1471
1412 def parents(self):
1472 def parents(self):
1413 '''return parent filectxs, following copies if necessary'''
1473 '''return parent filectxs, following copies if necessary'''
1414 def filenode(ctx, path):
1474 def filenode(ctx, path):
1415 return ctx._manifest.get(path, nullid)
1475 return ctx._manifest.get(path, nullid)
1416
1476
1417 path = self._path
1477 path = self._path
1418 fl = self._filelog
1478 fl = self._filelog
1419 pcl = self._changectx._parents
1479 pcl = self._changectx._parents
1420 renamed = self.renamed()
1480 renamed = self.renamed()
1421
1481
1422 if renamed:
1482 if renamed:
1423 pl = [renamed + (None,)]
1483 pl = [renamed + (None,)]
1424 else:
1484 else:
1425 pl = [(path, filenode(pcl[0], path), fl)]
1485 pl = [(path, filenode(pcl[0], path), fl)]
1426
1486
1427 for pc in pcl[1:]:
1487 for pc in pcl[1:]:
1428 pl.append((path, filenode(pc, path), fl))
1488 pl.append((path, filenode(pc, path), fl))
1429
1489
1430 return [filectx(self._repo, p, fileid=n, filelog=l)
1490 return [filectx(self._repo, p, fileid=n, filelog=l)
1431 for p, n, l in pl if n != nullid]
1491 for p, n, l in pl if n != nullid]
1432
1492
1433 def children(self):
1493 def children(self):
1434 return []
1494 return []
1435
1495
1436 class workingfilectx(committablefilectx):
1496 class workingfilectx(committablefilectx):
1437 """A workingfilectx object makes access to data related to a particular
1497 """A workingfilectx object makes access to data related to a particular
1438 file in the working directory convenient."""
1498 file in the working directory convenient."""
1439 def __init__(self, repo, path, filelog=None, workingctx=None):
1499 def __init__(self, repo, path, filelog=None, workingctx=None):
1440 super(workingfilectx, self).__init__(repo, path, filelog, workingctx)
1500 super(workingfilectx, self).__init__(repo, path, filelog, workingctx)
1441
1501
1442 @propertycache
1502 @propertycache
1443 def _changectx(self):
1503 def _changectx(self):
1444 return workingctx(self._repo)
1504 return workingctx(self._repo)
1445
1505
1446 def data(self):
1506 def data(self):
1447 return self._repo.wread(self._path)
1507 return self._repo.wread(self._path)
1448 def renamed(self):
1508 def renamed(self):
1449 rp = self._repo.dirstate.copied(self._path)
1509 rp = self._repo.dirstate.copied(self._path)
1450 if not rp:
1510 if not rp:
1451 return None
1511 return None
1452 return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
1512 return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
1453
1513
1454 def size(self):
1514 def size(self):
1455 return self._repo.wvfs.lstat(self._path).st_size
1515 return self._repo.wvfs.lstat(self._path).st_size
1456 def date(self):
1516 def date(self):
1457 t, tz = self._changectx.date()
1517 t, tz = self._changectx.date()
1458 try:
1518 try:
1459 return (int(self._repo.wvfs.lstat(self._path).st_mtime), tz)
1519 return (int(self._repo.wvfs.lstat(self._path).st_mtime), tz)
1460 except OSError, err:
1520 except OSError, err:
1461 if err.errno != errno.ENOENT:
1521 if err.errno != errno.ENOENT:
1462 raise
1522 raise
1463 return (t, tz)
1523 return (t, tz)
1464
1524
1465 def cmp(self, fctx):
1525 def cmp(self, fctx):
1466 """compare with other file context
1526 """compare with other file context
1467
1527
1468 returns True if different than fctx.
1528 returns True if different than fctx.
1469 """
1529 """
1470 # fctx should be a filectx (not a workingfilectx)
1530 # fctx should be a filectx (not a workingfilectx)
1471 # invert comparison to reuse the same code path
1531 # invert comparison to reuse the same code path
1472 return fctx.cmp(self)
1532 return fctx.cmp(self)
1473
1533
1474 class memctx(object):
1534 class memctx(object):
1475 """Use memctx to perform in-memory commits via localrepo.commitctx().
1535 """Use memctx to perform in-memory commits via localrepo.commitctx().
1476
1536
1477 Revision information is supplied at initialization time while
1537 Revision information is supplied at initialization time while
1478 related files data and is made available through a callback
1538 related files data and is made available through a callback
1479 mechanism. 'repo' is the current localrepo, 'parents' is a
1539 mechanism. 'repo' is the current localrepo, 'parents' is a
1480 sequence of two parent revisions identifiers (pass None for every
1540 sequence of two parent revisions identifiers (pass None for every
1481 missing parent), 'text' is the commit message and 'files' lists
1541 missing parent), 'text' is the commit message and 'files' lists
1482 names of files touched by the revision (normalized and relative to
1542 names of files touched by the revision (normalized and relative to
1483 repository root).
1543 repository root).
1484
1544
1485 filectxfn(repo, memctx, path) is a callable receiving the
1545 filectxfn(repo, memctx, path) is a callable receiving the
1486 repository, the current memctx object and the normalized path of
1546 repository, the current memctx object and the normalized path of
1487 requested file, relative to repository root. It is fired by the
1547 requested file, relative to repository root. It is fired by the
1488 commit function for every file in 'files', but calls order is
1548 commit function for every file in 'files', but calls order is
1489 undefined. If the file is available in the revision being
1549 undefined. If the file is available in the revision being
1490 committed (updated or added), filectxfn returns a memfilectx
1550 committed (updated or added), filectxfn returns a memfilectx
1491 object. If the file was removed, filectxfn raises an
1551 object. If the file was removed, filectxfn raises an
1492 IOError. Moved files are represented by marking the source file
1552 IOError. Moved files are represented by marking the source file
1493 removed and the new file added with copy information (see
1553 removed and the new file added with copy information (see
1494 memfilectx).
1554 memfilectx).
1495
1555
1496 user receives the committer name and defaults to current
1556 user receives the committer name and defaults to current
1497 repository username, date is the commit date in any format
1557 repository username, date is the commit date in any format
1498 supported by util.parsedate() and defaults to current date, extra
1558 supported by util.parsedate() and defaults to current date, extra
1499 is a dictionary of metadata or is left empty.
1559 is a dictionary of metadata or is left empty.
1500 """
1560 """
1501 def __init__(self, repo, parents, text, files, filectxfn, user=None,
1561 def __init__(self, repo, parents, text, files, filectxfn, user=None,
1502 date=None, extra=None, editor=False):
1562 date=None, extra=None, editor=False):
1503 self._repo = repo
1563 self._repo = repo
1504 self._rev = None
1564 self._rev = None
1505 self._node = None
1565 self._node = None
1506 self._text = text
1566 self._text = text
1507 self._date = date and util.parsedate(date) or util.makedate()
1567 self._date = date and util.parsedate(date) or util.makedate()
1508 self._user = user
1568 self._user = user
1509 parents = [(p or nullid) for p in parents]
1569 parents = [(p or nullid) for p in parents]
1510 p1, p2 = parents
1570 p1, p2 = parents
1511 self._parents = [changectx(self._repo, p) for p in (p1, p2)]
1571 self._parents = [changectx(self._repo, p) for p in (p1, p2)]
1512 files = sorted(set(files))
1572 files = sorted(set(files))
1513 self._status = [files, [], [], [], []]
1573 self._status = [files, [], [], [], []]
1514 self._filectxfn = filectxfn
1574 self._filectxfn = filectxfn
1515
1575
1516 self._extra = extra and extra.copy() or {}
1576 self._extra = extra and extra.copy() or {}
1517 if self._extra.get('branch', '') == '':
1577 if self._extra.get('branch', '') == '':
1518 self._extra['branch'] = 'default'
1578 self._extra['branch'] = 'default'
1519
1579
1520 if editor:
1580 if editor:
1521 self._text = editor(self._repo, self, [])
1581 self._text = editor(self._repo, self, [])
1522 self._repo.savecommitmessage(self._text)
1582 self._repo.savecommitmessage(self._text)
1523
1583
1524 def __str__(self):
1584 def __str__(self):
1525 return str(self._parents[0]) + "+"
1585 return str(self._parents[0]) + "+"
1526
1586
1527 def __int__(self):
1587 def __int__(self):
1528 return self._rev
1588 return self._rev
1529
1589
1530 def __nonzero__(self):
1590 def __nonzero__(self):
1531 return True
1591 return True
1532
1592
1533 def __getitem__(self, key):
1593 def __getitem__(self, key):
1534 return self.filectx(key)
1594 return self.filectx(key)
1535
1595
1536 def p1(self):
1596 def p1(self):
1537 return self._parents[0]
1597 return self._parents[0]
1538 def p2(self):
1598 def p2(self):
1539 return self._parents[1]
1599 return self._parents[1]
1540
1600
1541 def user(self):
1601 def user(self):
1542 return self._user or self._repo.ui.username()
1602 return self._user or self._repo.ui.username()
1543 def date(self):
1603 def date(self):
1544 return self._date
1604 return self._date
1545 def description(self):
1605 def description(self):
1546 return self._text
1606 return self._text
1547 def files(self):
1607 def files(self):
1548 return self.modified()
1608 return self.modified()
1549 def modified(self):
1609 def modified(self):
1550 return self._status[0]
1610 return self._status[0]
1551 def added(self):
1611 def added(self):
1552 return self._status[1]
1612 return self._status[1]
1553 def removed(self):
1613 def removed(self):
1554 return self._status[2]
1614 return self._status[2]
1555 def deleted(self):
1615 def deleted(self):
1556 return self._status[3]
1616 return self._status[3]
1557 def unknown(self):
1617 def unknown(self):
1558 return self._status[4]
1618 return self._status[4]
1559 def ignored(self):
1619 def ignored(self):
1560 return self._status[5]
1620 return self._status[5]
1561 def clean(self):
1621 def clean(self):
1562 return self._status[6]
1622 return self._status[6]
1563 def branch(self):
1623 def branch(self):
1564 return encoding.tolocal(self._extra['branch'])
1624 return encoding.tolocal(self._extra['branch'])
1565 def extra(self):
1625 def extra(self):
1566 return self._extra
1626 return self._extra
1567 def flags(self, f):
1627 def flags(self, f):
1568 return self[f].flags()
1628 return self[f].flags()
1569
1629
1570 def parents(self):
1630 def parents(self):
1571 """return contexts for each parent changeset"""
1631 """return contexts for each parent changeset"""
1572 return self._parents
1632 return self._parents
1573
1633
1574 def filectx(self, path, filelog=None):
1634 def filectx(self, path, filelog=None):
1575 """get a file context from the working directory"""
1635 """get a file context from the working directory"""
1576 return self._filectxfn(self._repo, self, path)
1636 return self._filectxfn(self._repo, self, path)
1577
1637
1578 def commit(self):
1638 def commit(self):
1579 """commit context to the repo"""
1639 """commit context to the repo"""
1580 return self._repo.commitctx(self)
1640 return self._repo.commitctx(self)
1581
1641
1582 class memfilectx(object):
1642 class memfilectx(object):
1583 """memfilectx represents an in-memory file to commit.
1643 """memfilectx represents an in-memory file to commit.
1584
1644
1585 See memctx for more details.
1645 See memctx for more details.
1586 """
1646 """
1587 def __init__(self, path, data, islink=False, isexec=False, copied=None):
1647 def __init__(self, path, data, islink=False, isexec=False, copied=None):
1588 """
1648 """
1589 path is the normalized file path relative to repository root.
1649 path is the normalized file path relative to repository root.
1590 data is the file content as a string.
1650 data is the file content as a string.
1591 islink is True if the file is a symbolic link.
1651 islink is True if the file is a symbolic link.
1592 isexec is True if the file is executable.
1652 isexec is True if the file is executable.
1593 copied is the source file path if current file was copied in the
1653 copied is the source file path if current file was copied in the
1594 revision being committed, or None."""
1654 revision being committed, or None."""
1595 self._path = path
1655 self._path = path
1596 self._data = data
1656 self._data = data
1597 self._flags = (islink and 'l' or '') + (isexec and 'x' or '')
1657 self._flags = (islink and 'l' or '') + (isexec and 'x' or '')
1598 self._copied = None
1658 self._copied = None
1599 if copied:
1659 if copied:
1600 self._copied = (copied, nullid)
1660 self._copied = (copied, nullid)
1601
1661
1602 def __nonzero__(self):
1662 def __nonzero__(self):
1603 return True
1663 return True
1604 def __str__(self):
1664 def __str__(self):
1605 return "%s@%s" % (self.path(), self._changectx)
1665 return "%s@%s" % (self.path(), self._changectx)
1606 def path(self):
1666 def path(self):
1607 return self._path
1667 return self._path
1608 def data(self):
1668 def data(self):
1609 return self._data
1669 return self._data
1610 def flags(self):
1670 def flags(self):
1611 return self._flags
1671 return self._flags
1612 def isexec(self):
1672 def isexec(self):
1613 return 'x' in self._flags
1673 return 'x' in self._flags
1614 def islink(self):
1674 def islink(self):
1615 return 'l' in self._flags
1675 return 'l' in self._flags
1616 def renamed(self):
1676 def renamed(self):
1617 return self._copied
1677 return self._copied
General Comments 0
You need to be logged in to leave comments. Login now