Initial commit on Forgejo
Fresh repository history for elektrine/tarakan-client hosted at https://git.elektrine.com/elektrine/tarakan-client.
This commit is contained in:
commit
58b29a7f84
70 changed files with 12994 additions and 0 deletions
175
internal/app/worktree.go
Normal file
175
internal/app/worktree.go
Normal file
|
|
@ -0,0 +1,175 @@
|
|||
package app
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/atomine-elektrine/tarakan-client/internal/api"
|
||||
repoctx "github.com/atomine-elektrine/tarakan-client/internal/context"
|
||||
)
|
||||
|
||||
// worktreeForTask returns a git repository root that contains the job's pinned
|
||||
// commit. Uses the local clone when origin matches; otherwise clones from the
|
||||
// job's host (GitHub, Tarakan-hosted, or CanonicalURL) into a temp directory.
|
||||
//
|
||||
// apiBase is TARAKAN_URL (e.g. http://localhost:4000) used for Tarakan-hosted
|
||||
// clones when CanonicalURL points at the public host but git is served locally.
|
||||
// report is optional; non-empty lines are status updates for the TUI.
|
||||
func worktreeForTask(local repoctx.Info, task api.Task, apiBase string, report func(string)) (root string, cleanup func(), err error) {
|
||||
if report == nil {
|
||||
report = func(string) {}
|
||||
}
|
||||
cleanup = func() {}
|
||||
if len(task.CommitSHA) != 40 {
|
||||
return "", cleanup, fmt.Errorf("job %d has no full commit SHA to pin", task.ID)
|
||||
}
|
||||
slug := task.Repository.Slug()
|
||||
if local.IsGit && localMatchesTask(local, task) {
|
||||
report("Using local clone of " + slug + " at " + local.Root)
|
||||
return local.Root, cleanup, nil
|
||||
}
|
||||
|
||||
owner := task.Repository.Owner
|
||||
name := task.Repository.Name
|
||||
if owner == "" || name == "" {
|
||||
return "", cleanup, fmt.Errorf("job %d has no repository identity", task.ID)
|
||||
}
|
||||
|
||||
remote, err := cloneRemoteURL(task.Repository, apiBase)
|
||||
if err != nil {
|
||||
return "", cleanup, err
|
||||
}
|
||||
|
||||
report("Cloning " + slug + " from " + remote + "…")
|
||||
base, err := os.MkdirTemp("", "tarakan-pickup-")
|
||||
if err != nil {
|
||||
return "", cleanup, fmt.Errorf("create temp worktree: %w", err)
|
||||
}
|
||||
cleanup = func() { _ = os.RemoveAll(base) }
|
||||
|
||||
repoDir := filepath.Join(base, "repository")
|
||||
if err := runGit("", "clone", "--filter=blob:none", "--no-checkout", remote, repoDir); err != nil {
|
||||
cleanup()
|
||||
return "", func() {}, fmt.Errorf("clone %s for job %d: %w\n(hint: cd into a clone of %s/%s and re-run)", remote, task.ID, err, owner, name)
|
||||
}
|
||||
report("Fetching pinned commit " + shortSHA(task.CommitSHA) + "…")
|
||||
if err := runGit(repoDir, "fetch", "--depth", "1", "origin", task.CommitSHA); err != nil {
|
||||
if err2 := runGit(repoDir, "fetch", "origin", task.CommitSHA); err2 != nil {
|
||||
cleanup()
|
||||
return "", func() {}, fmt.Errorf("fetch commit %s: %w", shortSHA(task.CommitSHA), err2)
|
||||
}
|
||||
}
|
||||
report("Checking out " + shortSHA(task.CommitSHA) + "…")
|
||||
if err := runGit(repoDir, "checkout", "--detach", "--force", task.CommitSHA); err != nil {
|
||||
cleanup()
|
||||
return "", func() {}, fmt.Errorf("checkout %s: %w", shortSHA(task.CommitSHA), err)
|
||||
}
|
||||
report("Worktree ready for " + slug + " @ " + shortSHA(task.CommitSHA))
|
||||
return repoDir, cleanup, nil
|
||||
}
|
||||
|
||||
func localMatchesTask(local repoctx.Info, task api.Task) bool {
|
||||
owner, name, ok := local.RemoteSlug()
|
||||
if !ok {
|
||||
owner, name = local.GitHubOwner, local.GitHubName
|
||||
if owner == "" || name == "" {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if !strings.EqualFold(owner, task.Repository.Owner) || !strings.EqualFold(name, task.Repository.Name) {
|
||||
return false
|
||||
}
|
||||
taskHost := normalizeTaskHost(task.Repository.Host)
|
||||
localHost := strings.ToLower(local.Host)
|
||||
if taskHost == "" || localHost == "" {
|
||||
// Owner/name match is enough when one side lacks host.
|
||||
return true
|
||||
}
|
||||
if taskHost == localHost {
|
||||
return true
|
||||
}
|
||||
// Local clone of hosted repo via localhost vs public tarakan.lol.
|
||||
if isTarakanHost(taskHost) && (isTarakanHost(localHost) || isLoopback(localHost)) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func cloneRemoteURL(repo api.Repository, apiBase string) (string, error) {
|
||||
host := normalizeTaskHost(repo.Host)
|
||||
owner, name := repo.Owner, repo.Name
|
||||
|
||||
// Prefer live Tarakan base for hosted repos (dev often uses localhost while
|
||||
// canonical_url says https://tarakan.lol/…).
|
||||
if isTarakanHost(host) || host == "" && strings.Contains(strings.ToLower(repo.CanonicalURL), "tarakan") {
|
||||
if base := strings.TrimRight(strings.TrimSpace(apiBase), "/"); base != "" {
|
||||
return base + "/" + owner + "/" + name + ".git", nil
|
||||
}
|
||||
}
|
||||
|
||||
if u := strings.TrimSpace(repo.CanonicalURL); u != "" {
|
||||
u = strings.TrimRight(u, "/")
|
||||
if !strings.HasSuffix(strings.ToLower(u), ".git") {
|
||||
u += ".git"
|
||||
}
|
||||
if _, err := url.Parse(u); err == nil {
|
||||
return u, nil
|
||||
}
|
||||
}
|
||||
|
||||
switch {
|
||||
case host == "" || host == "github.com":
|
||||
return "https://github.com/" + owner + "/" + name + ".git", nil
|
||||
case isTarakanHost(host):
|
||||
return "https://tarakan.lol/" + owner + "/" + name + ".git", nil
|
||||
default:
|
||||
return "", fmt.Errorf("job host %q is not supported for auto-clone; clone %s/%s locally and re-run", repo.Host, owner, name)
|
||||
}
|
||||
}
|
||||
|
||||
func normalizeTaskHost(host string) string {
|
||||
h := strings.ToLower(strings.TrimSpace(host))
|
||||
switch h {
|
||||
case "github", "www.github.com":
|
||||
return "github.com"
|
||||
case "tarakan", "www.tarakan.lol":
|
||||
return "tarakan.lol"
|
||||
default:
|
||||
return h
|
||||
}
|
||||
}
|
||||
|
||||
func isTarakanHost(host string) bool {
|
||||
h := normalizeTaskHost(host)
|
||||
return h == "tarakan.lol"
|
||||
}
|
||||
|
||||
func isLoopback(host string) bool {
|
||||
h := strings.ToLower(host)
|
||||
return h == "localhost" || h == "127.0.0.1" || h == "::1"
|
||||
}
|
||||
|
||||
func runGit(dir string, args ...string) error {
|
||||
cmd := exec.Command("git", args...)
|
||||
if dir != "" {
|
||||
cmd.Dir = dir
|
||||
}
|
||||
cmd.Env = append(os.Environ(),
|
||||
"GIT_TERMINAL_PROMPT=0",
|
||||
"GIT_CONFIG_NOSYSTEM=1",
|
||||
"GCM_INTERACTIVE=never",
|
||||
)
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
msg := strings.TrimSpace(string(output))
|
||||
if msg == "" {
|
||||
return err
|
||||
}
|
||||
return fmt.Errorf("%w: %s", err, msg)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue