Benjamin Poirier 600ead
#!/usr/bin/python3
Benjamin Poirier 0aaea3
# -*- coding: utf-8 -*-
Benjamin Poirier 0aaea3
Benjamin Poirier c7a109
# Copyright (C) 2018 SUSE LLC
Benjamin Poirier c7a109
#
Benjamin Poirier c7a109
# This program is free software; you can redistribute it and/or
Benjamin Poirier c7a109
# modify it under the terms of the GNU General Public License
Benjamin Poirier c7a109
# as published by the Free Software Foundation; either version 2
Benjamin Poirier c7a109
# of the License, or (at your option) any later version.
Benjamin Poirier c7a109
#
Benjamin Poirier c7a109
# This program is distributed in the hope that it will be useful,
Benjamin Poirier c7a109
# but WITHOUT ANY WARRANTY; without even the implied warranty of
Benjamin Poirier c7a109
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
Benjamin Poirier c7a109
# GNU General Public License for more details.
Benjamin Poirier c7a109
#
Benjamin Poirier c7a109
# You should have received a copy of the GNU General Public License
Benjamin Poirier c7a109
# along with this program; if not, write to the Free Software
Benjamin Poirier c7a109
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
Benjamin Poirier c7a109
# USA.
Benjamin Poirier c7a109
Benjamin Poirier 0aaea3
import argparse
Benjamin Poirier 0abe03
import bisect
Benjamin Poirier 897bbc
import collections
Benjamin Poirier 600ead
import dbm
Benjamin Poirier 404509
import functools
Benjamin Poirier e8d72d
import operator
Benjamin Poirier 0aaea3
import os
Benjamin Poirier 0aaea3
import os.path
Benjamin Poirier 0aaea3
import pprint
Benjamin Poirier 897bbc
import re
Benjamin Poirier 0aaea3
import shelve
Benjamin Poirier 0aaea3
import subprocess
Benjamin Poirier 0aaea3
import sys
Benjamin Poirier 587a8d
import types
Benjamin Poirier 0aaea3
Benjamin Poirier 50602b
import pygit2_wrapper as pygit2
Benjamin Poirier 50602b
Benjamin Poirier 0aaea3
Benjamin Poirier e8de5e
class GSException(BaseException):
Benjamin Poirier e8de5e
    pass
Benjamin Poirier e8de5e
Benjamin Poirier e8de5e
Benjamin Poirier e8de5e
class GSError(GSException):
Benjamin Poirier e8de5e
    pass
Benjamin Poirier e8de5e
Benjamin Poirier e8de5e
Benjamin Poirier 544f5d
class GSKeyError(GSException):
Benjamin Poirier 544f5d
    pass
Benjamin Poirier 544f5d
Benjamin Poirier 544f5d
Benjamin Poirier 0abe03
class GSNotFound(GSException):
Benjamin Poirier 0abe03
    pass
Benjamin Poirier 0abe03
Benjamin Poirier 0abe03
Benjamin Poirier 897bbc
class RepoURL(object):
Benjamin Poirier 897bbc
    k_org_canon_prefix = "git://git.kernel.org/pub/scm/linux/kernel/git/"
Benjamin Poirier 897bbc
    proto_match = re.compile("(git|https?)://")
Benjamin Poirier 897bbc
    ext = ".git"
Benjamin Poirier 0aaea3
Benjamin Poirier 897bbc
    def __init__(self, url):
Benjamin Poirier 2056d7
        if url is None or url == repr(None):
Benjamin Poirier 2056d7
            self.url = None
Benjamin Poirier 2056d7
            return
Benjamin Poirier 2056d7
Benjamin Poirier 897bbc
        k_org_prefixes = [
Benjamin Poirier 720a01
            "http://git.kernel.org/pub/scm/linux/kernel/git/",
Benjamin Poirier 897bbc
            "https://git.kernel.org/pub/scm/linux/kernel/git/",
Matthias Brugger 27af81
            "ssh://git@gitolite.kernel.org/pub/scm/linux/kernel/git/",
Benjamin Poirier 897bbc
            "https://kernel.googlesource.com/pub/scm/linux/kernel/git/",
Benjamin Poirier 897bbc
        ]
Benjamin Poirier 897bbc
        for prefix in k_org_prefixes:
Benjamin Poirier 897bbc
            if url.startswith(prefix):
Benjamin Poirier 897bbc
                url = url.replace(prefix, self.k_org_canon_prefix)
Benjamin Poirier 897bbc
                break
Benjamin Poirier 0aaea3
Benjamin Poirier 16928c
        if not self.proto_match.match(url):
Benjamin Poirier 897bbc
            url = self.k_org_canon_prefix + url
Benjamin Poirier 439e8e
Benjamin Poirier 439e8e
        if not url.endswith(self.ext):
Benjamin Poirier 439e8e
            url = url + self.ext
Benjamin Poirier 0aaea3
Benjamin Poirier 5aa06b
        # an undocumented alias
Benjamin Poirier 5aa06b
        if url == "git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git":
Benjamin Poirier 5aa06b
            url = "git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git"
Benjamin Poirier 5aa06b
Benjamin Poirier 897bbc
        self.url = url
Benjamin Poirier 0aaea3
Benjamin Poirier 0aaea3
Benjamin Poirier 404509
    def _is_valid_operand(self, other):
Benjamin Poirier 404509
        return hasattr(other, "url")
Benjamin Poirier 404509
Benjamin Poirier 404509
Benjamin Poirier 897bbc
    def __eq__(self, other):
Benjamin Poirier 404509
        if not self._is_valid_operand(other):
Benjamin Poirier 404509
            return NotImplemented
Benjamin Poirier 897bbc
        return self.url == other.url
Benjamin Poirier 0aaea3
Benjamin Poirier 0aaea3
Benjamin Poirier 404509
    def __ne__(self, other):
Benjamin Poirier 404509
        if not self._is_valid_operand(other):
Benjamin Poirier 404509
            return NotImplemented
Benjamin Poirier 404509
        return self.url != other.url
Benjamin Poirier 7b51a6
Benjamin Poirier 7b51a6
Benjamin Poirier 897bbc
    def __hash__(self):
Benjamin Poirier 897bbc
        return hash(self.url)
Benjamin Poirier 0aaea3
Benjamin Poirier 897bbc
Benjamin Poirier 897bbc
    def __repr__(self):
Benjamin Poirier 897bbc
        return "%s" % (self.url,)
Benjamin Poirier 897bbc
Benjamin Poirier 897bbc
Benjamin Poirier 897bbc
    def __str__(self):
