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