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