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