summaryrefslogtreecommitdiffstats
path: root/test/lib/ansible_test/_internal/git.py
blob: acc39f3f69f1101b7e3a11fe4bf796715ca749a9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
"""Wrapper around git command-line tools."""
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type

import re

from . import types as t

from .util import (
    SubprocessError,
    raw_command,
)


class Git:
    """Wrapper around git command-line tools."""
    def __init__(self, root=None):  # type: (t.Optional[str]) -> None
        self.git = 'git'
        self.root = root

    def get_diff(self, args, git_options=None):
        """
        :type args: list[str]
        :type git_options: list[str] | None
        :rtype: list[str]
        """
        cmd = ['diff'] + args
        if git_options is None:
            git_options = ['-c', 'core.quotePath=']
        return self.run_git_split(git_options + cmd, '\n', str_errors='replace')

    def get_diff_names(self, args):
        """
        :type args: list[str]
        :rtype: list[str]
        """
        cmd = ['diff', '--name-only', '--no-renames', '-z'] + args
        return self.run_git_split(cmd, '\0')

    def get_submodule_paths(self):  # type: () -> t.List[str]
        """Return a list of submodule paths recursively."""
        cmd = ['submodule', 'status', '--recursive']
        output = self.run_git_split(cmd, '\n')
        submodule_paths = [re.search(r'^.[0-9a-f]+ (?P<path>[^ ]+)', line).group('path') for line in output]

        # status is returned for all submodules in the current git repository relative to the current directory
        # when the current directory is not the root of the git repository this can yield relative paths which are not below the current directory
        # this can occur when multiple collections are in a git repo and some collections are submodules when others are not
        # specifying "." as the path to enumerate would limit results to the current directory, but can cause the git command to fail with the error:
        #   error: pathspec '.' did not match any file(s) known to git
        # this can occur when the current directory contains no files tracked by git
        # instead we'll filter out the relative paths, since we're only interested in those at or below the current directory
        submodule_paths = [path for path in submodule_paths if not path.startswith('../')]

        return submodule_paths

    def get_file_names(self, args):
        """
        :type args: list[str]
        :rtype: list[str]
        """
        cmd = ['ls-files', '-z'] + args
        return self.run_git_split(cmd, '\0')

    def get_branches(self):
        """
        :rtype: list[str]
        """
        cmd = ['for-each-ref', 'refs/heads/', '--format', '%(refname:strip=2)']
        return self.run_git_split(cmd)

    def get_branch(self):
        """
        :rtype: str
        """
        cmd = ['symbolic-ref', '--short', 'HEAD']
        return self.run_git(cmd).strip()

    def get_rev_list(self, commits=None, max_count=None):
        """
        :type commits: list[str] | None
        :type max_count: int | None
        :rtype: list[str]
        """
        cmd = ['rev-list']

        if commits:
            cmd += commits
        else:
            cmd += ['HEAD']

        if max_count:
            cmd += ['--max-count', '%s' % max_count]

        return self.run_git_split(cmd)

    def get_branch_fork_point(self, branch):
        """
        :type branch: str
        :rtype: str
        """
        cmd = ['merge-base', '--fork-point', branch]
        return self.run_git(cmd).strip()

    def is_valid_ref(self, ref):
        """
        :type ref: str
        :rtype: bool
        """
        cmd = ['show', ref]
        try:
            self.run_git(cmd, str_errors='replace')
            return True
        except SubprocessError:
            return False

    def run_git_split(self, cmd, separator=None, str_errors='strict'):
        """
        :type cmd: list[str]
        :type separator: str | None
        :type str_errors: str
        :rtype: list[str]
        """
        output = self.run_git(cmd, str_errors=str_errors).strip(separator)

        if not output:
            return []

        return output.split(separator)

    def run_git(self, cmd, str_errors='strict'):
        """
        :type cmd: list[str]
        :type str_errors: str
        :rtype: str
        """
        return raw_command([self.git] + cmd, cwd=self.root, capture=True, str_errors=str_errors)[0]