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