##// END OF EJS Templates
commit: factor out post-commit cleanup into workingctx...
David Schleimer -
r18661:4fb92f14 default
parent child Browse files
Show More
@@ -1,1364 +1,1380 b''
1 # context.py - changeset and file context objects for mercurial
1 # context.py - changeset and file context objects for mercurial
2 #
2 #
3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from node import nullid, nullrev, short, hex, bin
8 from node import nullid, nullrev, short, hex, bin
9 from i18n import _
9 from i18n import _
10 import ancestor, mdiff, error, util, scmutil, subrepo, patch, encoding, phases
10 import ancestor, mdiff, error, util, scmutil, subrepo, patch, encoding, phases
11 import copies
11 import copies
12 import match as matchmod
12 import match as matchmod
13 import os, errno, stat
13 import os, errno, stat
14 import obsolete as obsmod
14 import obsolete as obsmod
15 import repoview
15 import repoview
16
16
17 propertycache = util.propertycache
17 propertycache = util.propertycache
18
18
19 class changectx(object):
19 class changectx(object):
20 """A changecontext object makes access to data related to a particular
20 """A changecontext object makes access to data related to a particular
21 changeset convenient."""
21 changeset convenient."""
22 def __init__(self, repo, changeid=''):
22 def __init__(self, repo, changeid=''):
23 """changeid is a revision number, node, or tag"""
23 """changeid is a revision number, node, or tag"""
24 if changeid == '':
24 if changeid == '':
25 changeid = '.'
25 changeid = '.'
26 self._repo = repo
26 self._repo = repo
27
27
28 if isinstance(changeid, int):
28 if isinstance(changeid, int):
29 try:
29 try:
30 self._node = repo.changelog.node(changeid)
30 self._node = repo.changelog.node(changeid)
31 except IndexError:
31 except IndexError:
32 raise error.RepoLookupError(
32 raise error.RepoLookupError(
33 _("unknown revision '%s'") % changeid)
33 _("unknown revision '%s'") % changeid)
34 self._rev = changeid
34 self._rev = changeid
35 return
35 return
36 if isinstance(changeid, long):
36 if isinstance(changeid, long):
37 changeid = str(changeid)
37 changeid = str(changeid)
38 if changeid == '.':
38 if changeid == '.':
39 self._node = repo.dirstate.p1()
39 self._node = repo.dirstate.p1()
40 self._rev = repo.changelog.rev(self._node)
40 self._rev = repo.changelog.rev(self._node)
41 return
41 return
42 if changeid == 'null':
42 if changeid == 'null':
43 self._node = nullid
43 self._node = nullid
44 self._rev = nullrev
44 self._rev = nullrev
45 return
45 return
46 if changeid == 'tip':
46 if changeid == 'tip':
47 self._node = repo.changelog.tip()
47 self._node = repo.changelog.tip()
48 self._rev = repo.changelog.rev(self._node)
48 self._rev = repo.changelog.rev(self._node)
49 return
49 return
50 if len(changeid) == 20:
50 if len(changeid) == 20:
51 try:
51 try:
52 self._node = changeid
52 self._node = changeid
53 self._rev = repo.changelog.rev(changeid)
53 self._rev = repo.changelog.rev(changeid)
54 return
54 return
55 except LookupError:
55 except LookupError:
56 pass
56 pass
57
57
58 try:
58 try:
59 r = int(changeid)
59 r = int(changeid)
60 if str(r) != changeid:
60 if str(r) != changeid:
61 raise ValueError
61 raise ValueError
62 l = len(repo.changelog)
62 l = len(repo.changelog)
63 if r < 0:
63 if r < 0:
64 r += l
64 r += l
65 if r < 0 or r >= l:
65 if r < 0 or r >= l:
66 raise ValueError
66 raise ValueError
67 self._rev = r
67 self._rev = r
68 self._node = repo.changelog.node(r)
68 self._node = repo.changelog.node(r)
69 return
69 return
70 except (ValueError, OverflowError, IndexError):
70 except (ValueError, OverflowError, IndexError):
71 pass
71 pass
72
72
73 if len(changeid) == 40:
73 if len(changeid) == 40:
74 try:
74 try:
75 self._node = bin(changeid)
75 self._node = bin(changeid)
76 self._rev = repo.changelog.rev(self._node)
76 self._rev = repo.changelog.rev(self._node)
77 return
77 return
78 except (TypeError, LookupError):
78 except (TypeError, LookupError):
79 pass
79 pass
80
80
81 if changeid in repo._bookmarks:
81 if changeid in repo._bookmarks:
82 self._node = repo._bookmarks[changeid]
82 self._node = repo._bookmarks[changeid]
83 self._rev = repo.changelog.rev(self._node)
83 self._rev = repo.changelog.rev(self._node)
84 return
84 return
85 if changeid in repo._tagscache.tags:
85 if changeid in repo._tagscache.tags:
86 self._node = repo._tagscache.tags[changeid]
86 self._node = repo._tagscache.tags[changeid]
87 self._rev = repo.changelog.rev(self._node)
87 self._rev = repo.changelog.rev(self._node)
88 return
88 return
89 try:
89 try:
90 self._node = repo.branchtip(changeid)
90 self._node = repo.branchtip(changeid)
91 self._rev = repo.changelog.rev(self._node)
91 self._rev = repo.changelog.rev(self._node)
92 return
92 return
93 except error.RepoLookupError:
93 except error.RepoLookupError:
94 pass
94 pass
95
95
96 self._node = repo.changelog._partialmatch(changeid)
96 self._node = repo.changelog._partialmatch(changeid)
97 if self._node is not None:
97 if self._node is not None:
98 self._rev = repo.changelog.rev(self._node)
98 self._rev = repo.changelog.rev(self._node)
99 return
99 return
100
100
101 # lookup failed
101 # lookup failed
102 # check if it might have come from damaged dirstate
102 # check if it might have come from damaged dirstate
103 #
103 #
104 # XXX we could avoid the unfiltered if we had a recognizable exception
104 # XXX we could avoid the unfiltered if we had a recognizable exception
105 # for filtered changeset access
105 # for filtered changeset access
106 if changeid in repo.unfiltered().dirstate.parents():
106 if changeid in repo.unfiltered().dirstate.parents():
107 raise error.Abort(_("working directory has unknown parent '%s'!")
107 raise error.Abort(_("working directory has unknown parent '%s'!")
108 % short(changeid))
108 % short(changeid))
109 try:
109 try:
110 if len(changeid) == 20:
110 if len(changeid) == 20:
111 changeid = hex(changeid)
111 changeid = hex(changeid)
112 except TypeError:
112 except TypeError:
113 pass
113 pass
114 raise error.RepoLookupError(
114 raise error.RepoLookupError(
115 _("unknown revision '%s'") % changeid)
115 _("unknown revision '%s'") % changeid)
116
116
117 def __str__(self):
117 def __str__(self):
118 return short(self.node())
118 return short(self.node())
119
119
120 def __int__(self):
120 def __int__(self):
121 return self.rev()
121 return self.rev()
122
122
123 def __repr__(self):
123 def __repr__(self):
124 return "<changectx %s>" % str(self)
124 return "<changectx %s>" % str(self)
125
125
126 def __hash__(self):
126 def __hash__(self):
127 try:
127 try:
128 return hash(self._rev)
128 return hash(self._rev)
129 except AttributeError:
129 except AttributeError:
130 return id(self)
130 return id(self)
131
131
132 def __eq__(self, other):
132 def __eq__(self, other):
133 try:
133 try:
134 return self._rev == other._rev
134 return self._rev == other._rev
135 except AttributeError:
135 except AttributeError:
136 return False
136 return False
137
137
138 def __ne__(self, other):
138 def __ne__(self, other):
139 return not (self == other)
139 return not (self == other)
140
140
141 def __nonzero__(self):
141 def __nonzero__(self):
142 return self._rev != nullrev
142 return self._rev != nullrev
143
143
144 @propertycache
144 @propertycache
145 def _changeset(self):
145 def _changeset(self):
146 return self._repo.changelog.read(self.rev())
146 return self._repo.changelog.read(self.rev())
147
147
148 @propertycache
148 @propertycache
149 def _manifest(self):
149 def _manifest(self):
150 return self._repo.manifest.read(self._changeset[0])
150 return self._repo.manifest.read(self._changeset[0])
151
151
152 @propertycache
152 @propertycache
153 def _manifestdelta(self):
153 def _manifestdelta(self):
154 return self._repo.manifest.readdelta(self._changeset[0])
154 return self._repo.manifest.readdelta(self._changeset[0])
155
155
156 @propertycache
156 @propertycache
157 def _parents(self):
157 def _parents(self):
158 p = self._repo.changelog.parentrevs(self._rev)
158 p = self._repo.changelog.parentrevs(self._rev)
159 if p[1] == nullrev:
159 if p[1] == nullrev:
160 p = p[:-1]
160 p = p[:-1]
161 return [changectx(self._repo, x) for x in p]
161 return [changectx(self._repo, x) for x in p]
162
162
163 @propertycache
163 @propertycache
164 def substate(self):
164 def substate(self):
165 return subrepo.state(self, self._repo.ui)
165 return subrepo.state(self, self._repo.ui)
166
166
167 def __contains__(self, key):
167 def __contains__(self, key):
168 return key in self._manifest
168 return key in self._manifest
169
169
170 def __getitem__(self, key):
170 def __getitem__(self, key):
171 return self.filectx(key)
171 return self.filectx(key)
172
172
173 def __iter__(self):
173 def __iter__(self):
174 for f in sorted(self._manifest):
174 for f in sorted(self._manifest):
175 yield f
175 yield f
176
176
177 def changeset(self):
177 def changeset(self):
178 return self._changeset
178 return self._changeset
179 def manifest(self):
179 def manifest(self):
180 return self._manifest
180 return self._manifest
181 def manifestnode(self):
181 def manifestnode(self):
182 return self._changeset[0]
182 return self._changeset[0]
183
183
184 def rev(self):
184 def rev(self):
185 return self._rev
185 return self._rev
186 def node(self):
186 def node(self):
187 return self._node
187 return self._node
188 def hex(self):
188 def hex(self):
189 return hex(self._node)
189 return hex(self._node)
190 def user(self):
190 def user(self):
191 return self._changeset[1]
191 return self._changeset[1]
192 def date(self):
192 def date(self):
193 return self._changeset[2]
193 return self._changeset[2]
194 def files(self):
194 def files(self):
195 return self._changeset[3]
195 return self._changeset[3]
196 def description(self):
196 def description(self):
197 return self._changeset[4]
197 return self._changeset[4]
198 def branch(self):
198 def branch(self):
199 return encoding.tolocal(self._changeset[5].get("branch"))
199 return encoding.tolocal(self._changeset[5].get("branch"))
200 def closesbranch(self):
200 def closesbranch(self):
201 return 'close' in self._changeset[5]
201 return 'close' in self._changeset[5]
202 def extra(self):
202 def extra(self):
203 return self._changeset[5]
203 return self._changeset[5]
204 def tags(self):
204 def tags(self):
205 return self._repo.nodetags(self._node)
205 return self._repo.nodetags(self._node)
206 def bookmarks(self):
206 def bookmarks(self):
207 return self._repo.nodebookmarks(self._node)
207 return self._repo.nodebookmarks(self._node)
208 def phase(self):
208 def phase(self):
209 return self._repo._phasecache.phase(self._repo, self._rev)
209 return self._repo._phasecache.phase(self._repo, self._rev)
210 def phasestr(self):
210 def phasestr(self):
211 return phases.phasenames[self.phase()]
211 return phases.phasenames[self.phase()]
212 def mutable(self):
212 def mutable(self):
213 return self.phase() > phases.public
213 return self.phase() > phases.public
214 def hidden(self):
214 def hidden(self):
215 return self._rev in repoview.filterrevs(self._repo, 'visible')
215 return self._rev in repoview.filterrevs(self._repo, 'visible')
216
216
217 def parents(self):
217 def parents(self):
218 """return contexts for each parent changeset"""
218 """return contexts for each parent changeset"""
219 return self._parents
219 return self._parents
220
220
221 def p1(self):
221 def p1(self):
222 return self._parents[0]
222 return self._parents[0]
223
223
224 def p2(self):
224 def p2(self):
225 if len(self._parents) == 2:
225 if len(self._parents) == 2:
226 return self._parents[1]
226 return self._parents[1]
227 return changectx(self._repo, -1)
227 return changectx(self._repo, -1)
228
228
229 def children(self):
229 def children(self):
230 """return contexts for each child changeset"""
230 """return contexts for each child changeset"""
231 c = self._repo.changelog.children(self._node)
231 c = self._repo.changelog.children(self._node)
232 return [changectx(self._repo, x) for x in c]
232 return [changectx(self._repo, x) for x in c]
233
233
234 def ancestors(self):
234 def ancestors(self):
235 for a in self._repo.changelog.ancestors([self._rev]):
235 for a in self._repo.changelog.ancestors([self._rev]):
236 yield changectx(self._repo, a)
236 yield changectx(self._repo, a)
237
237
238 def descendants(self):
238 def descendants(self):
239 for d in self._repo.changelog.descendants([self._rev]):
239 for d in self._repo.changelog.descendants([self._rev]):
240 yield changectx(self._repo, d)
240 yield changectx(self._repo, d)
241
241
242 def obsolete(self):
242 def obsolete(self):
243 """True if the changeset is obsolete"""
243 """True if the changeset is obsolete"""
244 return self.rev() in obsmod.getrevs(self._repo, 'obsolete')
244 return self.rev() in obsmod.getrevs(self._repo, 'obsolete')
245
245
246 def extinct(self):
246 def extinct(self):
247 """True if the changeset is extinct"""
247 """True if the changeset is extinct"""
248 return self.rev() in obsmod.getrevs(self._repo, 'extinct')
248 return self.rev() in obsmod.getrevs(self._repo, 'extinct')
249
249
250 def unstable(self):
250 def unstable(self):
251 """True if the changeset is not obsolete but it's ancestor are"""
251 """True if the changeset is not obsolete but it's ancestor are"""
252 return self.rev() in obsmod.getrevs(self._repo, 'unstable')
252 return self.rev() in obsmod.getrevs(self._repo, 'unstable')
253
253
254 def bumped(self):
254 def bumped(self):
255 """True if the changeset try to be a successor of a public changeset
255 """True if the changeset try to be a successor of a public changeset
256
256
257 Only non-public and non-obsolete changesets may be bumped.
257 Only non-public and non-obsolete changesets may be bumped.
258 """
258 """
259 return self.rev() in obsmod.getrevs(self._repo, 'bumped')
259 return self.rev() in obsmod.getrevs(self._repo, 'bumped')
260
260
261 def divergent(self):
261 def divergent(self):
262 """Is a successors of a changeset with multiple possible successors set
262 """Is a successors of a changeset with multiple possible successors set
263
263
264 Only non-public and non-obsolete changesets may be divergent.
264 Only non-public and non-obsolete changesets may be divergent.
265 """
265 """
266 return self.rev() in obsmod.getrevs(self._repo, 'divergent')
266 return self.rev() in obsmod.getrevs(self._repo, 'divergent')
267
267
268 def troubled(self):
268 def troubled(self):
269 """True if the changeset is either unstable, bumped or divergent"""
269 """True if the changeset is either unstable, bumped or divergent"""
270 return self.unstable() or self.bumped() or self.divergent()
270 return self.unstable() or self.bumped() or self.divergent()
271
271
272 def troubles(self):
272 def troubles(self):
273 """return the list of troubles affecting this changesets.
273 """return the list of troubles affecting this changesets.
274
274
275 Troubles are returned as strings. possible values are:
275 Troubles are returned as strings. possible values are:
276 - unstable,
276 - unstable,
277 - bumped,
277 - bumped,
278 - divergent.
278 - divergent.
279 """
279 """
280 troubles = []
280 troubles = []
281 if self.unstable():
281 if self.unstable():
282 troubles.append('unstable')
282 troubles.append('unstable')
283 if self.bumped():
283 if self.bumped():
284 troubles.append('bumped')
284 troubles.append('bumped')
285 if self.divergent():
285 if self.divergent():
286 troubles.append('divergent')
286 troubles.append('divergent')
287 return troubles
287 return troubles
288
288
289 def _fileinfo(self, path):
289 def _fileinfo(self, path):
290 if '_manifest' in self.__dict__:
290 if '_manifest' in self.__dict__:
291 try:
291 try:
292 return self._manifest[path], self._manifest.flags(path)
292 return self._manifest[path], self._manifest.flags(path)
293 except KeyError:
293 except KeyError:
294 raise error.LookupError(self._node, path,
294 raise error.LookupError(self._node, path,
295 _('not found in manifest'))
295 _('not found in manifest'))
296 if '_manifestdelta' in self.__dict__ or path in self.files():
296 if '_manifestdelta' in self.__dict__ or path in self.files():
297 if path in self._manifestdelta:
297 if path in self._manifestdelta:
298 return (self._manifestdelta[path],
298 return (self._manifestdelta[path],
299 self._manifestdelta.flags(path))
299 self._manifestdelta.flags(path))
300 node, flag = self._repo.manifest.find(self._changeset[0], path)
300 node, flag = self._repo.manifest.find(self._changeset[0], path)
301 if not node:
301 if not node:
302 raise error.LookupError(self._node, path,
302 raise error.LookupError(self._node, path,
303 _('not found in manifest'))
303 _('not found in manifest'))
304
304
305 return node, flag
305 return node, flag
306
306
307 def filenode(self, path):
307 def filenode(self, path):
308 return self._fileinfo(path)[0]
308 return self._fileinfo(path)[0]
309
309
310 def flags(self, path):
310 def flags(self, path):
311 try:
311 try:
312 return self._fileinfo(path)[1]
312 return self._fileinfo(path)[1]
313 except error.LookupError:
313 except error.LookupError:
314 return ''
314 return ''
315
315
316 def filectx(self, path, fileid=None, filelog=None):
316 def filectx(self, path, fileid=None, filelog=None):
317 """get a file context from this changeset"""
317 """get a file context from this changeset"""
318 if fileid is None:
318 if fileid is None:
319 fileid = self.filenode(path)
319 fileid = self.filenode(path)
320 return filectx(self._repo, path, fileid=fileid,
320 return filectx(self._repo, path, fileid=fileid,
321 changectx=self, filelog=filelog)
321 changectx=self, filelog=filelog)
322
322
323 def ancestor(self, c2):
323 def ancestor(self, c2):
324 """
324 """
325 return the ancestor context of self and c2
325 return the ancestor context of self and c2
326 """
326 """
327 # deal with workingctxs
327 # deal with workingctxs
328 n2 = c2._node
328 n2 = c2._node
329 if n2 is None:
329 if n2 is None:
330 n2 = c2._parents[0]._node
330 n2 = c2._parents[0]._node
331 n = self._repo.changelog.ancestor(self._node, n2)
331 n = self._repo.changelog.ancestor(self._node, n2)
332 return changectx(self._repo, n)
332 return changectx(self._repo, n)
333
333
334 def descendant(self, other):
334 def descendant(self, other):
335 """True if other is descendant of this changeset"""
335 """True if other is descendant of this changeset"""
336 return self._repo.changelog.descendant(self._rev, other._rev)
336 return self._repo.changelog.descendant(self._rev, other._rev)
337
337
338 def walk(self, match):
338 def walk(self, match):
339 fset = set(match.files())
339 fset = set(match.files())
340 # for dirstate.walk, files=['.'] means "walk the whole tree".
340 # for dirstate.walk, files=['.'] means "walk the whole tree".
341 # follow that here, too
341 # follow that here, too
342 fset.discard('.')
342 fset.discard('.')
343 for fn in self:
343 for fn in self:
344 if fn in fset:
344 if fn in fset:
345 # specified pattern is the exact name
345 # specified pattern is the exact name
346 fset.remove(fn)
346 fset.remove(fn)
347 if match(fn):
347 if match(fn):
348 yield fn
348 yield fn
349 for fn in sorted(fset):
349 for fn in sorted(fset):
350 if fn in self._dirs:
350 if fn in self._dirs:
351 # specified pattern is a directory
351 # specified pattern is a directory
352 continue
352 continue
353 if match.bad(fn, _('no such file in rev %s') % self) and match(fn):
353 if match.bad(fn, _('no such file in rev %s') % self) and match(fn):
354 yield fn
354 yield fn
355
355
356 def sub(self, path):
356 def sub(self, path):
357 return subrepo.subrepo(self, path)
357 return subrepo.subrepo(self, path)
358
358
359 def match(self, pats=[], include=None, exclude=None, default='glob'):
359 def match(self, pats=[], include=None, exclude=None, default='glob'):
360 r = self._repo
360 r = self._repo
361 return matchmod.match(r.root, r.getcwd(), pats,
361 return matchmod.match(r.root, r.getcwd(), pats,
362 include, exclude, default,
362 include, exclude, default,
363 auditor=r.auditor, ctx=self)
363 auditor=r.auditor, ctx=self)
364
364
365 def diff(self, ctx2=None, match=None, **opts):
365 def diff(self, ctx2=None, match=None, **opts):
366 """Returns a diff generator for the given contexts and matcher"""
366 """Returns a diff generator for the given contexts and matcher"""
367 if ctx2 is None:
367 if ctx2 is None:
368 ctx2 = self.p1()
368 ctx2 = self.p1()
369 if ctx2 is not None and not isinstance(ctx2, changectx):
369 if ctx2 is not None and not isinstance(ctx2, changectx):
370 ctx2 = self._repo[ctx2]
370 ctx2 = self._repo[ctx2]
371 diffopts = patch.diffopts(self._repo.ui, opts)
371 diffopts = patch.diffopts(self._repo.ui, opts)
372 return patch.diff(self._repo, ctx2.node(), self.node(),
372 return patch.diff(self._repo, ctx2.node(), self.node(),
373 match=match, opts=diffopts)
373 match=match, opts=diffopts)
374
374
375 @propertycache
375 @propertycache
376 def _dirs(self):
376 def _dirs(self):
377 dirs = set()
377 dirs = set()
378 for f in self._manifest:
378 for f in self._manifest:
379 pos = f.rfind('/')
379 pos = f.rfind('/')
380 while pos != -1:
380 while pos != -1:
381 f = f[:pos]
381 f = f[:pos]
382 if f in dirs:
382 if f in dirs:
383 break # dirs already contains this and above
383 break # dirs already contains this and above
384 dirs.add(f)
384 dirs.add(f)
385 pos = f.rfind('/')
385 pos = f.rfind('/')
386 return dirs
386 return dirs
387
387
388 def dirs(self):
388 def dirs(self):
389 return self._dirs
389 return self._dirs
390
390
391 def dirty(self):
391 def dirty(self):
392 return False
392 return False
393
393
394 class filectx(object):
394 class filectx(object):
395 """A filecontext object makes access to data related to a particular
395 """A filecontext object makes access to data related to a particular
396 filerevision convenient."""
396 filerevision convenient."""
397 def __init__(self, repo, path, changeid=None, fileid=None,
397 def __init__(self, repo, path, changeid=None, fileid=None,
398 filelog=None, changectx=None):
398 filelog=None, changectx=None):
399 """changeid can be a changeset revision, node, or tag.
399 """changeid can be a changeset revision, node, or tag.
400 fileid can be a file revision or node."""
400 fileid can be a file revision or node."""
401 self._repo = repo
401 self._repo = repo
402 self._path = path
402 self._path = path
403
403
404 assert (changeid is not None
404 assert (changeid is not None
405 or fileid is not None
405 or fileid is not None
406 or changectx is not None), \
406 or changectx is not None), \
407 ("bad args: changeid=%r, fileid=%r, changectx=%r"
407 ("bad args: changeid=%r, fileid=%r, changectx=%r"
408 % (changeid, fileid, changectx))
408 % (changeid, fileid, changectx))
409
409
410 if filelog:
410 if filelog:
411 self._filelog = filelog
411 self._filelog = filelog
412
412
413 if changeid is not None:
413 if changeid is not None:
414 self._changeid = changeid
414 self._changeid = changeid
415 if changectx is not None:
415 if changectx is not None:
416 self._changectx = changectx
416 self._changectx = changectx
417 if fileid is not None:
417 if fileid is not None:
418 self._fileid = fileid
418 self._fileid = fileid
419
419
420 @propertycache
420 @propertycache
421 def _changectx(self):
421 def _changectx(self):
422 try:
422 try:
423 return changectx(self._repo, self._changeid)
423 return changectx(self._repo, self._changeid)
424 except error.RepoLookupError:
424 except error.RepoLookupError:
425 # Linkrev may point to any revision in the repository. When the
425 # Linkrev may point to any revision in the repository. When the
426 # repository is filtered this may lead to `filectx` trying to build
426 # repository is filtered this may lead to `filectx` trying to build
427 # `changectx` for filtered revision. In such case we fallback to
427 # `changectx` for filtered revision. In such case we fallback to
428 # creating `changectx` on the unfiltered version of the reposition.
428 # creating `changectx` on the unfiltered version of the reposition.
429 # This fallback should not be an issue because `changectx` from
429 # This fallback should not be an issue because `changectx` from
430 # `filectx` are not used in complex operations that care about
430 # `filectx` are not used in complex operations that care about
431 # filtering.
431 # filtering.
432 #
432 #
433 # This fallback is a cheap and dirty fix that prevent several
433 # This fallback is a cheap and dirty fix that prevent several
434 # crashes. It does not ensure the behavior is correct. However the
434 # crashes. It does not ensure the behavior is correct. However the
435 # behavior was not correct before filtering either and "incorrect
435 # behavior was not correct before filtering either and "incorrect
436 # behavior" is seen as better as "crash"
436 # behavior" is seen as better as "crash"
437 #
437 #
438 # Linkrevs have several serious troubles with filtering that are
438 # Linkrevs have several serious troubles with filtering that are
439 # complicated to solve. Proper handling of the issue here should be
439 # complicated to solve. Proper handling of the issue here should be
440 # considered when solving linkrev issue are on the table.
440 # considered when solving linkrev issue are on the table.
441 return changectx(self._repo.unfiltered(), self._changeid)
441 return changectx(self._repo.unfiltered(), self._changeid)
442
442
443 @propertycache
443 @propertycache
444 def _filelog(self):
444 def _filelog(self):
445 return self._repo.file(self._path)
445 return self._repo.file(self._path)
446
446
447 @propertycache
447 @propertycache
448 def _changeid(self):
448 def _changeid(self):
449 if '_changectx' in self.__dict__:
449 if '_changectx' in self.__dict__:
450 return self._changectx.rev()
450 return self._changectx.rev()
451 else:
451 else:
452 return self._filelog.linkrev(self._filerev)
452 return self._filelog.linkrev(self._filerev)
453
453
454 @propertycache
454 @propertycache
455 def _filenode(self):
455 def _filenode(self):
456 if '_fileid' in self.__dict__:
456 if '_fileid' in self.__dict__:
457 return self._filelog.lookup(self._fileid)
457 return self._filelog.lookup(self._fileid)
458 else:
458 else:
459 return self._changectx.filenode(self._path)
459 return self._changectx.filenode(self._path)
460
460
461 @propertycache
461 @propertycache
462 def _filerev(self):
462 def _filerev(self):
463 return self._filelog.rev(self._filenode)
463 return self._filelog.rev(self._filenode)
464
464
465 @propertycache
465 @propertycache
466 def _repopath(self):
466 def _repopath(self):
467 return self._path
467 return self._path
468
468
469 def __nonzero__(self):
469 def __nonzero__(self):
470 try:
470 try:
471 self._filenode
471 self._filenode
472 return True
472 return True
473 except error.LookupError:
473 except error.LookupError:
474 # file is missing
474 # file is missing
475 return False
475 return False
476
476
477 def __str__(self):
477 def __str__(self):
478 return "%s@%s" % (self.path(), short(self.node()))
478 return "%s@%s" % (self.path(), short(self.node()))
479
479
480 def __repr__(self):
480 def __repr__(self):
481 return "<filectx %s>" % str(self)
481 return "<filectx %s>" % str(self)
482
482
483 def __hash__(self):
483 def __hash__(self):
484 try:
484 try:
485 return hash((self._path, self._filenode))
485 return hash((self._path, self._filenode))
486 except AttributeError:
486 except AttributeError:
487 return id(self)
487 return id(self)
488
488
489 def __eq__(self, other):
489 def __eq__(self, other):
490 try:
490 try:
491 return (self._path == other._path
491 return (self._path == other._path
492 and self._filenode == other._filenode)
492 and self._filenode == other._filenode)
493 except AttributeError:
493 except AttributeError:
494 return False
494 return False
495
495
496 def __ne__(self, other):
496 def __ne__(self, other):
497 return not (self == other)
497 return not (self == other)
498
498
499 def filectx(self, fileid):
499 def filectx(self, fileid):
500 '''opens an arbitrary revision of the file without
500 '''opens an arbitrary revision of the file without
501 opening a new filelog'''
501 opening a new filelog'''
502 return filectx(self._repo, self._path, fileid=fileid,
502 return filectx(self._repo, self._path, fileid=fileid,
503 filelog=self._filelog)
503 filelog=self._filelog)
504
504
505 def filerev(self):
505 def filerev(self):
506 return self._filerev
506 return self._filerev
507 def filenode(self):
507 def filenode(self):
508 return self._filenode
508 return self._filenode
509 def flags(self):
509 def flags(self):
510 return self._changectx.flags(self._path)
510 return self._changectx.flags(self._path)
511 def filelog(self):
511 def filelog(self):
512 return self._filelog
512 return self._filelog
513
513
514 def rev(self):
514 def rev(self):
515 if '_changectx' in self.__dict__:
515 if '_changectx' in self.__dict__:
516 return self._changectx.rev()
516 return self._changectx.rev()
517 if '_changeid' in self.__dict__:
517 if '_changeid' in self.__dict__:
518 return self._changectx.rev()
518 return self._changectx.rev()
519 return self._filelog.linkrev(self._filerev)
519 return self._filelog.linkrev(self._filerev)
520
520
521 def linkrev(self):
521 def linkrev(self):
522 return self._filelog.linkrev(self._filerev)
522 return self._filelog.linkrev(self._filerev)
523 def node(self):
523 def node(self):
524 return self._changectx.node()
524 return self._changectx.node()
525 def hex(self):
525 def hex(self):
526 return hex(self.node())
526 return hex(self.node())
527 def user(self):
527 def user(self):
528 return self._changectx.user()
528 return self._changectx.user()
529 def date(self):
529 def date(self):
530 return self._changectx.date()
530 return self._changectx.date()
531 def files(self):
531 def files(self):
532 return self._changectx.files()
532 return self._changectx.files()
533 def description(self):
533 def description(self):
534 return self._changectx.description()
534 return self._changectx.description()
535 def branch(self):
535 def branch(self):
536 return self._changectx.branch()
536 return self._changectx.branch()
537 def extra(self):
537 def extra(self):
538 return self._changectx.extra()
538 return self._changectx.extra()
539 def phase(self):
539 def phase(self):
540 return self._changectx.phase()
540 return self._changectx.phase()
541 def phasestr(self):
541 def phasestr(self):
542 return self._changectx.phasestr()
542 return self._changectx.phasestr()
543 def manifest(self):
543 def manifest(self):
544 return self._changectx.manifest()
544 return self._changectx.manifest()
545 def changectx(self):
545 def changectx(self):
546 return self._changectx
546 return self._changectx
547
547
548 def data(self):
548 def data(self):
549 return self._filelog.read(self._filenode)
549 return self._filelog.read(self._filenode)
550 def path(self):
550 def path(self):
551 return self._path
551 return self._path
552 def size(self):
552 def size(self):
553 return self._filelog.size(self._filerev)
553 return self._filelog.size(self._filerev)
554
554
555 def isbinary(self):
555 def isbinary(self):
556 try:
556 try:
557 return util.binary(self.data())
557 return util.binary(self.data())
558 except IOError:
558 except IOError:
559 return False
559 return False
560
560
561 def cmp(self, fctx):
561 def cmp(self, fctx):
562 """compare with other file context
562 """compare with other file context
563
563
564 returns True if different than fctx.
564 returns True if different than fctx.
565 """
565 """
566 if (fctx._filerev is None
566 if (fctx._filerev is None
567 and (self._repo._encodefilterpats
567 and (self._repo._encodefilterpats
568 # if file data starts with '\1\n', empty metadata block is
568 # if file data starts with '\1\n', empty metadata block is
569 # prepended, which adds 4 bytes to filelog.size().
569 # prepended, which adds 4 bytes to filelog.size().
570 or self.size() - 4 == fctx.size())
570 or self.size() - 4 == fctx.size())
571 or self.size() == fctx.size()):
571 or self.size() == fctx.size()):
572 return self._filelog.cmp(self._filenode, fctx.data())
572 return self._filelog.cmp(self._filenode, fctx.data())
573
573
574 return True
574 return True
575
575
576 def renamed(self):
576 def renamed(self):
577 """check if file was actually renamed in this changeset revision
577 """check if file was actually renamed in this changeset revision
578
578
579 If rename logged in file revision, we report copy for changeset only
579 If rename logged in file revision, we report copy for changeset only
580 if file revisions linkrev points back to the changeset in question
580 if file revisions linkrev points back to the changeset in question
581 or both changeset parents contain different file revisions.
581 or both changeset parents contain different file revisions.
582 """
582 """
583
583
584 renamed = self._filelog.renamed(self._filenode)
584 renamed = self._filelog.renamed(self._filenode)
585 if not renamed:
585 if not renamed:
586 return renamed
586 return renamed
587
587
588 if self.rev() == self.linkrev():
588 if self.rev() == self.linkrev():
589 return renamed
589 return renamed
590
590
591 name = self.path()
591 name = self.path()
592 fnode = self._filenode
592 fnode = self._filenode
593 for p in self._changectx.parents():
593 for p in self._changectx.parents():
594 try:
594 try:
595 if fnode == p.filenode(name):
595 if fnode == p.filenode(name):
596 return None
596 return None
597 except error.LookupError:
597 except error.LookupError:
598 pass
598 pass
599 return renamed
599 return renamed
600
600
601 def parents(self):
601 def parents(self):
602 p = self._path
602 p = self._path
603 fl = self._filelog
603 fl = self._filelog
604 pl = [(p, n, fl) for n in self._filelog.parents(self._filenode)]
604 pl = [(p, n, fl) for n in self._filelog.parents(self._filenode)]
605
605
606 r = self._filelog.renamed(self._filenode)
606 r = self._filelog.renamed(self._filenode)
607 if r:
607 if r:
608 pl[0] = (r[0], r[1], None)
608 pl[0] = (r[0], r[1], None)
609
609
610 return [filectx(self._repo, p, fileid=n, filelog=l)
610 return [filectx(self._repo, p, fileid=n, filelog=l)
611 for p, n, l in pl if n != nullid]
611 for p, n, l in pl if n != nullid]
612
612
613 def p1(self):
613 def p1(self):
614 return self.parents()[0]
614 return self.parents()[0]
615
615
616 def p2(self):
616 def p2(self):
617 p = self.parents()
617 p = self.parents()
618 if len(p) == 2:
618 if len(p) == 2:
619 return p[1]
619 return p[1]
620 return filectx(self._repo, self._path, fileid=-1, filelog=self._filelog)
620 return filectx(self._repo, self._path, fileid=-1, filelog=self._filelog)
621
621
622 def children(self):
622 def children(self):
623 # hard for renames
623 # hard for renames
624 c = self._filelog.children(self._filenode)
624 c = self._filelog.children(self._filenode)
625 return [filectx(self._repo, self._path, fileid=x,
625 return [filectx(self._repo, self._path, fileid=x,
626 filelog=self._filelog) for x in c]
626 filelog=self._filelog) for x in c]
627
627
628 def annotate(self, follow=False, linenumber=None, diffopts=None):
628 def annotate(self, follow=False, linenumber=None, diffopts=None):
629 '''returns a list of tuples of (ctx, line) for each line
629 '''returns a list of tuples of (ctx, line) for each line
630 in the file, where ctx is the filectx of the node where
630 in the file, where ctx is the filectx of the node where
631 that line was last changed.
631 that line was last changed.
632 This returns tuples of ((ctx, linenumber), line) for each line,
632 This returns tuples of ((ctx, linenumber), line) for each line,
633 if "linenumber" parameter is NOT "None".
633 if "linenumber" parameter is NOT "None".
634 In such tuples, linenumber means one at the first appearance
634 In such tuples, linenumber means one at the first appearance
635 in the managed file.
635 in the managed file.
636 To reduce annotation cost,
636 To reduce annotation cost,
637 this returns fixed value(False is used) as linenumber,
637 this returns fixed value(False is used) as linenumber,
638 if "linenumber" parameter is "False".'''
638 if "linenumber" parameter is "False".'''
639
639
640 def decorate_compat(text, rev):
640 def decorate_compat(text, rev):
641 return ([rev] * len(text.splitlines()), text)
641 return ([rev] * len(text.splitlines()), text)
642
642
643 def without_linenumber(text, rev):
643 def without_linenumber(text, rev):
644 return ([(rev, False)] * len(text.splitlines()), text)
644 return ([(rev, False)] * len(text.splitlines()), text)
645
645
646 def with_linenumber(text, rev):
646 def with_linenumber(text, rev):
647 size = len(text.splitlines())
647 size = len(text.splitlines())
648 return ([(rev, i) for i in xrange(1, size + 1)], text)
648 return ([(rev, i) for i in xrange(1, size + 1)], text)
649
649
650 decorate = (((linenumber is None) and decorate_compat) or
650 decorate = (((linenumber is None) and decorate_compat) or
651 (linenumber and with_linenumber) or
651 (linenumber and with_linenumber) or
652 without_linenumber)
652 without_linenumber)
653
653
654 def pair(parent, child):
654 def pair(parent, child):
655 blocks = mdiff.allblocks(parent[1], child[1], opts=diffopts,
655 blocks = mdiff.allblocks(parent[1], child[1], opts=diffopts,
656 refine=True)
656 refine=True)
657 for (a1, a2, b1, b2), t in blocks:
657 for (a1, a2, b1, b2), t in blocks:
658 # Changed blocks ('!') or blocks made only of blank lines ('~')
658 # Changed blocks ('!') or blocks made only of blank lines ('~')
659 # belong to the child.
659 # belong to the child.
660 if t == '=':
660 if t == '=':
661 child[0][b1:b2] = parent[0][a1:a2]
661 child[0][b1:b2] = parent[0][a1:a2]
662 return child
662 return child
663
663
664 getlog = util.lrucachefunc(lambda x: self._repo.file(x))
664 getlog = util.lrucachefunc(lambda x: self._repo.file(x))
665 def getctx(path, fileid):
665 def getctx(path, fileid):
666 log = path == self._path and self._filelog or getlog(path)
666 log = path == self._path and self._filelog or getlog(path)
667 return filectx(self._repo, path, fileid=fileid, filelog=log)
667 return filectx(self._repo, path, fileid=fileid, filelog=log)
668 getctx = util.lrucachefunc(getctx)
668 getctx = util.lrucachefunc(getctx)
669
669
670 def parents(f):
670 def parents(f):
671 # we want to reuse filectx objects as much as possible
671 # we want to reuse filectx objects as much as possible
672 p = f._path
672 p = f._path
673 if f._filerev is None: # working dir
673 if f._filerev is None: # working dir
674 pl = [(n.path(), n.filerev()) for n in f.parents()]
674 pl = [(n.path(), n.filerev()) for n in f.parents()]
675 else:
675 else:
676 pl = [(p, n) for n in f._filelog.parentrevs(f._filerev)]
676 pl = [(p, n) for n in f._filelog.parentrevs(f._filerev)]
677
677
678 if follow:
678 if follow:
679 r = f.renamed()
679 r = f.renamed()
680 if r:
680 if r:
681 pl[0] = (r[0], getlog(r[0]).rev(r[1]))
681 pl[0] = (r[0], getlog(r[0]).rev(r[1]))
682
682
683 return [getctx(p, n) for p, n in pl if n != nullrev]
683 return [getctx(p, n) for p, n in pl if n != nullrev]
684
684
685 # use linkrev to find the first changeset where self appeared
685 # use linkrev to find the first changeset where self appeared
686 if self.rev() != self.linkrev():
686 if self.rev() != self.linkrev():
687 base = self.filectx(self.filerev())
687 base = self.filectx(self.filerev())
688 else:
688 else:
689 base = self
689 base = self
690
690
691 # This algorithm would prefer to be recursive, but Python is a
691 # This algorithm would prefer to be recursive, but Python is a
692 # bit recursion-hostile. Instead we do an iterative
692 # bit recursion-hostile. Instead we do an iterative
693 # depth-first search.
693 # depth-first search.
694
694
695 visit = [base]
695 visit = [base]
696 hist = {}
696 hist = {}
697 pcache = {}
697 pcache = {}
698 needed = {base: 1}
698 needed = {base: 1}
699 while visit:
699 while visit:
700 f = visit[-1]
700 f = visit[-1]
701 if f not in pcache:
701 if f not in pcache:
702 pcache[f] = parents(f)
702 pcache[f] = parents(f)
703
703
704 ready = True
704 ready = True
705 pl = pcache[f]
705 pl = pcache[f]
706 for p in pl:
706 for p in pl:
707 if p not in hist:
707 if p not in hist:
708 ready = False
708 ready = False
709 visit.append(p)
709 visit.append(p)
710 needed[p] = needed.get(p, 0) + 1
710 needed[p] = needed.get(p, 0) + 1
711 if ready:
711 if ready:
712 visit.pop()
712 visit.pop()
713 curr = decorate(f.data(), f)
713 curr = decorate(f.data(), f)
714 for p in pl:
714 for p in pl:
715 curr = pair(hist[p], curr)
715 curr = pair(hist[p], curr)
716 if needed[p] == 1:
716 if needed[p] == 1:
717 del hist[p]
717 del hist[p]
718 else:
718 else:
719 needed[p] -= 1
719 needed[p] -= 1
720
720
721 hist[f] = curr
721 hist[f] = curr
722 pcache[f] = []
722 pcache[f] = []
723
723
724 return zip(hist[base][0], hist[base][1].splitlines(True))
724 return zip(hist[base][0], hist[base][1].splitlines(True))
725
725
726 def ancestor(self, fc2, actx):
726 def ancestor(self, fc2, actx):
727 """
727 """
728 find the common ancestor file context, if any, of self, and fc2
728 find the common ancestor file context, if any, of self, and fc2
729
729
730 actx must be the changectx of the common ancestor
730 actx must be the changectx of the common ancestor
731 of self's and fc2's respective changesets.
731 of self's and fc2's respective changesets.
732 """
732 """
733
733
734 # the easy case: no (relevant) renames
734 # the easy case: no (relevant) renames
735 if fc2.path() == self.path() and self.path() in actx:
735 if fc2.path() == self.path() and self.path() in actx:
736 return actx[self.path()]
736 return actx[self.path()]
737
737
738 # the next easiest cases: unambiguous predecessor (name trumps
738 # the next easiest cases: unambiguous predecessor (name trumps
739 # history)
739 # history)
740 if self.path() in actx and fc2.path() not in actx:
740 if self.path() in actx and fc2.path() not in actx:
741 return actx[self.path()]
741 return actx[self.path()]
742 if fc2.path() in actx and self.path() not in actx:
742 if fc2.path() in actx and self.path() not in actx:
743 return actx[fc2.path()]
743 return actx[fc2.path()]
744
744
745 # prime the ancestor cache for the working directory
745 # prime the ancestor cache for the working directory
746 acache = {}
746 acache = {}
747 for c in (self, fc2):
747 for c in (self, fc2):
748 if c._filerev is None:
748 if c._filerev is None:
749 pl = [(n.path(), n.filenode()) for n in c.parents()]
749 pl = [(n.path(), n.filenode()) for n in c.parents()]
750 acache[(c._path, None)] = pl
750 acache[(c._path, None)] = pl
751
751
752 flcache = {self._repopath:self._filelog, fc2._repopath:fc2._filelog}
752 flcache = {self._repopath:self._filelog, fc2._repopath:fc2._filelog}
753 def parents(vertex):
753 def parents(vertex):
754 if vertex in acache:
754 if vertex in acache:
755 return acache[vertex]
755 return acache[vertex]
756 f, n = vertex
756 f, n = vertex
757 if f not in flcache:
757 if f not in flcache:
758 flcache[f] = self._repo.file(f)
758 flcache[f] = self._repo.file(f)
759 fl = flcache[f]
759 fl = flcache[f]
760 pl = [(f, p) for p in fl.parents(n) if p != nullid]
760 pl = [(f, p) for p in fl.parents(n) if p != nullid]
761 re = fl.renamed(n)
761 re = fl.renamed(n)
762 if re:
762 if re:
763 pl.append(re)
763 pl.append(re)
764 acache[vertex] = pl
764 acache[vertex] = pl
765 return pl
765 return pl
766
766
767 a, b = (self._path, self._filenode), (fc2._path, fc2._filenode)
767 a, b = (self._path, self._filenode), (fc2._path, fc2._filenode)
768 v = ancestor.ancestor(a, b, parents)
768 v = ancestor.ancestor(a, b, parents)
769 if v:
769 if v:
770 f, n = v
770 f, n = v
771 return filectx(self._repo, f, fileid=n, filelog=flcache[f])
771 return filectx(self._repo, f, fileid=n, filelog=flcache[f])
772
772
773 return None
773 return None
774
774
775 def ancestors(self, followfirst=False):
775 def ancestors(self, followfirst=False):
776 visit = {}
776 visit = {}
777 c = self
777 c = self
778 cut = followfirst and 1 or None
778 cut = followfirst and 1 or None
779 while True:
779 while True:
780 for parent in c.parents()[:cut]:
780 for parent in c.parents()[:cut]:
781 visit[(parent.rev(), parent.node())] = parent
781 visit[(parent.rev(), parent.node())] = parent
782 if not visit:
782 if not visit:
783 break
783 break
784 c = visit.pop(max(visit))
784 c = visit.pop(max(visit))
785 yield c
785 yield c
786
786
787 def copies(self, c2):
787 def copies(self, c2):
788 if not util.safehasattr(self, "_copycache"):
788 if not util.safehasattr(self, "_copycache"):
789 self._copycache = {}
789 self._copycache = {}
790 sc2 = str(c2)
790 sc2 = str(c2)
791 if sc2 not in self._copycache:
791 if sc2 not in self._copycache:
792 self._copycache[sc2] = copies.pathcopies(c2)
792 self._copycache[sc2] = copies.pathcopies(c2)
793 return self._copycache[sc2]
793 return self._copycache[sc2]
794
794
795 class workingctx(changectx):
795 class workingctx(changectx):
796 """A workingctx object makes access to data related to
796 """A workingctx object makes access to data related to
797 the current working directory convenient.
797 the current working directory convenient.
798 date - any valid date string or (unixtime, offset), or None.
798 date - any valid date string or (unixtime, offset), or None.
799 user - username string, or None.
799 user - username string, or None.
800 extra - a dictionary of extra values, or None.
800 extra - a dictionary of extra values, or None.
801 changes - a list of file lists as returned by localrepo.status()
801 changes - a list of file lists as returned by localrepo.status()
802 or None to use the repository status.
802 or None to use the repository status.
803 """
803 """
804 def __init__(self, repo, text="", user=None, date=None, extra=None,
804 def __init__(self, repo, text="", user=None, date=None, extra=None,
805 changes=None):
805 changes=None):
806 self._repo = repo
806 self._repo = repo
807 self._rev = None
807 self._rev = None
808 self._node = None
808 self._node = None
809 self._text = text
809 self._text = text
810 if date:
810 if date:
811 self._date = util.parsedate(date)
811 self._date = util.parsedate(date)
812 if user:
812 if user:
813 self._user = user
813 self._user = user
814 if changes:
814 if changes:
815 self._status = list(changes[:4])
815 self._status = list(changes[:4])
816 self._unknown = changes[4]
816 self._unknown = changes[4]
817 self._ignored = changes[5]
817 self._ignored = changes[5]
818 self._clean = changes[6]
818 self._clean = changes[6]
819 else:
819 else:
820 self._unknown = None
820 self._unknown = None
821 self._ignored = None
821 self._ignored = None
822 self._clean = None
822 self._clean = None
823
823
824 self._extra = {}
824 self._extra = {}
825 if extra:
825 if extra:
826 self._extra = extra.copy()
826 self._extra = extra.copy()
827 if 'branch' not in self._extra:
827 if 'branch' not in self._extra:
828 try:
828 try:
829 branch = encoding.fromlocal(self._repo.dirstate.branch())
829 branch = encoding.fromlocal(self._repo.dirstate.branch())
830 except UnicodeDecodeError:
830 except UnicodeDecodeError:
831 raise util.Abort(_('branch name not in UTF-8!'))
831 raise util.Abort(_('branch name not in UTF-8!'))
832 self._extra['branch'] = branch
832 self._extra['branch'] = branch
833 if self._extra['branch'] == '':
833 if self._extra['branch'] == '':
834 self._extra['branch'] = 'default'
834 self._extra['branch'] = 'default'
835
835
836 def __str__(self):
836 def __str__(self):
837 return str(self._parents[0]) + "+"
837 return str(self._parents[0]) + "+"
838
838
839 def __repr__(self):
839 def __repr__(self):
840 return "<workingctx %s>" % str(self)
840 return "<workingctx %s>" % str(self)
841
841
842 def __nonzero__(self):
842 def __nonzero__(self):
843 return True
843 return True
844
844
845 def __contains__(self, key):
845 def __contains__(self, key):
846 return self._repo.dirstate[key] not in "?r"
846 return self._repo.dirstate[key] not in "?r"
847
847
848 def _buildflagfunc(self):
848 def _buildflagfunc(self):
849 # Create a fallback function for getting file flags when the
849 # Create a fallback function for getting file flags when the
850 # filesystem doesn't support them
850 # filesystem doesn't support them
851
851
852 copiesget = self._repo.dirstate.copies().get
852 copiesget = self._repo.dirstate.copies().get
853
853
854 if len(self._parents) < 2:
854 if len(self._parents) < 2:
855 # when we have one parent, it's easy: copy from parent
855 # when we have one parent, it's easy: copy from parent
856 man = self._parents[0].manifest()
856 man = self._parents[0].manifest()
857 def func(f):
857 def func(f):
858 f = copiesget(f, f)
858 f = copiesget(f, f)
859 return man.flags(f)
859 return man.flags(f)
860 else:
860 else:
861 # merges are tricky: we try to reconstruct the unstored
861 # merges are tricky: we try to reconstruct the unstored
862 # result from the merge (issue1802)
862 # result from the merge (issue1802)
863 p1, p2 = self._parents
863 p1, p2 = self._parents
864 pa = p1.ancestor(p2)
864 pa = p1.ancestor(p2)
865 m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest()
865 m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest()
866
866
867 def func(f):
867 def func(f):
868 f = copiesget(f, f) # may be wrong for merges with copies
868 f = copiesget(f, f) # may be wrong for merges with copies
869 fl1, fl2, fla = m1.flags(f), m2.flags(f), ma.flags(f)
869 fl1, fl2, fla = m1.flags(f), m2.flags(f), ma.flags(f)
870 if fl1 == fl2:
870 if fl1 == fl2:
871 return fl1
871 return fl1
872 if fl1 == fla:
872 if fl1 == fla:
873 return fl2
873 return fl2
874 if fl2 == fla:
874 if fl2 == fla:
875 return fl1
875 return fl1
876 return '' # punt for conflicts
876 return '' # punt for conflicts
877
877
878 return func
878 return func
879
879
880 @propertycache
880 @propertycache
881 def _flagfunc(self):
881 def _flagfunc(self):
882 return self._repo.dirstate.flagfunc(self._buildflagfunc)
882 return self._repo.dirstate.flagfunc(self._buildflagfunc)
883
883
884 @propertycache
884 @propertycache
885 def _manifest(self):
885 def _manifest(self):
886 """generate a manifest corresponding to the working directory"""
886 """generate a manifest corresponding to the working directory"""
887
887
888 man = self._parents[0].manifest().copy()
888 man = self._parents[0].manifest().copy()
889 if len(self._parents) > 1:
889 if len(self._parents) > 1:
890 man2 = self.p2().manifest()
890 man2 = self.p2().manifest()
891 def getman(f):
891 def getman(f):
892 if f in man:
892 if f in man:
893 return man
893 return man
894 return man2
894 return man2
895 else:
895 else:
896 getman = lambda f: man
896 getman = lambda f: man
897
897
898 copied = self._repo.dirstate.copies()
898 copied = self._repo.dirstate.copies()
899 ff = self._flagfunc
899 ff = self._flagfunc
900 modified, added, removed, deleted = self._status
900 modified, added, removed, deleted = self._status
901 for i, l in (("a", added), ("m", modified)):
901 for i, l in (("a", added), ("m", modified)):
902 for f in l:
902 for f in l:
903 orig = copied.get(f, f)
903 orig = copied.get(f, f)
904 man[f] = getman(orig).get(orig, nullid) + i
904 man[f] = getman(orig).get(orig, nullid) + i
905 try:
905 try:
906 man.set(f, ff(f))
906 man.set(f, ff(f))
907 except OSError:
907 except OSError:
908 pass
908 pass
909
909
910 for f in deleted + removed:
910 for f in deleted + removed:
911 if f in man:
911 if f in man:
912 del man[f]
912 del man[f]
913
913
914 return man
914 return man
915
915
916 def __iter__(self):
916 def __iter__(self):
917 d = self._repo.dirstate
917 d = self._repo.dirstate
918 for f in d:
918 for f in d:
919 if d[f] != 'r':
919 if d[f] != 'r':
920 yield f
920 yield f
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 @propertycache
934 @propertycache
935 def _parents(self):
935 def _parents(self):
936 p = self._repo.dirstate.parents()
936 p = self._repo.dirstate.parents()
937 if p[1] == nullid:
937 if p[1] == nullid:
938 p = p[:-1]
938 p = p[:-1]
939 return [changectx(self._repo, x) for x in p]
939 return [changectx(self._repo, x) for x in p]
940
940
941 def status(self, ignored=False, clean=False, unknown=False):
941 def status(self, ignored=False, clean=False, unknown=False):
942 """Explicit status query
942 """Explicit status query
943 Unless this method is used to query the working copy status, the
943 Unless this method is used to query the working copy status, the
944 _status property will implicitly read the status using its default
944 _status property will implicitly read the status using its default
945 arguments."""
945 arguments."""
946 stat = self._repo.status(ignored=ignored, clean=clean, unknown=unknown)
946 stat = self._repo.status(ignored=ignored, clean=clean, unknown=unknown)
947 self._unknown = self._ignored = self._clean = None
947 self._unknown = self._ignored = self._clean = None
948 if unknown:
948 if unknown:
949 self._unknown = stat[4]
949 self._unknown = stat[4]
950 if ignored:
950 if ignored:
951 self._ignored = stat[5]
951 self._ignored = stat[5]
952 if clean:
952 if clean:
953 self._clean = stat[6]
953 self._clean = stat[6]
954 self._status = stat[:4]
954 self._status = stat[:4]
955 return stat
955 return stat
956
956
957 def manifest(self):
957 def manifest(self):
958 return self._manifest
958 return self._manifest
959 def user(self):
959 def user(self):
960 return self._user or self._repo.ui.username()
960 return self._user or self._repo.ui.username()
961 def date(self):
961 def date(self):
962 return self._date
962 return self._date
963 def description(self):
963 def description(self):
964 return self._text
964 return self._text
965 def files(self):
965 def files(self):
966 return sorted(self._status[0] + self._status[1] + self._status[2])
966 return sorted(self._status[0] + self._status[1] + self._status[2])
967
967
968 def modified(self):
968 def modified(self):
969 return self._status[0]
969 return self._status[0]
970 def added(self):
970 def added(self):
971 return self._status[1]
971 return self._status[1]
972 def removed(self):
972 def removed(self):
973 return self._status[2]
973 return self._status[2]
974 def deleted(self):
974 def deleted(self):
975 return self._status[3]
975 return self._status[3]
976 def unknown(self):
976 def unknown(self):
977 assert self._unknown is not None # must call status first
977 assert self._unknown is not None # must call status first
978 return self._unknown
978 return self._unknown
979 def ignored(self):
979 def ignored(self):
980 assert self._ignored is not None # must call status first
980 assert self._ignored is not None # must call status first
981 return self._ignored
981 return self._ignored
982 def clean(self):
982 def clean(self):
983 assert self._clean is not None # must call status first
983 assert self._clean is not None # must call status first
984 return self._clean
984 return self._clean
985 def branch(self):
985 def branch(self):
986 return encoding.tolocal(self._extra['branch'])
986 return encoding.tolocal(self._extra['branch'])
987 def closesbranch(self):
987 def closesbranch(self):
988 return 'close' in self._extra
988 return 'close' in self._extra
989 def extra(self):
989 def extra(self):
990 return self._extra
990 return self._extra
991
991
992 def tags(self):
992 def tags(self):
993 t = []
993 t = []
994 for p in self.parents():
994 for p in self.parents():
995 t.extend(p.tags())
995 t.extend(p.tags())
996 return t
996 return t
997
997
998 def bookmarks(self):
998 def bookmarks(self):
999 b = []
999 b = []
1000 for p in self.parents():
1000 for p in self.parents():
1001 b.extend(p.bookmarks())
1001 b.extend(p.bookmarks())
1002 return b
1002 return b
1003
1003
1004 def phase(self):
1004 def phase(self):
1005 phase = phases.draft # default phase to draft
1005 phase = phases.draft # default phase to draft
1006 for p in self.parents():
1006 for p in self.parents():
1007 phase = max(phase, p.phase())
1007 phase = max(phase, p.phase())
1008 return phase
1008 return phase
1009
1009
1010 def hidden(self):
1010 def hidden(self):
1011 return False
1011 return False
1012
1012
1013 def children(self):
1013 def children(self):
1014 return []
1014 return []
1015
1015
1016 def flags(self, path):
1016 def flags(self, path):
1017 if '_manifest' in self.__dict__:
1017 if '_manifest' in self.__dict__:
1018 try:
1018 try:
1019 return self._manifest.flags(path)
1019 return self._manifest.flags(path)
1020 except KeyError:
1020 except KeyError:
1021 return ''
1021 return ''
1022
1022
1023 try:
1023 try:
1024 return self._flagfunc(path)
1024 return self._flagfunc(path)
1025 except OSError:
1025 except OSError:
1026 return ''
1026 return ''
1027
1027
1028 def filectx(self, path, filelog=None):
1028 def filectx(self, path, filelog=None):
1029 """get a file context from the working directory"""
1029 """get a file context from the working directory"""
1030 return workingfilectx(self._repo, path, workingctx=self,
1030 return workingfilectx(self._repo, path, workingctx=self,
1031 filelog=filelog)
1031 filelog=filelog)
1032
1032
1033 def ancestor(self, c2):
1033 def ancestor(self, c2):
1034 """return the ancestor context of self and c2"""
1034 """return the ancestor context of self and c2"""
1035 return self._parents[0].ancestor(c2) # punt on two parents for now
1035 return self._parents[0].ancestor(c2) # punt on two parents for now
1036
1036
1037 def walk(self, match):
1037 def walk(self, match):
1038 return sorted(self._repo.dirstate.walk(match, sorted(self.substate),
1038 return sorted(self._repo.dirstate.walk(match, sorted(self.substate),
1039 True, False))
1039 True, False))
1040
1040
1041 def dirty(self, missing=False, merge=True, branch=True):
1041 def dirty(self, missing=False, merge=True, branch=True):
1042 "check whether a working directory is modified"
1042 "check whether a working directory is modified"
1043 # check subrepos first
1043 # check subrepos first
1044 for s in sorted(self.substate):
1044 for s in sorted(self.substate):
1045 if self.sub(s).dirty():
1045 if self.sub(s).dirty():
1046 return True
1046 return True
1047 # check current working dir
1047 # check current working dir
1048 return ((merge and self.p2()) or
1048 return ((merge and self.p2()) or
1049 (branch and self.branch() != self.p1().branch()) or
1049 (branch and self.branch() != self.p1().branch()) or
1050 self.modified() or self.added() or self.removed() or
1050 self.modified() or self.added() or self.removed() or
1051 (missing and self.deleted()))
1051 (missing and self.deleted()))
1052
1052
1053 def add(self, list, prefix=""):
1053 def add(self, list, prefix=""):
1054 join = lambda f: os.path.join(prefix, f)
1054 join = lambda f: os.path.join(prefix, f)
1055 wlock = self._repo.wlock()
1055 wlock = self._repo.wlock()
1056 ui, ds = self._repo.ui, self._repo.dirstate
1056 ui, ds = self._repo.ui, self._repo.dirstate
1057 try:
1057 try:
1058 rejected = []
1058 rejected = []
1059 for f in list:
1059 for f in list:
1060 scmutil.checkportable(ui, join(f))
1060 scmutil.checkportable(ui, join(f))
1061 p = self._repo.wjoin(f)
1061 p = self._repo.wjoin(f)
1062 try:
1062 try:
1063 st = os.lstat(p)
1063 st = os.lstat(p)
1064 except OSError:
1064 except OSError:
1065 ui.warn(_("%s does not exist!\n") % join(f))
1065 ui.warn(_("%s does not exist!\n") % join(f))
1066 rejected.append(f)
1066 rejected.append(f)
1067 continue
1067 continue
1068 if st.st_size > 10000000:
1068 if st.st_size > 10000000:
1069 ui.warn(_("%s: up to %d MB of RAM may be required "
1069 ui.warn(_("%s: up to %d MB of RAM may be required "
1070 "to manage this file\n"
1070 "to manage this file\n"
1071 "(use 'hg revert %s' to cancel the "
1071 "(use 'hg revert %s' to cancel the "
1072 "pending addition)\n")
1072 "pending addition)\n")
1073 % (f, 3 * st.st_size // 1000000, join(f)))
1073 % (f, 3 * st.st_size // 1000000, join(f)))
1074 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
1074 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
1075 ui.warn(_("%s not added: only files and symlinks "
1075 ui.warn(_("%s not added: only files and symlinks "
1076 "supported currently\n") % join(f))
1076 "supported currently\n") % join(f))
1077 rejected.append(p)
1077 rejected.append(p)
1078 elif ds[f] in 'amn':
1078 elif ds[f] in 'amn':
1079 ui.warn(_("%s already tracked!\n") % join(f))
1079 ui.warn(_("%s already tracked!\n") % join(f))
1080 elif ds[f] == 'r':
1080 elif ds[f] == 'r':
1081 ds.normallookup(f)
1081 ds.normallookup(f)
1082 else:
1082 else:
1083 ds.add(f)
1083 ds.add(f)
1084 return rejected
1084 return rejected
1085 finally:
1085 finally:
1086 wlock.release()
1086 wlock.release()
1087
1087
1088 def forget(self, files, prefix=""):
1088 def forget(self, files, prefix=""):
1089 join = lambda f: os.path.join(prefix, f)
1089 join = lambda f: os.path.join(prefix, f)
1090 wlock = self._repo.wlock()
1090 wlock = self._repo.wlock()
1091 try:
1091 try:
1092 rejected = []
1092 rejected = []
1093 for f in files:
1093 for f in files:
1094 if f not in self._repo.dirstate:
1094 if f not in self._repo.dirstate:
1095 self._repo.ui.warn(_("%s not tracked!\n") % join(f))
1095 self._repo.ui.warn(_("%s not tracked!\n") % join(f))
1096 rejected.append(f)
1096 rejected.append(f)
1097 elif self._repo.dirstate[f] != 'a':
1097 elif self._repo.dirstate[f] != 'a':
1098 self._repo.dirstate.remove(f)
1098 self._repo.dirstate.remove(f)
1099 else:
1099 else:
1100 self._repo.dirstate.drop(f)
1100 self._repo.dirstate.drop(f)
1101 return rejected
1101 return rejected
1102 finally:
1102 finally:
1103 wlock.release()
1103 wlock.release()
1104
1104
1105 def ancestors(self):
1105 def ancestors(self):
1106 for a in self._repo.changelog.ancestors(
1106 for a in self._repo.changelog.ancestors(
1107 [p.rev() for p in self._parents]):
1107 [p.rev() for p in self._parents]):
1108 yield changectx(self._repo, a)
1108 yield changectx(self._repo, a)
1109
1109
1110 def undelete(self, list):
1110 def undelete(self, list):
1111 pctxs = self.parents()
1111 pctxs = self.parents()
1112 wlock = self._repo.wlock()
1112 wlock = self._repo.wlock()
1113 try:
1113 try:
1114 for f in list:
1114 for f in list:
1115 if self._repo.dirstate[f] != 'r':
1115 if self._repo.dirstate[f] != 'r':
1116 self._repo.ui.warn(_("%s not removed!\n") % f)
1116 self._repo.ui.warn(_("%s not removed!\n") % f)
1117 else:
1117 else:
1118 fctx = f in pctxs[0] and pctxs[0][f] or pctxs[1][f]
1118 fctx = f in pctxs[0] and pctxs[0][f] or pctxs[1][f]
1119 t = fctx.data()
1119 t = fctx.data()
1120 self._repo.wwrite(f, t, fctx.flags())
1120 self._repo.wwrite(f, t, fctx.flags())
1121 self._repo.dirstate.normal(f)
1121 self._repo.dirstate.normal(f)
1122 finally:
1122 finally:
1123 wlock.release()
1123 wlock.release()
1124
1124
1125 def copy(self, source, dest):
1125 def copy(self, source, dest):
1126 p = self._repo.wjoin(dest)
1126 p = self._repo.wjoin(dest)
1127 if not os.path.lexists(p):
1127 if not os.path.lexists(p):
1128 self._repo.ui.warn(_("%s does not exist!\n") % dest)
1128 self._repo.ui.warn(_("%s does not exist!\n") % dest)
1129 elif not (os.path.isfile(p) or os.path.islink(p)):
1129 elif not (os.path.isfile(p) or os.path.islink(p)):
1130 self._repo.ui.warn(_("copy failed: %s is not a file or a "
1130 self._repo.ui.warn(_("copy failed: %s is not a file or a "
1131 "symbolic link\n") % dest)
1131 "symbolic link\n") % dest)
1132 else:
1132 else:
1133 wlock = self._repo.wlock()
1133 wlock = self._repo.wlock()
1134 try:
1134 try:
1135 if self._repo.dirstate[dest] in '?r':
1135 if self._repo.dirstate[dest] in '?r':
1136 self._repo.dirstate.add(dest)
1136 self._repo.dirstate.add(dest)
1137 self._repo.dirstate.copy(source, dest)
1137 self._repo.dirstate.copy(source, dest)
1138 finally:
1138 finally:
1139 wlock.release()
1139 wlock.release()
1140
1140
1141 def markcommitted(self, node):
1142 """Perform post-commit cleanup necessary after commiting this workingctx
1143
1144 Specifically, this updates backing stores this working context
1145 wraps to reflect the fact that the changes reflected by this
1146 workingctx have been committed. For example, it marks
1147 modified and added files as normal in the dirstate.
1148
1149 """
1150
1151 for f in self.modified() + self.added():
1152 self._repo.dirstate.normal(f)
1153 for f in self.removed():
1154 self._repo.dirstate.drop(f)
1155 self._repo.dirstate.setparents(node)
1156
1141 def dirs(self):
1157 def dirs(self):
1142 return set(self._repo.dirstate.dirs())
1158 return set(self._repo.dirstate.dirs())
1143
1159
1144 class workingfilectx(filectx):
1160 class workingfilectx(filectx):
1145 """A workingfilectx object makes access to data related to a particular
1161 """A workingfilectx object makes access to data related to a particular
1146 file in the working directory convenient."""
1162 file in the working directory convenient."""
1147 def __init__(self, repo, path, filelog=None, workingctx=None):
1163 def __init__(self, repo, path, filelog=None, workingctx=None):
1148 """changeid can be a changeset revision, node, or tag.
1164 """changeid can be a changeset revision, node, or tag.
1149 fileid can be a file revision or node."""
1165 fileid can be a file revision or node."""
1150 self._repo = repo
1166 self._repo = repo
1151 self._path = path
1167 self._path = path
1152 self._changeid = None
1168 self._changeid = None
1153 self._filerev = self._filenode = None
1169 self._filerev = self._filenode = None
1154
1170
1155 if filelog:
1171 if filelog:
1156 self._filelog = filelog
1172 self._filelog = filelog
1157 if workingctx:
1173 if workingctx:
1158 self._changectx = workingctx
1174 self._changectx = workingctx
1159
1175
1160 @propertycache
1176 @propertycache
1161 def _changectx(self):
1177 def _changectx(self):
1162 return workingctx(self._repo)
1178 return workingctx(self._repo)
1163
1179
1164 def __nonzero__(self):
1180 def __nonzero__(self):
1165 return True
1181 return True
1166
1182
1167 def __str__(self):
1183 def __str__(self):
1168 return "%s@%s" % (self.path(), self._changectx)
1184 return "%s@%s" % (self.path(), self._changectx)
1169
1185
1170 def __repr__(self):
1186 def __repr__(self):
1171 return "<workingfilectx %s>" % str(self)
1187 return "<workingfilectx %s>" % str(self)
1172
1188
1173 def data(self):
1189 def data(self):
1174 return self._repo.wread(self._path)
1190 return self._repo.wread(self._path)
1175 def renamed(self):
1191 def renamed(self):
1176 rp = self._repo.dirstate.copied(self._path)
1192 rp = self._repo.dirstate.copied(self._path)
1177 if not rp:
1193 if not rp:
1178 return None
1194 return None
1179 return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
1195 return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
1180
1196
1181 def parents(self):
1197 def parents(self):
1182 '''return parent filectxs, following copies if necessary'''
1198 '''return parent filectxs, following copies if necessary'''
1183 def filenode(ctx, path):
1199 def filenode(ctx, path):
1184 return ctx._manifest.get(path, nullid)
1200 return ctx._manifest.get(path, nullid)
1185
1201
1186 path = self._path
1202 path = self._path
1187 fl = self._filelog
1203 fl = self._filelog
1188 pcl = self._changectx._parents
1204 pcl = self._changectx._parents
1189 renamed = self.renamed()
1205 renamed = self.renamed()
1190
1206
1191 if renamed:
1207 if renamed:
1192 pl = [renamed + (None,)]
1208 pl = [renamed + (None,)]
1193 else:
1209 else:
1194 pl = [(path, filenode(pcl[0], path), fl)]
1210 pl = [(path, filenode(pcl[0], path), fl)]
1195
1211
1196 for pc in pcl[1:]:
1212 for pc in pcl[1:]:
1197 pl.append((path, filenode(pc, path), fl))
1213 pl.append((path, filenode(pc, path), fl))
1198
1214
1199 return [filectx(self._repo, p, fileid=n, filelog=l)
1215 return [filectx(self._repo, p, fileid=n, filelog=l)
1200 for p, n, l in pl if n != nullid]
1216 for p, n, l in pl if n != nullid]
1201
1217
1202 def children(self):
1218 def children(self):
1203 return []
1219 return []
1204
1220
1205 def size(self):
1221 def size(self):
1206 return os.lstat(self._repo.wjoin(self._path)).st_size
1222 return os.lstat(self._repo.wjoin(self._path)).st_size
1207 def date(self):
1223 def date(self):
1208 t, tz = self._changectx.date()
1224 t, tz = self._changectx.date()
1209 try:
1225 try:
1210 return (int(os.lstat(self._repo.wjoin(self._path)).st_mtime), tz)
1226 return (int(os.lstat(self._repo.wjoin(self._path)).st_mtime), tz)
1211 except OSError, err:
1227 except OSError, err:
1212 if err.errno != errno.ENOENT:
1228 if err.errno != errno.ENOENT:
1213 raise
1229 raise
1214 return (t, tz)
1230 return (t, tz)
1215
1231
1216 def cmp(self, fctx):
1232 def cmp(self, fctx):
1217 """compare with other file context
1233 """compare with other file context
1218
1234
1219 returns True if different than fctx.
1235 returns True if different than fctx.
1220 """
1236 """
1221 # fctx should be a filectx (not a workingfilectx)
1237 # fctx should be a filectx (not a workingfilectx)
1222 # invert comparison to reuse the same code path
1238 # invert comparison to reuse the same code path
1223 return fctx.cmp(self)
1239 return fctx.cmp(self)
1224
1240
1225 class memctx(object):
1241 class memctx(object):
1226 """Use memctx to perform in-memory commits via localrepo.commitctx().
1242 """Use memctx to perform in-memory commits via localrepo.commitctx().
1227
1243
1228 Revision information is supplied at initialization time while
1244 Revision information is supplied at initialization time while
1229 related files data and is made available through a callback
1245 related files data and is made available through a callback
1230 mechanism. 'repo' is the current localrepo, 'parents' is a
1246 mechanism. 'repo' is the current localrepo, 'parents' is a
1231 sequence of two parent revisions identifiers (pass None for every
1247 sequence of two parent revisions identifiers (pass None for every
1232 missing parent), 'text' is the commit message and 'files' lists
1248 missing parent), 'text' is the commit message and 'files' lists
1233 names of files touched by the revision (normalized and relative to
1249 names of files touched by the revision (normalized and relative to
1234 repository root).
1250 repository root).
1235
1251
1236 filectxfn(repo, memctx, path) is a callable receiving the
1252 filectxfn(repo, memctx, path) is a callable receiving the
1237 repository, the current memctx object and the normalized path of
1253 repository, the current memctx object and the normalized path of
1238 requested file, relative to repository root. It is fired by the
1254 requested file, relative to repository root. It is fired by the
1239 commit function for every file in 'files', but calls order is
1255 commit function for every file in 'files', but calls order is
1240 undefined. If the file is available in the revision being
1256 undefined. If the file is available in the revision being
1241 committed (updated or added), filectxfn returns a memfilectx
1257 committed (updated or added), filectxfn returns a memfilectx
1242 object. If the file was removed, filectxfn raises an
1258 object. If the file was removed, filectxfn raises an
1243 IOError. Moved files are represented by marking the source file
1259 IOError. Moved files are represented by marking the source file
1244 removed and the new file added with copy information (see
1260 removed and the new file added with copy information (see
1245 memfilectx).
1261 memfilectx).
1246
1262
1247 user receives the committer name and defaults to current
1263 user receives the committer name and defaults to current
1248 repository username, date is the commit date in any format
1264 repository username, date is the commit date in any format
1249 supported by util.parsedate() and defaults to current date, extra
1265 supported by util.parsedate() and defaults to current date, extra
1250 is a dictionary of metadata or is left empty.
1266 is a dictionary of metadata or is left empty.
1251 """
1267 """
1252 def __init__(self, repo, parents, text, files, filectxfn, user=None,
1268 def __init__(self, repo, parents, text, files, filectxfn, user=None,
1253 date=None, extra=None):
1269 date=None, extra=None):
1254 self._repo = repo
1270 self._repo = repo
1255 self._rev = None
1271 self._rev = None
1256 self._node = None
1272 self._node = None
1257 self._text = text
1273 self._text = text
1258 self._date = date and util.parsedate(date) or util.makedate()
1274 self._date = date and util.parsedate(date) or util.makedate()
1259 self._user = user
1275 self._user = user
1260 parents = [(p or nullid) for p in parents]
1276 parents = [(p or nullid) for p in parents]
1261 p1, p2 = parents
1277 p1, p2 = parents
1262 self._parents = [changectx(self._repo, p) for p in (p1, p2)]
1278 self._parents = [changectx(self._repo, p) for p in (p1, p2)]
1263 files = sorted(set(files))
1279 files = sorted(set(files))
1264 self._status = [files, [], [], [], []]
1280 self._status = [files, [], [], [], []]
1265 self._filectxfn = filectxfn
1281 self._filectxfn = filectxfn
1266
1282
1267 self._extra = extra and extra.copy() or {}
1283 self._extra = extra and extra.copy() or {}
1268 if self._extra.get('branch', '') == '':
1284 if self._extra.get('branch', '') == '':
1269 self._extra['branch'] = 'default'
1285 self._extra['branch'] = 'default'
1270
1286
1271 def __str__(self):
1287 def __str__(self):
1272 return str(self._parents[0]) + "+"
1288 return str(self._parents[0]) + "+"
1273
1289
1274 def __int__(self):
1290 def __int__(self):
1275 return self._rev
1291 return self._rev
1276
1292
1277 def __nonzero__(self):
1293 def __nonzero__(self):
1278 return True
1294 return True
1279
1295
1280 def __getitem__(self, key):
1296 def __getitem__(self, key):
1281 return self.filectx(key)
1297 return self.filectx(key)
1282
1298
1283 def p1(self):
1299 def p1(self):
1284 return self._parents[0]
1300 return self._parents[0]
1285 def p2(self):
1301 def p2(self):
1286 return self._parents[1]
1302 return self._parents[1]
1287
1303
1288 def user(self):
1304 def user(self):
1289 return self._user or self._repo.ui.username()
1305 return self._user or self._repo.ui.username()
1290 def date(self):
1306 def date(self):
1291 return self._date
1307 return self._date
1292 def description(self):
1308 def description(self):
1293 return self._text
1309 return self._text
1294 def files(self):
1310 def files(self):
1295 return self.modified()
1311 return self.modified()
1296 def modified(self):
1312 def modified(self):
1297 return self._status[0]
1313 return self._status[0]
1298 def added(self):
1314 def added(self):
1299 return self._status[1]
1315 return self._status[1]
1300 def removed(self):
1316 def removed(self):
1301 return self._status[2]
1317 return self._status[2]
1302 def deleted(self):
1318 def deleted(self):
1303 return self._status[3]
1319 return self._status[3]
1304 def unknown(self):
1320 def unknown(self):
1305 return self._status[4]
1321 return self._status[4]
1306 def ignored(self):
1322 def ignored(self):
1307 return self._status[5]
1323 return self._status[5]
1308 def clean(self):
1324 def clean(self):
1309 return self._status[6]
1325 return self._status[6]
1310 def branch(self):
1326 def branch(self):
1311 return encoding.tolocal(self._extra['branch'])
1327 return encoding.tolocal(self._extra['branch'])
1312 def extra(self):
1328 def extra(self):
1313 return self._extra
1329 return self._extra
1314 def flags(self, f):
1330 def flags(self, f):
1315 return self[f].flags()
1331 return self[f].flags()
1316
1332
1317 def parents(self):
1333 def parents(self):
1318 """return contexts for each parent changeset"""
1334 """return contexts for each parent changeset"""
1319 return self._parents
1335 return self._parents
1320
1336
1321 def filectx(self, path, filelog=None):
1337 def filectx(self, path, filelog=None):
1322 """get a file context from the working directory"""
1338 """get a file context from the working directory"""
1323 return self._filectxfn(self._repo, self, path)
1339 return self._filectxfn(self._repo, self, path)
1324
1340
1325 def commit(self):
1341 def commit(self):
1326 """commit context to the repo"""
1342 """commit context to the repo"""
1327 return self._repo.commitctx(self)
1343 return self._repo.commitctx(self)
1328
1344
1329 class memfilectx(object):
1345 class memfilectx(object):
1330 """memfilectx represents an in-memory file to commit.
1346 """memfilectx represents an in-memory file to commit.
1331
1347
1332 See memctx for more details.
1348 See memctx for more details.
1333 """
1349 """
1334 def __init__(self, path, data, islink=False, isexec=False, copied=None):
1350 def __init__(self, path, data, islink=False, isexec=False, copied=None):
1335 """
1351 """
1336 path is the normalized file path relative to repository root.
1352 path is the normalized file path relative to repository root.
1337 data is the file content as a string.
1353 data is the file content as a string.
1338 islink is True if the file is a symbolic link.
1354 islink is True if the file is a symbolic link.
1339 isexec is True if the file is executable.
1355 isexec is True if the file is executable.
1340 copied is the source file path if current file was copied in the
1356 copied is the source file path if current file was copied in the
1341 revision being committed, or None."""
1357 revision being committed, or None."""
1342 self._path = path
1358 self._path = path
1343 self._data = data
1359 self._data = data
1344 self._flags = (islink and 'l' or '') + (isexec and 'x' or '')
1360 self._flags = (islink and 'l' or '') + (isexec and 'x' or '')
1345 self._copied = None
1361 self._copied = None
1346 if copied:
1362 if copied:
1347 self._copied = (copied, nullid)
1363 self._copied = (copied, nullid)
1348
1364
1349 def __nonzero__(self):
1365 def __nonzero__(self):
1350 return True
1366 return True
1351 def __str__(self):
1367 def __str__(self):
1352 return "%s@%s" % (self.path(), self._changectx)
1368 return "%s@%s" % (self.path(), self._changectx)
1353 def path(self):
1369 def path(self):
1354 return self._path
1370 return self._path
1355 def data(self):
1371 def data(self):
1356 return self._data
1372 return self._data
1357 def flags(self):
1373 def flags(self):
1358 return self._flags
1374 return self._flags
1359 def isexec(self):
1375 def isexec(self):
1360 return 'x' in self._flags
1376 return 'x' in self._flags
1361 def islink(self):
1377 def islink(self):
1362 return 'l' in self._flags
1378 return 'l' in self._flags
1363 def renamed(self):
1379 def renamed(self):
1364 return self._copied
1380 return self._copied
@@ -1,2589 +1,2585 b''
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 peer, changegroup, subrepo, discovery, pushkey, obsolete, repoview
9 import peer, changegroup, subrepo, discovery, pushkey, obsolete, repoview
10 import changelog, dirstate, filelog, manifest, context, bookmarks, phases
10 import changelog, dirstate, filelog, manifest, context, bookmarks, phases
11 import lock, transaction, store, encoding, base85
11 import lock, transaction, store, encoding, base85
12 import scmutil, util, extensions, hook, error, revset
12 import scmutil, util, extensions, hook, error, revset
13 import match as matchmod
13 import match as matchmod
14 import merge as mergemod
14 import merge as mergemod
15 import tags as tagsmod
15 import tags as tagsmod
16 from lock import release
16 from lock import release
17 import weakref, errno, os, time, inspect
17 import weakref, errno, os, time, inspect
18 import branchmap
18 import branchmap
19 propertycache = util.propertycache
19 propertycache = util.propertycache
20 filecache = scmutil.filecache
20 filecache = scmutil.filecache
21
21
22 class repofilecache(filecache):
22 class repofilecache(filecache):
23 """All filecache usage on repo are done for logic that should be unfiltered
23 """All filecache usage on repo are done for logic that should be unfiltered
24 """
24 """
25
25
26 def __get__(self, repo, type=None):
26 def __get__(self, repo, type=None):
27 return super(repofilecache, self).__get__(repo.unfiltered(), type)
27 return super(repofilecache, self).__get__(repo.unfiltered(), type)
28 def __set__(self, repo, value):
28 def __set__(self, repo, value):
29 return super(repofilecache, self).__set__(repo.unfiltered(), value)
29 return super(repofilecache, self).__set__(repo.unfiltered(), value)
30 def __delete__(self, repo):
30 def __delete__(self, repo):
31 return super(repofilecache, self).__delete__(repo.unfiltered())
31 return super(repofilecache, self).__delete__(repo.unfiltered())
32
32
33 class storecache(repofilecache):
33 class storecache(repofilecache):
34 """filecache for files in the store"""
34 """filecache for files in the store"""
35 def join(self, obj, fname):
35 def join(self, obj, fname):
36 return obj.sjoin(fname)
36 return obj.sjoin(fname)
37
37
38 class unfilteredpropertycache(propertycache):
38 class unfilteredpropertycache(propertycache):
39 """propertycache that apply to unfiltered repo only"""
39 """propertycache that apply to unfiltered repo only"""
40
40
41 def __get__(self, repo, type=None):
41 def __get__(self, repo, type=None):
42 return super(unfilteredpropertycache, self).__get__(repo.unfiltered())
42 return super(unfilteredpropertycache, self).__get__(repo.unfiltered())
43
43
44 class filteredpropertycache(propertycache):
44 class filteredpropertycache(propertycache):
45 """propertycache that must take filtering in account"""
45 """propertycache that must take filtering in account"""
46
46
47 def cachevalue(self, obj, value):
47 def cachevalue(self, obj, value):
48 object.__setattr__(obj, self.name, value)
48 object.__setattr__(obj, self.name, value)
49
49
50
50
51 def hasunfilteredcache(repo, name):
51 def hasunfilteredcache(repo, name):
52 """check if a repo has an unfilteredpropertycache value for <name>"""
52 """check if a repo has an unfilteredpropertycache value for <name>"""
53 return name in vars(repo.unfiltered())
53 return name in vars(repo.unfiltered())
54
54
55 def unfilteredmethod(orig):
55 def unfilteredmethod(orig):
56 """decorate method that always need to be run on unfiltered version"""
56 """decorate method that always need to be run on unfiltered version"""
57 def wrapper(repo, *args, **kwargs):
57 def wrapper(repo, *args, **kwargs):
58 return orig(repo.unfiltered(), *args, **kwargs)
58 return orig(repo.unfiltered(), *args, **kwargs)
59 return wrapper
59 return wrapper
60
60
61 MODERNCAPS = set(('lookup', 'branchmap', 'pushkey', 'known', 'getbundle'))
61 MODERNCAPS = set(('lookup', 'branchmap', 'pushkey', 'known', 'getbundle'))
62 LEGACYCAPS = MODERNCAPS.union(set(['changegroupsubset']))
62 LEGACYCAPS = MODERNCAPS.union(set(['changegroupsubset']))
63
63
64 class localpeer(peer.peerrepository):
64 class localpeer(peer.peerrepository):
65 '''peer for a local repo; reflects only the most recent API'''
65 '''peer for a local repo; reflects only the most recent API'''
66
66
67 def __init__(self, repo, caps=MODERNCAPS):
67 def __init__(self, repo, caps=MODERNCAPS):
68 peer.peerrepository.__init__(self)
68 peer.peerrepository.__init__(self)
69 self._repo = repo.filtered('served')
69 self._repo = repo.filtered('served')
70 self.ui = repo.ui
70 self.ui = repo.ui
71 self._caps = repo._restrictcapabilities(caps)
71 self._caps = repo._restrictcapabilities(caps)
72 self.requirements = repo.requirements
72 self.requirements = repo.requirements
73 self.supportedformats = repo.supportedformats
73 self.supportedformats = repo.supportedformats
74
74
75 def close(self):
75 def close(self):
76 self._repo.close()
76 self._repo.close()
77
77
78 def _capabilities(self):
78 def _capabilities(self):
79 return self._caps
79 return self._caps
80
80
81 def local(self):
81 def local(self):
82 return self._repo
82 return self._repo
83
83
84 def canpush(self):
84 def canpush(self):
85 return True
85 return True
86
86
87 def url(self):
87 def url(self):
88 return self._repo.url()
88 return self._repo.url()
89
89
90 def lookup(self, key):
90 def lookup(self, key):
91 return self._repo.lookup(key)
91 return self._repo.lookup(key)
92
92
93 def branchmap(self):
93 def branchmap(self):
94 return self._repo.branchmap()
94 return self._repo.branchmap()
95
95
96 def heads(self):
96 def heads(self):
97 return self._repo.heads()
97 return self._repo.heads()
98
98
99 def known(self, nodes):
99 def known(self, nodes):
100 return self._repo.known(nodes)
100 return self._repo.known(nodes)
101
101
102 def getbundle(self, source, heads=None, common=None):
102 def getbundle(self, source, heads=None, common=None):
103 return self._repo.getbundle(source, heads=heads, common=common)
103 return self._repo.getbundle(source, heads=heads, common=common)
104
104
105 # TODO We might want to move the next two calls into legacypeer and add
105 # TODO We might want to move the next two calls into legacypeer and add
106 # unbundle instead.
106 # unbundle instead.
107
107
108 def lock(self):
108 def lock(self):
109 return self._repo.lock()
109 return self._repo.lock()
110
110
111 def addchangegroup(self, cg, source, url):
111 def addchangegroup(self, cg, source, url):
112 return self._repo.addchangegroup(cg, source, url)
112 return self._repo.addchangegroup(cg, source, url)
113
113
114 def pushkey(self, namespace, key, old, new):
114 def pushkey(self, namespace, key, old, new):
115 return self._repo.pushkey(namespace, key, old, new)
115 return self._repo.pushkey(namespace, key, old, new)
116
116
117 def listkeys(self, namespace):
117 def listkeys(self, namespace):
118 return self._repo.listkeys(namespace)
118 return self._repo.listkeys(namespace)
119
119
120 def debugwireargs(self, one, two, three=None, four=None, five=None):
120 def debugwireargs(self, one, two, three=None, four=None, five=None):
121 '''used to test argument passing over the wire'''
121 '''used to test argument passing over the wire'''
122 return "%s %s %s %s %s" % (one, two, three, four, five)
122 return "%s %s %s %s %s" % (one, two, three, four, five)
123
123
124 class locallegacypeer(localpeer):
124 class locallegacypeer(localpeer):
125 '''peer extension which implements legacy methods too; used for tests with
125 '''peer extension which implements legacy methods too; used for tests with
126 restricted capabilities'''
126 restricted capabilities'''
127
127
128 def __init__(self, repo):
128 def __init__(self, repo):
129 localpeer.__init__(self, repo, caps=LEGACYCAPS)
129 localpeer.__init__(self, repo, caps=LEGACYCAPS)
130
130
131 def branches(self, nodes):
131 def branches(self, nodes):
132 return self._repo.branches(nodes)
132 return self._repo.branches(nodes)
133
133
134 def between(self, pairs):
134 def between(self, pairs):
135 return self._repo.between(pairs)
135 return self._repo.between(pairs)
136
136
137 def changegroup(self, basenodes, source):
137 def changegroup(self, basenodes, source):
138 return self._repo.changegroup(basenodes, source)
138 return self._repo.changegroup(basenodes, source)
139
139
140 def changegroupsubset(self, bases, heads, source):
140 def changegroupsubset(self, bases, heads, source):
141 return self._repo.changegroupsubset(bases, heads, source)
141 return self._repo.changegroupsubset(bases, heads, source)
142
142
143 class localrepository(object):
143 class localrepository(object):
144
144
145 supportedformats = set(('revlogv1', 'generaldelta'))
145 supportedformats = set(('revlogv1', 'generaldelta'))
146 supported = supportedformats | set(('store', 'fncache', 'shared',
146 supported = supportedformats | set(('store', 'fncache', 'shared',
147 'dotencode'))
147 'dotencode'))
148 openerreqs = set(('revlogv1', 'generaldelta'))
148 openerreqs = set(('revlogv1', 'generaldelta'))
149 requirements = ['revlogv1']
149 requirements = ['revlogv1']
150 filtername = None
150 filtername = None
151
151
152 def _baserequirements(self, create):
152 def _baserequirements(self, create):
153 return self.requirements[:]
153 return self.requirements[:]
154
154
155 def __init__(self, baseui, path=None, create=False):
155 def __init__(self, baseui, path=None, create=False):
156 self.wvfs = scmutil.vfs(path, expand=True)
156 self.wvfs = scmutil.vfs(path, expand=True)
157 self.wopener = self.wvfs
157 self.wopener = self.wvfs
158 self.root = self.wvfs.base
158 self.root = self.wvfs.base
159 self.path = self.wvfs.join(".hg")
159 self.path = self.wvfs.join(".hg")
160 self.origroot = path
160 self.origroot = path
161 self.auditor = scmutil.pathauditor(self.root, self._checknested)
161 self.auditor = scmutil.pathauditor(self.root, self._checknested)
162 self.vfs = scmutil.vfs(self.path)
162 self.vfs = scmutil.vfs(self.path)
163 self.opener = self.vfs
163 self.opener = self.vfs
164 self.baseui = baseui
164 self.baseui = baseui
165 self.ui = baseui.copy()
165 self.ui = baseui.copy()
166 # A list of callback to shape the phase if no data were found.
166 # A list of callback to shape the phase if no data were found.
167 # Callback are in the form: func(repo, roots) --> processed root.
167 # Callback are in the form: func(repo, roots) --> processed root.
168 # This list it to be filled by extension during repo setup
168 # This list it to be filled by extension during repo setup
169 self._phasedefaults = []
169 self._phasedefaults = []
170 try:
170 try:
171 self.ui.readconfig(self.join("hgrc"), self.root)
171 self.ui.readconfig(self.join("hgrc"), self.root)
172 extensions.loadall(self.ui)
172 extensions.loadall(self.ui)
173 except IOError:
173 except IOError:
174 pass
174 pass
175
175
176 if not self.vfs.isdir():
176 if not self.vfs.isdir():
177 if create:
177 if create:
178 if not self.wvfs.exists():
178 if not self.wvfs.exists():
179 self.wvfs.makedirs()
179 self.wvfs.makedirs()
180 self.vfs.makedir(notindexed=True)
180 self.vfs.makedir(notindexed=True)
181 requirements = self._baserequirements(create)
181 requirements = self._baserequirements(create)
182 if self.ui.configbool('format', 'usestore', True):
182 if self.ui.configbool('format', 'usestore', True):
183 self.vfs.mkdir("store")
183 self.vfs.mkdir("store")
184 requirements.append("store")
184 requirements.append("store")
185 if self.ui.configbool('format', 'usefncache', True):
185 if self.ui.configbool('format', 'usefncache', True):
186 requirements.append("fncache")
186 requirements.append("fncache")
187 if self.ui.configbool('format', 'dotencode', True):
187 if self.ui.configbool('format', 'dotencode', True):
188 requirements.append('dotencode')
188 requirements.append('dotencode')
189 # create an invalid changelog
189 # create an invalid changelog
190 self.vfs.append(
190 self.vfs.append(
191 "00changelog.i",
191 "00changelog.i",
192 '\0\0\0\2' # represents revlogv2
192 '\0\0\0\2' # represents revlogv2
193 ' dummy changelog to prevent using the old repo layout'
193 ' dummy changelog to prevent using the old repo layout'
194 )
194 )
195 if self.ui.configbool('format', 'generaldelta', False):
195 if self.ui.configbool('format', 'generaldelta', False):
196 requirements.append("generaldelta")
196 requirements.append("generaldelta")
197 requirements = set(requirements)
197 requirements = set(requirements)
198 else:
198 else:
199 raise error.RepoError(_("repository %s not found") % path)
199 raise error.RepoError(_("repository %s not found") % path)
200 elif create:
200 elif create:
201 raise error.RepoError(_("repository %s already exists") % path)
201 raise error.RepoError(_("repository %s already exists") % path)
202 else:
202 else:
203 try:
203 try:
204 requirements = scmutil.readrequires(self.vfs, self.supported)
204 requirements = scmutil.readrequires(self.vfs, self.supported)
205 except IOError, inst:
205 except IOError, inst:
206 if inst.errno != errno.ENOENT:
206 if inst.errno != errno.ENOENT:
207 raise
207 raise
208 requirements = set()
208 requirements = set()
209
209
210 self.sharedpath = self.path
210 self.sharedpath = self.path
211 try:
211 try:
212 s = os.path.realpath(self.opener.read("sharedpath").rstrip('\n'))
212 s = os.path.realpath(self.opener.read("sharedpath").rstrip('\n'))
213 if not os.path.exists(s):
213 if not os.path.exists(s):
214 raise error.RepoError(
214 raise error.RepoError(
215 _('.hg/sharedpath points to nonexistent directory %s') % s)
215 _('.hg/sharedpath points to nonexistent directory %s') % s)
216 self.sharedpath = s
216 self.sharedpath = s
217 except IOError, inst:
217 except IOError, inst:
218 if inst.errno != errno.ENOENT:
218 if inst.errno != errno.ENOENT:
219 raise
219 raise
220
220
221 self.store = store.store(requirements, self.sharedpath, scmutil.vfs)
221 self.store = store.store(requirements, self.sharedpath, scmutil.vfs)
222 self.spath = self.store.path
222 self.spath = self.store.path
223 self.svfs = self.store.vfs
223 self.svfs = self.store.vfs
224 self.sopener = self.svfs
224 self.sopener = self.svfs
225 self.sjoin = self.store.join
225 self.sjoin = self.store.join
226 self.vfs.createmode = self.store.createmode
226 self.vfs.createmode = self.store.createmode
227 self._applyrequirements(requirements)
227 self._applyrequirements(requirements)
228 if create:
228 if create:
229 self._writerequirements()
229 self._writerequirements()
230
230
231
231
232 self._branchcaches = {}
232 self._branchcaches = {}
233 self.filterpats = {}
233 self.filterpats = {}
234 self._datafilters = {}
234 self._datafilters = {}
235 self._transref = self._lockref = self._wlockref = None
235 self._transref = self._lockref = self._wlockref = None
236
236
237 # A cache for various files under .hg/ that tracks file changes,
237 # A cache for various files under .hg/ that tracks file changes,
238 # (used by the filecache decorator)
238 # (used by the filecache decorator)
239 #
239 #
240 # Maps a property name to its util.filecacheentry
240 # Maps a property name to its util.filecacheentry
241 self._filecache = {}
241 self._filecache = {}
242
242
243 # hold sets of revision to be filtered
243 # hold sets of revision to be filtered
244 # should be cleared when something might have changed the filter value:
244 # should be cleared when something might have changed the filter value:
245 # - new changesets,
245 # - new changesets,
246 # - phase change,
246 # - phase change,
247 # - new obsolescence marker,
247 # - new obsolescence marker,
248 # - working directory parent change,
248 # - working directory parent change,
249 # - bookmark changes
249 # - bookmark changes
250 self.filteredrevcache = {}
250 self.filteredrevcache = {}
251
251
252 def close(self):
252 def close(self):
253 pass
253 pass
254
254
255 def _restrictcapabilities(self, caps):
255 def _restrictcapabilities(self, caps):
256 return caps
256 return caps
257
257
258 def _applyrequirements(self, requirements):
258 def _applyrequirements(self, requirements):
259 self.requirements = requirements
259 self.requirements = requirements
260 self.sopener.options = dict((r, 1) for r in requirements
260 self.sopener.options = dict((r, 1) for r in requirements
261 if r in self.openerreqs)
261 if r in self.openerreqs)
262
262
263 def _writerequirements(self):
263 def _writerequirements(self):
264 reqfile = self.opener("requires", "w")
264 reqfile = self.opener("requires", "w")
265 for r in sorted(self.requirements):
265 for r in sorted(self.requirements):
266 reqfile.write("%s\n" % r)
266 reqfile.write("%s\n" % r)
267 reqfile.close()
267 reqfile.close()
268
268
269 def _checknested(self, path):
269 def _checknested(self, path):
270 """Determine if path is a legal nested repository."""
270 """Determine if path is a legal nested repository."""
271 if not path.startswith(self.root):
271 if not path.startswith(self.root):
272 return False
272 return False
273 subpath = path[len(self.root) + 1:]
273 subpath = path[len(self.root) + 1:]
274 normsubpath = util.pconvert(subpath)
274 normsubpath = util.pconvert(subpath)
275
275
276 # XXX: Checking against the current working copy is wrong in
276 # XXX: Checking against the current working copy is wrong in
277 # the sense that it can reject things like
277 # the sense that it can reject things like
278 #
278 #
279 # $ hg cat -r 10 sub/x.txt
279 # $ hg cat -r 10 sub/x.txt
280 #
280 #
281 # if sub/ is no longer a subrepository in the working copy
281 # if sub/ is no longer a subrepository in the working copy
282 # parent revision.
282 # parent revision.
283 #
283 #
284 # However, it can of course also allow things that would have
284 # However, it can of course also allow things that would have
285 # been rejected before, such as the above cat command if sub/
285 # been rejected before, such as the above cat command if sub/
286 # is a subrepository now, but was a normal directory before.
286 # is a subrepository now, but was a normal directory before.
287 # The old path auditor would have rejected by mistake since it
287 # The old path auditor would have rejected by mistake since it
288 # panics when it sees sub/.hg/.
288 # panics when it sees sub/.hg/.
289 #
289 #
290 # All in all, checking against the working copy seems sensible
290 # All in all, checking against the working copy seems sensible
291 # since we want to prevent access to nested repositories on
291 # since we want to prevent access to nested repositories on
292 # the filesystem *now*.
292 # the filesystem *now*.
293 ctx = self[None]
293 ctx = self[None]
294 parts = util.splitpath(subpath)
294 parts = util.splitpath(subpath)
295 while parts:
295 while parts:
296 prefix = '/'.join(parts)
296 prefix = '/'.join(parts)
297 if prefix in ctx.substate:
297 if prefix in ctx.substate:
298 if prefix == normsubpath:
298 if prefix == normsubpath:
299 return True
299 return True
300 else:
300 else:
301 sub = ctx.sub(prefix)
301 sub = ctx.sub(prefix)
302 return sub.checknested(subpath[len(prefix) + 1:])
302 return sub.checknested(subpath[len(prefix) + 1:])
303 else:
303 else:
304 parts.pop()
304 parts.pop()
305 return False
305 return False
306
306
307 def peer(self):
307 def peer(self):
308 return localpeer(self) # not cached to avoid reference cycle
308 return localpeer(self) # not cached to avoid reference cycle
309
309
310 def unfiltered(self):
310 def unfiltered(self):
311 """Return unfiltered version of the repository
311 """Return unfiltered version of the repository
312
312
313 Intended to be overwritten by filtered repo."""
313 Intended to be overwritten by filtered repo."""
314 return self
314 return self
315
315
316 def filtered(self, name):
316 def filtered(self, name):
317 """Return a filtered version of a repository"""
317 """Return a filtered version of a repository"""
318 # build a new class with the mixin and the current class
318 # build a new class with the mixin and the current class
319 # (possibly subclass of the repo)
319 # (possibly subclass of the repo)
320 class proxycls(repoview.repoview, self.unfiltered().__class__):
320 class proxycls(repoview.repoview, self.unfiltered().__class__):
321 pass
321 pass
322 return proxycls(self, name)
322 return proxycls(self, name)
323
323
324 @repofilecache('bookmarks')
324 @repofilecache('bookmarks')
325 def _bookmarks(self):
325 def _bookmarks(self):
326 return bookmarks.bmstore(self)
326 return bookmarks.bmstore(self)
327
327
328 @repofilecache('bookmarks.current')
328 @repofilecache('bookmarks.current')
329 def _bookmarkcurrent(self):
329 def _bookmarkcurrent(self):
330 return bookmarks.readcurrent(self)
330 return bookmarks.readcurrent(self)
331
331
332 def bookmarkheads(self, bookmark):
332 def bookmarkheads(self, bookmark):
333 name = bookmark.split('@', 1)[0]
333 name = bookmark.split('@', 1)[0]
334 heads = []
334 heads = []
335 for mark, n in self._bookmarks.iteritems():
335 for mark, n in self._bookmarks.iteritems():
336 if mark.split('@', 1)[0] == name:
336 if mark.split('@', 1)[0] == name:
337 heads.append(n)
337 heads.append(n)
338 return heads
338 return heads
339
339
340 @storecache('phaseroots')
340 @storecache('phaseroots')
341 def _phasecache(self):
341 def _phasecache(self):
342 return phases.phasecache(self, self._phasedefaults)
342 return phases.phasecache(self, self._phasedefaults)
343
343
344 @storecache('obsstore')
344 @storecache('obsstore')
345 def obsstore(self):
345 def obsstore(self):
346 store = obsolete.obsstore(self.sopener)
346 store = obsolete.obsstore(self.sopener)
347 if store and not obsolete._enabled:
347 if store and not obsolete._enabled:
348 # message is rare enough to not be translated
348 # message is rare enough to not be translated
349 msg = 'obsolete feature not enabled but %i markers found!\n'
349 msg = 'obsolete feature not enabled but %i markers found!\n'
350 self.ui.warn(msg % len(list(store)))
350 self.ui.warn(msg % len(list(store)))
351 return store
351 return store
352
352
353 @storecache('00changelog.i')
353 @storecache('00changelog.i')
354 def changelog(self):
354 def changelog(self):
355 c = changelog.changelog(self.sopener)
355 c = changelog.changelog(self.sopener)
356 if 'HG_PENDING' in os.environ:
356 if 'HG_PENDING' in os.environ:
357 p = os.environ['HG_PENDING']
357 p = os.environ['HG_PENDING']
358 if p.startswith(self.root):
358 if p.startswith(self.root):
359 c.readpending('00changelog.i.a')
359 c.readpending('00changelog.i.a')
360 return c
360 return c
361
361
362 @storecache('00manifest.i')
362 @storecache('00manifest.i')
363 def manifest(self):
363 def manifest(self):
364 return manifest.manifest(self.sopener)
364 return manifest.manifest(self.sopener)
365
365
366 @repofilecache('dirstate')
366 @repofilecache('dirstate')
367 def dirstate(self):
367 def dirstate(self):
368 warned = [0]
368 warned = [0]
369 def validate(node):
369 def validate(node):
370 try:
370 try:
371 self.changelog.rev(node)
371 self.changelog.rev(node)
372 return node
372 return node
373 except error.LookupError:
373 except error.LookupError:
374 if not warned[0]:
374 if not warned[0]:
375 warned[0] = True
375 warned[0] = True
376 self.ui.warn(_("warning: ignoring unknown"
376 self.ui.warn(_("warning: ignoring unknown"
377 " working parent %s!\n") % short(node))
377 " working parent %s!\n") % short(node))
378 return nullid
378 return nullid
379
379
380 return dirstate.dirstate(self.opener, self.ui, self.root, validate)
380 return dirstate.dirstate(self.opener, self.ui, self.root, validate)
381
381
382 def __getitem__(self, changeid):
382 def __getitem__(self, changeid):
383 if changeid is None:
383 if changeid is None:
384 return context.workingctx(self)
384 return context.workingctx(self)
385 return context.changectx(self, changeid)
385 return context.changectx(self, changeid)
386
386
387 def __contains__(self, changeid):
387 def __contains__(self, changeid):
388 try:
388 try:
389 return bool(self.lookup(changeid))
389 return bool(self.lookup(changeid))
390 except error.RepoLookupError:
390 except error.RepoLookupError:
391 return False
391 return False
392
392
393 def __nonzero__(self):
393 def __nonzero__(self):
394 return True
394 return True
395
395
396 def __len__(self):
396 def __len__(self):
397 return len(self.changelog)
397 return len(self.changelog)
398
398
399 def __iter__(self):
399 def __iter__(self):
400 return iter(self.changelog)
400 return iter(self.changelog)
401
401
402 def revs(self, expr, *args):
402 def revs(self, expr, *args):
403 '''Return a list of revisions matching the given revset'''
403 '''Return a list of revisions matching the given revset'''
404 expr = revset.formatspec(expr, *args)
404 expr = revset.formatspec(expr, *args)
405 m = revset.match(None, expr)
405 m = revset.match(None, expr)
406 return [r for r in m(self, list(self))]
406 return [r for r in m(self, list(self))]
407
407
408 def set(self, expr, *args):
408 def set(self, expr, *args):
409 '''
409 '''
410 Yield a context for each matching revision, after doing arg
410 Yield a context for each matching revision, after doing arg
411 replacement via revset.formatspec
411 replacement via revset.formatspec
412 '''
412 '''
413 for r in self.revs(expr, *args):
413 for r in self.revs(expr, *args):
414 yield self[r]
414 yield self[r]
415
415
416 def url(self):
416 def url(self):
417 return 'file:' + self.root
417 return 'file:' + self.root
418
418
419 def hook(self, name, throw=False, **args):
419 def hook(self, name, throw=False, **args):
420 return hook.hook(self.ui, self, name, throw, **args)
420 return hook.hook(self.ui, self, name, throw, **args)
421
421
422 @unfilteredmethod
422 @unfilteredmethod
423 def _tag(self, names, node, message, local, user, date, extra={}):
423 def _tag(self, names, node, message, local, user, date, extra={}):
424 if isinstance(names, str):
424 if isinstance(names, str):
425 names = (names,)
425 names = (names,)
426
426
427 branches = self.branchmap()
427 branches = self.branchmap()
428 for name in names:
428 for name in names:
429 self.hook('pretag', throw=True, node=hex(node), tag=name,
429 self.hook('pretag', throw=True, node=hex(node), tag=name,
430 local=local)
430 local=local)
431 if name in branches:
431 if name in branches:
432 self.ui.warn(_("warning: tag %s conflicts with existing"
432 self.ui.warn(_("warning: tag %s conflicts with existing"
433 " branch name\n") % name)
433 " branch name\n") % name)
434
434
435 def writetags(fp, names, munge, prevtags):
435 def writetags(fp, names, munge, prevtags):
436 fp.seek(0, 2)
436 fp.seek(0, 2)
437 if prevtags and prevtags[-1] != '\n':
437 if prevtags and prevtags[-1] != '\n':
438 fp.write('\n')
438 fp.write('\n')
439 for name in names:
439 for name in names:
440 m = munge and munge(name) or name
440 m = munge and munge(name) or name
441 if (self._tagscache.tagtypes and
441 if (self._tagscache.tagtypes and
442 name in self._tagscache.tagtypes):
442 name in self._tagscache.tagtypes):
443 old = self.tags().get(name, nullid)
443 old = self.tags().get(name, nullid)
444 fp.write('%s %s\n' % (hex(old), m))
444 fp.write('%s %s\n' % (hex(old), m))
445 fp.write('%s %s\n' % (hex(node), m))
445 fp.write('%s %s\n' % (hex(node), m))
446 fp.close()
446 fp.close()
447
447
448 prevtags = ''
448 prevtags = ''
449 if local:
449 if local:
450 try:
450 try:
451 fp = self.opener('localtags', 'r+')
451 fp = self.opener('localtags', 'r+')
452 except IOError:
452 except IOError:
453 fp = self.opener('localtags', 'a')
453 fp = self.opener('localtags', 'a')
454 else:
454 else:
455 prevtags = fp.read()
455 prevtags = fp.read()
456
456
457 # local tags are stored in the current charset
457 # local tags are stored in the current charset
458 writetags(fp, names, None, prevtags)
458 writetags(fp, names, None, prevtags)
459 for name in names:
459 for name in names:
460 self.hook('tag', node=hex(node), tag=name, local=local)
460 self.hook('tag', node=hex(node), tag=name, local=local)
461 return
461 return
462
462
463 try:
463 try:
464 fp = self.wfile('.hgtags', 'rb+')
464 fp = self.wfile('.hgtags', 'rb+')
465 except IOError, e:
465 except IOError, e:
466 if e.errno != errno.ENOENT:
466 if e.errno != errno.ENOENT:
467 raise
467 raise
468 fp = self.wfile('.hgtags', 'ab')
468 fp = self.wfile('.hgtags', 'ab')
469 else:
469 else:
470 prevtags = fp.read()
470 prevtags = fp.read()
471
471
472 # committed tags are stored in UTF-8
472 # committed tags are stored in UTF-8
473 writetags(fp, names, encoding.fromlocal, prevtags)
473 writetags(fp, names, encoding.fromlocal, prevtags)
474
474
475 fp.close()
475 fp.close()
476
476
477 self.invalidatecaches()
477 self.invalidatecaches()
478
478
479 if '.hgtags' not in self.dirstate:
479 if '.hgtags' not in self.dirstate:
480 self[None].add(['.hgtags'])
480 self[None].add(['.hgtags'])
481
481
482 m = matchmod.exact(self.root, '', ['.hgtags'])
482 m = matchmod.exact(self.root, '', ['.hgtags'])
483 tagnode = self.commit(message, user, date, extra=extra, match=m)
483 tagnode = self.commit(message, user, date, extra=extra, match=m)
484
484
485 for name in names:
485 for name in names:
486 self.hook('tag', node=hex(node), tag=name, local=local)
486 self.hook('tag', node=hex(node), tag=name, local=local)
487
487
488 return tagnode
488 return tagnode
489
489
490 def tag(self, names, node, message, local, user, date):
490 def tag(self, names, node, message, local, user, date):
491 '''tag a revision with one or more symbolic names.
491 '''tag a revision with one or more symbolic names.
492
492
493 names is a list of strings or, when adding a single tag, names may be a
493 names is a list of strings or, when adding a single tag, names may be a
494 string.
494 string.
495
495
496 if local is True, the tags are stored in a per-repository file.
496 if local is True, the tags are stored in a per-repository file.
497 otherwise, they are stored in the .hgtags file, and a new
497 otherwise, they are stored in the .hgtags file, and a new
498 changeset is committed with the change.
498 changeset is committed with the change.
499
499
500 keyword arguments:
500 keyword arguments:
501
501
502 local: whether to store tags in non-version-controlled file
502 local: whether to store tags in non-version-controlled file
503 (default False)
503 (default False)
504
504
505 message: commit message to use if committing
505 message: commit message to use if committing
506
506
507 user: name of user to use if committing
507 user: name of user to use if committing
508
508
509 date: date tuple to use if committing'''
509 date: date tuple to use if committing'''
510
510
511 if not local:
511 if not local:
512 for x in self.status()[:5]:
512 for x in self.status()[:5]:
513 if '.hgtags' in x:
513 if '.hgtags' in x:
514 raise util.Abort(_('working copy of .hgtags is changed '
514 raise util.Abort(_('working copy of .hgtags is changed '
515 '(please commit .hgtags manually)'))
515 '(please commit .hgtags manually)'))
516
516
517 self.tags() # instantiate the cache
517 self.tags() # instantiate the cache
518 self._tag(names, node, message, local, user, date)
518 self._tag(names, node, message, local, user, date)
519
519
520 @filteredpropertycache
520 @filteredpropertycache
521 def _tagscache(self):
521 def _tagscache(self):
522 '''Returns a tagscache object that contains various tags related
522 '''Returns a tagscache object that contains various tags related
523 caches.'''
523 caches.'''
524
524
525 # This simplifies its cache management by having one decorated
525 # This simplifies its cache management by having one decorated
526 # function (this one) and the rest simply fetch things from it.
526 # function (this one) and the rest simply fetch things from it.
527 class tagscache(object):
527 class tagscache(object):
528 def __init__(self):
528 def __init__(self):
529 # These two define the set of tags for this repository. tags
529 # These two define the set of tags for this repository. tags
530 # maps tag name to node; tagtypes maps tag name to 'global' or
530 # maps tag name to node; tagtypes maps tag name to 'global' or
531 # 'local'. (Global tags are defined by .hgtags across all
531 # 'local'. (Global tags are defined by .hgtags across all
532 # heads, and local tags are defined in .hg/localtags.)
532 # heads, and local tags are defined in .hg/localtags.)
533 # They constitute the in-memory cache of tags.
533 # They constitute the in-memory cache of tags.
534 self.tags = self.tagtypes = None
534 self.tags = self.tagtypes = None
535
535
536 self.nodetagscache = self.tagslist = None
536 self.nodetagscache = self.tagslist = None
537
537
538 cache = tagscache()
538 cache = tagscache()
539 cache.tags, cache.tagtypes = self._findtags()
539 cache.tags, cache.tagtypes = self._findtags()
540
540
541 return cache
541 return cache
542
542
543 def tags(self):
543 def tags(self):
544 '''return a mapping of tag to node'''
544 '''return a mapping of tag to node'''
545 t = {}
545 t = {}
546 if self.changelog.filteredrevs:
546 if self.changelog.filteredrevs:
547 tags, tt = self._findtags()
547 tags, tt = self._findtags()
548 else:
548 else:
549 tags = self._tagscache.tags
549 tags = self._tagscache.tags
550 for k, v in tags.iteritems():
550 for k, v in tags.iteritems():
551 try:
551 try:
552 # ignore tags to unknown nodes
552 # ignore tags to unknown nodes
553 self.changelog.rev(v)
553 self.changelog.rev(v)
554 t[k] = v
554 t[k] = v
555 except (error.LookupError, ValueError):
555 except (error.LookupError, ValueError):
556 pass
556 pass
557 return t
557 return t
558
558
559 def _findtags(self):
559 def _findtags(self):
560 '''Do the hard work of finding tags. Return a pair of dicts
560 '''Do the hard work of finding tags. Return a pair of dicts
561 (tags, tagtypes) where tags maps tag name to node, and tagtypes
561 (tags, tagtypes) where tags maps tag name to node, and tagtypes
562 maps tag name to a string like \'global\' or \'local\'.
562 maps tag name to a string like \'global\' or \'local\'.
563 Subclasses or extensions are free to add their own tags, but
563 Subclasses or extensions are free to add their own tags, but
564 should be aware that the returned dicts will be retained for the
564 should be aware that the returned dicts will be retained for the
565 duration of the localrepo object.'''
565 duration of the localrepo object.'''
566
566
567 # XXX what tagtype should subclasses/extensions use? Currently
567 # XXX what tagtype should subclasses/extensions use? Currently
568 # mq and bookmarks add tags, but do not set the tagtype at all.
568 # mq and bookmarks add tags, but do not set the tagtype at all.
569 # Should each extension invent its own tag type? Should there
569 # Should each extension invent its own tag type? Should there
570 # be one tagtype for all such "virtual" tags? Or is the status
570 # be one tagtype for all such "virtual" tags? Or is the status
571 # quo fine?
571 # quo fine?
572
572
573 alltags = {} # map tag name to (node, hist)
573 alltags = {} # map tag name to (node, hist)
574 tagtypes = {}
574 tagtypes = {}
575
575
576 tagsmod.findglobaltags(self.ui, self, alltags, tagtypes)
576 tagsmod.findglobaltags(self.ui, self, alltags, tagtypes)
577 tagsmod.readlocaltags(self.ui, self, alltags, tagtypes)
577 tagsmod.readlocaltags(self.ui, self, alltags, tagtypes)
578
578
579 # Build the return dicts. Have to re-encode tag names because
579 # Build the return dicts. Have to re-encode tag names because
580 # the tags module always uses UTF-8 (in order not to lose info
580 # the tags module always uses UTF-8 (in order not to lose info
581 # writing to the cache), but the rest of Mercurial wants them in
581 # writing to the cache), but the rest of Mercurial wants them in
582 # local encoding.
582 # local encoding.
583 tags = {}
583 tags = {}
584 for (name, (node, hist)) in alltags.iteritems():
584 for (name, (node, hist)) in alltags.iteritems():
585 if node != nullid:
585 if node != nullid:
586 tags[encoding.tolocal(name)] = node
586 tags[encoding.tolocal(name)] = node
587 tags['tip'] = self.changelog.tip()
587 tags['tip'] = self.changelog.tip()
588 tagtypes = dict([(encoding.tolocal(name), value)
588 tagtypes = dict([(encoding.tolocal(name), value)
589 for (name, value) in tagtypes.iteritems()])
589 for (name, value) in tagtypes.iteritems()])
590 return (tags, tagtypes)
590 return (tags, tagtypes)
591
591
592 def tagtype(self, tagname):
592 def tagtype(self, tagname):
593 '''
593 '''
594 return the type of the given tag. result can be:
594 return the type of the given tag. result can be:
595
595
596 'local' : a local tag
596 'local' : a local tag
597 'global' : a global tag
597 'global' : a global tag
598 None : tag does not exist
598 None : tag does not exist
599 '''
599 '''
600
600
601 return self._tagscache.tagtypes.get(tagname)
601 return self._tagscache.tagtypes.get(tagname)
602
602
603 def tagslist(self):
603 def tagslist(self):
604 '''return a list of tags ordered by revision'''
604 '''return a list of tags ordered by revision'''
605 if not self._tagscache.tagslist:
605 if not self._tagscache.tagslist:
606 l = []
606 l = []
607 for t, n in self.tags().iteritems():
607 for t, n in self.tags().iteritems():
608 r = self.changelog.rev(n)
608 r = self.changelog.rev(n)
609 l.append((r, t, n))
609 l.append((r, t, n))
610 self._tagscache.tagslist = [(t, n) for r, t, n in sorted(l)]
610 self._tagscache.tagslist = [(t, n) for r, t, n in sorted(l)]
611
611
612 return self._tagscache.tagslist
612 return self._tagscache.tagslist
613
613
614 def nodetags(self, node):
614 def nodetags(self, node):
615 '''return the tags associated with a node'''
615 '''return the tags associated with a node'''
616 if not self._tagscache.nodetagscache:
616 if not self._tagscache.nodetagscache:
617 nodetagscache = {}
617 nodetagscache = {}
618 for t, n in self._tagscache.tags.iteritems():
618 for t, n in self._tagscache.tags.iteritems():
619 nodetagscache.setdefault(n, []).append(t)
619 nodetagscache.setdefault(n, []).append(t)
620 for tags in nodetagscache.itervalues():
620 for tags in nodetagscache.itervalues():
621 tags.sort()
621 tags.sort()
622 self._tagscache.nodetagscache = nodetagscache
622 self._tagscache.nodetagscache = nodetagscache
623 return self._tagscache.nodetagscache.get(node, [])
623 return self._tagscache.nodetagscache.get(node, [])
624
624
625 def nodebookmarks(self, node):
625 def nodebookmarks(self, node):
626 marks = []
626 marks = []
627 for bookmark, n in self._bookmarks.iteritems():
627 for bookmark, n in self._bookmarks.iteritems():
628 if n == node:
628 if n == node:
629 marks.append(bookmark)
629 marks.append(bookmark)
630 return sorted(marks)
630 return sorted(marks)
631
631
632 def branchmap(self):
632 def branchmap(self):
633 '''returns a dictionary {branch: [branchheads]}'''
633 '''returns a dictionary {branch: [branchheads]}'''
634 branchmap.updatecache(self)
634 branchmap.updatecache(self)
635 return self._branchcaches[self.filtername]
635 return self._branchcaches[self.filtername]
636
636
637
637
638 def _branchtip(self, heads):
638 def _branchtip(self, heads):
639 '''return the tipmost branch head in heads'''
639 '''return the tipmost branch head in heads'''
640 tip = heads[-1]
640 tip = heads[-1]
641 for h in reversed(heads):
641 for h in reversed(heads):
642 if not self[h].closesbranch():
642 if not self[h].closesbranch():
643 tip = h
643 tip = h
644 break
644 break
645 return tip
645 return tip
646
646
647 def branchtip(self, branch):
647 def branchtip(self, branch):
648 '''return the tip node for a given branch'''
648 '''return the tip node for a given branch'''
649 if branch not in self.branchmap():
649 if branch not in self.branchmap():
650 raise error.RepoLookupError(_("unknown branch '%s'") % branch)
650 raise error.RepoLookupError(_("unknown branch '%s'") % branch)
651 return self._branchtip(self.branchmap()[branch])
651 return self._branchtip(self.branchmap()[branch])
652
652
653 def branchtags(self):
653 def branchtags(self):
654 '''return a dict where branch names map to the tipmost head of
654 '''return a dict where branch names map to the tipmost head of
655 the branch, open heads come before closed'''
655 the branch, open heads come before closed'''
656 bt = {}
656 bt = {}
657 for bn, heads in self.branchmap().iteritems():
657 for bn, heads in self.branchmap().iteritems():
658 bt[bn] = self._branchtip(heads)
658 bt[bn] = self._branchtip(heads)
659 return bt
659 return bt
660
660
661 def lookup(self, key):
661 def lookup(self, key):
662 return self[key].node()
662 return self[key].node()
663
663
664 def lookupbranch(self, key, remote=None):
664 def lookupbranch(self, key, remote=None):
665 repo = remote or self
665 repo = remote or self
666 if key in repo.branchmap():
666 if key in repo.branchmap():
667 return key
667 return key
668
668
669 repo = (remote and remote.local()) and remote or self
669 repo = (remote and remote.local()) and remote or self
670 return repo[key].branch()
670 return repo[key].branch()
671
671
672 def known(self, nodes):
672 def known(self, nodes):
673 nm = self.changelog.nodemap
673 nm = self.changelog.nodemap
674 pc = self._phasecache
674 pc = self._phasecache
675 result = []
675 result = []
676 for n in nodes:
676 for n in nodes:
677 r = nm.get(n)
677 r = nm.get(n)
678 resp = not (r is None or pc.phase(self, r) >= phases.secret)
678 resp = not (r is None or pc.phase(self, r) >= phases.secret)
679 result.append(resp)
679 result.append(resp)
680 return result
680 return result
681
681
682 def local(self):
682 def local(self):
683 return self
683 return self
684
684
685 def cancopy(self):
685 def cancopy(self):
686 return self.local() # so statichttprepo's override of local() works
686 return self.local() # so statichttprepo's override of local() works
687
687
688 def join(self, f):
688 def join(self, f):
689 return os.path.join(self.path, f)
689 return os.path.join(self.path, f)
690
690
691 def wjoin(self, f):
691 def wjoin(self, f):
692 return os.path.join(self.root, f)
692 return os.path.join(self.root, f)
693
693
694 def file(self, f):
694 def file(self, f):
695 if f[0] == '/':
695 if f[0] == '/':
696 f = f[1:]
696 f = f[1:]
697 return filelog.filelog(self.sopener, f)
697 return filelog.filelog(self.sopener, f)
698
698
699 def changectx(self, changeid):
699 def changectx(self, changeid):
700 return self[changeid]
700 return self[changeid]
701
701
702 def parents(self, changeid=None):
702 def parents(self, changeid=None):
703 '''get list of changectxs for parents of changeid'''
703 '''get list of changectxs for parents of changeid'''
704 return self[changeid].parents()
704 return self[changeid].parents()
705
705
706 def setparents(self, p1, p2=nullid):
706 def setparents(self, p1, p2=nullid):
707 copies = self.dirstate.setparents(p1, p2)
707 copies = self.dirstate.setparents(p1, p2)
708 if copies:
708 if copies:
709 # Adjust copy records, the dirstate cannot do it, it
709 # Adjust copy records, the dirstate cannot do it, it
710 # requires access to parents manifests. Preserve them
710 # requires access to parents manifests. Preserve them
711 # only for entries added to first parent.
711 # only for entries added to first parent.
712 pctx = self[p1]
712 pctx = self[p1]
713 for f in copies:
713 for f in copies:
714 if f not in pctx and copies[f] in pctx:
714 if f not in pctx and copies[f] in pctx:
715 self.dirstate.copy(copies[f], f)
715 self.dirstate.copy(copies[f], f)
716
716
717 def filectx(self, path, changeid=None, fileid=None):
717 def filectx(self, path, changeid=None, fileid=None):
718 """changeid can be a changeset revision, node, or tag.
718 """changeid can be a changeset revision, node, or tag.
719 fileid can be a file revision or node."""
719 fileid can be a file revision or node."""
720 return context.filectx(self, path, changeid, fileid)
720 return context.filectx(self, path, changeid, fileid)
721
721
722 def getcwd(self):
722 def getcwd(self):
723 return self.dirstate.getcwd()
723 return self.dirstate.getcwd()
724
724
725 def pathto(self, f, cwd=None):
725 def pathto(self, f, cwd=None):
726 return self.dirstate.pathto(f, cwd)
726 return self.dirstate.pathto(f, cwd)
727
727
728 def wfile(self, f, mode='r'):
728 def wfile(self, f, mode='r'):
729 return self.wopener(f, mode)
729 return self.wopener(f, mode)
730
730
731 def _link(self, f):
731 def _link(self, f):
732 return os.path.islink(self.wjoin(f))
732 return os.path.islink(self.wjoin(f))
733
733
734 def _loadfilter(self, filter):
734 def _loadfilter(self, filter):
735 if filter not in self.filterpats:
735 if filter not in self.filterpats:
736 l = []
736 l = []
737 for pat, cmd in self.ui.configitems(filter):
737 for pat, cmd in self.ui.configitems(filter):
738 if cmd == '!':
738 if cmd == '!':
739 continue
739 continue
740 mf = matchmod.match(self.root, '', [pat])
740 mf = matchmod.match(self.root, '', [pat])
741 fn = None
741 fn = None
742 params = cmd
742 params = cmd
743 for name, filterfn in self._datafilters.iteritems():
743 for name, filterfn in self._datafilters.iteritems():
744 if cmd.startswith(name):
744 if cmd.startswith(name):
745 fn = filterfn
745 fn = filterfn
746 params = cmd[len(name):].lstrip()
746 params = cmd[len(name):].lstrip()
747 break
747 break
748 if not fn:
748 if not fn:
749 fn = lambda s, c, **kwargs: util.filter(s, c)
749 fn = lambda s, c, **kwargs: util.filter(s, c)
750 # Wrap old filters not supporting keyword arguments
750 # Wrap old filters not supporting keyword arguments
751 if not inspect.getargspec(fn)[2]:
751 if not inspect.getargspec(fn)[2]:
752 oldfn = fn
752 oldfn = fn
753 fn = lambda s, c, **kwargs: oldfn(s, c)
753 fn = lambda s, c, **kwargs: oldfn(s, c)
754 l.append((mf, fn, params))
754 l.append((mf, fn, params))
755 self.filterpats[filter] = l
755 self.filterpats[filter] = l
756 return self.filterpats[filter]
756 return self.filterpats[filter]
757
757
758 def _filter(self, filterpats, filename, data):
758 def _filter(self, filterpats, filename, data):
759 for mf, fn, cmd in filterpats:
759 for mf, fn, cmd in filterpats:
760 if mf(filename):
760 if mf(filename):
761 self.ui.debug("filtering %s through %s\n" % (filename, cmd))
761 self.ui.debug("filtering %s through %s\n" % (filename, cmd))
762 data = fn(data, cmd, ui=self.ui, repo=self, filename=filename)
762 data = fn(data, cmd, ui=self.ui, repo=self, filename=filename)
763 break
763 break
764
764
765 return data
765 return data
766
766
767 @unfilteredpropertycache
767 @unfilteredpropertycache
768 def _encodefilterpats(self):
768 def _encodefilterpats(self):
769 return self._loadfilter('encode')
769 return self._loadfilter('encode')
770
770
771 @unfilteredpropertycache
771 @unfilteredpropertycache
772 def _decodefilterpats(self):
772 def _decodefilterpats(self):
773 return self._loadfilter('decode')
773 return self._loadfilter('decode')
774
774
775 def adddatafilter(self, name, filter):
775 def adddatafilter(self, name, filter):
776 self._datafilters[name] = filter
776 self._datafilters[name] = filter
777
777
778 def wread(self, filename):
778 def wread(self, filename):
779 if self._link(filename):
779 if self._link(filename):
780 data = os.readlink(self.wjoin(filename))
780 data = os.readlink(self.wjoin(filename))
781 else:
781 else:
782 data = self.wopener.read(filename)
782 data = self.wopener.read(filename)
783 return self._filter(self._encodefilterpats, filename, data)
783 return self._filter(self._encodefilterpats, filename, data)
784
784
785 def wwrite(self, filename, data, flags):
785 def wwrite(self, filename, data, flags):
786 data = self._filter(self._decodefilterpats, filename, data)
786 data = self._filter(self._decodefilterpats, filename, data)
787 if 'l' in flags:
787 if 'l' in flags:
788 self.wopener.symlink(data, filename)
788 self.wopener.symlink(data, filename)
789 else:
789 else:
790 self.wopener.write(filename, data)
790 self.wopener.write(filename, data)
791 if 'x' in flags:
791 if 'x' in flags:
792 util.setflags(self.wjoin(filename), False, True)
792 util.setflags(self.wjoin(filename), False, True)
793
793
794 def wwritedata(self, filename, data):
794 def wwritedata(self, filename, data):
795 return self._filter(self._decodefilterpats, filename, data)
795 return self._filter(self._decodefilterpats, filename, data)
796
796
797 def transaction(self, desc):
797 def transaction(self, desc):
798 tr = self._transref and self._transref() or None
798 tr = self._transref and self._transref() or None
799 if tr and tr.running():
799 if tr and tr.running():
800 return tr.nest()
800 return tr.nest()
801
801
802 # abort here if the journal already exists
802 # abort here if the journal already exists
803 if os.path.exists(self.sjoin("journal")):
803 if os.path.exists(self.sjoin("journal")):
804 raise error.RepoError(
804 raise error.RepoError(
805 _("abandoned transaction found - run hg recover"))
805 _("abandoned transaction found - run hg recover"))
806
806
807 self._writejournal(desc)
807 self._writejournal(desc)
808 renames = [(x, undoname(x)) for x in self._journalfiles()]
808 renames = [(x, undoname(x)) for x in self._journalfiles()]
809
809
810 tr = transaction.transaction(self.ui.warn, self.sopener,
810 tr = transaction.transaction(self.ui.warn, self.sopener,
811 self.sjoin("journal"),
811 self.sjoin("journal"),
812 aftertrans(renames),
812 aftertrans(renames),
813 self.store.createmode)
813 self.store.createmode)
814 self._transref = weakref.ref(tr)
814 self._transref = weakref.ref(tr)
815 return tr
815 return tr
816
816
817 def _journalfiles(self):
817 def _journalfiles(self):
818 return (self.sjoin('journal'), self.join('journal.dirstate'),
818 return (self.sjoin('journal'), self.join('journal.dirstate'),
819 self.join('journal.branch'), self.join('journal.desc'),
819 self.join('journal.branch'), self.join('journal.desc'),
820 self.join('journal.bookmarks'),
820 self.join('journal.bookmarks'),
821 self.sjoin('journal.phaseroots'))
821 self.sjoin('journal.phaseroots'))
822
822
823 def undofiles(self):
823 def undofiles(self):
824 return [undoname(x) for x in self._journalfiles()]
824 return [undoname(x) for x in self._journalfiles()]
825
825
826 def _writejournal(self, desc):
826 def _writejournal(self, desc):
827 self.opener.write("journal.dirstate",
827 self.opener.write("journal.dirstate",
828 self.opener.tryread("dirstate"))
828 self.opener.tryread("dirstate"))
829 self.opener.write("journal.branch",
829 self.opener.write("journal.branch",
830 encoding.fromlocal(self.dirstate.branch()))
830 encoding.fromlocal(self.dirstate.branch()))
831 self.opener.write("journal.desc",
831 self.opener.write("journal.desc",
832 "%d\n%s\n" % (len(self), desc))
832 "%d\n%s\n" % (len(self), desc))
833 self.opener.write("journal.bookmarks",
833 self.opener.write("journal.bookmarks",
834 self.opener.tryread("bookmarks"))
834 self.opener.tryread("bookmarks"))
835 self.sopener.write("journal.phaseroots",
835 self.sopener.write("journal.phaseroots",
836 self.sopener.tryread("phaseroots"))
836 self.sopener.tryread("phaseroots"))
837
837
838 def recover(self):
838 def recover(self):
839 lock = self.lock()
839 lock = self.lock()
840 try:
840 try:
841 if os.path.exists(self.sjoin("journal")):
841 if os.path.exists(self.sjoin("journal")):
842 self.ui.status(_("rolling back interrupted transaction\n"))
842 self.ui.status(_("rolling back interrupted transaction\n"))
843 transaction.rollback(self.sopener, self.sjoin("journal"),
843 transaction.rollback(self.sopener, self.sjoin("journal"),
844 self.ui.warn)
844 self.ui.warn)
845 self.invalidate()
845 self.invalidate()
846 return True
846 return True
847 else:
847 else:
848 self.ui.warn(_("no interrupted transaction available\n"))
848 self.ui.warn(_("no interrupted transaction available\n"))
849 return False
849 return False
850 finally:
850 finally:
851 lock.release()
851 lock.release()
852
852
853 def rollback(self, dryrun=False, force=False):
853 def rollback(self, dryrun=False, force=False):
854 wlock = lock = None
854 wlock = lock = None
855 try:
855 try:
856 wlock = self.wlock()
856 wlock = self.wlock()
857 lock = self.lock()
857 lock = self.lock()
858 if os.path.exists(self.sjoin("undo")):
858 if os.path.exists(self.sjoin("undo")):
859 return self._rollback(dryrun, force)
859 return self._rollback(dryrun, force)
860 else:
860 else:
861 self.ui.warn(_("no rollback information available\n"))
861 self.ui.warn(_("no rollback information available\n"))
862 return 1
862 return 1
863 finally:
863 finally:
864 release(lock, wlock)
864 release(lock, wlock)
865
865
866 @unfilteredmethod # Until we get smarter cache management
866 @unfilteredmethod # Until we get smarter cache management
867 def _rollback(self, dryrun, force):
867 def _rollback(self, dryrun, force):
868 ui = self.ui
868 ui = self.ui
869 try:
869 try:
870 args = self.opener.read('undo.desc').splitlines()
870 args = self.opener.read('undo.desc').splitlines()
871 (oldlen, desc, detail) = (int(args[0]), args[1], None)
871 (oldlen, desc, detail) = (int(args[0]), args[1], None)
872 if len(args) >= 3:
872 if len(args) >= 3:
873 detail = args[2]
873 detail = args[2]
874 oldtip = oldlen - 1
874 oldtip = oldlen - 1
875
875
876 if detail and ui.verbose:
876 if detail and ui.verbose:
877 msg = (_('repository tip rolled back to revision %s'
877 msg = (_('repository tip rolled back to revision %s'
878 ' (undo %s: %s)\n')
878 ' (undo %s: %s)\n')
879 % (oldtip, desc, detail))
879 % (oldtip, desc, detail))
880 else:
880 else:
881 msg = (_('repository tip rolled back to revision %s'
881 msg = (_('repository tip rolled back to revision %s'
882 ' (undo %s)\n')
882 ' (undo %s)\n')
883 % (oldtip, desc))
883 % (oldtip, desc))
884 except IOError:
884 except IOError:
885 msg = _('rolling back unknown transaction\n')
885 msg = _('rolling back unknown transaction\n')
886 desc = None
886 desc = None
887
887
888 if not force and self['.'] != self['tip'] and desc == 'commit':
888 if not force and self['.'] != self['tip'] and desc == 'commit':
889 raise util.Abort(
889 raise util.Abort(
890 _('rollback of last commit while not checked out '
890 _('rollback of last commit while not checked out '
891 'may lose data'), hint=_('use -f to force'))
891 'may lose data'), hint=_('use -f to force'))
892
892
893 ui.status(msg)
893 ui.status(msg)
894 if dryrun:
894 if dryrun:
895 return 0
895 return 0
896
896
897 parents = self.dirstate.parents()
897 parents = self.dirstate.parents()
898 self.destroying()
898 self.destroying()
899 transaction.rollback(self.sopener, self.sjoin('undo'), ui.warn)
899 transaction.rollback(self.sopener, self.sjoin('undo'), ui.warn)
900 if os.path.exists(self.join('undo.bookmarks')):
900 if os.path.exists(self.join('undo.bookmarks')):
901 util.rename(self.join('undo.bookmarks'),
901 util.rename(self.join('undo.bookmarks'),
902 self.join('bookmarks'))
902 self.join('bookmarks'))
903 if os.path.exists(self.sjoin('undo.phaseroots')):
903 if os.path.exists(self.sjoin('undo.phaseroots')):
904 util.rename(self.sjoin('undo.phaseroots'),
904 util.rename(self.sjoin('undo.phaseroots'),
905 self.sjoin('phaseroots'))
905 self.sjoin('phaseroots'))
906 self.invalidate()
906 self.invalidate()
907
907
908 parentgone = (parents[0] not in self.changelog.nodemap or
908 parentgone = (parents[0] not in self.changelog.nodemap or
909 parents[1] not in self.changelog.nodemap)
909 parents[1] not in self.changelog.nodemap)
910 if parentgone:
910 if parentgone:
911 util.rename(self.join('undo.dirstate'), self.join('dirstate'))
911 util.rename(self.join('undo.dirstate'), self.join('dirstate'))
912 try:
912 try:
913 branch = self.opener.read('undo.branch')
913 branch = self.opener.read('undo.branch')
914 self.dirstate.setbranch(encoding.tolocal(branch))
914 self.dirstate.setbranch(encoding.tolocal(branch))
915 except IOError:
915 except IOError:
916 ui.warn(_('named branch could not be reset: '
916 ui.warn(_('named branch could not be reset: '
917 'current branch is still \'%s\'\n')
917 'current branch is still \'%s\'\n')
918 % self.dirstate.branch())
918 % self.dirstate.branch())
919
919
920 self.dirstate.invalidate()
920 self.dirstate.invalidate()
921 parents = tuple([p.rev() for p in self.parents()])
921 parents = tuple([p.rev() for p in self.parents()])
922 if len(parents) > 1:
922 if len(parents) > 1:
923 ui.status(_('working directory now based on '
923 ui.status(_('working directory now based on '
924 'revisions %d and %d\n') % parents)
924 'revisions %d and %d\n') % parents)
925 else:
925 else:
926 ui.status(_('working directory now based on '
926 ui.status(_('working directory now based on '
927 'revision %d\n') % parents)
927 'revision %d\n') % parents)
928 # TODO: if we know which new heads may result from this rollback, pass
928 # TODO: if we know which new heads may result from this rollback, pass
929 # them to destroy(), which will prevent the branchhead cache from being
929 # them to destroy(), which will prevent the branchhead cache from being
930 # invalidated.
930 # invalidated.
931 self.destroyed()
931 self.destroyed()
932 return 0
932 return 0
933
933
934 def invalidatecaches(self):
934 def invalidatecaches(self):
935
935
936 if '_tagscache' in vars(self):
936 if '_tagscache' in vars(self):
937 # can't use delattr on proxy
937 # can't use delattr on proxy
938 del self.__dict__['_tagscache']
938 del self.__dict__['_tagscache']
939
939
940 self.unfiltered()._branchcaches.clear()
940 self.unfiltered()._branchcaches.clear()
941 self.invalidatevolatilesets()
941 self.invalidatevolatilesets()
942
942
943 def invalidatevolatilesets(self):
943 def invalidatevolatilesets(self):
944 self.filteredrevcache.clear()
944 self.filteredrevcache.clear()
945 obsolete.clearobscaches(self)
945 obsolete.clearobscaches(self)
946
946
947 def invalidatedirstate(self):
947 def invalidatedirstate(self):
948 '''Invalidates the dirstate, causing the next call to dirstate
948 '''Invalidates the dirstate, causing the next call to dirstate
949 to check if it was modified since the last time it was read,
949 to check if it was modified since the last time it was read,
950 rereading it if it has.
950 rereading it if it has.
951
951
952 This is different to dirstate.invalidate() that it doesn't always
952 This is different to dirstate.invalidate() that it doesn't always
953 rereads the dirstate. Use dirstate.invalidate() if you want to
953 rereads the dirstate. Use dirstate.invalidate() if you want to
954 explicitly read the dirstate again (i.e. restoring it to a previous
954 explicitly read the dirstate again (i.e. restoring it to a previous
955 known good state).'''
955 known good state).'''
956 if hasunfilteredcache(self, 'dirstate'):
956 if hasunfilteredcache(self, 'dirstate'):
957 for k in self.dirstate._filecache:
957 for k in self.dirstate._filecache:
958 try:
958 try:
959 delattr(self.dirstate, k)
959 delattr(self.dirstate, k)
960 except AttributeError:
960 except AttributeError:
961 pass
961 pass
962 delattr(self.unfiltered(), 'dirstate')
962 delattr(self.unfiltered(), 'dirstate')
963
963
964 def invalidate(self):
964 def invalidate(self):
965 unfiltered = self.unfiltered() # all file caches are stored unfiltered
965 unfiltered = self.unfiltered() # all file caches are stored unfiltered
966 for k in self._filecache:
966 for k in self._filecache:
967 # dirstate is invalidated separately in invalidatedirstate()
967 # dirstate is invalidated separately in invalidatedirstate()
968 if k == 'dirstate':
968 if k == 'dirstate':
969 continue
969 continue
970
970
971 try:
971 try:
972 delattr(unfiltered, k)
972 delattr(unfiltered, k)
973 except AttributeError:
973 except AttributeError:
974 pass
974 pass
975 self.invalidatecaches()
975 self.invalidatecaches()
976
976
977 def _lock(self, lockname, wait, releasefn, acquirefn, desc):
977 def _lock(self, lockname, wait, releasefn, acquirefn, desc):
978 try:
978 try:
979 l = lock.lock(lockname, 0, releasefn, desc=desc)
979 l = lock.lock(lockname, 0, releasefn, desc=desc)
980 except error.LockHeld, inst:
980 except error.LockHeld, inst:
981 if not wait:
981 if not wait:
982 raise
982 raise
983 self.ui.warn(_("waiting for lock on %s held by %r\n") %
983 self.ui.warn(_("waiting for lock on %s held by %r\n") %
984 (desc, inst.locker))
984 (desc, inst.locker))
985 # default to 600 seconds timeout
985 # default to 600 seconds timeout
986 l = lock.lock(lockname, int(self.ui.config("ui", "timeout", "600")),
986 l = lock.lock(lockname, int(self.ui.config("ui", "timeout", "600")),
987 releasefn, desc=desc)
987 releasefn, desc=desc)
988 if acquirefn:
988 if acquirefn:
989 acquirefn()
989 acquirefn()
990 return l
990 return l
991
991
992 def _afterlock(self, callback):
992 def _afterlock(self, callback):
993 """add a callback to the current repository lock.
993 """add a callback to the current repository lock.
994
994
995 The callback will be executed on lock release."""
995 The callback will be executed on lock release."""
996 l = self._lockref and self._lockref()
996 l = self._lockref and self._lockref()
997 if l:
997 if l:
998 l.postrelease.append(callback)
998 l.postrelease.append(callback)
999 else:
999 else:
1000 callback()
1000 callback()
1001
1001
1002 def lock(self, wait=True):
1002 def lock(self, wait=True):
1003 '''Lock the repository store (.hg/store) and return a weak reference
1003 '''Lock the repository store (.hg/store) and return a weak reference
1004 to the lock. Use this before modifying the store (e.g. committing or
1004 to the lock. Use this before modifying the store (e.g. committing or
1005 stripping). If you are opening a transaction, get a lock as well.)'''
1005 stripping). If you are opening a transaction, get a lock as well.)'''
1006 l = self._lockref and self._lockref()
1006 l = self._lockref and self._lockref()
1007 if l is not None and l.held:
1007 if l is not None and l.held:
1008 l.lock()
1008 l.lock()
1009 return l
1009 return l
1010
1010
1011 def unlock():
1011 def unlock():
1012 self.store.write()
1012 self.store.write()
1013 if hasunfilteredcache(self, '_phasecache'):
1013 if hasunfilteredcache(self, '_phasecache'):
1014 self._phasecache.write()
1014 self._phasecache.write()
1015 for k, ce in self._filecache.items():
1015 for k, ce in self._filecache.items():
1016 if k == 'dirstate' or k not in self.__dict__:
1016 if k == 'dirstate' or k not in self.__dict__:
1017 continue
1017 continue
1018 ce.refresh()
1018 ce.refresh()
1019
1019
1020 l = self._lock(self.sjoin("lock"), wait, unlock,
1020 l = self._lock(self.sjoin("lock"), wait, unlock,
1021 self.invalidate, _('repository %s') % self.origroot)
1021 self.invalidate, _('repository %s') % self.origroot)
1022 self._lockref = weakref.ref(l)
1022 self._lockref = weakref.ref(l)
1023 return l
1023 return l
1024
1024
1025 def wlock(self, wait=True):
1025 def wlock(self, wait=True):
1026 '''Lock the non-store parts of the repository (everything under
1026 '''Lock the non-store parts of the repository (everything under
1027 .hg except .hg/store) and return a weak reference to the lock.
1027 .hg except .hg/store) and return a weak reference to the lock.
1028 Use this before modifying files in .hg.'''
1028 Use this before modifying files in .hg.'''
1029 l = self._wlockref and self._wlockref()
1029 l = self._wlockref and self._wlockref()
1030 if l is not None and l.held:
1030 if l is not None and l.held:
1031 l.lock()
1031 l.lock()
1032 return l
1032 return l
1033
1033
1034 def unlock():
1034 def unlock():
1035 self.dirstate.write()
1035 self.dirstate.write()
1036 self._filecache['dirstate'].refresh()
1036 self._filecache['dirstate'].refresh()
1037
1037
1038 l = self._lock(self.join("wlock"), wait, unlock,
1038 l = self._lock(self.join("wlock"), wait, unlock,
1039 self.invalidatedirstate, _('working directory of %s') %
1039 self.invalidatedirstate, _('working directory of %s') %
1040 self.origroot)
1040 self.origroot)
1041 self._wlockref = weakref.ref(l)
1041 self._wlockref = weakref.ref(l)
1042 return l
1042 return l
1043
1043
1044 def _filecommit(self, fctx, manifest1, manifest2, linkrev, tr, changelist):
1044 def _filecommit(self, fctx, manifest1, manifest2, linkrev, tr, changelist):
1045 """
1045 """
1046 commit an individual file as part of a larger transaction
1046 commit an individual file as part of a larger transaction
1047 """
1047 """
1048
1048
1049 fname = fctx.path()
1049 fname = fctx.path()
1050 text = fctx.data()
1050 text = fctx.data()
1051 flog = self.file(fname)
1051 flog = self.file(fname)
1052 fparent1 = manifest1.get(fname, nullid)
1052 fparent1 = manifest1.get(fname, nullid)
1053 fparent2 = fparent2o = manifest2.get(fname, nullid)
1053 fparent2 = fparent2o = manifest2.get(fname, nullid)
1054
1054
1055 meta = {}
1055 meta = {}
1056 copy = fctx.renamed()
1056 copy = fctx.renamed()
1057 if copy and copy[0] != fname:
1057 if copy and copy[0] != fname:
1058 # Mark the new revision of this file as a copy of another
1058 # Mark the new revision of this file as a copy of another
1059 # file. This copy data will effectively act as a parent
1059 # file. This copy data will effectively act as a parent
1060 # of this new revision. If this is a merge, the first
1060 # of this new revision. If this is a merge, the first
1061 # parent will be the nullid (meaning "look up the copy data")
1061 # parent will be the nullid (meaning "look up the copy data")
1062 # and the second one will be the other parent. For example:
1062 # and the second one will be the other parent. For example:
1063 #
1063 #
1064 # 0 --- 1 --- 3 rev1 changes file foo
1064 # 0 --- 1 --- 3 rev1 changes file foo
1065 # \ / rev2 renames foo to bar and changes it
1065 # \ / rev2 renames foo to bar and changes it
1066 # \- 2 -/ rev3 should have bar with all changes and
1066 # \- 2 -/ rev3 should have bar with all changes and
1067 # should record that bar descends from
1067 # should record that bar descends from
1068 # bar in rev2 and foo in rev1
1068 # bar in rev2 and foo in rev1
1069 #
1069 #
1070 # this allows this merge to succeed:
1070 # this allows this merge to succeed:
1071 #
1071 #
1072 # 0 --- 1 --- 3 rev4 reverts the content change from rev2
1072 # 0 --- 1 --- 3 rev4 reverts the content change from rev2
1073 # \ / merging rev3 and rev4 should use bar@rev2
1073 # \ / merging rev3 and rev4 should use bar@rev2
1074 # \- 2 --- 4 as the merge base
1074 # \- 2 --- 4 as the merge base
1075 #
1075 #
1076
1076
1077 cfname = copy[0]
1077 cfname = copy[0]
1078 crev = manifest1.get(cfname)
1078 crev = manifest1.get(cfname)
1079 newfparent = fparent2
1079 newfparent = fparent2
1080
1080
1081 if manifest2: # branch merge
1081 if manifest2: # branch merge
1082 if fparent2 == nullid or crev is None: # copied on remote side
1082 if fparent2 == nullid or crev is None: # copied on remote side
1083 if cfname in manifest2:
1083 if cfname in manifest2:
1084 crev = manifest2[cfname]
1084 crev = manifest2[cfname]
1085 newfparent = fparent1
1085 newfparent = fparent1
1086
1086
1087 # find source in nearest ancestor if we've lost track
1087 # find source in nearest ancestor if we've lost track
1088 if not crev:
1088 if not crev:
1089 self.ui.debug(" %s: searching for copy revision for %s\n" %
1089 self.ui.debug(" %s: searching for copy revision for %s\n" %
1090 (fname, cfname))
1090 (fname, cfname))
1091 for ancestor in self[None].ancestors():
1091 for ancestor in self[None].ancestors():
1092 if cfname in ancestor:
1092 if cfname in ancestor:
1093 crev = ancestor[cfname].filenode()
1093 crev = ancestor[cfname].filenode()
1094 break
1094 break
1095
1095
1096 if crev:
1096 if crev:
1097 self.ui.debug(" %s: copy %s:%s\n" % (fname, cfname, hex(crev)))
1097 self.ui.debug(" %s: copy %s:%s\n" % (fname, cfname, hex(crev)))
1098 meta["copy"] = cfname
1098 meta["copy"] = cfname
1099 meta["copyrev"] = hex(crev)
1099 meta["copyrev"] = hex(crev)
1100 fparent1, fparent2 = nullid, newfparent
1100 fparent1, fparent2 = nullid, newfparent
1101 else:
1101 else:
1102 self.ui.warn(_("warning: can't find ancestor for '%s' "
1102 self.ui.warn(_("warning: can't find ancestor for '%s' "
1103 "copied from '%s'!\n") % (fname, cfname))
1103 "copied from '%s'!\n") % (fname, cfname))
1104
1104
1105 elif fparent2 != nullid:
1105 elif fparent2 != nullid:
1106 # is one parent an ancestor of the other?
1106 # is one parent an ancestor of the other?
1107 fparentancestor = flog.ancestor(fparent1, fparent2)
1107 fparentancestor = flog.ancestor(fparent1, fparent2)
1108 if fparentancestor == fparent1:
1108 if fparentancestor == fparent1:
1109 fparent1, fparent2 = fparent2, nullid
1109 fparent1, fparent2 = fparent2, nullid
1110 elif fparentancestor == fparent2:
1110 elif fparentancestor == fparent2:
1111 fparent2 = nullid
1111 fparent2 = nullid
1112
1112
1113 # is the file changed?
1113 # is the file changed?
1114 if fparent2 != nullid or flog.cmp(fparent1, text) or meta:
1114 if fparent2 != nullid or flog.cmp(fparent1, text) or meta:
1115 changelist.append(fname)
1115 changelist.append(fname)
1116 return flog.add(text, meta, tr, linkrev, fparent1, fparent2)
1116 return flog.add(text, meta, tr, linkrev, fparent1, fparent2)
1117
1117
1118 # are just the flags changed during merge?
1118 # are just the flags changed during merge?
1119 if fparent1 != fparent2o and manifest1.flags(fname) != fctx.flags():
1119 if fparent1 != fparent2o and manifest1.flags(fname) != fctx.flags():
1120 changelist.append(fname)
1120 changelist.append(fname)
1121
1121
1122 return fparent1
1122 return fparent1
1123
1123
1124 @unfilteredmethod
1124 @unfilteredmethod
1125 def commit(self, text="", user=None, date=None, match=None, force=False,
1125 def commit(self, text="", user=None, date=None, match=None, force=False,
1126 editor=False, extra={}):
1126 editor=False, extra={}):
1127 """Add a new revision to current repository.
1127 """Add a new revision to current repository.
1128
1128
1129 Revision information is gathered from the working directory,
1129 Revision information is gathered from the working directory,
1130 match can be used to filter the committed files. If editor is
1130 match can be used to filter the committed files. If editor is
1131 supplied, it is called to get a commit message.
1131 supplied, it is called to get a commit message.
1132 """
1132 """
1133
1133
1134 def fail(f, msg):
1134 def fail(f, msg):
1135 raise util.Abort('%s: %s' % (f, msg))
1135 raise util.Abort('%s: %s' % (f, msg))
1136
1136
1137 if not match:
1137 if not match:
1138 match = matchmod.always(self.root, '')
1138 match = matchmod.always(self.root, '')
1139
1139
1140 if not force:
1140 if not force:
1141 vdirs = []
1141 vdirs = []
1142 match.dir = vdirs.append
1142 match.dir = vdirs.append
1143 match.bad = fail
1143 match.bad = fail
1144
1144
1145 wlock = self.wlock()
1145 wlock = self.wlock()
1146 try:
1146 try:
1147 wctx = self[None]
1147 wctx = self[None]
1148 merge = len(wctx.parents()) > 1
1148 merge = len(wctx.parents()) > 1
1149
1149
1150 if (not force and merge and match and
1150 if (not force and merge and match and
1151 (match.files() or match.anypats())):
1151 (match.files() or match.anypats())):
1152 raise util.Abort(_('cannot partially commit a merge '
1152 raise util.Abort(_('cannot partially commit a merge '
1153 '(do not specify files or patterns)'))
1153 '(do not specify files or patterns)'))
1154
1154
1155 changes = self.status(match=match, clean=force)
1155 changes = self.status(match=match, clean=force)
1156 if force:
1156 if force:
1157 changes[0].extend(changes[6]) # mq may commit unchanged files
1157 changes[0].extend(changes[6]) # mq may commit unchanged files
1158
1158
1159 # check subrepos
1159 # check subrepos
1160 subs = []
1160 subs = []
1161 commitsubs = set()
1161 commitsubs = set()
1162 newstate = wctx.substate.copy()
1162 newstate = wctx.substate.copy()
1163 # only manage subrepos and .hgsubstate if .hgsub is present
1163 # only manage subrepos and .hgsubstate if .hgsub is present
1164 if '.hgsub' in wctx:
1164 if '.hgsub' in wctx:
1165 # we'll decide whether to track this ourselves, thanks
1165 # we'll decide whether to track this ourselves, thanks
1166 if '.hgsubstate' in changes[0]:
1166 if '.hgsubstate' in changes[0]:
1167 changes[0].remove('.hgsubstate')
1167 changes[0].remove('.hgsubstate')
1168 if '.hgsubstate' in changes[2]:
1168 if '.hgsubstate' in changes[2]:
1169 changes[2].remove('.hgsubstate')
1169 changes[2].remove('.hgsubstate')
1170
1170
1171 # compare current state to last committed state
1171 # compare current state to last committed state
1172 # build new substate based on last committed state
1172 # build new substate based on last committed state
1173 oldstate = wctx.p1().substate
1173 oldstate = wctx.p1().substate
1174 for s in sorted(newstate.keys()):
1174 for s in sorted(newstate.keys()):
1175 if not match(s):
1175 if not match(s):
1176 # ignore working copy, use old state if present
1176 # ignore working copy, use old state if present
1177 if s in oldstate:
1177 if s in oldstate:
1178 newstate[s] = oldstate[s]
1178 newstate[s] = oldstate[s]
1179 continue
1179 continue
1180 if not force:
1180 if not force:
1181 raise util.Abort(
1181 raise util.Abort(
1182 _("commit with new subrepo %s excluded") % s)
1182 _("commit with new subrepo %s excluded") % s)
1183 if wctx.sub(s).dirty(True):
1183 if wctx.sub(s).dirty(True):
1184 if not self.ui.configbool('ui', 'commitsubrepos'):
1184 if not self.ui.configbool('ui', 'commitsubrepos'):
1185 raise util.Abort(
1185 raise util.Abort(
1186 _("uncommitted changes in subrepo %s") % s,
1186 _("uncommitted changes in subrepo %s") % s,
1187 hint=_("use --subrepos for recursive commit"))
1187 hint=_("use --subrepos for recursive commit"))
1188 subs.append(s)
1188 subs.append(s)
1189 commitsubs.add(s)
1189 commitsubs.add(s)
1190 else:
1190 else:
1191 bs = wctx.sub(s).basestate()
1191 bs = wctx.sub(s).basestate()
1192 newstate[s] = (newstate[s][0], bs, newstate[s][2])
1192 newstate[s] = (newstate[s][0], bs, newstate[s][2])
1193 if oldstate.get(s, (None, None, None))[1] != bs:
1193 if oldstate.get(s, (None, None, None))[1] != bs:
1194 subs.append(s)
1194 subs.append(s)
1195
1195
1196 # check for removed subrepos
1196 # check for removed subrepos
1197 for p in wctx.parents():
1197 for p in wctx.parents():
1198 r = [s for s in p.substate if s not in newstate]
1198 r = [s for s in p.substate if s not in newstate]
1199 subs += [s for s in r if match(s)]
1199 subs += [s for s in r if match(s)]
1200 if subs:
1200 if subs:
1201 if (not match('.hgsub') and
1201 if (not match('.hgsub') and
1202 '.hgsub' in (wctx.modified() + wctx.added())):
1202 '.hgsub' in (wctx.modified() + wctx.added())):
1203 raise util.Abort(
1203 raise util.Abort(
1204 _("can't commit subrepos without .hgsub"))
1204 _("can't commit subrepos without .hgsub"))
1205 changes[0].insert(0, '.hgsubstate')
1205 changes[0].insert(0, '.hgsubstate')
1206
1206
1207 elif '.hgsub' in changes[2]:
1207 elif '.hgsub' in changes[2]:
1208 # clean up .hgsubstate when .hgsub is removed
1208 # clean up .hgsubstate when .hgsub is removed
1209 if ('.hgsubstate' in wctx and
1209 if ('.hgsubstate' in wctx and
1210 '.hgsubstate' not in changes[0] + changes[1] + changes[2]):
1210 '.hgsubstate' not in changes[0] + changes[1] + changes[2]):
1211 changes[2].insert(0, '.hgsubstate')
1211 changes[2].insert(0, '.hgsubstate')
1212
1212
1213 # make sure all explicit patterns are matched
1213 # make sure all explicit patterns are matched
1214 if not force and match.files():
1214 if not force and match.files():
1215 matched = set(changes[0] + changes[1] + changes[2])
1215 matched = set(changes[0] + changes[1] + changes[2])
1216
1216
1217 for f in match.files():
1217 for f in match.files():
1218 f = self.dirstate.normalize(f)
1218 f = self.dirstate.normalize(f)
1219 if f == '.' or f in matched or f in wctx.substate:
1219 if f == '.' or f in matched or f in wctx.substate:
1220 continue
1220 continue
1221 if f in changes[3]: # missing
1221 if f in changes[3]: # missing
1222 fail(f, _('file not found!'))
1222 fail(f, _('file not found!'))
1223 if f in vdirs: # visited directory
1223 if f in vdirs: # visited directory
1224 d = f + '/'
1224 d = f + '/'
1225 for mf in matched:
1225 for mf in matched:
1226 if mf.startswith(d):
1226 if mf.startswith(d):
1227 break
1227 break
1228 else:
1228 else:
1229 fail(f, _("no match under directory!"))
1229 fail(f, _("no match under directory!"))
1230 elif f not in self.dirstate:
1230 elif f not in self.dirstate:
1231 fail(f, _("file not tracked!"))
1231 fail(f, _("file not tracked!"))
1232
1232
1233 cctx = context.workingctx(self, text, user, date, extra, changes)
1233 cctx = context.workingctx(self, text, user, date, extra, changes)
1234
1234
1235 if (not force and not extra.get("close") and not merge
1235 if (not force and not extra.get("close") and not merge
1236 and not cctx.files()
1236 and not cctx.files()
1237 and wctx.branch() == wctx.p1().branch()):
1237 and wctx.branch() == wctx.p1().branch()):
1238 return None
1238 return None
1239
1239
1240 if merge and cctx.deleted():
1240 if merge and cctx.deleted():
1241 raise util.Abort(_("cannot commit merge with missing files"))
1241 raise util.Abort(_("cannot commit merge with missing files"))
1242
1242
1243 ms = mergemod.mergestate(self)
1243 ms = mergemod.mergestate(self)
1244 for f in changes[0]:
1244 for f in changes[0]:
1245 if f in ms and ms[f] == 'u':
1245 if f in ms and ms[f] == 'u':
1246 raise util.Abort(_("unresolved merge conflicts "
1246 raise util.Abort(_("unresolved merge conflicts "
1247 "(see hg help resolve)"))
1247 "(see hg help resolve)"))
1248
1248
1249 if editor:
1249 if editor:
1250 cctx._text = editor(self, cctx, subs)
1250 cctx._text = editor(self, cctx, subs)
1251 edited = (text != cctx._text)
1251 edited = (text != cctx._text)
1252
1252
1253 # commit subs and write new state
1253 # commit subs and write new state
1254 if subs:
1254 if subs:
1255 for s in sorted(commitsubs):
1255 for s in sorted(commitsubs):
1256 sub = wctx.sub(s)
1256 sub = wctx.sub(s)
1257 self.ui.status(_('committing subrepository %s\n') %
1257 self.ui.status(_('committing subrepository %s\n') %
1258 subrepo.subrelpath(sub))
1258 subrepo.subrelpath(sub))
1259 sr = sub.commit(cctx._text, user, date)
1259 sr = sub.commit(cctx._text, user, date)
1260 newstate[s] = (newstate[s][0], sr)
1260 newstate[s] = (newstate[s][0], sr)
1261 subrepo.writestate(self, newstate)
1261 subrepo.writestate(self, newstate)
1262
1262
1263 # Save commit message in case this transaction gets rolled back
1263 # Save commit message in case this transaction gets rolled back
1264 # (e.g. by a pretxncommit hook). Leave the content alone on
1264 # (e.g. by a pretxncommit hook). Leave the content alone on
1265 # the assumption that the user will use the same editor again.
1265 # the assumption that the user will use the same editor again.
1266 msgfn = self.savecommitmessage(cctx._text)
1266 msgfn = self.savecommitmessage(cctx._text)
1267
1267
1268 p1, p2 = self.dirstate.parents()
1268 p1, p2 = self.dirstate.parents()
1269 hookp1, hookp2 = hex(p1), (p2 != nullid and hex(p2) or '')
1269 hookp1, hookp2 = hex(p1), (p2 != nullid and hex(p2) or '')
1270 try:
1270 try:
1271 self.hook("precommit", throw=True, parent1=hookp1,
1271 self.hook("precommit", throw=True, parent1=hookp1,
1272 parent2=hookp2)
1272 parent2=hookp2)
1273 ret = self.commitctx(cctx, True)
1273 ret = self.commitctx(cctx, True)
1274 except: # re-raises
1274 except: # re-raises
1275 if edited:
1275 if edited:
1276 self.ui.write(
1276 self.ui.write(
1277 _('note: commit message saved in %s\n') % msgfn)
1277 _('note: commit message saved in %s\n') % msgfn)
1278 raise
1278 raise
1279
1279
1280 # update bookmarks, dirstate and mergestate
1280 # update bookmarks, dirstate and mergestate
1281 bookmarks.update(self, [p1, p2], ret)
1281 bookmarks.update(self, [p1, p2], ret)
1282 for f in changes[0] + changes[1]:
1282 cctx.markcommitted(ret)
1283 self.dirstate.normal(f)
1284 for f in changes[2]:
1285 self.dirstate.drop(f)
1286 self.dirstate.setparents(ret)
1287 ms.reset()
1283 ms.reset()
1288 finally:
1284 finally:
1289 wlock.release()
1285 wlock.release()
1290
1286
1291 def commithook(node=hex(ret), parent1=hookp1, parent2=hookp2):
1287 def commithook(node=hex(ret), parent1=hookp1, parent2=hookp2):
1292 self.hook("commit", node=node, parent1=parent1, parent2=parent2)
1288 self.hook("commit", node=node, parent1=parent1, parent2=parent2)
1293 self._afterlock(commithook)
1289 self._afterlock(commithook)
1294 return ret
1290 return ret
1295
1291
1296 @unfilteredmethod
1292 @unfilteredmethod
1297 def commitctx(self, ctx, error=False):
1293 def commitctx(self, ctx, error=False):
1298 """Add a new revision to current repository.
1294 """Add a new revision to current repository.
1299 Revision information is passed via the context argument.
1295 Revision information is passed via the context argument.
1300 """
1296 """
1301
1297
1302 tr = lock = None
1298 tr = lock = None
1303 removed = list(ctx.removed())
1299 removed = list(ctx.removed())
1304 p1, p2 = ctx.p1(), ctx.p2()
1300 p1, p2 = ctx.p1(), ctx.p2()
1305 user = ctx.user()
1301 user = ctx.user()
1306
1302
1307 lock = self.lock()
1303 lock = self.lock()
1308 try:
1304 try:
1309 tr = self.transaction("commit")
1305 tr = self.transaction("commit")
1310 trp = weakref.proxy(tr)
1306 trp = weakref.proxy(tr)
1311
1307
1312 if ctx.files():
1308 if ctx.files():
1313 m1 = p1.manifest().copy()
1309 m1 = p1.manifest().copy()
1314 m2 = p2.manifest()
1310 m2 = p2.manifest()
1315
1311
1316 # check in files
1312 # check in files
1317 new = {}
1313 new = {}
1318 changed = []
1314 changed = []
1319 linkrev = len(self)
1315 linkrev = len(self)
1320 for f in sorted(ctx.modified() + ctx.added()):
1316 for f in sorted(ctx.modified() + ctx.added()):
1321 self.ui.note(f + "\n")
1317 self.ui.note(f + "\n")
1322 try:
1318 try:
1323 fctx = ctx[f]
1319 fctx = ctx[f]
1324 new[f] = self._filecommit(fctx, m1, m2, linkrev, trp,
1320 new[f] = self._filecommit(fctx, m1, m2, linkrev, trp,
1325 changed)
1321 changed)
1326 m1.set(f, fctx.flags())
1322 m1.set(f, fctx.flags())
1327 except OSError, inst:
1323 except OSError, inst:
1328 self.ui.warn(_("trouble committing %s!\n") % f)
1324 self.ui.warn(_("trouble committing %s!\n") % f)
1329 raise
1325 raise
1330 except IOError, inst:
1326 except IOError, inst:
1331 errcode = getattr(inst, 'errno', errno.ENOENT)
1327 errcode = getattr(inst, 'errno', errno.ENOENT)
1332 if error or errcode and errcode != errno.ENOENT:
1328 if error or errcode and errcode != errno.ENOENT:
1333 self.ui.warn(_("trouble committing %s!\n") % f)
1329 self.ui.warn(_("trouble committing %s!\n") % f)
1334 raise
1330 raise
1335 else:
1331 else:
1336 removed.append(f)
1332 removed.append(f)
1337
1333
1338 # update manifest
1334 # update manifest
1339 m1.update(new)
1335 m1.update(new)
1340 removed = [f for f in sorted(removed) if f in m1 or f in m2]
1336 removed = [f for f in sorted(removed) if f in m1 or f in m2]
1341 drop = [f for f in removed if f in m1]
1337 drop = [f for f in removed if f in m1]
1342 for f in drop:
1338 for f in drop:
1343 del m1[f]
1339 del m1[f]
1344 mn = self.manifest.add(m1, trp, linkrev, p1.manifestnode(),
1340 mn = self.manifest.add(m1, trp, linkrev, p1.manifestnode(),
1345 p2.manifestnode(), (new, drop))
1341 p2.manifestnode(), (new, drop))
1346 files = changed + removed
1342 files = changed + removed
1347 else:
1343 else:
1348 mn = p1.manifestnode()
1344 mn = p1.manifestnode()
1349 files = []
1345 files = []
1350
1346
1351 # update changelog
1347 # update changelog
1352 self.changelog.delayupdate()
1348 self.changelog.delayupdate()
1353 n = self.changelog.add(mn, files, ctx.description(),
1349 n = self.changelog.add(mn, files, ctx.description(),
1354 trp, p1.node(), p2.node(),
1350 trp, p1.node(), p2.node(),
1355 user, ctx.date(), ctx.extra().copy())
1351 user, ctx.date(), ctx.extra().copy())
1356 p = lambda: self.changelog.writepending() and self.root or ""
1352 p = lambda: self.changelog.writepending() and self.root or ""
1357 xp1, xp2 = p1.hex(), p2 and p2.hex() or ''
1353 xp1, xp2 = p1.hex(), p2 and p2.hex() or ''
1358 self.hook('pretxncommit', throw=True, node=hex(n), parent1=xp1,
1354 self.hook('pretxncommit', throw=True, node=hex(n), parent1=xp1,
1359 parent2=xp2, pending=p)
1355 parent2=xp2, pending=p)
1360 self.changelog.finalize(trp)
1356 self.changelog.finalize(trp)
1361 # set the new commit is proper phase
1357 # set the new commit is proper phase
1362 targetphase = phases.newcommitphase(self.ui)
1358 targetphase = phases.newcommitphase(self.ui)
1363 if targetphase:
1359 if targetphase:
1364 # retract boundary do not alter parent changeset.
1360 # retract boundary do not alter parent changeset.
1365 # if a parent have higher the resulting phase will
1361 # if a parent have higher the resulting phase will
1366 # be compliant anyway
1362 # be compliant anyway
1367 #
1363 #
1368 # if minimal phase was 0 we don't need to retract anything
1364 # if minimal phase was 0 we don't need to retract anything
1369 phases.retractboundary(self, targetphase, [n])
1365 phases.retractboundary(self, targetphase, [n])
1370 tr.close()
1366 tr.close()
1371 branchmap.updatecache(self.filtered('served'))
1367 branchmap.updatecache(self.filtered('served'))
1372 return n
1368 return n
1373 finally:
1369 finally:
1374 if tr:
1370 if tr:
1375 tr.release()
1371 tr.release()
1376 lock.release()
1372 lock.release()
1377
1373
1378 @unfilteredmethod
1374 @unfilteredmethod
1379 def destroying(self):
1375 def destroying(self):
1380 '''Inform the repository that nodes are about to be destroyed.
1376 '''Inform the repository that nodes are about to be destroyed.
1381 Intended for use by strip and rollback, so there's a common
1377 Intended for use by strip and rollback, so there's a common
1382 place for anything that has to be done before destroying history.
1378 place for anything that has to be done before destroying history.
1383
1379
1384 This is mostly useful for saving state that is in memory and waiting
1380 This is mostly useful for saving state that is in memory and waiting
1385 to be flushed when the current lock is released. Because a call to
1381 to be flushed when the current lock is released. Because a call to
1386 destroyed is imminent, the repo will be invalidated causing those
1382 destroyed is imminent, the repo will be invalidated causing those
1387 changes to stay in memory (waiting for the next unlock), or vanish
1383 changes to stay in memory (waiting for the next unlock), or vanish
1388 completely.
1384 completely.
1389 '''
1385 '''
1390 # When using the same lock to commit and strip, the phasecache is left
1386 # When using the same lock to commit and strip, the phasecache is left
1391 # dirty after committing. Then when we strip, the repo is invalidated,
1387 # dirty after committing. Then when we strip, the repo is invalidated,
1392 # causing those changes to disappear.
1388 # causing those changes to disappear.
1393 if '_phasecache' in vars(self):
1389 if '_phasecache' in vars(self):
1394 self._phasecache.write()
1390 self._phasecache.write()
1395
1391
1396 @unfilteredmethod
1392 @unfilteredmethod
1397 def destroyed(self):
1393 def destroyed(self):
1398 '''Inform the repository that nodes have been destroyed.
1394 '''Inform the repository that nodes have been destroyed.
1399 Intended for use by strip and rollback, so there's a common
1395 Intended for use by strip and rollback, so there's a common
1400 place for anything that has to be done after destroying history.
1396 place for anything that has to be done after destroying history.
1401 '''
1397 '''
1402 # When one tries to:
1398 # When one tries to:
1403 # 1) destroy nodes thus calling this method (e.g. strip)
1399 # 1) destroy nodes thus calling this method (e.g. strip)
1404 # 2) use phasecache somewhere (e.g. commit)
1400 # 2) use phasecache somewhere (e.g. commit)
1405 #
1401 #
1406 # then 2) will fail because the phasecache contains nodes that were
1402 # then 2) will fail because the phasecache contains nodes that were
1407 # removed. We can either remove phasecache from the filecache,
1403 # removed. We can either remove phasecache from the filecache,
1408 # causing it to reload next time it is accessed, or simply filter
1404 # causing it to reload next time it is accessed, or simply filter
1409 # the removed nodes now and write the updated cache.
1405 # the removed nodes now and write the updated cache.
1410 if '_phasecache' in self._filecache:
1406 if '_phasecache' in self._filecache:
1411 self._phasecache.filterunknown(self)
1407 self._phasecache.filterunknown(self)
1412 self._phasecache.write()
1408 self._phasecache.write()
1413
1409
1414 # update the 'served' branch cache to help read only server process
1410 # update the 'served' branch cache to help read only server process
1415 # Thanks to branchcache collaboration this is done from the nearest
1411 # Thanks to branchcache collaboration this is done from the nearest
1416 # filtered subset and it is expected to be fast.
1412 # filtered subset and it is expected to be fast.
1417 branchmap.updatecache(self.filtered('served'))
1413 branchmap.updatecache(self.filtered('served'))
1418
1414
1419 # Ensure the persistent tag cache is updated. Doing it now
1415 # Ensure the persistent tag cache is updated. Doing it now
1420 # means that the tag cache only has to worry about destroyed
1416 # means that the tag cache only has to worry about destroyed
1421 # heads immediately after a strip/rollback. That in turn
1417 # heads immediately after a strip/rollback. That in turn
1422 # guarantees that "cachetip == currenttip" (comparing both rev
1418 # guarantees that "cachetip == currenttip" (comparing both rev
1423 # and node) always means no nodes have been added or destroyed.
1419 # and node) always means no nodes have been added or destroyed.
1424
1420
1425 # XXX this is suboptimal when qrefresh'ing: we strip the current
1421 # XXX this is suboptimal when qrefresh'ing: we strip the current
1426 # head, refresh the tag cache, then immediately add a new head.
1422 # head, refresh the tag cache, then immediately add a new head.
1427 # But I think doing it this way is necessary for the "instant
1423 # But I think doing it this way is necessary for the "instant
1428 # tag cache retrieval" case to work.
1424 # tag cache retrieval" case to work.
1429 self.invalidate()
1425 self.invalidate()
1430
1426
1431 def walk(self, match, node=None):
1427 def walk(self, match, node=None):
1432 '''
1428 '''
1433 walk recursively through the directory tree or a given
1429 walk recursively through the directory tree or a given
1434 changeset, finding all files matched by the match
1430 changeset, finding all files matched by the match
1435 function
1431 function
1436 '''
1432 '''
1437 return self[node].walk(match)
1433 return self[node].walk(match)
1438
1434
1439 def status(self, node1='.', node2=None, match=None,
1435 def status(self, node1='.', node2=None, match=None,
1440 ignored=False, clean=False, unknown=False,
1436 ignored=False, clean=False, unknown=False,
1441 listsubrepos=False):
1437 listsubrepos=False):
1442 """return status of files between two nodes or node and working
1438 """return status of files between two nodes or node and working
1443 directory.
1439 directory.
1444
1440
1445 If node1 is None, use the first dirstate parent instead.
1441 If node1 is None, use the first dirstate parent instead.
1446 If node2 is None, compare node1 with working directory.
1442 If node2 is None, compare node1 with working directory.
1447 """
1443 """
1448
1444
1449 def mfmatches(ctx):
1445 def mfmatches(ctx):
1450 mf = ctx.manifest().copy()
1446 mf = ctx.manifest().copy()
1451 if match.always():
1447 if match.always():
1452 return mf
1448 return mf
1453 for fn in mf.keys():
1449 for fn in mf.keys():
1454 if not match(fn):
1450 if not match(fn):
1455 del mf[fn]
1451 del mf[fn]
1456 return mf
1452 return mf
1457
1453
1458 if isinstance(node1, context.changectx):
1454 if isinstance(node1, context.changectx):
1459 ctx1 = node1
1455 ctx1 = node1
1460 else:
1456 else:
1461 ctx1 = self[node1]
1457 ctx1 = self[node1]
1462 if isinstance(node2, context.changectx):
1458 if isinstance(node2, context.changectx):
1463 ctx2 = node2
1459 ctx2 = node2
1464 else:
1460 else:
1465 ctx2 = self[node2]
1461 ctx2 = self[node2]
1466
1462
1467 working = ctx2.rev() is None
1463 working = ctx2.rev() is None
1468 parentworking = working and ctx1 == self['.']
1464 parentworking = working and ctx1 == self['.']
1469 match = match or matchmod.always(self.root, self.getcwd())
1465 match = match or matchmod.always(self.root, self.getcwd())
1470 listignored, listclean, listunknown = ignored, clean, unknown
1466 listignored, listclean, listunknown = ignored, clean, unknown
1471
1467
1472 # load earliest manifest first for caching reasons
1468 # load earliest manifest first for caching reasons
1473 if not working and ctx2.rev() < ctx1.rev():
1469 if not working and ctx2.rev() < ctx1.rev():
1474 ctx2.manifest()
1470 ctx2.manifest()
1475
1471
1476 if not parentworking:
1472 if not parentworking:
1477 def bad(f, msg):
1473 def bad(f, msg):
1478 # 'f' may be a directory pattern from 'match.files()',
1474 # 'f' may be a directory pattern from 'match.files()',
1479 # so 'f not in ctx1' is not enough
1475 # so 'f not in ctx1' is not enough
1480 if f not in ctx1 and f not in ctx1.dirs():
1476 if f not in ctx1 and f not in ctx1.dirs():
1481 self.ui.warn('%s: %s\n' % (self.dirstate.pathto(f), msg))
1477 self.ui.warn('%s: %s\n' % (self.dirstate.pathto(f), msg))
1482 match.bad = bad
1478 match.bad = bad
1483
1479
1484 if working: # we need to scan the working dir
1480 if working: # we need to scan the working dir
1485 subrepos = []
1481 subrepos = []
1486 if '.hgsub' in self.dirstate:
1482 if '.hgsub' in self.dirstate:
1487 subrepos = sorted(ctx2.substate)
1483 subrepos = sorted(ctx2.substate)
1488 s = self.dirstate.status(match, subrepos, listignored,
1484 s = self.dirstate.status(match, subrepos, listignored,
1489 listclean, listunknown)
1485 listclean, listunknown)
1490 cmp, modified, added, removed, deleted, unknown, ignored, clean = s
1486 cmp, modified, added, removed, deleted, unknown, ignored, clean = s
1491
1487
1492 # check for any possibly clean files
1488 # check for any possibly clean files
1493 if parentworking and cmp:
1489 if parentworking and cmp:
1494 fixup = []
1490 fixup = []
1495 # do a full compare of any files that might have changed
1491 # do a full compare of any files that might have changed
1496 for f in sorted(cmp):
1492 for f in sorted(cmp):
1497 if (f not in ctx1 or ctx2.flags(f) != ctx1.flags(f)
1493 if (f not in ctx1 or ctx2.flags(f) != ctx1.flags(f)
1498 or ctx1[f].cmp(ctx2[f])):
1494 or ctx1[f].cmp(ctx2[f])):
1499 modified.append(f)
1495 modified.append(f)
1500 else:
1496 else:
1501 fixup.append(f)
1497 fixup.append(f)
1502
1498
1503 # update dirstate for files that are actually clean
1499 # update dirstate for files that are actually clean
1504 if fixup:
1500 if fixup:
1505 if listclean:
1501 if listclean:
1506 clean += fixup
1502 clean += fixup
1507
1503
1508 try:
1504 try:
1509 # updating the dirstate is optional
1505 # updating the dirstate is optional
1510 # so we don't wait on the lock
1506 # so we don't wait on the lock
1511 wlock = self.wlock(False)
1507 wlock = self.wlock(False)
1512 try:
1508 try:
1513 for f in fixup:
1509 for f in fixup:
1514 self.dirstate.normal(f)
1510 self.dirstate.normal(f)
1515 finally:
1511 finally:
1516 wlock.release()
1512 wlock.release()
1517 except error.LockError:
1513 except error.LockError:
1518 pass
1514 pass
1519
1515
1520 if not parentworking:
1516 if not parentworking:
1521 mf1 = mfmatches(ctx1)
1517 mf1 = mfmatches(ctx1)
1522 if working:
1518 if working:
1523 # we are comparing working dir against non-parent
1519 # we are comparing working dir against non-parent
1524 # generate a pseudo-manifest for the working dir
1520 # generate a pseudo-manifest for the working dir
1525 mf2 = mfmatches(self['.'])
1521 mf2 = mfmatches(self['.'])
1526 for f in cmp + modified + added:
1522 for f in cmp + modified + added:
1527 mf2[f] = None
1523 mf2[f] = None
1528 mf2.set(f, ctx2.flags(f))
1524 mf2.set(f, ctx2.flags(f))
1529 for f in removed:
1525 for f in removed:
1530 if f in mf2:
1526 if f in mf2:
1531 del mf2[f]
1527 del mf2[f]
1532 else:
1528 else:
1533 # we are comparing two revisions
1529 # we are comparing two revisions
1534 deleted, unknown, ignored = [], [], []
1530 deleted, unknown, ignored = [], [], []
1535 mf2 = mfmatches(ctx2)
1531 mf2 = mfmatches(ctx2)
1536
1532
1537 modified, added, clean = [], [], []
1533 modified, added, clean = [], [], []
1538 withflags = mf1.withflags() | mf2.withflags()
1534 withflags = mf1.withflags() | mf2.withflags()
1539 for fn in mf2:
1535 for fn in mf2:
1540 if fn in mf1:
1536 if fn in mf1:
1541 if (fn not in deleted and
1537 if (fn not in deleted and
1542 ((fn in withflags and mf1.flags(fn) != mf2.flags(fn)) or
1538 ((fn in withflags and mf1.flags(fn) != mf2.flags(fn)) or
1543 (mf1[fn] != mf2[fn] and
1539 (mf1[fn] != mf2[fn] and
1544 (mf2[fn] or ctx1[fn].cmp(ctx2[fn]))))):
1540 (mf2[fn] or ctx1[fn].cmp(ctx2[fn]))))):
1545 modified.append(fn)
1541 modified.append(fn)
1546 elif listclean:
1542 elif listclean:
1547 clean.append(fn)
1543 clean.append(fn)
1548 del mf1[fn]
1544 del mf1[fn]
1549 elif fn not in deleted:
1545 elif fn not in deleted:
1550 added.append(fn)
1546 added.append(fn)
1551 removed = mf1.keys()
1547 removed = mf1.keys()
1552
1548
1553 if working and modified and not self.dirstate._checklink:
1549 if working and modified and not self.dirstate._checklink:
1554 # Symlink placeholders may get non-symlink-like contents
1550 # Symlink placeholders may get non-symlink-like contents
1555 # via user error or dereferencing by NFS or Samba servers,
1551 # via user error or dereferencing by NFS or Samba servers,
1556 # so we filter out any placeholders that don't look like a
1552 # so we filter out any placeholders that don't look like a
1557 # symlink
1553 # symlink
1558 sane = []
1554 sane = []
1559 for f in modified:
1555 for f in modified:
1560 if ctx2.flags(f) == 'l':
1556 if ctx2.flags(f) == 'l':
1561 d = ctx2[f].data()
1557 d = ctx2[f].data()
1562 if len(d) >= 1024 or '\n' in d or util.binary(d):
1558 if len(d) >= 1024 or '\n' in d or util.binary(d):
1563 self.ui.debug('ignoring suspect symlink placeholder'
1559 self.ui.debug('ignoring suspect symlink placeholder'
1564 ' "%s"\n' % f)
1560 ' "%s"\n' % f)
1565 continue
1561 continue
1566 sane.append(f)
1562 sane.append(f)
1567 modified = sane
1563 modified = sane
1568
1564
1569 r = modified, added, removed, deleted, unknown, ignored, clean
1565 r = modified, added, removed, deleted, unknown, ignored, clean
1570
1566
1571 if listsubrepos:
1567 if listsubrepos:
1572 for subpath, sub in subrepo.itersubrepos(ctx1, ctx2):
1568 for subpath, sub in subrepo.itersubrepos(ctx1, ctx2):
1573 if working:
1569 if working:
1574 rev2 = None
1570 rev2 = None
1575 else:
1571 else:
1576 rev2 = ctx2.substate[subpath][1]
1572 rev2 = ctx2.substate[subpath][1]
1577 try:
1573 try:
1578 submatch = matchmod.narrowmatcher(subpath, match)
1574 submatch = matchmod.narrowmatcher(subpath, match)
1579 s = sub.status(rev2, match=submatch, ignored=listignored,
1575 s = sub.status(rev2, match=submatch, ignored=listignored,
1580 clean=listclean, unknown=listunknown,
1576 clean=listclean, unknown=listunknown,
1581 listsubrepos=True)
1577 listsubrepos=True)
1582 for rfiles, sfiles in zip(r, s):
1578 for rfiles, sfiles in zip(r, s):
1583 rfiles.extend("%s/%s" % (subpath, f) for f in sfiles)
1579 rfiles.extend("%s/%s" % (subpath, f) for f in sfiles)
1584 except error.LookupError:
1580 except error.LookupError:
1585 self.ui.status(_("skipping missing subrepository: %s\n")
1581 self.ui.status(_("skipping missing subrepository: %s\n")
1586 % subpath)
1582 % subpath)
1587
1583
1588 for l in r:
1584 for l in r:
1589 l.sort()
1585 l.sort()
1590 return r
1586 return r
1591
1587
1592 def heads(self, start=None):
1588 def heads(self, start=None):
1593 heads = self.changelog.heads(start)
1589 heads = self.changelog.heads(start)
1594 # sort the output in rev descending order
1590 # sort the output in rev descending order
1595 return sorted(heads, key=self.changelog.rev, reverse=True)
1591 return sorted(heads, key=self.changelog.rev, reverse=True)
1596
1592
1597 def branchheads(self, branch=None, start=None, closed=False):
1593 def branchheads(self, branch=None, start=None, closed=False):
1598 '''return a (possibly filtered) list of heads for the given branch
1594 '''return a (possibly filtered) list of heads for the given branch
1599
1595
1600 Heads are returned in topological order, from newest to oldest.
1596 Heads are returned in topological order, from newest to oldest.
1601 If branch is None, use the dirstate branch.
1597 If branch is None, use the dirstate branch.
1602 If start is not None, return only heads reachable from start.
1598 If start is not None, return only heads reachable from start.
1603 If closed is True, return heads that are marked as closed as well.
1599 If closed is True, return heads that are marked as closed as well.
1604 '''
1600 '''
1605 if branch is None:
1601 if branch is None:
1606 branch = self[None].branch()
1602 branch = self[None].branch()
1607 branches = self.branchmap()
1603 branches = self.branchmap()
1608 if branch not in branches:
1604 if branch not in branches:
1609 return []
1605 return []
1610 # the cache returns heads ordered lowest to highest
1606 # the cache returns heads ordered lowest to highest
1611 bheads = list(reversed(branches[branch]))
1607 bheads = list(reversed(branches[branch]))
1612 if start is not None:
1608 if start is not None:
1613 # filter out the heads that cannot be reached from startrev
1609 # filter out the heads that cannot be reached from startrev
1614 fbheads = set(self.changelog.nodesbetween([start], bheads)[2])
1610 fbheads = set(self.changelog.nodesbetween([start], bheads)[2])
1615 bheads = [h for h in bheads if h in fbheads]
1611 bheads = [h for h in bheads if h in fbheads]
1616 if not closed:
1612 if not closed:
1617 bheads = [h for h in bheads if not self[h].closesbranch()]
1613 bheads = [h for h in bheads if not self[h].closesbranch()]
1618 return bheads
1614 return bheads
1619
1615
1620 def branches(self, nodes):
1616 def branches(self, nodes):
1621 if not nodes:
1617 if not nodes:
1622 nodes = [self.changelog.tip()]
1618 nodes = [self.changelog.tip()]
1623 b = []
1619 b = []
1624 for n in nodes:
1620 for n in nodes:
1625 t = n
1621 t = n
1626 while True:
1622 while True:
1627 p = self.changelog.parents(n)
1623 p = self.changelog.parents(n)
1628 if p[1] != nullid or p[0] == nullid:
1624 if p[1] != nullid or p[0] == nullid:
1629 b.append((t, n, p[0], p[1]))
1625 b.append((t, n, p[0], p[1]))
1630 break
1626 break
1631 n = p[0]
1627 n = p[0]
1632 return b
1628 return b
1633
1629
1634 def between(self, pairs):
1630 def between(self, pairs):
1635 r = []
1631 r = []
1636
1632
1637 for top, bottom in pairs:
1633 for top, bottom in pairs:
1638 n, l, i = top, [], 0
1634 n, l, i = top, [], 0
1639 f = 1
1635 f = 1
1640
1636
1641 while n != bottom and n != nullid:
1637 while n != bottom and n != nullid:
1642 p = self.changelog.parents(n)[0]
1638 p = self.changelog.parents(n)[0]
1643 if i == f:
1639 if i == f:
1644 l.append(n)
1640 l.append(n)
1645 f = f * 2
1641 f = f * 2
1646 n = p
1642 n = p
1647 i += 1
1643 i += 1
1648
1644
1649 r.append(l)
1645 r.append(l)
1650
1646
1651 return r
1647 return r
1652
1648
1653 def pull(self, remote, heads=None, force=False):
1649 def pull(self, remote, heads=None, force=False):
1654 # don't open transaction for nothing or you break future useful
1650 # don't open transaction for nothing or you break future useful
1655 # rollback call
1651 # rollback call
1656 tr = None
1652 tr = None
1657 trname = 'pull\n' + util.hidepassword(remote.url())
1653 trname = 'pull\n' + util.hidepassword(remote.url())
1658 lock = self.lock()
1654 lock = self.lock()
1659 try:
1655 try:
1660 tmp = discovery.findcommonincoming(self, remote, heads=heads,
1656 tmp = discovery.findcommonincoming(self, remote, heads=heads,
1661 force=force)
1657 force=force)
1662 common, fetch, rheads = tmp
1658 common, fetch, rheads = tmp
1663 if not fetch:
1659 if not fetch:
1664 self.ui.status(_("no changes found\n"))
1660 self.ui.status(_("no changes found\n"))
1665 added = []
1661 added = []
1666 result = 0
1662 result = 0
1667 else:
1663 else:
1668 tr = self.transaction(trname)
1664 tr = self.transaction(trname)
1669 if heads is None and list(common) == [nullid]:
1665 if heads is None and list(common) == [nullid]:
1670 self.ui.status(_("requesting all changes\n"))
1666 self.ui.status(_("requesting all changes\n"))
1671 elif heads is None and remote.capable('changegroupsubset'):
1667 elif heads is None and remote.capable('changegroupsubset'):
1672 # issue1320, avoid a race if remote changed after discovery
1668 # issue1320, avoid a race if remote changed after discovery
1673 heads = rheads
1669 heads = rheads
1674
1670
1675 if remote.capable('getbundle'):
1671 if remote.capable('getbundle'):
1676 cg = remote.getbundle('pull', common=common,
1672 cg = remote.getbundle('pull', common=common,
1677 heads=heads or rheads)
1673 heads=heads or rheads)
1678 elif heads is None:
1674 elif heads is None:
1679 cg = remote.changegroup(fetch, 'pull')
1675 cg = remote.changegroup(fetch, 'pull')
1680 elif not remote.capable('changegroupsubset'):
1676 elif not remote.capable('changegroupsubset'):
1681 raise util.Abort(_("partial pull cannot be done because "
1677 raise util.Abort(_("partial pull cannot be done because "
1682 "other repository doesn't support "
1678 "other repository doesn't support "
1683 "changegroupsubset."))
1679 "changegroupsubset."))
1684 else:
1680 else:
1685 cg = remote.changegroupsubset(fetch, heads, 'pull')
1681 cg = remote.changegroupsubset(fetch, heads, 'pull')
1686 # we use unfiltered changelog here because hidden revision must
1682 # we use unfiltered changelog here because hidden revision must
1687 # be taken in account for phase synchronization. They may
1683 # be taken in account for phase synchronization. They may
1688 # becomes public and becomes visible again.
1684 # becomes public and becomes visible again.
1689 cl = self.unfiltered().changelog
1685 cl = self.unfiltered().changelog
1690 clstart = len(cl)
1686 clstart = len(cl)
1691 result = self.addchangegroup(cg, 'pull', remote.url())
1687 result = self.addchangegroup(cg, 'pull', remote.url())
1692 clend = len(cl)
1688 clend = len(cl)
1693 added = [cl.node(r) for r in xrange(clstart, clend)]
1689 added = [cl.node(r) for r in xrange(clstart, clend)]
1694
1690
1695 # compute target subset
1691 # compute target subset
1696 if heads is None:
1692 if heads is None:
1697 # We pulled every thing possible
1693 # We pulled every thing possible
1698 # sync on everything common
1694 # sync on everything common
1699 subset = common + added
1695 subset = common + added
1700 else:
1696 else:
1701 # We pulled a specific subset
1697 # We pulled a specific subset
1702 # sync on this subset
1698 # sync on this subset
1703 subset = heads
1699 subset = heads
1704
1700
1705 # Get remote phases data from remote
1701 # Get remote phases data from remote
1706 remotephases = remote.listkeys('phases')
1702 remotephases = remote.listkeys('phases')
1707 publishing = bool(remotephases.get('publishing', False))
1703 publishing = bool(remotephases.get('publishing', False))
1708 if remotephases and not publishing:
1704 if remotephases and not publishing:
1709 # remote is new and unpublishing
1705 # remote is new and unpublishing
1710 pheads, _dr = phases.analyzeremotephases(self, subset,
1706 pheads, _dr = phases.analyzeremotephases(self, subset,
1711 remotephases)
1707 remotephases)
1712 phases.advanceboundary(self, phases.public, pheads)
1708 phases.advanceboundary(self, phases.public, pheads)
1713 phases.advanceboundary(self, phases.draft, subset)
1709 phases.advanceboundary(self, phases.draft, subset)
1714 else:
1710 else:
1715 # Remote is old or publishing all common changesets
1711 # Remote is old or publishing all common changesets
1716 # should be seen as public
1712 # should be seen as public
1717 phases.advanceboundary(self, phases.public, subset)
1713 phases.advanceboundary(self, phases.public, subset)
1718
1714
1719 if obsolete._enabled:
1715 if obsolete._enabled:
1720 self.ui.debug('fetching remote obsolete markers\n')
1716 self.ui.debug('fetching remote obsolete markers\n')
1721 remoteobs = remote.listkeys('obsolete')
1717 remoteobs = remote.listkeys('obsolete')
1722 if 'dump0' in remoteobs:
1718 if 'dump0' in remoteobs:
1723 if tr is None:
1719 if tr is None:
1724 tr = self.transaction(trname)
1720 tr = self.transaction(trname)
1725 for key in sorted(remoteobs, reverse=True):
1721 for key in sorted(remoteobs, reverse=True):
1726 if key.startswith('dump'):
1722 if key.startswith('dump'):
1727 data = base85.b85decode(remoteobs[key])
1723 data = base85.b85decode(remoteobs[key])
1728 self.obsstore.mergemarkers(tr, data)
1724 self.obsstore.mergemarkers(tr, data)
1729 self.invalidatevolatilesets()
1725 self.invalidatevolatilesets()
1730 if tr is not None:
1726 if tr is not None:
1731 tr.close()
1727 tr.close()
1732 finally:
1728 finally:
1733 if tr is not None:
1729 if tr is not None:
1734 tr.release()
1730 tr.release()
1735 lock.release()
1731 lock.release()
1736
1732
1737 return result
1733 return result
1738
1734
1739 def checkpush(self, force, revs):
1735 def checkpush(self, force, revs):
1740 """Extensions can override this function if additional checks have
1736 """Extensions can override this function if additional checks have
1741 to be performed before pushing, or call it if they override push
1737 to be performed before pushing, or call it if they override push
1742 command.
1738 command.
1743 """
1739 """
1744 pass
1740 pass
1745
1741
1746 def push(self, remote, force=False, revs=None, newbranch=False):
1742 def push(self, remote, force=False, revs=None, newbranch=False):
1747 '''Push outgoing changesets (limited by revs) from the current
1743 '''Push outgoing changesets (limited by revs) from the current
1748 repository to remote. Return an integer:
1744 repository to remote. Return an integer:
1749 - None means nothing to push
1745 - None means nothing to push
1750 - 0 means HTTP error
1746 - 0 means HTTP error
1751 - 1 means we pushed and remote head count is unchanged *or*
1747 - 1 means we pushed and remote head count is unchanged *or*
1752 we have outgoing changesets but refused to push
1748 we have outgoing changesets but refused to push
1753 - other values as described by addchangegroup()
1749 - other values as described by addchangegroup()
1754 '''
1750 '''
1755 # there are two ways to push to remote repo:
1751 # there are two ways to push to remote repo:
1756 #
1752 #
1757 # addchangegroup assumes local user can lock remote
1753 # addchangegroup assumes local user can lock remote
1758 # repo (local filesystem, old ssh servers).
1754 # repo (local filesystem, old ssh servers).
1759 #
1755 #
1760 # unbundle assumes local user cannot lock remote repo (new ssh
1756 # unbundle assumes local user cannot lock remote repo (new ssh
1761 # servers, http servers).
1757 # servers, http servers).
1762
1758
1763 if not remote.canpush():
1759 if not remote.canpush():
1764 raise util.Abort(_("destination does not support push"))
1760 raise util.Abort(_("destination does not support push"))
1765 unfi = self.unfiltered()
1761 unfi = self.unfiltered()
1766 # get local lock as we might write phase data
1762 # get local lock as we might write phase data
1767 locallock = self.lock()
1763 locallock = self.lock()
1768 try:
1764 try:
1769 self.checkpush(force, revs)
1765 self.checkpush(force, revs)
1770 lock = None
1766 lock = None
1771 unbundle = remote.capable('unbundle')
1767 unbundle = remote.capable('unbundle')
1772 if not unbundle:
1768 if not unbundle:
1773 lock = remote.lock()
1769 lock = remote.lock()
1774 try:
1770 try:
1775 # discovery
1771 # discovery
1776 fci = discovery.findcommonincoming
1772 fci = discovery.findcommonincoming
1777 commoninc = fci(unfi, remote, force=force)
1773 commoninc = fci(unfi, remote, force=force)
1778 common, inc, remoteheads = commoninc
1774 common, inc, remoteheads = commoninc
1779 fco = discovery.findcommonoutgoing
1775 fco = discovery.findcommonoutgoing
1780 outgoing = fco(unfi, remote, onlyheads=revs,
1776 outgoing = fco(unfi, remote, onlyheads=revs,
1781 commoninc=commoninc, force=force)
1777 commoninc=commoninc, force=force)
1782
1778
1783
1779
1784 if not outgoing.missing:
1780 if not outgoing.missing:
1785 # nothing to push
1781 # nothing to push
1786 scmutil.nochangesfound(unfi.ui, unfi, outgoing.excluded)
1782 scmutil.nochangesfound(unfi.ui, unfi, outgoing.excluded)
1787 ret = None
1783 ret = None
1788 else:
1784 else:
1789 # something to push
1785 # something to push
1790 if not force:
1786 if not force:
1791 # if self.obsstore == False --> no obsolete
1787 # if self.obsstore == False --> no obsolete
1792 # then, save the iteration
1788 # then, save the iteration
1793 if unfi.obsstore:
1789 if unfi.obsstore:
1794 # this message are here for 80 char limit reason
1790 # this message are here for 80 char limit reason
1795 mso = _("push includes obsolete changeset: %s!")
1791 mso = _("push includes obsolete changeset: %s!")
1796 mst = "push includes %s changeset: %s!"
1792 mst = "push includes %s changeset: %s!"
1797 # plain versions for i18n tool to detect them
1793 # plain versions for i18n tool to detect them
1798 _("push includes unstable changeset: %s!")
1794 _("push includes unstable changeset: %s!")
1799 _("push includes bumped changeset: %s!")
1795 _("push includes bumped changeset: %s!")
1800 _("push includes divergent changeset: %s!")
1796 _("push includes divergent changeset: %s!")
1801 # If we are to push if there is at least one
1797 # If we are to push if there is at least one
1802 # obsolete or unstable changeset in missing, at
1798 # obsolete or unstable changeset in missing, at
1803 # least one of the missinghead will be obsolete or
1799 # least one of the missinghead will be obsolete or
1804 # unstable. So checking heads only is ok
1800 # unstable. So checking heads only is ok
1805 for node in outgoing.missingheads:
1801 for node in outgoing.missingheads:
1806 ctx = unfi[node]
1802 ctx = unfi[node]
1807 if ctx.obsolete():
1803 if ctx.obsolete():
1808 raise util.Abort(mso % ctx)
1804 raise util.Abort(mso % ctx)
1809 elif ctx.troubled():
1805 elif ctx.troubled():
1810 raise util.Abort(_(mst)
1806 raise util.Abort(_(mst)
1811 % (ctx.troubles()[0],
1807 % (ctx.troubles()[0],
1812 ctx))
1808 ctx))
1813 discovery.checkheads(unfi, remote, outgoing,
1809 discovery.checkheads(unfi, remote, outgoing,
1814 remoteheads, newbranch,
1810 remoteheads, newbranch,
1815 bool(inc))
1811 bool(inc))
1816
1812
1817 # create a changegroup from local
1813 # create a changegroup from local
1818 if revs is None and not outgoing.excluded:
1814 if revs is None and not outgoing.excluded:
1819 # push everything,
1815 # push everything,
1820 # use the fast path, no race possible on push
1816 # use the fast path, no race possible on push
1821 cg = self._changegroup(outgoing.missing, 'push')
1817 cg = self._changegroup(outgoing.missing, 'push')
1822 else:
1818 else:
1823 cg = self.getlocalbundle('push', outgoing)
1819 cg = self.getlocalbundle('push', outgoing)
1824
1820
1825 # apply changegroup to remote
1821 # apply changegroup to remote
1826 if unbundle:
1822 if unbundle:
1827 # local repo finds heads on server, finds out what
1823 # local repo finds heads on server, finds out what
1828 # revs it must push. once revs transferred, if server
1824 # revs it must push. once revs transferred, if server
1829 # finds it has different heads (someone else won
1825 # finds it has different heads (someone else won
1830 # commit/push race), server aborts.
1826 # commit/push race), server aborts.
1831 if force:
1827 if force:
1832 remoteheads = ['force']
1828 remoteheads = ['force']
1833 # ssh: return remote's addchangegroup()
1829 # ssh: return remote's addchangegroup()
1834 # http: return remote's addchangegroup() or 0 for error
1830 # http: return remote's addchangegroup() or 0 for error
1835 ret = remote.unbundle(cg, remoteheads, 'push')
1831 ret = remote.unbundle(cg, remoteheads, 'push')
1836 else:
1832 else:
1837 # we return an integer indicating remote head count
1833 # we return an integer indicating remote head count
1838 # change
1834 # change
1839 ret = remote.addchangegroup(cg, 'push', self.url())
1835 ret = remote.addchangegroup(cg, 'push', self.url())
1840
1836
1841 if ret:
1837 if ret:
1842 # push succeed, synchronize target of the push
1838 # push succeed, synchronize target of the push
1843 cheads = outgoing.missingheads
1839 cheads = outgoing.missingheads
1844 elif revs is None:
1840 elif revs is None:
1845 # All out push fails. synchronize all common
1841 # All out push fails. synchronize all common
1846 cheads = outgoing.commonheads
1842 cheads = outgoing.commonheads
1847 else:
1843 else:
1848 # I want cheads = heads(::missingheads and ::commonheads)
1844 # I want cheads = heads(::missingheads and ::commonheads)
1849 # (missingheads is revs with secret changeset filtered out)
1845 # (missingheads is revs with secret changeset filtered out)
1850 #
1846 #
1851 # This can be expressed as:
1847 # This can be expressed as:
1852 # cheads = ( (missingheads and ::commonheads)
1848 # cheads = ( (missingheads and ::commonheads)
1853 # + (commonheads and ::missingheads))"
1849 # + (commonheads and ::missingheads))"
1854 # )
1850 # )
1855 #
1851 #
1856 # while trying to push we already computed the following:
1852 # while trying to push we already computed the following:
1857 # common = (::commonheads)
1853 # common = (::commonheads)
1858 # missing = ((commonheads::missingheads) - commonheads)
1854 # missing = ((commonheads::missingheads) - commonheads)
1859 #
1855 #
1860 # We can pick:
1856 # We can pick:
1861 # * missingheads part of common (::commonheads)
1857 # * missingheads part of common (::commonheads)
1862 common = set(outgoing.common)
1858 common = set(outgoing.common)
1863 cheads = [node for node in revs if node in common]
1859 cheads = [node for node in revs if node in common]
1864 # and
1860 # and
1865 # * commonheads parents on missing
1861 # * commonheads parents on missing
1866 revset = unfi.set('%ln and parents(roots(%ln))',
1862 revset = unfi.set('%ln and parents(roots(%ln))',
1867 outgoing.commonheads,
1863 outgoing.commonheads,
1868 outgoing.missing)
1864 outgoing.missing)
1869 cheads.extend(c.node() for c in revset)
1865 cheads.extend(c.node() for c in revset)
1870 # even when we don't push, exchanging phase data is useful
1866 # even when we don't push, exchanging phase data is useful
1871 remotephases = remote.listkeys('phases')
1867 remotephases = remote.listkeys('phases')
1872 if (self.ui.configbool('ui', '_usedassubrepo', False)
1868 if (self.ui.configbool('ui', '_usedassubrepo', False)
1873 and remotephases # server supports phases
1869 and remotephases # server supports phases
1874 and ret is None # nothing was pushed
1870 and ret is None # nothing was pushed
1875 and remotephases.get('publishing', False)):
1871 and remotephases.get('publishing', False)):
1876 # When:
1872 # When:
1877 # - this is a subrepo push
1873 # - this is a subrepo push
1878 # - and remote support phase
1874 # - and remote support phase
1879 # - and no changeset was pushed
1875 # - and no changeset was pushed
1880 # - and remote is publishing
1876 # - and remote is publishing
1881 # We may be in issue 3871 case!
1877 # We may be in issue 3871 case!
1882 # We drop the possible phase synchronisation done by
1878 # We drop the possible phase synchronisation done by
1883 # courtesy to publish changesets possibly locally draft
1879 # courtesy to publish changesets possibly locally draft
1884 # on the remote.
1880 # on the remote.
1885 remotephases = {'publishing': 'True'}
1881 remotephases = {'publishing': 'True'}
1886 if not remotephases: # old server or public only repo
1882 if not remotephases: # old server or public only repo
1887 phases.advanceboundary(self, phases.public, cheads)
1883 phases.advanceboundary(self, phases.public, cheads)
1888 # don't push any phase data as there is nothing to push
1884 # don't push any phase data as there is nothing to push
1889 else:
1885 else:
1890 ana = phases.analyzeremotephases(self, cheads, remotephases)
1886 ana = phases.analyzeremotephases(self, cheads, remotephases)
1891 pheads, droots = ana
1887 pheads, droots = ana
1892 ### Apply remote phase on local
1888 ### Apply remote phase on local
1893 if remotephases.get('publishing', False):
1889 if remotephases.get('publishing', False):
1894 phases.advanceboundary(self, phases.public, cheads)
1890 phases.advanceboundary(self, phases.public, cheads)
1895 else: # publish = False
1891 else: # publish = False
1896 phases.advanceboundary(self, phases.public, pheads)
1892 phases.advanceboundary(self, phases.public, pheads)
1897 phases.advanceboundary(self, phases.draft, cheads)
1893 phases.advanceboundary(self, phases.draft, cheads)
1898 ### Apply local phase on remote
1894 ### Apply local phase on remote
1899
1895
1900 # Get the list of all revs draft on remote by public here.
1896 # Get the list of all revs draft on remote by public here.
1901 # XXX Beware that revset break if droots is not strictly
1897 # XXX Beware that revset break if droots is not strictly
1902 # XXX root we may want to ensure it is but it is costly
1898 # XXX root we may want to ensure it is but it is costly
1903 outdated = unfi.set('heads((%ln::%ln) and public())',
1899 outdated = unfi.set('heads((%ln::%ln) and public())',
1904 droots, cheads)
1900 droots, cheads)
1905 for newremotehead in outdated:
1901 for newremotehead in outdated:
1906 r = remote.pushkey('phases',
1902 r = remote.pushkey('phases',
1907 newremotehead.hex(),
1903 newremotehead.hex(),
1908 str(phases.draft),
1904 str(phases.draft),
1909 str(phases.public))
1905 str(phases.public))
1910 if not r:
1906 if not r:
1911 self.ui.warn(_('updating %s to public failed!\n')
1907 self.ui.warn(_('updating %s to public failed!\n')
1912 % newremotehead)
1908 % newremotehead)
1913 self.ui.debug('try to push obsolete markers to remote\n')
1909 self.ui.debug('try to push obsolete markers to remote\n')
1914 if (obsolete._enabled and self.obsstore and
1910 if (obsolete._enabled and self.obsstore and
1915 'obsolete' in remote.listkeys('namespaces')):
1911 'obsolete' in remote.listkeys('namespaces')):
1916 rslts = []
1912 rslts = []
1917 remotedata = self.listkeys('obsolete')
1913 remotedata = self.listkeys('obsolete')
1918 for key in sorted(remotedata, reverse=True):
1914 for key in sorted(remotedata, reverse=True):
1919 # reverse sort to ensure we end with dump0
1915 # reverse sort to ensure we end with dump0
1920 data = remotedata[key]
1916 data = remotedata[key]
1921 rslts.append(remote.pushkey('obsolete', key, '', data))
1917 rslts.append(remote.pushkey('obsolete', key, '', data))
1922 if [r for r in rslts if not r]:
1918 if [r for r in rslts if not r]:
1923 msg = _('failed to push some obsolete markers!\n')
1919 msg = _('failed to push some obsolete markers!\n')
1924 self.ui.warn(msg)
1920 self.ui.warn(msg)
1925 finally:
1921 finally:
1926 if lock is not None:
1922 if lock is not None:
1927 lock.release()
1923 lock.release()
1928 finally:
1924 finally:
1929 locallock.release()
1925 locallock.release()
1930
1926
1931 self.ui.debug("checking for updated bookmarks\n")
1927 self.ui.debug("checking for updated bookmarks\n")
1932 rb = remote.listkeys('bookmarks')
1928 rb = remote.listkeys('bookmarks')
1933 for k in rb.keys():
1929 for k in rb.keys():
1934 if k in unfi._bookmarks:
1930 if k in unfi._bookmarks:
1935 nr, nl = rb[k], hex(self._bookmarks[k])
1931 nr, nl = rb[k], hex(self._bookmarks[k])
1936 if nr in unfi:
1932 if nr in unfi:
1937 cr = unfi[nr]
1933 cr = unfi[nr]
1938 cl = unfi[nl]
1934 cl = unfi[nl]
1939 if bookmarks.validdest(unfi, cr, cl):
1935 if bookmarks.validdest(unfi, cr, cl):
1940 r = remote.pushkey('bookmarks', k, nr, nl)
1936 r = remote.pushkey('bookmarks', k, nr, nl)
1941 if r:
1937 if r:
1942 self.ui.status(_("updating bookmark %s\n") % k)
1938 self.ui.status(_("updating bookmark %s\n") % k)
1943 else:
1939 else:
1944 self.ui.warn(_('updating bookmark %s'
1940 self.ui.warn(_('updating bookmark %s'
1945 ' failed!\n') % k)
1941 ' failed!\n') % k)
1946
1942
1947 return ret
1943 return ret
1948
1944
1949 def changegroupinfo(self, nodes, source):
1945 def changegroupinfo(self, nodes, source):
1950 if self.ui.verbose or source == 'bundle':
1946 if self.ui.verbose or source == 'bundle':
1951 self.ui.status(_("%d changesets found\n") % len(nodes))
1947 self.ui.status(_("%d changesets found\n") % len(nodes))
1952 if self.ui.debugflag:
1948 if self.ui.debugflag:
1953 self.ui.debug("list of changesets:\n")
1949 self.ui.debug("list of changesets:\n")
1954 for node in nodes:
1950 for node in nodes:
1955 self.ui.debug("%s\n" % hex(node))
1951 self.ui.debug("%s\n" % hex(node))
1956
1952
1957 def changegroupsubset(self, bases, heads, source):
1953 def changegroupsubset(self, bases, heads, source):
1958 """Compute a changegroup consisting of all the nodes that are
1954 """Compute a changegroup consisting of all the nodes that are
1959 descendants of any of the bases and ancestors of any of the heads.
1955 descendants of any of the bases and ancestors of any of the heads.
1960 Return a chunkbuffer object whose read() method will return
1956 Return a chunkbuffer object whose read() method will return
1961 successive changegroup chunks.
1957 successive changegroup chunks.
1962
1958
1963 It is fairly complex as determining which filenodes and which
1959 It is fairly complex as determining which filenodes and which
1964 manifest nodes need to be included for the changeset to be complete
1960 manifest nodes need to be included for the changeset to be complete
1965 is non-trivial.
1961 is non-trivial.
1966
1962
1967 Another wrinkle is doing the reverse, figuring out which changeset in
1963 Another wrinkle is doing the reverse, figuring out which changeset in
1968 the changegroup a particular filenode or manifestnode belongs to.
1964 the changegroup a particular filenode or manifestnode belongs to.
1969 """
1965 """
1970 cl = self.changelog
1966 cl = self.changelog
1971 if not bases:
1967 if not bases:
1972 bases = [nullid]
1968 bases = [nullid]
1973 csets, bases, heads = cl.nodesbetween(bases, heads)
1969 csets, bases, heads = cl.nodesbetween(bases, heads)
1974 # We assume that all ancestors of bases are known
1970 # We assume that all ancestors of bases are known
1975 common = cl.ancestors([cl.rev(n) for n in bases])
1971 common = cl.ancestors([cl.rev(n) for n in bases])
1976 return self._changegroupsubset(common, csets, heads, source)
1972 return self._changegroupsubset(common, csets, heads, source)
1977
1973
1978 def getlocalbundle(self, source, outgoing):
1974 def getlocalbundle(self, source, outgoing):
1979 """Like getbundle, but taking a discovery.outgoing as an argument.
1975 """Like getbundle, but taking a discovery.outgoing as an argument.
1980
1976
1981 This is only implemented for local repos and reuses potentially
1977 This is only implemented for local repos and reuses potentially
1982 precomputed sets in outgoing."""
1978 precomputed sets in outgoing."""
1983 if not outgoing.missing:
1979 if not outgoing.missing:
1984 return None
1980 return None
1985 return self._changegroupsubset(outgoing.common,
1981 return self._changegroupsubset(outgoing.common,
1986 outgoing.missing,
1982 outgoing.missing,
1987 outgoing.missingheads,
1983 outgoing.missingheads,
1988 source)
1984 source)
1989
1985
1990 def getbundle(self, source, heads=None, common=None):
1986 def getbundle(self, source, heads=None, common=None):
1991 """Like changegroupsubset, but returns the set difference between the
1987 """Like changegroupsubset, but returns the set difference between the
1992 ancestors of heads and the ancestors common.
1988 ancestors of heads and the ancestors common.
1993
1989
1994 If heads is None, use the local heads. If common is None, use [nullid].
1990 If heads is None, use the local heads. If common is None, use [nullid].
1995
1991
1996 The nodes in common might not all be known locally due to the way the
1992 The nodes in common might not all be known locally due to the way the
1997 current discovery protocol works.
1993 current discovery protocol works.
1998 """
1994 """
1999 cl = self.changelog
1995 cl = self.changelog
2000 if common:
1996 if common:
2001 hasnode = cl.hasnode
1997 hasnode = cl.hasnode
2002 common = [n for n in common if hasnode(n)]
1998 common = [n for n in common if hasnode(n)]
2003 else:
1999 else:
2004 common = [nullid]
2000 common = [nullid]
2005 if not heads:
2001 if not heads:
2006 heads = cl.heads()
2002 heads = cl.heads()
2007 return self.getlocalbundle(source,
2003 return self.getlocalbundle(source,
2008 discovery.outgoing(cl, common, heads))
2004 discovery.outgoing(cl, common, heads))
2009
2005
2010 @unfilteredmethod
2006 @unfilteredmethod
2011 def _changegroupsubset(self, commonrevs, csets, heads, source):
2007 def _changegroupsubset(self, commonrevs, csets, heads, source):
2012
2008
2013 cl = self.changelog
2009 cl = self.changelog
2014 mf = self.manifest
2010 mf = self.manifest
2015 mfs = {} # needed manifests
2011 mfs = {} # needed manifests
2016 fnodes = {} # needed file nodes
2012 fnodes = {} # needed file nodes
2017 changedfiles = set()
2013 changedfiles = set()
2018 fstate = ['', {}]
2014 fstate = ['', {}]
2019 count = [0, 0]
2015 count = [0, 0]
2020
2016
2021 # can we go through the fast path ?
2017 # can we go through the fast path ?
2022 heads.sort()
2018 heads.sort()
2023 if heads == sorted(self.heads()):
2019 if heads == sorted(self.heads()):
2024 return self._changegroup(csets, source)
2020 return self._changegroup(csets, source)
2025
2021
2026 # slow path
2022 # slow path
2027 self.hook('preoutgoing', throw=True, source=source)
2023 self.hook('preoutgoing', throw=True, source=source)
2028 self.changegroupinfo(csets, source)
2024 self.changegroupinfo(csets, source)
2029
2025
2030 # filter any nodes that claim to be part of the known set
2026 # filter any nodes that claim to be part of the known set
2031 def prune(revlog, missing):
2027 def prune(revlog, missing):
2032 rr, rl = revlog.rev, revlog.linkrev
2028 rr, rl = revlog.rev, revlog.linkrev
2033 return [n for n in missing
2029 return [n for n in missing
2034 if rl(rr(n)) not in commonrevs]
2030 if rl(rr(n)) not in commonrevs]
2035
2031
2036 progress = self.ui.progress
2032 progress = self.ui.progress
2037 _bundling = _('bundling')
2033 _bundling = _('bundling')
2038 _changesets = _('changesets')
2034 _changesets = _('changesets')
2039 _manifests = _('manifests')
2035 _manifests = _('manifests')
2040 _files = _('files')
2036 _files = _('files')
2041
2037
2042 def lookup(revlog, x):
2038 def lookup(revlog, x):
2043 if revlog == cl:
2039 if revlog == cl:
2044 c = cl.read(x)
2040 c = cl.read(x)
2045 changedfiles.update(c[3])
2041 changedfiles.update(c[3])
2046 mfs.setdefault(c[0], x)
2042 mfs.setdefault(c[0], x)
2047 count[0] += 1
2043 count[0] += 1
2048 progress(_bundling, count[0],
2044 progress(_bundling, count[0],
2049 unit=_changesets, total=count[1])
2045 unit=_changesets, total=count[1])
2050 return x
2046 return x
2051 elif revlog == mf:
2047 elif revlog == mf:
2052 clnode = mfs[x]
2048 clnode = mfs[x]
2053 mdata = mf.readfast(x)
2049 mdata = mf.readfast(x)
2054 for f, n in mdata.iteritems():
2050 for f, n in mdata.iteritems():
2055 if f in changedfiles:
2051 if f in changedfiles:
2056 fnodes[f].setdefault(n, clnode)
2052 fnodes[f].setdefault(n, clnode)
2057 count[0] += 1
2053 count[0] += 1
2058 progress(_bundling, count[0],
2054 progress(_bundling, count[0],
2059 unit=_manifests, total=count[1])
2055 unit=_manifests, total=count[1])
2060 return clnode
2056 return clnode
2061 else:
2057 else:
2062 progress(_bundling, count[0], item=fstate[0],
2058 progress(_bundling, count[0], item=fstate[0],
2063 unit=_files, total=count[1])
2059 unit=_files, total=count[1])
2064 return fstate[1][x]
2060 return fstate[1][x]
2065
2061
2066 bundler = changegroup.bundle10(lookup)
2062 bundler = changegroup.bundle10(lookup)
2067 reorder = self.ui.config('bundle', 'reorder', 'auto')
2063 reorder = self.ui.config('bundle', 'reorder', 'auto')
2068 if reorder == 'auto':
2064 if reorder == 'auto':
2069 reorder = None
2065 reorder = None
2070 else:
2066 else:
2071 reorder = util.parsebool(reorder)
2067 reorder = util.parsebool(reorder)
2072
2068
2073 def gengroup():
2069 def gengroup():
2074 # Create a changenode group generator that will call our functions
2070 # Create a changenode group generator that will call our functions
2075 # back to lookup the owning changenode and collect information.
2071 # back to lookup the owning changenode and collect information.
2076 count[:] = [0, len(csets)]
2072 count[:] = [0, len(csets)]
2077 for chunk in cl.group(csets, bundler, reorder=reorder):
2073 for chunk in cl.group(csets, bundler, reorder=reorder):
2078 yield chunk
2074 yield chunk
2079 progress(_bundling, None)
2075 progress(_bundling, None)
2080
2076
2081 # Create a generator for the manifestnodes that calls our lookup
2077 # Create a generator for the manifestnodes that calls our lookup
2082 # and data collection functions back.
2078 # and data collection functions back.
2083 for f in changedfiles:
2079 for f in changedfiles:
2084 fnodes[f] = {}
2080 fnodes[f] = {}
2085 count[:] = [0, len(mfs)]
2081 count[:] = [0, len(mfs)]
2086 for chunk in mf.group(prune(mf, mfs), bundler, reorder=reorder):
2082 for chunk in mf.group(prune(mf, mfs), bundler, reorder=reorder):
2087 yield chunk
2083 yield chunk
2088 progress(_bundling, None)
2084 progress(_bundling, None)
2089
2085
2090 mfs.clear()
2086 mfs.clear()
2091
2087
2092 # Go through all our files in order sorted by name.
2088 # Go through all our files in order sorted by name.
2093 count[:] = [0, len(changedfiles)]
2089 count[:] = [0, len(changedfiles)]
2094 for fname in sorted(changedfiles):
2090 for fname in sorted(changedfiles):
2095 filerevlog = self.file(fname)
2091 filerevlog = self.file(fname)
2096 if not len(filerevlog):
2092 if not len(filerevlog):
2097 raise util.Abort(_("empty or missing revlog for %s")
2093 raise util.Abort(_("empty or missing revlog for %s")
2098 % fname)
2094 % fname)
2099 fstate[0] = fname
2095 fstate[0] = fname
2100 fstate[1] = fnodes.pop(fname, {})
2096 fstate[1] = fnodes.pop(fname, {})
2101
2097
2102 nodelist = prune(filerevlog, fstate[1])
2098 nodelist = prune(filerevlog, fstate[1])
2103 if nodelist:
2099 if nodelist:
2104 count[0] += 1
2100 count[0] += 1
2105 yield bundler.fileheader(fname)
2101 yield bundler.fileheader(fname)
2106 for chunk in filerevlog.group(nodelist, bundler, reorder):
2102 for chunk in filerevlog.group(nodelist, bundler, reorder):
2107 yield chunk
2103 yield chunk
2108
2104
2109 # Signal that no more groups are left.
2105 # Signal that no more groups are left.
2110 yield bundler.close()
2106 yield bundler.close()
2111 progress(_bundling, None)
2107 progress(_bundling, None)
2112
2108
2113 if csets:
2109 if csets:
2114 self.hook('outgoing', node=hex(csets[0]), source=source)
2110 self.hook('outgoing', node=hex(csets[0]), source=source)
2115
2111
2116 return changegroup.unbundle10(util.chunkbuffer(gengroup()), 'UN')
2112 return changegroup.unbundle10(util.chunkbuffer(gengroup()), 'UN')
2117
2113
2118 def changegroup(self, basenodes, source):
2114 def changegroup(self, basenodes, source):
2119 # to avoid a race we use changegroupsubset() (issue1320)
2115 # to avoid a race we use changegroupsubset() (issue1320)
2120 return self.changegroupsubset(basenodes, self.heads(), source)
2116 return self.changegroupsubset(basenodes, self.heads(), source)
2121
2117
2122 @unfilteredmethod
2118 @unfilteredmethod
2123 def _changegroup(self, nodes, source):
2119 def _changegroup(self, nodes, source):
2124 """Compute the changegroup of all nodes that we have that a recipient
2120 """Compute the changegroup of all nodes that we have that a recipient
2125 doesn't. Return a chunkbuffer object whose read() method will return
2121 doesn't. Return a chunkbuffer object whose read() method will return
2126 successive changegroup chunks.
2122 successive changegroup chunks.
2127
2123
2128 This is much easier than the previous function as we can assume that
2124 This is much easier than the previous function as we can assume that
2129 the recipient has any changenode we aren't sending them.
2125 the recipient has any changenode we aren't sending them.
2130
2126
2131 nodes is the set of nodes to send"""
2127 nodes is the set of nodes to send"""
2132
2128
2133 cl = self.changelog
2129 cl = self.changelog
2134 mf = self.manifest
2130 mf = self.manifest
2135 mfs = {}
2131 mfs = {}
2136 changedfiles = set()
2132 changedfiles = set()
2137 fstate = ['']
2133 fstate = ['']
2138 count = [0, 0]
2134 count = [0, 0]
2139
2135
2140 self.hook('preoutgoing', throw=True, source=source)
2136 self.hook('preoutgoing', throw=True, source=source)
2141 self.changegroupinfo(nodes, source)
2137 self.changegroupinfo(nodes, source)
2142
2138
2143 revset = set([cl.rev(n) for n in nodes])
2139 revset = set([cl.rev(n) for n in nodes])
2144
2140
2145 def gennodelst(log):
2141 def gennodelst(log):
2146 ln, llr = log.node, log.linkrev
2142 ln, llr = log.node, log.linkrev
2147 return [ln(r) for r in log if llr(r) in revset]
2143 return [ln(r) for r in log if llr(r) in revset]
2148
2144
2149 progress = self.ui.progress
2145 progress = self.ui.progress
2150 _bundling = _('bundling')
2146 _bundling = _('bundling')
2151 _changesets = _('changesets')
2147 _changesets = _('changesets')
2152 _manifests = _('manifests')
2148 _manifests = _('manifests')
2153 _files = _('files')
2149 _files = _('files')
2154
2150
2155 def lookup(revlog, x):
2151 def lookup(revlog, x):
2156 if revlog == cl:
2152 if revlog == cl:
2157 c = cl.read(x)
2153 c = cl.read(x)
2158 changedfiles.update(c[3])
2154 changedfiles.update(c[3])
2159 mfs.setdefault(c[0], x)
2155 mfs.setdefault(c[0], x)
2160 count[0] += 1
2156 count[0] += 1
2161 progress(_bundling, count[0],
2157 progress(_bundling, count[0],
2162 unit=_changesets, total=count[1])
2158 unit=_changesets, total=count[1])
2163 return x
2159 return x
2164 elif revlog == mf:
2160 elif revlog == mf:
2165 count[0] += 1
2161 count[0] += 1
2166 progress(_bundling, count[0],
2162 progress(_bundling, count[0],
2167 unit=_manifests, total=count[1])
2163 unit=_manifests, total=count[1])
2168 return cl.node(revlog.linkrev(revlog.rev(x)))
2164 return cl.node(revlog.linkrev(revlog.rev(x)))
2169 else:
2165 else:
2170 progress(_bundling, count[0], item=fstate[0],
2166 progress(_bundling, count[0], item=fstate[0],
2171 total=count[1], unit=_files)
2167 total=count[1], unit=_files)
2172 return cl.node(revlog.linkrev(revlog.rev(x)))
2168 return cl.node(revlog.linkrev(revlog.rev(x)))
2173
2169
2174 bundler = changegroup.bundle10(lookup)
2170 bundler = changegroup.bundle10(lookup)
2175 reorder = self.ui.config('bundle', 'reorder', 'auto')
2171 reorder = self.ui.config('bundle', 'reorder', 'auto')
2176 if reorder == 'auto':
2172 if reorder == 'auto':
2177 reorder = None
2173 reorder = None
2178 else:
2174 else:
2179 reorder = util.parsebool(reorder)
2175 reorder = util.parsebool(reorder)
2180
2176
2181 def gengroup():
2177 def gengroup():
2182 '''yield a sequence of changegroup chunks (strings)'''
2178 '''yield a sequence of changegroup chunks (strings)'''
2183 # construct a list of all changed files
2179 # construct a list of all changed files
2184
2180
2185 count[:] = [0, len(nodes)]
2181 count[:] = [0, len(nodes)]
2186 for chunk in cl.group(nodes, bundler, reorder=reorder):
2182 for chunk in cl.group(nodes, bundler, reorder=reorder):
2187 yield chunk
2183 yield chunk
2188 progress(_bundling, None)
2184 progress(_bundling, None)
2189
2185
2190 count[:] = [0, len(mfs)]
2186 count[:] = [0, len(mfs)]
2191 for chunk in mf.group(gennodelst(mf), bundler, reorder=reorder):
2187 for chunk in mf.group(gennodelst(mf), bundler, reorder=reorder):
2192 yield chunk
2188 yield chunk
2193 progress(_bundling, None)
2189 progress(_bundling, None)
2194
2190
2195 count[:] = [0, len(changedfiles)]
2191 count[:] = [0, len(changedfiles)]
2196 for fname in sorted(changedfiles):
2192 for fname in sorted(changedfiles):
2197 filerevlog = self.file(fname)
2193 filerevlog = self.file(fname)
2198 if not len(filerevlog):
2194 if not len(filerevlog):
2199 raise util.Abort(_("empty or missing revlog for %s")
2195 raise util.Abort(_("empty or missing revlog for %s")
2200 % fname)
2196 % fname)
2201 fstate[0] = fname
2197 fstate[0] = fname
2202 nodelist = gennodelst(filerevlog)
2198 nodelist = gennodelst(filerevlog)
2203 if nodelist:
2199 if nodelist:
2204 count[0] += 1
2200 count[0] += 1
2205 yield bundler.fileheader(fname)
2201 yield bundler.fileheader(fname)
2206 for chunk in filerevlog.group(nodelist, bundler, reorder):
2202 for chunk in filerevlog.group(nodelist, bundler, reorder):
2207 yield chunk
2203 yield chunk
2208 yield bundler.close()
2204 yield bundler.close()
2209 progress(_bundling, None)
2205 progress(_bundling, None)
2210
2206
2211 if nodes:
2207 if nodes:
2212 self.hook('outgoing', node=hex(nodes[0]), source=source)
2208 self.hook('outgoing', node=hex(nodes[0]), source=source)
2213
2209
2214 return changegroup.unbundle10(util.chunkbuffer(gengroup()), 'UN')
2210 return changegroup.unbundle10(util.chunkbuffer(gengroup()), 'UN')
2215
2211
2216 @unfilteredmethod
2212 @unfilteredmethod
2217 def addchangegroup(self, source, srctype, url, emptyok=False):
2213 def addchangegroup(self, source, srctype, url, emptyok=False):
2218 """Add the changegroup returned by source.read() to this repo.
2214 """Add the changegroup returned by source.read() to this repo.
2219 srctype is a string like 'push', 'pull', or 'unbundle'. url is
2215 srctype is a string like 'push', 'pull', or 'unbundle'. url is
2220 the URL of the repo where this changegroup is coming from.
2216 the URL of the repo where this changegroup is coming from.
2221
2217
2222 Return an integer summarizing the change to this repo:
2218 Return an integer summarizing the change to this repo:
2223 - nothing changed or no source: 0
2219 - nothing changed or no source: 0
2224 - more heads than before: 1+added heads (2..n)
2220 - more heads than before: 1+added heads (2..n)
2225 - fewer heads than before: -1-removed heads (-2..-n)
2221 - fewer heads than before: -1-removed heads (-2..-n)
2226 - number of heads stays the same: 1
2222 - number of heads stays the same: 1
2227 """
2223 """
2228 def csmap(x):
2224 def csmap(x):
2229 self.ui.debug("add changeset %s\n" % short(x))
2225 self.ui.debug("add changeset %s\n" % short(x))
2230 return len(cl)
2226 return len(cl)
2231
2227
2232 def revmap(x):
2228 def revmap(x):
2233 return cl.rev(x)
2229 return cl.rev(x)
2234
2230
2235 if not source:
2231 if not source:
2236 return 0
2232 return 0
2237
2233
2238 self.hook('prechangegroup', throw=True, source=srctype, url=url)
2234 self.hook('prechangegroup', throw=True, source=srctype, url=url)
2239
2235
2240 changesets = files = revisions = 0
2236 changesets = files = revisions = 0
2241 efiles = set()
2237 efiles = set()
2242
2238
2243 # write changelog data to temp files so concurrent readers will not see
2239 # write changelog data to temp files so concurrent readers will not see
2244 # inconsistent view
2240 # inconsistent view
2245 cl = self.changelog
2241 cl = self.changelog
2246 cl.delayupdate()
2242 cl.delayupdate()
2247 oldheads = cl.heads()
2243 oldheads = cl.heads()
2248
2244
2249 tr = self.transaction("\n".join([srctype, util.hidepassword(url)]))
2245 tr = self.transaction("\n".join([srctype, util.hidepassword(url)]))
2250 try:
2246 try:
2251 trp = weakref.proxy(tr)
2247 trp = weakref.proxy(tr)
2252 # pull off the changeset group
2248 # pull off the changeset group
2253 self.ui.status(_("adding changesets\n"))
2249 self.ui.status(_("adding changesets\n"))
2254 clstart = len(cl)
2250 clstart = len(cl)
2255 class prog(object):
2251 class prog(object):
2256 step = _('changesets')
2252 step = _('changesets')
2257 count = 1
2253 count = 1
2258 ui = self.ui
2254 ui = self.ui
2259 total = None
2255 total = None
2260 def __call__(self):
2256 def __call__(self):
2261 self.ui.progress(self.step, self.count, unit=_('chunks'),
2257 self.ui.progress(self.step, self.count, unit=_('chunks'),
2262 total=self.total)
2258 total=self.total)
2263 self.count += 1
2259 self.count += 1
2264 pr = prog()
2260 pr = prog()
2265 source.callback = pr
2261 source.callback = pr
2266
2262
2267 source.changelogheader()
2263 source.changelogheader()
2268 srccontent = cl.addgroup(source, csmap, trp)
2264 srccontent = cl.addgroup(source, csmap, trp)
2269 if not (srccontent or emptyok):
2265 if not (srccontent or emptyok):
2270 raise util.Abort(_("received changelog group is empty"))
2266 raise util.Abort(_("received changelog group is empty"))
2271 clend = len(cl)
2267 clend = len(cl)
2272 changesets = clend - clstart
2268 changesets = clend - clstart
2273 for c in xrange(clstart, clend):
2269 for c in xrange(clstart, clend):
2274 efiles.update(self[c].files())
2270 efiles.update(self[c].files())
2275 efiles = len(efiles)
2271 efiles = len(efiles)
2276 self.ui.progress(_('changesets'), None)
2272 self.ui.progress(_('changesets'), None)
2277
2273
2278 # pull off the manifest group
2274 # pull off the manifest group
2279 self.ui.status(_("adding manifests\n"))
2275 self.ui.status(_("adding manifests\n"))
2280 pr.step = _('manifests')
2276 pr.step = _('manifests')
2281 pr.count = 1
2277 pr.count = 1
2282 pr.total = changesets # manifests <= changesets
2278 pr.total = changesets # manifests <= changesets
2283 # no need to check for empty manifest group here:
2279 # no need to check for empty manifest group here:
2284 # if the result of the merge of 1 and 2 is the same in 3 and 4,
2280 # if the result of the merge of 1 and 2 is the same in 3 and 4,
2285 # no new manifest will be created and the manifest group will
2281 # no new manifest will be created and the manifest group will
2286 # be empty during the pull
2282 # be empty during the pull
2287 source.manifestheader()
2283 source.manifestheader()
2288 self.manifest.addgroup(source, revmap, trp)
2284 self.manifest.addgroup(source, revmap, trp)
2289 self.ui.progress(_('manifests'), None)
2285 self.ui.progress(_('manifests'), None)
2290
2286
2291 needfiles = {}
2287 needfiles = {}
2292 if self.ui.configbool('server', 'validate', default=False):
2288 if self.ui.configbool('server', 'validate', default=False):
2293 # validate incoming csets have their manifests
2289 # validate incoming csets have their manifests
2294 for cset in xrange(clstart, clend):
2290 for cset in xrange(clstart, clend):
2295 mfest = self.changelog.read(self.changelog.node(cset))[0]
2291 mfest = self.changelog.read(self.changelog.node(cset))[0]
2296 mfest = self.manifest.readdelta(mfest)
2292 mfest = self.manifest.readdelta(mfest)
2297 # store file nodes we must see
2293 # store file nodes we must see
2298 for f, n in mfest.iteritems():
2294 for f, n in mfest.iteritems():
2299 needfiles.setdefault(f, set()).add(n)
2295 needfiles.setdefault(f, set()).add(n)
2300
2296
2301 # process the files
2297 # process the files
2302 self.ui.status(_("adding file changes\n"))
2298 self.ui.status(_("adding file changes\n"))
2303 pr.step = _('files')
2299 pr.step = _('files')
2304 pr.count = 1
2300 pr.count = 1
2305 pr.total = efiles
2301 pr.total = efiles
2306 source.callback = None
2302 source.callback = None
2307
2303
2308 while True:
2304 while True:
2309 chunkdata = source.filelogheader()
2305 chunkdata = source.filelogheader()
2310 if not chunkdata:
2306 if not chunkdata:
2311 break
2307 break
2312 f = chunkdata["filename"]
2308 f = chunkdata["filename"]
2313 self.ui.debug("adding %s revisions\n" % f)
2309 self.ui.debug("adding %s revisions\n" % f)
2314 pr()
2310 pr()
2315 fl = self.file(f)
2311 fl = self.file(f)
2316 o = len(fl)
2312 o = len(fl)
2317 if not fl.addgroup(source, revmap, trp):
2313 if not fl.addgroup(source, revmap, trp):
2318 raise util.Abort(_("received file revlog group is empty"))
2314 raise util.Abort(_("received file revlog group is empty"))
2319 revisions += len(fl) - o
2315 revisions += len(fl) - o
2320 files += 1
2316 files += 1
2321 if f in needfiles:
2317 if f in needfiles:
2322 needs = needfiles[f]
2318 needs = needfiles[f]
2323 for new in xrange(o, len(fl)):
2319 for new in xrange(o, len(fl)):
2324 n = fl.node(new)
2320 n = fl.node(new)
2325 if n in needs:
2321 if n in needs:
2326 needs.remove(n)
2322 needs.remove(n)
2327 else:
2323 else:
2328 raise util.Abort(
2324 raise util.Abort(
2329 _("received spurious file revlog entry"))
2325 _("received spurious file revlog entry"))
2330 if not needs:
2326 if not needs:
2331 del needfiles[f]
2327 del needfiles[f]
2332 self.ui.progress(_('files'), None)
2328 self.ui.progress(_('files'), None)
2333
2329
2334 for f, needs in needfiles.iteritems():
2330 for f, needs in needfiles.iteritems():
2335 fl = self.file(f)
2331 fl = self.file(f)
2336 for n in needs:
2332 for n in needs:
2337 try:
2333 try:
2338 fl.rev(n)
2334 fl.rev(n)
2339 except error.LookupError:
2335 except error.LookupError:
2340 raise util.Abort(
2336 raise util.Abort(
2341 _('missing file data for %s:%s - run hg verify') %
2337 _('missing file data for %s:%s - run hg verify') %
2342 (f, hex(n)))
2338 (f, hex(n)))
2343
2339
2344 dh = 0
2340 dh = 0
2345 if oldheads:
2341 if oldheads:
2346 heads = cl.heads()
2342 heads = cl.heads()
2347 dh = len(heads) - len(oldheads)
2343 dh = len(heads) - len(oldheads)
2348 for h in heads:
2344 for h in heads:
2349 if h not in oldheads and self[h].closesbranch():
2345 if h not in oldheads and self[h].closesbranch():
2350 dh -= 1
2346 dh -= 1
2351 htext = ""
2347 htext = ""
2352 if dh:
2348 if dh:
2353 htext = _(" (%+d heads)") % dh
2349 htext = _(" (%+d heads)") % dh
2354
2350
2355 self.ui.status(_("added %d changesets"
2351 self.ui.status(_("added %d changesets"
2356 " with %d changes to %d files%s\n")
2352 " with %d changes to %d files%s\n")
2357 % (changesets, revisions, files, htext))
2353 % (changesets, revisions, files, htext))
2358 self.invalidatevolatilesets()
2354 self.invalidatevolatilesets()
2359
2355
2360 if changesets > 0:
2356 if changesets > 0:
2361 p = lambda: cl.writepending() and self.root or ""
2357 p = lambda: cl.writepending() and self.root or ""
2362 self.hook('pretxnchangegroup', throw=True,
2358 self.hook('pretxnchangegroup', throw=True,
2363 node=hex(cl.node(clstart)), source=srctype,
2359 node=hex(cl.node(clstart)), source=srctype,
2364 url=url, pending=p)
2360 url=url, pending=p)
2365
2361
2366 added = [cl.node(r) for r in xrange(clstart, clend)]
2362 added = [cl.node(r) for r in xrange(clstart, clend)]
2367 publishing = self.ui.configbool('phases', 'publish', True)
2363 publishing = self.ui.configbool('phases', 'publish', True)
2368 if srctype == 'push':
2364 if srctype == 'push':
2369 # Old server can not push the boundary themself.
2365 # Old server can not push the boundary themself.
2370 # New server won't push the boundary if changeset already
2366 # New server won't push the boundary if changeset already
2371 # existed locally as secrete
2367 # existed locally as secrete
2372 #
2368 #
2373 # We should not use added here but the list of all change in
2369 # We should not use added here but the list of all change in
2374 # the bundle
2370 # the bundle
2375 if publishing:
2371 if publishing:
2376 phases.advanceboundary(self, phases.public, srccontent)
2372 phases.advanceboundary(self, phases.public, srccontent)
2377 else:
2373 else:
2378 phases.advanceboundary(self, phases.draft, srccontent)
2374 phases.advanceboundary(self, phases.draft, srccontent)
2379 phases.retractboundary(self, phases.draft, added)
2375 phases.retractboundary(self, phases.draft, added)
2380 elif srctype != 'strip':
2376 elif srctype != 'strip':
2381 # publishing only alter behavior during push
2377 # publishing only alter behavior during push
2382 #
2378 #
2383 # strip should not touch boundary at all
2379 # strip should not touch boundary at all
2384 phases.retractboundary(self, phases.draft, added)
2380 phases.retractboundary(self, phases.draft, added)
2385
2381
2386 # make changelog see real files again
2382 # make changelog see real files again
2387 cl.finalize(trp)
2383 cl.finalize(trp)
2388
2384
2389 tr.close()
2385 tr.close()
2390
2386
2391 if changesets > 0:
2387 if changesets > 0:
2392 if srctype != 'strip':
2388 if srctype != 'strip':
2393 # During strip, branchcache is invalid but coming call to
2389 # During strip, branchcache is invalid but coming call to
2394 # `destroyed` will repair it.
2390 # `destroyed` will repair it.
2395 # In other case we can safely update cache on disk.
2391 # In other case we can safely update cache on disk.
2396 branchmap.updatecache(self.filtered('served'))
2392 branchmap.updatecache(self.filtered('served'))
2397 def runhooks():
2393 def runhooks():
2398 # forcefully update the on-disk branch cache
2394 # forcefully update the on-disk branch cache
2399 self.ui.debug("updating the branch cache\n")
2395 self.ui.debug("updating the branch cache\n")
2400 self.hook("changegroup", node=hex(cl.node(clstart)),
2396 self.hook("changegroup", node=hex(cl.node(clstart)),
2401 source=srctype, url=url)
2397 source=srctype, url=url)
2402
2398
2403 for n in added:
2399 for n in added:
2404 self.hook("incoming", node=hex(n), source=srctype,
2400 self.hook("incoming", node=hex(n), source=srctype,
2405 url=url)
2401 url=url)
2406 self._afterlock(runhooks)
2402 self._afterlock(runhooks)
2407
2403
2408 finally:
2404 finally:
2409 tr.release()
2405 tr.release()
2410 # never return 0 here:
2406 # never return 0 here:
2411 if dh < 0:
2407 if dh < 0:
2412 return dh - 1
2408 return dh - 1
2413 else:
2409 else:
2414 return dh + 1
2410 return dh + 1
2415
2411
2416 def stream_in(self, remote, requirements):
2412 def stream_in(self, remote, requirements):
2417 lock = self.lock()
2413 lock = self.lock()
2418 try:
2414 try:
2419 # Save remote branchmap. We will use it later
2415 # Save remote branchmap. We will use it later
2420 # to speed up branchcache creation
2416 # to speed up branchcache creation
2421 rbranchmap = None
2417 rbranchmap = None
2422 if remote.capable("branchmap"):
2418 if remote.capable("branchmap"):
2423 rbranchmap = remote.branchmap()
2419 rbranchmap = remote.branchmap()
2424
2420
2425 fp = remote.stream_out()
2421 fp = remote.stream_out()
2426 l = fp.readline()
2422 l = fp.readline()
2427 try:
2423 try:
2428 resp = int(l)
2424 resp = int(l)
2429 except ValueError:
2425 except ValueError:
2430 raise error.ResponseError(
2426 raise error.ResponseError(
2431 _('unexpected response from remote server:'), l)
2427 _('unexpected response from remote server:'), l)
2432 if resp == 1:
2428 if resp == 1:
2433 raise util.Abort(_('operation forbidden by server'))
2429 raise util.Abort(_('operation forbidden by server'))
2434 elif resp == 2:
2430 elif resp == 2:
2435 raise util.Abort(_('locking the remote repository failed'))
2431 raise util.Abort(_('locking the remote repository failed'))
2436 elif resp != 0:
2432 elif resp != 0:
2437 raise util.Abort(_('the server sent an unknown error code'))
2433 raise util.Abort(_('the server sent an unknown error code'))
2438 self.ui.status(_('streaming all changes\n'))
2434 self.ui.status(_('streaming all changes\n'))
2439 l = fp.readline()
2435 l = fp.readline()
2440 try:
2436 try:
2441 total_files, total_bytes = map(int, l.split(' ', 1))
2437 total_files, total_bytes = map(int, l.split(' ', 1))
2442 except (ValueError, TypeError):
2438 except (ValueError, TypeError):
2443 raise error.ResponseError(
2439 raise error.ResponseError(
2444 _('unexpected response from remote server:'), l)
2440 _('unexpected response from remote server:'), l)
2445 self.ui.status(_('%d files to transfer, %s of data\n') %
2441 self.ui.status(_('%d files to transfer, %s of data\n') %
2446 (total_files, util.bytecount(total_bytes)))
2442 (total_files, util.bytecount(total_bytes)))
2447 handled_bytes = 0
2443 handled_bytes = 0
2448 self.ui.progress(_('clone'), 0, total=total_bytes)
2444 self.ui.progress(_('clone'), 0, total=total_bytes)
2449 start = time.time()
2445 start = time.time()
2450 for i in xrange(total_files):
2446 for i in xrange(total_files):
2451 # XXX doesn't support '\n' or '\r' in filenames
2447 # XXX doesn't support '\n' or '\r' in filenames
2452 l = fp.readline()
2448 l = fp.readline()
2453 try:
2449 try:
2454 name, size = l.split('\0', 1)
2450 name, size = l.split('\0', 1)
2455 size = int(size)
2451 size = int(size)
2456 except (ValueError, TypeError):
2452 except (ValueError, TypeError):
2457 raise error.ResponseError(
2453 raise error.ResponseError(
2458 _('unexpected response from remote server:'), l)
2454 _('unexpected response from remote server:'), l)
2459 if self.ui.debugflag:
2455 if self.ui.debugflag:
2460 self.ui.debug('adding %s (%s)\n' %
2456 self.ui.debug('adding %s (%s)\n' %
2461 (name, util.bytecount(size)))
2457 (name, util.bytecount(size)))
2462 # for backwards compat, name was partially encoded
2458 # for backwards compat, name was partially encoded
2463 ofp = self.sopener(store.decodedir(name), 'w')
2459 ofp = self.sopener(store.decodedir(name), 'w')
2464 for chunk in util.filechunkiter(fp, limit=size):
2460 for chunk in util.filechunkiter(fp, limit=size):
2465 handled_bytes += len(chunk)
2461 handled_bytes += len(chunk)
2466 self.ui.progress(_('clone'), handled_bytes,
2462 self.ui.progress(_('clone'), handled_bytes,
2467 total=total_bytes)
2463 total=total_bytes)
2468 ofp.write(chunk)
2464 ofp.write(chunk)
2469 ofp.close()
2465 ofp.close()
2470 elapsed = time.time() - start
2466 elapsed = time.time() - start
2471 if elapsed <= 0:
2467 if elapsed <= 0:
2472 elapsed = 0.001
2468 elapsed = 0.001
2473 self.ui.progress(_('clone'), None)
2469 self.ui.progress(_('clone'), None)
2474 self.ui.status(_('transferred %s in %.1f seconds (%s/sec)\n') %
2470 self.ui.status(_('transferred %s in %.1f seconds (%s/sec)\n') %
2475 (util.bytecount(total_bytes), elapsed,
2471 (util.bytecount(total_bytes), elapsed,
2476 util.bytecount(total_bytes / elapsed)))
2472 util.bytecount(total_bytes / elapsed)))
2477
2473
2478 # new requirements = old non-format requirements +
2474 # new requirements = old non-format requirements +
2479 # new format-related
2475 # new format-related
2480 # requirements from the streamed-in repository
2476 # requirements from the streamed-in repository
2481 requirements.update(set(self.requirements) - self.supportedformats)
2477 requirements.update(set(self.requirements) - self.supportedformats)
2482 self._applyrequirements(requirements)
2478 self._applyrequirements(requirements)
2483 self._writerequirements()
2479 self._writerequirements()
2484
2480
2485 if rbranchmap:
2481 if rbranchmap:
2486 rbheads = []
2482 rbheads = []
2487 for bheads in rbranchmap.itervalues():
2483 for bheads in rbranchmap.itervalues():
2488 rbheads.extend(bheads)
2484 rbheads.extend(bheads)
2489
2485
2490 if rbheads:
2486 if rbheads:
2491 rtiprev = max((int(self.changelog.rev(node))
2487 rtiprev = max((int(self.changelog.rev(node))
2492 for node in rbheads))
2488 for node in rbheads))
2493 cache = branchmap.branchcache(rbranchmap,
2489 cache = branchmap.branchcache(rbranchmap,
2494 self[rtiprev].node(),
2490 self[rtiprev].node(),
2495 rtiprev)
2491 rtiprev)
2496 # Try to stick it as low as possible
2492 # Try to stick it as low as possible
2497 # filter above served are unlikely to be fetch from a clone
2493 # filter above served are unlikely to be fetch from a clone
2498 for candidate in ('base', 'immutable', 'served'):
2494 for candidate in ('base', 'immutable', 'served'):
2499 rview = self.filtered(candidate)
2495 rview = self.filtered(candidate)
2500 if cache.validfor(rview):
2496 if cache.validfor(rview):
2501 self._branchcaches[candidate] = cache
2497 self._branchcaches[candidate] = cache
2502 cache.write(rview)
2498 cache.write(rview)
2503 break
2499 break
2504 self.invalidate()
2500 self.invalidate()
2505 return len(self.heads()) + 1
2501 return len(self.heads()) + 1
2506 finally:
2502 finally:
2507 lock.release()
2503 lock.release()
2508
2504
2509 def clone(self, remote, heads=[], stream=False):
2505 def clone(self, remote, heads=[], stream=False):
2510 '''clone remote repository.
2506 '''clone remote repository.
2511
2507
2512 keyword arguments:
2508 keyword arguments:
2513 heads: list of revs to clone (forces use of pull)
2509 heads: list of revs to clone (forces use of pull)
2514 stream: use streaming clone if possible'''
2510 stream: use streaming clone if possible'''
2515
2511
2516 # now, all clients that can request uncompressed clones can
2512 # now, all clients that can request uncompressed clones can
2517 # read repo formats supported by all servers that can serve
2513 # read repo formats supported by all servers that can serve
2518 # them.
2514 # them.
2519
2515
2520 # if revlog format changes, client will have to check version
2516 # if revlog format changes, client will have to check version
2521 # and format flags on "stream" capability, and use
2517 # and format flags on "stream" capability, and use
2522 # uncompressed only if compatible.
2518 # uncompressed only if compatible.
2523
2519
2524 if not stream:
2520 if not stream:
2525 # if the server explicitly prefers to stream (for fast LANs)
2521 # if the server explicitly prefers to stream (for fast LANs)
2526 stream = remote.capable('stream-preferred')
2522 stream = remote.capable('stream-preferred')
2527
2523
2528 if stream and not heads:
2524 if stream and not heads:
2529 # 'stream' means remote revlog format is revlogv1 only
2525 # 'stream' means remote revlog format is revlogv1 only
2530 if remote.capable('stream'):
2526 if remote.capable('stream'):
2531 return self.stream_in(remote, set(('revlogv1',)))
2527 return self.stream_in(remote, set(('revlogv1',)))
2532 # otherwise, 'streamreqs' contains the remote revlog format
2528 # otherwise, 'streamreqs' contains the remote revlog format
2533 streamreqs = remote.capable('streamreqs')
2529 streamreqs = remote.capable('streamreqs')
2534 if streamreqs:
2530 if streamreqs:
2535 streamreqs = set(streamreqs.split(','))
2531 streamreqs = set(streamreqs.split(','))
2536 # if we support it, stream in and adjust our requirements
2532 # if we support it, stream in and adjust our requirements
2537 if not streamreqs - self.supportedformats:
2533 if not streamreqs - self.supportedformats:
2538 return self.stream_in(remote, streamreqs)
2534 return self.stream_in(remote, streamreqs)
2539 return self.pull(remote, heads)
2535 return self.pull(remote, heads)
2540
2536
2541 def pushkey(self, namespace, key, old, new):
2537 def pushkey(self, namespace, key, old, new):
2542 self.hook('prepushkey', throw=True, namespace=namespace, key=key,
2538 self.hook('prepushkey', throw=True, namespace=namespace, key=key,
2543 old=old, new=new)
2539 old=old, new=new)
2544 self.ui.debug('pushing key for "%s:%s"\n' % (namespace, key))
2540 self.ui.debug('pushing key for "%s:%s"\n' % (namespace, key))
2545 ret = pushkey.push(self, namespace, key, old, new)
2541 ret = pushkey.push(self, namespace, key, old, new)
2546 self.hook('pushkey', namespace=namespace, key=key, old=old, new=new,
2542 self.hook('pushkey', namespace=namespace, key=key, old=old, new=new,
2547 ret=ret)
2543 ret=ret)
2548 return ret
2544 return ret
2549
2545
2550 def listkeys(self, namespace):
2546 def listkeys(self, namespace):
2551 self.hook('prelistkeys', throw=True, namespace=namespace)
2547 self.hook('prelistkeys', throw=True, namespace=namespace)
2552 self.ui.debug('listing keys for "%s"\n' % namespace)
2548 self.ui.debug('listing keys for "%s"\n' % namespace)
2553 values = pushkey.list(self, namespace)
2549 values = pushkey.list(self, namespace)
2554 self.hook('listkeys', namespace=namespace, values=values)
2550 self.hook('listkeys', namespace=namespace, values=values)
2555 return values
2551 return values
2556
2552
2557 def debugwireargs(self, one, two, three=None, four=None, five=None):
2553 def debugwireargs(self, one, two, three=None, four=None, five=None):
2558 '''used to test argument passing over the wire'''
2554 '''used to test argument passing over the wire'''
2559 return "%s %s %s %s %s" % (one, two, three, four, five)
2555 return "%s %s %s %s %s" % (one, two, three, four, five)
2560
2556
2561 def savecommitmessage(self, text):
2557 def savecommitmessage(self, text):
2562 fp = self.opener('last-message.txt', 'wb')
2558 fp = self.opener('last-message.txt', 'wb')
2563 try:
2559 try:
2564 fp.write(text)
2560 fp.write(text)
2565 finally:
2561 finally:
2566 fp.close()
2562 fp.close()
2567 return self.pathto(fp.name[len(self.root) + 1:])
2563 return self.pathto(fp.name[len(self.root) + 1:])
2568
2564
2569 # used to avoid circular references so destructors work
2565 # used to avoid circular references so destructors work
2570 def aftertrans(files):
2566 def aftertrans(files):
2571 renamefiles = [tuple(t) for t in files]
2567 renamefiles = [tuple(t) for t in files]
2572 def a():
2568 def a():
2573 for src, dest in renamefiles:
2569 for src, dest in renamefiles:
2574 try:
2570 try:
2575 util.rename(src, dest)
2571 util.rename(src, dest)
2576 except OSError: # journal file does not yet exist
2572 except OSError: # journal file does not yet exist
2577 pass
2573 pass
2578 return a
2574 return a
2579
2575
2580 def undoname(fn):
2576 def undoname(fn):
2581 base, name = os.path.split(fn)
2577 base, name = os.path.split(fn)
2582 assert name.startswith('journal')
2578 assert name.startswith('journal')
2583 return os.path.join(base, name.replace('journal', 'undo', 1))
2579 return os.path.join(base, name.replace('journal', 'undo', 1))
2584
2580
2585 def instance(ui, path, create):
2581 def instance(ui, path, create):
2586 return localrepository(ui, util.urllocalpath(path), create)
2582 return localrepository(ui, util.urllocalpath(path), create)
2587
2583
2588 def islocal(path):
2584 def islocal(path):
2589 return True
2585 return True
General Comments 0
You need to be logged in to leave comments. Login now