Benjamin Poirier 897bbc
        url = self.url
Benjamin Poirier 2056d7
        if url is None:
Benjamin Poirier 897bbc
            url = ""
Benjamin Poirier 2056d7
        elif url.startswith(self.k_org_canon_prefix) and url.endswith(self.ext):
Benjamin Poirier 2056d7
            url = url[len(self.k_org_canon_prefix):-1 * len(self.ext)]
Benjamin Poirier 897bbc
Benjamin Poirier 897bbc
        return url
Benjamin Poirier 897bbc
Benjamin Poirier 897bbc
Benjamin Poirier 404509
@functools.total_ordering
Benjamin Poirier 897bbc
class Head(object):
Benjamin Poirier 897bbc
    def __init__(self, repo_url, rev="master"):
Benjamin Poirier 897bbc
        self.repo_url = repo_url
Benjamin Poirier 897bbc
        self.rev = rev
Benjamin Poirier 897bbc
Benjamin Poirier 897bbc
Benjamin Poirier 404509
    def _is_valid_operand(self, other):
Benjamin Poirier 404509
        return hasattr(other, "repo_url") and hasattr(other, "rev")
Benjamin Poirier 404509
Benjamin Poirier 404509
Benjamin Poirier 404509
    def _get_index(self):
Benjamin Poirier 404509
        """
Benjamin Poirier 404509
        A head with no url is considered out of tree. Any other head with a
Benjamin Poirier 404509
        url is upstream of it.
Benjamin Poirier 404509
        """
Benjamin Poirier 404509
        if self.repo_url == RepoURL(None):
Benjamin Poirier 404509
            return len(remotes)
Benjamin Poirier 404509
        else:
Benjamin Poirier 404509
            return remote_index[self]
Benjamin Poirier 404509
Benjamin Poirier 404509
Benjamin Poirier 897bbc
    def __eq__(self, other):
Benjamin Poirier 404509
        if not self._is_valid_operand(other):
Benjamin Poirier 404509
            return NotImplemented
Benjamin Poirier 897bbc
        return (self.repo_url == other.repo_url and self.rev == other.rev)
Benjamin Poirier 897bbc
Benjamin Poirier 897bbc
Benjamin Poirier 404509
    def __lt__(self, other):
Benjamin Poirier 404509
        if not self._is_valid_operand(other):
Benjamin Poirier 404509
            return NotImplemented
Benjamin Poirier 404509
        return self._get_index() < other._get_index()
Benjamin Poirier 404509
Benjamin Poirier 404509
Benjamin Poirier 897bbc
    def __hash__(self):
Benjamin Poirier 897bbc
        return hash((self.repo_url, self.rev,))
Benjamin Poirier 0aaea3
Benjamin Poirier 0aaea3
Benjamin Poirier 76bf0c
    def __repr__(self):
Benjamin Poirier 897bbc
        return "%s %s" % (repr(self.repo_url), self.rev,)
Benjamin Poirier 897bbc
Benjamin Poirier 897bbc
Benjamin Poirier 897bbc
    def __str__(self):
Benjamin Poirier 897bbc
        url = str(self.repo_url)
Benjamin Poirier 897bbc
        if self.rev == "master":
Benjamin Poirier 897bbc
            return url
Benjamin Poirier 897bbc
        else:
Benjamin Poirier 897bbc
            result = "%s %s" % (url, self.rev,)
Benjamin Poirier 897bbc
            return result.strip()
