##// END OF EJS Templates
push: move local lock logic in pushoperation...
Pierre-Yves David -
r20436:2f2e8d1c default
parent child Browse files
Show More
@@ -1,295 +1,299 b''
1 1 # exchange.py - utily to exchange data between repo.
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from i18n import _
9 9 from node import hex
10 10 import errno
11 11 import util, scmutil, changegroup
12 12 import discovery, phases, obsolete, bookmarks
13 13
14 14
15 15 class pushoperation(object):
16 16 """A object that represent a single push operation
17 17
18 18 It purpose is to carry push related state and very common operation.
19 19
20 20 A new should be created at the begining of each push and discarded
21 21 afterward.
22 22 """
23 23
24 24 def __init__(self, repo, remote, force=False, revs=None, newbranch=False):
25 25 # repo we push from
26 26 self.repo = repo
27 27 self.ui = repo.ui
28 28 # repo we push to
29 29 self.remote = remote
30 30 # force option provided
31 31 self.force = force
32 32 # revs to be pushed (None is "all")
33 33 self.revs = revs
34 34 # allow push of new branch
35 35 self.newbranch = newbranch
36 # did a local lock get acquired?
37 self.locallocked = None
36 38
37 39 def push(repo, remote, force=False, revs=None, newbranch=False):
38 40 '''Push outgoing changesets (limited by revs) from a local
39 41 repository to remote. Return an integer:
40 42 - None means nothing to push
41 43 - 0 means HTTP error
42 44 - 1 means we pushed and remote head count is unchanged *or*
43 45 we have outgoing changesets but refused to push
44 46 - other values as described by addchangegroup()
45 47 '''
46 48 pushop = pushoperation(repo, remote, force, revs, newbranch)
47 49 if pushop.remote.local():
48 50 missing = (set(pushop.repo.requirements)
49 51 - pushop.remote.local().supported)
50 52 if missing:
51 53 msg = _("required features are not"
52 54 " supported in the destination:"
53 55 " %s") % (', '.join(sorted(missing)))
54 56 raise util.Abort(msg)
55 57
56 58 # there are two ways to push to remote repo:
57 59 #
58 60 # addchangegroup assumes local user can lock remote
59 61 # repo (local filesystem, old ssh servers).
60 62 #
61 63 # unbundle assumes local user cannot lock remote repo (new ssh
62 64 # servers, http servers).
63 65
64 66 if not pushop.remote.canpush():
65 67 raise util.Abort(_("destination does not support push"))
66 68 unfi = pushop.repo.unfiltered()
67 69 def localphasemove(nodes, phase=phases.public):
68 70 """move <nodes> to <phase> in the local source repo"""
69 if locallock is not None:
71 if pushop.locallocked:
70 72 phases.advanceboundary(pushop.repo, phase, nodes)
71 73 else:
72 74 # repo is not locked, do not change any phases!
73 75 # Informs the user that phases should have been moved when
74 76 # applicable.
75 77 actualmoves = [n for n in nodes if phase < pushop.repo[n].phase()]
76 78 phasestr = phases.phasenames[phase]
77 79 if actualmoves:
78 80 pushop.ui.status(_('cannot lock source repo, skipping '
79 81 'local %s phase update\n') % phasestr)
80 82 # get local lock as we might write phase data
81 83 locallock = None
82 84 try:
83 85 locallock = pushop.repo.lock()
86 pushop.locallocked = True
84 87 except IOError, err:
88 pushop.locallocked = False
85 89 if err.errno != errno.EACCES:
86 90 raise
87 91 # source repo cannot be locked.
88 92 # We do not abort the push, but just disable the local phase
89 93 # synchronisation.
90 94 msg = 'cannot lock source repository: %s\n' % err
91 95 pushop.ui.debug(msg)
92 96 try:
93 97 pushop.repo.checkpush(pushop.force, pushop.revs)
94 98 lock = None
95 99 unbundle = pushop.remote.capable('unbundle')
96 100 if not unbundle:
97 101 lock = pushop.remote.lock()
98 102 try:
99 103 # discovery
100 104 fci = discovery.findcommonincoming
101 105 commoninc = fci(unfi, pushop.remote, force=pushop.force)
102 106 common, inc, remoteheads = commoninc
103 107 fco = discovery.findcommonoutgoing
104 108 outgoing = fco(unfi, pushop.remote, onlyheads=pushop.revs,
105 109 commoninc=commoninc, force=pushop.force)
106 110
107 111
108 112 if not outgoing.missing:
109 113 # nothing to push
110 114 scmutil.nochangesfound(unfi.ui, unfi, outgoing.excluded)
111 115 ret = None
112 116 else:
113 117 # something to push
114 118 if not pushop.force:
115 119 # if repo.obsstore == False --> no obsolete
116 120 # then, save the iteration
117 121 if unfi.obsstore:
118 122 # this message are here for 80 char limit reason
119 123 mso = _("push includes obsolete changeset: %s!")
120 124 mst = "push includes %s changeset: %s!"
121 125 # plain versions for i18n tool to detect them
122 126 _("push includes unstable changeset: %s!")
123 127 _("push includes bumped changeset: %s!")
124 128 _("push includes divergent changeset: %s!")
125 129 # If we are to push if there is at least one
126 130 # obsolete or unstable changeset in missing, at
127 131 # least one of the missinghead will be obsolete or
128 132 # unstable. So checking heads only is ok
129 133 for node in outgoing.missingheads:
130 134 ctx = unfi[node]
131 135 if ctx.obsolete():
132 136 raise util.Abort(mso % ctx)
133 137 elif ctx.troubled():
134 138 raise util.Abort(_(mst)
135 139 % (ctx.troubles()[0],
136 140 ctx))
137 141 newbm = pushop.ui.configlist('bookmarks', 'pushing')
138 142 discovery.checkheads(unfi, pushop.remote, outgoing,
139 143 remoteheads, pushop.newbranch,
140 144 bool(inc), newbm)
141 145
142 146 # TODO: get bundlecaps from remote
143 147 bundlecaps = None
144 148 # create a changegroup from local
145 149 if pushop.revs is None and not (outgoing.excluded
146 150 or pushop.repo.changelog.filteredrevs):
147 151 # push everything,
148 152 # use the fast path, no race possible on push
149 153 bundler = changegroup.bundle10(pushop.repo, bundlecaps)
150 154 cg = pushop.repo._changegroupsubset(outgoing,
151 155 bundler,
152 156 'push',
153 157 fastpath=True)
154 158 else:
155 159 cg = pushop.repo.getlocalbundle('push', outgoing,
156 160 bundlecaps)
157 161
158 162 # apply changegroup to remote
159 163 if unbundle:
160 164 # local repo finds heads on server, finds out what
161 165 # revs it must push. once revs transferred, if server
162 166 # finds it has different heads (someone else won
163 167 # commit/push race), server aborts.
164 168 if pushop.force:
165 169 remoteheads = ['force']
166 170 # ssh: return remote's addchangegroup()
167 171 # http: return remote's addchangegroup() or 0 for error
168 172 ret = pushop.remote.unbundle(cg, remoteheads, 'push')
169 173 else:
170 174 # we return an integer indicating remote head count
171 175 # change
172 176 ret = pushop.remote.addchangegroup(cg, 'push',
173 177 pushop.repo.url())
174 178
175 179 if ret:
176 180 # push succeed, synchronize target of the push
177 181 cheads = outgoing.missingheads
178 182 elif pushop.revs is None:
179 183 # All out push fails. synchronize all common
180 184 cheads = outgoing.commonheads
181 185 else:
182 186 # I want cheads = heads(::missingheads and ::commonheads)
183 187 # (missingheads is revs with secret changeset filtered out)
184 188 #
185 189 # This can be expressed as:
186 190 # cheads = ( (missingheads and ::commonheads)
187 191 # + (commonheads and ::missingheads))"
188 192 # )
189 193 #
190 194 # while trying to push we already computed the following:
191 195 # common = (::commonheads)
192 196 # missing = ((commonheads::missingheads) - commonheads)
193 197 #
194 198 # We can pick:
195 199 # * missingheads part of common (::commonheads)
196 200 common = set(outgoing.common)
197 201 nm = pushop.repo.changelog.nodemap
198 202 cheads = [node for node in pushop.revs if nm[node] in common]
199 203 # and
200 204 # * commonheads parents on missing
201 205 revset = unfi.set('%ln and parents(roots(%ln))',
202 206 outgoing.commonheads,
203 207 outgoing.missing)
204 208 cheads.extend(c.node() for c in revset)
205 209 # even when we don't push, exchanging phase data is useful
206 210 remotephases = pushop.remote.listkeys('phases')
207 211 if (pushop.ui.configbool('ui', '_usedassubrepo', False)
208 212 and remotephases # server supports phases
209 213 and ret is None # nothing was pushed
210 214 and remotephases.get('publishing', False)):
211 215 # When:
212 216 # - this is a subrepo push
213 217 # - and remote support phase
214 218 # - and no changeset was pushed
215 219 # - and remote is publishing
216 220 # We may be in issue 3871 case!
217 221 # We drop the possible phase synchronisation done by
218 222 # courtesy to publish changesets possibly locally draft
219 223 # on the remote.
220 224 remotephases = {'publishing': 'True'}
221 225 if not remotephases: # old server or public only repo
222 226 localphasemove(cheads)
223 227 # don't push any phase data as there is nothing to push
224 228 else:
225 229 ana = phases.analyzeremotephases(pushop.repo, cheads,
226 230 remotephases)
227 231 pheads, droots = ana
228 232 ### Apply remote phase on local
229 233 if remotephases.get('publishing', False):
230 234 localphasemove(cheads)
231 235 else: # publish = False
232 236 localphasemove(pheads)
233 237 localphasemove(cheads, phases.draft)
234 238 ### Apply local phase on remote
235 239
236 240 # Get the list of all revs draft on remote by public here.
237 241 # XXX Beware that revset break if droots is not strictly
238 242 # XXX root we may want to ensure it is but it is costly
239 243 outdated = unfi.set('heads((%ln::%ln) and public())',
240 244 droots, cheads)
241 245 for newremotehead in outdated:
242 246 r = pushop.remote.pushkey('phases',
243 247 newremotehead.hex(),
244 248 str(phases.draft),
245 249 str(phases.public))
246 250 if not r:
247 251 pushop.ui.warn(_('updating %s to public failed!\n')
248 252 % newremotehead)
249 253 _pushobsolete(pushop)
250 254 finally:
251 255 if lock is not None:
252 256 lock.release()
253 257 finally:
254 258 if locallock is not None:
255 259 locallock.release()
256 260
257 261 _pushbookmark(pushop)
258 262 return ret
259 263
260 264 def _pushobsolete(pushop):
261 265 """utility function to push obsolete markers to a remote"""
262 266 pushop.ui.debug('try to push obsolete markers to remote\n')
263 267 repo = pushop.repo
264 268 remote = pushop.remote
265 269 if (obsolete._enabled and repo.obsstore and
266 270 'obsolete' in remote.listkeys('namespaces')):
267 271 rslts = []
268 272 remotedata = repo.listkeys('obsolete')
269 273 for key in sorted(remotedata, reverse=True):
270 274 # reverse sort to ensure we end with dump0
271 275 data = remotedata[key]
272 276 rslts.append(remote.pushkey('obsolete', key, '', data))
273 277 if [r for r in rslts if not r]:
274 278 msg = _('failed to push some obsolete markers!\n')
275 279 repo.ui.warn(msg)
276 280
277 281 def _pushbookmark(pushop):
278 282 """Update bookmark position on remote"""
279 283 ui = pushop.ui
280 284 repo = pushop.repo.unfiltered()
281 285 remote = pushop.remote
282 286 ui.debug("checking for updated bookmarks\n")
283 287 revnums = map(repo.changelog.rev, pushop.revs or [])
284 288 ancestors = [a for a in repo.changelog.ancestors(revnums, inclusive=True)]
285 289 (addsrc, adddst, advsrc, advdst, diverge, differ, invalid
286 290 ) = bookmarks.compare(repo, repo._bookmarks, remote.listkeys('bookmarks'),
287 291 srchex=hex)
288 292
289 293 for b, scid, dcid in advsrc:
290 294 if ancestors and repo[scid].rev() not in ancestors:
291 295 continue
292 296 if remote.pushkey('bookmarks', b, dcid, scid):
293 297 ui.status(_("updating bookmark %s\n") % b)
294 298 else:
295 299 ui.warn(_('updating bookmark %s failed!\n') % b)
General Comments 0
You need to be logged in to leave comments. Login now