2026-11-01 00:49:07 +01:00
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
import io
|
|
|
|
|
import os
|
|
|
|
|
import shutil
|
|
|
|
|
import subprocess
|
|
|
|
|
import tempfile
|
|
|
|
|
import unittest
|
|
|
|
|
from contextlib import redirect_stdout
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
from unittest.mock import patch
|
|
|
|
|
|
|
|
|
|
from pkgmgr.actions.release.retry import retry_release
|
2026-11-07 13:26:40 +00:00
|
|
|
from pkgmgr.actions.release.workflow import _release_impl
|
2026-11-01 00:49:07 +01:00
|
|
|
from pkgmgr.actions.repository.push import (
|
|
|
|
|
PushRefusedError,
|
|
|
|
|
push_branch,
|
|
|
|
|
sign_unpushed,
|
|
|
|
|
)
|
|
|
|
|
from pkgmgr.cli.context import CLIContext
|
|
|
|
|
from pkgmgr.cli.parser import create_parser
|
|
|
|
|
from pkgmgr.cli.proxy import maybe_handle_proxy
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def git(cwd: str, *args: str) -> str:
|
|
|
|
|
return subprocess.run(
|
|
|
|
|
["git", *args], cwd=cwd, check=True, capture_output=True, text=True
|
|
|
|
|
).stdout.strip()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@unittest.skipUnless(shutil.which("ssh-keygen"), "ssh-keygen signs the commits")
|
|
|
|
|
class TestPushMirrorLeases(unittest.TestCase):
|
|
|
|
|
def setUp(self) -> None:
|
|
|
|
|
self._tmp = tempfile.TemporaryDirectory()
|
|
|
|
|
self.addCleanup(self._tmp.cleanup)
|
|
|
|
|
root = Path(os.path.realpath(self._tmp.name))
|
|
|
|
|
self.root = str(root)
|
|
|
|
|
key = root / "key"
|
|
|
|
|
subprocess.run(
|
|
|
|
|
["ssh-keygen", "-q", "-t", "ed25519", "-N", "", "-f", str(key)],
|
|
|
|
|
check=True,
|
|
|
|
|
)
|
|
|
|
|
(root / "allowed").write_text(f"t@example.org {(root / 'key.pub').read_text()}")
|
|
|
|
|
(root / "gitconfig").write_text(
|
|
|
|
|
"[user]\n\tname = t\n\temail = t@example.org\n"
|
|
|
|
|
f"\tsigningkey = {key}\n"
|
|
|
|
|
"[gpg]\n\tformat = ssh\n"
|
|
|
|
|
f'[gpg "ssh"]\n\tallowedSignersFile = {root / "allowed"}\n'
|
|
|
|
|
"[commit]\n\tgpgsign = false\n"
|
|
|
|
|
"[init]\n\tdefaultBranch = main\n"
|
|
|
|
|
)
|
|
|
|
|
env = patch.dict(
|
|
|
|
|
os.environ,
|
|
|
|
|
{"GIT_CONFIG_GLOBAL": str(root / "gitconfig"), "GIT_CONFIG_NOSYSTEM": "1"},
|
|
|
|
|
)
|
|
|
|
|
env.start()
|
|
|
|
|
self.addCleanup(env.stop)
|
|
|
|
|
|
|
|
|
|
self.canonical = str(root / "canonical.git")
|
|
|
|
|
self.mirror = str(root / "mirror.git")
|
|
|
|
|
self.repo = str(root / "repo")
|
|
|
|
|
git(self.root, "init", "-q", "--bare", self.canonical)
|
|
|
|
|
git(self.root, "init", "-q", "--bare", self.mirror)
|
|
|
|
|
git(self.root, "init", "-q", self.repo)
|
|
|
|
|
git(self.repo, "remote", "add", "origin", self.canonical)
|
|
|
|
|
git(self.repo, "remote", "set-url", "--add", "--push", "origin", self.canonical)
|
|
|
|
|
git(self.repo, "remote", "set-url", "--add", "--push", "origin", self.mirror)
|
|
|
|
|
self.init = self._commit("init")
|
|
|
|
|
git(self.repo, "push", "-q", self.canonical, "main")
|
|
|
|
|
git(self.repo, "push", "-q", self.mirror, "main")
|
|
|
|
|
git(self.repo, "fetch", "-q", "origin")
|
|
|
|
|
git(self.repo, "branch", "-q", "--set-upstream-to=origin/main")
|
|
|
|
|
|
|
|
|
|
def _commit(self, name: str, cwd: str | None = None, *flags: str) -> str:
|
|
|
|
|
cwd = cwd or self.repo
|
|
|
|
|
Path(cwd, f"{name}.txt").write_text(name)
|
|
|
|
|
git(cwd, "add", f"{name}.txt")
|
|
|
|
|
git(cwd, "commit", "-q", *flags, "-m", name)
|
|
|
|
|
return git(cwd, "rev-parse", "HEAD")
|
|
|
|
|
|
|
|
|
|
def _push(self, rewrite: bool = False) -> str:
|
|
|
|
|
with redirect_stdout(io.StringIO()) as out:
|
|
|
|
|
sign_unpushed(self.repo)
|
2026-11-01 01:49:57 +01:00
|
|
|
push_branch(self.repo, [], sign=True, rewrite=rewrite)
|
2026-11-01 00:49:07 +01:00
|
|
|
return out.getvalue()
|
|
|
|
|
|
2026-11-01 01:49:57 +01:00
|
|
|
def _cli_push(self, cwd: str, *argv: str) -> None:
|
|
|
|
|
ctx = CLIContext(
|
|
|
|
|
config_merged={},
|
|
|
|
|
repositories_base_dir=os.path.join(self.root, "base"),
|
|
|
|
|
all_repositories=[],
|
|
|
|
|
binaries_dir=os.path.join(self.root, "bin"),
|
|
|
|
|
user_config_path=os.path.join(self.root, "config.yaml"),
|
|
|
|
|
)
|
|
|
|
|
args = create_parser("pkgmgr").parse_args(["push", "-j", "1", *argv])
|
|
|
|
|
prev = os.getcwd()
|
|
|
|
|
os.chdir(cwd)
|
|
|
|
|
self.addCleanup(os.chdir, prev)
|
|
|
|
|
|
|
|
|
|
with redirect_stdout(io.StringIO()), self.assertRaises(SystemExit) as cm:
|
|
|
|
|
maybe_handle_proxy(args, ctx)
|
|
|
|
|
|
|
|
|
|
self.assertEqual(cm.exception.code, 0)
|
|
|
|
|
|
2026-11-01 00:49:07 +01:00
|
|
|
def _tip(self, bare: str, ref: str = "refs/heads/main") -> str:
|
|
|
|
|
return subprocess.run(
|
|
|
|
|
["git", "rev-parse", "--verify", "--quiet", ref],
|
|
|
|
|
cwd=bare,
|
|
|
|
|
check=False,
|
|
|
|
|
capture_output=True,
|
|
|
|
|
text=True,
|
|
|
|
|
).stdout.strip()
|
|
|
|
|
|
|
|
|
|
def _assert_shipped_signed(self) -> None:
|
|
|
|
|
head = git(self.repo, "rev-parse", "HEAD")
|
|
|
|
|
self.assertEqual(self._tip(self.canonical), head)
|
|
|
|
|
self.assertEqual(self._tip(self.mirror), head)
|
|
|
|
|
signatures = git(self.repo, "log", "--format=%G?", f"{self.init}..HEAD")
|
|
|
|
|
self.assertEqual(set(signatures.split()), {"G"})
|
|
|
|
|
|
|
|
|
|
def test_mirror_equal_gets_the_signed_commit(self) -> None:
|
|
|
|
|
self._commit("feature")
|
|
|
|
|
|
|
|
|
|
self._push()
|
|
|
|
|
|
|
|
|
|
self._assert_shipped_signed()
|
|
|
|
|
|
|
|
|
|
def test_mirror_behind_is_fast_forwarded(self) -> None:
|
|
|
|
|
self._commit("first", None, "-S")
|
|
|
|
|
git(self.repo, "push", "-q", self.canonical, "main")
|
|
|
|
|
git(self.repo, "fetch", "-q", "origin")
|
|
|
|
|
self._commit("second")
|
|
|
|
|
|
|
|
|
|
self._push()
|
|
|
|
|
|
|
|
|
|
self._assert_shipped_signed()
|
|
|
|
|
|
|
|
|
|
def test_mirror_on_older_rewrite_is_replaced_after_resign(self) -> None:
|
|
|
|
|
unsigned = self._commit("feature")
|
|
|
|
|
git(self.repo, "push", "-q", self.mirror, "main")
|
|
|
|
|
|
|
|
|
|
self._push()
|
|
|
|
|
|
|
|
|
|
self._assert_shipped_signed()
|
|
|
|
|
self.assertNotEqual(self._tip(self.mirror), unsigned)
|
|
|
|
|
|
|
|
|
|
def test_mirror_only_commit_is_refused_and_kept(self) -> None:
|
|
|
|
|
other = os.path.join(self.root, "other")
|
|
|
|
|
git(self.root, "clone", "-q", self.mirror, other)
|
|
|
|
|
foreign = self._commit("foreign", other)
|
|
|
|
|
git(other, "push", "-q", "origin", "main")
|
|
|
|
|
self._commit("feature")
|
|
|
|
|
|
|
|
|
|
with self.assertRaises(PushRefusedError) as cm:
|
|
|
|
|
self._push()
|
|
|
|
|
with self.assertRaises(PushRefusedError):
|
|
|
|
|
self._push(rewrite=True)
|
|
|
|
|
|
|
|
|
|
self.assertIn(self.mirror, str(cm.exception))
|
|
|
|
|
self.assertEqual(self._tip(self.mirror), foreign)
|
|
|
|
|
self.assertEqual(self._tip(self.canonical), git(self.repo, "rev-parse", "HEAD"))
|
|
|
|
|
|
|
|
|
|
def test_content_rewrite_needs_the_rewrite_flag(self) -> None:
|
|
|
|
|
self._commit("feature")
|
|
|
|
|
self._push()
|
|
|
|
|
shipped = git(self.repo, "rev-parse", "HEAD")
|
|
|
|
|
Path(self.repo, "feature.txt").write_text("changed")
|
|
|
|
|
git(self.repo, "commit", "-q", "-a", "--amend", "--no-edit")
|
|
|
|
|
|
|
|
|
|
with self.assertRaises(PushRefusedError) as cm:
|
|
|
|
|
self._push()
|
|
|
|
|
|
|
|
|
|
self.assertIn(self.canonical, str(cm.exception))
|
|
|
|
|
self.assertEqual(self._tip(self.canonical), shipped)
|
|
|
|
|
self.assertEqual(self._tip(self.mirror), shipped)
|
|
|
|
|
|
|
|
|
|
self._push(rewrite=True)
|
|
|
|
|
|
|
|
|
|
self._assert_shipped_signed()
|
|
|
|
|
self.assertNotEqual(self._tip(self.mirror), shipped)
|
|
|
|
|
|
|
|
|
|
def test_worktree_outside_base_dir_is_pushed(self) -> None:
|
|
|
|
|
worktree = os.path.join(self.root, "wt")
|
|
|
|
|
git(self.repo, "worktree", "add", "-q", "-b", "feature", worktree)
|
|
|
|
|
head_before = self._commit("feature", worktree)
|
|
|
|
|
|
2026-11-01 01:49:57 +01:00
|
|
|
self._cli_push(worktree)
|
2026-11-01 00:49:07 +01:00
|
|
|
|
|
|
|
|
head = git(worktree, "rev-parse", "HEAD")
|
|
|
|
|
self.assertNotEqual(head, head_before)
|
|
|
|
|
self.assertEqual(self._tip(self.canonical, "refs/heads/feature"), head)
|
|
|
|
|
self.assertEqual(self._tip(self.mirror, "refs/heads/feature"), head)
|
|
|
|
|
self.assertEqual(
|
|
|
|
|
git(worktree, "rev-parse", "--abbrev-ref", "@{u}"), "origin/feature"
|
|
|
|
|
)
|
|
|
|
|
|
2026-11-01 01:49:57 +01:00
|
|
|
def test_no_sign_pushes_unsigned_commits_to_every_url(self) -> None:
|
|
|
|
|
unsigned = self._commit("feature")
|
|
|
|
|
|
|
|
|
|
self._cli_push(self.repo, "--no-sign")
|
|
|
|
|
|
|
|
|
|
self.assertEqual(git(self.repo, "rev-parse", "HEAD"), unsigned)
|
|
|
|
|
self.assertEqual(self._tip(self.canonical), unsigned)
|
|
|
|
|
self.assertEqual(self._tip(self.mirror), unsigned)
|
|
|
|
|
self.assertEqual(git(self.repo, "log", "-1", "--format=%G?"), "N")
|
|
|
|
|
|
2026-11-07 12:20:01 +00:00
|
|
|
def test_only_head_is_signed_and_older_unsigned_commits_stay(self) -> None:
|
|
|
|
|
older = self._commit("older")
|
|
|
|
|
self._commit("head")
|
|
|
|
|
|
|
|
|
|
out = self._push()
|
|
|
|
|
|
|
|
|
|
head = git(self.repo, "rev-parse", "HEAD")
|
|
|
|
|
self.assertEqual(git(self.repo, "rev-parse", "HEAD^"), older)
|
|
|
|
|
self.assertEqual(git(self.repo, "log", "-1", "--format=%G?", head), "G")
|
|
|
|
|
self.assertEqual(git(self.repo, "log", "-1", "--format=%G?", older), "N")
|
|
|
|
|
self.assertIn("WARN 1 older unsigned commit(s)", out)
|
|
|
|
|
self.assertEqual(self._tip(self.canonical), head)
|
|
|
|
|
self.assertEqual(self._tip(self.mirror), head)
|
|
|
|
|
|
|
|
|
|
def test_pushed_remote_branch_is_the_base_without_upstream(self) -> None:
|
|
|
|
|
git(self.repo, "checkout", "-q", "-b", "feature")
|
|
|
|
|
with patch.dict(os.environ, {"GIT_COMMITTER_DATE": "2001-01-01T00:00:00+0000"}):
|
|
|
|
|
pushed = self._commit("pushed", None, "-S")
|
|
|
|
|
git(self.repo, "push", "-q", "origin", "feature")
|
|
|
|
|
git(self.repo, "fetch", "-q", "origin")
|
|
|
|
|
self._commit("new")
|
|
|
|
|
upstream = subprocess.run(
|
|
|
|
|
["git", "rev-parse", "--abbrev-ref", "@{u}"],
|
|
|
|
|
cwd=self.repo,
|
|
|
|
|
check=False,
|
|
|
|
|
capture_output=True,
|
|
|
|
|
)
|
|
|
|
|
self.assertNotEqual(upstream.returncode, 0)
|
|
|
|
|
|
|
|
|
|
out = self._push()
|
|
|
|
|
|
|
|
|
|
head = git(self.repo, "rev-parse", "HEAD")
|
|
|
|
|
self.assertEqual(git(self.repo, "rev-parse", "HEAD^"), pushed)
|
|
|
|
|
self.assertEqual(git(self.repo, "log", "-1", "--format=%G?", head), "G")
|
|
|
|
|
self.assertNotIn("WARN", out)
|
|
|
|
|
self.assertEqual(self._tip(self.canonical, "refs/heads/feature"), head)
|
|
|
|
|
|
2026-11-07 13:26:40 +00:00
|
|
|
def test_repository_config_that_runs_a_program_is_refused(self) -> None:
|
|
|
|
|
self._commit("feature")
|
|
|
|
|
for key in (
|
|
|
|
|
"core.sshCommand",
|
|
|
|
|
"core.gitProxy",
|
|
|
|
|
"core.askPass",
|
|
|
|
|
"core.fsmonitor",
|
|
|
|
|
"core.alternateRefsCommand",
|
|
|
|
|
"credential.helper",
|
|
|
|
|
"credential.https://example.org.helper",
|
|
|
|
|
"gpg.program",
|
|
|
|
|
"gpg.ssh.program",
|
|
|
|
|
"gpg.ssh.defaultKeyCommand",
|
|
|
|
|
"protocol.ext.allow",
|
|
|
|
|
"hook.probe.command",
|
|
|
|
|
"gc.recentObjectsHook",
|
|
|
|
|
"http.proxy",
|
|
|
|
|
"http.sslVerify",
|
|
|
|
|
"http.sslCAInfo",
|
|
|
|
|
"http.sslCAPath",
|
|
|
|
|
"http.cookieFile",
|
|
|
|
|
"http.saveCookies",
|
|
|
|
|
"remote.origin.receivepack",
|
|
|
|
|
"remote.origin.uploadpack",
|
|
|
|
|
"remote.origin.vcs",
|
|
|
|
|
"remote.origin.proxy",
|
|
|
|
|
"url.x.insteadOf",
|
|
|
|
|
"url.x.pushInsteadOf",
|
|
|
|
|
):
|
|
|
|
|
for push in (self._push, lambda: push_branch(self.repo, [], sign=False)):
|
|
|
|
|
with self.subTest(key=key, push=push):
|
|
|
|
|
git(self.repo, "config", key, "false")
|
|
|
|
|
|
|
|
|
|
with self.assertRaises(PushRefusedError) as cm:
|
|
|
|
|
push()
|
|
|
|
|
|
|
|
|
|
git(self.repo, "config", "--unset", key)
|
|
|
|
|
self.assertIn(key.lower(), str(cm.exception))
|
|
|
|
|
self.assertEqual(self._tip(self.canonical), self.init)
|
|
|
|
|
self.assertEqual(self._tip(self.mirror), self.init)
|
|
|
|
|
|
|
|
|
|
def test_ordinary_repository_config_is_pushed(self) -> None:
|
|
|
|
|
for key, value in (
|
|
|
|
|
("core.hooksPath", ".husky/_"),
|
|
|
|
|
("filter.lfs.process", "git-lfs filter-process"),
|
|
|
|
|
("gpg.format", "ssh"),
|
|
|
|
|
("credential.useHttpPath", "true"),
|
|
|
|
|
("include.path", "../shared.gitconfig"),
|
|
|
|
|
):
|
|
|
|
|
git(self.repo, "config", key, value)
|
|
|
|
|
self._commit("feature")
|
|
|
|
|
|
|
|
|
|
self._push()
|
|
|
|
|
|
|
|
|
|
self._assert_shipped_signed()
|
|
|
|
|
|
|
|
|
|
def test_staged_gitlink_is_not_folded_into_the_signed_head(self) -> None:
|
|
|
|
|
self._commit("feature")
|
|
|
|
|
head = git(self.repo, "rev-parse", "HEAD")
|
|
|
|
|
git(self.repo, "config", "diff.ignoreSubmodules", "all")
|
|
|
|
|
git(self.repo, "update-index", "--add", "--cacheinfo", f"160000,{head},sub")
|
|
|
|
|
|
|
|
|
|
with self.assertRaises(PushRefusedError) as cm:
|
|
|
|
|
self._push()
|
|
|
|
|
|
|
|
|
|
self.assertIn("staged changes", str(cm.exception))
|
|
|
|
|
self.assertEqual(git(self.repo, "rev-parse", "HEAD"), head)
|
|
|
|
|
|
|
|
|
|
def test_unstaged_edit_stays_out_of_the_signed_head(self) -> None:
|
|
|
|
|
self._commit("feature")
|
|
|
|
|
Path(self.repo, "feature.txt").write_text("unstaged")
|
|
|
|
|
|
|
|
|
|
self._push()
|
|
|
|
|
|
|
|
|
|
self._assert_shipped_signed()
|
|
|
|
|
self.assertEqual(git(self.repo, "status", "--porcelain"), "M feature.txt")
|
|
|
|
|
|
|
|
|
|
def test_signing_amend_runs_no_repository_hook(self) -> None:
|
|
|
|
|
marker = Path(self.root, "hooked")
|
|
|
|
|
hook = Path(self.repo, ".git", "hooks", "post-commit")
|
|
|
|
|
hook.write_text(f"#!/bin/sh\ntouch {marker}\n")
|
|
|
|
|
hook.chmod(0o755)
|
|
|
|
|
self._commit("feature")
|
|
|
|
|
marker.unlink()
|
|
|
|
|
|
|
|
|
|
self._push()
|
|
|
|
|
|
|
|
|
|
self._assert_shipped_signed()
|
|
|
|
|
self.assertFalse(marker.exists())
|
|
|
|
|
|
|
|
|
|
def test_signing_amend_runs_no_config_hook(self) -> None:
|
|
|
|
|
marker = Path(self.root, "hooked")
|
|
|
|
|
git(self.repo, "config", "hook.probe.command", f"touch {marker}")
|
|
|
|
|
git(self.repo, "config", "hook.probe.event", "post-commit")
|
|
|
|
|
self._commit("feature")
|
|
|
|
|
hooks_fire = marker.exists()
|
|
|
|
|
marker.unlink(missing_ok=True)
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
self._push()
|
|
|
|
|
except PushRefusedError:
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
self.assertFalse(marker.exists(), f"config hooks fire on commit: {hooks_fire}")
|
|
|
|
|
|
|
|
|
|
def test_mirror_lease_runs_no_textconv(self) -> None:
|
|
|
|
|
marker = Path(self.root, "textconv")
|
|
|
|
|
Path(self.repo, ".gitattributes").write_text("* diff=probe\n")
|
|
|
|
|
git(self.repo, "add", ".gitattributes")
|
|
|
|
|
git(self.repo, "commit", "-q", "-S", "-m", "attributes")
|
|
|
|
|
git(self.repo, "push", "-q", self.mirror, "main")
|
|
|
|
|
git(self.repo, "config", "diff.probe.textconv", f"touch {marker};cat")
|
|
|
|
|
self._commit("feature")
|
|
|
|
|
git(self.repo, "diff", "HEAD~1", "HEAD")
|
|
|
|
|
self.assertTrue(marker.exists())
|
|
|
|
|
marker.unlink()
|
|
|
|
|
|
|
|
|
|
self._push()
|
|
|
|
|
|
|
|
|
|
self._assert_shipped_signed()
|
|
|
|
|
self.assertFalse(marker.exists())
|
|
|
|
|
|
|
|
|
|
def test_remote_named_like_an_option_is_pushed(self) -> None:
|
|
|
|
|
git(self.repo, "config", "remote.-r.url", self.canonical)
|
|
|
|
|
git(self.repo, "config", "remote.-r.fetch", "+refs/heads/*:refs/remotes/-r/*")
|
|
|
|
|
git(self.repo, "config", "branch.main.pushRemote", "-r")
|
|
|
|
|
git(self.repo, "fetch", "-q", "--", "-r")
|
|
|
|
|
self._commit("feature")
|
|
|
|
|
|
|
|
|
|
self._push()
|
|
|
|
|
|
|
|
|
|
self.assertEqual(self._tip(self.canonical), git(self.repo, "rev-parse", "HEAD"))
|
|
|
|
|
|
|
|
|
|
def test_release_refuses_unsafe_config_before_any_change(self) -> None:
|
|
|
|
|
head = git(self.repo, "rev-parse", "HEAD")
|
|
|
|
|
git(self.repo, "config", "core.sshCommand", "false")
|
|
|
|
|
cwd = os.getcwd()
|
|
|
|
|
os.chdir(self.repo)
|
|
|
|
|
self.addCleanup(os.chdir, cwd)
|
|
|
|
|
|
|
|
|
|
with redirect_stdout(io.StringIO()), self.assertRaises(PushRefusedError):
|
|
|
|
|
_release_impl(
|
|
|
|
|
pyproject_path=os.path.join(self.repo, "pyproject.toml"), force=True
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
self.assertEqual(git(self.repo, "rev-parse", "HEAD"), head)
|
|
|
|
|
self.assertEqual(git(self.repo, "status", "--porcelain"), "")
|
|
|
|
|
|
2026-11-01 00:49:07 +01:00
|
|
|
def test_release_retry_refuses_unsigned_range(self) -> None:
|
|
|
|
|
self._commit("release")
|
|
|
|
|
git(self.repo, "tag", "-a", "v1.0.0", "-m", "v1.0.0")
|
|
|
|
|
cwd = os.getcwd()
|
|
|
|
|
os.chdir(self.repo)
|
|
|
|
|
self.addCleanup(os.chdir, cwd)
|
|
|
|
|
|
|
|
|
|
with redirect_stdout(io.StringIO()), self.assertRaises(PushRefusedError):
|
|
|
|
|
retry_release(pyproject_path=os.path.join(self.repo, "pyproject.toml"))
|
|
|
|
|
|
|
|
|
|
self.assertEqual(self._tip(self.canonical), git(self.repo, "rev-parse", "@{u}"))
|
|
|
|
|
self.assertEqual(self._tip(self.canonical, "refs/tags/v1.0.0"), "")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
unittest.main()
|