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