Пример #1
0
def _get_version_from_bzr_lib(path):
    import bzrlib.tag, bzrlib.branch
    fullpath = os.path.abspath(path)
    if sys.platform == 'win32':
        fullpath = fullpath.replace('\\', '/')
        fullpath = '/' + fullpath
    branch = bzrlib.branch.Branch.open('file://' + fullpath)
    tags = bzrlib.tag.BasicTags(branch)
    #print "Getting version information from bzr branch..."
    branch.lock_read()
    try:
        history = branch.iter_merge_sorted_revisions(direction="reverse")
        version = None
        extra_version = []
        for revid, depth, revno, end_of_merge in history:
            for tag_name, tag_revid in tags.get_tag_dict().iteritems():
                #print tag_revid, "<==>", revid
                if tag_revid == revid:
                    #print "%s matches tag %s" % (revid, tag_name)
                    version = [int(s) for s in tag_name.split('.')]
                    ## if the current revision does not match the last
                    ## tag, we append current revno to the version
                    if tag_revid != branch.last_revision():
                        extra_version = [branch.revno()]
                    break
            if version:
                break
    finally:
        branch.unlock()
    assert version is not None
    _version = version + extra_version
    return _version
def create_components_from_branches(branches=None):

    if branches is None:
        branches = os.listdir('/srv/lava/branches')

    components = {}

    for branch_name in branches:
        component = Component(branch_name)
        branch = bzrlib.branch.Branch.open(
            os.path.join('/srv/lava/branches', branch_name))
        branch.lock_read()
        try:
            branch_tags = branch.tags.get_tag_dict()
            revno2release = {}
            release2revno = {}
            for tag, revid in branch_tags.iteritems():
                if tag.startswith('release-'):
                    try:
                        revno = branch.revision_id_to_dotted_revno(revid)
                    except bzrlib.errors.NoSuchRevision:
                        pass
                    else:
                        if len(revno) > 1:
                            continue
                        revno2release[revno[0]] = tag[len('release-'):]
                        release2revno[tag[len('release-'):]] = revno[0]
            if revno2release:
                component.released_revno = max(revno2release)
                component.last_release = revno2release[max(revno2release)]
            else:
                component.released_revno = None
            component.release2revno = release2revno
            component.revno2release = revno2release
            revno, revid = branch.last_revision_info()
            component.tip_revno = revno
            component.tip_revid = revid
            mainline_revs = []
            unreleased_revs = []
            while True:
                rev = branch.repository.get_revision(revid)
                if rev.message.strip() != 'post release bump':
                    if component.released_revno and revno > component.released_revno:
                        unreleased_revs.append((rev, revno))
                    mainline_revs.append((rev, revno))
                if not rev.parent_ids:
                    break
                revid = rev.parent_ids[0]
                revno -= 1
            component.unreleased_revisions = unreleased_revs
            component.mainline_revs = mainline_revs
        finally:
            branch.unlock()
        components[branch_name] = component

    return components
def create_components_from_branches(branches=None):

    if branches is None:
        branches = os.listdir('/srv/lava/branches')

    components = {}

    for branch_name in branches:
        component = Component(branch_name)
        branch = bzrlib.branch.Branch.open(
            os.path.join('/srv/lava/branches', branch_name))
        branch.lock_read()
        try:
            branch_tags = branch.tags.get_tag_dict()
            revno2release = {}
            release2revno = {}
            for tag, revid in branch_tags.iteritems():
                if tag.startswith('release-'):
                    try:
                        revno = branch.revision_id_to_dotted_revno(revid)
                    except bzrlib.errors.NoSuchRevision:
                        pass
                    else:
                        if len(revno) > 1:
                            continue
                        revno2release[revno[0]] = tag[len('release-'):]
                        release2revno[tag[len('release-'):]] = revno[0]
            if revno2release:
                component.released_revno = max(revno2release)
                component.last_release = revno2release[max(revno2release)]
            else:
                component.released_revno = None
            component.release2revno = release2revno
            component.revno2release = revno2release
            revno, revid = branch.last_revision_info()
            component.tip_revno = revno
            component.tip_revid = revid
            mainline_revs = []
            unreleased_revs = []
            while True:
                rev = branch.repository.get_revision(revid)
                if rev.message.strip() != 'post release bump':
                    if component.released_revno and revno > component.released_revno:
                        unreleased_revs.append((rev, revno))
                    mainline_revs.append((rev, revno))
                if not rev.parent_ids:
                    break
                revid = rev.parent_ids[0]
                revno -= 1
            component.unreleased_revisions = unreleased_revs
            component.mainline_revs = mainline_revs
        finally:
            branch.unlock()
        components[branch_name] = component

    return components