Benjamin Poirier 897bbc
Benjamin Poirier 897bbc
Benjamin Poirier 897bbc
# a list of each remote head which is indexed by this script
Benjamin Poirier 7c5bc6
# If the working repository is a clone of linux.git (it fetches from mainline,
Benjamin Poirier 7c5bc6
# the first remote) and a commit does not appear in one of these remotes, it is
Benjamin Poirier 7c5bc6
# considered "not upstream" and cannot be sorted.
Benjamin Poirier 897bbc
# Repositories that come first in the list should be pulling/merging from
Benjamin Poirier 897bbc
# repositories lower down in the list. Said differently, commits should trickle
Benjamin Poirier 897bbc
# up from repositories at the end of the list to repositories higher up. For
Benjamin Poirier 897bbc
# example, network commits usually follow "net-next" -> "net" -> "linux.git".
Benjamin Poirier 897bbc
#
Benjamin Poirier 4809eb
# linux-next is not a good reference because it gets rebased. If a commit is in
Benjamin Poirier 4809eb
# linux-next, it comes from some other tree. Please tag the patch accordingly.
Benjamin Poirier 4809eb
#
Benjamin Poirier 897bbc
# Head(RepoURL(remote url), remote branch name)[]
Benjamin Poirier 897bbc
# Note that "remote url" can be abbreviated if it starts with one of the usual
Benjamin Poirier 897bbc
# kernel.org prefixes and "remote branch name" can be omitted if it is "master".
Benjamin Poirier 897bbc
remotes = (
Benjamin Poirier 897bbc
    Head(RepoURL("torvalds/linux.git")),
Michal Kubecek 445746
    Head(RepoURL("netdev/net.git")),
Benjamin Poirier 897bbc
    Head(RepoURL("davem/net.git")),
Michal Kubecek 445746
    Head(RepoURL("netdev/net-next.git")),
Benjamin Poirier 897bbc
    Head(RepoURL("davem/net-next.git")),
Michal Suchanek ec2ea2
    Head(RepoURL("rdma/rdma.git"), "for-rc"),
Michal Suchanek ec2ea2
    Head(RepoURL("rdma/rdma.git"), "for-next"),
Benjamin Poirier 897bbc
    Head(RepoURL("dledford/rdma.git"), "k.o/for-next"),
Petr Tesarik 0e18ba
    Head(RepoURL("jejb/scsi.git"), "for-next"),
Benjamin Poirier 897bbc
    Head(RepoURL("bp/bp.git"), "for-next"),
Benjamin Poirier 897bbc
    Head(RepoURL("tiwai/sound.git")),
Benjamin Poirier 131a90
    Head(RepoURL("git://linuxtv.org/media_tree.git")),
Benjamin Poirier 897bbc
    Head(RepoURL("powerpc/linux.git"), "next"),
Jeff Mahoney 1f975c
    Head(RepoURL("powerpc/linux.git"), "fixes"),
Benjamin Poirier 897bbc
    Head(RepoURL("tip/tip.git")),
Benjamin Poirier 5d61b5
    Head(RepoURL("shli/md.git"), "for-next"),
Benjamin Poirier 5d61b5
    Head(RepoURL("dhowells/linux-fs.git"), "keys-uefi"),
Jiri Kosina 9f5c18
    Head(RepoURL("tytso/ext4.git"), "dev"),
Petr Tesarik d41c9f
    Head(RepoURL("s390/linux.git"), "fixes"),
Benjamin Poirier 131a90
    Head(RepoURL("https://github.com/kdave/btrfs-devel.git"), "misc-next"),
Benjamin Poirier a3e6d3
    Head(RepoURL("git://people.freedesktop.org/~airlied/linux"), "drm-next"),
Thomas Zimmermann 379ad3
    Head(RepoURL("git://anongit.freedesktop.org/drm/drm-misc"), "drm-misc-next"),
Benjamin Poirier a3e6d3
    Head(RepoURL("gregkh/tty.git"), "tty-next"),
Goldwyn Rodrigues d8d320
    Head(RepoURL("jj/linux-apparmor.git"), "apparmor-next"),
Michal Kubecek daa89d
    Head(RepoURL("pablo/nf.git")),
Michal Kubecek daa89d
    Head(RepoURL("pablo/nf-next.git")),
Michal Kubecek daa89d
    Head(RepoURL("horms/ipvs.git")),
Michal Kubecek daa89d
    Head(RepoURL("horms/ipvs-next.git")),
Michal Kubecek daa89d
    Head(RepoURL("klassert/ipsec.git")),
Michal Kubecek daa89d
    Head(RepoURL("klassert/ipsec-next.git")),
Petr Tesarik 46e9bd
    Head(RepoURL("kvalo/wireless-drivers-next.git")),
Johannes Thumshirn 8ce95c
    Head(RepoURL("mkp/scsi.git"), "queue"),
Johannes Thumshirn b5a813
    Head(RepoURL("mkp/scsi.git"), "fixes"),
Borislav Petkov 9adbb9
    Head(RepoURL("git://git.kernel.dk/linux-block.git"), "for-next"),
Johannes Thumshirn 40fc9c
    Head(RepoURL("git://git.kernel.org/pub/scm/virt/kvm/kvm.git"), "queue"),
Daniel Wagner 3bdd6d
    Head(RepoURL("git://git.infradead.org/nvme.git"), "nvme-5.15"),
Michal Suchanek f433a0
    Head(RepoURL("dhowells/linux-fs.git")),
Benjamin Poirier 64ee72
    Head(RepoURL("herbert/cryptodev-2.6.git")),
Olaf Hering 718b01
    Head(RepoURL("helgaas/pci.git"), "next"),
Luis Henriques 82df85
    Head(RepoURL("viro/vfs.git"), "for-linus"),
Goldwyn Rodrigues de81e9
    Head(RepoURL("viro/vfs.git"), "fixes"),
Jessica Yu 31d0a4
    Head(RepoURL("jeyu/linux.git"), "modules-next"),
Joerg Roedel 3ac5af
    Head(RepoURL("joro/iommu.git"), "next"),
Johannes Thumshirn 2a73ff
    Head(RepoURL("nvdimm/nvdimm.git"), "libnvdimm-for-next"),
Johannes Thumshirn c8c847
    Head(RepoURL("nvdimm/nvdimm.git"), "libnvdimm-fixes"),
Johannes Thumshirn 7d6ade
    Head(RepoURL("djbw/nvdimm.git"), "libnvdimm-pending"),
86af8b
    Head(RepoURL("git://git.linux-nfs.org/projects/anna/linux-nfs.git"), "linux-next"),
Michal Suchanek 727e37
    Head(RepoURL("acme/linux.git"), "perf/core"),
Matthias Brugger 3d5eb0
    Head(RepoURL("will/linux.git"), "for-joerg/arm-smmu/updates"),
Michal Suchanek f74c58
    Head(RepoURL("herbert/crypto-2.6.git"), "master"),
Thomas Renninger f216f5
    Head(RepoURL("rafael/linux-pm.git")),
NeilBrown ec6f7e
    Head(RepoURL("git://git.linux-nfs.org/~bfields/linux.git"), "nfsd-next"),
Michal Suchanek 99afd5
    Head(RepoURL("vkoul/soundwire.git"),"fixes"),
Michal Suchanek 99afd5
    Head(RepoURL("vkoul/soundwire.git"),"next"),
Matthias Brugger 2e5580
    Head(RepoURL("arm64/linux.git"), "for-next/core"),
Nicolas Saenz Julienne 11db49
    Head(RepoURL("robh/linux.git"), "for-next"),
Nicolas Saenz Julienne 5d9839
    Head(RepoURL("git://git.infradead.org/users/hch/dma-mapping.git"), "for-next"),
Matthias Brugger 84aca3
    Head(RepoURL("thermal/linux.git"), "thermal/linux-next"),
Matthias Brugger 78b705
    Head(RepoURL("git://github.com/cminyard/linux-ipmi.git"), "for-next"),
Borislav Petkov e71810
    Head(RepoURL("ras/ras.git"), "edac-for-next"),
Nicolas Saenz Julienne 73604d
    Head(RepoURL("linusw/linux-pinctrl.git"), "for-next"),
Lee, Chun-Yi 5c191a
    Head(RepoURL("efi/efi.git"), "next"),
Nicolas Saenz Julienne 7febf5
    Head(RepoURL("ulfh/mmc.git"), "next"),
Jessica Yu 1ed297
    Head(RepoURL("masahiroy/linux-kbuild.git"), "for-next"),
Cho, Yu-Chen f7fbbb
    Head(RepoURL("bluetooth/bluetooth-next.git")),
Nicolas Saenz Julienne 543517
    Head(RepoURL("clk/linux.git"), "clk-next"),
Luis Henriques 3233d6
    Head(RepoURL("git://github.com/ceph/ceph-client"), "testing"),
Gary Lin 65979e
    Head(RepoURL("bpf/bpf.git")),
Benjamin Poirier 897bbc
)
Benjamin Poirier 897bbc
Benjamin Poirier 897bbc
Benjamin Poirier 600ead
remote_index = dict(zip(remotes, list(range(len(remotes)))))
Benjamin Poirier 1a4488
oot = Head(RepoURL(None), "out-of-tree patches")
Benjamin Poirier 8d50e8
Benjamin Poirier f17031
Benjamin Poirier 587a8d
def get_heads(repo):
Benjamin Poirier 587a8d
    """
Benjamin Poirier 587a8d
    Returns
Benjamin Poirier 587a8d
    repo_heads[Head]
Benjamin Poirier 587a8d
        sha1
Benjamin Poirier 587a8d
    """
