123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170 |
- # -*- 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)
|