Пример #4
0
 def copy_tree(revid):
     files = files_cache[revid] = {}
     branch.lock_read()
     tree = branch.repository.revision_tree(revid)
     try:
         for path, entry in tree.iter_entries_by_dir():
             files[path] = [entry.file_id, None]
     finally:
         branch.unlock()
     return files
Пример #5
0
 def copy_tree(revid):
     files = files_cache[revid] = {}
     branch.lock_read()
     tree = branch.repository.revision_tree(revid)
     try:
         for path, entry in tree.iter_entries_by_dir():
             files[path] = [entry.file_id, None]
     finally:
         branch.unlock()
     return files
Пример #6
0
 def test_revision_history_when_locked(self):
     """Repeated calls to revision history will only call
     _gen_revision_history once while the branch is locked.
     """
     branch, calls = self.get_instrumented_branch()
     # Lock the branch, then repeatedly call revision_history.
     branch.lock_read()
     try:
         branch.revision_history()
         branch.revision_history()
         self.assertEqual(['_gen_revision_history'], calls)
     finally:
         branch.unlock()
Пример #7
0
 def test_revision_history_when_locked(self):
     """Repeated calls to revision history will only call
     _gen_revision_history once while the branch is locked.
     """
     branch, calls = self.get_instrumented_branch()
     # Lock the branch, then repeatedly call revision_history.
     branch.lock_read()
     try:
         branch.revision_history()
         branch.revision_history()
         self.assertEqual(['_gen_revision_history'], calls)
     finally:
         branch.unlock()
Пример #8
0
 def test_cached_revision_history_not_accidentally_mutable(self):
     """When there's a cached version of the history, revision_history
     returns a copy of the cached data so that callers cannot accidentally
     corrupt the cache.
     """
     branch = self.get_branch()
     # Lock the branch, then repeatedly call revision_history, mutating the
     # results.
     branch.lock_read()
     try:
         # The first time the data returned will not be in the cache.
         history = branch.revision_history()
         history.append('one')
         # The second time the data comes from the cache.
         history = branch.revision_history()
         history.append('two')
         # The revision_history() should still be unchanged, even though
         # we've mutated the return values from earlier calls.
         self.assertEqual([], branch.revision_history())
     finally:
         branch.unlock()
Пример #9
0
 def test_cached_revision_history_not_accidentally_mutable(self):
     """When there's a cached version of the history, revision_history
     returns a copy of the cached data so that callers cannot accidentally
     corrupt the cache.
     """
     branch = self.get_branch()
     # Lock the branch, then repeatedly call revision_history, mutating the
     # results.
     branch.lock_read()
     try:
         # The first time the data returned will not be in the cache.
         history = branch.revision_history()
         history.append('one')
         # The second time the data comes from the cache.
         history = branch.revision_history()
         history.append('two')
         # The revision_history() should still be unchanged, even though
         # we've mutated the return values from earlier calls.
         self.assertEqual([], branch.revision_history())
     finally:
         branch.unlock()
