branches: correctly show inactive multiheaded branches
Issue being fixed here: `hg branches` incorrectly renders inactive multiheaded
branches as active if they have closed heads.
Example:
```
$ hg log --template '{rev}:{node|short} "{desc}" ({branch}) [parents: {parents}]\n'
4:2e2fa7af8357 "merge" (default) [parents: 0:c94e548c8c7d 3:7be622ae5832 ]
3:7be622ae5832 "2" (somebranch) [parents: 1:81c1d9458987 ]
2:be82cf30409c "close" (somebranch) [parents: ]
1:81c1d9458987 "1" (somebranch) [parents: ]
0:c94e548c8c7d "initial" (default) [parents: ]
$ hg branches
default 4:2e2fa7af8357
somebranch 3:7be622ae5832
```
Branch `somebranch` have two heads, the 1st one being closed (rev 2) and
the other one being merged into default (rev 3). This branch should be shown as
inactive one.
This happens because we intersect branch heads with repo heads to check branch
activity. In this case intersection in a set with one node (rev 2). This head
is closed but the branch is marked as active nevertheless.
Fix is to check branch activity by intersecting only open heads set.
Fixed output:
```
$ hg branches
default 4:2e2fa7af8357
somebranch 3:7be622ae5832 (inactive)
```
Relevant tests for multihead branches added to test-branches suite.
Implentation note about adding `iteropen` method:
At first I have tried to modify `iterbranches` is such a way that it would
filter out closed heads itself. For example it could have `closed=False`
parameter. But in this case we would have to filter closed tips as well.
Reasoning in terms of `hg branches` we actually are not allowed to do this.
Also, we need to do heads filtering only if tip is not closed itself. But if it
is - we are ok to skip filtering, because branch is already known to be inactive.
So we can't implement heads filtering in `iterbranches` in elegant way, because
we will end up with something like `closed_heads=False` or even
`closed_heads_is_tip_is_open`. Finally I decided to move this logic to the
`branches` function, adding `iteropen` helper method.
Differential Revision: https://phab.mercurial-scm.org/D583
# sshserver.py - ssh protocol server support for mercurial
#
# Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
# Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
from __future__ import absolute_import
import sys
from .i18n import _
from . import (
encoding,
error,
hook,
util,
wireproto,
)
class sshserver(wireproto.abstractserverproto):
def __init__(self, ui, repo):
self.ui = ui
self.repo = repo
self.lock = None
self.fin = ui.fin
self.fout = ui.fout
self.name = 'ssh'
hook.redirect(True)
ui.fout = repo.ui.fout = ui.ferr
# Prevent insertion/deletion of CRs
util.setbinary(self.fin)
util.setbinary(self.fout)
def getargs(self, args):
data = {}
keys = args.split()
for n in xrange(len(keys)):
argline = self.fin.readline()[:-1]
arg, l = argline.split()
if arg not in keys:
raise error.Abort(_("unexpected parameter %r") % arg)
if arg == '*':
star = {}
for k in xrange(int(l)):
argline = self.fin.readline()[:-1]
arg, l = argline.split()
val = self.fin.read(int(l))
star[arg] = val
data['*'] = star
else:
val = self.fin.read(int(l))
data[arg] = val
return [data[k] for k in keys]
def getarg(self, name):
return self.getargs(name)[0]
def getfile(self, fpout):
self.sendresponse('')
count = int(self.fin.readline())
while count:
fpout.write(self.fin.read(count))
count = int(self.fin.readline())
def redirect(self):
pass
def sendresponse(self, v):
self.fout.write("%d\n" % len(v))
self.fout.write(v)
self.fout.flush()
def sendstream(self, source):
write = self.fout.write
if source.reader:
gen = iter(lambda: source.reader.read(4096), '')
else:
gen = source.gen
for chunk in gen:
write(chunk)
self.fout.flush()
def sendpushresponse(self, rsp):
self.sendresponse('')
self.sendresponse(str(rsp.res))
def sendpusherror(self, rsp):
self.sendresponse(rsp.res)
def sendooberror(self, rsp):
self.ui.ferr.write('%s\n-\n' % rsp.message)
self.ui.ferr.flush()
self.fout.write('\n')
self.fout.flush()
def serve_forever(self):
try:
while self.serve_one():
pass
finally:
if self.lock is not None:
self.lock.release()
sys.exit(0)
handlers = {
str: sendresponse,
wireproto.streamres: sendstream,
wireproto.pushres: sendpushresponse,
wireproto.pusherr: sendpusherror,
wireproto.ooberror: sendooberror,
}
def serve_one(self):
cmd = self.fin.readline()[:-1]
if cmd and cmd in wireproto.commands:
rsp = wireproto.dispatch(self.repo, self, cmd)
self.handlers[rsp.__class__](self, rsp)
elif cmd:
impl = getattr(self, 'do_' + cmd, None)
if impl:
r = impl()
if r is not None:
self.sendresponse(r)
else: self.sendresponse("")
return cmd != ''
def _client(self):
client = encoding.environ.get('SSH_CLIENT', '').split(' ', 1)[0]
return 'remote:ssh:' + client