fix: authenticate private module bootstrap in CI
This commit is contained in:
@@ -1,5 +1,7 @@
|
|||||||
name: Dependency Audit
|
name: Dependency Audit
|
||||||
|
|
||||||
|
permissions: read-all
|
||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
push:
|
push:
|
||||||
@@ -21,13 +23,13 @@ jobs:
|
|||||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
|
||||||
with:
|
with:
|
||||||
node-version: "22"
|
node-version: "22"
|
||||||
- name: Use anonymous HTTPS for public GovOPlaN repositories
|
- name: Use HTTPS for GovOPlaN repositories
|
||||||
run: |
|
run: |
|
||||||
git config --global --add url."https://git.add-ideas.de/GovOPlaN/govoplan".insteadOf "git@git.add-ideas.de:GovOPlaN/govoplan"
|
git config --global --add url."https://git.add-ideas.de/GovOPlaN/govoplan".insteadOf "git@git.add-ideas.de:GovOPlaN/govoplan"
|
||||||
git config --global --add url."https://git.add-ideas.de/GovOPlaN/govoplan".insteadOf "ssh://git@git.add-ideas.de/GovOPlaN/govoplan"
|
git config --global --add url."https://git.add-ideas.de/GovOPlaN/govoplan".insteadOf "ssh://git@git.add-ideas.de/GovOPlaN/govoplan"
|
||||||
- name: Bootstrap GovOPlaN repositories
|
- name: Bootstrap GovOPlaN repositories
|
||||||
working-directory: govoplan
|
working-directory: govoplan
|
||||||
run: python tools/repo/bootstrap-repositories.py --parent .. --transport public-https --exclude-repo addideas-govoplan-website
|
run: python tools/repo/bootstrap-repositories.py --parent .. --transport public-https --reuse-checkout-auth --exclude-repo addideas-govoplan-website
|
||||||
- name: Install backend dev audit dependencies
|
- name: Install backend dev audit dependencies
|
||||||
working-directory: govoplan
|
working-directory: govoplan
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
name: Module Matrix
|
name: Module Matrix
|
||||||
|
|
||||||
|
permissions: read-all
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
pull_request:
|
pull_request:
|
||||||
@@ -29,13 +31,13 @@ jobs:
|
|||||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
|
||||||
with:
|
with:
|
||||||
node-version: "22"
|
node-version: "22"
|
||||||
- name: Use anonymous HTTPS for public GovOPlaN repositories
|
- name: Use HTTPS for GovOPlaN repositories
|
||||||
run: |
|
run: |
|
||||||
git config --global --add url."https://git.add-ideas.de/GovOPlaN/govoplan".insteadOf "git@git.add-ideas.de:GovOPlaN/govoplan"
|
git config --global --add url."https://git.add-ideas.de/GovOPlaN/govoplan".insteadOf "git@git.add-ideas.de:GovOPlaN/govoplan"
|
||||||
git config --global --add url."https://git.add-ideas.de/GovOPlaN/govoplan".insteadOf "ssh://git@git.add-ideas.de/GovOPlaN/govoplan"
|
git config --global --add url."https://git.add-ideas.de/GovOPlaN/govoplan".insteadOf "ssh://git@git.add-ideas.de/GovOPlaN/govoplan"
|
||||||
- name: Bootstrap GovOPlaN repositories
|
- name: Bootstrap GovOPlaN repositories
|
||||||
working-directory: govoplan
|
working-directory: govoplan
|
||||||
run: python tools/repo/bootstrap-repositories.py --parent .. --transport public-https --exclude-repo addideas-govoplan-website
|
run: python tools/repo/bootstrap-repositories.py --parent .. --transport public-https --reuse-checkout-auth --exclude-repo addideas-govoplan-website
|
||||||
- name: Install backend release dependencies
|
- name: Install backend release dependencies
|
||||||
working-directory: govoplan
|
working-directory: govoplan
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
name: Release Integration
|
name: Release Integration
|
||||||
|
|
||||||
|
permissions: read-all
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
@@ -16,13 +18,13 @@ jobs:
|
|||||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
|
||||||
with:
|
with:
|
||||||
node-version: "22"
|
node-version: "22"
|
||||||
- name: Use anonymous HTTPS for public GovOPlaN repositories
|
- name: Use HTTPS for GovOPlaN repositories
|
||||||
run: |
|
run: |
|
||||||
git config --global --add url."https://git.add-ideas.de/GovOPlaN/govoplan".insteadOf "git@git.add-ideas.de:GovOPlaN/govoplan"
|
git config --global --add url."https://git.add-ideas.de/GovOPlaN/govoplan".insteadOf "git@git.add-ideas.de:GovOPlaN/govoplan"
|
||||||
git config --global --add url."https://git.add-ideas.de/GovOPlaN/govoplan".insteadOf "ssh://git@git.add-ideas.de/GovOPlaN/govoplan"
|
git config --global --add url."https://git.add-ideas.de/GovOPlaN/govoplan".insteadOf "ssh://git@git.add-ideas.de/GovOPlaN/govoplan"
|
||||||
- name: Bootstrap GovOPlaN repositories
|
- name: Bootstrap GovOPlaN repositories
|
||||||
working-directory: govoplan
|
working-directory: govoplan
|
||||||
run: python tools/repo/bootstrap-repositories.py --parent .. --transport public-https --exclude-repo addideas-govoplan-website
|
run: python tools/repo/bootstrap-repositories.py --parent .. --transport public-https --reuse-checkout-auth --exclude-repo addideas-govoplan-website
|
||||||
- name: Install backend release integration dependencies
|
- name: Install backend release integration dependencies
|
||||||
working-directory: govoplan
|
working-directory: govoplan
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
name: Security Audit
|
name: Security Audit
|
||||||
|
|
||||||
|
permissions: read-all
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
@@ -30,7 +32,7 @@ jobs:
|
|||||||
python-version: "3.12"
|
python-version: "3.12"
|
||||||
- name: Bootstrap GovOPlaN repositories
|
- name: Bootstrap GovOPlaN repositories
|
||||||
working-directory: govoplan
|
working-directory: govoplan
|
||||||
run: python tools/repo/bootstrap-repositories.py --parent .. --transport public-https --exclude-repo addideas-govoplan-website
|
run: python tools/repo/bootstrap-repositories.py --parent .. --transport public-https --reuse-checkout-auth --exclude-repo addideas-govoplan-website
|
||||||
- name: Run whole-system security audit
|
- name: Run whole-system security audit
|
||||||
working-directory: govoplan
|
working-directory: govoplan
|
||||||
run: tools/checks/security-audit/run.sh --mode "$SECURITY_AUDIT_MODE" --scope "$SECURITY_AUDIT_SCOPE" --reports-dir audit-reports
|
run: tools/checks/security-audit/run.sh --mode "$SECURITY_AUDIT_MODE" --scope "$SECURITY_AUDIT_SCOPE" --reports-dir audit-reports
|
||||||
|
|||||||
@@ -70,6 +70,12 @@ Clone missing repositories listed in `repositories.json`:
|
|||||||
./tools/repo/bootstrap-repositories.py
|
./tools/repo/bootstrap-repositories.py
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Gitea Actions jobs bootstrap the registered repositories over HTTPS and reuse
|
||||||
|
only the checkout job's short-lived authentication header. If registered
|
||||||
|
modules are private, allow the meta repository read access under
|
||||||
|
`GovOPlaN -> Settings -> Actions -> General -> Cross-Repository Access`; no
|
||||||
|
long-lived personal token is stored by the workflow or bootstrap tool.
|
||||||
|
|
||||||
Update generated repository type notes in all READMEs:
|
Update generated repository type notes in all READMEs:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
|
|||||||
@@ -94,8 +94,7 @@ class RepositoryBootstrapTests(unittest.TestCase):
|
|||||||
"name": "govoplan-core",
|
"name": "govoplan-core",
|
||||||
"path": "govoplan-core",
|
"path": "govoplan-core",
|
||||||
"remote": (
|
"remote": (
|
||||||
"git@git.add-ideas.de:"
|
"git@git.add-ideas.de:GovOPlaN/govoplan-core.git"
|
||||||
"GovOPlaN/govoplan-core.git"
|
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -134,6 +133,66 @@ class RepositoryBootstrapTests(unittest.TestCase):
|
|||||||
self.assertEqual("/bin/false", environment["GIT_ASKPASS"])
|
self.assertEqual("/bin/false", environment["GIT_ASKPASS"])
|
||||||
self.assertEqual("0", environment["GIT_TERMINAL_PROMPT"])
|
self.assertEqual("0", environment["GIT_TERMINAL_PROMPT"])
|
||||||
|
|
||||||
|
def test_checkout_auth_is_forwarded_without_entering_clone_arguments(self) -> None:
|
||||||
|
bootstrap = load_bootstrap_module()
|
||||||
|
environment = {
|
||||||
|
"GIT_CONFIG_COUNT": "1",
|
||||||
|
"GIT_CONFIG_KEY_0": "url.https://example.test/.insteadOf",
|
||||||
|
"GIT_CONFIG_VALUE_0": "ssh://example.test/",
|
||||||
|
}
|
||||||
|
auth_header = "AUTHORIZATION: basic c2hvcnQtbGl2ZWQtam9iLXRva2Vu"
|
||||||
|
|
||||||
|
with patch.object(
|
||||||
|
bootstrap.subprocess,
|
||||||
|
"run",
|
||||||
|
return_value=bootstrap.subprocess.CompletedProcess(
|
||||||
|
args=[],
|
||||||
|
returncode=0,
|
||||||
|
stdout=auth_header + "\n",
|
||||||
|
),
|
||||||
|
) as runner:
|
||||||
|
bootstrap._add_checkout_auth(environment, root=Path("/workspace/meta"))
|
||||||
|
|
||||||
|
runner.assert_called_once_with(
|
||||||
|
[
|
||||||
|
"git",
|
||||||
|
"-C",
|
||||||
|
"/workspace/meta",
|
||||||
|
"config",
|
||||||
|
"--local",
|
||||||
|
"--get",
|
||||||
|
bootstrap.GITEA_CHECKOUT_AUTH_KEY,
|
||||||
|
],
|
||||||
|
check=False,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
)
|
||||||
|
self.assertEqual("2", environment["GIT_CONFIG_COUNT"])
|
||||||
|
self.assertEqual(
|
||||||
|
bootstrap.GITEA_CHECKOUT_AUTH_KEY,
|
||||||
|
environment["GIT_CONFIG_KEY_1"],
|
||||||
|
)
|
||||||
|
self.assertEqual(auth_header, environment["GIT_CONFIG_VALUE_1"])
|
||||||
|
|
||||||
|
def test_checkout_auth_fails_closed_when_checkout_did_not_persist_it(self) -> None:
|
||||||
|
bootstrap = load_bootstrap_module()
|
||||||
|
|
||||||
|
with (
|
||||||
|
patch.object(
|
||||||
|
bootstrap.subprocess,
|
||||||
|
"run",
|
||||||
|
return_value=bootstrap.subprocess.CompletedProcess(
|
||||||
|
args=[],
|
||||||
|
returncode=1,
|
||||||
|
stdout="",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
self.assertRaisesRegex(
|
||||||
|
ValueError, "checkout authentication is unavailable"
|
||||||
|
),
|
||||||
|
):
|
||||||
|
bootstrap._add_checkout_auth({}, root=Path("/workspace/meta"))
|
||||||
|
|
||||||
def test_main_validates_every_remote_before_cloning(self) -> None:
|
def test_main_validates_every_remote_before_cloning(self) -> None:
|
||||||
bootstrap = load_bootstrap_module()
|
bootstrap = load_bootstrap_module()
|
||||||
with tempfile.TemporaryDirectory(prefix="govoplan-bootstrap-") as directory:
|
with tempfile.TemporaryDirectory(prefix="govoplan-bootstrap-") as directory:
|
||||||
@@ -149,8 +208,7 @@ class RepositoryBootstrapTests(unittest.TestCase):
|
|||||||
"name": "govoplan-core",
|
"name": "govoplan-core",
|
||||||
"path": "govoplan-core",
|
"path": "govoplan-core",
|
||||||
"remote": (
|
"remote": (
|
||||||
"git@git.add-ideas.de:"
|
"git@git.add-ideas.de:GovOPlaN/govoplan-core.git"
|
||||||
"GovOPlaN/govoplan-core.git"
|
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -197,9 +255,7 @@ class RepositoryBootstrapTests(unittest.TestCase):
|
|||||||
"git@git.add-ideas.de:"
|
"git@git.add-ideas.de:"
|
||||||
"add-ideas/addideas-govoplan-website.git"
|
"add-ideas/addideas-govoplan-website.git"
|
||||||
),
|
),
|
||||||
"bootstrap_transport": (
|
"bootstrap_transport": (bootstrap.REGISTERED_TRANSPORT),
|
||||||
bootstrap.REGISTERED_TRANSPORT
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
@@ -240,10 +296,7 @@ class RepositoryBootstrapTests(unittest.TestCase):
|
|||||||
{
|
{
|
||||||
"name": name,
|
"name": name,
|
||||||
"path": name,
|
"path": name,
|
||||||
"remote": (
|
"remote": (f"git@git.add-ideas.de:GovOPlaN/{name}.git"),
|
||||||
"git@git.add-ideas.de:GovOPlaN/"
|
|
||||||
f"{name}.git"
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
for name in ("govoplan-core", "govoplan-poll")
|
for name in ("govoplan-core", "govoplan-poll")
|
||||||
],
|
],
|
||||||
@@ -291,8 +344,7 @@ class RepositoryBootstrapTests(unittest.TestCase):
|
|||||||
"name": "govoplan-core",
|
"name": "govoplan-core",
|
||||||
"path": "govoplan-core",
|
"path": "govoplan-core",
|
||||||
"remote": (
|
"remote": (
|
||||||
"git@git.add-ideas.de:"
|
"git@git.add-ideas.de:GovOPlaN/govoplan-core.git"
|
||||||
"GovOPlaN/govoplan-core.git"
|
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -333,8 +385,7 @@ class RepositoryBootstrapTests(unittest.TestCase):
|
|||||||
"name": "govoplan-core",
|
"name": "govoplan-core",
|
||||||
"path": "govoplan-core",
|
"path": "govoplan-core",
|
||||||
"remote": (
|
"remote": (
|
||||||
"git@git.add-ideas.de:"
|
"git@git.add-ideas.de:GovOPlaN/govoplan-core.git"
|
||||||
"GovOPlaN/govoplan-core.git"
|
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -359,7 +410,7 @@ class RepositoryBootstrapTests(unittest.TestCase):
|
|||||||
self.assertEqual(1, status)
|
self.assertEqual(1, status)
|
||||||
runner.assert_not_called()
|
runner.assert_not_called()
|
||||||
|
|
||||||
def test_anonymous_ci_bootstrap_excludes_registered_transport_repositories(
|
def test_ci_bootstrap_reuses_checkout_auth_and_excludes_registered_transport_repositories(
|
||||||
self,
|
self,
|
||||||
) -> None:
|
) -> None:
|
||||||
manifest = json.loads(
|
manifest = json.loads(
|
||||||
@@ -372,9 +423,7 @@ class RepositoryBootstrapTests(unittest.TestCase):
|
|||||||
}
|
}
|
||||||
self.assertTrue(registered_only)
|
self.assertTrue(registered_only)
|
||||||
|
|
||||||
for workflow in sorted(
|
for workflow in sorted((META_ROOT / ".gitea" / "workflows").glob("*.yml")):
|
||||||
(META_ROOT / ".gitea" / "workflows").glob("*.yml")
|
|
||||||
):
|
|
||||||
contents = workflow.read_text(encoding="utf-8")
|
contents = workflow.read_text(encoding="utf-8")
|
||||||
if (
|
if (
|
||||||
"bootstrap-repositories.py" not in contents
|
"bootstrap-repositories.py" not in contents
|
||||||
@@ -382,6 +431,7 @@ class RepositoryBootstrapTests(unittest.TestCase):
|
|||||||
):
|
):
|
||||||
continue
|
continue
|
||||||
with self.subTest(workflow=workflow.name):
|
with self.subTest(workflow=workflow.name):
|
||||||
|
self.assertIn("--reuse-checkout-auth", contents)
|
||||||
for repository in registered_only:
|
for repository in registered_only:
|
||||||
self.assertIn(
|
self.assertIn(
|
||||||
f"--exclude-repo {repository}",
|
f"--exclude-repo {repository}",
|
||||||
|
|||||||
@@ -16,15 +16,20 @@ REGISTERED_TRANSPORT = "registered"
|
|||||||
PUBLIC_HTTPS_TRANSPORT = "public-https"
|
PUBLIC_HTTPS_TRANSPORT = "public-https"
|
||||||
GITEA_SSH_PREFIX = "git@git.add-ideas.de:"
|
GITEA_SSH_PREFIX = "git@git.add-ideas.de:"
|
||||||
GITEA_HTTPS_PREFIX = "https://git.add-ideas.de/"
|
GITEA_HTTPS_PREFIX = "https://git.add-ideas.de/"
|
||||||
GITEA_REPOSITORY_PATH = re.compile(
|
GITEA_CHECKOUT_AUTH_KEY = "http.https://git.add-ideas.de/.extraheader"
|
||||||
r"(?:GovOPlaN|add-ideas)/[a-z0-9][a-z0-9-]*[.]git"
|
GITEA_REPOSITORY_PATH = re.compile(r"(?:GovOPlaN|add-ideas)/[a-z0-9][a-z0-9-]*[.]git")
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
|
def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
|
||||||
parser = argparse.ArgumentParser(description="Clone GovOPlaN repositories listed in repositories.json.")
|
parser = argparse.ArgumentParser(
|
||||||
parser.add_argument("--check", action="store_true", help="Only report missing repositories.")
|
description="Clone GovOPlaN repositories listed in repositories.json."
|
||||||
parser.add_argument("--parent", type=Path, help="Override checkout parent directory.")
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--check", action="store_true", help="Only report missing repositories."
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--parent", type=Path, help="Override checkout parent directory."
|
||||||
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--repo",
|
"--repo",
|
||||||
action="append",
|
action="append",
|
||||||
@@ -48,6 +53,14 @@ def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
|
|||||||
"git.add-ideas.de SSH remotes to anonymous HTTPS."
|
"git.add-ideas.de SSH remotes to anonymous HTTPS."
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--reuse-checkout-auth",
|
||||||
|
action="store_true",
|
||||||
|
help=(
|
||||||
|
"Reuse the checkout repository's short-lived Gitea HTTP auth "
|
||||||
|
"header for child clones without printing or persisting it."
|
||||||
|
),
|
||||||
|
)
|
||||||
return parser.parse_args(argv)
|
return parser.parse_args(argv)
|
||||||
|
|
||||||
|
|
||||||
@@ -82,6 +95,45 @@ def clone_remote(remote: str, *, transport: str) -> str:
|
|||||||
return GITEA_HTTPS_PREFIX + repository_path
|
return GITEA_HTTPS_PREFIX + repository_path
|
||||||
|
|
||||||
|
|
||||||
|
def _add_checkout_auth(environment: dict[str, str], *, root: Path) -> None:
|
||||||
|
result = subprocess.run(
|
||||||
|
[
|
||||||
|
"git",
|
||||||
|
"-C",
|
||||||
|
str(root),
|
||||||
|
"config",
|
||||||
|
"--local",
|
||||||
|
"--get",
|
||||||
|
GITEA_CHECKOUT_AUTH_KEY,
|
||||||
|
],
|
||||||
|
check=False,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
)
|
||||||
|
auth_header = result.stdout.strip()
|
||||||
|
if result.returncode != 0 or not auth_header:
|
||||||
|
raise ValueError(
|
||||||
|
"checkout authentication is unavailable; ensure actions/checkout "
|
||||||
|
"persists the GITEA_TOKEN credentials"
|
||||||
|
)
|
||||||
|
if re.fullmatch(
|
||||||
|
r"authorization: basic [A-Za-z0-9+/=]+",
|
||||||
|
auth_header,
|
||||||
|
flags=re.IGNORECASE,
|
||||||
|
) is None:
|
||||||
|
raise ValueError("checkout authentication has an unsupported format")
|
||||||
|
|
||||||
|
try:
|
||||||
|
config_count = int(environment.get("GIT_CONFIG_COUNT", "0"))
|
||||||
|
except ValueError as exc:
|
||||||
|
raise ValueError("GIT_CONFIG_COUNT must be an integer") from exc
|
||||||
|
if config_count < 0:
|
||||||
|
raise ValueError("GIT_CONFIG_COUNT cannot be negative")
|
||||||
|
environment[f"GIT_CONFIG_KEY_{config_count}"] = GITEA_CHECKOUT_AUTH_KEY
|
||||||
|
environment[f"GIT_CONFIG_VALUE_{config_count}"] = auth_header
|
||||||
|
environment["GIT_CONFIG_COUNT"] = str(config_count + 1)
|
||||||
|
|
||||||
|
|
||||||
def main(argv: list[str] | None = None) -> int:
|
def main(argv: list[str] | None = None) -> int:
|
||||||
args = parse_args(argv)
|
args = parse_args(argv)
|
||||||
manifest = json.loads((ROOT / "repositories.json").read_text(encoding="utf-8"))
|
manifest = json.loads((ROOT / "repositories.json").read_text(encoding="utf-8"))
|
||||||
@@ -95,7 +147,9 @@ def main(argv: list[str] | None = None) -> int:
|
|||||||
excluded = set(args.exclude_repo)
|
excluded = set(args.exclude_repo)
|
||||||
unknown = (requested | excluded) - set(names)
|
unknown = (requested | excluded) - set(names)
|
||||||
if unknown:
|
if unknown:
|
||||||
raise ValueError(f"unknown registered repositories: {', '.join(sorted(unknown))}")
|
raise ValueError(
|
||||||
|
f"unknown registered repositories: {', '.join(sorted(unknown))}"
|
||||||
|
)
|
||||||
overlap = requested & excluded
|
overlap = requested & excluded
|
||||||
if overlap:
|
if overlap:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
@@ -119,6 +173,8 @@ def main(argv: list[str] | None = None) -> int:
|
|||||||
"GIT_TERMINAL_PROMPT": "0",
|
"GIT_TERMINAL_PROMPT": "0",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
if args.reuse_checkout_auth and missing and not args.check:
|
||||||
|
_add_checkout_auth(environment, root=ROOT)
|
||||||
for entry, repo, remote in missing:
|
for entry, repo, remote in missing:
|
||||||
print(f"missing: {entry['name']} -> {repo}")
|
print(f"missing: {entry['name']} -> {repo}")
|
||||||
if not args.check:
|
if not args.check:
|
||||||
|
|||||||
Reference in New Issue
Block a user