Пример #10
0
def do_list(parser):
    master_branch = None

    for name in branches:
        if not master_branch:
            master_branch = name
        print "? refs/heads/%s" % name

    branch = get_remote_branch(master_branch)
    branch.lock_read()
    for tag, revid in branch.tags.get_tag_dict().items():
        try:
            branch.revision_id_to_dotted_revno(revid)
        except bzrlib.errors.NoSuchRevision:
            continue
        if not ref_is_valid(tag):
            continue
        print "? refs/tags/%s" % tag
        tags[tag] = revid
    branch.unlock()

    print "@refs/heads/%s HEAD" % master_branch
    print
Пример #11
0
def do_list(parser):
    master_branch = None

    for name in branches:
        if not master_branch:
            master_branch = name
        print "? refs/heads/%s" % name

    branch = get_remote_branch(master_branch)
    branch.lock_read()
    for tag, revid in branch.tags.get_tag_dict().items():
        try:
            branch.revision_id_to_dotted_revno(revid)
        except bzrlib.errors.NoSuchRevision:
            continue
        if not ref_is_valid(tag):
            continue
        print "? refs/tags/%s" % tag
        tags[tag] = revid
    branch.unlock()

    print "@refs/heads/%s HEAD" % master_branch
    print
Пример #12
0
def export_branch(repo, name):
    ref = '%s/heads/%s' % (prefix, name)
    tip = marks.get_tip(name)

    branch = get_remote_branch(name)
    repo = branch.repository

    branch.lock_read()
    revs = branch.iter_merge_sorted_revisions(None, tip, 'exclude', 'forward')
    try:
        tip_revno = branch.revision_id_to_revno(tip)
        last_revno, _ = branch.last_revision_info()
        total = last_revno - tip_revno
    except bzrlib.errors.NoSuchRevision:
        tip_revno = 0
        total = 0

    for revid, _, seq, _ in revs:

        if marks.is_marked(revid):
            continue

        rev = repo.get_revision(revid)
        revno = seq[0]

        parents = rev.parent_ids
        time = rev.timestamp
        tz = rev.timezone
        committer = rev.committer.encode('utf-8')
        committer = "%s %u %s" % (fixup_user(committer), time, gittz(tz))
        authors = rev.get_apparent_authors()
        if authors:
            author = authors[0].encode('utf-8')
            author = "%s %u %s" % (fixup_user(author), time, gittz(tz))
        else:
            author = committer
        msg = rev.message.encode('utf-8')

        msg += '\n'

        if len(parents) == 0:
            parent = bzrlib.revision.NULL_REVISION
        else:
            parent = parents[0]

        cur_tree = repo.revision_tree(revid)
        prev = repo.revision_tree(parent)
        modified, removed = get_filechanges(cur_tree, prev)

        modified_final = export_files(cur_tree, modified)

        if len(parents) == 0:
            print 'reset %s' % ref

        print "commit %s" % ref
        print "mark :%d" % (marks.get_mark(revid))
        print "author %s" % (author)
        print "committer %s" % (committer)
        print "data %d" % (len(msg))
        print msg

        for i, p in enumerate(parents):
            try:
                m = rev_to_mark(p)
            except KeyError:
                # ghost?
                continue
            if i == 0:
                print "from :%s" % m
            else:
                print "merge :%s" % m

        for f in removed:
            print "D %s" % (f, )
        for f in modified_final:
            print "M %s :%u %s" % f
        print

        if len(seq) > 1:
            # let's skip branch revisions from the progress report
            continue

        progress = (revno - tip_revno)
        if (progress % 100 == 0):
            if total:
                print "progress revision %d '%s' (%d/%d)" % (revno, name,
                                                             progress, total)
            else:
                print "progress revision %d '%s' (%d)" % (revno, name,
                                                          progress)

    branch.unlock()

    revid = branch.last_revision()

    # make sure the ref is updated
    print "reset %s" % ref
    print "from :%u" % rev_to_mark(revid)
    print

    marks.set_tip(name, revid)
