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 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163
|
# -*- coding: utf-8 -*-
import collections
import datetime
import logging
import os
import re
import subprocess
import tarfile
import tempfile
GitRef = collections.namedtuple(
"VersionRef",
["name", "commit", "source", "is_remote", "refname", "creatordate",],
)
logger = logging.getLogger(__name__)
def get_toplevel_path(cwd=None):
cmd = (
"git",
"rev-parse",
"--show-toplevel",
)
output = subprocess.check_output(cmd, cwd=cwd).decode()
return output.rstrip("\n")
def get_all_refs(gitroot):
cmd = (
"git",
"for-each-ref",
"--format",
"%(objectname)\t%(refname)\t%(creatordate:iso)",
"refs",
)
output = subprocess.check_output(cmd, cwd=gitroot).decode()
for line in output.splitlines():
is_remote = False
fields = line.strip().split("\t")
if len(fields) != 3:
continue
commit = fields[0]
refname = fields[1]
creatordate = datetime.datetime.strptime(
fields[2], "%Y-%m-%d %H:%M:%S %z"
)
# Parse refname
matchobj = re.match(
r"^refs/(heads|tags|remotes/[^/]+)/(\S+)$", refname
)
if not matchobj:
continue
source = matchobj.group(1)
name = matchobj.group(2)
if source.startswith("remotes/"):
is_remote = True
yield GitRef(name, commit, source, is_remote, refname, creatordate)
def get_refs(
gitroot, tag_whitelist, branch_whitelist, remote_whitelist, files=()
):
for ref in get_all_refs(gitroot):
if ref.source == "tags":
if tag_whitelist is None or not re.match(tag_whitelist, ref.name):
logger.debug(
"Skipping '%s' because tag '%s' doesn't match the "
"whitelist pattern",
ref.refname,
ref.name,
)
continue
elif ref.source == "heads":
if branch_whitelist is None or not re.match(
branch_whitelist, ref.name
):
logger.debug(
"Skipping '%s' because branch '%s' doesn't match the "
"whitelist pattern",
ref.refname,
ref.name,
)
continue
elif ref.is_remote and remote_whitelist is not None:
remote_name = ref.source.partition("/")[2]
if not re.match(remote_whitelist, remote_name):
logger.debug(
"Skipping '%s' because remote '%s' doesn't match the "
"whitelist pattern",
ref.refname,
remote_name,
)
continue
if branch_whitelist is None or not re.match(
branch_whitelist, ref.name
):
logger.debug(
"Skipping '%s' because branch '%s' doesn't match the "
"whitelist pattern",
ref.refname,
ref.name,
)
continue
else:
logger.debug(
"Skipping '%s' because its not a branch or tag", ref.refname
)
continue
missing_files = [
filename
for filename in files
if filename != "."
and not file_exists(gitroot, ref.refname, filename)
]
if missing_files:
logger.debug(
"Skipping '%s' because it lacks required files: %r",
ref.refname,
missing_files,
)
continue
yield ref
def file_exists(gitroot, refname, filename):
if os.sep != "/":
# Git requires / path sep, make sure we use that
filename = filename.replace(os.sep, "/")
cmd = (
"git",
"cat-file",
"-e",
"{}:{}".format(refname, filename),
)
proc = subprocess.run(
cmd, cwd=gitroot, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL
)
return proc.returncode == 0
def copy_tree(gitroot, src, dst, reference, sourcepath="."):
with tempfile.SpooledTemporaryFile() as fp:
cmd = (
"git",
"archive",
"--format",
"tar",
reference.commit,
"--",
sourcepath,
)
subprocess.check_call(cmd, cwd=gitroot, stdout=fp)
fp.seek(0)
with tarfile.TarFile(fileobj=fp) as tarfp:
tarfp.extractall(dst)
|