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