Пример #13
0
def export_branch(repo, name):
    ref = '%s/heads/%s' % (prefix, name)
    tip = marks.get_tip(name)

    branch = get_remote_branch(name)
    repo = branch.repository

    branch.lock_read()
    revs = branch.iter_merge_sorted_revisions(None, tip, 'exclude', 'forward')
    try:
        tip_revno = branch.revision_id_to_revno(tip)
        last_revno, _ = branch.last_revision_info()
        total = last_revno - tip_revno
    except bzrlib.errors.NoSuchRevision:
        tip_revno = 0
        total = 0

    for revid, _, seq, _ in revs:

        if marks.is_marked(revid):
            continue

        rev = repo.get_revision(revid)
        revno = seq[0]

        parents = rev.parent_ids
        time = rev.timestamp
        tz = rev.timezone
        committer = rev.committer.encode('utf-8')
        committer = "%s %u %s" % (fixup_user(committer), time, gittz(tz))
        authors = rev.get_apparent_authors()
        if authors:
            author = authors[0].encode('utf-8')
            author = "%s %u %s" % (fixup_user(author), time, gittz(tz))
        else:
            author = committer
        msg = rev.message.encode('utf-8')

        msg += '\n'

        if len(parents) == 0:
            parent = bzrlib.revision.NULL_REVISION
        else:
            parent = parents[0]

        cur_tree = repo.revision_tree(revid)
        prev = repo.revision_tree(parent)
        modified, removed = get_filechanges(cur_tree, prev)

        modified_final = export_files(cur_tree, modified)

        if len(parents) == 0:
            print 'reset %s' % ref

        print "commit %s" % ref
        print "mark :%d" % (marks.get_mark(revid))
        print "author %s" % (author)
        print "committer %s" % (committer)
        print "data %d" % (len(msg))
        print msg

        for i, p in enumerate(parents):
            try:
                m = rev_to_mark(p)
            except KeyError:
                # ghost?
                continue
            if i == 0:
                print "from :%s" % m
            else:
                print "merge :%s" % m

        for f in removed:
            print "D %s" % (f,)
        for f in modified_final:
            print "M %s :%u %s" % f
        print

        if len(seq) > 1:
            # let's skip branch revisions from the progress report
            continue

        progress = (revno - tip_revno)
        if (progress % 100 == 0):
            if total:
                print "progress revision %d '%s' (%d/%d)" % (revno, name, progress, total)
            else:
                print "progress revision %d '%s' (%d)" % (revno, name, progress)

    branch.unlock()

    revid = branch.last_revision()

    # make sure the ref is updated
    print "reset %s" % ref
    print "from :%u" % rev_to_mark(revid)
    print

    marks.set_tip(name, revid)