Benjamin Poirier 587a8d
    result = collections.OrderedDict()
Benjamin Poirier 56e868
    repo_remotes = collections.OrderedDict(
Benjamin Poirier 56e868
        ((RepoURL(remote.url), remote,) for remote in repo.remotes))
Benjamin Poirier 4f1bbb
Benjamin Poirier 587a8d
    for head in remotes:
Benjamin Poirier 7c5bc6
        if head in result:
Benjamin Poirier 7c5bc6
            raise GSException("head \"%s\" is not unique." % (head,))
Benjamin Poirier 7c5bc6
Benjamin Poirier 76bf0c
        try:
Benjamin Poirier 56e868
            remote = repo_remotes[head.repo_url]
Benjamin Poirier 587a8d
        except KeyError:
Benjamin Poirier 587a8d
            continue
Benjamin Poirier 0aaea3
Benjamin Poirier 56e868
        lhs = "refs/heads/%s" % (head.rev,)
Benjamin Poirier 56e868
        rhs = None
Benjamin Poirier 56e868
        nb = len(remote.fetch_refspecs)
Benjamin Poirier 56e868
        if nb == 0:
Benjamin Poirier 56e868
            # `git clone --bare` case
Benjamin Poirier 56e868
            rhs = lhs
Benjamin Poirier 56e868
        else:
Benjamin Poirier 56e868
            for i in range(nb):
Benjamin Poirier 56e868
                r = remote.get_refspec(i)
Benjamin Poirier 56e868
                if r.src_matches(lhs):
Benjamin Poirier 56e868
                    rhs = r.transform(lhs)
Benjamin Poirier 56e868
                    break
Benjamin Poirier 56e868
        if rhs is None:
Benjamin Poirier 56e868
            raise GSError("No matching fetch refspec for head \"%s\"." %
Benjamin Poirier 56e868
                          (head,))
Benjamin Poirier 587a8d
        try:
Benjamin Poirier 56e868
            commit = repo.revparse_single(rhs)
Benjamin Poirier 587a8d
        except KeyError:
Benjamin Poirier 56e868
            raise GSError("Could not read revision \"%s\". Perhaps you need "
Benjamin Poirier 56e868
                          "to fetch from remote \"%s\"" % (rhs, remote.name,))
Benjamin Poirier 587a8d
        result[head] = str(commit.id)
Benjamin Poirier 587a8d
Benjamin Poirier 600ead
    if len(result) == 0 or list(result.keys())[0] != remotes[0]:
Benjamin Poirier 587a8d
        # According to the urls in remotes, this is not a clone of linux.git
Benjamin Poirier 587a8d
        # Sort according to commits reachable from the current head
Benjamin Poirier 587a8d
        result = collections.OrderedDict(
Benjamin Poirier 587a8d
            [(Head(RepoURL(None), "HEAD"),
Benjamin Poirier 587a8d
              str(repo.revparse_single("HEAD").id),)])
Benjamin Poirier 587a8d
Benjamin Poirier 587a8d
    return result
Benjamin Poirier 587a8d
Benjamin Poirier 587a8d
Benjamin Poirier 587a8d
def get_history(repo, repo_heads):
Benjamin Poirier 587a8d
    """
Benjamin Poirier 587a8d
    Returns
Benjamin Poirier 587a8d
    history[Head][commit hash represented as string of 40 characters]
Benjamin Poirier 587a8d
            index, an ordinal number such that
Benjamin Poirier 587a8d
            commit a is an ancestor of commit b -> index(a) < index(b)
Benjamin Poirier 587a8d
    """
Benjamin Poirier 587a8d
    processed = []
Benjamin Poirier 587a8d
    history = collections.OrderedDict()
Benjamin Poirier 587a8d
    args = ["git", "log", "--topo-order", "--pretty=tformat:%H"]
Benjamin Poirier 587a8d
    for head, rev in repo_heads.items():
Benjamin Poirier 587a8d
        sp = subprocess.Popen(args + processed + [rev],
Benjamin Poirier 587a8d
                              cwd=repo.path,
Benjamin Poirier 587a8d
                              env={},
Benjamin Poirier 587a8d
                              stdout=subprocess.PIPE,
Benjamin Poirier 587a8d
                              stderr=subprocess.STDOUT)
Benjamin Poirier 587a8d
Benjamin Poirier 587a8d
        result = {}
Benjamin Poirier 587a8d
        for l in sp.stdout:
Benjamin Poirier 600ead
            result[l.decode().strip()] = len(result)
Benjamin Poirier 587a8d
        # reverse indexes
Benjamin Poirier 587a8d
        history[head] = {commit : len(result) - val for commit, val in
Benjamin Poirier 587a8d
                         result.items()}
Benjamin Poirier 587a8d
Benjamin Poirier 587a8d
        sp.communicate()
Benjamin Poirier 587a8d
        if sp.returncode != 0:
Benjamin Poirier 587a8d
            raise GSError("git log exited with an error:\n" +
Benjamin Poirier 587a8d
                          "\n".join(history[head]))
Benjamin Poirier 587a8d
Benjamin Poirier 587a8d
        processed.append("^%s" % (rev,))
Benjamin Poirier 587a8d
Benjamin Poirier 587a8d
    return history
Benjamin Poirier 587a8d
Benjamin Poirier 8d50e8
Benjamin Poirier 2c7d8e
class CException(BaseException):
Benjamin Poirier 2c7d8e
    pass
Benjamin Poirier 0aaea3
Benjamin Poirier 76bf0c
Benjamin Poirier 2c7d8e
class CError(CException):
Benjamin Poirier 2c7d8e
    pass
Benjamin Poirier f28d0a
Benjamin Poirier 76bf0c
Benjamin Poirier 2c7d8e
class CNeedsRebuild(CException):
Benjamin Poirier 2c7d8e
    pass
Benjamin Poirier 76bf0c
Benjamin Poirier 76bf0c
Benjamin Poirier 2c7d8e
class CAbsent(CNeedsRebuild):
Benjamin Poirier 2c7d8e
    pass
Benjamin Poirier 76bf0c
Benjamin Poirier 76bf0c
Benjamin Poirier 2c7d8e
class CKeyError(CNeedsRebuild):
Benjamin Poirier 2c7d8e
    pass
Benjamin Poirier 76bf0c
Benjamin Poirier 76bf0c
Benjamin Poirier 2c7d8e
class CUnsupported(CNeedsRebuild):
Benjamin Poirier 2c7d8e
    pass
