2021-01-02 13:30:24 +01:00
|
|
|
from giteapc.config import REPO_FOLDER
|
|
|
|
from giteapc.util import *
|
|
|
|
import subprocess
|
|
|
|
from subprocess import PIPE
|
|
|
|
import os.path
|
|
|
|
import shutil
|
|
|
|
import glob
|
2021-01-04 12:06:58 +01:00
|
|
|
import re
|
2021-01-02 13:30:24 +01:00
|
|
|
|
|
|
|
class RemoteRepo:
|
|
|
|
# Create a remote repo from the JSON object returned by Gitea
|
|
|
|
def __init__(self, j):
|
|
|
|
self.j = j
|
|
|
|
self.remote = True
|
|
|
|
|
|
|
|
@property
|
|
|
|
def name(self):
|
|
|
|
return self.j["name"]
|
|
|
|
|
|
|
|
@property
|
|
|
|
def owner(self):
|
|
|
|
return self.j["owner"]["username"]
|
|
|
|
|
|
|
|
@property
|
|
|
|
def fullname(self):
|
|
|
|
return self.j["full_name"]
|
|
|
|
|
|
|
|
@property
|
|
|
|
def description(self):
|
|
|
|
return self.j["description"]
|
|
|
|
|
|
|
|
@property
|
|
|
|
def parent(self):
|
|
|
|
p = self.j["parent"]
|
|
|
|
return RemoteRepo(p) if p is not None else None
|
|
|
|
|
|
|
|
@property
|
|
|
|
def url(self):
|
|
|
|
return f"/repos/{self.owner}/{self.name}"
|
|
|
|
|
|
|
|
def clone_url(self, protocol):
|
|
|
|
if protocol == "ssh":
|
|
|
|
return self.j["ssh_url"]
|
|
|
|
else:
|
|
|
|
return self.j["clone_url"]
|
|
|
|
|
|
|
|
|
|
|
|
class LocalRepo:
|
|
|
|
# Create a remote repo from the full name or path
|
|
|
|
def __init__(self, fullname):
|
|
|
|
if fullname.startswith(REPO_FOLDER + "/"):
|
|
|
|
fullname = fullname[len(REPO_FOLDER)+1:]
|
|
|
|
|
|
|
|
assert fullname.count("/") == 1
|
|
|
|
|
|
|
|
self.fullname = fullname
|
|
|
|
self.owner, self.name = fullname.split("/")
|
|
|
|
self.folder = REPO_FOLDER + "/" + fullname
|
|
|
|
self.remote = False
|
2024-08-08 17:50:28 +02:00
|
|
|
|
|
|
|
if os.path.exists(self.folder + "/.giteapc/giteapc.make"):
|
|
|
|
self.makefile = self.folder + "/.giteapc/giteapc.make"
|
|
|
|
else:
|
|
|
|
self.makefile = self.folder + "/giteapc.make"
|
2021-01-02 13:30:24 +01:00
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def path(fullname):
|
|
|
|
return REPO_FOLDER + "/" + fullname
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def exists(fullname):
|
|
|
|
return os.path.exists(LocalRepo.path(fullname))
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def all():
|
|
|
|
return [ LocalRepo(path) for path in glob.glob(REPO_FOLDER + f"/*/*") ]
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def clone(r, method="https"):
|
|
|
|
src = r.clone_url(method)
|
|
|
|
dst = LocalRepo.path(r.fullname)
|
|
|
|
mkdir_p(dst)
|
|
|
|
cmd = ["git", "clone", src, dst]
|
|
|
|
|
|
|
|
try:
|
|
|
|
run(cmd, stdout=PIPE, stderr=PIPE)
|
|
|
|
return LocalRepo(r.fullname)
|
|
|
|
except ProcessError as e:
|
|
|
|
# On error, delete the failed clone
|
|
|
|
shutil.rmtree(dst)
|
|
|
|
raise e
|
|
|
|
|
|
|
|
# Git commands
|
|
|
|
|
|
|
|
def _git(self, command, *args, **kwargs):
|
|
|
|
return run(["git", "-C", self.folder] + command,
|
|
|
|
*args, **kwargs)
|
|
|
|
|
|
|
|
def is_on_branch(self):
|
|
|
|
try:
|
2021-01-04 12:06:58 +01:00
|
|
|
self._git(["symbolic-ref", "-q", "HEAD"], stdout=PIPE)
|
2021-01-02 13:30:24 +01:00
|
|
|
return True
|
|
|
|
except ProcessError as e:
|
|
|
|
if e.returncode == 1:
|
|
|
|
return False
|
|
|
|
raise e
|
|
|
|
|
2024-03-13 23:00:32 +01:00
|
|
|
def updateRemotes(self):
|
|
|
|
# Automatically switch old gitea URLs to Forgejo
|
|
|
|
for r in self._git(["remote"], stdout=PIPE).stdout.split():
|
|
|
|
url = self._git(["remote", "get-url", r], stdout=PIPE).stdout.rstrip(b"\n")
|
|
|
|
new = url
|
|
|
|
if url.startswith(b"gitea@gitea.planet-casio.com:"):
|
|
|
|
new = b"forgejo@git" + url[11:]
|
|
|
|
if url.startswith(b"https://gitea.planet-casio.com/"):
|
|
|
|
new = b"https://git." + url[14:]
|
|
|
|
if new != url:
|
|
|
|
self._git(["remote", "set-url", r, new])
|
|
|
|
|
2021-01-02 13:30:24 +01:00
|
|
|
def fetch(self):
|
2024-03-13 23:00:32 +01:00
|
|
|
self.updateRemotes()
|
2021-01-02 13:30:24 +01:00
|
|
|
self._git(["fetch"])
|
|
|
|
|
|
|
|
def pull(self):
|
2024-03-13 23:00:32 +01:00
|
|
|
self.updateRemotes()
|
2021-01-02 13:30:24 +01:00
|
|
|
if self.is_on_branch():
|
|
|
|
self._git(["pull"])
|
|
|
|
|
2021-01-04 12:06:58 +01:00
|
|
|
def describe(self, tags=True, all=False, always=True):
|
|
|
|
args = ["--tags"]*tags + ["--all"]*all + ["--always"]*always
|
|
|
|
p = self._git(["describe"] + args, stdout=PIPE)
|
|
|
|
return p.stdout.decode("utf-8").strip()
|
|
|
|
|
2021-01-02 13:30:24 +01:00
|
|
|
def checkout(self, version):
|
|
|
|
self._git(["checkout", version], stdout=PIPE, stderr=PIPE)
|
|
|
|
|
|
|
|
def branches(self):
|
|
|
|
proc = self._git(["branch"], stdout=subprocess.PIPE)
|
|
|
|
local = proc.stdout.decode("utf-8").split("\n")
|
|
|
|
local = [ b[2:] for b in local if b ]
|
|
|
|
|
|
|
|
proc = self._git(["branch", "-r"], stdout=subprocess.PIPE)
|
|
|
|
remote = proc.stdout.decode("utf-8").split("\n")
|
|
|
|
remote = [ b[9:] for b in remote
|
|
|
|
if b and b.startswith(" origin/") and "/HEAD " not in b ]
|
|
|
|
remote = [ b for b in remote if b not in local ]
|
|
|
|
|
|
|
|
return [ {"name": b, "local": True } for b in local ] + \
|
|
|
|
[ {"name": b, "local": False} for b in remote ]
|
|
|
|
|
|
|
|
def tags(self):
|
|
|
|
proc = self._git(["tag", "--list"], stdout=subprocess.PIPE)
|
|
|
|
tags = proc.stdout.decode("utf-8").split("\n")
|
|
|
|
return [ {"name": t} for t in tags if t ]
|
|
|
|
|
|
|
|
# Make commands
|
|
|
|
|
|
|
|
def make(self, target, env=None):
|
2021-03-18 09:55:24 +01:00
|
|
|
# Use GNU Make even on OpenBSD
|
|
|
|
if shutil.which("gmake") is not None:
|
|
|
|
command = "gmake"
|
|
|
|
else:
|
|
|
|
command = "make"
|
|
|
|
|
2021-01-02 13:30:24 +01:00
|
|
|
with ChangeDirectory(self.folder):
|
2024-08-08 17:50:28 +02:00
|
|
|
return run([command, "-f", self.makefile, target], env=env)
|
2021-01-04 12:06:58 +01:00
|
|
|
|
2024-04-17 08:29:01 +02:00
|
|
|
def configs(self):
|
|
|
|
RE_NAME = re.compile(r'giteapc-config-(.*)\.make')
|
|
|
|
files = glob.glob(self.folder + "/giteapc-config-*.make")
|
2024-08-08 17:50:28 +02:00
|
|
|
files += glob.glob(self.folder + "/.giteapc/giteapc-config-*.make")
|
2024-04-17 08:29:01 +02:00
|
|
|
return sorted(RE_NAME.match(os.path.basename(x))[1] for x in files)
|
|
|
|
|
2021-01-15 16:02:22 +01:00
|
|
|
def set_config(self, config):
|
2024-08-08 17:50:28 +02:00
|
|
|
source = os.path.dirname(self.makefile) + f"/giteapc-config.make"
|
|
|
|
target = os.path.dirname(self.makefile) + f"/giteapc-config-{config}.make"
|
2021-01-15 16:02:22 +01:00
|
|
|
|
2024-04-07 14:26:36 +02:00
|
|
|
if config == "":
|
|
|
|
if os.path.islink(source):
|
|
|
|
os.remove(source)
|
|
|
|
return
|
|
|
|
|
|
|
|
if not os.path.exists(target):
|
|
|
|
raise Error(f"config '{config}' does not exist")
|
|
|
|
|
|
|
|
if os.path.islink(source):
|
2021-01-15 16:02:22 +01:00
|
|
|
os.remove(source)
|
2024-04-07 14:26:36 +02:00
|
|
|
elif os.path.exists(source):
|
|
|
|
raise Error("giteapc-config.make is not a link, was it altered?")
|
|
|
|
|
2021-01-15 16:02:22 +01:00
|
|
|
os.symlink(target, source)
|
|
|
|
|
2021-01-04 12:06:58 +01:00
|
|
|
# Metadata
|
|
|
|
|
|
|
|
def metadata(self):
|
|
|
|
RE_METADATA = re.compile(r'^\s*#\s*giteapc\s*:\s*(.*)$')
|
|
|
|
metadata = dict()
|
|
|
|
|
2024-08-08 17:50:28 +02:00
|
|
|
with open(self.makefile, "r") as fp:
|
2021-01-04 12:06:58 +01:00
|
|
|
makefile = fp.read()
|
|
|
|
|
|
|
|
for line in makefile.split("\n"):
|
|
|
|
m = re.match(RE_METADATA, line)
|
|
|
|
if m is None:
|
|
|
|
break
|
|
|
|
|
|
|
|
for assignment in m[1].split():
|
|
|
|
name, value = assignment.split("=", 1)
|
|
|
|
if name not in metadata:
|
|
|
|
metadata[name] = value
|
|
|
|
else:
|
|
|
|
metadata[name] += "," + value
|
|
|
|
|
|
|
|
return metadata
|
|
|
|
|
|
|
|
def dependencies(self):
|
|
|
|
metadata = self.metadata()
|
|
|
|
if "depends" in metadata:
|
|
|
|
return metadata["depends"].split(",")
|
|
|
|
else:
|
|
|
|
return []
|