Пример #14
0
def export_branch(repo, name):
    ref = '%s/heads/%s' % (prefix, name)
    tip = marks.get_tip(name)

    branch = get_remote_branch(name)
    repo = branch.repository

    branch.lock_read()
    revs = branch.iter_merge_sorted_revisions(None, tip, 'exclude', 'forward')
    try:
        tip_revno = branch.revision_id_to_revno(tip)
        last_revno, _ = branch.last_revision_info()
        total = last_revno - tip_revno
    except bzrlib.errors.NoSuchRevision:
        tip_revno = 0
        total = 0

    for revid, _, seq, _ in revs:

        if marks.is_marked(revid):
            continue

        rev = repo.get_revision(revid)
        revno = seq[0]

        parents = rev.parent_ids
        time = rev.timestamp
        tz = rev.timezone
        committer = rev.committer.encode('utf-8')
        committer = "%s %u %s" % (fixup_user(committer), time, gittz(tz))
        authors = rev.get_apparent_authors()
        if authors:
            author = authors[0].encode('utf-8')
            author = "%s %u %s" % (fixup_user(author), time, gittz(tz))
        else:
            author = committer
        msg = rev.message.encode('utf-8')

        msg += '\n'

        if rev.properties.has_key('file-info'):
            from bzrlib import bencode
            try:
                files = bencode.bdecode(
                    rev.properties['file-info'].encode('utf-8'))
            except Exception, e:
                # protect against repository corruption
                # (happens in the wild, see MySQL tree)
                files = ()

            rmsg = msg.rstrip('\r\n ')
            file_comments = []
            for file in files:
                fmsg = file['message'].rstrip('\r\n ')
                # Skip empty file comments and file comments identical to the
                # commit comment (they originate from tools and policies that
                # require writing per-file comments and users simply copy-paste
                # revision comment over, these comments add no value as a part of
                # the commit comment).
                if fmsg == '' or fmsg == rmsg:
                    continue

                file_comments.append(file['path'] + ':')
                for l in fmsg.split('\n'):
                    file_comments.append('  ' + l)

            msg += '\n' + '\n'.join(file_comments) + '\n'

        if len(parents) == 0:
            parent = bzrlib.revision.NULL_REVISION
        else:
            parent = parents[0]

        cur_tree = repo.revision_tree(revid)
        prev = repo.revision_tree(parent)
        modified, removed = get_filechanges(cur_tree, prev)

        modified_final = export_files(cur_tree, modified)

        if len(parents) == 0:
            print 'reset %s' % ref

        print "commit %s" % ref
        print "mark :%d" % (marks.get_mark(revid))
        print "author %s" % (author)
        print "committer %s" % (committer)
        print "data %d" % (len(msg))
        print msg

        for i, p in enumerate(parents):
            try:
                m = rev_to_mark(p)
            except KeyError:
                # ghost?
                continue
            if i == 0:
                print "from :%s" % m
            else:
                print "merge :%s" % m

        for f in removed:
            print "D %s" % (f, )
        for f in modified_final:
            print "M %s :%u %s" % f
        print

        if len(seq) > 1:
            # let's skip branch revisions from the progress report
            continue

        progress = (revno - tip_revno)
        if (progress % 100 == 0):
            if total:
                print "progress revision %d '%s' (%d/%d)" % (revno, name,
                                                             progress, total)
            else:
                print "progress revision %d '%s' (%d)" % (revno, name,
                                                          progress)
Пример #15
0
def export_branch(repo, name):
    ref = '%s/heads/%s' % (prefix, name)
    tip = marks.get_tip(name)

    branch = get_remote_branch(name)
    repo = branch.repository

    branch.lock_read()
    revs = branch.iter_merge_sorted_revisions(None, tip, 'exclude', 'forward')
    try:
        tip_revno = branch.revision_id_to_revno(tip)
        last_revno, _ = branch.last_revision_info()
        total = last_revno - tip_revno
    except bzrlib.errors.NoSuchRevision:
        tip_revno = 0
        total = 0

    for revid, _, seq, _ in revs:

        if marks.is_marked(revid):
            continue

        rev = repo.get_revision(revid)
        revno = seq[0]

        parents = rev.parent_ids
        time = rev.timestamp
        tz = rev.timezone
        committer = rev.committer.encode('utf-8')
        committer = "%s %u %s" % (fixup_user(committer), time, gittz(tz))
        authors = rev.get_apparent_authors()
        if authors:
            author = authors[0].encode('utf-8')
            author = "%s %u %s" % (fixup_user(author), time, gittz(tz))
        else:
            author = committer
        msg = rev.message.encode('utf-8')

        msg += '\n'

        if rev.properties.has_key('file-info'):
            from bzrlib import bencode
            try:
                files = bencode.bdecode(rev.properties['file-info'].encode('utf-8'))
            except Exception, e:
                # protect against repository corruption
                # (happens in the wild, see MySQL tree)
                files = ()

            rmsg = msg.rstrip('\r\n ')
            file_comments = []
            for file in files:
              fmsg = file['message'].rstrip('\r\n ')
              # Skip empty file comments and file comments identical to the
              # commit comment (they originate from tools and policies that
              # require writing per-file comments and users simply copy-paste
              # revision comment over, these comments add no value as a part of
              # the commit comment).
              if fmsg == '' or fmsg == rmsg:
                  continue

              file_comments.append(file['path'] + ':')
              for l in fmsg.split('\n'):
                  file_comments.append('  ' + l)

            msg += '\n' + '\n'.join(file_comments) + '\n'

        if len(parents) == 0:
            parent = bzrlib.revision.NULL_REVISION
        else:
            parent = parents[0]

        cur_tree = repo.revision_tree(revid)
        prev = repo.revision_tree(parent)
        modified, removed = get_filechanges(cur_tree, prev)

        modified_final = export_files(cur_tree, modified)

        if len(parents) == 0:
            print 'reset %s' % ref

        print "commit %s" % ref
        print "mark :%d" % (marks.get_mark(revid))
        print "author %s" % (author)
        print "committer %s" % (committer)
        print "data %d" % (len(msg))
        print msg

        for i, p in enumerate(parents):
            try:
                m = rev_to_mark(p)
            except KeyError:
                # ghost?
                continue
            if i == 0:
                print "from :%s" % m
            else:
                print "merge :%s" % m

        for f in removed:
            print "D %s" % (f,)
        for f in modified_final:
            print "M %s :%u %s" % f
        print

        if len(seq) > 1:
            # let's skip branch revisions from the progress report
            continue

        progress = (revno - tip_revno)
        if (progress % 100 == 0):
            if total:
                print "progress revision %d '%s' (%d/%d)" % (revno, name, progress, total)
            else:
                print "progress revision %d '%s' (%d)" % (revno, name, progress)