Benjamin Poirier 76bf0c
Benjamin Poirier 76bf0c
Benjamin Poirier 2c7d8e
class CInconsistent(CNeedsRebuild):
Benjamin Poirier 2c7d8e
    pass
Benjamin Poirier 76bf0c
Benjamin Poirier 2c7d8e
Benjamin Poirier 2c7d8e
class Cache(object):
Benjamin Poirier 2c7d8e
    """
Benjamin Poirier 2c7d8e
    cache
Benjamin Poirier 2c7d8e
        version
Benjamin Poirier 2c7d8e
        history[]
Benjamin Poirier 2c7d8e
            (url, rev, sha1,
Benjamin Poirier 2c7d8e
             history[commit hash represented as string of 40 characters]
Benjamin Poirier 2c7d8e
                index (as described in get_history())
Benjamin Poirier 2c7d8e
             ,)
Benjamin Poirier 2c7d8e
Benjamin Poirier 2c7d8e
    The cache is stored using basic types.
Benjamin Poirier 2c7d8e
    """
Benjamin Poirier 2c7d8e
    version = 3
Benjamin Poirier 2c7d8e
Benjamin Poirier 2c7d8e
Benjamin Poirier 2c7d8e
    def __init__(self, write_enable=False):
Benjamin Poirier 2c7d8e
        self.write_enable = write_enable
Benjamin Poirier 2c7d8e
        self.closed = True
Benjamin Poirier db0e54
        try:
Benjamin Poirier db0e54
            cache_dir = os.environ["XDG_CACHE_HOME"]
Benjamin Poirier db0e54
        except KeyError:
Benjamin Poirier db0e54
            cache_dir = os.path.expanduser("~/.cache")
Benjamin Poirier 2c7d8e
        cache_path = os.path.join(cache_dir, "git-sort")
Benjamin Poirier 2c7d8e
        try:
Benjamin Poirier 2c7d8e
            os.stat(cache_path)
Benjamin Poirier 2c7d8e
        except OSError as e:
Benjamin Poirier 2c7d8e
            if e.errno == 2:
Benjamin Poirier 2c7d8e
                if write_enable:
Benjamin Poirier 2c7d8e
                    if not os.path.isdir(cache_dir):
Benjamin Poirier 2c7d8e
                        try:
Benjamin Poirier 2c7d8e
                            os.makedirs(cache_dir)
Benjamin Poirier 2c7d8e
                        except OSError as err:
Benjamin Poirier 2c7d8e
                            raise CError("Could not create cache directory:\n" +
Benjamin Poirier 2c7d8e
                                         str(err))
Benjamin Poirier 2c7d8e
                else:
Benjamin Poirier 2c7d8e
                    raise CAbsent
Benjamin Poirier 2c7d8e
            else:
Benjamin Poirier 2c7d8e
                raise
Benjamin Poirier 2c7d8e
Benjamin Poirier 15bd1c
        if write_enable:
Benjamin Poirier 15bd1c
            # In case there is already a database file of an unsupported format,
Benjamin Poirier 15bd1c
            # one would hope that with flag="n" a new database would be created
Benjamin Poirier 15bd1c
            # to overwrite the current one. Alas, that is not the case... :'(
Benjamin Poirier 15bd1c
            try:
Benjamin Poirier 15bd1c
                os.unlink(cache_path)
Benjamin Poirier 15bd1c
            except OSError as e:
Benjamin Poirier 15bd1c
                if e.errno != 2:
Benjamin Poirier 15bd1c
                    raise
Benjamin Poirier 15bd1c
Benjamin Poirier 2c7d8e
        flag_map = {False : "r", True : "n"}
Benjamin Poirier 15bd1c
        try:
Benjamin Poirier 15bd1c
            self.cache = shelve.open(cache_path, flag=flag_map[write_enable])
Benjamin Poirier 600ead
        except dbm.error:
Benjamin Poirier 600ead
            raise CUnsupported
Benjamin Poirier 2c7d8e
        self.closed = False
Benjamin Poirier 2c7d8e
        if write_enable:
Benjamin Poirier 2c7d8e
            self.cache["version"] = Cache.version
Benjamin Poirier 2c7d8e
Benjamin Poirier 2c7d8e
Benjamin Poirier 2c7d8e
    def __del__(self):
Benjamin Poirier 2c7d8e
        self.close()
Benjamin Poirier 2c7d8e
Benjamin Poirier 2c7d8e
Benjamin Poirier 2c7d8e
    def __enter__(self):
Benjamin Poirier 2c7d8e
        return self
Benjamin Poirier 2c7d8e
Benjamin Poirier 2c7d8e
Benjamin Poirier 2c7d8e
    def __exit__(self, *args):
Benjamin Poirier 2c7d8e
        self.close()
Benjamin Poirier 2c7d8e
Benjamin Poirier 2c7d8e
Benjamin Poirier 2c7d8e
    def close(self):
Benjamin Poirier 2c7d8e
        if not self.closed:
Benjamin Poirier 2c7d8e
            self.cache.close()
Benjamin Poirier 2c7d8e
        self.closed = True
Benjamin Poirier 2c7d8e
Benjamin Poirier 2c7d8e
Benjamin Poirier 2c7d8e
    def __getitem__(self, key):
Benjamin Poirier 2c7d8e
        """
Benjamin Poirier 2c7d8e
        Supported keys:
Benjamin Poirier 2c7d8e
            "version"
Benjamin Poirier 2c7d8e
                int
Benjamin Poirier 2c7d8e
            "history"
Benjamin Poirier 2c7d8e
                OrderedDict((Head, sha1) : history)
Benjamin Poirier 2c7d8e
        """
Benjamin Poirier 2c7d8e
        if self.closed:
Benjamin Poirier 2c7d8e
            raise ValueError
Benjamin Poirier 2c7d8e
Benjamin Poirier 15bd1c
        try:
Benjamin Poirier 15bd1c
            version = self.cache["version"]
Benjamin Poirier 15bd1c
        except KeyError:
Benjamin Poirier 15bd1c
            key_error = True
Benjamin Poirier 15bd1c
        except ValueError as err:
Benjamin Poirier 15bd1c
            raise CUnsupported(str(err))
Benjamin Poirier 15bd1c
        else:
Benjamin Poirier 15bd1c
            key_error = False
Benjamin Poirier 15bd1c
Benjamin Poirier 2c7d8e
        if key == "version":
Benjamin Poirier 15bd1c
            if key_error:
Benjamin Poirier 2c7d8e
                raise CKeyError
