##// END OF EJS Templates
push: move bookmarks exchange in the exchange module...
Pierre-Yves David -
r20352:58300f61 default
parent child Browse files
Show More
@@ -1,442 +1,426 b''
1 1 # Mercurial bookmark support code
2 2 #
3 3 # Copyright 2008 David Soria Parra <dsp@php.net>
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 mercurial.i18n import _
9 9 from mercurial.node import hex, bin
10 10 from mercurial import encoding, error, util, obsolete
11 11 import errno
12 12
13 13 class bmstore(dict):
14 14 """Storage for bookmarks.
15 15
16 16 This object should do all bookmark reads and writes, so that it's
17 17 fairly simple to replace the storage underlying bookmarks without
18 18 having to clone the logic surrounding bookmarks.
19 19
20 20 This particular bmstore implementation stores bookmarks as
21 21 {hash}\s{name}\n (the same format as localtags) in
22 22 .hg/bookmarks. The mapping is stored as {name: nodeid}.
23 23
24 24 This class does NOT handle the "current" bookmark state at this
25 25 time.
26 26 """
27 27
28 28 def __init__(self, repo):
29 29 dict.__init__(self)
30 30 self._repo = repo
31 31 try:
32 32 for line in repo.vfs('bookmarks'):
33 33 line = line.strip()
34 34 if not line:
35 35 continue
36 36 if ' ' not in line:
37 37 repo.ui.warn(_('malformed line in .hg/bookmarks: %r\n')
38 38 % line)
39 39 continue
40 40 sha, refspec = line.split(' ', 1)
41 41 refspec = encoding.tolocal(refspec)
42 42 try:
43 43 self[refspec] = repo.changelog.lookup(sha)
44 44 except LookupError:
45 45 pass
46 46 except IOError, inst:
47 47 if inst.errno != errno.ENOENT:
48 48 raise
49 49
50 50 def write(self):
51 51 '''Write bookmarks
52 52
53 53 Write the given bookmark => hash dictionary to the .hg/bookmarks file
54 54 in a format equal to those of localtags.
55 55
56 56 We also store a backup of the previous state in undo.bookmarks that
57 57 can be copied back on rollback.
58 58 '''
59 59 repo = self._repo
60 60 if repo._bookmarkcurrent not in self:
61 61 unsetcurrent(repo)
62 62
63 63 wlock = repo.wlock()
64 64 try:
65 65
66 66 file = repo.vfs('bookmarks', 'w', atomictemp=True)
67 67 for name, node in self.iteritems():
68 68 file.write("%s %s\n" % (hex(node), encoding.fromlocal(name)))
69 69 file.close()
70 70
71 71 # touch 00changelog.i so hgweb reloads bookmarks (no lock needed)
72 72 try:
73 73 repo.svfs.utime('00changelog.i', None)
74 74 except OSError:
75 75 pass
76 76
77 77 finally:
78 78 wlock.release()
79 79
80 80 def readcurrent(repo):
81 81 '''Get the current bookmark
82 82
83 83 If we use gittish branches we have a current bookmark that
84 84 we are on. This function returns the name of the bookmark. It
85 85 is stored in .hg/bookmarks.current
86 86 '''
87 87 mark = None
88 88 try:
89 89 file = repo.opener('bookmarks.current')
90 90 except IOError, inst:
91 91 if inst.errno != errno.ENOENT:
92 92 raise
93 93 return None
94 94 try:
95 95 # No readline() in osutil.posixfile, reading everything is cheap
96 96 mark = encoding.tolocal((file.readlines() or [''])[0])
97 97 if mark == '' or mark not in repo._bookmarks:
98 98 mark = None
99 99 finally:
100 100 file.close()
101 101 return mark
102 102
103 103 def setcurrent(repo, mark):
104 104 '''Set the name of the bookmark that we are currently on
105 105
106 106 Set the name of the bookmark that we are on (hg update <bookmark>).
107 107 The name is recorded in .hg/bookmarks.current
108 108 '''
109 109 if mark not in repo._bookmarks:
110 110 raise AssertionError('bookmark %s does not exist!' % mark)
111 111
112 112 current = repo._bookmarkcurrent
113 113 if current == mark:
114 114 return
115 115
116 116 wlock = repo.wlock()
117 117 try:
118 118 file = repo.opener('bookmarks.current', 'w', atomictemp=True)
119 119 file.write(encoding.fromlocal(mark))
120 120 file.close()
121 121 finally:
122 122 wlock.release()
123 123 repo._bookmarkcurrent = mark
124 124
125 125 def unsetcurrent(repo):
126 126 wlock = repo.wlock()
127 127 try:
128 128 try:
129 129 repo.vfs.unlink('bookmarks.current')
130 130 repo._bookmarkcurrent = None
131 131 except OSError, inst:
132 132 if inst.errno != errno.ENOENT:
133 133 raise
134 134 finally:
135 135 wlock.release()
136 136
137 137 def iscurrent(repo, mark=None, parents=None):
138 138 '''Tell whether the current bookmark is also active
139 139
140 140 I.e., the bookmark listed in .hg/bookmarks.current also points to a
141 141 parent of the working directory.
142 142 '''
143 143 if not mark:
144 144 mark = repo._bookmarkcurrent
145 145 if not parents:
146 146 parents = [p.node() for p in repo[None].parents()]
147 147 marks = repo._bookmarks
148 148 return (mark in marks and marks[mark] in parents)
149 149
150 150 def updatecurrentbookmark(repo, oldnode, curbranch):
151 151 try:
152 152 return update(repo, oldnode, repo.branchtip(curbranch))
153 153 except error.RepoLookupError:
154 154 if curbranch == "default": # no default branch!
155 155 return update(repo, oldnode, repo.lookup("tip"))
156 156 else:
157 157 raise util.Abort(_("branch %s not found") % curbranch)
158 158
159 159 def deletedivergent(repo, deletefrom, bm):
160 160 '''Delete divergent versions of bm on nodes in deletefrom.
161 161
162 162 Return True if at least one bookmark was deleted, False otherwise.'''
163 163 deleted = False
164 164 marks = repo._bookmarks
165 165 divergent = [b for b in marks if b.split('@', 1)[0] == bm.split('@', 1)[0]]
166 166 for mark in divergent:
167 167 if mark and marks[mark] in deletefrom:
168 168 if mark != bm:
169 169 del marks[mark]
170 170 deleted = True
171 171 return deleted
172 172
173 173 def calculateupdate(ui, repo, checkout):
174 174 '''Return a tuple (targetrev, movemarkfrom) indicating the rev to
175 175 check out and where to move the active bookmark from, if needed.'''
176 176 movemarkfrom = None
177 177 if checkout is None:
178 178 curmark = repo._bookmarkcurrent
179 179 if iscurrent(repo):
180 180 movemarkfrom = repo['.'].node()
181 181 elif curmark:
182 182 ui.status(_("updating to active bookmark %s\n") % curmark)
183 183 checkout = curmark
184 184 return (checkout, movemarkfrom)
185 185
186 186 def update(repo, parents, node):
187 187 deletefrom = parents
188 188 marks = repo._bookmarks
189 189 update = False
190 190 cur = repo._bookmarkcurrent
191 191 if not cur:
192 192 return False
193 193
194 194 if marks[cur] in parents:
195 195 new = repo[node]
196 196 divs = [repo[b] for b in marks
197 197 if b.split('@', 1)[0] == cur.split('@', 1)[0]]
198 198 anc = repo.changelog.ancestors([new.rev()])
199 199 deletefrom = [b.node() for b in divs if b.rev() in anc or b == new]
200 200 if validdest(repo, repo[marks[cur]], new):
201 201 marks[cur] = new.node()
202 202 update = True
203 203
204 204 if deletedivergent(repo, deletefrom, cur):
205 205 update = True
206 206
207 207 if update:
208 208 marks.write()
209 209 return update
210 210
211 211 def listbookmarks(repo):
212 212 # We may try to list bookmarks on a repo type that does not
213 213 # support it (e.g., statichttprepository).
214 214 marks = getattr(repo, '_bookmarks', {})
215 215
216 216 d = {}
217 217 hasnode = repo.changelog.hasnode
218 218 for k, v in marks.iteritems():
219 219 # don't expose local divergent bookmarks
220 220 if hasnode(v) and ('@' not in k or k.endswith('@')):
221 221 d[k] = hex(v)
222 222 return d
223 223
224 224 def pushbookmark(repo, key, old, new):
225 225 w = repo.wlock()
226 226 try:
227 227 marks = repo._bookmarks
228 228 if hex(marks.get(key, '')) != old:
229 229 return False
230 230 if new == '':
231 231 del marks[key]
232 232 else:
233 233 if new not in repo:
234 234 return False
235 235 marks[key] = repo[new].node()
236 236 marks.write()
237 237 return True
238 238 finally:
239 239 w.release()
240 240
241 241 def compare(repo, srcmarks, dstmarks,
242 242 srchex=None, dsthex=None, targets=None):
243 243 '''Compare bookmarks between srcmarks and dstmarks
244 244
245 245 This returns tuple "(addsrc, adddst, advsrc, advdst, diverge,
246 246 differ, invalid)", each are list of bookmarks below:
247 247
248 248 :addsrc: added on src side (removed on dst side, perhaps)
249 249 :adddst: added on dst side (removed on src side, perhaps)
250 250 :advsrc: advanced on src side
251 251 :advdst: advanced on dst side
252 252 :diverge: diverge
253 253 :differ: changed, but changeset referred on src is unknown on dst
254 254 :invalid: unknown on both side
255 255
256 256 Each elements of lists in result tuple is tuple "(bookmark name,
257 257 changeset ID on source side, changeset ID on destination
258 258 side)". Each changeset IDs are 40 hexadecimal digit string or
259 259 None.
260 260
261 261 Changeset IDs of tuples in "addsrc", "adddst", "differ" or
262 262 "invalid" list may be unknown for repo.
263 263
264 264 This function expects that "srcmarks" and "dstmarks" return
265 265 changeset ID in 40 hexadecimal digit string for specified
266 266 bookmark. If not so (e.g. bmstore "repo._bookmarks" returning
267 267 binary value), "srchex" or "dsthex" should be specified to convert
268 268 into such form.
269 269
270 270 If "targets" is specified, only bookmarks listed in it are
271 271 examined.
272 272 '''
273 273 if not srchex:
274 274 srchex = lambda x: x
275 275 if not dsthex:
276 276 dsthex = lambda x: x
277 277
278 278 if targets:
279 279 bset = set(targets)
280 280 else:
281 281 srcmarkset = set(srcmarks)
282 282 dstmarkset = set(dstmarks)
283 283 bset = srcmarkset ^ dstmarkset
284 284 for b in srcmarkset & dstmarkset:
285 285 if srchex(srcmarks[b]) != dsthex(dstmarks[b]):
286 286 bset.add(b)
287 287
288 288 results = ([], [], [], [], [], [], [])
289 289 addsrc = results[0].append
290 290 adddst = results[1].append
291 291 advsrc = results[2].append
292 292 advdst = results[3].append
293 293 diverge = results[4].append
294 294 differ = results[5].append
295 295 invalid = results[6].append
296 296
297 297 for b in sorted(bset):
298 298 if b not in srcmarks:
299 299 if b in dstmarks:
300 300 adddst((b, None, dsthex(dstmarks[b])))
301 301 else:
302 302 invalid((b, None, None))
303 303 elif b not in dstmarks:
304 304 addsrc((b, srchex(srcmarks[b]), None))
305 305 else:
306 306 scid = srchex(srcmarks[b])
307 307 dcid = dsthex(dstmarks[b])
308 308 if scid in repo and dcid in repo:
309 309 sctx = repo[scid]
310 310 dctx = repo[dcid]
311 311 if sctx.rev() < dctx.rev():
312 312 if validdest(repo, sctx, dctx):
313 313 advdst((b, scid, dcid))
314 314 else:
315 315 diverge((b, scid, dcid))
316 316 else:
317 317 if validdest(repo, dctx, sctx):
318 318 advsrc((b, scid, dcid))
319 319 else:
320 320 diverge((b, scid, dcid))
321 321 else:
322 322 # it is too expensive to examine in detail, in this case
323 323 differ((b, scid, dcid))
324 324
325 325 return results
326 326
327 327 def _diverge(ui, b, path, localmarks):
328 328 if b == '@':
329 329 b = ''
330 330 # find a unique @ suffix
331 331 for x in range(1, 100):
332 332 n = '%s@%d' % (b, x)
333 333 if n not in localmarks:
334 334 break
335 335 # try to use an @pathalias suffix
336 336 # if an @pathalias already exists, we overwrite (update) it
337 337 for p, u in ui.configitems("paths"):
338 338 if path == u:
339 339 n = '%s@%s' % (b, p)
340 340 return n
341 341
342 342 def updatefromremote(ui, repo, remotemarks, path):
343 343 ui.debug("checking for updated bookmarks\n")
344 344 localmarks = repo._bookmarks
345 345 (addsrc, adddst, advsrc, advdst, diverge, differ, invalid
346 346 ) = compare(repo, remotemarks, localmarks, dsthex=hex)
347 347
348 348 changed = []
349 349 for b, scid, dcid in addsrc:
350 350 if scid in repo: # add remote bookmarks for changes we already have
351 351 changed.append((b, bin(scid), ui.status,
352 352 _("adding remote bookmark %s\n") % (b)))
353 353 for b, scid, dcid in advsrc:
354 354 changed.append((b, bin(scid), ui.status,
355 355 _("updating bookmark %s\n") % (b)))
356 356 for b, scid, dcid in diverge:
357 357 db = _diverge(ui, b, path, localmarks)
358 358 changed.append((db, bin(scid), ui.warn,
359 359 _("divergent bookmark %s stored as %s\n") % (b, db)))
360 360 if changed:
361 361 for b, node, writer, msg in sorted(changed):
362 362 localmarks[b] = node
363 363 writer(msg)
364 364 localmarks.write()
365 365
366 def updateremote(ui, repo, remote, revs):
367 ui.debug("checking for updated bookmarks\n")
368 revnums = map(repo.changelog.rev, revs or [])
369 ancestors = [a for a in repo.changelog.ancestors(revnums, inclusive=True)]
370 (addsrc, adddst, advsrc, advdst, diverge, differ, invalid
371 ) = compare(repo, repo._bookmarks, remote.listkeys('bookmarks'),
372 srchex=hex)
373
374 for b, scid, dcid in advsrc:
375 if ancestors and repo[scid].rev() not in ancestors:
376 continue
377 if remote.pushkey('bookmarks', b, dcid, scid):
378 ui.status(_("updating bookmark %s\n") % b)
379 else:
380 ui.warn(_('updating bookmark %s failed!\n') % b)
381
382 366 def pushtoremote(ui, repo, remote, targets):
383 367 (addsrc, adddst, advsrc, advdst, diverge, differ, invalid
384 368 ) = compare(repo, repo._bookmarks, remote.listkeys('bookmarks'),
385 369 srchex=hex, targets=targets)
386 370 if invalid:
387 371 b, scid, dcid = invalid[0]
388 372 ui.warn(_('bookmark %s does not exist on the local '
389 373 'or remote repository!\n') % b)
390 374 return 2
391 375
392 376 def push(b, old, new):
393 377 r = remote.pushkey('bookmarks', b, old, new)
394 378 if not r:
395 379 ui.warn(_('updating bookmark %s failed!\n') % b)
396 380 return 1
397 381 return 0
398 382 failed = 0
399 383 for b, scid, dcid in sorted(addsrc + advsrc + advdst + diverge + differ):
400 384 ui.status(_("exporting bookmark %s\n") % b)
401 385 if dcid is None:
402 386 dcid = ''
403 387 failed += push(b, dcid, scid)
404 388 for b, scid, dcid in adddst:
405 389 # treat as "deleted locally"
406 390 ui.status(_("deleting remote bookmark %s\n") % b)
407 391 failed += push(b, dcid, '')
408 392
409 393 if failed:
410 394 return 1
411 395
412 396 def diff(ui, dst, src):
413 397 ui.status(_("searching for changed bookmarks\n"))
414 398
415 399 smarks = src.listkeys('bookmarks')
416 400 dmarks = dst.listkeys('bookmarks')
417 401
418 402 diff = sorted(set(smarks) - set(dmarks))
419 403 for k in diff:
420 404 mark = ui.debugflag and smarks[k] or smarks[k][:12]
421 405 ui.write(" %-25s %s\n" % (k, mark))
422 406
423 407 if len(diff) <= 0:
424 408 ui.status(_("no changed bookmarks found\n"))
425 409 return 1
426 410 return 0
427 411
428 412 def validdest(repo, old, new):
429 413 """Is the new bookmark destination a valid update from the old one"""
430 414 repo = repo.unfiltered()
431 415 if old == new:
432 416 # Old == new -> nothing to update.
433 417 return False
434 418 elif not old:
435 419 # old is nullrev, anything is valid.
436 420 # (new != nullrev has been excluded by the previous check)
437 421 return True
438 422 elif repo.obsstore:
439 423 return new.node() in obsolete.foreground(repo, [old.node()])
440 424 else:
441 425 # still an independent clause as it is lazyer (and therefore faster)
442 426 return old.descendant(new)
@@ -1,259 +1,276 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 36
37 37 def push(repo, remote, force=False, revs=None, newbranch=False):
38 38 '''Push outgoing changesets (limited by revs) from a local
39 39 repository to remote. Return an integer:
40 40 - None means nothing to push
41 41 - 0 means HTTP error
42 42 - 1 means we pushed and remote head count is unchanged *or*
43 43 we have outgoing changesets but refused to push
44 44 - other values as described by addchangegroup()
45 45 '''
46 46 pushop = pushoperation(repo, remote, force, revs, newbranch)
47 47 if pushop.remote.local():
48 48 missing = (set(pushop.repo.requirements)
49 49 - pushop.remote.local().supported)
50 50 if missing:
51 51 msg = _("required features are not"
52 52 " supported in the destination:"
53 53 " %s") % (', '.join(sorted(missing)))
54 54 raise util.Abort(msg)
55 55
56 56 # there are two ways to push to remote repo:
57 57 #
58 58 # addchangegroup assumes local user can lock remote
59 59 # repo (local filesystem, old ssh servers).
60 60 #
61 61 # unbundle assumes local user cannot lock remote repo (new ssh
62 62 # servers, http servers).
63 63
64 64 if not pushop.remote.canpush():
65 65 raise util.Abort(_("destination does not support push"))
66 66 unfi = pushop.repo.unfiltered()
67 67 def localphasemove(nodes, phase=phases.public):
68 68 """move <nodes> to <phase> in the local source repo"""
69 69 if locallock is not None:
70 70 phases.advanceboundary(pushop.repo, phase, nodes)
71 71 else:
72 72 # repo is not locked, do not change any phases!
73 73 # Informs the user that phases should have been moved when
74 74 # applicable.
75 75 actualmoves = [n for n in nodes if phase < pushop.repo[n].phase()]
76 76 phasestr = phases.phasenames[phase]
77 77 if actualmoves:
78 78 pushop.ui.status(_('cannot lock source repo, skipping '
79 79 'local %s phase update\n') % phasestr)
80 80 # get local lock as we might write phase data
81 81 locallock = None
82 82 try:
83 83 locallock = pushop.repo.lock()
84 84 except IOError, err:
85 85 if err.errno != errno.EACCES:
86 86 raise
87 87 # source repo cannot be locked.
88 88 # We do not abort the push, but just disable the local phase
89 89 # synchronisation.
90 90 msg = 'cannot lock source repository: %s\n' % err
91 91 pushop.ui.debug(msg)
92 92 try:
93 93 pushop.repo.checkpush(pushop.force, pushop.revs)
94 94 lock = None
95 95 unbundle = pushop.remote.capable('unbundle')
96 96 if not unbundle:
97 97 lock = pushop.remote.lock()
98 98 try:
99 99 # discovery
100 100 fci = discovery.findcommonincoming
101 101 commoninc = fci(unfi, pushop.remote, force=pushop.force)
102 102 common, inc, remoteheads = commoninc
103 103 fco = discovery.findcommonoutgoing
104 104 outgoing = fco(unfi, pushop.remote, onlyheads=pushop.revs,
105 105 commoninc=commoninc, force=pushop.force)
106 106
107 107
108 108 if not outgoing.missing:
109 109 # nothing to push
110 110 scmutil.nochangesfound(unfi.ui, unfi, outgoing.excluded)
111 111 ret = None
112 112 else:
113 113 # something to push
114 114 if not pushop.force:
115 115 # if repo.obsstore == False --> no obsolete
116 116 # then, save the iteration
117 117 if unfi.obsstore:
118 118 # this message are here for 80 char limit reason
119 119 mso = _("push includes obsolete changeset: %s!")
120 120 mst = "push includes %s changeset: %s!"
121 121 # plain versions for i18n tool to detect them
122 122 _("push includes unstable changeset: %s!")
123 123 _("push includes bumped changeset: %s!")
124 124 _("push includes divergent changeset: %s!")
125 125 # If we are to push if there is at least one
126 126 # obsolete or unstable changeset in missing, at
127 127 # least one of the missinghead will be obsolete or
128 128 # unstable. So checking heads only is ok
129 129 for node in outgoing.missingheads:
130 130 ctx = unfi[node]
131 131 if ctx.obsolete():
132 132 raise util.Abort(mso % ctx)
133 133 elif ctx.troubled():
134 134 raise util.Abort(_(mst)
135 135 % (ctx.troubles()[0],
136 136 ctx))
137 137 newbm = pushop.ui.configlist('bookmarks', 'pushing')
138 138 discovery.checkheads(unfi, pushop.remote, outgoing,
139 139 remoteheads, pushop.newbranch,
140 140 bool(inc), newbm)
141 141
142 142 # TODO: get bundlecaps from remote
143 143 bundlecaps = None
144 144 # create a changegroup from local
145 145 if pushop.revs is None and not (outgoing.excluded
146 146 or pushop.repo.changelog.filteredrevs):
147 147 # push everything,
148 148 # use the fast path, no race possible on push
149 149 bundler = changegroup.bundle10(pushop.repo, bundlecaps)
150 150 cg = pushop.repo._changegroupsubset(outgoing,
151 151 bundler,
152 152 'push',
153 153 fastpath=True)
154 154 else:
155 155 cg = pushop.repo.getlocalbundle('push', outgoing,
156 156 bundlecaps)
157 157
158 158 # apply changegroup to remote
159 159 if unbundle:
160 160 # local repo finds heads on server, finds out what
161 161 # revs it must push. once revs transferred, if server
162 162 # finds it has different heads (someone else won
163 163 # commit/push race), server aborts.
164 164 if pushop.force:
165 165 remoteheads = ['force']
166 166 # ssh: return remote's addchangegroup()
167 167 # http: return remote's addchangegroup() or 0 for error
168 168 ret = pushop.remote.unbundle(cg, remoteheads, 'push')
169 169 else:
170 170 # we return an integer indicating remote head count
171 171 # change
172 172 ret = pushop.remote.addchangegroup(cg, 'push',
173 173 pushop.repo.url())
174 174
175 175 if ret:
176 176 # push succeed, synchronize target of the push
177 177 cheads = outgoing.missingheads
178 178 elif pushop.revs is None:
179 179 # All out push fails. synchronize all common
180 180 cheads = outgoing.commonheads
181 181 else:
182 182 # I want cheads = heads(::missingheads and ::commonheads)
183 183 # (missingheads is revs with secret changeset filtered out)
184 184 #
185 185 # This can be expressed as:
186 186 # cheads = ( (missingheads and ::commonheads)
187 187 # + (commonheads and ::missingheads))"
188 188 # )
189 189 #
190 190 # while trying to push we already computed the following:
191 191 # common = (::commonheads)
192 192 # missing = ((commonheads::missingheads) - commonheads)
193 193 #
194 194 # We can pick:
195 195 # * missingheads part of common (::commonheads)
196 196 common = set(outgoing.common)
197 197 nm = pushop.repo.changelog.nodemap
198 198 cheads = [node for node in pushop.revs if nm[node] in common]
199 199 # and
200 200 # * commonheads parents on missing
201 201 revset = unfi.set('%ln and parents(roots(%ln))',
202 202 outgoing.commonheads,
203 203 outgoing.missing)
204 204 cheads.extend(c.node() for c in revset)
205 205 # even when we don't push, exchanging phase data is useful
206 206 remotephases = pushop.remote.listkeys('phases')
207 207 if (pushop.ui.configbool('ui', '_usedassubrepo', False)
208 208 and remotephases # server supports phases
209 209 and ret is None # nothing was pushed
210 210 and remotephases.get('publishing', False)):
211 211 # When:
212 212 # - this is a subrepo push
213 213 # - and remote support phase
214 214 # - and no changeset was pushed
215 215 # - and remote is publishing
216 216 # We may be in issue 3871 case!
217 217 # We drop the possible phase synchronisation done by
218 218 # courtesy to publish changesets possibly locally draft
219 219 # on the remote.
220 220 remotephases = {'publishing': 'True'}
221 221 if not remotephases: # old server or public only repo
222 222 localphasemove(cheads)
223 223 # don't push any phase data as there is nothing to push
224 224 else:
225 225 ana = phases.analyzeremotephases(pushop.repo, cheads,
226 226 remotephases)
227 227 pheads, droots = ana
228 228 ### Apply remote phase on local
229 229 if remotephases.get('publishing', False):
230 230 localphasemove(cheads)
231 231 else: # publish = False
232 232 localphasemove(pheads)
233 233 localphasemove(cheads, phases.draft)
234 234 ### Apply local phase on remote
235 235
236 236 # Get the list of all revs draft on remote by public here.
237 237 # XXX Beware that revset break if droots is not strictly
238 238 # XXX root we may want to ensure it is but it is costly
239 239 outdated = unfi.set('heads((%ln::%ln) and public())',
240 240 droots, cheads)
241 241 for newremotehead in outdated:
242 242 r = pushop.remote.pushkey('phases',
243 243 newremotehead.hex(),
244 244 str(phases.draft),
245 245 str(phases.public))
246 246 if not r:
247 247 pushop.ui.warn(_('updating %s to public failed!\n')
248 248 % newremotehead)
249 249 pushop.ui.debug('try to push obsolete markers to remote\n')
250 250 obsolete.syncpush(pushop.repo, pushop.remote)
251 251 finally:
252 252 if lock is not None:
253 253 lock.release()
254 254 finally:
255 255 if locallock is not None:
256 256 locallock.release()
257 257
258 bookmarks.updateremote(pushop.ui, unfi, pushop.remote, pushop.revs)
258 _pushbookmark(pushop.ui, unfi, pushop.remote, pushop.revs)
259 259 return ret
260
261 def _pushbookmark(ui, repo, remote, revs):
262 """Update bookmark position on remote"""
263 ui.debug("checking for updated bookmarks\n")
264 revnums = map(repo.changelog.rev, revs or [])
265 ancestors = [a for a in repo.changelog.ancestors(revnums, inclusive=True)]
266 (addsrc, adddst, advsrc, advdst, diverge, differ, invalid
267 ) = bookmarks.compare(repo, repo._bookmarks, remote.listkeys('bookmarks'),
268 srchex=hex)
269
270 for b, scid, dcid in advsrc:
271 if ancestors and repo[scid].rev() not in ancestors:
272 continue
273 if remote.pushkey('bookmarks', b, dcid, scid):
274 ui.status(_("updating bookmark %s\n") % b)
275 else:
276 ui.warn(_('updating bookmark %s failed!\n') % b)
General Comments 0
You need to be logged in to leave comments. Login now