Blame pagure/lib/repo.py

Pierre-Yves Chibon 6aab3c
# -*- coding: utf-8 -*-
Pierre-Yves Chibon 6aab3c
Pierre-Yves Chibon 6aab3c
"""
Pierre-Yves Chibon a87742
 (c) 2015-2019 - Copyright Red Hat Inc
Pierre-Yves Chibon 6aab3c
Pierre-Yves Chibon 6aab3c
 Authors:
Pierre-Yves Chibon 6aab3c
   Pierre-Yves Chibon <pingou@pingoured.fr></pingou@pingoured.fr>
Pierre-Yves Chibon 6aab3c
Pierre-Yves Chibon 6aab3c
"""
Pierre-Yves Chibon 67d1cc
from __future__ import print_function, unicode_literals, absolute_import
Pierre-Yves Chibon 6aab3c
Pierre-Yves Chibon 4635b5
import logging
Pierre-Yves Chibon a87742
import subprocess
Pierre-Yves Chibon 6aab3c
Pierre-Yves Chibon 6aab3c
import pygit2
Pierre-Yves Chibon 6aab3c
Pierre-Yves Chibon bcd30b
import pagure
Pierre-Yves Chibon a4c07e
import pagure.exceptions
Pierre-Yves Chibon fd1aab
Pierre-Yves Chibon fd1aab
Pierre-Yves Chibon 4635b5
_log = logging.getLogger(__name__)
Pierre-Yves Chibon 4635b5
Pierre-Yves Chibon 4635b5
Pierre-Yves Chibon fd1aab
def get_pygit2_version():
Pierre-Yves Chibon 9c2953
    """ Return pygit2 version as a tuple of integers.
Pierre-Yves Chibon fd1aab
    This is needed for correct version comparison.
Pierre-Yves Chibon 9c2953
    """
Pierre-Yves Chibon 9c2953
    return tuple([int(i) for i in pygit2.__version__.split(".")])
Pierre-Yves Chibon a4c07e
Pierre-Yves Chibon 6aab3c
Pierre-Yves Chibon a87742
def run_command(command):
Pierre-Yves Chibon a87742
    _log.info("Running command: %s", command)
Pierre-Yves Chibon a87742
    try:
Pierre-Yves Chibon a87742
        out = subprocess.check_output(command, stderr=subprocess.STDOUT)
Pierre-Yves Chibon a87742
        _log.info("   command ran successfully")
Pierre-Yves Chibon a87742
        _log.debug("Output: %s" % out)
Pierre-Yves Chibon a87742
    except subprocess.CalledProcessError as err:
Pierre-Yves Chibon a87742
        _log.debug(
Pierre-Yves Chibon a87742
            "Command FAILED: {cmd} returned code {code} with the "
Pierre-Yves Chibon a87742
            "following output: {output}".format(
Pierre-Yves Chibon a87742
                cmd=err.cmd, code=err.returncode, output=err.output
Pierre-Yves Chibon a87742
            )
Pierre-Yves Chibon a87742
        )
Pierre-Yves Chibon a87742
        raise pagure.exceptions.PagureException(
Pierre-Yves Chibon a87742
            "Did not manage to rebase this pull-request"
Pierre-Yves Chibon a87742
        )
Pierre-Yves Chibon a87742
Pierre-Yves Chibon a87742
Pierre-Yves Chibon 6aab3c
class PagureRepo(pygit2.Repository):
Pierre-Yves Chibon 6aab3c
    """ An utility class allowing to go around pygit2's inability to be
Pierre-Yves Chibon 6aab3c
    stable.
Pierre-Yves Chibon 6aab3c
Pierre-Yves Chibon 6aab3c
    """
Pierre-Yves Chibon 6aab3c
Pierre-Yves Chibon 6aab3c
    @staticmethod
Pierre-Yves Chibon a87742
    def clone(path_from, path_to, checkout_branch=None, bare=False):
Pierre-Yves Chibon a87742
        """ Clone the git repo at the specified path to the specified location.
Pierre-Yves Chibon a87742
Pierre-Yves Chibon a87742
        This method is meant to replace pygit2.clone_repository which for us
Pierre-Yves Chibon a87742
        leaks file descriptors on large project leading to "Too many open files
Pierre-Yves Chibon a87742
        error" which then prevent some tasks from completing.
Pierre-Yves Chibon a87742
Pierre-Yves Chibon a87742
        :arg path_from: the path or url of the git repository to clone
Pierre-Yves Chibon a87742
        :type path_from: str
Pierre-Yves Chibon a87742
        :arg path_to: the path where the git repository should be cloned
Pierre-Yves Chibon a87742
        :type path_to: str
Pierre-Yves Chibon a87742
        :
Pierre-Yves Chibon a87742
        """
Pierre-Yves Chibon a87742
        cmd = ["git", "clone", path_from, path_to]