Benjamin Poirier 15bd1c
            else:
Benjamin Poirier 15bd1c
                return version
Benjamin Poirier 2c7d8e
        elif key == "history":
Benjamin Poirier 15bd1c
            if key_error or version != Cache.version:
Benjamin Poirier 2c7d8e
                raise CUnsupported
Benjamin Poirier 2c7d8e
Benjamin Poirier db0e54
            try:
Benjamin Poirier 2c7d8e
                cache_history = self.cache["history"]
Benjamin Poirier 2c7d8e
            except KeyError:
Benjamin Poirier 2c7d8e
                raise CInconsistent
Benjamin Poirier 2c7d8e
Benjamin Poirier bcc8a7
            # This detailed check may be needed if an older git-sort (which
Benjamin Poirier bcc8a7
            # didn't set a cache version) modified the cache.
Benjamin Poirier 600ead
            if (not isinstance(cache_history, list) or
Thomas Zimmermann c5e56e
                len(cache_history) < 1 or
Benjamin Poirier bcc8a7
                len(cache_history[0]) != 4 or
Benjamin Poirier 600ead
                not isinstance(cache_history[0][3], dict)):
Benjamin Poirier bcc8a7
                raise CInconsistent
Benjamin Poirier bcc8a7
Benjamin Poirier 2c7d8e
            return collections.OrderedDict([
Benjamin Poirier 2c7d8e
                (
Benjamin Poirier 2c7d8e
                    (Head(RepoURL(e[0]), e[1]), e[2],),
Benjamin Poirier 2c7d8e
                    e[3],
Benjamin Poirier 2c7d8e
                ) for e in cache_history])
Benjamin Poirier 2c7d8e
        else:
Benjamin Poirier 2c7d8e
            raise KeyError
Benjamin Poirier 76bf0c
Benjamin Poirier 76bf0c
Benjamin Poirier 2c7d8e
    def __setitem__(self, key, value):
Benjamin Poirier 76bf0c
        """
Benjamin Poirier 2c7d8e
        Supported keys:
Benjamin Poirier 2c7d8e
            "history"
Benjamin Poirier 2c7d8e
                OrderedDict((Head, sha1) : history)
Benjamin Poirier 76bf0c
        """
Benjamin Poirier 2c7d8e
        if self.closed or not self.write_enable:
Benjamin Poirier 2c7d8e
            raise ValueError
Benjamin Poirier 2c7d8e
Benjamin Poirier 2c7d8e
        if key == "history":
Benjamin Poirier 2c7d8e
            self.cache["history"] = [(
Benjamin Poirier 2c7d8e
                repr(desc[0].repo_url), desc[0].rev, desc[1], log,
Benjamin Poirier 2c7d8e
            ) for desc, log in value.items()]
Benjamin Poirier 2c7d8e
        else:
Benjamin Poirier 2c7d8e
            raise KeyError
Benjamin Poirier 897bbc
Benjamin Poirier 897bbc
Benjamin Poirier 395993
@functools.total_ordering
Benjamin Poirier 395993
class IndexedCommit(object):
Benjamin Poirier 395993
    def __init__(self, head, index):
Benjamin Poirier 395993
        self.head = head
Benjamin Poirier 395993
        self.index = index
Benjamin Poirier 395993
Benjamin Poirier 395993
Benjamin Poirier 395993
    def _is_valid_operand(self, other):
Benjamin Poirier 395993
        return hasattr(other, "head") and hasattr(other, "index")
Benjamin Poirier 395993
Benjamin Poirier 395993
Benjamin Poirier 395993
    def __eq__(self, other):
Benjamin Poirier 395993
        if not self._is_valid_operand(other):
Benjamin Poirier 395993
            return NotImplemented
Benjamin Poirier 395993
        return (self.head == other.head and self.index == other.index)
Benjamin Poirier 395993
Benjamin Poirier 395993
Benjamin Poirier 395993
    def __lt__(self, other):
Benjamin Poirier 395993
        if not self._is_valid_operand(other):
Benjamin Poirier 395993
            return NotImplemented
Benjamin Poirier 395993
        if self.head == other.head:
Benjamin Poirier 395993
            return self.index < other.index
Benjamin Poirier 395993
        else:
Benjamin Poirier 395993
            return self.head < other.head
Benjamin Poirier 395993
Benjamin Poirier 395993
Benjamin Poirier 395993
    def __hash__(self):
Benjamin Poirier 395993
        return hash((self.head, self.index,))
Benjamin Poirier 395993
Benjamin Poirier 395993
Benjamin Poirier 395993
    def __repr__(self):
Benjamin Poirier 395993
        return "%s %d" % (repr(self.head), self.index,)
Benjamin Poirier 395993
Benjamin Poirier 395993
Benjamin Poirier 2c7d8e
class SortIndex(object):
Benjamin Poirier 2c7d8e
    version_match = re.compile("refs/tags/v(2\.6\.\d+|\d\.\d+)(-rc\d+)?$")
Benjamin Poirier 897bbc
Benjamin Poirier 897bbc
Benjamin Poirier 2c7d8e
    def __init__(self, repo):
Benjamin Poirier 2c7d8e
        self.repo = repo
Benjamin Poirier 2c7d8e
        needs_rebuild = False
Benjamin Poirier 76bf0c
        try:
Benjamin Poirier 2c7d8e
            with Cache() as cache:
Benjamin Poirier 2c7d8e
                try:
Benjamin Poirier 2c7d8e
                    history = cache["history"]
Benjamin Poirier 2c7d8e
                except CNeedsRebuild:
Benjamin Poirier 2c7d8e
                    needs_rebuild = True
Benjamin Poirier 15bd1c
        except CNeedsRebuild:
Benjamin Poirier 2c7d8e
            needs_rebuild = True
Benjamin Poirier 2c7d8e
        except CError as err:
Benjamin Poirier 2c7d8e
            print("Error: %s" % (err,), file=sys.stderr)
Benjamin Poirier 2c7d8e
            sys.exit(1)
Benjamin Poirier 76bf0c
Benjamin Poirier 2c7d8e
        try:
Benjamin Poirier 2c7d8e
            repo_heads = get_heads(repo)
Benjamin Poirier 2c7d8e
        except GSError as err:
Benjamin Poirier 2c7d8e
            print("Error: %s" % (err,), file=sys.stderr)
Benjamin Poirier 2c7d8e
            sys.exit(1)
Benjamin Poirier 76bf0c
Benjamin Poirier 600ead
        if needs_rebuild or list(history.keys()) != list(repo_heads.items()):
