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