def make_html(components, instances):
    table = tags.table(class_='main')
    heading_row = tags.tr()
    for heading in  'component', 'tip revno', 'unreleased revisions', 'latest release':
        heading_row(tags.th(heading))
    for instance_name in sorted(instances):
        heading_row(tags.th(instance_name, class_="instance-name"))
    table(tags.thead(heading_row))
    tbody = tags.tbody()
    for name, component in sorted(components.items()):
        row = tags.tr(class_="component")
        revs_between_ids = {}
        extra_rows = []
        def td(*args, **kwargs):
            row(tags.td(*args, **kwargs))
        td(name)
        td(str(component.tip_revno), class_='version')
        unreleased_count = len(component.unreleased_revisions)
        if unreleased_count:
            id_ = get_id()
            td(
                tags.a(str(unreleased_count), href='#', class_='highlight'),
                class_='version clickable', id=id_)
            sub_name = 'revs between %s (r%s) and tip (r%s)' % (
                component.last_release, component.released_revno,
                component.tip_revno)
            extra_rows.append(
                tags.tr(
                    tags.td(
                        format_revlist(component.unreleased_revisions, name=sub_name),
                        colspan=str(4 + len(instances))),
                    class_='hidden',
                    id="show-" + id_))
        elif not component.last_release:
            td(u'\N{EM DASH}', class_='version')
        else:
            td(str(unreleased_count), class_='version')
        if component.last_release:
            td(component.last_release, class_='version')
        else:
            td(u'???', class_='version')
        for instance_name, instance in sorted(instances.items()):
            ver, location = instance.get(name, (None, None))
            if ver is None:
                td(u'\N{EM DASH}', class_='version')
            elif ver == component.last_release:
                td(ver, class_='version')
            elif ver in component.release2revno:
                revno_low = component.release2revno[ver]
                sub_name = 'revs between %s (r%s) and %s (r%s)' % (
                    ver, revno_low,
                    component.last_release, component.released_revno)
                revlist = []
                for rev, revno in component.mainline_revs:
                    if revno_low < revno < component.released_revno:
                        revlist.append((rev, revno))
                if revlist:
                    id_ = get_id()
                    revs_between_ids[revno_low] = id_
                    extra_rows.append(
                        tags.tr(
                            tags.td(
                                format_revlist(revlist, name=sub_name),
                                colspan=str(4 + len(instances))),
                            class_='hidden branch-diff',
                            id="show-" + id_))
                    td(
                        tags.a(ver, href='#', class_='highlight'),
                        class_='version clickable', id=id_)
                else:
                    td(tags.span(ver, class_='highlight'), class_='version')
            elif location:
                try:
                    branch = bzrlib.branch.Branch.open(location)
                except bzrlib.errors.NoSuchBranch:
                    td(tags.span(ver, class_='highlight'), class_='version')
                else:
                    branch.lock_read()
                    try:
                        # This utterly half-assed version of bzr missing
                        # doesn't take merges into account!
                        revno, revid = branch.last_revision_info()
                        ver = ver.split('dev')[0] + 'dev' + str(revno)
                        mainline_revids = dict(
                            (rev.revision_id, revno)
                            for rev, revno in component.mainline_revs)
                        in_branch_revs = []
                        while revid not in mainline_revids:
                            rev = branch.repository.get_revision(revid)
                            if rev.message != 'post release bump':
                                in_branch_revs.append((rev, revno))
                            revno -= 1
                            if not rev.parent_ids:
                                break
                            revid = rev.parent_ids[0]
                        tables = []
                        if in_branch_revs:
                            tables.append(
                                format_revlist(
                                    in_branch_revs,
                                    'in branch (with nick %s) but not tip' % branch.nick))
                        in_trunk_revs = []
                        lca_revno = revno
                        for rev, revno in component.mainline_revs:
                            if revno > lca_revno:
                                in_trunk_revs.append((rev, revno))
                        if in_trunk_revs:
                            tables.append(
                                format_revlist(
                                    in_trunk_revs,
                                    'in tip but not branch'))
                        if tables:
                            id_ = get_id()
                            td(
                                tags.a(ver, href='#', class_='highlight'),
                                class_='version clickable', id=id_)
                            extra_rows.append(
                                tags.tr(
                                    tags.td(
                                        tables,
                                        colspan=str(4 + len(instances))),
                                    class_='hidden branch-diff',
                                    id="show-" + id_))
                        else:
                            if branch.last_revision() == component.tip_revno:
                                td(ver, class_='highlight version')
                            else:
                                td(ver, class_='version')
                    finally:
                        branch.unlock()
            else:
                td(tags.span(ver, class_='highlight'), class_='version')
        tbody(row, *extra_rows)
    table(tbody)
    html = tags.html(
        tags.head(
            tags.title("Deployment report"),
            tags.script(
                src='https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js',
                type='text/javascript'),
            tags.script(
                src='https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.18/jquery-ui.min.js',
                type='text/javascript'),
            tags.script(CDATA(js), type='text/javascript'),
            tags.style(CDATA(css), type="text/css"),
            ),
        tags.body(
            tags.h1("Deployment report"),
            table,
            ),
        )
    html(xmlns="http://www.w3.org/1999/xhtml")
    return DOCTYPE + flatten(html)