Pierre-Yves Chibon a87742
        if checkout_branch:
Pierre-Yves Chibon a87742
            cmd.extend(["-b", checkout_branch])
Pierre-Yves Chibon a87742
        if bare:
Pierre-Yves Chibon a87742
            cmd.append("--bare")
Pierre-Yves Chibon a87742
Pierre-Yves Chibon a87742
        run_command(cmd)
Pierre-Yves Chibon a87742
Pierre-Yves Chibon a87742
    @staticmethod
Pierre-Yves Chibon 6aab3c
    def push(remote, refname):
Pierre-Yves Chibon 6aab3c
        """ Push the given reference to the specified remote. """
Pierre-Yves Chibon 76f4a8
        pygit2_version = get_pygit2_version()
Daniel Mach e44e00
        if pygit2_version >= (0, 22):
Pierre-Yves Chibon 6aab3c
            remote.push([refname])
Pierre-Yves Chibon 6aab3c
        else:
Pierre-Yves Chibon 6aab3c
            remote.push(refname)
Pierre-Yves Chibon a4c07e
Pierre-Yves Chibon 9c2953
    def pull(self, remote_name="origin", branch="master", force=False):
Pierre-Yves Chibon 9c2953
        """ pull changes for the specified remote (defaults to origin).
Pierre-Yves Chibon a4c07e
Pierre-Yves Chibon a4c07e
        Code from MichaelBoselowitz at:
Pierre-Yves Chibon a4c07e
        https://github.com/MichaelBoselowitz/pygit2-examples/blob/
Pierre-Yves Chibon a4c07e
            68e889e50a592d30ab4105a2e7b9f28fac7324c8/examples.py#L58
Pierre-Yves Chibon a4c07e
        licensed under the MIT license.
Pierre-Yves Chibon 9c2953
        """
Pierre-Yves Chibon a4c07e
Pierre-Yves Chibon a4c07e
        for remote in self.remotes:
Pierre-Yves Chibon a4c07e
            if remote.name == remote_name:
Pierre-Yves Chibon a4c07e
                remote.fetch()
Pierre-Yves Chibon a4c07e
                remote_master_id = self.lookup_reference(
Pierre-Yves Chibon 9c2953
                    "refs/remotes/origin/%s" % branch
Pierre-Yves Chibon 9c2953
                ).target
Pierre-Yves Chibon c4c8bc
Pierre-Yves Chibon c4c8bc
                if force:
Pierre-Yves Chibon c4c8bc
                    repo_branch = self.lookup_reference(
Pierre-Yves Chibon 9c2953
                        "refs/heads/%s" % branch
Pierre-Yves Chibon 9c2953
                    )
Pierre-Yves Chibon c4c8bc
                    repo_branch.set_target(remote_master_id)
Pierre-Yves Chibon c4c8bc
Pierre-Yves Chibon a4c07e
                merge_result, _ = self.merge_analysis(remote_master_id)
Pierre-Yves Chibon a4c07e
                # Up to date, do nothing
Pierre-Yves Chibon a4c07e
                if merge_result & pygit2.GIT_MERGE_ANALYSIS_UP_TO_DATE:
Pierre-Yves Chibon a4c07e
                    return
Pierre-Yves Chibon a4c07e
                # We can just fastforward
Pierre-Yves Chibon a4c07e
                elif merge_result & pygit2.GIT_MERGE_ANALYSIS_FASTFORWARD:
Pierre-Yves Chibon a4c07e
                    self.checkout_tree(self.get(remote_master_id))
Pierre-Yves Chibon a4c07e
                    master_ref = self.lookup_reference(
Pierre-Yves Chibon 9c2953
                        "refs/heads/%s" % branch
Pierre-Yves Chibon 9c2953
                    )
Pierre-Yves Chibon a4c07e
                    master_ref.set_target(remote_master_id)
Pierre-Yves Chibon a4c07e
                    self.head.set_target(remote_master_id)
Pierre-Yves Chibon a4c07e
                elif merge_result & pygit2.GIT_MERGE_ANALYSIS_NORMAL:
Pierre-Yves Chibon 21522b
                    raise pagure.exceptions.GitConflictsException(
Pierre-Yves Chibon 9c2953
                        "Pulling remote changes leads to a conflict"
Pierre-Yves Chibon 9c2953
                    )
Pierre-Yves Chibon a4c07e
                else:
Pierre-Yves Chibon 4635b5
                    _log.debug(
Pierre-Yves Chibon 9c2953
                        "Unexpected merge result: %s"
Pierre-Yves Chibon 9c2953
                        % (pygit2.GIT_MERGE_ANALYSIS_NORMAL)
Pierre-Yves Chibon 9c2953
                    )
Pierre-Yves Chibon 9c2953
                    raise AssertionError("Unknown merge analysis result")