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