// Copyright (c) The Geekeey Authors // SPDX-License-Identifier: EUPL-1.2 using System.CommandLine; using System.Text; namespace Geekeey.Actions.Core.Commands; internal sealed class Checkout : Command { #pragma warning disable format // @formatter:off private static readonly Option Repository = new("--repository") { Required = true }; private static readonly Option Destination = new("--path") { Required = true }; private static readonly Option Reference = new("--reference") { Required = true }; #pragma warning restore format // @formatter:on internal Checkout() : base("checkout") { Add(Repository); Add(Destination); Add(Reference); SetAction(HandleAsync); } private async Task HandleAsync(ParseResult result, CancellationToken cancellationToken) { var server = result.GetRequiredValue(Program.Server); var access = result.GetRequiredValue(Program.Token); var workspace = result.GetRequiredValue(Destination); await $"git init -q {workspace.FullName}"; await $"git -C {workspace.FullName} config set --local protocol.version 2"; await $"git -C {workspace.FullName} config set --local gc.auto 0"; var repository = result.GetRequiredValue(Repository); if (!repository.IsAbsoluteUri) { // relative repository urls are resolved against the server url repository = new Uri(server, repository); await $"git -C {workspace.FullName} config set --local --append url.{repository}.insteadOf git@{repository.Host}"; await $"git -C {workspace.FullName} config set --local --append url.{repository}.insteadOf ssh://git@{repository.Host}"; await $"git -C {workspace.FullName} config set --local --append url.{repository}.insteadOf git://{repository.Host}"; var header = $"Authorization: Basic {Convert.ToBase64String(Encoding.UTF8.GetBytes($"x-access-token::${access}"))}"; await $"git -C {workspace.FullName} config set --local http.{repository}.extraheader '{header}'"; } var origin = "origin"; var reference = result.GetRequiredValue(Reference); await $"git -C {workspace.FullName} remote add {origin} {repository}"; var list = (await $"git -C {workspace.FullName} ls-remote {origin} {reference}").Split('\t'); if (list.Length < 2) { throw new InvalidOperationException("git ls-remote resolved nothing"); } var @sha = list[0].Trim(); var @ref = list[1].Trim(); if (@ref.TryCutPrefix("refs/heads/", out var name)) { var remote = $"refs/remotes/{origin}/{name}"; var branch = name; await $"git -C {workspace.FullName} fetch --no-tags --prune --no-recurse-submodules --depth=1 {origin} +{@sha}:{remote}"; await $"git -C {workspace.FullName} checkout --force -B {branch} {remote}"; } else if (@ref.TryCutPrefix("refs/pull/", out name)) { var remote = $"refs/remotes/pull/{name}"; // Best-effort parity with the Go code's env.BaseRef/env.HeadRef: var baseRef = Environment.GetEnvironmentVariable("GITHUB_BASE_REF"); var headRef = Environment.GetEnvironmentVariable("GITHUB_HEAD_REF"); var branch = !string.IsNullOrEmpty(baseRef) ? baseRef : !string.IsNullOrEmpty(headRef) ? headRef : throw new InvalidOperationException("pull request can not find base ref for branch"); await $"git -C {workspace.FullName} fetch --no-tags --prune --no-recurse-submodules --depth=1 {origin} +{@sha}:{remote}"; await $"git -C {workspace.FullName} checkout --force -B {branch} {branch}"; } else if (@ref.TryCutPrefix("refs/tags/", out name)) { var remote = $"refs/tags/{name}"; await $"git -C {workspace.FullName} fetch --no-tags --prune --no-recurse-submodules --depth=1 {origin} +{@sha}:{remote}"; await $"git -C {workspace.FullName} checkout --force {remote}"; } else { await $"git -C {workspace.FullName} fetch --no-tags --prune --no-recurse-submodules --depth=1 {origin} {@ref}"; await $"git -C {workspace.FullName} checkout --force {@ref}"; } return 0; } }