##// END OF EJS Templates
push: move `commonheads` into the push object...
Pierre-Yves David -
r20467:ef880ced default
parent child Browse files
Show More
@@ -1,335 +1,338
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, newbranch=False):
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
34 # allow push of new branch
35 self.newbranch = newbranch
35 self.newbranch = newbranch
36 # did a local lock get acquired?
36 # did a local lock get acquired?
37 self.locallocked = None
37 self.locallocked = None
38 # Integer version of the push result
38 # Integer version of the push result
39 # - None means nothing to push
39 # - None means nothing to push
40 # - 0 means HTTP error
40 # - 0 means HTTP error
41 # - 1 means we pushed and remote head count is unchanged *or*
41 # - 1 means we pushed and remote head count is unchanged *or*
42 # we have outgoing changesets but refused to push
42 # we have outgoing changesets but refused to push
43 # - other values as described by addchangegroup()
43 # - other values as described by addchangegroup()
44 self.ret = None
44 self.ret = None
45 # discover.outgoing object (contains common and outgoin data)
45 # discover.outgoing object (contains common and outgoin data)
46 self.outgoing = None
46 self.outgoing = None
47 # all remote heads before the push
47 # all remote heads before the push
48 self.remoteheads = None
48 self.remoteheads = None
49 # testable as a boolean indicating if any nodes are missing locally.
49 # testable as a boolean indicating if any nodes are missing locally.
50 self.incoming = None
50 self.incoming = None
51 # set of all heads common after changeset bundle push
52 self.commonheads = None
51
53
52 def push(repo, remote, force=False, revs=None, newbranch=False):
54 def push(repo, remote, force=False, revs=None, newbranch=False):
53 '''Push outgoing changesets (limited by revs) from a local
55 '''Push outgoing changesets (limited by revs) from a local
54 repository to remote. Return an integer:
56 repository to remote. Return an integer:
55 - None means nothing to push
57 - None means nothing to push
56 - 0 means HTTP error
58 - 0 means HTTP error
57 - 1 means we pushed and remote head count is unchanged *or*
59 - 1 means we pushed and remote head count is unchanged *or*
58 we have outgoing changesets but refused to push
60 we have outgoing changesets but refused to push
59 - other values as described by addchangegroup()
61 - other values as described by addchangegroup()
60 '''
62 '''
61 pushop = pushoperation(repo, remote, force, revs, newbranch)
63 pushop = pushoperation(repo, remote, force, revs, newbranch)
62 if pushop.remote.local():
64 if pushop.remote.local():
63 missing = (set(pushop.repo.requirements)
65 missing = (set(pushop.repo.requirements)
64 - pushop.remote.local().supported)
66 - pushop.remote.local().supported)
65 if missing:
67 if missing:
66 msg = _("required features are not"
68 msg = _("required features are not"
67 " supported in the destination:"
69 " supported in the destination:"
68 " %s") % (', '.join(sorted(missing)))
70 " %s") % (', '.join(sorted(missing)))
69 raise util.Abort(msg)
71 raise util.Abort(msg)
70
72
71 # there are two ways to push to remote repo:
73 # there are two ways to push to remote repo:
72 #
74 #
73 # addchangegroup assumes local user can lock remote
75 # addchangegroup assumes local user can lock remote
74 # repo (local filesystem, old ssh servers).
76 # repo (local filesystem, old ssh servers).
75 #
77 #
76 # unbundle assumes local user cannot lock remote repo (new ssh
78 # unbundle assumes local user cannot lock remote repo (new ssh
77 # servers, http servers).
79 # servers, http servers).
78
80
79 if not pushop.remote.canpush():
81 if not pushop.remote.canpush():
80 raise util.Abort(_("destination does not support push"))
82 raise util.Abort(_("destination does not support push"))
81 # get local lock as we might write phase data
83 # get local lock as we might write phase data
82 locallock = None
84 locallock = None
83 try:
85 try:
84 locallock = pushop.repo.lock()
86 locallock = pushop.repo.lock()
85 pushop.locallocked = True
87 pushop.locallocked = True
86 except IOError, err:
88 except IOError, err:
87 pushop.locallocked = False
89 pushop.locallocked = False
88 if err.errno != errno.EACCES:
90 if err.errno != errno.EACCES:
89 raise
91 raise
90 # source repo cannot be locked.
92 # source repo cannot be locked.
91 # We do not abort the push, but just disable the local phase
93 # We do not abort the push, but just disable the local phase
92 # synchronisation.
94 # synchronisation.
93 msg = 'cannot lock source repository: %s\n' % err
95 msg = 'cannot lock source repository: %s\n' % err
94 pushop.ui.debug(msg)
96 pushop.ui.debug(msg)
95 try:
97 try:
96 pushop.repo.checkpush(pushop.force, pushop.revs)
98 pushop.repo.checkpush(pushop.force, pushop.revs)
97 lock = None
99 lock = None
98 unbundle = pushop.remote.capable('unbundle')
100 unbundle = pushop.remote.capable('unbundle')
99 if not unbundle:
101 if not unbundle:
100 lock = pushop.remote.lock()
102 lock = pushop.remote.lock()
101 try:
103 try:
102 _pushdiscovery(pushop)
104 _pushdiscovery(pushop)
103 if _pushcheckoutgoing(pushop):
105 if _pushcheckoutgoing(pushop):
104 _pushchangeset(pushop)
106 _pushchangeset(pushop)
105 _pushsyncphase(pushop)
107 _pushsyncphase(pushop)
106 _pushobsolete(pushop)
108 _pushobsolete(pushop)
107 finally:
109 finally:
108 if lock is not None:
110 if lock is not None:
109 lock.release()
111 lock.release()
110 finally:
112 finally:
111 if locallock is not None:
113 if locallock is not None:
112 locallock.release()
114 locallock.release()
113
115
114 _pushbookmark(pushop)
116 _pushbookmark(pushop)
115 return pushop.ret
117 return pushop.ret
116
118
117 def _pushdiscovery(pushop):
119 def _pushdiscovery(pushop):
118 # discovery
120 # discovery
119 unfi = pushop.repo.unfiltered()
121 unfi = pushop.repo.unfiltered()
120 fci = discovery.findcommonincoming
122 fci = discovery.findcommonincoming
121 commoninc = fci(unfi, pushop.remote, force=pushop.force)
123 commoninc = fci(unfi, pushop.remote, force=pushop.force)
122 common, inc, remoteheads = commoninc
124 common, inc, remoteheads = commoninc
123 fco = discovery.findcommonoutgoing
125 fco = discovery.findcommonoutgoing
124 outgoing = fco(unfi, pushop.remote, onlyheads=pushop.revs,
126 outgoing = fco(unfi, pushop.remote, onlyheads=pushop.revs,
125 commoninc=commoninc, force=pushop.force)
127 commoninc=commoninc, force=pushop.force)
126 pushop.outgoing = outgoing
128 pushop.outgoing = outgoing
127 pushop.remoteheads = remoteheads
129 pushop.remoteheads = remoteheads
128 pushop.incoming = inc
130 pushop.incoming = inc
129
131
130 def _pushcheckoutgoing(pushop):
132 def _pushcheckoutgoing(pushop):
131 outgoing = pushop.outgoing
133 outgoing = pushop.outgoing
132 unfi = pushop.repo.unfiltered()
134 unfi = pushop.repo.unfiltered()
133 if not outgoing.missing:
135 if not outgoing.missing:
134 # nothing to push
136 # nothing to push
135 scmutil.nochangesfound(unfi.ui, unfi, outgoing.excluded)
137 scmutil.nochangesfound(unfi.ui, unfi, outgoing.excluded)
136 return False
138 return False
137 # something to push
139 # something to push
138 if not pushop.force:
140 if not pushop.force:
139 # if repo.obsstore == False --> no obsolete
141 # if repo.obsstore == False --> no obsolete
140 # then, save the iteration
142 # then, save the iteration
141 if unfi.obsstore:
143 if unfi.obsstore:
142 # this message are here for 80 char limit reason
144 # this message are here for 80 char limit reason
143 mso = _("push includes obsolete changeset: %s!")
145 mso = _("push includes obsolete changeset: %s!")
144 mst = "push includes %s changeset: %s!"
146 mst = "push includes %s changeset: %s!"
145 # plain versions for i18n tool to detect them
147 # plain versions for i18n tool to detect them
146 _("push includes unstable changeset: %s!")
148 _("push includes unstable changeset: %s!")
147 _("push includes bumped changeset: %s!")
149 _("push includes bumped changeset: %s!")
148 _("push includes divergent changeset: %s!")
150 _("push includes divergent changeset: %s!")
149 # If we are to push if there is at least one
151 # If we are to push if there is at least one
150 # obsolete or unstable changeset in missing, at
152 # obsolete or unstable changeset in missing, at
151 # least one of the missinghead will be obsolete or
153 # least one of the missinghead will be obsolete or
152 # unstable. So checking heads only is ok
154 # unstable. So checking heads only is ok
153 for node in outgoing.missingheads:
155 for node in outgoing.missingheads:
154 ctx = unfi[node]
156 ctx = unfi[node]
155 if ctx.obsolete():
157 if ctx.obsolete():
156 raise util.Abort(mso % ctx)
158 raise util.Abort(mso % ctx)
157 elif ctx.troubled():
159 elif ctx.troubled():
158 raise util.Abort(_(mst)
160 raise util.Abort(_(mst)
159 % (ctx.troubles()[0],
161 % (ctx.troubles()[0],
160 ctx))
162 ctx))
161 newbm = pushop.ui.configlist('bookmarks', 'pushing')
163 newbm = pushop.ui.configlist('bookmarks', 'pushing')
162 discovery.checkheads(unfi, pushop.remote, outgoing,
164 discovery.checkheads(unfi, pushop.remote, outgoing,
163 pushop.remoteheads,
165 pushop.remoteheads,
164 pushop.newbranch,
166 pushop.newbranch,
165 bool(pushop.incoming),
167 bool(pushop.incoming),
166 newbm)
168 newbm)
167 return True
169 return True
168
170
169 def _pushchangeset(pushop):
171 def _pushchangeset(pushop):
170 """Make the actual push of changeset bundle to remote repo"""
172 """Make the actual push of changeset bundle to remote repo"""
171 outgoing = pushop.outgoing
173 outgoing = pushop.outgoing
172 unbundle = pushop.remote.capable('unbundle')
174 unbundle = pushop.remote.capable('unbundle')
173 # TODO: get bundlecaps from remote
175 # TODO: get bundlecaps from remote
174 bundlecaps = None
176 bundlecaps = None
175 # create a changegroup from local
177 # create a changegroup from local
176 if pushop.revs is None and not (outgoing.excluded
178 if pushop.revs is None and not (outgoing.excluded
177 or pushop.repo.changelog.filteredrevs):
179 or pushop.repo.changelog.filteredrevs):
178 # push everything,
180 # push everything,
179 # use the fast path, no race possible on push
181 # use the fast path, no race possible on push
180 bundler = changegroup.bundle10(pushop.repo, bundlecaps)
182 bundler = changegroup.bundle10(pushop.repo, bundlecaps)
181 cg = pushop.repo._changegroupsubset(outgoing,
183 cg = pushop.repo._changegroupsubset(outgoing,
182 bundler,
184 bundler,
183 'push',
185 'push',
184 fastpath=True)
186 fastpath=True)
185 else:
187 else:
186 cg = pushop.repo.getlocalbundle('push', outgoing, bundlecaps)
188 cg = pushop.repo.getlocalbundle('push', outgoing, bundlecaps)
187
189
188 # apply changegroup to remote
190 # apply changegroup to remote
189 if unbundle:
191 if unbundle:
190 # local repo finds heads on server, finds out what
192 # local repo finds heads on server, finds out what
191 # revs it must push. once revs transferred, if server
193 # revs it must push. once revs transferred, if server
192 # finds it has different heads (someone else won
194 # finds it has different heads (someone else won
193 # commit/push race), server aborts.
195 # commit/push race), server aborts.
194 if pushop.force:
196 if pushop.force:
195 remoteheads = ['force']
197 remoteheads = ['force']
196 else:
198 else:
197 remoteheads = pushop.remoteheads
199 remoteheads = pushop.remoteheads
198 # ssh: return remote's addchangegroup()
200 # ssh: return remote's addchangegroup()
199 # http: return remote's addchangegroup() or 0 for error
201 # http: return remote's addchangegroup() or 0 for error
200 pushop.ret = pushop.remote.unbundle(cg, remoteheads,
202 pushop.ret = pushop.remote.unbundle(cg, remoteheads,
201 'push')
203 'push')
202 else:
204 else:
203 # we return an integer indicating remote head count
205 # we return an integer indicating remote head count
204 # change
206 # change
205 pushop.ret = pushop.remote.addchangegroup(cg, 'push',
207 pushop.ret = pushop.remote.addchangegroup(cg, 'push',
206 pushop.repo.url())
208 pushop.repo.url())
207
209
208 def _pushsyncphase(pushop):
210 def _pushsyncphase(pushop):
209 """synchronise phase information locally and remotly"""
211 """synchronise phase information locally and remotly"""
210 unfi = pushop.repo.unfiltered()
212 unfi = pushop.repo.unfiltered()
211 if pushop.ret:
213 if pushop.ret:
212 # push succeed, synchronize target of the push
214 # push succeed, synchronize target of the push
213 cheads = pushop.outgoing.missingheads
215 cheads = pushop.outgoing.missingheads
214 elif pushop.revs is None:
216 elif pushop.revs is None:
215 # All out push fails. synchronize all common
217 # All out push fails. synchronize all common
216 cheads = pushop.outgoing.commonheads
218 cheads = pushop.outgoing.commonheads
217 else:
219 else:
218 # I want cheads = heads(::missingheads and ::commonheads)
220 # I want cheads = heads(::missingheads and ::commonheads)
219 # (missingheads is revs with secret changeset filtered out)
221 # (missingheads is revs with secret changeset filtered out)
220 #
222 #
221 # This can be expressed as:
223 # This can be expressed as:
222 # cheads = ( (missingheads and ::commonheads)
224 # cheads = ( (missingheads and ::commonheads)
223 # + (commonheads and ::missingheads))"
225 # + (commonheads and ::missingheads))"
224 # )
226 # )
225 #
227 #
226 # while trying to push we already computed the following:
228 # while trying to push we already computed the following:
227 # common = (::commonheads)
229 # common = (::commonheads)
228 # missing = ((commonheads::missingheads) - commonheads)
230 # missing = ((commonheads::missingheads) - commonheads)
229 #
231 #
230 # We can pick:
232 # We can pick:
231 # * missingheads part of common (::commonheads)
233 # * missingheads part of common (::commonheads)
232 common = set(pushop.outgoing.common)
234 common = set(pushop.outgoing.common)
233 nm = pushop.repo.changelog.nodemap
235 nm = pushop.repo.changelog.nodemap
234 cheads = [node for node in pushop.revs if nm[node] in common]
236 cheads = [node for node in pushop.revs if nm[node] in common]
235 # and
237 # and
236 # * commonheads parents on missing
238 # * commonheads parents on missing
237 revset = unfi.set('%ln and parents(roots(%ln))',
239 revset = unfi.set('%ln and parents(roots(%ln))',
238 pushop.outgoing.commonheads,
240 pushop.outgoing.commonheads,
239 pushop.outgoing.missing)
241 pushop.outgoing.missing)
240 cheads.extend(c.node() for c in revset)
242 cheads.extend(c.node() for c in revset)
243 pushop.commonheads = cheads
241 # even when we don't push, exchanging phase data is useful
244 # even when we don't push, exchanging phase data is useful
242 remotephases = pushop.remote.listkeys('phases')
245 remotephases = pushop.remote.listkeys('phases')
243 if (pushop.ui.configbool('ui', '_usedassubrepo', False)
246 if (pushop.ui.configbool('ui', '_usedassubrepo', False)
244 and remotephases # server supports phases
247 and remotephases # server supports phases
245 and pushop.ret is None # nothing was pushed
248 and pushop.ret is None # nothing was pushed
246 and remotephases.get('publishing', False)):
249 and remotephases.get('publishing', False)):
247 # When:
250 # When:
248 # - this is a subrepo push
251 # - this is a subrepo push
249 # - and remote support phase
252 # - and remote support phase
250 # - and no changeset was pushed
253 # - and no changeset was pushed
251 # - and remote is publishing
254 # - and remote is publishing
252 # We may be in issue 3871 case!
255 # We may be in issue 3871 case!
253 # We drop the possible phase synchronisation done by
256 # We drop the possible phase synchronisation done by
254 # courtesy to publish changesets possibly locally draft
257 # courtesy to publish changesets possibly locally draft
255 # on the remote.
258 # on the remote.
256 remotephases = {'publishing': 'True'}
259 remotephases = {'publishing': 'True'}
257 if not remotephases: # old server or public only rer
260 if not remotephases: # old server or public only rer
258 _localphasemove(pushop, cheads)
261 _localphasemove(pushop, cheads)
259 # don't push any phase data as there is nothing to push
262 # don't push any phase data as there is nothing to push
260 else:
263 else:
261 ana = phases.analyzeremotephases(pushop.repo, cheads,
264 ana = phases.analyzeremotephases(pushop.repo, cheads,
262 remotephases)
265 remotephases)
263 pheads, droots = ana
266 pheads, droots = ana
264 ### Apply remote phase on local
267 ### Apply remote phase on local
265 if remotephases.get('publishing', False):
268 if remotephases.get('publishing', False):
266 _localphasemove(pushop, cheads)
269 _localphasemove(pushop, cheads)
267 else: # publish = False
270 else: # publish = False
268 _localphasemove(pushop, pheads)
271 _localphasemove(pushop, pheads)
269 _localphasemove(pushop, cheads, phases.draft)
272 _localphasemove(pushop, cheads, phases.draft)
270 ### Apply local phase on remote
273 ### Apply local phase on remote
271
274
272 # Get the list of all revs draft on remote by public here.
275 # Get the list of all revs draft on remote by public here.
273 # XXX Beware that revset break if droots is not strictly
276 # XXX Beware that revset break if droots is not strictly
274 # XXX root we may want to ensure it is but it is costly
277 # XXX root we may want to ensure it is but it is costly
275 outdated = unfi.set('heads((%ln::%ln) and public())',
278 outdated = unfi.set('heads((%ln::%ln) and public())',
276 droots, cheads)
279 droots, cheads)
277 for newremotehead in outdated:
280 for newremotehead in outdated:
278 r = pushop.remote.pushkey('phases',
281 r = pushop.remote.pushkey('phases',
279 newremotehead.hex(),
282 newremotehead.hex(),
280 str(phases.draft),
283 str(phases.draft),
281 str(phases.public))
284 str(phases.public))
282 if not r:
285 if not r:
283 pushop.ui.warn(_('updating %s to public failed!\n')
286 pushop.ui.warn(_('updating %s to public failed!\n')
284 % newremotehead)
287 % newremotehead)
285
288
286 def _localphasemove(pushop, nodes, phase=phases.public):
289 def _localphasemove(pushop, nodes, phase=phases.public):
287 """move <nodes> to <phase> in the local source repo"""
290 """move <nodes> to <phase> in the local source repo"""
288 if pushop.locallocked:
291 if pushop.locallocked:
289 phases.advanceboundary(pushop.repo, phase, nodes)
292 phases.advanceboundary(pushop.repo, phase, nodes)
290 else:
293 else:
291 # repo is not locked, do not change any phases!
294 # repo is not locked, do not change any phases!
292 # Informs the user that phases should have been moved when
295 # Informs the user that phases should have been moved when
293 # applicable.
296 # applicable.
294 actualmoves = [n for n in nodes if phase < pushop.repo[n].phase()]
297 actualmoves = [n for n in nodes if phase < pushop.repo[n].phase()]
295 phasestr = phases.phasenames[phase]
298 phasestr = phases.phasenames[phase]
296 if actualmoves:
299 if actualmoves:
297 pushop.ui.status(_('cannot lock source repo, skipping '
300 pushop.ui.status(_('cannot lock source repo, skipping '
298 'local %s phase update\n') % phasestr)
301 'local %s phase update\n') % phasestr)
299
302
300 def _pushobsolete(pushop):
303 def _pushobsolete(pushop):
301 """utility function to push obsolete markers to a remote"""
304 """utility function to push obsolete markers to a remote"""
302 pushop.ui.debug('try to push obsolete markers to remote\n')
305 pushop.ui.debug('try to push obsolete markers to remote\n')
303 repo = pushop.repo
306 repo = pushop.repo
304 remote = pushop.remote
307 remote = pushop.remote
305 if (obsolete._enabled and repo.obsstore and
308 if (obsolete._enabled and repo.obsstore and
306 'obsolete' in remote.listkeys('namespaces')):
309 'obsolete' in remote.listkeys('namespaces')):
307 rslts = []
310 rslts = []
308 remotedata = repo.listkeys('obsolete')
311 remotedata = repo.listkeys('obsolete')
309 for key in sorted(remotedata, reverse=True):
312 for key in sorted(remotedata, reverse=True):
310 # reverse sort to ensure we end with dump0
313 # reverse sort to ensure we end with dump0
311 data = remotedata[key]
314 data = remotedata[key]
312 rslts.append(remote.pushkey('obsolete', key, '', data))
315 rslts.append(remote.pushkey('obsolete', key, '', data))
313 if [r for r in rslts if not r]:
316 if [r for r in rslts if not r]:
314 msg = _('failed to push some obsolete markers!\n')
317 msg = _('failed to push some obsolete markers!\n')
315 repo.ui.warn(msg)
318 repo.ui.warn(msg)
316
319
317 def _pushbookmark(pushop):
320 def _pushbookmark(pushop):
318 """Update bookmark position on remote"""
321 """Update bookmark position on remote"""
319 ui = pushop.ui
322 ui = pushop.ui
320 repo = pushop.repo.unfiltered()
323 repo = pushop.repo.unfiltered()
321 remote = pushop.remote
324 remote = pushop.remote
322 ui.debug("checking for updated bookmarks\n")
325 ui.debug("checking for updated bookmarks\n")
323 revnums = map(repo.changelog.rev, pushop.revs or [])
326 revnums = map(repo.changelog.rev, pushop.revs or [])
324 ancestors = [a for a in repo.changelog.ancestors(revnums, inclusive=True)]
327 ancestors = [a for a in repo.changelog.ancestors(revnums, inclusive=True)]
325 (addsrc, adddst, advsrc, advdst, diverge, differ, invalid
328 (addsrc, adddst, advsrc, advdst, diverge, differ, invalid
326 ) = bookmarks.compare(repo, repo._bookmarks, remote.listkeys('bookmarks'),
329 ) = bookmarks.compare(repo, repo._bookmarks, remote.listkeys('bookmarks'),
327 srchex=hex)
330 srchex=hex)
328
331
329 for b, scid, dcid in advsrc:
332 for b, scid, dcid in advsrc:
330 if ancestors and repo[scid].rev() not in ancestors:
333 if ancestors and repo[scid].rev() not in ancestors:
331 continue
334 continue
332 if remote.pushkey('bookmarks', b, dcid, scid):
335 if remote.pushkey('bookmarks', b, dcid, scid):
333 ui.status(_("updating bookmark %s\n") % b)
336 ui.status(_("updating bookmark %s\n") % b)
334 else:
337 else:
335 ui.warn(_('updating bookmark %s failed!\n') % b)
338 ui.warn(_('updating bookmark %s failed!\n') % b)
General Comments 0
You need to be logged in to leave comments. Login now