##// END OF EJS Templates
localrepo: factor out parentworking logic for comparing files...
Sean Farley -
r21395:f251b92d default
parent child Browse files
Show More
@@ -1,1422 +1,1455
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, editor)
211 date, extra, editor)
212 return ctx
212 return ctx
213
213
214 class changectx(basectx):
214 class changectx(basectx):
215 """A changecontext object makes access to data related to a particular
215 """A changecontext object makes access to data related to a particular
216 changeset convenient. It represents a read-only context already present in
216 changeset convenient. It represents a read-only context already present in
217 the repo."""
217 the repo."""
218 def __init__(self, repo, changeid=''):
218 def __init__(self, repo, changeid=''):
219 """changeid is a revision number, node, or tag"""
219 """changeid is a revision number, node, or tag"""
220
220
221 # since basectx.__new__ already took care of copying the object, we
221 # since basectx.__new__ already took care of copying the object, we
222 # don't need to do anything in __init__, so we just exit here
222 # don't need to do anything in __init__, so we just exit here
223 if isinstance(changeid, basectx):
223 if isinstance(changeid, basectx):
224 return
224 return
225
225
226 if changeid == '':
226 if changeid == '':
227 changeid = '.'
227 changeid = '.'
228 self._repo = repo
228 self._repo = repo
229
229
230 if isinstance(changeid, int):
230 if isinstance(changeid, int):
231 try:
231 try:
232 self._node = repo.changelog.node(changeid)
232 self._node = repo.changelog.node(changeid)
233 except IndexError:
233 except IndexError:
234 raise error.RepoLookupError(
234 raise error.RepoLookupError(
235 _("unknown revision '%s'") % changeid)
235 _("unknown revision '%s'") % changeid)
236 self._rev = changeid
236 self._rev = changeid
237 return
237 return
238 if isinstance(changeid, long):
238 if isinstance(changeid, long):
239 changeid = str(changeid)
239 changeid = str(changeid)
240 if changeid == '.':
240 if changeid == '.':
241 self._node = repo.dirstate.p1()
241 self._node = repo.dirstate.p1()
242 self._rev = repo.changelog.rev(self._node)
242 self._rev = repo.changelog.rev(self._node)
243 return
243 return
244 if changeid == 'null':
244 if changeid == 'null':
245 self._node = nullid
245 self._node = nullid
246 self._rev = nullrev
246 self._rev = nullrev
247 return
247 return
248 if changeid == 'tip':
248 if changeid == 'tip':
249 self._node = repo.changelog.tip()
249 self._node = repo.changelog.tip()
250 self._rev = repo.changelog.rev(self._node)
250 self._rev = repo.changelog.rev(self._node)
251 return
251 return
252 if len(changeid) == 20:
252 if len(changeid) == 20:
253 try:
253 try:
254 self._node = changeid
254 self._node = changeid
255 self._rev = repo.changelog.rev(changeid)
255 self._rev = repo.changelog.rev(changeid)
256 return
256 return
257 except LookupError:
257 except LookupError:
258 pass
258 pass
259
259
260 try:
260 try:
261 r = int(changeid)
261 r = int(changeid)
262 if str(r) != changeid:
262 if str(r) != changeid:
263 raise ValueError
263 raise ValueError
264 l = len(repo.changelog)
264 l = len(repo.changelog)
265 if r < 0:
265 if r < 0:
266 r += l
266 r += l
267 if r < 0 or r >= l:
267 if r < 0 or r >= l:
268 raise ValueError
268 raise ValueError
269 self._rev = r
269 self._rev = r
270 self._node = repo.changelog.node(r)
270 self._node = repo.changelog.node(r)
271 return
271 return
272 except (ValueError, OverflowError, IndexError):
272 except (ValueError, OverflowError, IndexError):
273 pass
273 pass
274
274
275 if len(changeid) == 40:
275 if len(changeid) == 40:
276 try:
276 try:
277 self._node = bin(changeid)
277 self._node = bin(changeid)
278 self._rev = repo.changelog.rev(self._node)
278 self._rev = repo.changelog.rev(self._node)
279 return
279 return
280 except (TypeError, LookupError):
280 except (TypeError, LookupError):
281 pass
281 pass
282
282
283 if changeid in repo._bookmarks:
283 if changeid in repo._bookmarks:
284 self._node = repo._bookmarks[changeid]
284 self._node = repo._bookmarks[changeid]
285 self._rev = repo.changelog.rev(self._node)
285 self._rev = repo.changelog.rev(self._node)
286 return
286 return
287 if changeid in repo._tagscache.tags:
287 if changeid in repo._tagscache.tags:
288 self._node = repo._tagscache.tags[changeid]
288 self._node = repo._tagscache.tags[changeid]
289 self._rev = repo.changelog.rev(self._node)
289 self._rev = repo.changelog.rev(self._node)
290 return
290 return
291 try:
291 try:
292 self._node = repo.branchtip(changeid)
292 self._node = repo.branchtip(changeid)
293 self._rev = repo.changelog.rev(self._node)
293 self._rev = repo.changelog.rev(self._node)
294 return
294 return
295 except error.RepoLookupError:
295 except error.RepoLookupError:
296 pass
296 pass
297
297
298 self._node = repo.changelog._partialmatch(changeid)
298 self._node = repo.changelog._partialmatch(changeid)
299 if self._node is not None:
299 if self._node is not None:
300 self._rev = repo.changelog.rev(self._node)
300 self._rev = repo.changelog.rev(self._node)
301 return
301 return
302
302
303 # lookup failed
303 # lookup failed
304 # check if it might have come from damaged dirstate
304 # check if it might have come from damaged dirstate
305 #
305 #
306 # XXX we could avoid the unfiltered if we had a recognizable exception
306 # XXX we could avoid the unfiltered if we had a recognizable exception
307 # for filtered changeset access
307 # for filtered changeset access
308 if changeid in repo.unfiltered().dirstate.parents():
308 if changeid in repo.unfiltered().dirstate.parents():
309 raise error.Abort(_("working directory has unknown parent '%s'!")
309 raise error.Abort(_("working directory has unknown parent '%s'!")
310 % short(changeid))
310 % short(changeid))
311 try:
311 try:
312 if len(changeid) == 20:
312 if len(changeid) == 20:
313 changeid = hex(changeid)
313 changeid = hex(changeid)
314 except TypeError:
314 except TypeError:
315 pass
315 pass
316 raise error.RepoLookupError(
316 raise error.RepoLookupError(
317 _("unknown revision '%s'") % changeid)
317 _("unknown revision '%s'") % changeid)
318
318
319 def __hash__(self):
319 def __hash__(self):
320 try:
320 try:
321 return hash(self._rev)
321 return hash(self._rev)
322 except AttributeError:
322 except AttributeError:
323 return id(self)
323 return id(self)
324
324
325 def __nonzero__(self):
325 def __nonzero__(self):
326 return self._rev != nullrev
326 return self._rev != nullrev
327
327
328 @propertycache
328 @propertycache
329 def _changeset(self):
329 def _changeset(self):
330 return self._repo.changelog.read(self.rev())
330 return self._repo.changelog.read(self.rev())
331
331
332 @propertycache
332 @propertycache
333 def _manifest(self):
333 def _manifest(self):
334 return self._repo.manifest.read(self._changeset[0])
334 return self._repo.manifest.read(self._changeset[0])
335
335
336 @propertycache
336 @propertycache
337 def _manifestdelta(self):
337 def _manifestdelta(self):
338 return self._repo.manifest.readdelta(self._changeset[0])
338 return self._repo.manifest.readdelta(self._changeset[0])
339
339
340 @propertycache
340 @propertycache
341 def _parents(self):
341 def _parents(self):
342 p = self._repo.changelog.parentrevs(self._rev)
342 p = self._repo.changelog.parentrevs(self._rev)
343 if p[1] == nullrev:
343 if p[1] == nullrev:
344 p = p[:-1]
344 p = p[:-1]
345 return [changectx(self._repo, x) for x in p]
345 return [changectx(self._repo, x) for x in p]
346
346
347 def changeset(self):
347 def changeset(self):
348 return self._changeset
348 return self._changeset
349 def manifestnode(self):
349 def manifestnode(self):
350 return self._changeset[0]
350 return self._changeset[0]
351
351
352 def user(self):
352 def user(self):
353 return self._changeset[1]
353 return self._changeset[1]
354 def date(self):
354 def date(self):
355 return self._changeset[2]
355 return self._changeset[2]
356 def files(self):
356 def files(self):
357 return self._changeset[3]
357 return self._changeset[3]
358 def description(self):
358 def description(self):
359 return self._changeset[4]
359 return self._changeset[4]
360 def branch(self):
360 def branch(self):
361 return encoding.tolocal(self._changeset[5].get("branch"))
361 return encoding.tolocal(self._changeset[5].get("branch"))
362 def closesbranch(self):
362 def closesbranch(self):
363 return 'close' in self._changeset[5]
363 return 'close' in self._changeset[5]
364 def extra(self):
364 def extra(self):
365 return self._changeset[5]
365 return self._changeset[5]
366 def tags(self):
366 def tags(self):
367 return self._repo.nodetags(self._node)
367 return self._repo.nodetags(self._node)
368 def bookmarks(self):
368 def bookmarks(self):
369 return self._repo.nodebookmarks(self._node)
369 return self._repo.nodebookmarks(self._node)
370 def phase(self):
370 def phase(self):
371 return self._repo._phasecache.phase(self._repo, self._rev)
371 return self._repo._phasecache.phase(self._repo, self._rev)
372 def hidden(self):
372 def hidden(self):
373 return self._rev in repoview.filterrevs(self._repo, 'visible')
373 return self._rev in repoview.filterrevs(self._repo, 'visible')
374
374
375 def children(self):
375 def children(self):
376 """return contexts for each child changeset"""
376 """return contexts for each child changeset"""
377 c = self._repo.changelog.children(self._node)
377 c = self._repo.changelog.children(self._node)
378 return [changectx(self._repo, x) for x in c]
378 return [changectx(self._repo, x) for x in c]
379
379
380 def ancestors(self):
380 def ancestors(self):
381 for a in self._repo.changelog.ancestors([self._rev]):
381 for a in self._repo.changelog.ancestors([self._rev]):
382 yield changectx(self._repo, a)
382 yield changectx(self._repo, a)
383
383
384 def descendants(self):
384 def descendants(self):
385 for d in self._repo.changelog.descendants([self._rev]):
385 for d in self._repo.changelog.descendants([self._rev]):
386 yield changectx(self._repo, d)
386 yield changectx(self._repo, d)
387
387
388 def filectx(self, path, fileid=None, filelog=None):
388 def filectx(self, path, fileid=None, filelog=None):
389 """get a file context from this changeset"""
389 """get a file context from this changeset"""
390 if fileid is None:
390 if fileid is None:
391 fileid = self.filenode(path)
391 fileid = self.filenode(path)
392 return filectx(self._repo, path, fileid=fileid,
392 return filectx(self._repo, path, fileid=fileid,
393 changectx=self, filelog=filelog)
393 changectx=self, filelog=filelog)
394
394
395 def ancestor(self, c2, warn=False):
395 def ancestor(self, c2, warn=False):
396 """
396 """
397 return the "best" ancestor context of self and c2
397 return the "best" ancestor context of self and c2
398 """
398 """
399 # deal with workingctxs
399 # deal with workingctxs
400 n2 = c2._node
400 n2 = c2._node
401 if n2 is None:
401 if n2 is None:
402 n2 = c2._parents[0]._node
402 n2 = c2._parents[0]._node
403 cahs = self._repo.changelog.commonancestorsheads(self._node, n2)
403 cahs = self._repo.changelog.commonancestorsheads(self._node, n2)
404 if not cahs:
404 if not cahs:
405 anc = nullid
405 anc = nullid
406 elif len(cahs) == 1:
406 elif len(cahs) == 1:
407 anc = cahs[0]
407 anc = cahs[0]
408 else:
408 else:
409 for r in self._repo.ui.configlist('merge', 'preferancestor'):
409 for r in self._repo.ui.configlist('merge', 'preferancestor'):
410 ctx = changectx(self._repo, r)
410 ctx = changectx(self._repo, r)
411 anc = ctx.node()
411 anc = ctx.node()
412 if anc in cahs:
412 if anc in cahs:
413 break
413 break
414 else:
414 else:
415 anc = self._repo.changelog.ancestor(self._node, n2)
415 anc = self._repo.changelog.ancestor(self._node, n2)
416 if warn:
416 if warn:
417 self._repo.ui.status(
417 self._repo.ui.status(
418 (_("note: using %s as ancestor of %s and %s\n") %
418 (_("note: using %s as ancestor of %s and %s\n") %
419 (short(anc), short(self._node), short(n2))) +
419 (short(anc), short(self._node), short(n2))) +
420 ''.join(_(" alternatively, use --config "
420 ''.join(_(" alternatively, use --config "
421 "merge.preferancestor=%s\n") %
421 "merge.preferancestor=%s\n") %
422 short(n) for n in sorted(cahs) if n != anc))
422 short(n) for n in sorted(cahs) if n != anc))
423 return changectx(self._repo, anc)
423 return changectx(self._repo, anc)
424
424
425 def descendant(self, other):
425 def descendant(self, other):
426 """True if other is descendant of this changeset"""
426 """True if other is descendant of this changeset"""
427 return self._repo.changelog.descendant(self._rev, other._rev)
427 return self._repo.changelog.descendant(self._rev, other._rev)
428
428
429 def walk(self, match):
429 def walk(self, match):
430 fset = set(match.files())
430 fset = set(match.files())
431 # for dirstate.walk, files=['.'] means "walk the whole tree".
431 # for dirstate.walk, files=['.'] means "walk the whole tree".
432 # follow that here, too
432 # follow that here, too
433 fset.discard('.')
433 fset.discard('.')
434
434
435 # avoid the entire walk if we're only looking for specific files
435 # avoid the entire walk if we're only looking for specific files
436 if fset and not match.anypats():
436 if fset and not match.anypats():
437 if util.all([fn in self for fn in fset]):
437 if util.all([fn in self for fn in fset]):
438 for fn in sorted(fset):
438 for fn in sorted(fset):
439 if match(fn):
439 if match(fn):
440 yield fn
440 yield fn
441 raise StopIteration
441 raise StopIteration
442
442
443 for fn in self:
443 for fn in self:
444 if fn in fset:
444 if fn in fset:
445 # specified pattern is the exact name
445 # specified pattern is the exact name
446 fset.remove(fn)
446 fset.remove(fn)
447 if match(fn):
447 if match(fn):
448 yield fn
448 yield fn
449 for fn in sorted(fset):
449 for fn in sorted(fset):
450 if fn in self._dirs:
450 if fn in self._dirs:
451 # specified pattern is a directory
451 # specified pattern is a directory
452 continue
452 continue
453 match.bad(fn, _('no such file in rev %s') % self)
453 match.bad(fn, _('no such file in rev %s') % self)
454
454
455 class basefilectx(object):
455 class basefilectx(object):
456 """A filecontext object represents the common logic for its children:
456 """A filecontext object represents the common logic for its children:
457 filectx: read-only access to a filerevision that is already present
457 filectx: read-only access to a filerevision that is already present
458 in the repo,
458 in the repo,
459 workingfilectx: a filecontext that represents files from the working
459 workingfilectx: a filecontext that represents files from the working
460 directory,
460 directory,
461 memfilectx: a filecontext that represents files in-memory."""
461 memfilectx: a filecontext that represents files in-memory."""
462 def __new__(cls, repo, path, *args, **kwargs):
462 def __new__(cls, repo, path, *args, **kwargs):
463 return super(basefilectx, cls).__new__(cls)
463 return super(basefilectx, cls).__new__(cls)
464
464
465 @propertycache
465 @propertycache
466 def _filelog(self):
466 def _filelog(self):
467 return self._repo.file(self._path)
467 return self._repo.file(self._path)
468
468
469 @propertycache
469 @propertycache
470 def _changeid(self):
470 def _changeid(self):
471 if '_changeid' in self.__dict__:
471 if '_changeid' in self.__dict__:
472 return self._changeid
472 return self._changeid
473 elif '_changectx' in self.__dict__:
473 elif '_changectx' in self.__dict__:
474 return self._changectx.rev()
474 return self._changectx.rev()
475 else:
475 else:
476 return self._filelog.linkrev(self._filerev)
476 return self._filelog.linkrev(self._filerev)
477
477
478 @propertycache
478 @propertycache
479 def _filenode(self):
479 def _filenode(self):
480 if '_fileid' in self.__dict__:
480 if '_fileid' in self.__dict__:
481 return self._filelog.lookup(self._fileid)
481 return self._filelog.lookup(self._fileid)
482 else:
482 else:
483 return self._changectx.filenode(self._path)
483 return self._changectx.filenode(self._path)
484
484
485 @propertycache
485 @propertycache
486 def _filerev(self):
486 def _filerev(self):
487 return self._filelog.rev(self._filenode)
487 return self._filelog.rev(self._filenode)
488
488
489 @propertycache
489 @propertycache
490 def _repopath(self):
490 def _repopath(self):
491 return self._path
491 return self._path
492
492
493 def __nonzero__(self):
493 def __nonzero__(self):
494 try:
494 try:
495 self._filenode
495 self._filenode
496 return True
496 return True
497 except error.LookupError:
497 except error.LookupError:
498 # file is missing
498 # file is missing
499 return False
499 return False
500
500
501 def __str__(self):
501 def __str__(self):
502 return "%s@%s" % (self.path(), self._changectx)
502 return "%s@%s" % (self.path(), self._changectx)
503
503
504 def __repr__(self):
504 def __repr__(self):
505 return "<%s %s>" % (type(self).__name__, str(self))
505 return "<%s %s>" % (type(self).__name__, str(self))
506
506
507 def __hash__(self):
507 def __hash__(self):
508 try:
508 try:
509 return hash((self._path, self._filenode))
509 return hash((self._path, self._filenode))
510 except AttributeError:
510 except AttributeError:
511 return id(self)
511 return id(self)
512
512
513 def __eq__(self, other):
513 def __eq__(self, other):
514 try:
514 try:
515 return (type(self) == type(other) and self._path == other._path
515 return (type(self) == type(other) and self._path == other._path
516 and self._filenode == other._filenode)
516 and self._filenode == other._filenode)
517 except AttributeError:
517 except AttributeError:
518 return False
518 return False
519
519
520 def __ne__(self, other):
520 def __ne__(self, other):
521 return not (self == other)
521 return not (self == other)
522
522
523 def filerev(self):
523 def filerev(self):
524 return self._filerev
524 return self._filerev
525 def filenode(self):
525 def filenode(self):
526 return self._filenode
526 return self._filenode
527 def flags(self):
527 def flags(self):
528 return self._changectx.flags(self._path)
528 return self._changectx.flags(self._path)
529 def filelog(self):
529 def filelog(self):
530 return self._filelog
530 return self._filelog
531 def rev(self):
531 def rev(self):
532 return self._changeid
532 return self._changeid
533 def linkrev(self):
533 def linkrev(self):
534 return self._filelog.linkrev(self._filerev)
534 return self._filelog.linkrev(self._filerev)
535 def node(self):
535 def node(self):
536 return self._changectx.node()
536 return self._changectx.node()
537 def hex(self):
537 def hex(self):
538 return self._changectx.hex()
538 return self._changectx.hex()
539 def user(self):
539 def user(self):
540 return self._changectx.user()
540 return self._changectx.user()
541 def date(self):
541 def date(self):
542 return self._changectx.date()
542 return self._changectx.date()
543 def files(self):
543 def files(self):
544 return self._changectx.files()
544 return self._changectx.files()
545 def description(self):
545 def description(self):
546 return self._changectx.description()
546 return self._changectx.description()
547 def branch(self):
547 def branch(self):
548 return self._changectx.branch()
548 return self._changectx.branch()
549 def extra(self):
549 def extra(self):
550 return self._changectx.extra()
550 return self._changectx.extra()
551 def phase(self):
551 def phase(self):
552 return self._changectx.phase()
552 return self._changectx.phase()
553 def phasestr(self):
553 def phasestr(self):
554 return self._changectx.phasestr()
554 return self._changectx.phasestr()
555 def manifest(self):
555 def manifest(self):
556 return self._changectx.manifest()
556 return self._changectx.manifest()
557 def changectx(self):
557 def changectx(self):
558 return self._changectx
558 return self._changectx
559
559
560 def path(self):
560 def path(self):
561 return self._path
561 return self._path
562
562
563 def isbinary(self):
563 def isbinary(self):
564 try:
564 try:
565 return util.binary(self.data())
565 return util.binary(self.data())
566 except IOError:
566 except IOError:
567 return False
567 return False
568
568
569 def cmp(self, fctx):
569 def cmp(self, fctx):
570 """compare with other file context
570 """compare with other file context
571
571
572 returns True if different than fctx.
572 returns True if different than fctx.
573 """
573 """
574 if (fctx._filerev is None
574 if (fctx._filerev is None
575 and (self._repo._encodefilterpats
575 and (self._repo._encodefilterpats
576 # if file data starts with '\1\n', empty metadata block is
576 # if file data starts with '\1\n', empty metadata block is
577 # prepended, which adds 4 bytes to filelog.size().
577 # prepended, which adds 4 bytes to filelog.size().
578 or self.size() - 4 == fctx.size())
578 or self.size() - 4 == fctx.size())
579 or self.size() == fctx.size()):
579 or self.size() == fctx.size()):
580 return self._filelog.cmp(self._filenode, fctx.data())
580 return self._filelog.cmp(self._filenode, fctx.data())
581
581
582 return True
582 return True
583
583
584 def parents(self):
584 def parents(self):
585 p = self._path
585 p = self._path
586 fl = self._filelog
586 fl = self._filelog
587 pl = [(p, n, fl) for n in self._filelog.parents(self._filenode)]
587 pl = [(p, n, fl) for n in self._filelog.parents(self._filenode)]
588
588
589 r = self._filelog.renamed(self._filenode)
589 r = self._filelog.renamed(self._filenode)
590 if r:
590 if r:
591 pl[0] = (r[0], r[1], None)
591 pl[0] = (r[0], r[1], None)
592
592
593 return [filectx(self._repo, p, fileid=n, filelog=l)
593 return [filectx(self._repo, p, fileid=n, filelog=l)
594 for p, n, l in pl if n != nullid]
594 for p, n, l in pl if n != nullid]
595
595
596 def p1(self):
596 def p1(self):
597 return self.parents()[0]
597 return self.parents()[0]
598
598
599 def p2(self):
599 def p2(self):
600 p = self.parents()
600 p = self.parents()
601 if len(p) == 2:
601 if len(p) == 2:
602 return p[1]
602 return p[1]
603 return filectx(self._repo, self._path, fileid=-1, filelog=self._filelog)
603 return filectx(self._repo, self._path, fileid=-1, filelog=self._filelog)
604
604
605 def annotate(self, follow=False, linenumber=None, diffopts=None):
605 def annotate(self, follow=False, linenumber=None, diffopts=None):
606 '''returns a list of tuples of (ctx, line) for each line
606 '''returns a list of tuples of (ctx, line) for each line
607 in the file, where ctx is the filectx of the node where
607 in the file, where ctx is the filectx of the node where
608 that line was last changed.
608 that line was last changed.
609 This returns tuples of ((ctx, linenumber), line) for each line,
609 This returns tuples of ((ctx, linenumber), line) for each line,
610 if "linenumber" parameter is NOT "None".
610 if "linenumber" parameter is NOT "None".
611 In such tuples, linenumber means one at the first appearance
611 In such tuples, linenumber means one at the first appearance
612 in the managed file.
612 in the managed file.
613 To reduce annotation cost,
613 To reduce annotation cost,
614 this returns fixed value(False is used) as linenumber,
614 this returns fixed value(False is used) as linenumber,
615 if "linenumber" parameter is "False".'''
615 if "linenumber" parameter is "False".'''
616
616
617 def decorate_compat(text, rev):
617 def decorate_compat(text, rev):
618 return ([rev] * len(text.splitlines()), text)
618 return ([rev] * len(text.splitlines()), text)
619
619
620 def without_linenumber(text, rev):
620 def without_linenumber(text, rev):
621 return ([(rev, False)] * len(text.splitlines()), text)
621 return ([(rev, False)] * len(text.splitlines()), text)
622
622
623 def with_linenumber(text, rev):
623 def with_linenumber(text, rev):
624 size = len(text.splitlines())
624 size = len(text.splitlines())
625 return ([(rev, i) for i in xrange(1, size + 1)], text)
625 return ([(rev, i) for i in xrange(1, size + 1)], text)
626
626
627 decorate = (((linenumber is None) and decorate_compat) or
627 decorate = (((linenumber is None) and decorate_compat) or
628 (linenumber and with_linenumber) or
628 (linenumber and with_linenumber) or
629 without_linenumber)
629 without_linenumber)
630
630
631 def pair(parent, child):
631 def pair(parent, child):
632 blocks = mdiff.allblocks(parent[1], child[1], opts=diffopts,
632 blocks = mdiff.allblocks(parent[1], child[1], opts=diffopts,
633 refine=True)
633 refine=True)
634 for (a1, a2, b1, b2), t in blocks:
634 for (a1, a2, b1, b2), t in blocks:
635 # Changed blocks ('!') or blocks made only of blank lines ('~')
635 # Changed blocks ('!') or blocks made only of blank lines ('~')
636 # belong to the child.
636 # belong to the child.
637 if t == '=':
637 if t == '=':
638 child[0][b1:b2] = parent[0][a1:a2]
638 child[0][b1:b2] = parent[0][a1:a2]
639 return child
639 return child
640
640
641 getlog = util.lrucachefunc(lambda x: self._repo.file(x))
641 getlog = util.lrucachefunc(lambda x: self._repo.file(x))
642
642
643 def parents(f):
643 def parents(f):
644 pl = f.parents()
644 pl = f.parents()
645
645
646 # Don't return renamed parents if we aren't following.
646 # Don't return renamed parents if we aren't following.
647 if not follow:
647 if not follow:
648 pl = [p for p in pl if p.path() == f.path()]
648 pl = [p for p in pl if p.path() == f.path()]
649
649
650 # renamed filectx won't have a filelog yet, so set it
650 # renamed filectx won't have a filelog yet, so set it
651 # from the cache to save time
651 # from the cache to save time
652 for p in pl:
652 for p in pl:
653 if not '_filelog' in p.__dict__:
653 if not '_filelog' in p.__dict__:
654 p._filelog = getlog(p.path())
654 p._filelog = getlog(p.path())
655
655
656 return pl
656 return pl
657
657
658 # use linkrev to find the first changeset where self appeared
658 # use linkrev to find the first changeset where self appeared
659 if self.rev() != self.linkrev():
659 if self.rev() != self.linkrev():
660 base = self.filectx(self.filenode())
660 base = self.filectx(self.filenode())
661 else:
661 else:
662 base = self
662 base = self
663
663
664 # This algorithm would prefer to be recursive, but Python is a
664 # This algorithm would prefer to be recursive, but Python is a
665 # bit recursion-hostile. Instead we do an iterative
665 # bit recursion-hostile. Instead we do an iterative
666 # depth-first search.
666 # depth-first search.
667
667
668 visit = [base]
668 visit = [base]
669 hist = {}
669 hist = {}
670 pcache = {}
670 pcache = {}
671 needed = {base: 1}
671 needed = {base: 1}
672 while visit:
672 while visit:
673 f = visit[-1]
673 f = visit[-1]
674 pcached = f in pcache
674 pcached = f in pcache
675 if not pcached:
675 if not pcached:
676 pcache[f] = parents(f)
676 pcache[f] = parents(f)
677
677
678 ready = True
678 ready = True
679 pl = pcache[f]
679 pl = pcache[f]
680 for p in pl:
680 for p in pl:
681 if p not in hist:
681 if p not in hist:
682 ready = False
682 ready = False
683 visit.append(p)
683 visit.append(p)
684 if not pcached:
684 if not pcached:
685 needed[p] = needed.get(p, 0) + 1
685 needed[p] = needed.get(p, 0) + 1
686 if ready:
686 if ready:
687 visit.pop()
687 visit.pop()
688 reusable = f in hist
688 reusable = f in hist
689 if reusable:
689 if reusable:
690 curr = hist[f]
690 curr = hist[f]
691 else:
691 else:
692 curr = decorate(f.data(), f)
692 curr = decorate(f.data(), f)
693 for p in pl:
693 for p in pl:
694 if not reusable:
694 if not reusable:
695 curr = pair(hist[p], curr)
695 curr = pair(hist[p], curr)
696 if needed[p] == 1:
696 if needed[p] == 1:
697 del hist[p]
697 del hist[p]
698 del needed[p]
698 del needed[p]
699 else:
699 else:
700 needed[p] -= 1
700 needed[p] -= 1
701
701
702 hist[f] = curr
702 hist[f] = curr
703 pcache[f] = []
703 pcache[f] = []
704
704
705 return zip(hist[base][0], hist[base][1].splitlines(True))
705 return zip(hist[base][0], hist[base][1].splitlines(True))
706
706
707 def ancestors(self, followfirst=False):
707 def ancestors(self, followfirst=False):
708 visit = {}
708 visit = {}
709 c = self
709 c = self
710 cut = followfirst and 1 or None
710 cut = followfirst and 1 or None
711 while True:
711 while True:
712 for parent in c.parents()[:cut]:
712 for parent in c.parents()[:cut]:
713 visit[(parent.rev(), parent.node())] = parent
713 visit[(parent.rev(), parent.node())] = parent
714 if not visit:
714 if not visit:
715 break
715 break
716 c = visit.pop(max(visit))
716 c = visit.pop(max(visit))
717 yield c
717 yield c
718
718
719 class filectx(basefilectx):
719 class filectx(basefilectx):
720 """A filecontext object makes access to data related to a particular
720 """A filecontext object makes access to data related to a particular
721 filerevision convenient."""
721 filerevision convenient."""
722 def __init__(self, repo, path, changeid=None, fileid=None,
722 def __init__(self, repo, path, changeid=None, fileid=None,
723 filelog=None, changectx=None):
723 filelog=None, changectx=None):
724 """changeid can be a changeset revision, node, or tag.
724 """changeid can be a changeset revision, node, or tag.
725 fileid can be a file revision or node."""
725 fileid can be a file revision or node."""
726 self._repo = repo
726 self._repo = repo
727 self._path = path
727 self._path = path
728
728
729 assert (changeid is not None
729 assert (changeid is not None
730 or fileid is not None
730 or fileid is not None
731 or changectx is not None), \
731 or changectx is not None), \
732 ("bad args: changeid=%r, fileid=%r, changectx=%r"
732 ("bad args: changeid=%r, fileid=%r, changectx=%r"
733 % (changeid, fileid, changectx))
733 % (changeid, fileid, changectx))
734
734
735 if filelog is not None:
735 if filelog is not None:
736 self._filelog = filelog
736 self._filelog = filelog
737
737
738 if changeid is not None:
738 if changeid is not None:
739 self._changeid = changeid
739 self._changeid = changeid
740 if changectx is not None:
740 if changectx is not None:
741 self._changectx = changectx
741 self._changectx = changectx
742 if fileid is not None:
742 if fileid is not None:
743 self._fileid = fileid
743 self._fileid = fileid
744
744
745 @propertycache
745 @propertycache
746 def _changectx(self):
746 def _changectx(self):
747 try:
747 try:
748 return changectx(self._repo, self._changeid)
748 return changectx(self._repo, self._changeid)
749 except error.RepoLookupError:
749 except error.RepoLookupError:
750 # Linkrev may point to any revision in the repository. When the
750 # Linkrev may point to any revision in the repository. When the
751 # repository is filtered this may lead to `filectx` trying to build
751 # repository is filtered this may lead to `filectx` trying to build
752 # `changectx` for filtered revision. In such case we fallback to
752 # `changectx` for filtered revision. In such case we fallback to
753 # creating `changectx` on the unfiltered version of the reposition.
753 # creating `changectx` on the unfiltered version of the reposition.
754 # This fallback should not be an issue because `changectx` from
754 # This fallback should not be an issue because `changectx` from
755 # `filectx` are not used in complex operations that care about
755 # `filectx` are not used in complex operations that care about
756 # filtering.
756 # filtering.
757 #
757 #
758 # This fallback is a cheap and dirty fix that prevent several
758 # This fallback is a cheap and dirty fix that prevent several
759 # crashes. It does not ensure the behavior is correct. However the
759 # crashes. It does not ensure the behavior is correct. However the
760 # behavior was not correct before filtering either and "incorrect
760 # behavior was not correct before filtering either and "incorrect
761 # behavior" is seen as better as "crash"
761 # behavior" is seen as better as "crash"
762 #
762 #
763 # Linkrevs have several serious troubles with filtering that are
763 # Linkrevs have several serious troubles with filtering that are
764 # complicated to solve. Proper handling of the issue here should be
764 # complicated to solve. Proper handling of the issue here should be
765 # considered when solving linkrev issue are on the table.
765 # considered when solving linkrev issue are on the table.
766 return changectx(self._repo.unfiltered(), self._changeid)
766 return changectx(self._repo.unfiltered(), self._changeid)
767
767
768 def filectx(self, fileid):
768 def filectx(self, fileid):
769 '''opens an arbitrary revision of the file without
769 '''opens an arbitrary revision of the file without
770 opening a new filelog'''
770 opening a new filelog'''
771 return filectx(self._repo, self._path, fileid=fileid,
771 return filectx(self._repo, self._path, fileid=fileid,
772 filelog=self._filelog)
772 filelog=self._filelog)
773
773
774 def data(self):
774 def data(self):
775 return self._filelog.read(self._filenode)
775 return self._filelog.read(self._filenode)
776 def size(self):
776 def size(self):
777 return self._filelog.size(self._filerev)
777 return self._filelog.size(self._filerev)
778
778
779 def renamed(self):
779 def renamed(self):
780 """check if file was actually renamed in this changeset revision
780 """check if file was actually renamed in this changeset revision
781
781
782 If rename logged in file revision, we report copy for changeset only
782 If rename logged in file revision, we report copy for changeset only
783 if file revisions linkrev points back to the changeset in question
783 if file revisions linkrev points back to the changeset in question
784 or both changeset parents contain different file revisions.
784 or both changeset parents contain different file revisions.
785 """
785 """
786
786
787 renamed = self._filelog.renamed(self._filenode)
787 renamed = self._filelog.renamed(self._filenode)
788 if not renamed:
788 if not renamed:
789 return renamed
789 return renamed
790
790
791 if self.rev() == self.linkrev():
791 if self.rev() == self.linkrev():
792 return renamed
792 return renamed
793
793
794 name = self.path()
794 name = self.path()
795 fnode = self._filenode
795 fnode = self._filenode
796 for p in self._changectx.parents():
796 for p in self._changectx.parents():
797 try:
797 try:
798 if fnode == p.filenode(name):
798 if fnode == p.filenode(name):
799 return None
799 return None
800 except error.LookupError:
800 except error.LookupError:
801 pass
801 pass
802 return renamed
802 return renamed
803
803
804 def children(self):
804 def children(self):
805 # hard for renames
805 # hard for renames
806 c = self._filelog.children(self._filenode)
806 c = self._filelog.children(self._filenode)
807 return [filectx(self._repo, self._path, fileid=x,
807 return [filectx(self._repo, self._path, fileid=x,
808 filelog=self._filelog) for x in c]
808 filelog=self._filelog) for x in c]
809
809
810 class committablectx(basectx):
810 class committablectx(basectx):
811 """A committablectx object provides common functionality for a context that
811 """A committablectx object provides common functionality for a context that
812 wants the ability to commit, e.g. workingctx or memctx."""
812 wants the ability to commit, e.g. workingctx or memctx."""
813 def __init__(self, repo, text="", user=None, date=None, extra=None,
813 def __init__(self, repo, text="", user=None, date=None, extra=None,
814 changes=None):
814 changes=None):
815 self._repo = repo
815 self._repo = repo
816 self._rev = None
816 self._rev = None
817 self._node = None
817 self._node = None
818 self._text = text
818 self._text = text
819 if date:
819 if date:
820 self._date = util.parsedate(date)
820 self._date = util.parsedate(date)
821 if user:
821 if user:
822 self._user = user
822 self._user = user
823 if changes:
823 if changes:
824 self._status = list(changes[:4])
824 self._status = list(changes[:4])
825 self._unknown = changes[4]
825 self._unknown = changes[4]
826 self._ignored = changes[5]
826 self._ignored = changes[5]
827 self._clean = changes[6]
827 self._clean = changes[6]
828 else:
828 else:
829 self._unknown = None
829 self._unknown = None
830 self._ignored = None
830 self._ignored = None
831 self._clean = None
831 self._clean = None
832
832
833 self._extra = {}
833 self._extra = {}
834 if extra:
834 if extra:
835 self._extra = extra.copy()
835 self._extra = extra.copy()
836 if 'branch' not in self._extra:
836 if 'branch' not in self._extra:
837 try:
837 try:
838 branch = encoding.fromlocal(self._repo.dirstate.branch())
838 branch = encoding.fromlocal(self._repo.dirstate.branch())
839 except UnicodeDecodeError:
839 except UnicodeDecodeError:
840 raise util.Abort(_('branch name not in UTF-8!'))
840 raise util.Abort(_('branch name not in UTF-8!'))
841 self._extra['branch'] = branch
841 self._extra['branch'] = branch
842 if self._extra['branch'] == '':
842 if self._extra['branch'] == '':
843 self._extra['branch'] = 'default'
843 self._extra['branch'] = 'default'
844
844
845 def __str__(self):
845 def __str__(self):
846 return str(self._parents[0]) + "+"
846 return str(self._parents[0]) + "+"
847
847
848 def __nonzero__(self):
848 def __nonzero__(self):
849 return True
849 return True
850
850
851 def __contains__(self, key):
851 def __contains__(self, key):
852 return self._repo.dirstate[key] not in "?r"
852 return self._repo.dirstate[key] not in "?r"
853
853
854 def _buildflagfunc(self):
854 def _buildflagfunc(self):
855 # Create a fallback function for getting file flags when the
855 # Create a fallback function for getting file flags when the
856 # filesystem doesn't support them
856 # filesystem doesn't support them
857
857
858 copiesget = self._repo.dirstate.copies().get
858 copiesget = self._repo.dirstate.copies().get
859
859
860 if len(self._parents) < 2:
860 if len(self._parents) < 2:
861 # when we have one parent, it's easy: copy from parent
861 # when we have one parent, it's easy: copy from parent
862 man = self._parents[0].manifest()
862 man = self._parents[0].manifest()
863 def func(f):
863 def func(f):
864 f = copiesget(f, f)
864 f = copiesget(f, f)
865 return man.flags(f)
865 return man.flags(f)
866 else:
866 else:
867 # merges are tricky: we try to reconstruct the unstored
867 # merges are tricky: we try to reconstruct the unstored
868 # result from the merge (issue1802)
868 # result from the merge (issue1802)
869 p1, p2 = self._parents
869 p1, p2 = self._parents
870 pa = p1.ancestor(p2)
870 pa = p1.ancestor(p2)
871 m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest()
871 m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest()
872
872
873 def func(f):
873 def func(f):
874 f = copiesget(f, f) # may be wrong for merges with copies
874 f = copiesget(f, f) # may be wrong for merges with copies
875 fl1, fl2, fla = m1.flags(f), m2.flags(f), ma.flags(f)
875 fl1, fl2, fla = m1.flags(f), m2.flags(f), ma.flags(f)
876 if fl1 == fl2:
876 if fl1 == fl2:
877 return fl1
877 return fl1
878 if fl1 == fla:
878 if fl1 == fla:
879 return fl2
879 return fl2
880 if fl2 == fla:
880 if fl2 == fla:
881 return fl1
881 return fl1
882 return '' # punt for conflicts
882 return '' # punt for conflicts
883
883
884 return func
884 return func
885
885
886 @propertycache
886 @propertycache
887 def _flagfunc(self):
887 def _flagfunc(self):
888 return self._repo.dirstate.flagfunc(self._buildflagfunc)
888 return self._repo.dirstate.flagfunc(self._buildflagfunc)
889
889
890 @propertycache
890 @propertycache
891 def _manifest(self):
891 def _manifest(self):
892 """generate a manifest corresponding to the working directory"""
892 """generate a manifest corresponding to the working directory"""
893
893
894 man = self._parents[0].manifest().copy()
894 man = self._parents[0].manifest().copy()
895 if len(self._parents) > 1:
895 if len(self._parents) > 1:
896 man2 = self.p2().manifest()
896 man2 = self.p2().manifest()
897 def getman(f):
897 def getman(f):
898 if f in man:
898 if f in man:
899 return man
899 return man
900 return man2
900 return man2
901 else:
901 else:
902 getman = lambda f: man
902 getman = lambda f: man
903
903
904 copied = self._repo.dirstate.copies()
904 copied = self._repo.dirstate.copies()
905 ff = self._flagfunc
905 ff = self._flagfunc
906 modified, added, removed, deleted = self._status
906 modified, added, removed, deleted = self._status
907 for i, l in (("a", added), ("m", modified)):
907 for i, l in (("a", added), ("m", modified)):
908 for f in l:
908 for f in l:
909 orig = copied.get(f, f)
909 orig = copied.get(f, f)
910 man[f] = getman(orig).get(orig, nullid) + i
910 man[f] = getman(orig).get(orig, nullid) + i
911 try:
911 try:
912 man.set(f, ff(f))
912 man.set(f, ff(f))
913 except OSError:
913 except OSError:
914 pass
914 pass
915
915
916 for f in deleted + removed:
916 for f in deleted + removed:
917 if f in man:
917 if f in man:
918 del man[f]
918 del man[f]
919
919
920 return man
920 return man
921
921
922 @propertycache
922 @propertycache
923 def _status(self):
923 def _status(self):
924 return self._repo.status()[:4]
924 return self._repo.status()[:4]
925
925
926 @propertycache
926 @propertycache
927 def _user(self):
927 def _user(self):
928 return self._repo.ui.username()
928 return self._repo.ui.username()
929
929
930 @propertycache
930 @propertycache
931 def _date(self):
931 def _date(self):
932 return util.makedate()
932 return util.makedate()
933
933
934 def status(self, ignored=False, clean=False, unknown=False):
934 def status(self, ignored=False, clean=False, unknown=False):
935 """Explicit status query
935 """Explicit status query
936 Unless this method is used to query the working copy status, the
936 Unless this method is used to query the working copy status, the
937 _status property will implicitly read the status using its default
937 _status property will implicitly read the status using its default
938 arguments."""
938 arguments."""
939 stat = self._repo.status(ignored=ignored, clean=clean, unknown=unknown)
939 stat = self._repo.status(ignored=ignored, clean=clean, unknown=unknown)
940 self._unknown = self._ignored = self._clean = None
940 self._unknown = self._ignored = self._clean = None
941 if unknown:
941 if unknown:
942 self._unknown = stat[4]
942 self._unknown = stat[4]
943 if ignored:
943 if ignored:
944 self._ignored = stat[5]
944 self._ignored = stat[5]
945 if clean:
945 if clean:
946 self._clean = stat[6]
946 self._clean = stat[6]
947 self._status = stat[:4]
947 self._status = stat[:4]
948 return stat
948 return stat
949
949
950 def user(self):
950 def user(self):
951 return self._user or self._repo.ui.username()
951 return self._user or self._repo.ui.username()
952 def date(self):
952 def date(self):
953 return self._date
953 return self._date
954 def description(self):
954 def description(self):
955 return self._text
955 return self._text
956 def files(self):
956 def files(self):
957 return sorted(self._status[0] + self._status[1] + self._status[2])
957 return sorted(self._status[0] + self._status[1] + self._status[2])
958
958
959 def modified(self):
959 def modified(self):
960 return self._status[0]
960 return self._status[0]
961 def added(self):
961 def added(self):
962 return self._status[1]
962 return self._status[1]
963 def removed(self):
963 def removed(self):
964 return self._status[2]
964 return self._status[2]
965 def deleted(self):
965 def deleted(self):
966 return self._status[3]
966 return self._status[3]
967 def unknown(self):
967 def unknown(self):
968 assert self._unknown is not None # must call status first
968 assert self._unknown is not None # must call status first
969 return self._unknown
969 return self._unknown
970 def ignored(self):
970 def ignored(self):
971 assert self._ignored is not None # must call status first
971 assert self._ignored is not None # must call status first
972 return self._ignored
972 return self._ignored
973 def clean(self):
973 def clean(self):
974 assert self._clean is not None # must call status first
974 assert self._clean is not None # must call status first
975 return self._clean
975 return self._clean
976 def branch(self):
976 def branch(self):
977 return encoding.tolocal(self._extra['branch'])
977 return encoding.tolocal(self._extra['branch'])
978 def closesbranch(self):
978 def closesbranch(self):
979 return 'close' in self._extra
979 return 'close' in self._extra
980 def extra(self):
980 def extra(self):
981 return self._extra
981 return self._extra
982
982
983 def tags(self):
983 def tags(self):
984 t = []
984 t = []
985 for p in self.parents():
985 for p in self.parents():
986 t.extend(p.tags())
986 t.extend(p.tags())
987 return t
987 return t
988
988
989 def bookmarks(self):
989 def bookmarks(self):
990 b = []
990 b = []
991 for p in self.parents():
991 for p in self.parents():
992 b.extend(p.bookmarks())
992 b.extend(p.bookmarks())
993 return b
993 return b
994
994
995 def phase(self):
995 def phase(self):
996 phase = phases.draft # default phase to draft
996 phase = phases.draft # default phase to draft
997 for p in self.parents():
997 for p in self.parents():
998 phase = max(phase, p.phase())
998 phase = max(phase, p.phase())
999 return phase
999 return phase
1000
1000
1001 def hidden(self):
1001 def hidden(self):
1002 return False
1002 return False
1003
1003
1004 def children(self):
1004 def children(self):
1005 return []
1005 return []
1006
1006
1007 def flags(self, path):
1007 def flags(self, path):
1008 if '_manifest' in self.__dict__:
1008 if '_manifest' in self.__dict__:
1009 try:
1009 try:
1010 return self._manifest.flags(path)
1010 return self._manifest.flags(path)
1011 except KeyError:
1011 except KeyError:
1012 return ''
1012 return ''
1013
1013
1014 try:
1014 try:
1015 return self._flagfunc(path)
1015 return self._flagfunc(path)
1016 except OSError:
1016 except OSError:
1017 return ''
1017 return ''
1018
1018
1019 def ancestor(self, c2):
1019 def ancestor(self, c2):
1020 """return the ancestor context of self and c2"""
1020 """return the ancestor context of self and c2"""
1021 return self._parents[0].ancestor(c2) # punt on two parents for now
1021 return self._parents[0].ancestor(c2) # punt on two parents for now
1022
1022
1023 def walk(self, match):
1023 def walk(self, match):
1024 return sorted(self._repo.dirstate.walk(match, sorted(self.substate),
1024 return sorted(self._repo.dirstate.walk(match, sorted(self.substate),
1025 True, False))
1025 True, False))
1026
1026
1027 def ancestors(self):
1027 def ancestors(self):
1028 for a in self._repo.changelog.ancestors(
1028 for a in self._repo.changelog.ancestors(
1029 [p.rev() for p in self._parents]):
1029 [p.rev() for p in self._parents]):
1030 yield changectx(self._repo, a)
1030 yield changectx(self._repo, a)
1031
1031
1032 def markcommitted(self, node):
1032 def markcommitted(self, node):
1033 """Perform post-commit cleanup necessary after committing this ctx
1033 """Perform post-commit cleanup necessary after committing this ctx
1034
1034
1035 Specifically, this updates backing stores this working context
1035 Specifically, this updates backing stores this working context
1036 wraps to reflect the fact that the changes reflected by this
1036 wraps to reflect the fact that the changes reflected by this
1037 workingctx have been committed. For example, it marks
1037 workingctx have been committed. For example, it marks
1038 modified and added files as normal in the dirstate.
1038 modified and added files as normal in the dirstate.
1039
1039
1040 """
1040 """
1041
1041
1042 for f in self.modified() + self.added():
1042 for f in self.modified() + self.added():
1043 self._repo.dirstate.normal(f)
1043 self._repo.dirstate.normal(f)
1044 for f in self.removed():
1044 for f in self.removed():
1045 self._repo.dirstate.drop(f)
1045 self._repo.dirstate.drop(f)
1046 self._repo.dirstate.setparents(node)
1046 self._repo.dirstate.setparents(node)
1047
1047
1048 def dirs(self):
1048 def dirs(self):
1049 return self._repo.dirstate.dirs()
1049 return self._repo.dirstate.dirs()
1050
1050
1051 class workingctx(committablectx):
1051 class workingctx(committablectx):
1052 """A workingctx object makes access to data related to
1052 """A workingctx object makes access to data related to
1053 the current working directory convenient.
1053 the current working directory convenient.
1054 date - any valid date string or (unixtime, offset), or None.
1054 date - any valid date string or (unixtime, offset), or None.
1055 user - username string, or None.
1055 user - username string, or None.
1056 extra - a dictionary of extra values, or None.
1056 extra - a dictionary of extra values, or None.
1057 changes - a list of file lists as returned by localrepo.status()
1057 changes - a list of file lists as returned by localrepo.status()
1058 or None to use the repository status.
1058 or None to use the repository status.
1059 """
1059 """
1060 def __init__(self, repo, text="", user=None, date=None, extra=None,
1060 def __init__(self, repo, text="", user=None, date=None, extra=None,
1061 changes=None):
1061 changes=None):
1062 super(workingctx, self).__init__(repo, text, user, date, extra, changes)
1062 super(workingctx, self).__init__(repo, text, user, date, extra, changes)
1063
1063
1064 def __iter__(self):
1064 def __iter__(self):
1065 d = self._repo.dirstate
1065 d = self._repo.dirstate
1066 for f in d:
1066 for f in d:
1067 if d[f] != 'r':
1067 if d[f] != 'r':
1068 yield f
1068 yield f
1069
1069
1070 @propertycache
1070 @propertycache
1071 def _parents(self):
1071 def _parents(self):
1072 p = self._repo.dirstate.parents()
1072 p = self._repo.dirstate.parents()
1073 if p[1] == nullid:
1073 if p[1] == nullid:
1074 p = p[:-1]
1074 p = p[:-1]
1075 return [changectx(self._repo, x) for x in p]
1075 return [changectx(self._repo, x) for x in p]
1076
1076
1077 def filectx(self, path, filelog=None):
1077 def filectx(self, path, filelog=None):
1078 """get a file context from the working directory"""
1078 """get a file context from the working directory"""
1079 return workingfilectx(self._repo, path, workingctx=self,
1079 return workingfilectx(self._repo, path, workingctx=self,
1080 filelog=filelog)
1080 filelog=filelog)
1081
1081
1082 def dirty(self, missing=False, merge=True, branch=True):
1082 def dirty(self, missing=False, merge=True, branch=True):
1083 "check whether a working directory is modified"
1083 "check whether a working directory is modified"
1084 # check subrepos first
1084 # check subrepos first
1085 for s in sorted(self.substate):
1085 for s in sorted(self.substate):
1086 if self.sub(s).dirty():
1086 if self.sub(s).dirty():
1087 return True
1087 return True
1088 # check current working dir
1088 # check current working dir
1089 return ((merge and self.p2()) or
1089 return ((merge and self.p2()) or
1090 (branch and self.branch() != self.p1().branch()) or
1090 (branch and self.branch() != self.p1().branch()) or
1091 self.modified() or self.added() or self.removed() or
1091 self.modified() or self.added() or self.removed() or
1092 (missing and self.deleted()))
1092 (missing and self.deleted()))
1093
1093
1094 def add(self, list, prefix=""):
1094 def add(self, list, prefix=""):
1095 join = lambda f: os.path.join(prefix, f)
1095 join = lambda f: os.path.join(prefix, f)
1096 wlock = self._repo.wlock()
1096 wlock = self._repo.wlock()
1097 ui, ds = self._repo.ui, self._repo.dirstate
1097 ui, ds = self._repo.ui, self._repo.dirstate
1098 try:
1098 try:
1099 rejected = []
1099 rejected = []
1100 lstat = self._repo.wvfs.lstat
1100 lstat = self._repo.wvfs.lstat
1101 for f in list:
1101 for f in list:
1102 scmutil.checkportable(ui, join(f))
1102 scmutil.checkportable(ui, join(f))
1103 try:
1103 try:
1104 st = lstat(f)
1104 st = lstat(f)
1105 except OSError:
1105 except OSError:
1106 ui.warn(_("%s does not exist!\n") % join(f))
1106 ui.warn(_("%s does not exist!\n") % join(f))
1107 rejected.append(f)
1107 rejected.append(f)
1108 continue
1108 continue
1109 if st.st_size > 10000000:
1109 if st.st_size > 10000000:
1110 ui.warn(_("%s: up to %d MB of RAM may be required "
1110 ui.warn(_("%s: up to %d MB of RAM may be required "
1111 "to manage this file\n"
1111 "to manage this file\n"
1112 "(use 'hg revert %s' to cancel the "
1112 "(use 'hg revert %s' to cancel the "
1113 "pending addition)\n")
1113 "pending addition)\n")
1114 % (f, 3 * st.st_size // 1000000, join(f)))
1114 % (f, 3 * st.st_size // 1000000, join(f)))
1115 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
1115 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
1116 ui.warn(_("%s not added: only files and symlinks "
1116 ui.warn(_("%s not added: only files and symlinks "
1117 "supported currently\n") % join(f))
1117 "supported currently\n") % join(f))
1118 rejected.append(f)
1118 rejected.append(f)
1119 elif ds[f] in 'amn':
1119 elif ds[f] in 'amn':
1120 ui.warn(_("%s already tracked!\n") % join(f))
1120 ui.warn(_("%s already tracked!\n") % join(f))
1121 elif ds[f] == 'r':
1121 elif ds[f] == 'r':
1122 ds.normallookup(f)
1122 ds.normallookup(f)
1123 else:
1123 else:
1124 ds.add(f)
1124 ds.add(f)
1125 return rejected
1125 return rejected
1126 finally:
1126 finally:
1127 wlock.release()
1127 wlock.release()
1128
1128
1129 def forget(self, files, prefix=""):
1129 def forget(self, files, prefix=""):
1130 join = lambda f: os.path.join(prefix, f)
1130 join = lambda f: os.path.join(prefix, f)
1131 wlock = self._repo.wlock()
1131 wlock = self._repo.wlock()
1132 try:
1132 try:
1133 rejected = []
1133 rejected = []
1134 for f in files:
1134 for f in files:
1135 if f not in self._repo.dirstate:
1135 if f not in self._repo.dirstate:
1136 self._repo.ui.warn(_("%s not tracked!\n") % join(f))
1136 self._repo.ui.warn(_("%s not tracked!\n") % join(f))
1137 rejected.append(f)
1137 rejected.append(f)
1138 elif self._repo.dirstate[f] != 'a':
1138 elif self._repo.dirstate[f] != 'a':
1139 self._repo.dirstate.remove(f)
1139 self._repo.dirstate.remove(f)
1140 else:
1140 else:
1141 self._repo.dirstate.drop(f)
1141 self._repo.dirstate.drop(f)
1142 return rejected
1142 return rejected
1143 finally:
1143 finally:
1144 wlock.release()
1144 wlock.release()
1145
1145
1146 def undelete(self, list):
1146 def undelete(self, list):
1147 pctxs = self.parents()
1147 pctxs = self.parents()
1148 wlock = self._repo.wlock()
1148 wlock = self._repo.wlock()
1149 try:
1149 try:
1150 for f in list:
1150 for f in list:
1151 if self._repo.dirstate[f] != 'r':
1151 if self._repo.dirstate[f] != 'r':
1152 self._repo.ui.warn(_("%s not removed!\n") % f)
1152 self._repo.ui.warn(_("%s not removed!\n") % f)
1153 else:
1153 else:
1154 fctx = f in pctxs[0] and pctxs[0][f] or pctxs[1][f]
1154 fctx = f in pctxs[0] and pctxs[0][f] or pctxs[1][f]
1155 t = fctx.data()
1155 t = fctx.data()
1156 self._repo.wwrite(f, t, fctx.flags())
1156 self._repo.wwrite(f, t, fctx.flags())
1157 self._repo.dirstate.normal(f)
1157 self._repo.dirstate.normal(f)
1158 finally:
1158 finally:
1159 wlock.release()
1159 wlock.release()
1160
1160
1161 def copy(self, source, dest):
1161 def copy(self, source, dest):
1162 try:
1162 try:
1163 st = self._repo.wvfs.lstat(dest)
1163 st = self._repo.wvfs.lstat(dest)
1164 except OSError, err:
1164 except OSError, err:
1165 if err.errno != errno.ENOENT:
1165 if err.errno != errno.ENOENT:
1166 raise
1166 raise
1167 self._repo.ui.warn(_("%s does not exist!\n") % dest)
1167 self._repo.ui.warn(_("%s does not exist!\n") % dest)
1168 return
1168 return
1169 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
1169 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
1170 self._repo.ui.warn(_("copy failed: %s is not a file or a "
1170 self._repo.ui.warn(_("copy failed: %s is not a file or a "
1171 "symbolic link\n") % dest)
1171 "symbolic link\n") % dest)
1172 else:
1172 else:
1173 wlock = self._repo.wlock()
1173 wlock = self._repo.wlock()
1174 try:
1174 try:
1175 if self._repo.dirstate[dest] in '?r':
1175 if self._repo.dirstate[dest] in '?r':
1176 self._repo.dirstate.add(dest)
1176 self._repo.dirstate.add(dest)
1177 self._repo.dirstate.copy(source, dest)
1177 self._repo.dirstate.copy(source, dest)
1178 finally:
1178 finally:
1179 wlock.release()
1179 wlock.release()
1180
1180
1181 def _filtersuspectsymlink(self, files):
1181 def _filtersuspectsymlink(self, files):
1182 if not files or self._repo.dirstate._checklink:
1182 if not files or self._repo.dirstate._checklink:
1183 return files
1183 return files
1184
1184
1185 # Symlink placeholders may get non-symlink-like contents
1185 # Symlink placeholders may get non-symlink-like contents
1186 # via user error or dereferencing by NFS or Samba servers,
1186 # via user error or dereferencing by NFS or Samba servers,
1187 # so we filter out any placeholders that don't look like a
1187 # so we filter out any placeholders that don't look like a
1188 # symlink
1188 # symlink
1189 sane = []
1189 sane = []
1190 for f in files:
1190 for f in files:
1191 if self.flags(f) == 'l':
1191 if self.flags(f) == 'l':
1192 d = self[f].data()
1192 d = self[f].data()
1193 if d == '' or len(d) >= 1024 or '\n' in d or util.binary(d):
1193 if d == '' or len(d) >= 1024 or '\n' in d or util.binary(d):
1194 self._repo.ui.debug('ignoring suspect symlink placeholder'
1194 self._repo.ui.debug('ignoring suspect symlink placeholder'
1195 ' "%s"\n' % f)
1195 ' "%s"\n' % f)
1196 continue
1196 continue
1197 sane.append(f)
1197 sane.append(f)
1198 return sane
1198 return sane
1199
1199
1200 def _checklookup(self, files):
1201 # check for any possibly clean files
1202 if not files:
1203 return [], []
1204
1205 modified = []
1206 fixup = []
1207 pctx = self._parents[0]
1208 # do a full compare of any files that might have changed
1209 for f in sorted(files):
1210 if (f not in pctx or self.flags(f) != pctx.flags(f)
1211 or pctx[f].cmp(self[f])):
1212 modified.append(f)
1213 else:
1214 fixup.append(f)
1215
1216 # update dirstate for files that are actually clean
1217 if fixup:
1218 try:
1219 # updating the dirstate is optional
1220 # so we don't wait on the lock
1221 normal = self._repo.dirstate.normal
1222 wlock = self._repo.wlock(False)
1223 try:
1224 for f in fixup:
1225 normal(f)
1226 finally:
1227 wlock.release()
1228 except error.LockError:
1229 pass
1230 return modified, fixup
1231
1232
1200 class committablefilectx(basefilectx):
1233 class committablefilectx(basefilectx):
1201 """A committablefilectx provides common functionality for a file context
1234 """A committablefilectx provides common functionality for a file context
1202 that wants the ability to commit, e.g. workingfilectx or memfilectx."""
1235 that wants the ability to commit, e.g. workingfilectx or memfilectx."""
1203 def __init__(self, repo, path, filelog=None, ctx=None):
1236 def __init__(self, repo, path, filelog=None, ctx=None):
1204 self._repo = repo
1237 self._repo = repo
1205 self._path = path
1238 self._path = path
1206 self._changeid = None
1239 self._changeid = None
1207 self._filerev = self._filenode = None
1240 self._filerev = self._filenode = None
1208
1241
1209 if filelog is not None:
1242 if filelog is not None:
1210 self._filelog = filelog
1243 self._filelog = filelog
1211 if ctx:
1244 if ctx:
1212 self._changectx = ctx
1245 self._changectx = ctx
1213
1246
1214 def __nonzero__(self):
1247 def __nonzero__(self):
1215 return True
1248 return True
1216
1249
1217 def parents(self):
1250 def parents(self):
1218 '''return parent filectxs, following copies if necessary'''
1251 '''return parent filectxs, following copies if necessary'''
1219 def filenode(ctx, path):
1252 def filenode(ctx, path):
1220 return ctx._manifest.get(path, nullid)
1253 return ctx._manifest.get(path, nullid)
1221
1254
1222 path = self._path
1255 path = self._path
1223 fl = self._filelog
1256 fl = self._filelog
1224 pcl = self._changectx._parents
1257 pcl = self._changectx._parents
1225 renamed = self.renamed()
1258 renamed = self.renamed()
1226
1259
1227 if renamed:
1260 if renamed:
1228 pl = [renamed + (None,)]
1261 pl = [renamed + (None,)]
1229 else:
1262 else:
1230 pl = [(path, filenode(pcl[0], path), fl)]
1263 pl = [(path, filenode(pcl[0], path), fl)]
1231
1264
1232 for pc in pcl[1:]:
1265 for pc in pcl[1:]:
1233 pl.append((path, filenode(pc, path), fl))
1266 pl.append((path, filenode(pc, path), fl))
1234
1267
1235 return [filectx(self._repo, p, fileid=n, filelog=l)
1268 return [filectx(self._repo, p, fileid=n, filelog=l)
1236 for p, n, l in pl if n != nullid]
1269 for p, n, l in pl if n != nullid]
1237
1270
1238 def children(self):
1271 def children(self):
1239 return []
1272 return []
1240
1273
1241 class workingfilectx(committablefilectx):
1274 class workingfilectx(committablefilectx):
1242 """A workingfilectx object makes access to data related to a particular
1275 """A workingfilectx object makes access to data related to a particular
1243 file in the working directory convenient."""
1276 file in the working directory convenient."""
1244 def __init__(self, repo, path, filelog=None, workingctx=None):
1277 def __init__(self, repo, path, filelog=None, workingctx=None):
1245 super(workingfilectx, self).__init__(repo, path, filelog, workingctx)
1278 super(workingfilectx, self).__init__(repo, path, filelog, workingctx)
1246
1279
1247 @propertycache
1280 @propertycache
1248 def _changectx(self):
1281 def _changectx(self):
1249 return workingctx(self._repo)
1282 return workingctx(self._repo)
1250
1283
1251 def data(self):
1284 def data(self):
1252 return self._repo.wread(self._path)
1285 return self._repo.wread(self._path)
1253 def renamed(self):
1286 def renamed(self):
1254 rp = self._repo.dirstate.copied(self._path)
1287 rp = self._repo.dirstate.copied(self._path)
1255 if not rp:
1288 if not rp:
1256 return None
1289 return None
1257 return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
1290 return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
1258
1291
1259 def size(self):
1292 def size(self):
1260 return self._repo.wvfs.lstat(self._path).st_size
1293 return self._repo.wvfs.lstat(self._path).st_size
1261 def date(self):
1294 def date(self):
1262 t, tz = self._changectx.date()
1295 t, tz = self._changectx.date()
1263 try:
1296 try:
1264 return (int(self._repo.wvfs.lstat(self._path).st_mtime), tz)
1297 return (int(self._repo.wvfs.lstat(self._path).st_mtime), tz)
1265 except OSError, err:
1298 except OSError, err:
1266 if err.errno != errno.ENOENT:
1299 if err.errno != errno.ENOENT:
1267 raise
1300 raise
1268 return (t, tz)
1301 return (t, tz)
1269
1302
1270 def cmp(self, fctx):
1303 def cmp(self, fctx):
1271 """compare with other file context
1304 """compare with other file context
1272
1305
1273 returns True if different than fctx.
1306 returns True if different than fctx.
1274 """
1307 """
1275 # fctx should be a filectx (not a workingfilectx)
1308 # fctx should be a filectx (not a workingfilectx)
1276 # invert comparison to reuse the same code path
1309 # invert comparison to reuse the same code path
1277 return fctx.cmp(self)
1310 return fctx.cmp(self)
1278
1311
1279 class memctx(object):
1312 class memctx(object):
1280 """Use memctx to perform in-memory commits via localrepo.commitctx().
1313 """Use memctx to perform in-memory commits via localrepo.commitctx().
1281
1314
1282 Revision information is supplied at initialization time while
1315 Revision information is supplied at initialization time while
1283 related files data and is made available through a callback
1316 related files data and is made available through a callback
1284 mechanism. 'repo' is the current localrepo, 'parents' is a
1317 mechanism. 'repo' is the current localrepo, 'parents' is a
1285 sequence of two parent revisions identifiers (pass None for every
1318 sequence of two parent revisions identifiers (pass None for every
1286 missing parent), 'text' is the commit message and 'files' lists
1319 missing parent), 'text' is the commit message and 'files' lists
1287 names of files touched by the revision (normalized and relative to
1320 names of files touched by the revision (normalized and relative to
1288 repository root).
1321 repository root).
1289
1322
1290 filectxfn(repo, memctx, path) is a callable receiving the
1323 filectxfn(repo, memctx, path) is a callable receiving the
1291 repository, the current memctx object and the normalized path of
1324 repository, the current memctx object and the normalized path of
1292 requested file, relative to repository root. It is fired by the
1325 requested file, relative to repository root. It is fired by the
1293 commit function for every file in 'files', but calls order is
1326 commit function for every file in 'files', but calls order is
1294 undefined. If the file is available in the revision being
1327 undefined. If the file is available in the revision being
1295 committed (updated or added), filectxfn returns a memfilectx
1328 committed (updated or added), filectxfn returns a memfilectx
1296 object. If the file was removed, filectxfn raises an
1329 object. If the file was removed, filectxfn raises an
1297 IOError. Moved files are represented by marking the source file
1330 IOError. Moved files are represented by marking the source file
1298 removed and the new file added with copy information (see
1331 removed and the new file added with copy information (see
1299 memfilectx).
1332 memfilectx).
1300
1333
1301 user receives the committer name and defaults to current
1334 user receives the committer name and defaults to current
1302 repository username, date is the commit date in any format
1335 repository username, date is the commit date in any format
1303 supported by util.parsedate() and defaults to current date, extra
1336 supported by util.parsedate() and defaults to current date, extra
1304 is a dictionary of metadata or is left empty.
1337 is a dictionary of metadata or is left empty.
1305 """
1338 """
1306 def __init__(self, repo, parents, text, files, filectxfn, user=None,
1339 def __init__(self, repo, parents, text, files, filectxfn, user=None,
1307 date=None, extra=None, editor=False):
1340 date=None, extra=None, editor=False):
1308 self._repo = repo
1341 self._repo = repo
1309 self._rev = None
1342 self._rev = None
1310 self._node = None
1343 self._node = None
1311 self._text = text
1344 self._text = text
1312 self._date = date and util.parsedate(date) or util.makedate()
1345 self._date = date and util.parsedate(date) or util.makedate()
1313 self._user = user
1346 self._user = user
1314 parents = [(p or nullid) for p in parents]
1347 parents = [(p or nullid) for p in parents]
1315 p1, p2 = parents
1348 p1, p2 = parents
1316 self._parents = [changectx(self._repo, p) for p in (p1, p2)]
1349 self._parents = [changectx(self._repo, p) for p in (p1, p2)]
1317 files = sorted(set(files))
1350 files = sorted(set(files))
1318 self._status = [files, [], [], [], []]
1351 self._status = [files, [], [], [], []]
1319 self._filectxfn = filectxfn
1352 self._filectxfn = filectxfn
1320
1353
1321 self._extra = extra and extra.copy() or {}
1354 self._extra = extra and extra.copy() or {}
1322 if self._extra.get('branch', '') == '':
1355 if self._extra.get('branch', '') == '':
1323 self._extra['branch'] = 'default'
1356 self._extra['branch'] = 'default'
1324
1357
1325 if editor:
1358 if editor:
1326 self._text = editor(self._repo, self, [])
1359 self._text = editor(self._repo, self, [])
1327 self._repo.savecommitmessage(self._text)
1360 self._repo.savecommitmessage(self._text)
1328
1361
1329 def __str__(self):
1362 def __str__(self):
1330 return str(self._parents[0]) + "+"
1363 return str(self._parents[0]) + "+"
1331
1364
1332 def __int__(self):
1365 def __int__(self):
1333 return self._rev
1366 return self._rev
1334
1367
1335 def __nonzero__(self):
1368 def __nonzero__(self):
1336 return True
1369 return True
1337
1370
1338 def __getitem__(self, key):
1371 def __getitem__(self, key):
1339 return self.filectx(key)
1372 return self.filectx(key)
1340
1373
1341 def p1(self):
1374 def p1(self):
1342 return self._parents[0]
1375 return self._parents[0]
1343 def p2(self):
1376 def p2(self):
1344 return self._parents[1]
1377 return self._parents[1]
1345
1378
1346 def user(self):
1379 def user(self):
1347 return self._user or self._repo.ui.username()
1380 return self._user or self._repo.ui.username()
1348 def date(self):
1381 def date(self):
1349 return self._date
1382 return self._date
1350 def description(self):
1383 def description(self):
1351 return self._text
1384 return self._text
1352 def files(self):
1385 def files(self):
1353 return self.modified()
1386 return self.modified()
1354 def modified(self):
1387 def modified(self):
1355 return self._status[0]
1388 return self._status[0]
1356 def added(self):
1389 def added(self):
1357 return self._status[1]
1390 return self._status[1]
1358 def removed(self):
1391 def removed(self):
1359 return self._status[2]
1392 return self._status[2]
1360 def deleted(self):
1393 def deleted(self):
1361 return self._status[3]
1394 return self._status[3]
1362 def unknown(self):
1395 def unknown(self):
1363 return self._status[4]
1396 return self._status[4]
1364 def ignored(self):
1397 def ignored(self):
1365 return self._status[5]
1398 return self._status[5]
1366 def clean(self):
1399 def clean(self):
1367 return self._status[6]
1400 return self._status[6]
1368 def branch(self):
1401 def branch(self):
1369 return encoding.tolocal(self._extra['branch'])
1402 return encoding.tolocal(self._extra['branch'])
1370 def extra(self):
1403 def extra(self):
1371 return self._extra
1404 return self._extra
1372 def flags(self, f):
1405 def flags(self, f):
1373 return self[f].flags()
1406 return self[f].flags()
1374
1407
1375 def parents(self):
1408 def parents(self):
1376 """return contexts for each parent changeset"""
1409 """return contexts for each parent changeset"""
1377 return self._parents
1410 return self._parents
1378
1411
1379 def filectx(self, path, filelog=None):
1412 def filectx(self, path, filelog=None):
1380 """get a file context from the working directory"""
1413 """get a file context from the working directory"""
1381 return self._filectxfn(self._repo, self, path)
1414 return self._filectxfn(self._repo, self, path)
1382
1415
1383 def commit(self):
1416 def commit(self):
1384 """commit context to the repo"""
1417 """commit context to the repo"""
1385 return self._repo.commitctx(self)
1418 return self._repo.commitctx(self)
1386
1419
1387 class memfilectx(object):
1420 class memfilectx(object):
1388 """memfilectx represents an in-memory file to commit.
1421 """memfilectx represents an in-memory file to commit.
1389
1422
1390 See memctx for more details.
1423 See memctx for more details.
1391 """
1424 """
1392 def __init__(self, path, data, islink=False, isexec=False, copied=None):
1425 def __init__(self, path, data, islink=False, isexec=False, copied=None):
1393 """
1426 """
1394 path is the normalized file path relative to repository root.
1427 path is the normalized file path relative to repository root.
1395 data is the file content as a string.
1428 data is the file content as a string.
1396 islink is True if the file is a symbolic link.
1429 islink is True if the file is a symbolic link.
1397 isexec is True if the file is executable.
1430 isexec is True if the file is executable.
1398 copied is the source file path if current file was copied in the
1431 copied is the source file path if current file was copied in the
1399 revision being committed, or None."""
1432 revision being committed, or None."""
1400 self._path = path
1433 self._path = path
1401 self._data = data
1434 self._data = data
1402 self._flags = (islink and 'l' or '') + (isexec and 'x' or '')
1435 self._flags = (islink and 'l' or '') + (isexec and 'x' or '')
1403 self._copied = None
1436 self._copied = None
1404 if copied:
1437 if copied:
1405 self._copied = (copied, nullid)
1438 self._copied = (copied, nullid)
1406
1439
1407 def __nonzero__(self):
1440 def __nonzero__(self):
1408 return True
1441 return True
1409 def __str__(self):
1442 def __str__(self):
1410 return "%s@%s" % (self.path(), self._changectx)
1443 return "%s@%s" % (self.path(), self._changectx)
1411 def path(self):
1444 def path(self):
1412 return self._path
1445 return self._path
1413 def data(self):
1446 def data(self):
1414 return self._data
1447 return self._data
1415 def flags(self):
1448 def flags(self):
1416 return self._flags
1449 return self._flags
1417 def isexec(self):
1450 def isexec(self):
1418 return 'x' in self._flags
1451 return 'x' in self._flags
1419 def islink(self):
1452 def islink(self):
1420 return 'l' in self._flags
1453 return 'l' in self._flags
1421 def renamed(self):
1454 def renamed(self):
1422 return self._copied
1455 return self._copied
@@ -1,1901 +1,1881
1 # localrepo.py - read/write repository class for mercurial
1 # localrepo.py - read/write repository class for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-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 from node import hex, nullid, short
7 from node import hex, nullid, short
8 from i18n import _
8 from i18n import _
9 import urllib
9 import urllib
10 import peer, changegroup, subrepo, pushkey, obsolete, repoview
10 import peer, changegroup, subrepo, pushkey, obsolete, repoview
11 import changelog, dirstate, filelog, manifest, context, bookmarks, phases
11 import changelog, dirstate, filelog, manifest, context, bookmarks, phases
12 import lock as lockmod
12 import lock as lockmod
13 import transaction, store, encoding, exchange, bundle2
13 import transaction, store, encoding, exchange, bundle2
14 import scmutil, util, extensions, hook, error, revset
14 import scmutil, util, extensions, hook, error, revset
15 import match as matchmod
15 import match as matchmod
16 import merge as mergemod
16 import merge as mergemod
17 import tags as tagsmod
17 import tags as tagsmod
18 from lock import release
18 from lock import release
19 import weakref, errno, os, time, inspect
19 import weakref, errno, os, time, inspect
20 import branchmap, pathutil
20 import branchmap, pathutil
21 propertycache = util.propertycache
21 propertycache = util.propertycache
22 filecache = scmutil.filecache
22 filecache = scmutil.filecache
23
23
24 class repofilecache(filecache):
24 class repofilecache(filecache):
25 """All filecache usage on repo are done for logic that should be unfiltered
25 """All filecache usage on repo are done for logic that should be unfiltered
26 """
26 """
27
27
28 def __get__(self, repo, type=None):
28 def __get__(self, repo, type=None):
29 return super(repofilecache, self).__get__(repo.unfiltered(), type)
29 return super(repofilecache, self).__get__(repo.unfiltered(), type)
30 def __set__(self, repo, value):
30 def __set__(self, repo, value):
31 return super(repofilecache, self).__set__(repo.unfiltered(), value)
31 return super(repofilecache, self).__set__(repo.unfiltered(), value)
32 def __delete__(self, repo):
32 def __delete__(self, repo):
33 return super(repofilecache, self).__delete__(repo.unfiltered())
33 return super(repofilecache, self).__delete__(repo.unfiltered())
34
34
35 class storecache(repofilecache):
35 class storecache(repofilecache):
36 """filecache for files in the store"""
36 """filecache for files in the store"""
37 def join(self, obj, fname):
37 def join(self, obj, fname):
38 return obj.sjoin(fname)
38 return obj.sjoin(fname)
39
39
40 class unfilteredpropertycache(propertycache):
40 class unfilteredpropertycache(propertycache):
41 """propertycache that apply to unfiltered repo only"""
41 """propertycache that apply to unfiltered repo only"""
42
42
43 def __get__(self, repo, type=None):
43 def __get__(self, repo, type=None):
44 unfi = repo.unfiltered()
44 unfi = repo.unfiltered()
45 if unfi is repo:
45 if unfi is repo:
46 return super(unfilteredpropertycache, self).__get__(unfi)
46 return super(unfilteredpropertycache, self).__get__(unfi)
47 return getattr(unfi, self.name)
47 return getattr(unfi, self.name)
48
48
49 class filteredpropertycache(propertycache):
49 class filteredpropertycache(propertycache):
50 """propertycache that must take filtering in account"""
50 """propertycache that must take filtering in account"""
51
51
52 def cachevalue(self, obj, value):
52 def cachevalue(self, obj, value):
53 object.__setattr__(obj, self.name, value)
53 object.__setattr__(obj, self.name, value)
54
54
55
55
56 def hasunfilteredcache(repo, name):
56 def hasunfilteredcache(repo, name):
57 """check if a repo has an unfilteredpropertycache value for <name>"""
57 """check if a repo has an unfilteredpropertycache value for <name>"""
58 return name in vars(repo.unfiltered())
58 return name in vars(repo.unfiltered())
59
59
60 def unfilteredmethod(orig):
60 def unfilteredmethod(orig):
61 """decorate method that always need to be run on unfiltered version"""
61 """decorate method that always need to be run on unfiltered version"""
62 def wrapper(repo, *args, **kwargs):
62 def wrapper(repo, *args, **kwargs):
63 return orig(repo.unfiltered(), *args, **kwargs)
63 return orig(repo.unfiltered(), *args, **kwargs)
64 return wrapper
64 return wrapper
65
65
66 moderncaps = set(('lookup', 'branchmap', 'pushkey', 'known', 'getbundle',
66 moderncaps = set(('lookup', 'branchmap', 'pushkey', 'known', 'getbundle',
67 'unbundle'))
67 'unbundle'))
68 legacycaps = moderncaps.union(set(['changegroupsubset']))
68 legacycaps = moderncaps.union(set(['changegroupsubset']))
69
69
70 class localpeer(peer.peerrepository):
70 class localpeer(peer.peerrepository):
71 '''peer for a local repo; reflects only the most recent API'''
71 '''peer for a local repo; reflects only the most recent API'''
72
72
73 def __init__(self, repo, caps=moderncaps):
73 def __init__(self, repo, caps=moderncaps):
74 peer.peerrepository.__init__(self)
74 peer.peerrepository.__init__(self)
75 self._repo = repo.filtered('served')
75 self._repo = repo.filtered('served')
76 self.ui = repo.ui
76 self.ui = repo.ui
77 self._caps = repo._restrictcapabilities(caps)
77 self._caps = repo._restrictcapabilities(caps)
78 self.requirements = repo.requirements
78 self.requirements = repo.requirements
79 self.supportedformats = repo.supportedformats
79 self.supportedformats = repo.supportedformats
80
80
81 def close(self):
81 def close(self):
82 self._repo.close()
82 self._repo.close()
83
83
84 def _capabilities(self):
84 def _capabilities(self):
85 return self._caps
85 return self._caps
86
86
87 def local(self):
87 def local(self):
88 return self._repo
88 return self._repo
89
89
90 def canpush(self):
90 def canpush(self):
91 return True
91 return True
92
92
93 def url(self):
93 def url(self):
94 return self._repo.url()
94 return self._repo.url()
95
95
96 def lookup(self, key):
96 def lookup(self, key):
97 return self._repo.lookup(key)
97 return self._repo.lookup(key)
98
98
99 def branchmap(self):
99 def branchmap(self):
100 return self._repo.branchmap()
100 return self._repo.branchmap()
101
101
102 def heads(self):
102 def heads(self):
103 return self._repo.heads()
103 return self._repo.heads()
104
104
105 def known(self, nodes):
105 def known(self, nodes):
106 return self._repo.known(nodes)
106 return self._repo.known(nodes)
107
107
108 def getbundle(self, source, heads=None, common=None, bundlecaps=None,
108 def getbundle(self, source, heads=None, common=None, bundlecaps=None,
109 format='HG10', **kwargs):
109 format='HG10', **kwargs):
110 cg = exchange.getbundle(self._repo, source, heads=heads,
110 cg = exchange.getbundle(self._repo, source, heads=heads,
111 common=common, bundlecaps=bundlecaps, **kwargs)
111 common=common, bundlecaps=bundlecaps, **kwargs)
112 if bundlecaps is not None and 'HG2X' in bundlecaps:
112 if bundlecaps is not None and 'HG2X' in bundlecaps:
113 # When requesting a bundle2, getbundle returns a stream to make the
113 # When requesting a bundle2, getbundle returns a stream to make the
114 # wire level function happier. We need to build a proper object
114 # wire level function happier. We need to build a proper object
115 # from it in local peer.
115 # from it in local peer.
116 cg = bundle2.unbundle20(self.ui, cg)
116 cg = bundle2.unbundle20(self.ui, cg)
117 return cg
117 return cg
118
118
119 # TODO We might want to move the next two calls into legacypeer and add
119 # TODO We might want to move the next two calls into legacypeer and add
120 # unbundle instead.
120 # unbundle instead.
121
121
122 def unbundle(self, cg, heads, url):
122 def unbundle(self, cg, heads, url):
123 """apply a bundle on a repo
123 """apply a bundle on a repo
124
124
125 This function handles the repo locking itself."""
125 This function handles the repo locking itself."""
126 try:
126 try:
127 cg = exchange.readbundle(self.ui, cg, None)
127 cg = exchange.readbundle(self.ui, cg, None)
128 ret = exchange.unbundle(self._repo, cg, heads, 'push', url)
128 ret = exchange.unbundle(self._repo, cg, heads, 'push', url)
129 if util.safehasattr(ret, 'getchunks'):
129 if util.safehasattr(ret, 'getchunks'):
130 # This is a bundle20 object, turn it into an unbundler.
130 # This is a bundle20 object, turn it into an unbundler.
131 # This little dance should be dropped eventually when the API
131 # This little dance should be dropped eventually when the API
132 # is finally improved.
132 # is finally improved.
133 stream = util.chunkbuffer(ret.getchunks())
133 stream = util.chunkbuffer(ret.getchunks())
134 ret = bundle2.unbundle20(self.ui, stream)
134 ret = bundle2.unbundle20(self.ui, stream)
135 return ret
135 return ret
136 except error.PushRaced, exc:
136 except error.PushRaced, exc:
137 raise error.ResponseError(_('push failed:'), str(exc))
137 raise error.ResponseError(_('push failed:'), str(exc))
138
138
139 def lock(self):
139 def lock(self):
140 return self._repo.lock()
140 return self._repo.lock()
141
141
142 def addchangegroup(self, cg, source, url):
142 def addchangegroup(self, cg, source, url):
143 return changegroup.addchangegroup(self._repo, cg, source, url)
143 return changegroup.addchangegroup(self._repo, cg, source, url)
144
144
145 def pushkey(self, namespace, key, old, new):
145 def pushkey(self, namespace, key, old, new):
146 return self._repo.pushkey(namespace, key, old, new)
146 return self._repo.pushkey(namespace, key, old, new)
147
147
148 def listkeys(self, namespace):
148 def listkeys(self, namespace):
149 return self._repo.listkeys(namespace)
149 return self._repo.listkeys(namespace)
150
150
151 def debugwireargs(self, one, two, three=None, four=None, five=None):
151 def debugwireargs(self, one, two, three=None, four=None, five=None):
152 '''used to test argument passing over the wire'''
152 '''used to test argument passing over the wire'''
153 return "%s %s %s %s %s" % (one, two, three, four, five)
153 return "%s %s %s %s %s" % (one, two, three, four, five)
154
154
155 class locallegacypeer(localpeer):
155 class locallegacypeer(localpeer):
156 '''peer extension which implements legacy methods too; used for tests with
156 '''peer extension which implements legacy methods too; used for tests with
157 restricted capabilities'''
157 restricted capabilities'''
158
158
159 def __init__(self, repo):
159 def __init__(self, repo):
160 localpeer.__init__(self, repo, caps=legacycaps)
160 localpeer.__init__(self, repo, caps=legacycaps)
161
161
162 def branches(self, nodes):
162 def branches(self, nodes):
163 return self._repo.branches(nodes)
163 return self._repo.branches(nodes)
164
164
165 def between(self, pairs):
165 def between(self, pairs):
166 return self._repo.between(pairs)
166 return self._repo.between(pairs)
167
167
168 def changegroup(self, basenodes, source):
168 def changegroup(self, basenodes, source):
169 return changegroup.changegroup(self._repo, basenodes, source)
169 return changegroup.changegroup(self._repo, basenodes, source)
170
170
171 def changegroupsubset(self, bases, heads, source):
171 def changegroupsubset(self, bases, heads, source):
172 return changegroup.changegroupsubset(self._repo, bases, heads, source)
172 return changegroup.changegroupsubset(self._repo, bases, heads, source)
173
173
174 class localrepository(object):
174 class localrepository(object):
175
175
176 supportedformats = set(('revlogv1', 'generaldelta'))
176 supportedformats = set(('revlogv1', 'generaldelta'))
177 _basesupported = supportedformats | set(('store', 'fncache', 'shared',
177 _basesupported = supportedformats | set(('store', 'fncache', 'shared',
178 'dotencode'))
178 'dotencode'))
179 openerreqs = set(('revlogv1', 'generaldelta'))
179 openerreqs = set(('revlogv1', 'generaldelta'))
180 requirements = ['revlogv1']
180 requirements = ['revlogv1']
181 filtername = None
181 filtername = None
182
182
183 bundle2caps = {'HG2X': ()}
183 bundle2caps = {'HG2X': ()}
184
184
185 # a list of (ui, featureset) functions.
185 # a list of (ui, featureset) functions.
186 # only functions defined in module of enabled extensions are invoked
186 # only functions defined in module of enabled extensions are invoked
187 featuresetupfuncs = set()
187 featuresetupfuncs = set()
188
188
189 def _baserequirements(self, create):
189 def _baserequirements(self, create):
190 return self.requirements[:]
190 return self.requirements[:]
191
191
192 def __init__(self, baseui, path=None, create=False):
192 def __init__(self, baseui, path=None, create=False):
193 self.wvfs = scmutil.vfs(path, expandpath=True, realpath=True)
193 self.wvfs = scmutil.vfs(path, expandpath=True, realpath=True)
194 self.wopener = self.wvfs
194 self.wopener = self.wvfs
195 self.root = self.wvfs.base
195 self.root = self.wvfs.base
196 self.path = self.wvfs.join(".hg")
196 self.path = self.wvfs.join(".hg")
197 self.origroot = path
197 self.origroot = path
198 self.auditor = pathutil.pathauditor(self.root, self._checknested)
198 self.auditor = pathutil.pathauditor(self.root, self._checknested)
199 self.vfs = scmutil.vfs(self.path)
199 self.vfs = scmutil.vfs(self.path)
200 self.opener = self.vfs
200 self.opener = self.vfs
201 self.baseui = baseui
201 self.baseui = baseui
202 self.ui = baseui.copy()
202 self.ui = baseui.copy()
203 self.ui.copy = baseui.copy # prevent copying repo configuration
203 self.ui.copy = baseui.copy # prevent copying repo configuration
204 # A list of callback to shape the phase if no data were found.
204 # A list of callback to shape the phase if no data were found.
205 # Callback are in the form: func(repo, roots) --> processed root.
205 # Callback are in the form: func(repo, roots) --> processed root.
206 # This list it to be filled by extension during repo setup
206 # This list it to be filled by extension during repo setup
207 self._phasedefaults = []
207 self._phasedefaults = []
208 try:
208 try:
209 self.ui.readconfig(self.join("hgrc"), self.root)
209 self.ui.readconfig(self.join("hgrc"), self.root)
210 extensions.loadall(self.ui)
210 extensions.loadall(self.ui)
211 except IOError:
211 except IOError:
212 pass
212 pass
213
213
214 if self.featuresetupfuncs:
214 if self.featuresetupfuncs:
215 self.supported = set(self._basesupported) # use private copy
215 self.supported = set(self._basesupported) # use private copy
216 extmods = set(m.__name__ for n, m
216 extmods = set(m.__name__ for n, m
217 in extensions.extensions(self.ui))
217 in extensions.extensions(self.ui))
218 for setupfunc in self.featuresetupfuncs:
218 for setupfunc in self.featuresetupfuncs:
219 if setupfunc.__module__ in extmods:
219 if setupfunc.__module__ in extmods:
220 setupfunc(self.ui, self.supported)
220 setupfunc(self.ui, self.supported)
221 else:
221 else:
222 self.supported = self._basesupported
222 self.supported = self._basesupported
223
223
224 if not self.vfs.isdir():
224 if not self.vfs.isdir():
225 if create:
225 if create:
226 if not self.wvfs.exists():
226 if not self.wvfs.exists():
227 self.wvfs.makedirs()
227 self.wvfs.makedirs()
228 self.vfs.makedir(notindexed=True)
228 self.vfs.makedir(notindexed=True)
229 requirements = self._baserequirements(create)
229 requirements = self._baserequirements(create)
230 if self.ui.configbool('format', 'usestore', True):
230 if self.ui.configbool('format', 'usestore', True):
231 self.vfs.mkdir("store")
231 self.vfs.mkdir("store")
232 requirements.append("store")
232 requirements.append("store")
233 if self.ui.configbool('format', 'usefncache', True):
233 if self.ui.configbool('format', 'usefncache', True):
234 requirements.append("fncache")
234 requirements.append("fncache")
235 if self.ui.configbool('format', 'dotencode', True):
235 if self.ui.configbool('format', 'dotencode', True):
236 requirements.append('dotencode')
236 requirements.append('dotencode')
237 # create an invalid changelog
237 # create an invalid changelog
238 self.vfs.append(
238 self.vfs.append(
239 "00changelog.i",
239 "00changelog.i",
240 '\0\0\0\2' # represents revlogv2
240 '\0\0\0\2' # represents revlogv2
241 ' dummy changelog to prevent using the old repo layout'
241 ' dummy changelog to prevent using the old repo layout'
242 )
242 )
243 if self.ui.configbool('format', 'generaldelta', False):
243 if self.ui.configbool('format', 'generaldelta', False):
244 requirements.append("generaldelta")
244 requirements.append("generaldelta")
245 requirements = set(requirements)
245 requirements = set(requirements)
246 else:
246 else:
247 raise error.RepoError(_("repository %s not found") % path)
247 raise error.RepoError(_("repository %s not found") % path)
248 elif create:
248 elif create:
249 raise error.RepoError(_("repository %s already exists") % path)
249 raise error.RepoError(_("repository %s already exists") % path)
250 else:
250 else:
251 try:
251 try:
252 requirements = scmutil.readrequires(self.vfs, self.supported)
252 requirements = scmutil.readrequires(self.vfs, self.supported)
253 except IOError, inst:
253 except IOError, inst:
254 if inst.errno != errno.ENOENT:
254 if inst.errno != errno.ENOENT:
255 raise
255 raise
256 requirements = set()
256 requirements = set()
257
257
258 self.sharedpath = self.path
258 self.sharedpath = self.path
259 try:
259 try:
260 vfs = scmutil.vfs(self.vfs.read("sharedpath").rstrip('\n'),
260 vfs = scmutil.vfs(self.vfs.read("sharedpath").rstrip('\n'),
261 realpath=True)
261 realpath=True)
262 s = vfs.base
262 s = vfs.base
263 if not vfs.exists():
263 if not vfs.exists():
264 raise error.RepoError(
264 raise error.RepoError(
265 _('.hg/sharedpath points to nonexistent directory %s') % s)
265 _('.hg/sharedpath points to nonexistent directory %s') % s)
266 self.sharedpath = s
266 self.sharedpath = s
267 except IOError, inst:
267 except IOError, inst:
268 if inst.errno != errno.ENOENT:
268 if inst.errno != errno.ENOENT:
269 raise
269 raise
270
270
271 self.store = store.store(requirements, self.sharedpath, scmutil.vfs)
271 self.store = store.store(requirements, self.sharedpath, scmutil.vfs)
272 self.spath = self.store.path
272 self.spath = self.store.path
273 self.svfs = self.store.vfs
273 self.svfs = self.store.vfs
274 self.sopener = self.svfs
274 self.sopener = self.svfs
275 self.sjoin = self.store.join
275 self.sjoin = self.store.join
276 self.vfs.createmode = self.store.createmode
276 self.vfs.createmode = self.store.createmode
277 self._applyrequirements(requirements)
277 self._applyrequirements(requirements)
278 if create:
278 if create:
279 self._writerequirements()
279 self._writerequirements()
280
280
281
281
282 self._branchcaches = {}
282 self._branchcaches = {}
283 self.filterpats = {}
283 self.filterpats = {}
284 self._datafilters = {}
284 self._datafilters = {}
285 self._transref = self._lockref = self._wlockref = None
285 self._transref = self._lockref = self._wlockref = None
286
286
287 # A cache for various files under .hg/ that tracks file changes,
287 # A cache for various files under .hg/ that tracks file changes,
288 # (used by the filecache decorator)
288 # (used by the filecache decorator)
289 #
289 #
290 # Maps a property name to its util.filecacheentry
290 # Maps a property name to its util.filecacheentry
291 self._filecache = {}
291 self._filecache = {}
292
292
293 # hold sets of revision to be filtered
293 # hold sets of revision to be filtered
294 # should be cleared when something might have changed the filter value:
294 # should be cleared when something might have changed the filter value:
295 # - new changesets,
295 # - new changesets,
296 # - phase change,
296 # - phase change,
297 # - new obsolescence marker,
297 # - new obsolescence marker,
298 # - working directory parent change,
298 # - working directory parent change,
299 # - bookmark changes
299 # - bookmark changes
300 self.filteredrevcache = {}
300 self.filteredrevcache = {}
301
301
302 def close(self):
302 def close(self):
303 pass
303 pass
304
304
305 def _restrictcapabilities(self, caps):
305 def _restrictcapabilities(self, caps):
306 # bundle2 is not ready for prime time, drop it unless explicitly
306 # bundle2 is not ready for prime time, drop it unless explicitly
307 # required by the tests (or some brave tester)
307 # required by the tests (or some brave tester)
308 if self.ui.configbool('experimental', 'bundle2-exp', False):
308 if self.ui.configbool('experimental', 'bundle2-exp', False):
309 caps = set(caps)
309 caps = set(caps)
310 capsblob = bundle2.encodecaps(self.bundle2caps)
310 capsblob = bundle2.encodecaps(self.bundle2caps)
311 caps.add('bundle2-exp=' + urllib.quote(capsblob))
311 caps.add('bundle2-exp=' + urllib.quote(capsblob))
312 return caps
312 return caps
313
313
314 def _applyrequirements(self, requirements):
314 def _applyrequirements(self, requirements):
315 self.requirements = requirements
315 self.requirements = requirements
316 self.sopener.options = dict((r, 1) for r in requirements
316 self.sopener.options = dict((r, 1) for r in requirements
317 if r in self.openerreqs)
317 if r in self.openerreqs)
318 chunkcachesize = self.ui.configint('format', 'chunkcachesize')
318 chunkcachesize = self.ui.configint('format', 'chunkcachesize')
319 if chunkcachesize is not None:
319 if chunkcachesize is not None:
320 self.sopener.options['chunkcachesize'] = chunkcachesize
320 self.sopener.options['chunkcachesize'] = chunkcachesize
321
321
322 def _writerequirements(self):
322 def _writerequirements(self):
323 reqfile = self.opener("requires", "w")
323 reqfile = self.opener("requires", "w")
324 for r in sorted(self.requirements):
324 for r in sorted(self.requirements):
325 reqfile.write("%s\n" % r)
325 reqfile.write("%s\n" % r)
326 reqfile.close()
326 reqfile.close()
327
327
328 def _checknested(self, path):
328 def _checknested(self, path):
329 """Determine if path is a legal nested repository."""
329 """Determine if path is a legal nested repository."""
330 if not path.startswith(self.root):
330 if not path.startswith(self.root):
331 return False
331 return False
332 subpath = path[len(self.root) + 1:]
332 subpath = path[len(self.root) + 1:]
333 normsubpath = util.pconvert(subpath)
333 normsubpath = util.pconvert(subpath)
334
334
335 # XXX: Checking against the current working copy is wrong in
335 # XXX: Checking against the current working copy is wrong in
336 # the sense that it can reject things like
336 # the sense that it can reject things like
337 #
337 #
338 # $ hg cat -r 10 sub/x.txt
338 # $ hg cat -r 10 sub/x.txt
339 #
339 #
340 # if sub/ is no longer a subrepository in the working copy
340 # if sub/ is no longer a subrepository in the working copy
341 # parent revision.
341 # parent revision.
342 #
342 #
343 # However, it can of course also allow things that would have
343 # However, it can of course also allow things that would have
344 # been rejected before, such as the above cat command if sub/
344 # been rejected before, such as the above cat command if sub/
345 # is a subrepository now, but was a normal directory before.
345 # is a subrepository now, but was a normal directory before.
346 # The old path auditor would have rejected by mistake since it
346 # The old path auditor would have rejected by mistake since it
347 # panics when it sees sub/.hg/.
347 # panics when it sees sub/.hg/.
348 #
348 #
349 # All in all, checking against the working copy seems sensible
349 # All in all, checking against the working copy seems sensible
350 # since we want to prevent access to nested repositories on
350 # since we want to prevent access to nested repositories on
351 # the filesystem *now*.
351 # the filesystem *now*.
352 ctx = self[None]
352 ctx = self[None]
353 parts = util.splitpath(subpath)
353 parts = util.splitpath(subpath)
354 while parts:
354 while parts:
355 prefix = '/'.join(parts)
355 prefix = '/'.join(parts)
356 if prefix in ctx.substate:
356 if prefix in ctx.substate:
357 if prefix == normsubpath:
357 if prefix == normsubpath:
358 return True
358 return True
359 else:
359 else:
360 sub = ctx.sub(prefix)
360 sub = ctx.sub(prefix)
361 return sub.checknested(subpath[len(prefix) + 1:])
361 return sub.checknested(subpath[len(prefix) + 1:])
362 else:
362 else:
363 parts.pop()
363 parts.pop()
364 return False
364 return False
365
365
366 def peer(self):
366 def peer(self):
367 return localpeer(self) # not cached to avoid reference cycle
367 return localpeer(self) # not cached to avoid reference cycle
368
368
369 def unfiltered(self):
369 def unfiltered(self):
370 """Return unfiltered version of the repository
370 """Return unfiltered version of the repository
371
371
372 Intended to be overwritten by filtered repo."""
372 Intended to be overwritten by filtered repo."""
373 return self
373 return self
374
374
375 def filtered(self, name):
375 def filtered(self, name):
376 """Return a filtered version of a repository"""
376 """Return a filtered version of a repository"""
377 # build a new class with the mixin and the current class
377 # build a new class with the mixin and the current class
378 # (possibly subclass of the repo)
378 # (possibly subclass of the repo)
379 class proxycls(repoview.repoview, self.unfiltered().__class__):
379 class proxycls(repoview.repoview, self.unfiltered().__class__):
380 pass
380 pass
381 return proxycls(self, name)
381 return proxycls(self, name)
382
382
383 @repofilecache('bookmarks')
383 @repofilecache('bookmarks')
384 def _bookmarks(self):
384 def _bookmarks(self):
385 return bookmarks.bmstore(self)
385 return bookmarks.bmstore(self)
386
386
387 @repofilecache('bookmarks.current')
387 @repofilecache('bookmarks.current')
388 def _bookmarkcurrent(self):
388 def _bookmarkcurrent(self):
389 return bookmarks.readcurrent(self)
389 return bookmarks.readcurrent(self)
390
390
391 def bookmarkheads(self, bookmark):
391 def bookmarkheads(self, bookmark):
392 name = bookmark.split('@', 1)[0]
392 name = bookmark.split('@', 1)[0]
393 heads = []
393 heads = []
394 for mark, n in self._bookmarks.iteritems():
394 for mark, n in self._bookmarks.iteritems():
395 if mark.split('@', 1)[0] == name:
395 if mark.split('@', 1)[0] == name:
396 heads.append(n)
396 heads.append(n)
397 return heads
397 return heads
398
398
399 @storecache('phaseroots')
399 @storecache('phaseroots')
400 def _phasecache(self):
400 def _phasecache(self):
401 return phases.phasecache(self, self._phasedefaults)
401 return phases.phasecache(self, self._phasedefaults)
402
402
403 @storecache('obsstore')
403 @storecache('obsstore')
404 def obsstore(self):
404 def obsstore(self):
405 store = obsolete.obsstore(self.sopener)
405 store = obsolete.obsstore(self.sopener)
406 if store and not obsolete._enabled:
406 if store and not obsolete._enabled:
407 # message is rare enough to not be translated
407 # message is rare enough to not be translated
408 msg = 'obsolete feature not enabled but %i markers found!\n'
408 msg = 'obsolete feature not enabled but %i markers found!\n'
409 self.ui.warn(msg % len(list(store)))
409 self.ui.warn(msg % len(list(store)))
410 return store
410 return store
411
411
412 @storecache('00changelog.i')
412 @storecache('00changelog.i')
413 def changelog(self):
413 def changelog(self):
414 c = changelog.changelog(self.sopener)
414 c = changelog.changelog(self.sopener)
415 if 'HG_PENDING' in os.environ:
415 if 'HG_PENDING' in os.environ:
416 p = os.environ['HG_PENDING']
416 p = os.environ['HG_PENDING']
417 if p.startswith(self.root):
417 if p.startswith(self.root):
418 c.readpending('00changelog.i.a')
418 c.readpending('00changelog.i.a')
419 return c
419 return c
420
420
421 @storecache('00manifest.i')
421 @storecache('00manifest.i')
422 def manifest(self):
422 def manifest(self):
423 return manifest.manifest(self.sopener)
423 return manifest.manifest(self.sopener)
424
424
425 @repofilecache('dirstate')
425 @repofilecache('dirstate')
426 def dirstate(self):
426 def dirstate(self):
427 warned = [0]
427 warned = [0]
428 def validate(node):
428 def validate(node):
429 try:
429 try:
430 self.changelog.rev(node)
430 self.changelog.rev(node)
431 return node
431 return node
432 except error.LookupError:
432 except error.LookupError:
433 if not warned[0]:
433 if not warned[0]:
434 warned[0] = True
434 warned[0] = True
435 self.ui.warn(_("warning: ignoring unknown"
435 self.ui.warn(_("warning: ignoring unknown"
436 " working parent %s!\n") % short(node))
436 " working parent %s!\n") % short(node))
437 return nullid
437 return nullid
438
438
439 return dirstate.dirstate(self.opener, self.ui, self.root, validate)
439 return dirstate.dirstate(self.opener, self.ui, self.root, validate)
440
440
441 def __getitem__(self, changeid):
441 def __getitem__(self, changeid):
442 if changeid is None:
442 if changeid is None:
443 return context.workingctx(self)
443 return context.workingctx(self)
444 return context.changectx(self, changeid)
444 return context.changectx(self, changeid)
445
445
446 def __contains__(self, changeid):
446 def __contains__(self, changeid):
447 try:
447 try:
448 return bool(self.lookup(changeid))
448 return bool(self.lookup(changeid))
449 except error.RepoLookupError:
449 except error.RepoLookupError:
450 return False
450 return False
451
451
452 def __nonzero__(self):
452 def __nonzero__(self):
453 return True
453 return True
454
454
455 def __len__(self):
455 def __len__(self):
456 return len(self.changelog)
456 return len(self.changelog)
457
457
458 def __iter__(self):
458 def __iter__(self):
459 return iter(self.changelog)
459 return iter(self.changelog)
460
460
461 def revs(self, expr, *args):
461 def revs(self, expr, *args):
462 '''Return a list of revisions matching the given revset'''
462 '''Return a list of revisions matching the given revset'''
463 expr = revset.formatspec(expr, *args)
463 expr = revset.formatspec(expr, *args)
464 m = revset.match(None, expr)
464 m = revset.match(None, expr)
465 return m(self, revset.spanset(self))
465 return m(self, revset.spanset(self))
466
466
467 def set(self, expr, *args):
467 def set(self, expr, *args):
468 '''
468 '''
469 Yield a context for each matching revision, after doing arg
469 Yield a context for each matching revision, after doing arg
470 replacement via revset.formatspec
470 replacement via revset.formatspec
471 '''
471 '''
472 for r in self.revs(expr, *args):
472 for r in self.revs(expr, *args):
473 yield self[r]
473 yield self[r]
474
474
475 def url(self):
475 def url(self):
476 return 'file:' + self.root
476 return 'file:' + self.root
477
477
478 def hook(self, name, throw=False, **args):
478 def hook(self, name, throw=False, **args):
479 return hook.hook(self.ui, self, name, throw, **args)
479 return hook.hook(self.ui, self, name, throw, **args)
480
480
481 @unfilteredmethod
481 @unfilteredmethod
482 def _tag(self, names, node, message, local, user, date, extra={},
482 def _tag(self, names, node, message, local, user, date, extra={},
483 editor=False):
483 editor=False):
484 if isinstance(names, str):
484 if isinstance(names, str):
485 names = (names,)
485 names = (names,)
486
486
487 branches = self.branchmap()
487 branches = self.branchmap()
488 for name in names:
488 for name in names:
489 self.hook('pretag', throw=True, node=hex(node), tag=name,
489 self.hook('pretag', throw=True, node=hex(node), tag=name,
490 local=local)
490 local=local)
491 if name in branches:
491 if name in branches:
492 self.ui.warn(_("warning: tag %s conflicts with existing"
492 self.ui.warn(_("warning: tag %s conflicts with existing"
493 " branch name\n") % name)
493 " branch name\n") % name)
494
494
495 def writetags(fp, names, munge, prevtags):
495 def writetags(fp, names, munge, prevtags):
496 fp.seek(0, 2)
496 fp.seek(0, 2)
497 if prevtags and prevtags[-1] != '\n':
497 if prevtags and prevtags[-1] != '\n':
498 fp.write('\n')
498 fp.write('\n')
499 for name in names:
499 for name in names:
500 m = munge and munge(name) or name
500 m = munge and munge(name) or name
501 if (self._tagscache.tagtypes and
501 if (self._tagscache.tagtypes and
502 name in self._tagscache.tagtypes):
502 name in self._tagscache.tagtypes):
503 old = self.tags().get(name, nullid)
503 old = self.tags().get(name, nullid)
504 fp.write('%s %s\n' % (hex(old), m))
504 fp.write('%s %s\n' % (hex(old), m))
505 fp.write('%s %s\n' % (hex(node), m))
505 fp.write('%s %s\n' % (hex(node), m))
506 fp.close()
506 fp.close()
507
507
508 prevtags = ''
508 prevtags = ''
509 if local:
509 if local:
510 try:
510 try:
511 fp = self.opener('localtags', 'r+')
511 fp = self.opener('localtags', 'r+')
512 except IOError:
512 except IOError:
513 fp = self.opener('localtags', 'a')
513 fp = self.opener('localtags', 'a')
514 else:
514 else:
515 prevtags = fp.read()
515 prevtags = fp.read()
516
516
517 # local tags are stored in the current charset
517 # local tags are stored in the current charset
518 writetags(fp, names, None, prevtags)
518 writetags(fp, names, None, prevtags)
519 for name in names:
519 for name in names:
520 self.hook('tag', node=hex(node), tag=name, local=local)
520 self.hook('tag', node=hex(node), tag=name, local=local)
521 return
521 return
522
522
523 try:
523 try:
524 fp = self.wfile('.hgtags', 'rb+')
524 fp = self.wfile('.hgtags', 'rb+')
525 except IOError, e:
525 except IOError, e:
526 if e.errno != errno.ENOENT:
526 if e.errno != errno.ENOENT:
527 raise
527 raise
528 fp = self.wfile('.hgtags', 'ab')
528 fp = self.wfile('.hgtags', 'ab')
529 else:
529 else:
530 prevtags = fp.read()
530 prevtags = fp.read()
531
531
532 # committed tags are stored in UTF-8
532 # committed tags are stored in UTF-8
533 writetags(fp, names, encoding.fromlocal, prevtags)
533 writetags(fp, names, encoding.fromlocal, prevtags)
534
534
535 fp.close()
535 fp.close()
536
536
537 self.invalidatecaches()
537 self.invalidatecaches()
538
538
539 if '.hgtags' not in self.dirstate:
539 if '.hgtags' not in self.dirstate:
540 self[None].add(['.hgtags'])
540 self[None].add(['.hgtags'])
541
541
542 m = matchmod.exact(self.root, '', ['.hgtags'])
542 m = matchmod.exact(self.root, '', ['.hgtags'])
543 tagnode = self.commit(message, user, date, extra=extra, match=m,
543 tagnode = self.commit(message, user, date, extra=extra, match=m,
544 editor=editor)
544 editor=editor)
545
545
546 for name in names:
546 for name in names:
547 self.hook('tag', node=hex(node), tag=name, local=local)
547 self.hook('tag', node=hex(node), tag=name, local=local)
548
548
549 return tagnode
549 return tagnode
550
550
551 def tag(self, names, node, message, local, user, date, editor=False):
551 def tag(self, names, node, message, local, user, date, editor=False):
552 '''tag a revision with one or more symbolic names.
552 '''tag a revision with one or more symbolic names.
553
553
554 names is a list of strings or, when adding a single tag, names may be a
554 names is a list of strings or, when adding a single tag, names may be a
555 string.
555 string.
556
556
557 if local is True, the tags are stored in a per-repository file.
557 if local is True, the tags are stored in a per-repository file.
558 otherwise, they are stored in the .hgtags file, and a new
558 otherwise, they are stored in the .hgtags file, and a new
559 changeset is committed with the change.
559 changeset is committed with the change.
560
560
561 keyword arguments:
561 keyword arguments:
562
562
563 local: whether to store tags in non-version-controlled file
563 local: whether to store tags in non-version-controlled file
564 (default False)
564 (default False)
565
565
566 message: commit message to use if committing
566 message: commit message to use if committing
567
567
568 user: name of user to use if committing
568 user: name of user to use if committing
569
569
570 date: date tuple to use if committing'''
570 date: date tuple to use if committing'''
571
571
572 if not local:
572 if not local:
573 for x in self.status()[:5]:
573 for x in self.status()[:5]:
574 if '.hgtags' in x:
574 if '.hgtags' in x:
575 raise util.Abort(_('working copy of .hgtags is changed '
575 raise util.Abort(_('working copy of .hgtags is changed '
576 '(please commit .hgtags manually)'))
576 '(please commit .hgtags manually)'))
577
577
578 self.tags() # instantiate the cache
578 self.tags() # instantiate the cache
579 self._tag(names, node, message, local, user, date, editor=editor)
579 self._tag(names, node, message, local, user, date, editor=editor)
580
580
581 @filteredpropertycache
581 @filteredpropertycache
582 def _tagscache(self):
582 def _tagscache(self):
583 '''Returns a tagscache object that contains various tags related
583 '''Returns a tagscache object that contains various tags related
584 caches.'''
584 caches.'''
585
585
586 # This simplifies its cache management by having one decorated
586 # This simplifies its cache management by having one decorated
587 # function (this one) and the rest simply fetch things from it.
587 # function (this one) and the rest simply fetch things from it.
588 class tagscache(object):
588 class tagscache(object):
589 def __init__(self):
589 def __init__(self):
590 # These two define the set of tags for this repository. tags
590 # These two define the set of tags for this repository. tags
591 # maps tag name to node; tagtypes maps tag name to 'global' or
591 # maps tag name to node; tagtypes maps tag name to 'global' or
592 # 'local'. (Global tags are defined by .hgtags across all
592 # 'local'. (Global tags are defined by .hgtags across all
593 # heads, and local tags are defined in .hg/localtags.)
593 # heads, and local tags are defined in .hg/localtags.)
594 # They constitute the in-memory cache of tags.
594 # They constitute the in-memory cache of tags.
595 self.tags = self.tagtypes = None
595 self.tags = self.tagtypes = None
596
596
597 self.nodetagscache = self.tagslist = None
597 self.nodetagscache = self.tagslist = None
598
598
599 cache = tagscache()
599 cache = tagscache()
600 cache.tags, cache.tagtypes = self._findtags()
600 cache.tags, cache.tagtypes = self._findtags()
601
601
602 return cache
602 return cache
603
603
604 def tags(self):
604 def tags(self):
605 '''return a mapping of tag to node'''
605 '''return a mapping of tag to node'''
606 t = {}
606 t = {}
607 if self.changelog.filteredrevs:
607 if self.changelog.filteredrevs:
608 tags, tt = self._findtags()
608 tags, tt = self._findtags()
609 else:
609 else:
610 tags = self._tagscache.tags
610 tags = self._tagscache.tags
611 for k, v in tags.iteritems():
611 for k, v in tags.iteritems():
612 try:
612 try:
613 # ignore tags to unknown nodes
613 # ignore tags to unknown nodes
614 self.changelog.rev(v)
614 self.changelog.rev(v)
615 t[k] = v
615 t[k] = v
616 except (error.LookupError, ValueError):
616 except (error.LookupError, ValueError):
617 pass
617 pass
618 return t
618 return t
619
619
620 def _findtags(self):
620 def _findtags(self):
621 '''Do the hard work of finding tags. Return a pair of dicts
621 '''Do the hard work of finding tags. Return a pair of dicts
622 (tags, tagtypes) where tags maps tag name to node, and tagtypes
622 (tags, tagtypes) where tags maps tag name to node, and tagtypes
623 maps tag name to a string like \'global\' or \'local\'.
623 maps tag name to a string like \'global\' or \'local\'.
624 Subclasses or extensions are free to add their own tags, but
624 Subclasses or extensions are free to add their own tags, but
625 should be aware that the returned dicts will be retained for the
625 should be aware that the returned dicts will be retained for the
626 duration of the localrepo object.'''
626 duration of the localrepo object.'''
627
627
628 # XXX what tagtype should subclasses/extensions use? Currently
628 # XXX what tagtype should subclasses/extensions use? Currently
629 # mq and bookmarks add tags, but do not set the tagtype at all.
629 # mq and bookmarks add tags, but do not set the tagtype at all.
630 # Should each extension invent its own tag type? Should there
630 # Should each extension invent its own tag type? Should there
631 # be one tagtype for all such "virtual" tags? Or is the status
631 # be one tagtype for all such "virtual" tags? Or is the status
632 # quo fine?
632 # quo fine?
633
633
634 alltags = {} # map tag name to (node, hist)
634 alltags = {} # map tag name to (node, hist)
635 tagtypes = {}
635 tagtypes = {}
636
636
637 tagsmod.findglobaltags(self.ui, self, alltags, tagtypes)
637 tagsmod.findglobaltags(self.ui, self, alltags, tagtypes)
638 tagsmod.readlocaltags(self.ui, self, alltags, tagtypes)
638 tagsmod.readlocaltags(self.ui, self, alltags, tagtypes)
639
639
640 # Build the return dicts. Have to re-encode tag names because
640 # Build the return dicts. Have to re-encode tag names because
641 # the tags module always uses UTF-8 (in order not to lose info
641 # the tags module always uses UTF-8 (in order not to lose info
642 # writing to the cache), but the rest of Mercurial wants them in
642 # writing to the cache), but the rest of Mercurial wants them in
643 # local encoding.
643 # local encoding.
644 tags = {}
644 tags = {}
645 for (name, (node, hist)) in alltags.iteritems():
645 for (name, (node, hist)) in alltags.iteritems():
646 if node != nullid:
646 if node != nullid:
647 tags[encoding.tolocal(name)] = node
647 tags[encoding.tolocal(name)] = node
648 tags['tip'] = self.changelog.tip()
648 tags['tip'] = self.changelog.tip()
649 tagtypes = dict([(encoding.tolocal(name), value)
649 tagtypes = dict([(encoding.tolocal(name), value)
650 for (name, value) in tagtypes.iteritems()])
650 for (name, value) in tagtypes.iteritems()])
651 return (tags, tagtypes)
651 return (tags, tagtypes)
652
652
653 def tagtype(self, tagname):
653 def tagtype(self, tagname):
654 '''
654 '''
655 return the type of the given tag. result can be:
655 return the type of the given tag. result can be:
656
656
657 'local' : a local tag
657 'local' : a local tag
658 'global' : a global tag
658 'global' : a global tag
659 None : tag does not exist
659 None : tag does not exist
660 '''
660 '''
661
661
662 return self._tagscache.tagtypes.get(tagname)
662 return self._tagscache.tagtypes.get(tagname)
663
663
664 def tagslist(self):
664 def tagslist(self):
665 '''return a list of tags ordered by revision'''
665 '''return a list of tags ordered by revision'''
666 if not self._tagscache.tagslist:
666 if not self._tagscache.tagslist:
667 l = []
667 l = []
668 for t, n in self.tags().iteritems():
668 for t, n in self.tags().iteritems():
669 r = self.changelog.rev(n)
669 r = self.changelog.rev(n)
670 l.append((r, t, n))
670 l.append((r, t, n))
671 self._tagscache.tagslist = [(t, n) for r, t, n in sorted(l)]
671 self._tagscache.tagslist = [(t, n) for r, t, n in sorted(l)]
672
672
673 return self._tagscache.tagslist
673 return self._tagscache.tagslist
674
674
675 def nodetags(self, node):
675 def nodetags(self, node):
676 '''return the tags associated with a node'''
676 '''return the tags associated with a node'''
677 if not self._tagscache.nodetagscache:
677 if not self._tagscache.nodetagscache:
678 nodetagscache = {}
678 nodetagscache = {}
679 for t, n in self._tagscache.tags.iteritems():
679 for t, n in self._tagscache.tags.iteritems():
680 nodetagscache.setdefault(n, []).append(t)
680 nodetagscache.setdefault(n, []).append(t)
681 for tags in nodetagscache.itervalues():
681 for tags in nodetagscache.itervalues():
682 tags.sort()
682 tags.sort()
683 self._tagscache.nodetagscache = nodetagscache
683 self._tagscache.nodetagscache = nodetagscache
684 return self._tagscache.nodetagscache.get(node, [])
684 return self._tagscache.nodetagscache.get(node, [])
685
685
686 def nodebookmarks(self, node):
686 def nodebookmarks(self, node):
687 marks = []
687 marks = []
688 for bookmark, n in self._bookmarks.iteritems():
688 for bookmark, n in self._bookmarks.iteritems():
689 if n == node:
689 if n == node:
690 marks.append(bookmark)
690 marks.append(bookmark)
691 return sorted(marks)
691 return sorted(marks)
692
692
693 def branchmap(self):
693 def branchmap(self):
694 '''returns a dictionary {branch: [branchheads]} with branchheads
694 '''returns a dictionary {branch: [branchheads]} with branchheads
695 ordered by increasing revision number'''
695 ordered by increasing revision number'''
696 branchmap.updatecache(self)
696 branchmap.updatecache(self)
697 return self._branchcaches[self.filtername]
697 return self._branchcaches[self.filtername]
698
698
699 def branchtip(self, branch):
699 def branchtip(self, branch):
700 '''return the tip node for a given branch'''
700 '''return the tip node for a given branch'''
701 try:
701 try:
702 return self.branchmap().branchtip(branch)
702 return self.branchmap().branchtip(branch)
703 except KeyError:
703 except KeyError:
704 raise error.RepoLookupError(_("unknown branch '%s'") % branch)
704 raise error.RepoLookupError(_("unknown branch '%s'") % branch)
705
705
706 def lookup(self, key):
706 def lookup(self, key):
707 return self[key].node()
707 return self[key].node()
708
708
709 def lookupbranch(self, key, remote=None):
709 def lookupbranch(self, key, remote=None):
710 repo = remote or self
710 repo = remote or self
711 if key in repo.branchmap():
711 if key in repo.branchmap():
712 return key
712 return key
713
713
714 repo = (remote and remote.local()) and remote or self
714 repo = (remote and remote.local()) and remote or self
715 return repo[key].branch()
715 return repo[key].branch()
716
716
717 def known(self, nodes):
717 def known(self, nodes):
718 nm = self.changelog.nodemap
718 nm = self.changelog.nodemap
719 pc = self._phasecache
719 pc = self._phasecache
720 result = []
720 result = []
721 for n in nodes:
721 for n in nodes:
722 r = nm.get(n)
722 r = nm.get(n)
723 resp = not (r is None or pc.phase(self, r) >= phases.secret)
723 resp = not (r is None or pc.phase(self, r) >= phases.secret)
724 result.append(resp)
724 result.append(resp)
725 return result
725 return result
726
726
727 def local(self):
727 def local(self):
728 return self
728 return self
729
729
730 def cancopy(self):
730 def cancopy(self):
731 # so statichttprepo's override of local() works
731 # so statichttprepo's override of local() works
732 if not self.local():
732 if not self.local():
733 return False
733 return False
734 if not self.ui.configbool('phases', 'publish', True):
734 if not self.ui.configbool('phases', 'publish', True):
735 return True
735 return True
736 # if publishing we can't copy if there is filtered content
736 # if publishing we can't copy if there is filtered content
737 return not self.filtered('visible').changelog.filteredrevs
737 return not self.filtered('visible').changelog.filteredrevs
738
738
739 def join(self, f):
739 def join(self, f):
740 return os.path.join(self.path, f)
740 return os.path.join(self.path, f)
741
741
742 def wjoin(self, f):
742 def wjoin(self, f):
743 return os.path.join(self.root, f)
743 return os.path.join(self.root, f)
744
744
745 def file(self, f):
745 def file(self, f):
746 if f[0] == '/':
746 if f[0] == '/':
747 f = f[1:]
747 f = f[1:]
748 return filelog.filelog(self.sopener, f)
748 return filelog.filelog(self.sopener, f)
749
749
750 def changectx(self, changeid):
750 def changectx(self, changeid):
751 return self[changeid]
751 return self[changeid]
752
752
753 def parents(self, changeid=None):
753 def parents(self, changeid=None):
754 '''get list of changectxs for parents of changeid'''
754 '''get list of changectxs for parents of changeid'''
755 return self[changeid].parents()
755 return self[changeid].parents()
756
756
757 def setparents(self, p1, p2=nullid):
757 def setparents(self, p1, p2=nullid):
758 copies = self.dirstate.setparents(p1, p2)
758 copies = self.dirstate.setparents(p1, p2)
759 pctx = self[p1]
759 pctx = self[p1]
760 if copies:
760 if copies:
761 # Adjust copy records, the dirstate cannot do it, it
761 # Adjust copy records, the dirstate cannot do it, it
762 # requires access to parents manifests. Preserve them
762 # requires access to parents manifests. Preserve them
763 # only for entries added to first parent.
763 # only for entries added to first parent.
764 for f in copies:
764 for f in copies:
765 if f not in pctx and copies[f] in pctx:
765 if f not in pctx and copies[f] in pctx:
766 self.dirstate.copy(copies[f], f)
766 self.dirstate.copy(copies[f], f)
767 if p2 == nullid:
767 if p2 == nullid:
768 for f, s in sorted(self.dirstate.copies().items()):
768 for f, s in sorted(self.dirstate.copies().items()):
769 if f not in pctx and s not in pctx:
769 if f not in pctx and s not in pctx:
770 self.dirstate.copy(None, f)
770 self.dirstate.copy(None, f)
771
771
772 def filectx(self, path, changeid=None, fileid=None):
772 def filectx(self, path, changeid=None, fileid=None):
773 """changeid can be a changeset revision, node, or tag.
773 """changeid can be a changeset revision, node, or tag.
774 fileid can be a file revision or node."""
774 fileid can be a file revision or node."""
775 return context.filectx(self, path, changeid, fileid)
775 return context.filectx(self, path, changeid, fileid)
776
776
777 def getcwd(self):
777 def getcwd(self):
778 return self.dirstate.getcwd()
778 return self.dirstate.getcwd()
779
779
780 def pathto(self, f, cwd=None):
780 def pathto(self, f, cwd=None):
781 return self.dirstate.pathto(f, cwd)
781 return self.dirstate.pathto(f, cwd)
782
782
783 def wfile(self, f, mode='r'):
783 def wfile(self, f, mode='r'):
784 return self.wopener(f, mode)
784 return self.wopener(f, mode)
785
785
786 def _link(self, f):
786 def _link(self, f):
787 return self.wvfs.islink(f)
787 return self.wvfs.islink(f)
788
788
789 def _loadfilter(self, filter):
789 def _loadfilter(self, filter):
790 if filter not in self.filterpats:
790 if filter not in self.filterpats:
791 l = []
791 l = []
792 for pat, cmd in self.ui.configitems(filter):
792 for pat, cmd in self.ui.configitems(filter):
793 if cmd == '!':
793 if cmd == '!':
794 continue
794 continue
795 mf = matchmod.match(self.root, '', [pat])
795 mf = matchmod.match(self.root, '', [pat])
796 fn = None
796 fn = None
797 params = cmd
797 params = cmd
798 for name, filterfn in self._datafilters.iteritems():
798 for name, filterfn in self._datafilters.iteritems():
799 if cmd.startswith(name):
799 if cmd.startswith(name):
800 fn = filterfn
800 fn = filterfn
801 params = cmd[len(name):].lstrip()
801 params = cmd[len(name):].lstrip()
802 break
802 break
803 if not fn:
803 if not fn:
804 fn = lambda s, c, **kwargs: util.filter(s, c)
804 fn = lambda s, c, **kwargs: util.filter(s, c)
805 # Wrap old filters not supporting keyword arguments
805 # Wrap old filters not supporting keyword arguments
806 if not inspect.getargspec(fn)[2]:
806 if not inspect.getargspec(fn)[2]:
807 oldfn = fn
807 oldfn = fn
808 fn = lambda s, c, **kwargs: oldfn(s, c)
808 fn = lambda s, c, **kwargs: oldfn(s, c)
809 l.append((mf, fn, params))
809 l.append((mf, fn, params))
810 self.filterpats[filter] = l
810 self.filterpats[filter] = l
811 return self.filterpats[filter]
811 return self.filterpats[filter]
812
812
813 def _filter(self, filterpats, filename, data):
813 def _filter(self, filterpats, filename, data):
814 for mf, fn, cmd in filterpats:
814 for mf, fn, cmd in filterpats:
815 if mf(filename):
815 if mf(filename):
816 self.ui.debug("filtering %s through %s\n" % (filename, cmd))
816 self.ui.debug("filtering %s through %s\n" % (filename, cmd))
817 data = fn(data, cmd, ui=self.ui, repo=self, filename=filename)
817 data = fn(data, cmd, ui=self.ui, repo=self, filename=filename)
818 break
818 break
819
819
820 return data
820 return data
821
821
822 @unfilteredpropertycache
822 @unfilteredpropertycache
823 def _encodefilterpats(self):
823 def _encodefilterpats(self):
824 return self._loadfilter('encode')
824 return self._loadfilter('encode')
825
825
826 @unfilteredpropertycache
826 @unfilteredpropertycache
827 def _decodefilterpats(self):
827 def _decodefilterpats(self):
828 return self._loadfilter('decode')
828 return self._loadfilter('decode')
829
829
830 def adddatafilter(self, name, filter):
830 def adddatafilter(self, name, filter):
831 self._datafilters[name] = filter
831 self._datafilters[name] = filter
832
832
833 def wread(self, filename):
833 def wread(self, filename):
834 if self._link(filename):
834 if self._link(filename):
835 data = self.wvfs.readlink(filename)
835 data = self.wvfs.readlink(filename)
836 else:
836 else:
837 data = self.wopener.read(filename)
837 data = self.wopener.read(filename)
838 return self._filter(self._encodefilterpats, filename, data)
838 return self._filter(self._encodefilterpats, filename, data)
839
839
840 def wwrite(self, filename, data, flags):
840 def wwrite(self, filename, data, flags):
841 data = self._filter(self._decodefilterpats, filename, data)
841 data = self._filter(self._decodefilterpats, filename, data)
842 if 'l' in flags:
842 if 'l' in flags:
843 self.wopener.symlink(data, filename)
843 self.wopener.symlink(data, filename)
844 else:
844 else:
845 self.wopener.write(filename, data)
845 self.wopener.write(filename, data)
846 if 'x' in flags:
846 if 'x' in flags:
847 self.wvfs.setflags(filename, False, True)
847 self.wvfs.setflags(filename, False, True)
848
848
849 def wwritedata(self, filename, data):
849 def wwritedata(self, filename, data):
850 return self._filter(self._decodefilterpats, filename, data)
850 return self._filter(self._decodefilterpats, filename, data)
851
851
852 def transaction(self, desc, report=None):
852 def transaction(self, desc, report=None):
853 tr = self._transref and self._transref() or None
853 tr = self._transref and self._transref() or None
854 if tr and tr.running():
854 if tr and tr.running():
855 return tr.nest()
855 return tr.nest()
856
856
857 # abort here if the journal already exists
857 # abort here if the journal already exists
858 if self.svfs.exists("journal"):
858 if self.svfs.exists("journal"):
859 raise error.RepoError(
859 raise error.RepoError(
860 _("abandoned transaction found"),
860 _("abandoned transaction found"),
861 hint=_("run 'hg recover' to clean up transaction"))
861 hint=_("run 'hg recover' to clean up transaction"))
862
862
863 def onclose():
863 def onclose():
864 self.store.write(tr)
864 self.store.write(tr)
865
865
866 self._writejournal(desc)
866 self._writejournal(desc)
867 renames = [(vfs, x, undoname(x)) for vfs, x in self._journalfiles()]
867 renames = [(vfs, x, undoname(x)) for vfs, x in self._journalfiles()]
868 rp = report and report or self.ui.warn
868 rp = report and report or self.ui.warn
869 tr = transaction.transaction(rp, self.sopener,
869 tr = transaction.transaction(rp, self.sopener,
870 "journal",
870 "journal",
871 aftertrans(renames),
871 aftertrans(renames),
872 self.store.createmode,
872 self.store.createmode,
873 onclose)
873 onclose)
874 self._transref = weakref.ref(tr)
874 self._transref = weakref.ref(tr)
875 return tr
875 return tr
876
876
877 def _journalfiles(self):
877 def _journalfiles(self):
878 return ((self.svfs, 'journal'),
878 return ((self.svfs, 'journal'),
879 (self.vfs, 'journal.dirstate'),
879 (self.vfs, 'journal.dirstate'),
880 (self.vfs, 'journal.branch'),
880 (self.vfs, 'journal.branch'),
881 (self.vfs, 'journal.desc'),
881 (self.vfs, 'journal.desc'),
882 (self.vfs, 'journal.bookmarks'),
882 (self.vfs, 'journal.bookmarks'),
883 (self.svfs, 'journal.phaseroots'))
883 (self.svfs, 'journal.phaseroots'))
884
884
885 def undofiles(self):
885 def undofiles(self):
886 return [(vfs, undoname(x)) for vfs, x in self._journalfiles()]
886 return [(vfs, undoname(x)) for vfs, x in self._journalfiles()]
887
887
888 def _writejournal(self, desc):
888 def _writejournal(self, desc):
889 self.opener.write("journal.dirstate",
889 self.opener.write("journal.dirstate",
890 self.opener.tryread("dirstate"))
890 self.opener.tryread("dirstate"))
891 self.opener.write("journal.branch",
891 self.opener.write("journal.branch",
892 encoding.fromlocal(self.dirstate.branch()))
892 encoding.fromlocal(self.dirstate.branch()))
893 self.opener.write("journal.desc",
893 self.opener.write("journal.desc",
894 "%d\n%s\n" % (len(self), desc))
894 "%d\n%s\n" % (len(self), desc))
895 self.opener.write("journal.bookmarks",
895 self.opener.write("journal.bookmarks",
896 self.opener.tryread("bookmarks"))
896 self.opener.tryread("bookmarks"))
897 self.sopener.write("journal.phaseroots",
897 self.sopener.write("journal.phaseroots",
898 self.sopener.tryread("phaseroots"))
898 self.sopener.tryread("phaseroots"))
899
899
900 def recover(self):
900 def recover(self):
901 lock = self.lock()
901 lock = self.lock()
902 try:
902 try:
903 if self.svfs.exists("journal"):
903 if self.svfs.exists("journal"):
904 self.ui.status(_("rolling back interrupted transaction\n"))
904 self.ui.status(_("rolling back interrupted transaction\n"))
905 transaction.rollback(self.sopener, "journal",
905 transaction.rollback(self.sopener, "journal",
906 self.ui.warn)
906 self.ui.warn)
907 self.invalidate()
907 self.invalidate()
908 return True
908 return True
909 else:
909 else:
910 self.ui.warn(_("no interrupted transaction available\n"))
910 self.ui.warn(_("no interrupted transaction available\n"))
911 return False
911 return False
912 finally:
912 finally:
913 lock.release()
913 lock.release()
914
914
915 def rollback(self, dryrun=False, force=False):
915 def rollback(self, dryrun=False, force=False):
916 wlock = lock = None
916 wlock = lock = None
917 try:
917 try:
918 wlock = self.wlock()
918 wlock = self.wlock()
919 lock = self.lock()
919 lock = self.lock()
920 if self.svfs.exists("undo"):
920 if self.svfs.exists("undo"):
921 return self._rollback(dryrun, force)
921 return self._rollback(dryrun, force)
922 else:
922 else:
923 self.ui.warn(_("no rollback information available\n"))
923 self.ui.warn(_("no rollback information available\n"))
924 return 1
924 return 1
925 finally:
925 finally:
926 release(lock, wlock)
926 release(lock, wlock)
927
927
928 @unfilteredmethod # Until we get smarter cache management
928 @unfilteredmethod # Until we get smarter cache management
929 def _rollback(self, dryrun, force):
929 def _rollback(self, dryrun, force):
930 ui = self.ui
930 ui = self.ui
931 try:
931 try:
932 args = self.opener.read('undo.desc').splitlines()
932 args = self.opener.read('undo.desc').splitlines()
933 (oldlen, desc, detail) = (int(args[0]), args[1], None)
933 (oldlen, desc, detail) = (int(args[0]), args[1], None)
934 if len(args) >= 3:
934 if len(args) >= 3:
935 detail = args[2]
935 detail = args[2]
936 oldtip = oldlen - 1
936 oldtip = oldlen - 1
937
937
938 if detail and ui.verbose:
938 if detail and ui.verbose:
939 msg = (_('repository tip rolled back to revision %s'
939 msg = (_('repository tip rolled back to revision %s'
940 ' (undo %s: %s)\n')
940 ' (undo %s: %s)\n')
941 % (oldtip, desc, detail))
941 % (oldtip, desc, detail))
942 else:
942 else:
943 msg = (_('repository tip rolled back to revision %s'
943 msg = (_('repository tip rolled back to revision %s'
944 ' (undo %s)\n')
944 ' (undo %s)\n')
945 % (oldtip, desc))
945 % (oldtip, desc))
946 except IOError:
946 except IOError:
947 msg = _('rolling back unknown transaction\n')
947 msg = _('rolling back unknown transaction\n')
948 desc = None
948 desc = None
949
949
950 if not force and self['.'] != self['tip'] and desc == 'commit':
950 if not force and self['.'] != self['tip'] and desc == 'commit':
951 raise util.Abort(
951 raise util.Abort(
952 _('rollback of last commit while not checked out '
952 _('rollback of last commit while not checked out '
953 'may lose data'), hint=_('use -f to force'))
953 'may lose data'), hint=_('use -f to force'))
954
954
955 ui.status(msg)
955 ui.status(msg)
956 if dryrun:
956 if dryrun:
957 return 0
957 return 0
958
958
959 parents = self.dirstate.parents()
959 parents = self.dirstate.parents()
960 self.destroying()
960 self.destroying()
961 transaction.rollback(self.sopener, 'undo', ui.warn)
961 transaction.rollback(self.sopener, 'undo', ui.warn)
962 if self.vfs.exists('undo.bookmarks'):
962 if self.vfs.exists('undo.bookmarks'):
963 self.vfs.rename('undo.bookmarks', 'bookmarks')
963 self.vfs.rename('undo.bookmarks', 'bookmarks')
964 if self.svfs.exists('undo.phaseroots'):
964 if self.svfs.exists('undo.phaseroots'):
965 self.svfs.rename('undo.phaseroots', 'phaseroots')
965 self.svfs.rename('undo.phaseroots', 'phaseroots')
966 self.invalidate()
966 self.invalidate()
967
967
968 parentgone = (parents[0] not in self.changelog.nodemap or
968 parentgone = (parents[0] not in self.changelog.nodemap or
969 parents[1] not in self.changelog.nodemap)
969 parents[1] not in self.changelog.nodemap)
970 if parentgone:
970 if parentgone:
971 self.vfs.rename('undo.dirstate', 'dirstate')
971 self.vfs.rename('undo.dirstate', 'dirstate')
972 try:
972 try:
973 branch = self.opener.read('undo.branch')
973 branch = self.opener.read('undo.branch')
974 self.dirstate.setbranch(encoding.tolocal(branch))
974 self.dirstate.setbranch(encoding.tolocal(branch))
975 except IOError:
975 except IOError:
976 ui.warn(_('named branch could not be reset: '
976 ui.warn(_('named branch could not be reset: '
977 'current branch is still \'%s\'\n')
977 'current branch is still \'%s\'\n')
978 % self.dirstate.branch())
978 % self.dirstate.branch())
979
979
980 self.dirstate.invalidate()
980 self.dirstate.invalidate()
981 parents = tuple([p.rev() for p in self.parents()])
981 parents = tuple([p.rev() for p in self.parents()])
982 if len(parents) > 1:
982 if len(parents) > 1:
983 ui.status(_('working directory now based on '
983 ui.status(_('working directory now based on '
984 'revisions %d and %d\n') % parents)
984 'revisions %d and %d\n') % parents)
985 else:
985 else:
986 ui.status(_('working directory now based on '
986 ui.status(_('working directory now based on '
987 'revision %d\n') % parents)
987 'revision %d\n') % parents)
988 # TODO: if we know which new heads may result from this rollback, pass
988 # TODO: if we know which new heads may result from this rollback, pass
989 # them to destroy(), which will prevent the branchhead cache from being
989 # them to destroy(), which will prevent the branchhead cache from being
990 # invalidated.
990 # invalidated.
991 self.destroyed()
991 self.destroyed()
992 return 0
992 return 0
993
993
994 def invalidatecaches(self):
994 def invalidatecaches(self):
995
995
996 if '_tagscache' in vars(self):
996 if '_tagscache' in vars(self):
997 # can't use delattr on proxy
997 # can't use delattr on proxy
998 del self.__dict__['_tagscache']
998 del self.__dict__['_tagscache']
999
999
1000 self.unfiltered()._branchcaches.clear()
1000 self.unfiltered()._branchcaches.clear()
1001 self.invalidatevolatilesets()
1001 self.invalidatevolatilesets()
1002
1002
1003 def invalidatevolatilesets(self):
1003 def invalidatevolatilesets(self):
1004 self.filteredrevcache.clear()
1004 self.filteredrevcache.clear()
1005 obsolete.clearobscaches(self)
1005 obsolete.clearobscaches(self)
1006
1006
1007 def invalidatedirstate(self):
1007 def invalidatedirstate(self):
1008 '''Invalidates the dirstate, causing the next call to dirstate
1008 '''Invalidates the dirstate, causing the next call to dirstate
1009 to check if it was modified since the last time it was read,
1009 to check if it was modified since the last time it was read,
1010 rereading it if it has.
1010 rereading it if it has.
1011
1011
1012 This is different to dirstate.invalidate() that it doesn't always
1012 This is different to dirstate.invalidate() that it doesn't always
1013 rereads the dirstate. Use dirstate.invalidate() if you want to
1013 rereads the dirstate. Use dirstate.invalidate() if you want to
1014 explicitly read the dirstate again (i.e. restoring it to a previous
1014 explicitly read the dirstate again (i.e. restoring it to a previous
1015 known good state).'''
1015 known good state).'''
1016 if hasunfilteredcache(self, 'dirstate'):
1016 if hasunfilteredcache(self, 'dirstate'):
1017 for k in self.dirstate._filecache:
1017 for k in self.dirstate._filecache:
1018 try:
1018 try:
1019 delattr(self.dirstate, k)
1019 delattr(self.dirstate, k)
1020 except AttributeError:
1020 except AttributeError:
1021 pass
1021 pass
1022 delattr(self.unfiltered(), 'dirstate')
1022 delattr(self.unfiltered(), 'dirstate')
1023
1023
1024 def invalidate(self):
1024 def invalidate(self):
1025 unfiltered = self.unfiltered() # all file caches are stored unfiltered
1025 unfiltered = self.unfiltered() # all file caches are stored unfiltered
1026 for k in self._filecache:
1026 for k in self._filecache:
1027 # dirstate is invalidated separately in invalidatedirstate()
1027 # dirstate is invalidated separately in invalidatedirstate()
1028 if k == 'dirstate':
1028 if k == 'dirstate':
1029 continue
1029 continue
1030
1030
1031 try:
1031 try:
1032 delattr(unfiltered, k)
1032 delattr(unfiltered, k)
1033 except AttributeError:
1033 except AttributeError:
1034 pass
1034 pass
1035 self.invalidatecaches()
1035 self.invalidatecaches()
1036 self.store.invalidatecaches()
1036 self.store.invalidatecaches()
1037
1037
1038 def invalidateall(self):
1038 def invalidateall(self):
1039 '''Fully invalidates both store and non-store parts, causing the
1039 '''Fully invalidates both store and non-store parts, causing the
1040 subsequent operation to reread any outside changes.'''
1040 subsequent operation to reread any outside changes.'''
1041 # extension should hook this to invalidate its caches
1041 # extension should hook this to invalidate its caches
1042 self.invalidate()
1042 self.invalidate()
1043 self.invalidatedirstate()
1043 self.invalidatedirstate()
1044
1044
1045 def _lock(self, vfs, lockname, wait, releasefn, acquirefn, desc):
1045 def _lock(self, vfs, lockname, wait, releasefn, acquirefn, desc):
1046 try:
1046 try:
1047 l = lockmod.lock(vfs, lockname, 0, releasefn, desc=desc)
1047 l = lockmod.lock(vfs, lockname, 0, releasefn, desc=desc)
1048 except error.LockHeld, inst:
1048 except error.LockHeld, inst:
1049 if not wait:
1049 if not wait:
1050 raise
1050 raise
1051 self.ui.warn(_("waiting for lock on %s held by %r\n") %
1051 self.ui.warn(_("waiting for lock on %s held by %r\n") %
1052 (desc, inst.locker))
1052 (desc, inst.locker))
1053 # default to 600 seconds timeout
1053 # default to 600 seconds timeout
1054 l = lockmod.lock(vfs, lockname,
1054 l = lockmod.lock(vfs, lockname,
1055 int(self.ui.config("ui", "timeout", "600")),
1055 int(self.ui.config("ui", "timeout", "600")),
1056 releasefn, desc=desc)
1056 releasefn, desc=desc)
1057 self.ui.warn(_("got lock after %s seconds\n") % l.delay)
1057 self.ui.warn(_("got lock after %s seconds\n") % l.delay)
1058 if acquirefn:
1058 if acquirefn:
1059 acquirefn()
1059 acquirefn()
1060 return l
1060 return l
1061
1061
1062 def _afterlock(self, callback):
1062 def _afterlock(self, callback):
1063 """add a callback to the current repository lock.
1063 """add a callback to the current repository lock.
1064
1064
1065 The callback will be executed on lock release."""
1065 The callback will be executed on lock release."""
1066 l = self._lockref and self._lockref()
1066 l = self._lockref and self._lockref()
1067 if l:
1067 if l:
1068 l.postrelease.append(callback)
1068 l.postrelease.append(callback)
1069 else:
1069 else:
1070 callback()
1070 callback()
1071
1071
1072 def lock(self, wait=True):
1072 def lock(self, wait=True):
1073 '''Lock the repository store (.hg/store) and return a weak reference
1073 '''Lock the repository store (.hg/store) and return a weak reference
1074 to the lock. Use this before modifying the store (e.g. committing or
1074 to the lock. Use this before modifying the store (e.g. committing or
1075 stripping). If you are opening a transaction, get a lock as well.)'''
1075 stripping). If you are opening a transaction, get a lock as well.)'''
1076 l = self._lockref and self._lockref()
1076 l = self._lockref and self._lockref()
1077 if l is not None and l.held:
1077 if l is not None and l.held:
1078 l.lock()
1078 l.lock()
1079 return l
1079 return l
1080
1080
1081 def unlock():
1081 def unlock():
1082 if hasunfilteredcache(self, '_phasecache'):
1082 if hasunfilteredcache(self, '_phasecache'):
1083 self._phasecache.write()
1083 self._phasecache.write()
1084 for k, ce in self._filecache.items():
1084 for k, ce in self._filecache.items():
1085 if k == 'dirstate' or k not in self.__dict__:
1085 if k == 'dirstate' or k not in self.__dict__:
1086 continue
1086 continue
1087 ce.refresh()
1087 ce.refresh()
1088
1088
1089 l = self._lock(self.svfs, "lock", wait, unlock,
1089 l = self._lock(self.svfs, "lock", wait, unlock,
1090 self.invalidate, _('repository %s') % self.origroot)
1090 self.invalidate, _('repository %s') % self.origroot)
1091 self._lockref = weakref.ref(l)
1091 self._lockref = weakref.ref(l)
1092 return l
1092 return l
1093
1093
1094 def wlock(self, wait=True):
1094 def wlock(self, wait=True):
1095 '''Lock the non-store parts of the repository (everything under
1095 '''Lock the non-store parts of the repository (everything under
1096 .hg except .hg/store) and return a weak reference to the lock.
1096 .hg except .hg/store) and return a weak reference to the lock.
1097 Use this before modifying files in .hg.'''
1097 Use this before modifying files in .hg.'''
1098 l = self._wlockref and self._wlockref()
1098 l = self._wlockref and self._wlockref()
1099 if l is not None and l.held:
1099 if l is not None and l.held:
1100 l.lock()
1100 l.lock()
1101 return l
1101 return l
1102
1102
1103 def unlock():
1103 def unlock():
1104 self.dirstate.write()
1104 self.dirstate.write()
1105 self._filecache['dirstate'].refresh()
1105 self._filecache['dirstate'].refresh()
1106
1106
1107 l = self._lock(self.vfs, "wlock", wait, unlock,
1107 l = self._lock(self.vfs, "wlock", wait, unlock,
1108 self.invalidatedirstate, _('working directory of %s') %
1108 self.invalidatedirstate, _('working directory of %s') %
1109 self.origroot)
1109 self.origroot)
1110 self._wlockref = weakref.ref(l)
1110 self._wlockref = weakref.ref(l)
1111 return l
1111 return l
1112
1112
1113 def _filecommit(self, fctx, manifest1, manifest2, linkrev, tr, changelist):
1113 def _filecommit(self, fctx, manifest1, manifest2, linkrev, tr, changelist):
1114 """
1114 """
1115 commit an individual file as part of a larger transaction
1115 commit an individual file as part of a larger transaction
1116 """
1116 """
1117
1117
1118 fname = fctx.path()
1118 fname = fctx.path()
1119 text = fctx.data()
1119 text = fctx.data()
1120 flog = self.file(fname)
1120 flog = self.file(fname)
1121 fparent1 = manifest1.get(fname, nullid)
1121 fparent1 = manifest1.get(fname, nullid)
1122 fparent2 = fparent2o = manifest2.get(fname, nullid)
1122 fparent2 = fparent2o = manifest2.get(fname, nullid)
1123
1123
1124 meta = {}
1124 meta = {}
1125 copy = fctx.renamed()
1125 copy = fctx.renamed()
1126 if copy and copy[0] != fname:
1126 if copy and copy[0] != fname:
1127 # Mark the new revision of this file as a copy of another
1127 # Mark the new revision of this file as a copy of another
1128 # file. This copy data will effectively act as a parent
1128 # file. This copy data will effectively act as a parent
1129 # of this new revision. If this is a merge, the first
1129 # of this new revision. If this is a merge, the first
1130 # parent will be the nullid (meaning "look up the copy data")
1130 # parent will be the nullid (meaning "look up the copy data")
1131 # and the second one will be the other parent. For example:
1131 # and the second one will be the other parent. For example:
1132 #
1132 #
1133 # 0 --- 1 --- 3 rev1 changes file foo
1133 # 0 --- 1 --- 3 rev1 changes file foo
1134 # \ / rev2 renames foo to bar and changes it
1134 # \ / rev2 renames foo to bar and changes it
1135 # \- 2 -/ rev3 should have bar with all changes and
1135 # \- 2 -/ rev3 should have bar with all changes and
1136 # should record that bar descends from
1136 # should record that bar descends from
1137 # bar in rev2 and foo in rev1
1137 # bar in rev2 and foo in rev1
1138 #
1138 #
1139 # this allows this merge to succeed:
1139 # this allows this merge to succeed:
1140 #
1140 #
1141 # 0 --- 1 --- 3 rev4 reverts the content change from rev2
1141 # 0 --- 1 --- 3 rev4 reverts the content change from rev2
1142 # \ / merging rev3 and rev4 should use bar@rev2
1142 # \ / merging rev3 and rev4 should use bar@rev2
1143 # \- 2 --- 4 as the merge base
1143 # \- 2 --- 4 as the merge base
1144 #
1144 #
1145
1145
1146 cfname = copy[0]
1146 cfname = copy[0]
1147 crev = manifest1.get(cfname)
1147 crev = manifest1.get(cfname)
1148 newfparent = fparent2
1148 newfparent = fparent2
1149
1149
1150 if manifest2: # branch merge
1150 if manifest2: # branch merge
1151 if fparent2 == nullid or crev is None: # copied on remote side
1151 if fparent2 == nullid or crev is None: # copied on remote side
1152 if cfname in manifest2:
1152 if cfname in manifest2:
1153 crev = manifest2[cfname]
1153 crev = manifest2[cfname]
1154 newfparent = fparent1
1154 newfparent = fparent1
1155
1155
1156 # find source in nearest ancestor if we've lost track
1156 # find source in nearest ancestor if we've lost track
1157 if not crev:
1157 if not crev:
1158 self.ui.debug(" %s: searching for copy revision for %s\n" %
1158 self.ui.debug(" %s: searching for copy revision for %s\n" %
1159 (fname, cfname))
1159 (fname, cfname))
1160 for ancestor in self[None].ancestors():
1160 for ancestor in self[None].ancestors():
1161 if cfname in ancestor:
1161 if cfname in ancestor:
1162 crev = ancestor[cfname].filenode()
1162 crev = ancestor[cfname].filenode()
1163 break
1163 break
1164
1164
1165 if crev:
1165 if crev:
1166 self.ui.debug(" %s: copy %s:%s\n" % (fname, cfname, hex(crev)))
1166 self.ui.debug(" %s: copy %s:%s\n" % (fname, cfname, hex(crev)))
1167 meta["copy"] = cfname
1167 meta["copy"] = cfname
1168 meta["copyrev"] = hex(crev)
1168 meta["copyrev"] = hex(crev)
1169 fparent1, fparent2 = nullid, newfparent
1169 fparent1, fparent2 = nullid, newfparent
1170 else:
1170 else:
1171 self.ui.warn(_("warning: can't find ancestor for '%s' "
1171 self.ui.warn(_("warning: can't find ancestor for '%s' "
1172 "copied from '%s'!\n") % (fname, cfname))
1172 "copied from '%s'!\n") % (fname, cfname))
1173
1173
1174 elif fparent1 == nullid:
1174 elif fparent1 == nullid:
1175 fparent1, fparent2 = fparent2, nullid
1175 fparent1, fparent2 = fparent2, nullid
1176 elif fparent2 != nullid:
1176 elif fparent2 != nullid:
1177 # is one parent an ancestor of the other?
1177 # is one parent an ancestor of the other?
1178 fparentancestors = flog.commonancestorsheads(fparent1, fparent2)
1178 fparentancestors = flog.commonancestorsheads(fparent1, fparent2)
1179 if fparent1 in fparentancestors:
1179 if fparent1 in fparentancestors:
1180 fparent1, fparent2 = fparent2, nullid
1180 fparent1, fparent2 = fparent2, nullid
1181 elif fparent2 in fparentancestors:
1181 elif fparent2 in fparentancestors:
1182 fparent2 = nullid
1182 fparent2 = nullid
1183
1183
1184 # is the file changed?
1184 # is the file changed?
1185 if fparent2 != nullid or flog.cmp(fparent1, text) or meta:
1185 if fparent2 != nullid or flog.cmp(fparent1, text) or meta:
1186 changelist.append(fname)
1186 changelist.append(fname)
1187 return flog.add(text, meta, tr, linkrev, fparent1, fparent2)
1187 return flog.add(text, meta, tr, linkrev, fparent1, fparent2)
1188
1188
1189 # are just the flags changed during merge?
1189 # are just the flags changed during merge?
1190 if fparent1 != fparent2o and manifest1.flags(fname) != fctx.flags():
1190 if fparent1 != fparent2o and manifest1.flags(fname) != fctx.flags():
1191 changelist.append(fname)
1191 changelist.append(fname)
1192
1192
1193 return fparent1
1193 return fparent1
1194
1194
1195 @unfilteredmethod
1195 @unfilteredmethod
1196 def commit(self, text="", user=None, date=None, match=None, force=False,
1196 def commit(self, text="", user=None, date=None, match=None, force=False,
1197 editor=False, extra={}):
1197 editor=False, extra={}):
1198 """Add a new revision to current repository.
1198 """Add a new revision to current repository.
1199
1199
1200 Revision information is gathered from the working directory,
1200 Revision information is gathered from the working directory,
1201 match can be used to filter the committed files. If editor is
1201 match can be used to filter the committed files. If editor is
1202 supplied, it is called to get a commit message.
1202 supplied, it is called to get a commit message.
1203 """
1203 """
1204
1204
1205 def fail(f, msg):
1205 def fail(f, msg):
1206 raise util.Abort('%s: %s' % (f, msg))
1206 raise util.Abort('%s: %s' % (f, msg))
1207
1207
1208 if not match:
1208 if not match:
1209 match = matchmod.always(self.root, '')
1209 match = matchmod.always(self.root, '')
1210
1210
1211 if not force:
1211 if not force:
1212 vdirs = []
1212 vdirs = []
1213 match.explicitdir = vdirs.append
1213 match.explicitdir = vdirs.append
1214 match.bad = fail
1214 match.bad = fail
1215
1215
1216 wlock = self.wlock()
1216 wlock = self.wlock()
1217 try:
1217 try:
1218 wctx = self[None]
1218 wctx = self[None]
1219 merge = len(wctx.parents()) > 1
1219 merge = len(wctx.parents()) > 1
1220
1220
1221 if (not force and merge and match and
1221 if (not force and merge and match and
1222 (match.files() or match.anypats())):
1222 (match.files() or match.anypats())):
1223 raise util.Abort(_('cannot partially commit a merge '
1223 raise util.Abort(_('cannot partially commit a merge '
1224 '(do not specify files or patterns)'))
1224 '(do not specify files or patterns)'))
1225
1225
1226 changes = self.status(match=match, clean=force)
1226 changes = self.status(match=match, clean=force)
1227 if force:
1227 if force:
1228 changes[0].extend(changes[6]) # mq may commit unchanged files
1228 changes[0].extend(changes[6]) # mq may commit unchanged files
1229
1229
1230 # check subrepos
1230 # check subrepos
1231 subs = []
1231 subs = []
1232 commitsubs = set()
1232 commitsubs = set()
1233 newstate = wctx.substate.copy()
1233 newstate = wctx.substate.copy()
1234 # only manage subrepos and .hgsubstate if .hgsub is present
1234 # only manage subrepos and .hgsubstate if .hgsub is present
1235 if '.hgsub' in wctx:
1235 if '.hgsub' in wctx:
1236 # we'll decide whether to track this ourselves, thanks
1236 # we'll decide whether to track this ourselves, thanks
1237 for c in changes[:3]:
1237 for c in changes[:3]:
1238 if '.hgsubstate' in c:
1238 if '.hgsubstate' in c:
1239 c.remove('.hgsubstate')
1239 c.remove('.hgsubstate')
1240
1240
1241 # compare current state to last committed state
1241 # compare current state to last committed state
1242 # build new substate based on last committed state
1242 # build new substate based on last committed state
1243 oldstate = wctx.p1().substate
1243 oldstate = wctx.p1().substate
1244 for s in sorted(newstate.keys()):
1244 for s in sorted(newstate.keys()):
1245 if not match(s):
1245 if not match(s):
1246 # ignore working copy, use old state if present
1246 # ignore working copy, use old state if present
1247 if s in oldstate:
1247 if s in oldstate:
1248 newstate[s] = oldstate[s]
1248 newstate[s] = oldstate[s]
1249 continue
1249 continue
1250 if not force:
1250 if not force:
1251 raise util.Abort(
1251 raise util.Abort(
1252 _("commit with new subrepo %s excluded") % s)
1252 _("commit with new subrepo %s excluded") % s)
1253 if wctx.sub(s).dirty(True):
1253 if wctx.sub(s).dirty(True):
1254 if not self.ui.configbool('ui', 'commitsubrepos'):
1254 if not self.ui.configbool('ui', 'commitsubrepos'):
1255 raise util.Abort(
1255 raise util.Abort(
1256 _("uncommitted changes in subrepo %s") % s,
1256 _("uncommitted changes in subrepo %s") % s,
1257 hint=_("use --subrepos for recursive commit"))
1257 hint=_("use --subrepos for recursive commit"))
1258 subs.append(s)
1258 subs.append(s)
1259 commitsubs.add(s)
1259 commitsubs.add(s)
1260 else:
1260 else:
1261 bs = wctx.sub(s).basestate()
1261 bs = wctx.sub(s).basestate()
1262 newstate[s] = (newstate[s][0], bs, newstate[s][2])
1262 newstate[s] = (newstate[s][0], bs, newstate[s][2])
1263 if oldstate.get(s, (None, None, None))[1] != bs:
1263 if oldstate.get(s, (None, None, None))[1] != bs:
1264 subs.append(s)
1264 subs.append(s)
1265
1265
1266 # check for removed subrepos
1266 # check for removed subrepos
1267 for p in wctx.parents():
1267 for p in wctx.parents():
1268 r = [s for s in p.substate if s not in newstate]
1268 r = [s for s in p.substate if s not in newstate]
1269 subs += [s for s in r if match(s)]
1269 subs += [s for s in r if match(s)]
1270 if subs:
1270 if subs:
1271 if (not match('.hgsub') and
1271 if (not match('.hgsub') and
1272 '.hgsub' in (wctx.modified() + wctx.added())):
1272 '.hgsub' in (wctx.modified() + wctx.added())):
1273 raise util.Abort(
1273 raise util.Abort(
1274 _("can't commit subrepos without .hgsub"))
1274 _("can't commit subrepos without .hgsub"))
1275 changes[0].insert(0, '.hgsubstate')
1275 changes[0].insert(0, '.hgsubstate')
1276
1276
1277 elif '.hgsub' in changes[2]:
1277 elif '.hgsub' in changes[2]:
1278 # clean up .hgsubstate when .hgsub is removed
1278 # clean up .hgsubstate when .hgsub is removed
1279 if ('.hgsubstate' in wctx and
1279 if ('.hgsubstate' in wctx and
1280 '.hgsubstate' not in changes[0] + changes[1] + changes[2]):
1280 '.hgsubstate' not in changes[0] + changes[1] + changes[2]):
1281 changes[2].insert(0, '.hgsubstate')
1281 changes[2].insert(0, '.hgsubstate')
1282
1282
1283 # make sure all explicit patterns are matched
1283 # make sure all explicit patterns are matched
1284 if not force and match.files():
1284 if not force and match.files():
1285 matched = set(changes[0] + changes[1] + changes[2])
1285 matched = set(changes[0] + changes[1] + changes[2])
1286
1286
1287 for f in match.files():
1287 for f in match.files():
1288 f = self.dirstate.normalize(f)
1288 f = self.dirstate.normalize(f)
1289 if f == '.' or f in matched or f in wctx.substate:
1289 if f == '.' or f in matched or f in wctx.substate:
1290 continue
1290 continue
1291 if f in changes[3]: # missing
1291 if f in changes[3]: # missing
1292 fail(f, _('file not found!'))
1292 fail(f, _('file not found!'))
1293 if f in vdirs: # visited directory
1293 if f in vdirs: # visited directory
1294 d = f + '/'
1294 d = f + '/'
1295 for mf in matched:
1295 for mf in matched:
1296 if mf.startswith(d):
1296 if mf.startswith(d):
1297 break
1297 break
1298 else:
1298 else:
1299 fail(f, _("no match under directory!"))
1299 fail(f, _("no match under directory!"))
1300 elif f not in self.dirstate:
1300 elif f not in self.dirstate:
1301 fail(f, _("file not tracked!"))
1301 fail(f, _("file not tracked!"))
1302
1302
1303 cctx = context.workingctx(self, text, user, date, extra, changes)
1303 cctx = context.workingctx(self, text, user, date, extra, changes)
1304
1304
1305 if (not force and not extra.get("close") and not merge
1305 if (not force and not extra.get("close") and not merge
1306 and not cctx.files()
1306 and not cctx.files()
1307 and wctx.branch() == wctx.p1().branch()):
1307 and wctx.branch() == wctx.p1().branch()):
1308 return None
1308 return None
1309
1309
1310 if merge and cctx.deleted():
1310 if merge and cctx.deleted():
1311 raise util.Abort(_("cannot commit merge with missing files"))
1311 raise util.Abort(_("cannot commit merge with missing files"))
1312
1312
1313 ms = mergemod.mergestate(self)
1313 ms = mergemod.mergestate(self)
1314 for f in changes[0]:
1314 for f in changes[0]:
1315 if f in ms and ms[f] == 'u':
1315 if f in ms and ms[f] == 'u':
1316 raise util.Abort(_("unresolved merge conflicts "
1316 raise util.Abort(_("unresolved merge conflicts "
1317 "(see hg help resolve)"))
1317 "(see hg help resolve)"))
1318
1318
1319 if editor:
1319 if editor:
1320 cctx._text = editor(self, cctx, subs)
1320 cctx._text = editor(self, cctx, subs)
1321 edited = (text != cctx._text)
1321 edited = (text != cctx._text)
1322
1322
1323 # Save commit message in case this transaction gets rolled back
1323 # Save commit message in case this transaction gets rolled back
1324 # (e.g. by a pretxncommit hook). Leave the content alone on
1324 # (e.g. by a pretxncommit hook). Leave the content alone on
1325 # the assumption that the user will use the same editor again.
1325 # the assumption that the user will use the same editor again.
1326 msgfn = self.savecommitmessage(cctx._text)
1326 msgfn = self.savecommitmessage(cctx._text)
1327
1327
1328 # commit subs and write new state
1328 # commit subs and write new state
1329 if subs:
1329 if subs:
1330 for s in sorted(commitsubs):
1330 for s in sorted(commitsubs):
1331 sub = wctx.sub(s)
1331 sub = wctx.sub(s)
1332 self.ui.status(_('committing subrepository %s\n') %
1332 self.ui.status(_('committing subrepository %s\n') %
1333 subrepo.subrelpath(sub))
1333 subrepo.subrelpath(sub))
1334 sr = sub.commit(cctx._text, user, date)
1334 sr = sub.commit(cctx._text, user, date)
1335 newstate[s] = (newstate[s][0], sr)
1335 newstate[s] = (newstate[s][0], sr)
1336 subrepo.writestate(self, newstate)
1336 subrepo.writestate(self, newstate)
1337
1337
1338 p1, p2 = self.dirstate.parents()
1338 p1, p2 = self.dirstate.parents()
1339 hookp1, hookp2 = hex(p1), (p2 != nullid and hex(p2) or '')
1339 hookp1, hookp2 = hex(p1), (p2 != nullid and hex(p2) or '')
1340 try:
1340 try:
1341 self.hook("precommit", throw=True, parent1=hookp1,
1341 self.hook("precommit", throw=True, parent1=hookp1,
1342 parent2=hookp2)
1342 parent2=hookp2)
1343 ret = self.commitctx(cctx, True)
1343 ret = self.commitctx(cctx, True)
1344 except: # re-raises
1344 except: # re-raises
1345 if edited:
1345 if edited:
1346 self.ui.write(
1346 self.ui.write(
1347 _('note: commit message saved in %s\n') % msgfn)
1347 _('note: commit message saved in %s\n') % msgfn)
1348 raise
1348 raise
1349
1349
1350 # update bookmarks, dirstate and mergestate
1350 # update bookmarks, dirstate and mergestate
1351 bookmarks.update(self, [p1, p2], ret)
1351 bookmarks.update(self, [p1, p2], ret)
1352 cctx.markcommitted(ret)
1352 cctx.markcommitted(ret)
1353 ms.reset()
1353 ms.reset()
1354 finally:
1354 finally:
1355 wlock.release()
1355 wlock.release()
1356
1356
1357 def commithook(node=hex(ret), parent1=hookp1, parent2=hookp2):
1357 def commithook(node=hex(ret), parent1=hookp1, parent2=hookp2):
1358 self.hook("commit", node=node, parent1=parent1, parent2=parent2)
1358 self.hook("commit", node=node, parent1=parent1, parent2=parent2)
1359 self._afterlock(commithook)
1359 self._afterlock(commithook)
1360 return ret
1360 return ret
1361
1361
1362 @unfilteredmethod
1362 @unfilteredmethod
1363 def commitctx(self, ctx, error=False):
1363 def commitctx(self, ctx, error=False):
1364 """Add a new revision to current repository.
1364 """Add a new revision to current repository.
1365 Revision information is passed via the context argument.
1365 Revision information is passed via the context argument.
1366 """
1366 """
1367
1367
1368 tr = lock = None
1368 tr = lock = None
1369 removed = list(ctx.removed())
1369 removed = list(ctx.removed())
1370 p1, p2 = ctx.p1(), ctx.p2()
1370 p1, p2 = ctx.p1(), ctx.p2()
1371 user = ctx.user()
1371 user = ctx.user()
1372
1372
1373 lock = self.lock()
1373 lock = self.lock()
1374 try:
1374 try:
1375 tr = self.transaction("commit")
1375 tr = self.transaction("commit")
1376 trp = weakref.proxy(tr)
1376 trp = weakref.proxy(tr)
1377
1377
1378 if ctx.files():
1378 if ctx.files():
1379 m1 = p1.manifest().copy()
1379 m1 = p1.manifest().copy()
1380 m2 = p2.manifest()
1380 m2 = p2.manifest()
1381
1381
1382 # check in files
1382 # check in files
1383 new = {}
1383 new = {}
1384 changed = []
1384 changed = []
1385 linkrev = len(self)
1385 linkrev = len(self)
1386 for f in sorted(ctx.modified() + ctx.added()):
1386 for f in sorted(ctx.modified() + ctx.added()):
1387 self.ui.note(f + "\n")
1387 self.ui.note(f + "\n")
1388 try:
1388 try:
1389 fctx = ctx[f]
1389 fctx = ctx[f]
1390 new[f] = self._filecommit(fctx, m1, m2, linkrev, trp,
1390 new[f] = self._filecommit(fctx, m1, m2, linkrev, trp,
1391 changed)
1391 changed)
1392 m1.set(f, fctx.flags())
1392 m1.set(f, fctx.flags())
1393 except OSError, inst:
1393 except OSError, inst:
1394 self.ui.warn(_("trouble committing %s!\n") % f)
1394 self.ui.warn(_("trouble committing %s!\n") % f)
1395 raise
1395 raise
1396 except IOError, inst:
1396 except IOError, inst:
1397 errcode = getattr(inst, 'errno', errno.ENOENT)
1397 errcode = getattr(inst, 'errno', errno.ENOENT)
1398 if error or errcode and errcode != errno.ENOENT:
1398 if error or errcode and errcode != errno.ENOENT:
1399 self.ui.warn(_("trouble committing %s!\n") % f)
1399 self.ui.warn(_("trouble committing %s!\n") % f)
1400 raise
1400 raise
1401 else:
1401 else:
1402 removed.append(f)
1402 removed.append(f)
1403
1403
1404 # update manifest
1404 # update manifest
1405 m1.update(new)
1405 m1.update(new)
1406 removed = [f for f in sorted(removed) if f in m1 or f in m2]
1406 removed = [f for f in sorted(removed) if f in m1 or f in m2]
1407 drop = [f for f in removed if f in m1]
1407 drop = [f for f in removed if f in m1]
1408 for f in drop:
1408 for f in drop:
1409 del m1[f]
1409 del m1[f]
1410 mn = self.manifest.add(m1, trp, linkrev, p1.manifestnode(),
1410 mn = self.manifest.add(m1, trp, linkrev, p1.manifestnode(),
1411 p2.manifestnode(), (new, drop))
1411 p2.manifestnode(), (new, drop))
1412 files = changed + removed
1412 files = changed + removed
1413 else:
1413 else:
1414 mn = p1.manifestnode()
1414 mn = p1.manifestnode()
1415 files = []
1415 files = []
1416
1416
1417 # update changelog
1417 # update changelog
1418 self.changelog.delayupdate()
1418 self.changelog.delayupdate()
1419 n = self.changelog.add(mn, files, ctx.description(),
1419 n = self.changelog.add(mn, files, ctx.description(),
1420 trp, p1.node(), p2.node(),
1420 trp, p1.node(), p2.node(),
1421 user, ctx.date(), ctx.extra().copy())
1421 user, ctx.date(), ctx.extra().copy())
1422 p = lambda: self.changelog.writepending() and self.root or ""
1422 p = lambda: self.changelog.writepending() and self.root or ""
1423 xp1, xp2 = p1.hex(), p2 and p2.hex() or ''
1423 xp1, xp2 = p1.hex(), p2 and p2.hex() or ''
1424 self.hook('pretxncommit', throw=True, node=hex(n), parent1=xp1,
1424 self.hook('pretxncommit', throw=True, node=hex(n), parent1=xp1,
1425 parent2=xp2, pending=p)
1425 parent2=xp2, pending=p)
1426 self.changelog.finalize(trp)
1426 self.changelog.finalize(trp)
1427 # set the new commit is proper phase
1427 # set the new commit is proper phase
1428 targetphase = subrepo.newcommitphase(self.ui, ctx)
1428 targetphase = subrepo.newcommitphase(self.ui, ctx)
1429 if targetphase:
1429 if targetphase:
1430 # retract boundary do not alter parent changeset.
1430 # retract boundary do not alter parent changeset.
1431 # if a parent have higher the resulting phase will
1431 # if a parent have higher the resulting phase will
1432 # be compliant anyway
1432 # be compliant anyway
1433 #
1433 #
1434 # if minimal phase was 0 we don't need to retract anything
1434 # if minimal phase was 0 we don't need to retract anything
1435 phases.retractboundary(self, targetphase, [n])
1435 phases.retractboundary(self, targetphase, [n])
1436 tr.close()
1436 tr.close()
1437 branchmap.updatecache(self.filtered('served'))
1437 branchmap.updatecache(self.filtered('served'))
1438 return n
1438 return n
1439 finally:
1439 finally:
1440 if tr:
1440 if tr:
1441 tr.release()
1441 tr.release()
1442 lock.release()
1442 lock.release()
1443
1443
1444 @unfilteredmethod
1444 @unfilteredmethod
1445 def destroying(self):
1445 def destroying(self):
1446 '''Inform the repository that nodes are about to be destroyed.
1446 '''Inform the repository that nodes are about to be destroyed.
1447 Intended for use by strip and rollback, so there's a common
1447 Intended for use by strip and rollback, so there's a common
1448 place for anything that has to be done before destroying history.
1448 place for anything that has to be done before destroying history.
1449
1449
1450 This is mostly useful for saving state that is in memory and waiting
1450 This is mostly useful for saving state that is in memory and waiting
1451 to be flushed when the current lock is released. Because a call to
1451 to be flushed when the current lock is released. Because a call to
1452 destroyed is imminent, the repo will be invalidated causing those
1452 destroyed is imminent, the repo will be invalidated causing those
1453 changes to stay in memory (waiting for the next unlock), or vanish
1453 changes to stay in memory (waiting for the next unlock), or vanish
1454 completely.
1454 completely.
1455 '''
1455 '''
1456 # When using the same lock to commit and strip, the phasecache is left
1456 # When using the same lock to commit and strip, the phasecache is left
1457 # dirty after committing. Then when we strip, the repo is invalidated,
1457 # dirty after committing. Then when we strip, the repo is invalidated,
1458 # causing those changes to disappear.
1458 # causing those changes to disappear.
1459 if '_phasecache' in vars(self):
1459 if '_phasecache' in vars(self):
1460 self._phasecache.write()
1460 self._phasecache.write()
1461
1461
1462 @unfilteredmethod
1462 @unfilteredmethod
1463 def destroyed(self):
1463 def destroyed(self):
1464 '''Inform the repository that nodes have been destroyed.
1464 '''Inform the repository that nodes have been destroyed.
1465 Intended for use by strip and rollback, so there's a common
1465 Intended for use by strip and rollback, so there's a common
1466 place for anything that has to be done after destroying history.
1466 place for anything that has to be done after destroying history.
1467 '''
1467 '''
1468 # When one tries to:
1468 # When one tries to:
1469 # 1) destroy nodes thus calling this method (e.g. strip)
1469 # 1) destroy nodes thus calling this method (e.g. strip)
1470 # 2) use phasecache somewhere (e.g. commit)
1470 # 2) use phasecache somewhere (e.g. commit)
1471 #
1471 #
1472 # then 2) will fail because the phasecache contains nodes that were
1472 # then 2) will fail because the phasecache contains nodes that were
1473 # removed. We can either remove phasecache from the filecache,
1473 # removed. We can either remove phasecache from the filecache,
1474 # causing it to reload next time it is accessed, or simply filter
1474 # causing it to reload next time it is accessed, or simply filter
1475 # the removed nodes now and write the updated cache.
1475 # the removed nodes now and write the updated cache.
1476 self._phasecache.filterunknown(self)
1476 self._phasecache.filterunknown(self)
1477 self._phasecache.write()
1477 self._phasecache.write()
1478
1478
1479 # update the 'served' branch cache to help read only server process
1479 # update the 'served' branch cache to help read only server process
1480 # Thanks to branchcache collaboration this is done from the nearest
1480 # Thanks to branchcache collaboration this is done from the nearest
1481 # filtered subset and it is expected to be fast.
1481 # filtered subset and it is expected to be fast.
1482 branchmap.updatecache(self.filtered('served'))
1482 branchmap.updatecache(self.filtered('served'))
1483
1483
1484 # Ensure the persistent tag cache is updated. Doing it now
1484 # Ensure the persistent tag cache is updated. Doing it now
1485 # means that the tag cache only has to worry about destroyed
1485 # means that the tag cache only has to worry about destroyed
1486 # heads immediately after a strip/rollback. That in turn
1486 # heads immediately after a strip/rollback. That in turn
1487 # guarantees that "cachetip == currenttip" (comparing both rev
1487 # guarantees that "cachetip == currenttip" (comparing both rev
1488 # and node) always means no nodes have been added or destroyed.
1488 # and node) always means no nodes have been added or destroyed.
1489
1489
1490 # XXX this is suboptimal when qrefresh'ing: we strip the current
1490 # XXX this is suboptimal when qrefresh'ing: we strip the current
1491 # head, refresh the tag cache, then immediately add a new head.
1491 # head, refresh the tag cache, then immediately add a new head.
1492 # But I think doing it this way is necessary for the "instant
1492 # But I think doing it this way is necessary for the "instant
1493 # tag cache retrieval" case to work.
1493 # tag cache retrieval" case to work.
1494 self.invalidate()
1494 self.invalidate()
1495
1495
1496 def walk(self, match, node=None):
1496 def walk(self, match, node=None):
1497 '''
1497 '''
1498 walk recursively through the directory tree or a given
1498 walk recursively through the directory tree or a given
1499 changeset, finding all files matched by the match
1499 changeset, finding all files matched by the match
1500 function
1500 function
1501 '''
1501 '''
1502 return self[node].walk(match)
1502 return self[node].walk(match)
1503
1503
1504 def status(self, node1='.', node2=None, match=None,
1504 def status(self, node1='.', node2=None, match=None,
1505 ignored=False, clean=False, unknown=False,
1505 ignored=False, clean=False, unknown=False,
1506 listsubrepos=False):
1506 listsubrepos=False):
1507 """return status of files between two nodes or node and working
1507 """return status of files between two nodes or node and working
1508 directory.
1508 directory.
1509
1509
1510 If node1 is None, use the first dirstate parent instead.
1510 If node1 is None, use the first dirstate parent instead.
1511 If node2 is None, compare node1 with working directory.
1511 If node2 is None, compare node1 with working directory.
1512 """
1512 """
1513
1513
1514 def mfmatches(ctx):
1514 def mfmatches(ctx):
1515 mf = ctx.manifest().copy()
1515 mf = ctx.manifest().copy()
1516 if match.always():
1516 if match.always():
1517 return mf
1517 return mf
1518 for fn in mf.keys():
1518 for fn in mf.keys():
1519 if not match(fn):
1519 if not match(fn):
1520 del mf[fn]
1520 del mf[fn]
1521 return mf
1521 return mf
1522
1522
1523 ctx1 = self[node1]
1523 ctx1 = self[node1]
1524 ctx2 = self[node2]
1524 ctx2 = self[node2]
1525
1525
1526 working = ctx2.rev() is None
1526 working = ctx2.rev() is None
1527 parentworking = working and ctx1 == self['.']
1527 parentworking = working and ctx1 == self['.']
1528 match = match or matchmod.always(self.root, self.getcwd())
1528 match = match or matchmod.always(self.root, self.getcwd())
1529 listignored, listclean, listunknown = ignored, clean, unknown
1529 listignored, listclean, listunknown = ignored, clean, unknown
1530
1530
1531 # load earliest manifest first for caching reasons
1531 # load earliest manifest first for caching reasons
1532 if not working and ctx2.rev() < ctx1.rev():
1532 if not working and ctx2.rev() < ctx1.rev():
1533 ctx2.manifest()
1533 ctx2.manifest()
1534
1534
1535 if not parentworking:
1535 if not parentworking:
1536 def bad(f, msg):
1536 def bad(f, msg):
1537 # 'f' may be a directory pattern from 'match.files()',
1537 # 'f' may be a directory pattern from 'match.files()',
1538 # so 'f not in ctx1' is not enough
1538 # so 'f not in ctx1' is not enough
1539 if f not in ctx1 and f not in ctx1.dirs():
1539 if f not in ctx1 and f not in ctx1.dirs():
1540 self.ui.warn('%s: %s\n' % (self.dirstate.pathto(f), msg))
1540 self.ui.warn('%s: %s\n' % (self.dirstate.pathto(f), msg))
1541 match.bad = bad
1541 match.bad = bad
1542
1542
1543 if working: # we need to scan the working dir
1543 if working: # we need to scan the working dir
1544 subrepos = []
1544 subrepos = []
1545 if '.hgsub' in self.dirstate:
1545 if '.hgsub' in self.dirstate:
1546 subrepos = sorted(ctx2.substate)
1546 subrepos = sorted(ctx2.substate)
1547 s = self.dirstate.status(match, subrepos, listignored,
1547 s = self.dirstate.status(match, subrepos, listignored,
1548 listclean, listunknown)
1548 listclean, listunknown)
1549 cmp, modified, added, removed, deleted, unknown, ignored, clean = s
1549 cmp, modified, added, removed, deleted, unknown, ignored, clean = s
1550
1550
1551 # check for any possibly clean files
1551 # check for any possibly clean files
1552 if parentworking and cmp:
1552 if parentworking and cmp:
1553 fixup = []
1553 modified2, fixup = ctx2._checklookup(cmp)
1554 # do a full compare of any files that might have changed
1554 modified += modified2
1555 for f in sorted(cmp):
1556 if (f not in ctx1 or ctx2.flags(f) != ctx1.flags(f)
1557 or ctx1[f].cmp(ctx2[f])):
1558 modified.append(f)
1559 else:
1560 fixup.append(f)
1561
1555
1562 # update dirstate for files that are actually clean
1556 # update dirstate for files that are actually clean
1563 if fixup:
1557 if fixup and listclean:
1564 if listclean:
1558 clean += fixup
1565 clean += fixup
1566
1567 try:
1568 # updating the dirstate is optional
1569 # so we don't wait on the lock
1570 normal = self.dirstate.normal
1571 wlock = self.wlock(False)
1572 try:
1573 for f in fixup:
1574 normal(f)
1575 finally:
1576 wlock.release()
1577 except error.LockError:
1578 pass
1579
1559
1580 if not parentworking:
1560 if not parentworking:
1581 mf1 = mfmatches(ctx1)
1561 mf1 = mfmatches(ctx1)
1582 if working:
1562 if working:
1583 # we are comparing working dir against non-parent
1563 # we are comparing working dir against non-parent
1584 # generate a pseudo-manifest for the working dir
1564 # generate a pseudo-manifest for the working dir
1585 mf2 = mfmatches(self['.'])
1565 mf2 = mfmatches(self['.'])
1586 for f in cmp + modified + added:
1566 for f in cmp + modified + added:
1587 mf2[f] = None
1567 mf2[f] = None
1588 mf2.set(f, ctx2.flags(f))
1568 mf2.set(f, ctx2.flags(f))
1589 for f in removed:
1569 for f in removed:
1590 if f in mf2:
1570 if f in mf2:
1591 del mf2[f]
1571 del mf2[f]
1592 else:
1572 else:
1593 # we are comparing two revisions
1573 # we are comparing two revisions
1594 deleted, unknown, ignored = [], [], []
1574 deleted, unknown, ignored = [], [], []
1595 mf2 = mfmatches(ctx2)
1575 mf2 = mfmatches(ctx2)
1596
1576
1597 modified, added, clean = [], [], []
1577 modified, added, clean = [], [], []
1598 withflags = mf1.withflags() | mf2.withflags()
1578 withflags = mf1.withflags() | mf2.withflags()
1599 for fn, mf2node in mf2.iteritems():
1579 for fn, mf2node in mf2.iteritems():
1600 if fn in mf1:
1580 if fn in mf1:
1601 if (fn not in deleted and
1581 if (fn not in deleted and
1602 ((fn in withflags and mf1.flags(fn) != mf2.flags(fn)) or
1582 ((fn in withflags and mf1.flags(fn) != mf2.flags(fn)) or
1603 (mf1[fn] != mf2node and
1583 (mf1[fn] != mf2node and
1604 (mf2node or ctx1[fn].cmp(ctx2[fn]))))):
1584 (mf2node or ctx1[fn].cmp(ctx2[fn]))))):
1605 modified.append(fn)
1585 modified.append(fn)
1606 elif listclean:
1586 elif listclean:
1607 clean.append(fn)
1587 clean.append(fn)
1608 del mf1[fn]
1588 del mf1[fn]
1609 elif fn not in deleted:
1589 elif fn not in deleted:
1610 added.append(fn)
1590 added.append(fn)
1611 removed = mf1.keys()
1591 removed = mf1.keys()
1612
1592
1613 if working:
1593 if working:
1614 modified = ctx2._filtersuspectsymlink(modified)
1594 modified = ctx2._filtersuspectsymlink(modified)
1615
1595
1616 r = modified, added, removed, deleted, unknown, ignored, clean
1596 r = modified, added, removed, deleted, unknown, ignored, clean
1617
1597
1618 if listsubrepos:
1598 if listsubrepos:
1619 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
1599 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
1620 if working:
1600 if working:
1621 rev2 = None
1601 rev2 = None
1622 else:
1602 else:
1623 rev2 = ctx2.substate[subpath][1]
1603 rev2 = ctx2.substate[subpath][1]
1624 try:
1604 try:
1625 submatch = matchmod.narrowmatcher(subpath, match)
1605 submatch = matchmod.narrowmatcher(subpath, match)
1626 s = sub.status(rev2, match=submatch, ignored=listignored,
1606 s = sub.status(rev2, match=submatch, ignored=listignored,
1627 clean=listclean, unknown=listunknown,
1607 clean=listclean, unknown=listunknown,
1628 listsubrepos=True)
1608 listsubrepos=True)
1629 for rfiles, sfiles in zip(r, s):
1609 for rfiles, sfiles in zip(r, s):
1630 rfiles.extend("%s/%s" % (subpath, f) for f in sfiles)
1610 rfiles.extend("%s/%s" % (subpath, f) for f in sfiles)
1631 except error.LookupError:
1611 except error.LookupError:
1632 self.ui.status(_("skipping missing subrepository: %s\n")
1612 self.ui.status(_("skipping missing subrepository: %s\n")
1633 % subpath)
1613 % subpath)
1634
1614
1635 for l in r:
1615 for l in r:
1636 l.sort()
1616 l.sort()
1637 return r
1617 return r
1638
1618
1639 def heads(self, start=None):
1619 def heads(self, start=None):
1640 heads = self.changelog.heads(start)
1620 heads = self.changelog.heads(start)
1641 # sort the output in rev descending order
1621 # sort the output in rev descending order
1642 return sorted(heads, key=self.changelog.rev, reverse=True)
1622 return sorted(heads, key=self.changelog.rev, reverse=True)
1643
1623
1644 def branchheads(self, branch=None, start=None, closed=False):
1624 def branchheads(self, branch=None, start=None, closed=False):
1645 '''return a (possibly filtered) list of heads for the given branch
1625 '''return a (possibly filtered) list of heads for the given branch
1646
1626
1647 Heads are returned in topological order, from newest to oldest.
1627 Heads are returned in topological order, from newest to oldest.
1648 If branch is None, use the dirstate branch.
1628 If branch is None, use the dirstate branch.
1649 If start is not None, return only heads reachable from start.
1629 If start is not None, return only heads reachable from start.
1650 If closed is True, return heads that are marked as closed as well.
1630 If closed is True, return heads that are marked as closed as well.
1651 '''
1631 '''
1652 if branch is None:
1632 if branch is None:
1653 branch = self[None].branch()
1633 branch = self[None].branch()
1654 branches = self.branchmap()
1634 branches = self.branchmap()
1655 if branch not in branches:
1635 if branch not in branches:
1656 return []
1636 return []
1657 # the cache returns heads ordered lowest to highest
1637 # the cache returns heads ordered lowest to highest
1658 bheads = list(reversed(branches.branchheads(branch, closed=closed)))
1638 bheads = list(reversed(branches.branchheads(branch, closed=closed)))
1659 if start is not None:
1639 if start is not None:
1660 # filter out the heads that cannot be reached from startrev
1640 # filter out the heads that cannot be reached from startrev
1661 fbheads = set(self.changelog.nodesbetween([start], bheads)[2])
1641 fbheads = set(self.changelog.nodesbetween([start], bheads)[2])
1662 bheads = [h for h in bheads if h in fbheads]
1642 bheads = [h for h in bheads if h in fbheads]
1663 return bheads
1643 return bheads
1664
1644
1665 def branches(self, nodes):
1645 def branches(self, nodes):
1666 if not nodes:
1646 if not nodes:
1667 nodes = [self.changelog.tip()]
1647 nodes = [self.changelog.tip()]
1668 b = []
1648 b = []
1669 for n in nodes:
1649 for n in nodes:
1670 t = n
1650 t = n
1671 while True:
1651 while True:
1672 p = self.changelog.parents(n)
1652 p = self.changelog.parents(n)
1673 if p[1] != nullid or p[0] == nullid:
1653 if p[1] != nullid or p[0] == nullid:
1674 b.append((t, n, p[0], p[1]))
1654 b.append((t, n, p[0], p[1]))
1675 break
1655 break
1676 n = p[0]
1656 n = p[0]
1677 return b
1657 return b
1678
1658
1679 def between(self, pairs):
1659 def between(self, pairs):
1680 r = []
1660 r = []
1681
1661
1682 for top, bottom in pairs:
1662 for top, bottom in pairs:
1683 n, l, i = top, [], 0
1663 n, l, i = top, [], 0
1684 f = 1
1664 f = 1
1685
1665
1686 while n != bottom and n != nullid:
1666 while n != bottom and n != nullid:
1687 p = self.changelog.parents(n)[0]
1667 p = self.changelog.parents(n)[0]
1688 if i == f:
1668 if i == f:
1689 l.append(n)
1669 l.append(n)
1690 f = f * 2
1670 f = f * 2
1691 n = p
1671 n = p
1692 i += 1
1672 i += 1
1693
1673
1694 r.append(l)
1674 r.append(l)
1695
1675
1696 return r
1676 return r
1697
1677
1698 def pull(self, remote, heads=None, force=False):
1678 def pull(self, remote, heads=None, force=False):
1699 return exchange.pull (self, remote, heads, force)
1679 return exchange.pull (self, remote, heads, force)
1700
1680
1701 def checkpush(self, pushop):
1681 def checkpush(self, pushop):
1702 """Extensions can override this function if additional checks have
1682 """Extensions can override this function if additional checks have
1703 to be performed before pushing, or call it if they override push
1683 to be performed before pushing, or call it if they override push
1704 command.
1684 command.
1705 """
1685 """
1706 pass
1686 pass
1707
1687
1708 @unfilteredpropertycache
1688 @unfilteredpropertycache
1709 def prepushoutgoinghooks(self):
1689 def prepushoutgoinghooks(self):
1710 """Return util.hooks consists of "(repo, remote, outgoing)"
1690 """Return util.hooks consists of "(repo, remote, outgoing)"
1711 functions, which are called before pushing changesets.
1691 functions, which are called before pushing changesets.
1712 """
1692 """
1713 return util.hooks()
1693 return util.hooks()
1714
1694
1715 def push(self, remote, force=False, revs=None, newbranch=False):
1695 def push(self, remote, force=False, revs=None, newbranch=False):
1716 return exchange.push(self, remote, force, revs, newbranch)
1696 return exchange.push(self, remote, force, revs, newbranch)
1717
1697
1718 def stream_in(self, remote, requirements):
1698 def stream_in(self, remote, requirements):
1719 lock = self.lock()
1699 lock = self.lock()
1720 try:
1700 try:
1721 # Save remote branchmap. We will use it later
1701 # Save remote branchmap. We will use it later
1722 # to speed up branchcache creation
1702 # to speed up branchcache creation
1723 rbranchmap = None
1703 rbranchmap = None
1724 if remote.capable("branchmap"):
1704 if remote.capable("branchmap"):
1725 rbranchmap = remote.branchmap()
1705 rbranchmap = remote.branchmap()
1726
1706
1727 fp = remote.stream_out()
1707 fp = remote.stream_out()
1728 l = fp.readline()
1708 l = fp.readline()
1729 try:
1709 try:
1730 resp = int(l)
1710 resp = int(l)
1731 except ValueError:
1711 except ValueError:
1732 raise error.ResponseError(
1712 raise error.ResponseError(
1733 _('unexpected response from remote server:'), l)
1713 _('unexpected response from remote server:'), l)
1734 if resp == 1:
1714 if resp == 1:
1735 raise util.Abort(_('operation forbidden by server'))
1715 raise util.Abort(_('operation forbidden by server'))
1736 elif resp == 2:
1716 elif resp == 2:
1737 raise util.Abort(_('locking the remote repository failed'))
1717 raise util.Abort(_('locking the remote repository failed'))
1738 elif resp != 0:
1718 elif resp != 0:
1739 raise util.Abort(_('the server sent an unknown error code'))
1719 raise util.Abort(_('the server sent an unknown error code'))
1740 self.ui.status(_('streaming all changes\n'))
1720 self.ui.status(_('streaming all changes\n'))
1741 l = fp.readline()
1721 l = fp.readline()
1742 try:
1722 try:
1743 total_files, total_bytes = map(int, l.split(' ', 1))
1723 total_files, total_bytes = map(int, l.split(' ', 1))
1744 except (ValueError, TypeError):
1724 except (ValueError, TypeError):
1745 raise error.ResponseError(
1725 raise error.ResponseError(
1746 _('unexpected response from remote server:'), l)
1726 _('unexpected response from remote server:'), l)
1747 self.ui.status(_('%d files to transfer, %s of data\n') %
1727 self.ui.status(_('%d files to transfer, %s of data\n') %
1748 (total_files, util.bytecount(total_bytes)))
1728 (total_files, util.bytecount(total_bytes)))
1749 handled_bytes = 0
1729 handled_bytes = 0
1750 self.ui.progress(_('clone'), 0, total=total_bytes)
1730 self.ui.progress(_('clone'), 0, total=total_bytes)
1751 start = time.time()
1731 start = time.time()
1752
1732
1753 tr = self.transaction(_('clone'))
1733 tr = self.transaction(_('clone'))
1754 try:
1734 try:
1755 for i in xrange(total_files):
1735 for i in xrange(total_files):
1756 # XXX doesn't support '\n' or '\r' in filenames
1736 # XXX doesn't support '\n' or '\r' in filenames
1757 l = fp.readline()
1737 l = fp.readline()
1758 try:
1738 try:
1759 name, size = l.split('\0', 1)
1739 name, size = l.split('\0', 1)
1760 size = int(size)
1740 size = int(size)
1761 except (ValueError, TypeError):
1741 except (ValueError, TypeError):
1762 raise error.ResponseError(
1742 raise error.ResponseError(
1763 _('unexpected response from remote server:'), l)
1743 _('unexpected response from remote server:'), l)
1764 if self.ui.debugflag:
1744 if self.ui.debugflag:
1765 self.ui.debug('adding %s (%s)\n' %
1745 self.ui.debug('adding %s (%s)\n' %
1766 (name, util.bytecount(size)))
1746 (name, util.bytecount(size)))
1767 # for backwards compat, name was partially encoded
1747 # for backwards compat, name was partially encoded
1768 ofp = self.sopener(store.decodedir(name), 'w')
1748 ofp = self.sopener(store.decodedir(name), 'w')
1769 for chunk in util.filechunkiter(fp, limit=size):
1749 for chunk in util.filechunkiter(fp, limit=size):
1770 handled_bytes += len(chunk)
1750 handled_bytes += len(chunk)
1771 self.ui.progress(_('clone'), handled_bytes,
1751 self.ui.progress(_('clone'), handled_bytes,
1772 total=total_bytes)
1752 total=total_bytes)
1773 ofp.write(chunk)
1753 ofp.write(chunk)
1774 ofp.close()
1754 ofp.close()
1775 tr.close()
1755 tr.close()
1776 finally:
1756 finally:
1777 tr.release()
1757 tr.release()
1778
1758
1779 # Writing straight to files circumvented the inmemory caches
1759 # Writing straight to files circumvented the inmemory caches
1780 self.invalidate()
1760 self.invalidate()
1781
1761
1782 elapsed = time.time() - start
1762 elapsed = time.time() - start
1783 if elapsed <= 0:
1763 if elapsed <= 0:
1784 elapsed = 0.001
1764 elapsed = 0.001
1785 self.ui.progress(_('clone'), None)
1765 self.ui.progress(_('clone'), None)
1786 self.ui.status(_('transferred %s in %.1f seconds (%s/sec)\n') %
1766 self.ui.status(_('transferred %s in %.1f seconds (%s/sec)\n') %
1787 (util.bytecount(total_bytes), elapsed,
1767 (util.bytecount(total_bytes), elapsed,
1788 util.bytecount(total_bytes / elapsed)))
1768 util.bytecount(total_bytes / elapsed)))
1789
1769
1790 # new requirements = old non-format requirements +
1770 # new requirements = old non-format requirements +
1791 # new format-related
1771 # new format-related
1792 # requirements from the streamed-in repository
1772 # requirements from the streamed-in repository
1793 requirements.update(set(self.requirements) - self.supportedformats)
1773 requirements.update(set(self.requirements) - self.supportedformats)
1794 self._applyrequirements(requirements)
1774 self._applyrequirements(requirements)
1795 self._writerequirements()
1775 self._writerequirements()
1796
1776
1797 if rbranchmap:
1777 if rbranchmap:
1798 rbheads = []
1778 rbheads = []
1799 for bheads in rbranchmap.itervalues():
1779 for bheads in rbranchmap.itervalues():
1800 rbheads.extend(bheads)
1780 rbheads.extend(bheads)
1801
1781
1802 if rbheads:
1782 if rbheads:
1803 rtiprev = max((int(self.changelog.rev(node))
1783 rtiprev = max((int(self.changelog.rev(node))
1804 for node in rbheads))
1784 for node in rbheads))
1805 cache = branchmap.branchcache(rbranchmap,
1785 cache = branchmap.branchcache(rbranchmap,
1806 self[rtiprev].node(),
1786 self[rtiprev].node(),
1807 rtiprev)
1787 rtiprev)
1808 # Try to stick it as low as possible
1788 # Try to stick it as low as possible
1809 # filter above served are unlikely to be fetch from a clone
1789 # filter above served are unlikely to be fetch from a clone
1810 for candidate in ('base', 'immutable', 'served'):
1790 for candidate in ('base', 'immutable', 'served'):
1811 rview = self.filtered(candidate)
1791 rview = self.filtered(candidate)
1812 if cache.validfor(rview):
1792 if cache.validfor(rview):
1813 self._branchcaches[candidate] = cache
1793 self._branchcaches[candidate] = cache
1814 cache.write(rview)
1794 cache.write(rview)
1815 break
1795 break
1816 self.invalidate()
1796 self.invalidate()
1817 return len(self.heads()) + 1
1797 return len(self.heads()) + 1
1818 finally:
1798 finally:
1819 lock.release()
1799 lock.release()
1820
1800
1821 def clone(self, remote, heads=[], stream=False):
1801 def clone(self, remote, heads=[], stream=False):
1822 '''clone remote repository.
1802 '''clone remote repository.
1823
1803
1824 keyword arguments:
1804 keyword arguments:
1825 heads: list of revs to clone (forces use of pull)
1805 heads: list of revs to clone (forces use of pull)
1826 stream: use streaming clone if possible'''
1806 stream: use streaming clone if possible'''
1827
1807
1828 # now, all clients that can request uncompressed clones can
1808 # now, all clients that can request uncompressed clones can
1829 # read repo formats supported by all servers that can serve
1809 # read repo formats supported by all servers that can serve
1830 # them.
1810 # them.
1831
1811
1832 # if revlog format changes, client will have to check version
1812 # if revlog format changes, client will have to check version
1833 # and format flags on "stream" capability, and use
1813 # and format flags on "stream" capability, and use
1834 # uncompressed only if compatible.
1814 # uncompressed only if compatible.
1835
1815
1836 if not stream:
1816 if not stream:
1837 # if the server explicitly prefers to stream (for fast LANs)
1817 # if the server explicitly prefers to stream (for fast LANs)
1838 stream = remote.capable('stream-preferred')
1818 stream = remote.capable('stream-preferred')
1839
1819
1840 if stream and not heads:
1820 if stream and not heads:
1841 # 'stream' means remote revlog format is revlogv1 only
1821 # 'stream' means remote revlog format is revlogv1 only
1842 if remote.capable('stream'):
1822 if remote.capable('stream'):
1843 return self.stream_in(remote, set(('revlogv1',)))
1823 return self.stream_in(remote, set(('revlogv1',)))
1844 # otherwise, 'streamreqs' contains the remote revlog format
1824 # otherwise, 'streamreqs' contains the remote revlog format
1845 streamreqs = remote.capable('streamreqs')
1825 streamreqs = remote.capable('streamreqs')
1846 if streamreqs:
1826 if streamreqs:
1847 streamreqs = set(streamreqs.split(','))
1827 streamreqs = set(streamreqs.split(','))
1848 # if we support it, stream in and adjust our requirements
1828 # if we support it, stream in and adjust our requirements
1849 if not streamreqs - self.supportedformats:
1829 if not streamreqs - self.supportedformats:
1850 return self.stream_in(remote, streamreqs)
1830 return self.stream_in(remote, streamreqs)
1851 return self.pull(remote, heads)
1831 return self.pull(remote, heads)
1852
1832
1853 def pushkey(self, namespace, key, old, new):
1833 def pushkey(self, namespace, key, old, new):
1854 self.hook('prepushkey', throw=True, namespace=namespace, key=key,
1834 self.hook('prepushkey', throw=True, namespace=namespace, key=key,
1855 old=old, new=new)
1835 old=old, new=new)
1856 self.ui.debug('pushing key for "%s:%s"\n' % (namespace, key))
1836 self.ui.debug('pushing key for "%s:%s"\n' % (namespace, key))
1857 ret = pushkey.push(self, namespace, key, old, new)
1837 ret = pushkey.push(self, namespace, key, old, new)
1858 self.hook('pushkey', namespace=namespace, key=key, old=old, new=new,
1838 self.hook('pushkey', namespace=namespace, key=key, old=old, new=new,
1859 ret=ret)
1839 ret=ret)
1860 return ret
1840 return ret
1861
1841
1862 def listkeys(self, namespace):
1842 def listkeys(self, namespace):
1863 self.hook('prelistkeys', throw=True, namespace=namespace)
1843 self.hook('prelistkeys', throw=True, namespace=namespace)
1864 self.ui.debug('listing keys for "%s"\n' % namespace)
1844 self.ui.debug('listing keys for "%s"\n' % namespace)
1865 values = pushkey.list(self, namespace)
1845 values = pushkey.list(self, namespace)
1866 self.hook('listkeys', namespace=namespace, values=values)
1846 self.hook('listkeys', namespace=namespace, values=values)
1867 return values
1847 return values
1868
1848
1869 def debugwireargs(self, one, two, three=None, four=None, five=None):
1849 def debugwireargs(self, one, two, three=None, four=None, five=None):
1870 '''used to test argument passing over the wire'''
1850 '''used to test argument passing over the wire'''
1871 return "%s %s %s %s %s" % (one, two, three, four, five)
1851 return "%s %s %s %s %s" % (one, two, three, four, five)
1872
1852
1873 def savecommitmessage(self, text):
1853 def savecommitmessage(self, text):
1874 fp = self.opener('last-message.txt', 'wb')
1854 fp = self.opener('last-message.txt', 'wb')
1875 try:
1855 try:
1876 fp.write(text)
1856 fp.write(text)
1877 finally:
1857 finally:
1878 fp.close()
1858 fp.close()
1879 return self.pathto(fp.name[len(self.root) + 1:])
1859 return self.pathto(fp.name[len(self.root) + 1:])
1880
1860
1881 # used to avoid circular references so destructors work
1861 # used to avoid circular references so destructors work
1882 def aftertrans(files):
1862 def aftertrans(files):
1883 renamefiles = [tuple(t) for t in files]
1863 renamefiles = [tuple(t) for t in files]
1884 def a():
1864 def a():
1885 for vfs, src, dest in renamefiles:
1865 for vfs, src, dest in renamefiles:
1886 try:
1866 try:
1887 vfs.rename(src, dest)
1867 vfs.rename(src, dest)
1888 except OSError: # journal file does not yet exist
1868 except OSError: # journal file does not yet exist
1889 pass
1869 pass
1890 return a
1870 return a
1891
1871
1892 def undoname(fn):
1872 def undoname(fn):
1893 base, name = os.path.split(fn)
1873 base, name = os.path.split(fn)
1894 assert name.startswith('journal')
1874 assert name.startswith('journal')
1895 return os.path.join(base, name.replace('journal', 'undo', 1))
1875 return os.path.join(base, name.replace('journal', 'undo', 1))
1896
1876
1897 def instance(ui, path, create):
1877 def instance(ui, path, create):
1898 return localrepository(ui, util.urllocalpath(path), create)
1878 return localrepository(ui, util.urllocalpath(path), create)
1899
1879
1900 def islocal(path):
1880 def islocal(path):
1901 return True
1881 return True
General Comments 0
You need to be logged in to leave comments. Login now