Benjamin Poirier 2c7d8e
            try:
Benjamin Poirier 2c7d8e
                history = get_history(repo, repo_heads)
Benjamin Poirier 2c7d8e
            except GSError as err:
Benjamin Poirier 2c7d8e
                print("Error: %s" % (err,), file=sys.stderr)
Benjamin Poirier 2c7d8e
                sys.exit(1)
Benjamin Poirier 2c7d8e
            try:
Benjamin Poirier 2c7d8e
                with Cache(write_enable=True) as cache:
Benjamin Poirier 2c7d8e
                    cache["history"] = collections.OrderedDict(
Benjamin Poirier 2c7d8e
                        [((head, repo_heads[head],), log,)
Benjamin Poirier 2c7d8e
                         for head, log in history.items()])
Benjamin Poirier 2c7d8e
            except CError as err:
Benjamin Poirier 2c7d8e
                print("Error: %s" % (err,), file=sys.stderr)
Benjamin Poirier 2c7d8e
                sys.exit(1)
Benjamin Poirier 2c7d8e
            self.history = history
Benjamin Poirier 76bf0c
        else:
Benjamin Poirier 2c7d8e
            # no more need for the head sha1
Benjamin Poirier 2c7d8e
            self.history = collections.OrderedDict(
Benjamin Poirier 2c7d8e
                    [(key[0], log,) for key, log in history.items()])
Benjamin Poirier 2c7d8e
        self.version_indexes = None
Benjamin Poirier 6a71e7
        self.repo_heads = repo_heads
Benjamin Poirier 0aaea3
Benjamin Poirier 0aaea3
Benjamin Poirier 544f5d
    def lookup(self, commit):
Benjamin Poirier 544f5d
        for head, log in self.history.items():
Benjamin Poirier 544f5d
            try:
Benjamin Poirier 544f5d
                index = log[commit]
Benjamin Poirier 544f5d
            except KeyError:
Benjamin Poirier 544f5d
                continue
Benjamin Poirier 544f5d
            else:
Benjamin Poirier 395993
                return IndexedCommit(head, index)
Benjamin Poirier 544f5d
Benjamin Poirier 544f5d
        raise GSKeyError
Benjamin Poirier 544f5d
Benjamin Poirier 544f5d
Benjamin Poirier 0abe03
    def describe(self, index):
Benjamin Poirier 0abe03
        """
Benjamin Poirier 0abe03
        index must come from the mainline head (remotes[0]).
Benjamin Poirier 0abe03
        """
Benjamin Poirier 0abe03
        if self.version_indexes is None:
Benjamin Poirier 0abe03
            history = self.history[remotes[0]]
Benjamin Poirier cf4f00
            # Remove "refs/tags/"
Benjamin Poirier cf4f00
            # Mainline release tags are annotated tag objects attached to a
Benjamin Poirier cf4f00
            # commit object; do not consider other kinds of tags.
Benjamin Poirier cf4f00
            objects = [(obj_tag.get_object(), tag,)
Benjamin Poirier cf4f00
                       for obj_tag, tag in [
Benjamin Poirier cf4f00
                           (self.repo.revparse_single(tag), tag[10:],)
Benjamin Poirier cf4f00
                           for tag in self.repo.listall_references()
Benjamin Poirier cf4f00
                           if self.version_match.match(tag)
Benjamin Poirier cf4f00
                       ] if obj_tag.type == pygit2.GIT_OBJ_TAG]
Benjamin Poirier 0abe03
            revs = [(history[str(obj.id)], tag,)
Benjamin Poirier 0abe03
                    for obj, tag in objects
Benjamin Poirier 0abe03
                    if obj.type == pygit2.GIT_OBJ_COMMIT]
Benjamin Poirier 0abe03
            revs.sort(key=operator.itemgetter(0))
Benjamin Poirier 600ead
            self.version_indexes = list(zip(*revs))
Benjamin Poirier 0abe03
Benjamin Poirier 535548
        if not self.version_indexes:
Benjamin Poirier 535548
            raise GSError("Cannot describe commit, did not find any mainline "
Benjamin Poirier 535548
                          "release tags in repository.")
Benjamin Poirier 535548
Benjamin Poirier 0abe03
        indexes, tags = self.version_indexes
Benjamin Poirier 0abe03
        i = bisect.bisect_left(indexes, index)
Benjamin Poirier 0abe03
        if i == len(tags):
Benjamin Poirier 0abe03
            # not yet part of a tagged release
Michal Suchanek 7dce3d
            m = re.search("v([0-9]+)\.([0-9]+)(|-rc([0-9]+))$", tags[-1])
Michal Suchanek 7dce3d
            if m:
Michal Suchanek 7dce3d
                # Post-release commit with no rc, it'll be rc1
Michal Suchanek 7dce3d
                if m.group(3) == "":
Michal Suchanek 7dce3d
                    nexttag = "v%s.%d-rc1" % (m.group(1), int(m.group(2)) + 1)
Michal Suchanek 7dce3d
                else:
Michal Suchanek 7dce3d
                    nexttag = "v%s.%d or v%s.%s-rc%d (next release)" % \
Michal Kubecek 4f0865
                              (m.group(1), int(m.group(2)), m.group(1),
Michal Suchanek 7dce3d
                               m.group(2), int(m.group(4)) + 1)
Michal Suchanek 7dce3d
            return nexttag
Benjamin Poirier 0abe03
        else:
Benjamin Poirier 0abe03
            return tags[i]
Benjamin Poirier 0abe03
Benjamin Poirier 0abe03
Benjamin Poirier 0aaea3
if __name__ == "__main__":
Benjamin Poirier 0aaea3
    parser = argparse.ArgumentParser(
Benjamin Poirier 0aaea3
        description="Sort input lines according to the upstream order of "
Benjamin Poirier 0aaea3
        "commits that each line represents, with the first word on the line "
Benjamin Poirier 0aaea3
        "taken to be a commit id.")
Benjamin Poirier 0aaea3
    parser.add_argument("-d", "--dump-heads", action="store_true",
Benjamin Poirier 0aaea3
                        help="Print the branch heads used for sorting "
Benjamin Poirier 0aaea3
                        "(debugging).")
Benjamin Poirier 0aaea3
    args = parser.parse_args()
Benjamin Poirier 0aaea3
Benjamin Poirier 0aaea3
    try:
Benjamin Poirier 0aaea3
        path = os.environ["GIT_DIR"]
Benjamin Poirier 0aaea3
    except KeyError:
Benjamin Poirier 4f8279
        try:
Benjamin Poirier 9ae282
            # depending on the pygit2 version, discover_repository() will either
Benjamin Poirier 9ae282
            # raise KeyError or return None if a repository is not found.
Benjamin Poirier 4f8279
            path = pygit2.discover_repository(os.getcwd())
Benjamin Poirier 4f8279
        except KeyError:
Benjamin Poirier 9ae282
            path = None
Benjamin Poirier 9ae282
    if path is None:
Benjamin Poirier 9ae282
        print("Error: Not a git repository", file=sys.stderr)
Benjamin Poirier 9ae282
        sys.exit(1)
Benjamin Poirier 0aaea3
    repo = pygit2.Repository(path)
Benjamin Poirier 0aaea3
Benjamin Poirier 0aaea3
    if args.dump_heads:
Benjamin Poirier 2c7d8e
        needs_rebuild = False
Benjamin Poirier 0aaea3
        try:
Benjamin Poirier 2c7d8e
            with Cache() as cache:
Benjamin Poirier 2c7d8e
                try:
Benjamin Poirier 2c7d8e
                    print("Cached heads (version %d):" % cache["version"])
Benjamin Poirier 2c7d8e
                except CKeyError:
Benjamin Poirier 2c7d8e
                    print("No usable cache")
Benjamin Poirier 2c7d8e
                    needs_rebuild = True
Benjamin Poirier 2c7d8e
                else:
Benjamin Poirier 2c7d8e
                    try:
Benjamin Poirier 2c7d8e
                        history = cache["history"]
Benjamin Poirier 2c7d8e
                    except CUnsupported:
Benjamin Poirier 2c7d8e
                        print("Unsupported cache version")
Benjamin Poirier 2c7d8e
                        needs_rebuild = True
Benjamin Poirier 2c7d8e
                    except CInconsistent:
Benjamin Poirier 2c7d8e
                        print("Inconsistent cache content")
Benjamin Poirier 2c7d8e
                        needs_rebuild = True
Benjamin Poirier 2c7d8e
                    else:
Benjamin Poirier 600ead
                        pprint.pprint(list(history.keys()))
Benjamin Poirier 600ead
        except CAbsent:
Benjamin Poirier 4f1bbb
            print("No usable cache")
Benjamin Poirier 2c7d8e
            needs_rebuild = True
Benjamin Poirier 600ead
        except CNeedsRebuild:
Benjamin Poirier 600ead
            needs_rebuild = True
Benjamin Poirier 2c7d8e
        except CError as err:
Benjamin Poirier 2c7d8e
            print("Error: %s" % (err,), file=sys.stderr)
Benjamin Poirier 2c7d8e
            sys.exit(1)
Benjamin Poirier 2c7d8e
Benjamin Poirier 2c7d8e
        try:
Benjamin Poirier 2c7d8e
            repo_heads = get_heads(repo)
Benjamin Poirier 2c7d8e
        except GSError as err:
Benjamin Poirier 2c7d8e
            print("Error: %s" % (err,), file=sys.stderr)
Benjamin Poirier 2c7d8e
            sys.exit(1)
Benjamin Poirier 600ead
        if not needs_rebuild and list(history.keys()) != list(repo_heads.items()):
Benjamin Poirier 2c7d8e
            needs_rebuild = True
Benjamin Poirier 2c7d8e
        print("Current heads (version %d):" % Cache.version)
Benjamin Poirier 600ead
        pprint.pprint(list(repo_heads.items()))
Benjamin Poirier 2c7d8e
        if needs_rebuild:
Benjamin Poirier 2c7d8e
            action = "Will"
Benjamin Poirier 4f1bbb
        else:
Benjamin Poirier 0aaea3
            action = "Will not"
Benjamin Poirier 0aaea3
        print("%s rebuild history" % (action,))
Benjamin Poirier 0aaea3
        sys.exit(0)
Benjamin Poirier 0aaea3
Benjamin Poirier 2c7d8e
    index = SortIndex(repo)
Benjamin Poirier 395993
    dest = {}
Benjamin Poirier 395993
    oot = []
Benjamin Poirier 0aaea3
    num = 0
Benjamin Poirier 0aaea3
    for line in sys.stdin.readlines():
Benjamin Poirier 0aaea3
        num = num + 1
Benjamin Poirier 51f0b8
        tokens = line.strip().split(None, 1)
Benjamin Poirier 51f0b8
        if not tokens:
Benjamin Poirier 51f0b8
            continue
Benjamin Poirier 0aaea3
        try:
Benjamin Poirier 51f0b8
            commit = repo.revparse_single(tokens[0])
Benjamin Poirier 0aaea3
        except ValueError:
Benjamin Poirier 0aaea3
            print("Error: did not find a commit hash on line %d:\n%s" %
Benjamin Poirier 0aaea3
                  (num, line.strip(),), file=sys.stderr)
Benjamin Poirier 0aaea3
            sys.exit(1)
Benjamin Poirier 0aaea3
        except KeyError:
Benjamin Poirier 0aaea3
            print("Error: commit hash on line %d not found in the repository:\n%s" %
Benjamin Poirier 0aaea3
                  (num, line.strip(),), file=sys.stderr)
Benjamin Poirier 0aaea3
            sys.exit(1)
Benjamin Poirier 0aaea3
        h = str(commit.id)
Benjamin Poirier 395993
        if h in dest:
Benjamin Poirier 395993
            dest[h][1].append(line)
Benjamin Poirier 0aaea3
        else:
Benjamin Poirier 395993
            try:
Benjamin Poirier 395993
                ic = index.lookup(h)
Benjamin Poirier 395993
            except GSKeyError:
Benjamin Poirier 395993
                oot.append(line)
Benjamin Poirier 395993
            else:
Benjamin Poirier 395993
                dest[h] = (ic, [line],)
Benjamin Poirier 0aaea3
Benjamin Poirier e8d72d
    print("".join([line
Benjamin Poirier 395993
                   for ic, lines in sorted(dest.values(),
Benjamin Poirier 395993
                                           key=operator.itemgetter(0))
Benjamin Poirier 395993
                       for line in lines
Benjamin Poirier e8d72d
                  ]), end="")
Benjamin Poirier 0aaea3
Benjamin Poirier 395993
    if oot:
Benjamin Poirier 0aaea3
        print("Error: the following entries were not found in the indexed heads:",
Benjamin Poirier 0aaea3
              file=sys.stderr)
Benjamin Poirier 395993
        print("".join(oot), end="")
Benjamin Poirier 0aaea3
        sys.exit(1)