diff --git a/README.md b/README.md index 53e8393..df4b4b9 100644 --- a/README.md +++ b/README.md @@ -59,22 +59,24 @@ authenticated with write access here): ```sh # Windows, with the signing token in the environment: -python build.py --package --publish v2.47.0.anchorpoint.1 +python build.py --package --publish -# macOS — Apple Silicon, then Intel: -python build.py --package --arch arm64 --publish v2.47.0.anchorpoint.1 -python build.py --package --arch x86_64 --publish v2.47.0.anchorpoint.1 +# macOS — Apple Silicon, then Intel (notarytool leaves the bytes unchanged): +python build.py --package --arch arm64 --publish +xcrun notarytool submit dist/git-macos-arm64.zip --keychain-profile --wait +python build.py --package --arch x86_64 --publish +xcrun notarytool submit dist/git-macos-x64.zip --keychain-profile --wait ``` -Tag scheme `v.anchorpoint.` (e.g. `v2.47.0.anchorpoint.1`), like -Git for Windows' `.windows.N` — the upstream Git version plus an Anchorpoint -build number; bump `.anchorpoint.` when you re-cut the same Git version (e.g. -a `git-lfs` fork or gitconfig change), with the exact fork commit in the release -notes. `--publish` creates the release for the tag -if it doesn't exist, then uploads this machine's archives (clobbering a prior -upload of the same name), so each host adds its assets to the same release. -macOS notarization (`xcrun notarytool submit dist/.zip --wait`, then -staple) runs on the Mac before publishing. Consumers pin a tag and verify the +`--publish` auto-derives the tag `v.anchorpoint.` (GfW-style, like +Git for Windows' `.windows.N`) from the Git version it just built: it reuses the +highest existing `…anchorpoint.` for that Git version, or starts at `.1`. So +the first machine creates `…anchorpoint.1` and the others upload into the *same* +release (clobbering a prior upload of the same name) — all three archives land on +one tag. To re-cut the same Git version (e.g. a `git-lfs` fork or gitconfig +change), run the first machine with `--bump` (→ `…anchorpoint.2`) and the rest +plain `--publish`; override entirely with `--tag v2.47.0.anchorpoint.3`. Record +the exact fork commit in the release notes; consumers pin a tag and verify the `.sha256`. CI (`.github/workflows/ci.yml`) only compile-checks the `git-lfs` fork across diff --git a/build.py b/build.py index 66424c5..7ad039f 100644 --- a/build.py +++ b/build.py @@ -264,6 +264,56 @@ def package(dest: str, asset_name: str) -> None: # --------------------------------------------------------------------------- # # Publishing (local: each build host uploads its own assets to the shared tag) # --------------------------------------------------------------------------- # +def built_git_version(dest: str) -> str: + """Read the upstream Git version (e.g. "2.47.0") from the freshly-built git.""" + candidates = [ + os.path.join(dest, "cmd", "git.exe"), + os.path.join(dest, "mingw64", "bin", "git.exe"), + os.path.join(dest, "bin", "git"), + ] + gitbin = next((c for c in candidates if os.path.exists(c)), None) + if not gitbin: + raise SystemExit("[git-dist] cannot find built git to derive the version") + out = _capture([gitbin, "--version"]) # "git version 2.47.0[.windows.1]" + token = out.split()[-1] if out.split() else "" + nums = [] + for part in token.split("."): + if part.isdigit(): + nums.append(part) + else: + break + if len(nums) < 3: + raise SystemExit(f"[git-dist] could not parse git version from {out!r}") + return ".".join(nums[:3]) + + +def compute_tag(dest: str, bump: bool) -> str: + """Auto-derive v.anchorpoint. (GfW-style). reuses the highest + existing build for this Git version, or starts at 1; `bump` forces a new + number for a re-cut of the same Git version.""" + gitver = built_git_version(dest) + base = f"v{gitver}.anchorpoint" + out = _capture([ + "gh", "release", "list", "--limit", "200", + "--json", "tagName", "--jq", ".[].tagName", + ]) + prefix = base + "." + existing = sorted( + int(line[len(prefix):]) + for line in out.splitlines() + if line.startswith(prefix) and line[len(prefix):].isdigit() + ) + if not existing: + n = 1 + elif bump: + n = existing[-1] + 1 + else: + n = existing[-1] + tag = f"{base}.{n}" + print(f"[git-dist] auto tag: {tag} (git {gitver}; existing builds: {existing or 'none'})") + return tag + + def publish(tag: str, asset: str) -> None: files = [ os.path.join(ROOT, "dist", f"{asset}.zip"), @@ -336,6 +386,19 @@ def _run(args, cwd=None, env=None) -> int: return subprocess.call(args, cwd=cwd, env=env, **kwargs) +def _capture(args, cwd=None) -> str: + import subprocess + kwargs = {} + if platform.system() == "Windows": + kwargs["creationflags"] = 0x08000000 # CREATE_NO_WINDOW + try: + return subprocess.check_output( + args, cwd=cwd, text=True, stderr=subprocess.DEVNULL, **kwargs + ) + except Exception: + return "" + + def _run_shell(cmd: str) -> int: return os.system(cmd) @@ -349,8 +412,12 @@ def main() -> None: parser.add_argument("--nosign", action="store_true", help="skip code signing") parser.add_argument("--arch", choices=["arm64", "x86_64"], default=None, help="macOS target arch (defaults to host)") - parser.add_argument("--publish", metavar="TAG", default=None, - help="after --package, create/upload the GitHub release TAG via gh") + parser.add_argument("--publish", action="store_true", + help="after --package, create/upload the GitHub release via gh (auto-tagged)") + parser.add_argument("--tag", default=None, + help="explicit release tag (default: auto v.anchorpoint.)") + parser.add_argument("--bump", action="store_true", + help="with auto-tag, start a new build number for this git version (re-cut)") args = parser.parse_args() config = load_config() @@ -379,7 +446,8 @@ def main() -> None: if args.publish: if not args.package: raise SystemExit("[git-dist] --publish requires --package") - publish(args.publish, asset) + tag = args.tag or compute_tag(dest, args.bump) + publish(tag, asset) print(f"[git-dist] done -> {dest}")