def make_html(components, instances):
    table = tags.table(class_='main')
    heading_row = tags.tr()
    for heading in 'component', 'tip revno', 'unreleased revisions', 'latest release':
        heading_row(tags.th(heading))
    for instance_name in sorted(instances):
        heading_row(tags.th(instance_name, class_="instance-name"))
    table(tags.thead(heading_row))
    tbody = tags.tbody()
    for name, component in sorted(components.items()):
        row = tags.tr(class_="component")
        revs_between_ids = {}
        extra_rows = []

        def td(*args, **kwargs):
            row(tags.td(*args, **kwargs))

        td(name)
        td(str(component.tip_revno), class_='version')
        unreleased_count = len(component.unreleased_revisions)
        if unreleased_count:
            id_ = get_id()
            td(tags.a(str(unreleased_count), href='#', class_='highlight'),
               class_='version clickable',
               id=id_)
            sub_name = 'revs between %s (r%s) and tip (r%s)' % (
                component.last_release, component.released_revno,
                component.tip_revno)
            extra_rows.append(
                tags.tr(tags.td(format_revlist(component.unreleased_revisions,
                                               name=sub_name),
                                colspan=str(4 + len(instances))),
                        class_='hidden',
                        id="show-" + id_))
        elif not component.last_release:
            td(u'\N{EM DASH}', class_='version')
        else:
            td(str(unreleased_count), class_='version')
        if component.last_release:
            td(component.last_release, class_='version')
        else:
            td(u'???', class_='version')
        for instance_name, instance in sorted(instances.items()):
            ver, location = instance.get(name, (None, None))
            if ver is None:
                td(u'\N{EM DASH}', class_='version')
            elif ver == component.last_release:
                td(ver, class_='version')
            elif ver in component.release2revno:
                revno_low = component.release2revno[ver]
                sub_name = 'revs between %s (r%s) and %s (r%s)' % (
                    ver, revno_low, component.last_release,
                    component.released_revno)
                revlist = []
                for rev, revno in component.mainline_revs:
                    if revno_low < revno < component.released_revno:
                        revlist.append((rev, revno))
                if revlist:
                    id_ = get_id()
                    revs_between_ids[revno_low] = id_
                    extra_rows.append(
                        tags.tr(tags.td(format_revlist(revlist, name=sub_name),
                                        colspan=str(4 + len(instances))),
                                class_='hidden branch-diff',
                                id="show-" + id_))
                    td(tags.a(ver, href='#', class_='highlight'),
                       class_='version clickable',
                       id=id_)
                else:
                    td(tags.span(ver, class_='highlight'), class_='version')
            elif location:
                try:
                    branch = bzrlib.branch.Branch.open(location)
                except bzrlib.errors.NoSuchBranch:
                    td(tags.span(ver, class_='highlight'), class_='version')
                else:
                    branch.lock_read()
                    try:
                        # This utterly half-assed version of bzr missing
                        # doesn't take merges into account!
                        revno, revid = branch.last_revision_info()
                        ver = ver.split('dev')[0] + 'dev' + str(revno)
                        mainline_revids = dict(
                            (rev.revision_id, revno)
                            for rev, revno in component.mainline_revs)
                        in_branch_revs = []
                        while revid not in mainline_revids:
                            rev = branch.repository.get_revision(revid)
                            if rev.message != 'post release bump':
                                in_branch_revs.append((rev, revno))
                            revno -= 1
                            if not rev.parent_ids:
                                break
                            revid = rev.parent_ids[0]
                        tables = []
                        if in_branch_revs:
                            tables.append(
                                format_revlist(
                                    in_branch_revs,
                                    'in branch (with nick %s) but not tip' %
                                    branch.nick))
                        in_trunk_revs = []
                        lca_revno = revno
                        for rev, revno in component.mainline_revs:
                            if revno > lca_revno:
                                in_trunk_revs.append((rev, revno))
                        if in_trunk_revs:
                            tables.append(
                                format_revlist(in_trunk_revs,
                                               'in tip but not branch'))
                        if tables:
                            id_ = get_id()
                            td(tags.a(ver, href='#', class_='highlight'),
                               class_='version clickable',
                               id=id_)
                            extra_rows.append(
                                tags.tr(tags.td(tables,
                                                colspan=str(4 +
                                                            len(instances))),
                                        class_='hidden branch-diff',
                                        id="show-" + id_))
                        else:
                            if branch.last_revision() == component.tip_revno:
                                td(ver, class_='highlight version')
                            else:
                                td(ver, class_='version')
                    finally:
                        branch.unlock()
            else:
                td(tags.span(ver, class_='highlight'), class_='version')
        tbody(row, *extra_rows)
    table(tbody)
    html = tags.html(
        tags.head(
            tags.title("Deployment report"),
            tags.script(
                src=
                'https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js',
                type='text/javascript'),
            tags.script(
                src=
                'https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.18/jquery-ui.min.js',
                type='text/javascript'),
            tags.script(CDATA(js), type='text/javascript'),
            tags.style(CDATA(css), type="text/css"),
        ),
        tags.body(
            tags.h1("Deployment report"),
            table,
        ),
    )
    html(xmlns="http://www.w3.org/1999/xhtml")
    return DOCTYPE + flatten(html)