comparison hgext3rd/topic/__init__.py @ 4814:48b30ff742cb

python3: use format-source to run byteify-strings in .py files Using the format-source extension smooth out the pain of merging after auto-formatting. This change makes all of the Evolve test suite pass under python3 and has added benefit of being 100% automated using mercurial's `byteify-strings` script version 1.0 (revision 11498aa91c036c6d70f7ac5ee5af2664a84a1130). How to benefit from the help of format-source is explained in the README.
author Raphaël Gomès <rgomes@octobus.net>
date Tue, 06 Aug 2019 15:06:38 +0200
parents 67567d7f1174
children 6f5d3f58fbe4
comparison
equal deleted inserted replaced
4812:67567d7f1174 4814:48b30ff742cb
158 topicmap, 158 topicmap,
159 ) 159 )
160 160
161 cmdtable = {} 161 cmdtable = {}
162 command = registrar.command(cmdtable) 162 command = registrar.command(cmdtable)
163 colortable = {'topic.active': 'green', 163 colortable = {b'topic.active': b'green',
164 'topic.list.unstablecount': 'red', 164 b'topic.list.unstablecount': b'red',
165 'topic.list.headcount.multiple': 'yellow', 165 b'topic.list.headcount.multiple': b'yellow',
166 'topic.list.behindcount': 'cyan', 166 b'topic.list.behindcount': b'cyan',
167 'topic.list.behinderror': 'red', 167 b'topic.list.behinderror': b'red',
168 'stack.index': 'yellow', 168 b'stack.index': b'yellow',
169 'stack.index.base': 'none dim', 169 b'stack.index.base': b'none dim',
170 'stack.desc.base': 'none dim', 170 b'stack.desc.base': b'none dim',
171 'stack.shortnode.base': 'none dim', 171 b'stack.shortnode.base': b'none dim',
172 'stack.state.base': 'dim', 172 b'stack.state.base': b'dim',
173 'stack.state.clean': 'green', 173 b'stack.state.clean': b'green',
174 'stack.index.current': 'cyan', # random pick 174 b'stack.index.current': b'cyan', # random pick
175 'stack.state.current': 'cyan bold', # random pick 175 b'stack.state.current': b'cyan bold', # random pick
176 'stack.desc.current': 'cyan', # random pick 176 b'stack.desc.current': b'cyan', # random pick
177 'stack.shortnode.current': 'cyan', # random pick 177 b'stack.shortnode.current': b'cyan', # random pick
178 'stack.state.orphan': 'red', 178 b'stack.state.orphan': b'red',
179 'stack.state.content-divergent': 'red', 179 b'stack.state.content-divergent': b'red',
180 'stack.state.phase-divergent': 'red', 180 b'stack.state.phase-divergent': b'red',
181 'stack.summary.behindcount': 'cyan', 181 b'stack.summary.behindcount': b'cyan',
182 'stack.summary.behinderror': 'red', 182 b'stack.summary.behinderror': b'red',
183 'stack.summary.headcount.multiple': 'yellow', 183 b'stack.summary.headcount.multiple': b'yellow',
184 # default color to help log output and thg 184 # default color to help log output and thg
185 # (first pick I could think off, update as needed 185 # (first pick I could think off, update as needed
186 'log.topic': 'green_background', 186 b'log.topic': b'green_background',
187 'topic.active': 'green', 187 b'topic.active': b'green',
188 } 188 }
189 189
190 __version__ = b'0.17.0.dev' 190 __version__ = b'0.17.0.dev'
191 191
192 testedwith = b'4.5.2 4.6.2 4.7 4.8 4.9 5.0 5.1' 192 testedwith = b'4.5.2 4.6.2 4.7 4.8 4.9 5.0 5.1'
193 minimumhgversion = b'4.5' 193 minimumhgversion = b'4.5'
198 from mercurial import configitems 198 from mercurial import configitems
199 199
200 configtable = {} 200 configtable = {}
201 configitem = registrar.configitem(configtable) 201 configitem = registrar.configitem(configtable)
202 202
203 configitem('experimental', 'enforce-topic', 203 configitem(b'experimental', b'enforce-topic',
204 default=False, 204 default=False,
205 ) 205 )
206 configitem('experimental', 'enforce-single-head', 206 configitem(b'experimental', b'enforce-single-head',
207 default=False, 207 default=False,
208 ) 208 )
209 configitem('experimental', 'topic-mode', 209 configitem(b'experimental', b'topic-mode',
210 default=None, 210 default=None,
211 ) 211 )
212 configitem('experimental', 'topic.publish-bare-branch', 212 configitem(b'experimental', b'topic.publish-bare-branch',
213 default=False, 213 default=False,
214 ) 214 )
215 configitem('experimental', 'topic.allow-publish', 215 configitem(b'experimental', b'topic.allow-publish',
216 default=configitems.dynamicdefault, 216 default=configitems.dynamicdefault,
217 ) 217 )
218 configitem('_internal', 'keep-topic', 218 configitem(b'_internal', b'keep-topic',
219 default=False, 219 default=False,
220 ) 220 )
221 configitem('experimental', 'topic-mode.server', 221 configitem(b'experimental', b'topic-mode.server',
222 default=configitems.dynamicdefault, 222 default=configitems.dynamicdefault,
223 ) 223 )
224 224
225 def extsetup(ui): 225 def extsetup(ui):
226 # register config that strictly belong to other code (thg, core, etc) 226 # register config that strictly belong to other code (thg, core, etc)
227 # 227 #
228 # To ensure all config items we used are registered, we register them if 228 # To ensure all config items we used are registered, we register them if
229 # nobody else did so far. 229 # nobody else did so far.
230 from mercurial import configitems 230 from mercurial import configitems
231 extraitem = functools.partial(configitems._register, ui._knownconfig) 231 extraitem = functools.partial(configitems._register, ui._knownconfig)
232 if ('experimental' not in ui._knownconfig 232 if (b'experimental' not in ui._knownconfig
233 or not ui._knownconfig['experimental'].get('thg.displaynames')): 233 or not ui._knownconfig[b'experimental'].get(b'thg.displaynames')):
234 extraitem('experimental', 'thg.displaynames', 234 extraitem(b'experimental', b'thg.displaynames',
235 default=None, 235 default=None,
236 ) 236 )
237 if ('devel' not in ui._knownconfig 237 if (b'devel' not in ui._knownconfig
238 or not ui._knownconfig['devel'].get('random')): 238 or not ui._knownconfig[b'devel'].get(b'random')):
239 extraitem('devel', 'randomseed', 239 extraitem(b'devel', b'randomseed',
240 default=None, 240 default=None,
241 ) 241 )
242 242
243 # we need to do old style declaration for <= 4.5 243 # we need to do old style declaration for <= 4.5
244 templatekeyword = registrar.templatekeyword() 244 templatekeyword = registrar.templatekeyword()
245 post45template = r'requires=' in templatekeyword.__doc__ 245 post45template = r'requires=' in templatekeyword.__doc__
246 246
247 def _contexttopic(self, force=False): 247 def _contexttopic(self, force=False):
248 if not (force or self.mutable()): 248 if not (force or self.mutable()):
249 return '' 249 return b''
250 return self.extra().get(constants.extrakey, '') 250 return self.extra().get(constants.extrakey, b'')
251 context.basectx.topic = _contexttopic 251 context.basectx.topic = _contexttopic
252 252
253 def _contexttopicidx(self): 253 def _contexttopicidx(self):
254 topic = self.topic() 254 topic = self.topic()
255 if not topic or self.obsolete(): 255 if not topic or self.obsolete():
273 revs = None 273 revs = None
274 if stackrev.match(name): 274 if stackrev.match(name):
275 idx = int(name[1:]) 275 idx = int(name[1:])
276 tname = topic = repo.currenttopic 276 tname = topic = repo.currenttopic
277 if topic: 277 if topic:
278 ttype = 'topic' 278 ttype = b'topic'
279 revs = list(stack.stack(repo, topic=topic)) 279 revs = list(stack.stack(repo, topic=topic))
280 else: 280 else:
281 ttype = 'branch' 281 ttype = b'branch'
282 tname = branch = repo[None].branch() 282 tname = branch = repo[None].branch()
283 revs = list(stack.stack(repo, branch=branch)) 283 revs = list(stack.stack(repo, branch=branch))
284 elif topicrev.match(name): 284 elif topicrev.match(name):
285 idx = int(name[1:]) 285 idx = int(name[1:])
286 ttype = 'topic' 286 ttype = b'topic'
287 tname = topic = repo.currenttopic 287 tname = topic = repo.currenttopic
288 if not tname: 288 if not tname:
289 raise error.Abort(_('cannot resolve "%s": no active topic') % name) 289 raise error.Abort(_(b'cannot resolve "%s": no active topic') % name)
290 revs = list(stack.stack(repo, topic=topic)) 290 revs = list(stack.stack(repo, topic=topic))
291 291
292 if revs is not None: 292 if revs is not None:
293 try: 293 try:
294 r = revs[idx] 294 r = revs[idx]
295 except IndexError: 295 except IndexError:
296 if ttype == 'topic': 296 if ttype == b'topic':
297 msg = _('cannot resolve "%s": %s "%s" has only %d changesets') 297 msg = _(b'cannot resolve "%s": %s "%s" has only %d changesets')
298 elif ttype == 'branch': 298 elif ttype == b'branch':
299 msg = _('cannot resolve "%s": %s "%s" has only %d non-public changesets') 299 msg = _(b'cannot resolve "%s": %s "%s" has only %d non-public changesets')
300 raise error.Abort(msg % (name, ttype, tname, len(revs) - 1)) 300 raise error.Abort(msg % (name, ttype, tname, len(revs) - 1))
301 # t0 or s0 can be None 301 # t0 or s0 can be None
302 if r == -1 and idx == 0: 302 if r == -1 and idx == 0:
303 msg = _('the %s "%s" has no %s') 303 msg = _(b'the %s "%s" has no %s')
304 raise error.Abort(msg % (ttype, tname, name)) 304 raise error.Abort(msg % (ttype, tname, name))
305 return [repo[r].node()] 305 return [repo[r].node()]
306 if name not in repo.topics: 306 if name not in repo.topics:
307 return [] 307 return []
308 node = repo.changelog.node 308 node = repo.changelog.node
309 return [node(rev) for rev in repo.revs('topic(%s)', name)] 309 return [node(rev) for rev in repo.revs(b'topic(%s)', name)]
310 310
311 def _nodemap(repo, node): 311 def _nodemap(repo, node):
312 ctx = repo[node] 312 ctx = repo[node]
313 t = ctx.topic() 313 t = ctx.topic()
314 if t and ctx.phase() > phases.public: 314 if t and ctx.phase() > phases.public:
319 destination.modsetup(ui) 319 destination.modsetup(ui)
320 discovery.modsetup(ui) 320 discovery.modsetup(ui)
321 topicmap.modsetup(ui) 321 topicmap.modsetup(ui)
322 setupimportexport(ui) 322 setupimportexport(ui)
323 323
324 extensions.afterloaded('rebase', _fixrebase) 324 extensions.afterloaded(b'rebase', _fixrebase)
325 325
326 flow.installpushflag(ui) 326 flow.installpushflag(ui)
327 327
328 entry = extensions.wrapcommand(commands.table, 'commit', commitwrap) 328 entry = extensions.wrapcommand(commands.table, b'commit', commitwrap)
329 entry[1].append(('t', 'topic', '', 329 entry[1].append((b't', b'topic', b'',
330 _("use specified topic"), _('TOPIC'))) 330 _(b"use specified topic"), _(b'TOPIC')))
331 331
332 entry = extensions.wrapcommand(commands.table, 'push', pushoutgoingwrap) 332 entry = extensions.wrapcommand(commands.table, b'push', pushoutgoingwrap)
333 entry[1].append(('t', 'topic', '', 333 entry[1].append((b't', b'topic', b'',
334 _("topic to push"), _('TOPIC'))) 334 _(b"topic to push"), _(b'TOPIC')))
335 335
336 entry = extensions.wrapcommand(commands.table, 'outgoing', 336 entry = extensions.wrapcommand(commands.table, b'outgoing',
337 pushoutgoingwrap) 337 pushoutgoingwrap)
338 entry[1].append(('t', 'topic', '', 338 entry[1].append((b't', b'topic', b'',
339 _("topic to push"), _('TOPIC'))) 339 _(b"topic to push"), _(b'TOPIC')))
340 340
341 extensions.wrapfunction(cmdutil, 'buildcommittext', committextwrap) 341 extensions.wrapfunction(cmdutil, 'buildcommittext', committextwrap)
342 extensions.wrapfunction(merge, 'update', mergeupdatewrap) 342 extensions.wrapfunction(merge, 'update', mergeupdatewrap)
343 # We need to check whether t0 or b0 or s0 is passed to override the default update 343 # We need to check whether t0 or b0 or s0 is passed to override the default update
344 # behaviour of changing topic and I can't find a better way 344 # behaviour of changing topic and I can't find a better way
345 # to do that as scmutil.revsingle returns the rev number and hence we can't 345 # to do that as scmutil.revsingle returns the rev number and hence we can't
346 # plug into logic for this into mergemod.update(). 346 # plug into logic for this into mergemod.update().
347 extensions.wrapcommand(commands.table, 'update', checkt0) 347 extensions.wrapcommand(commands.table, b'update', checkt0)
348 348
349 try: 349 try:
350 evolve = extensions.find('evolve') 350 evolve = extensions.find(b'evolve')
351 extensions.wrapfunction(evolve.rewriteutil, "presplitupdate", 351 extensions.wrapfunction(evolve.rewriteutil, "presplitupdate",
352 presplitupdatetopic) 352 presplitupdatetopic)
353 except (KeyError, AttributeError): 353 except (KeyError, AttributeError):
354 pass 354 pass
355 355
356 cmdutil.summaryhooks.add('topic', summaryhook) 356 cmdutil.summaryhooks.add(b'topic', summaryhook)
357 357
358 if not post45template: 358 if not post45template:
359 templatekw.keywords['topic'] = topickw 359 templatekw.keywords[b'topic'] = topickw
360 templatekw.keywords['topicidx'] = topicidxkw 360 templatekw.keywords[b'topicidx'] = topicidxkw
361 # Wrap workingctx extra to return the topic name 361 # Wrap workingctx extra to return the topic name
362 extensions.wrapfunction(context.workingctx, '__init__', wrapinit) 362 extensions.wrapfunction(context.workingctx, '__init__', wrapinit)
363 # Wrap changelog.add to drop empty topic 363 # Wrap changelog.add to drop empty topic
364 extensions.wrapfunction(changelog.changelog, 'add', wrapadd) 364 extensions.wrapfunction(changelog.changelog, 'add', wrapadd)
365 365
367 if not isinstance(repo, localrepo.localrepository): 367 if not isinstance(repo, localrepo.localrepository):
368 return # this can be a peer in the ssh case (puzzling) 368 return # this can be a peer in the ssh case (puzzling)
369 369
370 repo = repo.unfiltered() 370 repo = repo.unfiltered()
371 371
372 if repo.ui.config('experimental', 'thg.displaynames') is None: 372 if repo.ui.config(b'experimental', b'thg.displaynames') is None:
373 repo.ui.setconfig('experimental', 'thg.displaynames', 'topics', 373 repo.ui.setconfig(b'experimental', b'thg.displaynames', b'topics',
374 source='topic-extension') 374 source=b'topic-extension')
375 375
376 class topicrepo(repo.__class__): 376 class topicrepo(repo.__class__):
377 377
378 # attribute for other code to distinct between repo with topic and repo without 378 # attribute for other code to distinct between repo with topic and repo without
379 hastopicext = True 379 hastopicext = True
380 380
381 def _restrictcapabilities(self, caps): 381 def _restrictcapabilities(self, caps):
382 caps = super(topicrepo, self)._restrictcapabilities(caps) 382 caps = super(topicrepo, self)._restrictcapabilities(caps)
383 caps.add('topics') 383 caps.add(b'topics')
384 return caps 384 return caps
385 385
386 def commit(self, *args, **kwargs): 386 def commit(self, *args, **kwargs):
387 backup = self.ui.backupconfig('ui', 'allowemptycommit') 387 backup = self.ui.backupconfig(b'ui', b'allowemptycommit')
388 try: 388 try:
389 if self.currenttopic != self['.'].topic(): 389 if self.currenttopic != self[b'.'].topic():
390 # bypass the core "nothing changed" logic 390 # bypass the core "nothing changed" logic
391 self.ui.setconfig('ui', 'allowemptycommit', True) 391 self.ui.setconfig(b'ui', b'allowemptycommit', True)
392 return super(topicrepo, self).commit(*args, **kwargs) 392 return super(topicrepo, self).commit(*args, **kwargs)
393 finally: 393 finally:
394 self.ui.restoreconfig(backup) 394 self.ui.restoreconfig(backup)
395 395
396 def commitctx(self, ctx, error=None): 396 def commitctx(self, ctx, error=None):
402 if isinstance(ctx, context.workingcommitctx): 402 if isinstance(ctx, context.workingcommitctx):
403 current = self.currenttopic 403 current = self.currenttopic
404 if current: 404 if current:
405 ctx.extra()[constants.extrakey] = current 405 ctx.extra()[constants.extrakey] = current
406 if (isinstance(ctx, context.memctx) 406 if (isinstance(ctx, context.memctx)
407 and ctx.extra().get('amend_source') 407 and ctx.extra().get(b'amend_source')
408 and ctx.topic() 408 and ctx.topic()
409 and not self.currenttopic): 409 and not self.currenttopic):
410 # we are amending and need to remove a topic 410 # we are amending and need to remove a topic
411 del ctx.extra()[constants.extrakey] 411 del ctx.extra()[constants.extrakey]
412 return super(topicrepo, self).commitctx(ctx, error=error) 412 return super(topicrepo, self).commitctx(ctx, error=error)
413 413
414 @property 414 @property
415 def topics(self): 415 def topics(self):
416 if self._topics is not None: 416 if self._topics is not None:
417 return self._topics 417 return self._topics
418 topics = set(['', self.currenttopic]) 418 topics = set([b'', self.currenttopic])
419 for c in self.set('not public()'): 419 for c in self.set(b'not public()'):
420 topics.add(c.topic()) 420 topics.add(c.topic())
421 topics.remove('') 421 topics.remove(b'')
422 self._topics = topics 422 self._topics = topics
423 return topics 423 return topics
424 424
425 @property 425 @property
426 def currenttopic(self): 426 def currenttopic(self):
427 return self.vfs.tryread('topic') 427 return self.vfs.tryread(b'topic')
428 428
429 # overwritten at the instance level by topicmap.py 429 # overwritten at the instance level by topicmap.py
430 _autobranchmaptopic = True 430 _autobranchmaptopic = True
431 431
432 def branchmap(self, topic=None): 432 def branchmap(self, topic=None):
439 439
440 def branchheads(self, branch=None, start=None, closed=False): 440 def branchheads(self, branch=None, start=None, closed=False):
441 if branch is None: 441 if branch is None:
442 branch = self[None].branch() 442 branch = self[None].branch()
443 if self.currenttopic: 443 if self.currenttopic:
444 branch = "%s:%s" % (branch, self.currenttopic) 444 branch = b"%s:%s" % (branch, self.currenttopic)
445 return super(topicrepo, self).branchheads(branch=branch, 445 return super(topicrepo, self).branchheads(branch=branch,
446 start=start, 446 start=start,
447 closed=closed) 447 closed=closed)
448 448
449 def invalidatevolatilesets(self): 449 def invalidatevolatilesets(self):
462 return peer 462 return peer
463 463
464 def transaction(self, desc, *a, **k): 464 def transaction(self, desc, *a, **k):
465 ctr = self.currenttransaction() 465 ctr = self.currenttransaction()
466 tr = super(topicrepo, self).transaction(desc, *a, **k) 466 tr = super(topicrepo, self).transaction(desc, *a, **k)
467 if desc in ('strip', 'repair') or ctr is not None: 467 if desc in (b'strip', b'repair') or ctr is not None:
468 return tr 468 return tr
469 469
470 reporef = weakref.ref(self) 470 reporef = weakref.ref(self)
471 if self.ui.configbool('experimental', 'enforce-single-head'): 471 if self.ui.configbool(b'experimental', b'enforce-single-head'):
472 if util.safehasattr(tr, 'validator'): # hg <= 4.7 472 if util.safehasattr(tr, 'validator'): # hg <= 4.7
473 origvalidator = tr.validator 473 origvalidator = tr.validator
474 else: 474 else:
475 origvalidator = tr._validator 475 origvalidator = tr._validator
476 476
482 if util.safehasattr(tr, 'validator'): # hg <= 4.7 482 if util.safehasattr(tr, 'validator'): # hg <= 4.7
483 tr.validator = validator 483 tr.validator = validator
484 else: 484 else:
485 tr._validator = validator 485 tr._validator = validator
486 486
487 topicmodeserver = self.ui.config('experimental', 487 topicmodeserver = self.ui.config(b'experimental',
488 'topic-mode.server', 'ignore') 488 b'topic-mode.server', b'ignore')
489 ispush = (desc.startswith('push') or desc.startswith('serve')) 489 ispush = (desc.startswith(b'push') or desc.startswith(b'serve'))
490 if (topicmodeserver != 'ignore' and ispush): 490 if (topicmodeserver != b'ignore' and ispush):
491 if util.safehasattr(tr, 'validator'): # hg <= 4.7 491 if util.safehasattr(tr, 'validator'): # hg <= 4.7
492 origvalidator = tr.validator 492 origvalidator = tr.validator
493 else: 493 else:
494 origvalidator = tr._validator 494 origvalidator = tr._validator
495 495
500 if util.safehasattr(tr, 'validator'): # hg <= 4.7 500 if util.safehasattr(tr, 'validator'): # hg <= 4.7
501 tr.validator = validator 501 tr.validator = validator
502 else: 502 else:
503 tr._validator = validator 503 tr._validator = validator
504 504
505 elif (self.ui.configbool('experimental', 'topic.publish-bare-branch') 505 elif (self.ui.configbool(b'experimental', b'topic.publish-bare-branch')
506 and (desc.startswith('push') 506 and (desc.startswith(b'push')
507 or desc.startswith('serve')) 507 or desc.startswith(b'serve'))
508 ): 508 ):
509 origclose = tr.close 509 origclose = tr.close
510 trref = weakref.ref(tr) 510 trref = weakref.ref(tr)
511 511
512 def close(): 512 def close():
513 repo = reporef() 513 repo = reporef()
514 tr2 = trref() 514 tr2 = trref()
515 flow.publishbarebranch(repo, tr2) 515 flow.publishbarebranch(repo, tr2)
516 origclose() 516 origclose()
517 tr.close = close 517 tr.close = close
518 allow_publish = self.ui.configbool('experimental', 518 allow_publish = self.ui.configbool(b'experimental',
519 'topic.allow-publish', 519 b'topic.allow-publish',
520 True) 520 True)
521 if not allow_publish: 521 if not allow_publish:
522 if util.safehasattr(tr, 'validator'): # hg <= 4.7 522 if util.safehasattr(tr, 'validator'): # hg <= 4.7
523 origvalidator = tr.validator 523 origvalidator = tr.validator
524 else: 524 else:
545 # check active topic emptiness 545 # check active topic emptiness
546 repo = reporef() 546 repo = reporef()
547 csetcount = stack.stack(repo, topic=ct).changesetcount 547 csetcount = stack.stack(repo, topic=ct).changesetcount
548 empty = csetcount == 0 548 empty = csetcount == 0
549 if empty and not ctwasempty: 549 if empty and not ctwasempty:
550 ui.status("active topic '%s' is now empty\n" % ct) 550 ui.status(b"active topic '%s' is now empty\n" % ct)
551 trnames = getattr(tr, 'names', getattr(tr, '_names', ())) 551 trnames = getattr(tr, 'names', getattr(tr, '_names', ()))
552 if ('phase' in trnames 552 if (b'phase' in trnames
553 or any(n.startswith('push-response') 553 or any(n.startswith(b'push-response')
554 for n in trnames)): 554 for n in trnames)):
555 ui.status(_("(use 'hg topic --clear' to clear it if needed)\n")) 555 ui.status(_(b"(use 'hg topic --clear' to clear it if needed)\n"))
556 hint = _("(see 'hg help topics' for more information)\n") 556 hint = _(b"(see 'hg help topics' for more information)\n")
557 if ctwasempty and not empty: 557 if ctwasempty and not empty:
558 if csetcount == 1: 558 if csetcount == 1:
559 msg = _("active topic '%s' grew its first changeset\n%s") 559 msg = _(b"active topic '%s' grew its first changeset\n%s")
560 ui.status(msg % (ct, hint)) 560 ui.status(msg % (ct, hint))
561 else: 561 else:
562 msg = _("active topic '%s' grew its %s first changesets\n%s") 562 msg = _(b"active topic '%s' grew its %s first changesets\n%s")
563 ui.status(msg % (ct, csetcount, hint)) 563 ui.status(msg % (ct, csetcount, hint))
564 564
565 tr.addpostclose('signalcurrenttopicempty', currenttopicempty) 565 tr.addpostclose(b'signalcurrenttopicempty', currenttopicempty)
566 return tr 566 return tr
567 567
568 repo.__class__ = topicrepo 568 repo.__class__ = topicrepo
569 repo._topics = None 569 repo._topics = None
570 if util.safehasattr(repo, 'names'): 570 if util.safehasattr(repo, 'names'):
571 repo.names.addnamespace(namespaces.namespace( 571 repo.names.addnamespace(namespaces.namespace(
572 'topics', 'topic', namemap=_namemap, nodemap=_nodemap, 572 b'topics', b'topic', namemap=_namemap, nodemap=_nodemap,
573 listnames=lambda repo: repo.topics)) 573 listnames=lambda repo: repo.topics))
574 574
575 if post45template: 575 if post45template:
576 @templatekeyword(b'topic', requires={b'ctx'}) 576 @templatekeyword(b'topic', requires={b'ctx'})
577 def topickw(context, mapping): 577 def topickw(context, mapping):
578 """:topic: String. The topic of the changeset""" 578 """:topic: String. The topic of the changeset"""
579 ctx = context.resource(mapping, 'ctx') 579 ctx = context.resource(mapping, b'ctx')
580 return ctx.topic() 580 return ctx.topic()
581 581
582 @templatekeyword(b'topicidx', requires={b'ctx'}) 582 @templatekeyword(b'topicidx', requires={b'ctx'})
583 def topicidxkw(context, mapping): 583 def topicidxkw(context, mapping):
584 """:topicidx: Integer. Index of the changeset as a stack alias""" 584 """:topicidx: Integer. Index of the changeset as a stack alias"""
585 ctx = context.resource(mapping, 'ctx') 585 ctx = context.resource(mapping, b'ctx')
586 return ctx.topicidx() 586 return ctx.topicidx()
587 else: 587 else:
588 def topickw(**args): 588 def topickw(**args):
589 """:topic: String. The topic of the changeset""" 589 """:topic: String. The topic of the changeset"""
590 return args['ctx'].topic() 590 return args[b'ctx'].topic()
591 591
592 def topicidxkw(**args): 592 def topicidxkw(**args):
593 """:topicidx: Integer. Index of the changeset as a stack alias""" 593 """:topicidx: Integer. Index of the changeset as a stack alias"""
594 return args['ctx'].topicidx() 594 return args[b'ctx'].topicidx()
595 595
596 def wrapinit(orig, self, repo, *args, **kwargs): 596 def wrapinit(orig, self, repo, *args, **kwargs):
597 orig(self, repo, *args, **kwargs) 597 orig(self, repo, *args, **kwargs)
598 if not hastopicext(repo): 598 if not hastopicext(repo):
599 return 599 return
600 if constants.extrakey not in self._extra: 600 if constants.extrakey not in self._extra:
601 if getattr(repo, 'currenttopic', ''): 601 if getattr(repo, 'currenttopic', b''):
602 self._extra[constants.extrakey] = repo.currenttopic 602 self._extra[constants.extrakey] = repo.currenttopic
603 else: 603 else:
604 # Empty key will be dropped from extra by another hack at the changegroup level 604 # Empty key will be dropped from extra by another hack at the changegroup level
605 self._extra[constants.extrakey] = '' 605 self._extra[constants.extrakey] = b''
606 606
607 def wrapadd(orig, cl, manifest, files, desc, transaction, p1, p2, user, 607 def wrapadd(orig, cl, manifest, files, desc, transaction, p1, p2, user,
608 date=None, extra=None, p1copies=None, p2copies=None, 608 date=None, extra=None, p1copies=None, p2copies=None,
609 filesadded=None, filesremoved=None): 609 filesadded=None, filesremoved=None):
610 if constants.extrakey in extra and not extra[constants.extrakey]: 610 if constants.extrakey in extra and not extra[constants.extrakey]:
677 rev = opts.get('rev') 677 rev = opts.get('rev')
678 current = opts.get('current') 678 current = opts.get('current')
679 age = opts.get('age') 679 age = opts.get('age')
680 680
681 if current and topic: 681 if current and topic:
682 raise error.Abort(_("cannot use --current when setting a topic")) 682 raise error.Abort(_(b"cannot use --current when setting a topic"))
683 if current and clear: 683 if current and clear:
684 raise error.Abort(_("cannot use --current and --clear")) 684 raise error.Abort(_(b"cannot use --current and --clear"))
685 if clear and topic: 685 if clear and topic:
686 raise error.Abort(_("cannot use --clear when setting a topic")) 686 raise error.Abort(_(b"cannot use --clear when setting a topic"))
687 if age and topic: 687 if age and topic:
688 raise error.Abort(_("cannot use --age while setting a topic")) 688 raise error.Abort(_(b"cannot use --age while setting a topic"))
689 689
690 touchedrevs = set() 690 touchedrevs = set()
691 if rev: 691 if rev:
692 touchedrevs = scmutil.revrange(repo, rev) 692 touchedrevs = scmutil.revrange(repo, rev)
693 693
694 if topic: 694 if topic:
695 topic = topic.strip() 695 topic = topic.strip()
696 if not topic: 696 if not topic:
697 raise error.Abort(_("topic name cannot consist entirely of whitespaces")) 697 raise error.Abort(_(b"topic name cannot consist entirely of whitespaces"))
698 # Have some restrictions on the topic name just like bookmark name 698 # Have some restrictions on the topic name just like bookmark name
699 scmutil.checknewlabel(repo, topic, 'topic') 699 scmutil.checknewlabel(repo, topic, b'topic')
700 700
701 rmatch = re.match(br'[-_.\w]+', topic) 701 rmatch = re.match(br'[-_.\w]+', topic)
702 if not rmatch or rmatch.group(0) != topic: 702 if not rmatch or rmatch.group(0) != topic:
703 helptxt = _("topic names can only consist of alphanumeric, '-'" 703 helptxt = _(b"topic names can only consist of alphanumeric, '-'"
704 " '_' and '.' characters") 704 b" '_' and '.' characters")
705 raise error.Abort(_("invalid topic name: '%s'") % topic, hint=helptxt) 705 raise error.Abort(_(b"invalid topic name: '%s'") % topic, hint=helptxt)
706 706
707 if list: 707 if list:
708 ui.pager('topics') 708 ui.pager(b'topics')
709 if clear or rev: 709 if clear or rev:
710 raise error.Abort(_("cannot use --clear or --rev with --list")) 710 raise error.Abort(_(b"cannot use --clear or --rev with --list"))
711 if not topic: 711 if not topic:
712 topic = repo.currenttopic 712 topic = repo.currenttopic
713 if not topic: 713 if not topic:
714 raise error.Abort(_('no active topic to list')) 714 raise error.Abort(_(b'no active topic to list'))
715 return stack.showstack(ui, repo, topic=topic, 715 return stack.showstack(ui, repo, topic=topic,
716 opts=pycompat.byteskwargs(opts)) 716 opts=pycompat.byteskwargs(opts))
717 717
718 if touchedrevs: 718 if touchedrevs:
719 if not obsolete.isenabled(repo, obsolete.createmarkersopt): 719 if not obsolete.isenabled(repo, obsolete.createmarkersopt):
720 raise error.Abort(_('must have obsolete enabled to change topics')) 720 raise error.Abort(_(b'must have obsolete enabled to change topics'))
721 if clear: 721 if clear:
722 topic = None 722 topic = None
723 elif opts.get('current'): 723 elif opts.get('current'):
724 topic = repo.currenttopic 724 topic = repo.currenttopic
725 elif not topic: 725 elif not topic:
726 raise error.Abort('changing topic requires a topic name or --clear') 726 raise error.Abort(b'changing topic requires a topic name or --clear')
727 if repo.revs('%ld and public()', touchedrevs): 727 if repo.revs(b'%ld and public()', touchedrevs):
728 raise error.Abort("can't change topic of a public change") 728 raise error.Abort(b"can't change topic of a public change")
729 wl = lock = txn = None 729 wl = lock = txn = None
730 try: 730 try:
731 wl = repo.wlock() 731 wl = repo.wlock()
732 lock = repo.lock() 732 lock = repo.lock()
733 txn = repo.transaction('rewrite-topics') 733 txn = repo.transaction(b'rewrite-topics')
734 rewrote = _changetopics(ui, repo, touchedrevs, topic) 734 rewrote = _changetopics(ui, repo, touchedrevs, topic)
735 txn.close() 735 txn.close()
736 if topic is None: 736 if topic is None:
737 ui.status('cleared topic on %d changesets\n' % rewrote) 737 ui.status(b'cleared topic on %d changesets\n' % rewrote)
738 else: 738 else:
739 ui.status('changed topic on %d changesets to "%s"\n' % (rewrote, 739 ui.status(b'changed topic on %d changesets to "%s"\n' % (rewrote,
740 topic)) 740 topic))
741 finally: 741 finally:
742 lockmod.release(txn, lock, wl) 742 lockmod.release(txn, lock, wl)
743 repo.invalidate() 743 repo.invalidate()
744 return 744 return
745 745
746 ct = repo.currenttopic 746 ct = repo.currenttopic
747 if clear: 747 if clear:
748 if ct: 748 if ct:
749 st = stack.stack(repo, topic=ct) 749 st = stack.stack(repo, topic=ct)
750 if not st: 750 if not st:
751 ui.status(_('clearing empty topic "%s"\n') % ct) 751 ui.status(_(b'clearing empty topic "%s"\n') % ct)
752 return _changecurrenttopic(repo, None) 752 return _changecurrenttopic(repo, None)
753 753
754 if topic: 754 if topic:
755 if not ct: 755 if not ct:
756 ui.status(_('marked working directory as topic: %s\n') % topic) 756 ui.status(_(b'marked working directory as topic: %s\n') % topic)
757 return _changecurrenttopic(repo, topic) 757 return _changecurrenttopic(repo, topic)
758 758
759 ui.pager('topics') 759 ui.pager(b'topics')
760 # `hg topic --current` 760 # `hg topic --current`
761 ret = 0 761 ret = 0
762 if current and not ct: 762 if current and not ct:
763 ui.write_err(_('no active topic\n')) 763 ui.write_err(_(b'no active topic\n'))
764 ret = 1 764 ret = 1
765 elif current: 765 elif current:
766 fm = ui.formatter('topic', pycompat.byteskwargs(opts)) 766 fm = ui.formatter(b'topic', pycompat.byteskwargs(opts))
767 namemask = '%s\n' 767 namemask = b'%s\n'
768 label = 'topic.active' 768 label = b'topic.active'
769 fm.startitem() 769 fm.startitem()
770 fm.write('topic', namemask, ct, label=label) 770 fm.write(b'topic', namemask, ct, label=label)
771 fm.end() 771 fm.end()
772 else: 772 else:
773 _listtopics(ui, repo, opts) 773 _listtopics(ui, repo, opts)
774 return ret 774 return ret
775 775
776 @command('stack', [ 776 @command(b'stack', [
777 ('c', 'children', None, 777 (b'c', b'children', None,
778 _('display data about children outside of the stack')) 778 _(b'display data about children outside of the stack'))
779 ] + commands.formatteropts, 779 ] + commands.formatteropts,
780 _('hg stack [TOPIC]')) 780 _(b'hg stack [TOPIC]'))
781 def cmdstack(ui, repo, topic='', **opts): 781 def cmdstack(ui, repo, topic=b'', **opts):
782 """list all changesets in a topic and other information 782 """list all changesets in a topic and other information
783 783
784 List the current topic by default. 784 List the current topic by default.
785 785
786 The --verbose version shows short nodes for the commits also. 786 The --verbose version shows short nodes for the commits also.
790 branch = None 790 branch = None
791 if topic is None and repo.currenttopic: 791 if topic is None and repo.currenttopic:
792 topic = repo.currenttopic 792 topic = repo.currenttopic
793 if topic is None: 793 if topic is None:
794 branch = repo[None].branch() 794 branch = repo[None].branch()
795 ui.pager('stack') 795 ui.pager(b'stack')
796 return stack.showstack(ui, repo, branch=branch, topic=topic, 796 return stack.showstack(ui, repo, branch=branch, topic=topic,
797 opts=pycompat.byteskwargs(opts)) 797 opts=pycompat.byteskwargs(opts))
798 798
799 @command('debugcb|debugconvertbookmark', [ 799 @command(b'debugcb|debugconvertbookmark', [
800 ('b', 'bookmark', '', _('bookmark to convert to topic')), 800 (b'b', b'bookmark', b'', _(b'bookmark to convert to topic')),
801 ('', 'all', None, _('convert all bookmarks to topics')), 801 (b'', b'all', None, _(b'convert all bookmarks to topics')),
802 ], 802 ],
803 _('[-b BOOKMARK] [--all]')) 803 _(b'[-b BOOKMARK] [--all]'))
804 def debugconvertbookmark(ui, repo, **opts): 804 def debugconvertbookmark(ui, repo, **opts):
805 """Converts a bookmark to a topic with the same name. 805 """Converts a bookmark to a topic with the same name.
806 """ 806 """
807 807
808 bookmark = opts.get('bookmark') 808 bookmark = opts.get('bookmark')
809 convertall = opts.get('all') 809 convertall = opts.get('all')
810 810
811 if convertall and bookmark: 811 if convertall and bookmark:
812 raise error.Abort(_("cannot use '--all' and '-b' together")) 812 raise error.Abort(_(b"cannot use '--all' and '-b' together"))
813 if not (convertall or bookmark): 813 if not (convertall or bookmark):
814 raise error.Abort(_("you must specify either '--all' or '-b'")) 814 raise error.Abort(_(b"you must specify either '--all' or '-b'"))
815 815
816 bmstore = repo._bookmarks 816 bmstore = repo._bookmarks
817 817
818 nodetobook = {} 818 nodetobook = {}
819 for book, revnode in bmstore.items(): 819 for book, revnode in bmstore.items():
834 lock = repo.lock() 834 lock = repo.lock()
835 if bookmark: 835 if bookmark:
836 try: 836 try:
837 node = bmstore[bookmark] 837 node = bmstore[bookmark]
838 except KeyError: 838 except KeyError:
839 raise error.Abort(_("no such bookmark exists: '%s'") % bookmark) 839 raise error.Abort(_(b"no such bookmark exists: '%s'") % bookmark)
840 840
841 revnum = repo[node].rev() 841 revnum = repo[node].rev()
842 if len(nodetobook[node]) > 1: 842 if len(nodetobook[node]) > 1:
843 ui.status(_("skipping revision '%d' as it has multiple bookmarks " 843 ui.status(_(b"skipping revision '%d' as it has multiple bookmarks "
844 "on it\n") % revnum) 844 b"on it\n") % revnum)
845 return 845 return
846 targetrevs = _findconvertbmarktopic(repo, bookmark) 846 targetrevs = _findconvertbmarktopic(repo, bookmark)
847 if targetrevs: 847 if targetrevs:
848 actions[(bookmark, revnum)] = targetrevs 848 actions[(bookmark, revnum)] = targetrevs
849 849
851 for bmark, revnode in sorted(bmstore.items()): 851 for bmark, revnode in sorted(bmstore.items()):
852 revnum = repo[revnode].rev() 852 revnum = repo[revnode].rev()
853 if revnum in skipped: 853 if revnum in skipped:
854 continue 854 continue
855 if len(nodetobook[revnode]) > 1: 855 if len(nodetobook[revnode]) > 1:
856 ui.status(_("skipping '%d' as it has multiple bookmarks on" 856 ui.status(_(b"skipping '%d' as it has multiple bookmarks on"
857 " it\n") % revnum) 857 b" it\n") % revnum)
858 skipped.append(revnum) 858 skipped.append(revnum)
859 continue 859 continue
860 if bmark == '@': 860 if bmark == b'@':
861 continue 861 continue
862 targetrevs = _findconvertbmarktopic(repo, bmark) 862 targetrevs = _findconvertbmarktopic(repo, bmark)
863 if targetrevs: 863 if targetrevs:
864 actions[(bmark, revnum)] = targetrevs 864 actions[(bmark, revnum)] = targetrevs
865 865
866 if actions: 866 if actions:
867 try: 867 try:
868 tr = repo.transaction('debugconvertbookmark') 868 tr = repo.transaction(b'debugconvertbookmark')
869 for ((bmark, revnum), targetrevs) in sorted(actions.items()): 869 for ((bmark, revnum), targetrevs) in sorted(actions.items()):
870 _applyconvertbmarktopic(ui, repo, targetrevs, revnum, bmark, tr) 870 _applyconvertbmarktopic(ui, repo, targetrevs, revnum, bmark, tr)
871 tr.close() 871 tr.close()
872 finally: 872 finally:
873 tr.release() 873 tr.release()
874 finally: 874 finally:
875 lockmod.release(lock, wlock) 875 lockmod.release(lock, wlock)
876 876
877 # inspired from mercurial.repair.stripbmrevset 877 # inspired from mercurial.repair.stripbmrevset
878 CONVERTBOOKREVSET = """ 878 CONVERTBOOKREVSET = b"""
879 not public() and ( 879 not public() and (
880 ancestors(bookmark(%s)) 880 ancestors(bookmark(%s))
881 and not ancestors( 881 and not ancestors(
882 ( 882 (
883 (head() and not bookmark(%s)) 883 (head() and not bookmark(%s))
911 # returned an empty set of revisions, so let's skip deleting the 911 # returned an empty set of revisions, so let's skip deleting the
912 # bookmark corresponding to which we didn't put a topic on any 912 # bookmark corresponding to which we didn't put a topic on any
913 # changeset 913 # changeset
914 if rewrote == 0: 914 if rewrote == 0:
915 return 915 return
916 ui.status(_('changed topic to "%s" on %d revisions\n') % (bmark, 916 ui.status(_(b'changed topic to "%s" on %d revisions\n') % (bmark,
917 rewrote)) 917 rewrote))
918 ui.debug('removing bookmark "%s" from "%d"' % (bmark, old)) 918 ui.debug(b'removing bookmark "%s" from "%d"' % (bmark, old))
919 bookmarks.delete(repo, tr, [bmark]) 919 bookmarks.delete(repo, tr, [bmark])
920 920
921 def _changecurrenttopic(repo, newtopic): 921 def _changecurrenttopic(repo, newtopic):
922 """changes the current topic.""" 922 """changes the current topic."""
923 923
924 if newtopic: 924 if newtopic:
925 with repo.wlock(): 925 with repo.wlock():
926 with repo.vfs.open(b'topic', b'w') as f: 926 with repo.vfs.open(b'topic', b'w') as f:
927 f.write(newtopic) 927 f.write(newtopic)
928 else: 928 else:
929 if repo.vfs.exists('topic'): 929 if repo.vfs.exists(b'topic'):
930 repo.vfs.unlink('topic') 930 repo.vfs.unlink(b'topic')
931 931
932 def _changetopics(ui, repo, revs, newtopic): 932 def _changetopics(ui, repo, revs, newtopic):
933 """ Changes topic to newtopic of all the revisions in the revset and return 933 """ Changes topic to newtopic of all the revisions in the revset and return
934 the count of revisions whose topic has been changed. 934 the count of revisions whose topic has been changed.
935 """ 935 """
944 try: 944 try:
945 return c[path] 945 return c[path]
946 except error.ManifestLookupError: 946 except error.ManifestLookupError:
947 return None 947 return None
948 fixedextra = dict(c.extra()) 948 fixedextra = dict(c.extra())
949 ui.debug('old node id is %s\n' % node.hex(c.node())) 949 ui.debug(b'old node id is %s\n' % node.hex(c.node()))
950 ui.debug('origextra: %r\n' % fixedextra) 950 ui.debug(b'origextra: %r\n' % fixedextra)
951 oldtopic = fixedextra.get(constants.extrakey, None) 951 oldtopic = fixedextra.get(constants.extrakey, None)
952 if oldtopic == newtopic: 952 if oldtopic == newtopic:
953 continue 953 continue
954 if newtopic is None: 954 if newtopic is None:
955 del fixedextra[constants.extrakey] 955 del fixedextra[constants.extrakey]
956 else: 956 else:
957 fixedextra[constants.extrakey] = newtopic 957 fixedextra[constants.extrakey] = newtopic
958 fixedextra[constants.changekey] = c.hex() 958 fixedextra[constants.changekey] = c.hex()
959 if 'amend_source' in fixedextra: 959 if b'amend_source' in fixedextra:
960 # TODO: right now the commitctx wrapper in 960 # TODO: right now the commitctx wrapper in
961 # topicrepo overwrites the topic in extra if 961 # topicrepo overwrites the topic in extra if
962 # amend_source is set to support 'hg commit 962 # amend_source is set to support 'hg commit
963 # --amend'. Support for amend should be adjusted 963 # --amend'. Support for amend should be adjusted
964 # to not be so invasive. 964 # to not be so invasive.
965 del fixedextra['amend_source'] 965 del fixedextra[b'amend_source']
966 ui.debug('changing topic of %s from %s to %s\n' % ( 966 ui.debug(b'changing topic of %s from %s to %s\n' % (
967 c, oldtopic or '<none>', newtopic or '<none>')) 967 c, oldtopic or b'<none>', newtopic or b'<none>'))
968 ui.debug('fixedextra: %r\n' % fixedextra) 968 ui.debug(b'fixedextra: %r\n' % fixedextra)
969 # While changing topic of set of linear commits, make sure that 969 # While changing topic of set of linear commits, make sure that
970 # we base our commits on new parent rather than old parent which 970 # we base our commits on new parent rather than old parent which
971 # was obsoleted while changing the topic 971 # was obsoleted while changing the topic
972 p1 = c.p1().node() 972 p1 = c.p1().node()
973 p2 = c.p2().node() 973 p2 = c.p2().node()
984 date=c.date(), 984 date=c.date(),
985 extra=fixedextra) 985 extra=fixedextra)
986 986
987 # phase handling 987 # phase handling
988 commitphase = c.phase() 988 commitphase = c.phase()
989 overrides = {('phases', 'new-commit'): commitphase} 989 overrides = {(b'phases', b'new-commit'): commitphase}
990 with repo.ui.configoverride(overrides, 'changetopic'): 990 with repo.ui.configoverride(overrides, b'changetopic'):
991 newnode = repo.commitctx(mc) 991 newnode = repo.commitctx(mc)
992 992
993 successors[c.node()] = (newnode,) 993 successors[c.node()] = (newnode,)
994 ui.debug('new node id is %s\n' % node.hex(newnode)) 994 ui.debug(b'new node id is %s\n' % node.hex(newnode))
995 rewrote += 1 995 rewrote += 1
996 996
997 # create obsmarkers and move bookmarks 997 # create obsmarkers and move bookmarks
998 # XXX we should be creating marker as we go instead of only at the end, 998 # XXX we should be creating marker as we go instead of only at the end,
999 # this makes the operations more modulars 999 # this makes the operations more modulars
1000 scmutil.cleanupnodes(repo, successors, 'changetopics') 1000 scmutil.cleanupnodes(repo, successors, b'changetopics')
1001 1001
1002 # move the working copy too 1002 # move the working copy too
1003 wctx = repo[None] 1003 wctx = repo[None]
1004 # in-progress merge is a bit too complex for now. 1004 # in-progress merge is a bit too complex for now.
1005 if len(wctx.parents()) == 1: 1005 if len(wctx.parents()) == 1:
1007 if newid is not None: 1007 if newid is not None:
1008 hg.update(repo, newid[0], quietempty=True) 1008 hg.update(repo, newid[0], quietempty=True)
1009 return rewrote 1009 return rewrote
1010 1010
1011 def _listtopics(ui, repo, opts): 1011 def _listtopics(ui, repo, opts):
1012 fm = ui.formatter('topics', pycompat.byteskwargs(opts)) 1012 fm = ui.formatter(b'topics', pycompat.byteskwargs(opts))
1013 activetopic = repo.currenttopic 1013 activetopic = repo.currenttopic
1014 namemask = '%s' 1014 namemask = b'%s'
1015 if repo.topics: 1015 if repo.topics:
1016 maxwidth = max(len(t) for t in repo.topics) 1016 maxwidth = max(len(t) for t in repo.topics)
1017 namemask = '%%-%is' % maxwidth 1017 namemask = b'%%-%is' % maxwidth
1018 if opts.get('age'): 1018 if opts.get('age'):
1019 # here we sort by age and topic name 1019 # here we sort by age and topic name
1020 topicsdata = sorted(_getlasttouched(repo, repo.topics)) 1020 topicsdata = sorted(_getlasttouched(repo, repo.topics))
1021 else: 1021 else:
1022 # here we sort by topic name only 1022 # here we sort by topic name only
1024 (None, topic, None, None) 1024 (None, topic, None, None)
1025 for topic in sorted(repo.topics) 1025 for topic in sorted(repo.topics)
1026 ) 1026 )
1027 for age, topic, date, user in topicsdata: 1027 for age, topic, date, user in topicsdata:
1028 fm.startitem() 1028 fm.startitem()
1029 marker = ' ' 1029 marker = b' '
1030 label = 'topic' 1030 label = b'topic'
1031 active = (topic == activetopic) 1031 active = (topic == activetopic)
1032 if active: 1032 if active:
1033 marker = '*' 1033 marker = b'*'
1034 label = 'topic.active' 1034 label = b'topic.active'
1035 if not ui.quiet: 1035 if not ui.quiet:
1036 # registering the active data is made explicitly later 1036 # registering the active data is made explicitly later
1037 fm.plain(' %s ' % marker, label=label) 1037 fm.plain(b' %s ' % marker, label=label)
1038 fm.write('topic', namemask, topic, label=label) 1038 fm.write(b'topic', namemask, topic, label=label)
1039 fm.data(active=active) 1039 fm.data(active=active)
1040 1040
1041 if ui.quiet: 1041 if ui.quiet:
1042 fm.plain('\n') 1042 fm.plain(b'\n')
1043 continue 1043 continue
1044 fm.plain(' (') 1044 fm.plain(b' (')
1045 if date: 1045 if date:
1046 if age == -1: 1046 if age == -1:
1047 timestr = 'empty and active' 1047 timestr = b'empty and active'
1048 else: 1048 else:
1049 timestr = templatefilters.age(date) 1049 timestr = templatefilters.age(date)
1050 fm.write('lasttouched', '%s', timestr, label='topic.list.time') 1050 fm.write(b'lasttouched', b'%s', timestr, label=b'topic.list.time')
1051 if user: 1051 if user:
1052 fm.write('usertouched', ' by %s', user, label='topic.list.user') 1052 fm.write(b'usertouched', b' by %s', user, label=b'topic.list.user')
1053 if date: 1053 if date:
1054 fm.plain(', ') 1054 fm.plain(b', ')
1055 data = stack.stack(repo, topic=topic) 1055 data = stack.stack(repo, topic=topic)
1056 if ui.verbose: 1056 if ui.verbose:
1057 fm.write('branches+', 'on branch: %s', 1057 fm.write(b'branches+', b'on branch: %s',
1058 '+'.join(data.branches), # XXX use list directly after 4.0 is released 1058 b'+'.join(data.branches), # XXX use list directly after 4.0 is released
1059 label='topic.list.branches') 1059 label=b'topic.list.branches')
1060 1060
1061 fm.plain(', ') 1061 fm.plain(b', ')
1062 fm.write('changesetcount', '%d changesets', data.changesetcount, 1062 fm.write(b'changesetcount', b'%d changesets', data.changesetcount,
1063 label='topic.list.changesetcount') 1063 label=b'topic.list.changesetcount')
1064 1064
1065 if data.unstablecount: 1065 if data.unstablecount:
1066 fm.plain(', ') 1066 fm.plain(b', ')
1067 fm.write('unstablecount', '%d unstable', 1067 fm.write(b'unstablecount', b'%d unstable',
1068 data.unstablecount, 1068 data.unstablecount,
1069 label='topic.list.unstablecount') 1069 label=b'topic.list.unstablecount')
1070 1070
1071 headcount = len(data.heads) 1071 headcount = len(data.heads)
1072 if 1 < headcount: 1072 if 1 < headcount:
1073 fm.plain(', ') 1073 fm.plain(b', ')
1074 fm.write('headcount', '%d heads', 1074 fm.write(b'headcount', b'%d heads',
1075 headcount, 1075 headcount,
1076 label='topic.list.headcount.multiple') 1076 label=b'topic.list.headcount.multiple')
1077 1077
1078 if ui.verbose: 1078 if ui.verbose:
1079 # XXX we should include the data even when not verbose 1079 # XXX we should include the data even when not verbose
1080 1080
1081 behindcount = data.behindcount 1081 behindcount = data.behindcount
1082 if 0 < behindcount: 1082 if 0 < behindcount:
1083 fm.plain(', ') 1083 fm.plain(b', ')
1084 fm.write('behindcount', '%d behind', 1084 fm.write(b'behindcount', b'%d behind',
1085 behindcount, 1085 behindcount,
1086 label='topic.list.behindcount') 1086 label=b'topic.list.behindcount')
1087 elif -1 == behindcount: 1087 elif -1 == behindcount:
1088 fm.plain(', ') 1088 fm.plain(b', ')
1089 fm.write('behinderror', '%s', 1089 fm.write(b'behinderror', b'%s',
1090 _('ambiguous destination: %s') % data.behinderror, 1090 _(b'ambiguous destination: %s') % data.behinderror,
1091 label='topic.list.behinderror') 1091 label=b'topic.list.behinderror')
1092 fm.plain(')\n') 1092 fm.plain(b')\n')
1093 fm.end() 1093 fm.end()
1094 1094
1095 def _getlasttouched(repo, topics): 1095 def _getlasttouched(repo, topics):
1096 """ 1096 """
1097 Calculates the last time a topic was used. Returns a generator of 4-tuples: 1097 Calculates the last time a topic was used. Returns a generator of 4-tuples:
1100 curtime = time.time() 1100 curtime = time.time()
1101 for topic in topics: 1101 for topic in topics:
1102 age = -1 1102 age = -1
1103 user = None 1103 user = None
1104 maxtime = (0, 0) 1104 maxtime = (0, 0)
1105 trevs = repo.revs("topic(%s)", topic) 1105 trevs = repo.revs(b"topic(%s)", topic)
1106 # Need to check for the time of all changesets in the topic, whether 1106 # Need to check for the time of all changesets in the topic, whether
1107 # they are obsolete of non-heads 1107 # they are obsolete of non-heads
1108 # XXX: can we just rely on the max rev number for this 1108 # XXX: can we just rely on the max rev number for this
1109 for revs in trevs: 1109 for revs in trevs:
1110 rt = repo[revs].date() 1110 rt = repo[revs].date()
1117 # last touch time. 1117 # last touch time.
1118 obsmarkers = compat.getmarkers(repo, [repo[revs].node()]) 1118 obsmarkers = compat.getmarkers(repo, [repo[revs].node()])
1119 for marker in obsmarkers: 1119 for marker in obsmarkers:
1120 rt = marker.date() 1120 rt = marker.date()
1121 if rt[0] > maxtime[0]: 1121 if rt[0] > maxtime[0]:
1122 user = marker.metadata().get('user', user) 1122 user = marker.metadata().get(b'user', user)
1123 maxtime = rt 1123 maxtime = rt
1124 1124
1125 username = stack.parseusername(user) 1125 username = stack.parseusername(user)
1126 if trevs: 1126 if trevs:
1127 age = curtime - maxtime[0] 1127 age = curtime - maxtime[0]
1128 1128
1129 yield (age, topic, maxtime, username) 1129 yield (age, topic, maxtime, username)
1130 1130
1131 def summaryhook(ui, repo): 1131 def summaryhook(ui, repo):
1132 t = getattr(repo, 'currenttopic', '') 1132 t = getattr(repo, 'currenttopic', b'')
1133 if not t: 1133 if not t:
1134 return 1134 return
1135 # i18n: column positioning for "hg summary" 1135 # i18n: column positioning for "hg summary"
1136 ui.write(_("topic: %s\n") % ui.label(t, 'topic.active')) 1136 ui.write(_(b"topic: %s\n") % ui.label(t, b'topic.active'))
1137 1137
1138 _validmode = [ 1138 _validmode = [
1139 'ignore', 1139 b'ignore',
1140 'warning', 1140 b'warning',
1141 'enforce', 1141 b'enforce',
1142 'enforce-all', 1142 b'enforce-all',
1143 'random', 1143 b'random',
1144 'random-all', 1144 b'random-all',
1145 ] 1145 ]
1146 1146
1147 def _configtopicmode(ui): 1147 def _configtopicmode(ui):
1148 """ Parse the config to get the topicmode 1148 """ Parse the config to get the topicmode
1149 """ 1149 """
1150 topicmode = ui.config('experimental', 'topic-mode') 1150 topicmode = ui.config(b'experimental', b'topic-mode')
1151 1151
1152 # Fallback to read enforce-topic 1152 # Fallback to read enforce-topic
1153 if topicmode is None: 1153 if topicmode is None:
1154 enforcetopic = ui.configbool('experimental', 'enforce-topic') 1154 enforcetopic = ui.configbool(b'experimental', b'enforce-topic')
1155 if enforcetopic: 1155 if enforcetopic:
1156 topicmode = "enforce" 1156 topicmode = b"enforce"
1157 if topicmode not in _validmode: 1157 if topicmode not in _validmode:
1158 topicmode = _validmode[0] 1158 topicmode = _validmode[0]
1159 1159
1160 return topicmode 1160 return topicmode
1161 1161
1165 with repo.wlock(): 1165 with repo.wlock():
1166 topicmode = _configtopicmode(ui) 1166 topicmode = _configtopicmode(ui)
1167 ismergecommit = len(repo[None].parents()) == 2 1167 ismergecommit = len(repo[None].parents()) == 2
1168 1168
1169 notopic = not repo.currenttopic 1169 notopic = not repo.currenttopic
1170 mayabort = (topicmode == "enforce" and not ismergecommit) 1170 mayabort = (topicmode == b"enforce" and not ismergecommit)
1171 maywarn = (topicmode == "warning" 1171 maywarn = (topicmode == b"warning"
1172 or (topicmode == "enforce" and ismergecommit)) 1172 or (topicmode == b"enforce" and ismergecommit))
1173 1173
1174 mayrandom = False 1174 mayrandom = False
1175 if topicmode == "random": 1175 if topicmode == b"random":
1176 mayrandom = not ismergecommit 1176 mayrandom = not ismergecommit
1177 elif topicmode == "random-all": 1177 elif topicmode == b"random-all":
1178 mayrandom = True 1178 mayrandom = True
1179 1179
1180 if topicmode == 'enforce-all': 1180 if topicmode == b'enforce-all':
1181 ismergecommit = False 1181 ismergecommit = False
1182 mayabort = True 1182 mayabort = True
1183 maywarn = False 1183 maywarn = False
1184 1184
1185 hint = _("see 'hg help -e topic.topic-mode' for details") 1185 hint = _(b"see 'hg help -e topic.topic-mode' for details")
1186 if opts.get('topic'): 1186 if opts.get('topic'):
1187 t = opts['topic'] 1187 t = opts['topic']
1188 with repo.vfs.open(b'topic', b'w') as f: 1188 with repo.vfs.open(b'topic', b'w') as f:
1189 f.write(t) 1189 f.write(t)
1190 elif opts.get('amend'): 1190 elif opts.get('amend'):
1191 pass 1191 pass
1192 elif notopic and mayabort: 1192 elif notopic and mayabort:
1193 msg = _("no active topic") 1193 msg = _(b"no active topic")
1194 raise error.Abort(msg, hint=hint) 1194 raise error.Abort(msg, hint=hint)
1195 elif notopic and maywarn: 1195 elif notopic and maywarn:
1196 ui.warn(_("warning: new draft commit without topic\n")) 1196 ui.warn(_(b"warning: new draft commit without topic\n"))
1197 if not ui.quiet: 1197 if not ui.quiet:
1198 ui.warn(("(%s)\n") % hint) 1198 ui.warn((b"(%s)\n") % hint)
1199 elif notopic and mayrandom: 1199 elif notopic and mayrandom:
1200 with repo.vfs.open(b'topic', b'w') as f: 1200 with repo.vfs.open(b'topic', b'w') as f:
1201 f.write(randomname.randomtopicname(ui)) 1201 f.write(randomname.randomtopicname(ui))
1202 return orig(ui, repo, *args, **opts) 1202 return orig(ui, repo, *args, **opts)
1203 1203
1204 def committextwrap(orig, repo, ctx, subs, extramsg): 1204 def committextwrap(orig, repo, ctx, subs, extramsg):
1205 ret = orig(repo, ctx, subs, extramsg) 1205 ret = orig(repo, ctx, subs, extramsg)
1206 if hastopicext(repo): 1206 if hastopicext(repo):
1207 t = repo.currenttopic 1207 t = repo.currenttopic
1208 if t: 1208 if t:
1209 ret = ret.replace("\nHG: branch", 1209 ret = ret.replace(b"\nHG: branch",
1210 "\nHG: topic '%s'\nHG: branch" % t) 1210 b"\nHG: topic '%s'\nHG: branch" % t)
1211 return ret 1211 return ret
1212 1212
1213 def pushoutgoingwrap(orig, ui, repo, *args, **opts): 1213 def pushoutgoingwrap(orig, ui, repo, *args, **opts):
1214 if opts.get('topic'): 1214 if opts.get('topic'):
1215 topicrevs = repo.revs('topic(%s) - obsolete()', opts['topic']) 1215 topicrevs = repo.revs(b'topic(%s) - obsolete()', opts['topic'])
1216 opts.setdefault('rev', []).extend(topicrevs) 1216 opts.setdefault('rev', []).extend(topicrevs)
1217 return orig(ui, repo, *args, **opts) 1217 return orig(ui, repo, *args, **opts)
1218 1218
1219 def mergeupdatewrap(orig, repo, node, branchmerge, force, *args, **kwargs): 1219 def mergeupdatewrap(orig, repo, node, branchmerge, force, *args, **kwargs):
1220 matcher = kwargs.get('matcher') 1220 matcher = kwargs.get('matcher')
1230 # current topic. This is right for merge but wrong for rebase. We check 1230 # current topic. This is right for merge but wrong for rebase. We check
1231 # if rebase is running and update the currenttopic to topic of new 1231 # if rebase is running and update the currenttopic to topic of new
1232 # rebased commit. We have explicitly stored in config if rebase is 1232 # rebased commit. We have explicitly stored in config if rebase is
1233 # running. 1233 # running.
1234 ot = repo.currenttopic 1234 ot = repo.currenttopic
1235 if repo.ui.hasconfig('experimental', 'topicrebase'): 1235 if repo.ui.hasconfig(b'experimental', b'topicrebase'):
1236 isrebase = True 1236 isrebase = True
1237 if repo.ui.configbool('_internal', 'keep-topic'): 1237 if repo.ui.configbool(b'_internal', b'keep-topic'):
1238 ist0 = True 1238 ist0 = True
1239 if ((not partial and not branchmerge) or isrebase) and not ist0: 1239 if ((not partial and not branchmerge) or isrebase) and not ist0:
1240 t = '' 1240 t = b''
1241 pctx = repo[node] 1241 pctx = repo[node]
1242 if pctx.phase() > phases.public: 1242 if pctx.phase() > phases.public:
1243 t = pctx.topic() 1243 t = pctx.topic()
1244 with repo.vfs.open(b'topic', b'w') as f: 1244 with repo.vfs.open(b'topic', b'w') as f:
1245 f.write(t) 1245 f.write(t)
1246 if t and t != ot: 1246 if t and t != ot:
1247 repo.ui.status(_("switching to topic %s\n") % t) 1247 repo.ui.status(_(b"switching to topic %s\n") % t)
1248 if ot and not t: 1248 if ot and not t:
1249 st = stack.stack(repo, topic=ot) 1249 st = stack.stack(repo, topic=ot)
1250 if not st: 1250 if not st:
1251 repo.ui.status(_('clearing empty topic "%s"\n') % ot) 1251 repo.ui.status(_(b'clearing empty topic "%s"\n') % ot)
1252 elif ist0: 1252 elif ist0:
1253 repo.ui.status(_("preserving the current topic '%s'\n") % ot) 1253 repo.ui.status(_(b"preserving the current topic '%s'\n") % ot)
1254 return ret 1254 return ret
1255 finally: 1255 finally:
1256 wlock.release() 1256 wlock.release()
1257 1257
1258 def checkt0(orig, ui, repo, node=None, rev=None, *args, **kwargs): 1258 def checkt0(orig, ui, repo, node=None, rev=None, *args, **kwargs):
1259 1259
1260 thezeros = set(['t0', 'b0', 's0']) 1260 thezeros = set([b't0', b'b0', b's0'])
1261 backup = repo.ui.backupconfig('_internal', 'keep-topic') 1261 backup = repo.ui.backupconfig(b'_internal', b'keep-topic')
1262 try: 1262 try:
1263 if node in thezeros or rev in thezeros: 1263 if node in thezeros or rev in thezeros:
1264 repo.ui.setconfig('_internal', 'keep-topic', 'yes', 1264 repo.ui.setconfig(b'_internal', b'keep-topic', b'yes',
1265 source='topic-extension') 1265 source=b'topic-extension')
1266 return orig(ui, repo, node=node, rev=rev, *args, **kwargs) 1266 return orig(ui, repo, node=node, rev=rev, *args, **kwargs)
1267 finally: 1267 finally:
1268 repo.ui.restoreconfig(backup) 1268 repo.ui.restoreconfig(backup)
1269 1269
1270 def _fixrebase(loaded): 1270 def _fixrebase(loaded):
1274 def savetopic(ctx, extra): 1274 def savetopic(ctx, extra):
1275 if ctx.topic(): 1275 if ctx.topic():
1276 extra[constants.extrakey] = ctx.topic() 1276 extra[constants.extrakey] = ctx.topic()
1277 1277
1278 def setrebaseconfig(orig, ui, repo, **opts): 1278 def setrebaseconfig(orig, ui, repo, **opts):
1279 repo.ui.setconfig('experimental', 'topicrebase', 'yes', 1279 repo.ui.setconfig(b'experimental', b'topicrebase', b'yes',
1280 source='topic-extension') 1280 source=b'topic-extension')
1281 return orig(ui, repo, **opts) 1281 return orig(ui, repo, **opts)
1282 1282
1283 def new_init(orig, *args, **kwargs): 1283 def new_init(orig, *args, **kwargs):
1284 runtime = orig(*args, **kwargs) 1284 runtime = orig(*args, **kwargs)
1285 1285
1287 runtime.extrafns.append(savetopic) 1287 runtime.extrafns.append(savetopic)
1288 1288
1289 return runtime 1289 return runtime
1290 1290
1291 try: 1291 try:
1292 rebase = extensions.find("rebase") 1292 rebase = extensions.find(b"rebase")
1293 extensions.wrapfunction(rebase.rebaseruntime, '__init__', new_init) 1293 extensions.wrapfunction(rebase.rebaseruntime, '__init__', new_init)
1294 # This exists to store in the config that rebase is running so that we can 1294 # This exists to store in the config that rebase is running so that we can
1295 # update the topic according to rebase. This is a hack and should be removed 1295 # update the topic according to rebase. This is a hack and should be removed
1296 # when we have better options. 1296 # when we have better options.
1297 extensions.wrapcommand(rebase.cmdtable, 'rebase', setrebaseconfig) 1297 extensions.wrapcommand(rebase.cmdtable, b'rebase', setrebaseconfig)
1298 except KeyError: 1298 except KeyError:
1299 pass 1299 pass
1300 1300
1301 ## preserve topic during import/export 1301 ## preserve topic during import/export
1302 1302
1303 def _exporttopic(seq, ctx): 1303 def _exporttopic(seq, ctx):
1304 topic = ctx.topic() 1304 topic = ctx.topic()
1305 if topic: 1305 if topic:
1306 return 'EXP-Topic %s' % topic 1306 return b'EXP-Topic %s' % topic
1307 return None 1307 return None
1308 1308
1309 def _importtopic(repo, patchdata, extra, opts): 1309 def _importtopic(repo, patchdata, extra, opts):
1310 if 'topic' in patchdata: 1310 if b'topic' in patchdata:
1311 extra['topic'] = patchdata['topic'] 1311 extra[b'topic'] = patchdata[b'topic']
1312 1312
1313 def setupimportexport(ui): 1313 def setupimportexport(ui):
1314 """run at ui setup time to install import/export logic""" 1314 """run at ui setup time to install import/export logic"""
1315 cmdutil.extraexport.append('topic') 1315 cmdutil.extraexport.append(b'topic')
1316 cmdutil.extraexportmap['topic'] = _exporttopic 1316 cmdutil.extraexportmap[b'topic'] = _exporttopic
1317 cmdutil.extrapreimport.append('topic') 1317 cmdutil.extrapreimport.append(b'topic')
1318 cmdutil.extrapreimportmap['topic'] = _importtopic 1318 cmdutil.extrapreimportmap[b'topic'] = _importtopic
1319 patch.patchheadermap.append(('EXP-Topic', 'topic')) 1319 patch.patchheadermap.append((b'EXP-Topic', b'topic'))
1320 1320
1321 ## preserve topic during split 1321 ## preserve topic during split
1322 1322
1323 def presplitupdatetopic(original, repo, ui, prev, ctx): 1323 def presplitupdatetopic(original, repo, ui, prev, ctx):
1324 # Save topic of revision 1324 # Save topic of revision