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