~james/core

core/spindle/models/clone.go -rw-r--r-- 4.1 KiB
452f17b7 — james chore(import): snapshot james.dad.town/core at f70c71c7696a 14 hours ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
package models

import (
	"fmt"
	"strings"

	"tangled.org/core/hostutil"
	"tangled.org/core/workflow"
)

type CloneStep struct {
	name     string
	kind     StepKind
	commands []string
}

func (s CloneStep) Name() string {
	return s.name
}

func (s CloneStep) Commands() []string {
	return s.commands
}

func (s CloneStep) Command() string {
	return strings.Join(s.commands, "\n")
}

func (s CloneStep) Kind() StepKind {
	return s.kind
}

// BuildCloneStep generates git clone commands.
// The caller must ensure the current working directory is set to the desired
// workspace directory before executing these commands.
//
// The generated commands are:
// - git init --object-format=sha1|sha256
// - git remote add origin <url>
// - git fetch --depth=<d> --recurse-submodules=<yes|no> <sha>
// - git checkout FETCH_HEAD
//
// Supports all trigger types (push, PR, manual) and clone options.
func BuildCloneStep(twf workflow.WorkflowSpec, tr workflow.TriggerMetadata, dev bool) CloneStep {
	if twf.Clone != nil && twf.Clone.Skip {
		return CloneStep{}
	}

	commitSHA, err := ExtractCommitSHA(tr)
	if err != nil {
		return CloneStep{
			kind:     StepKindSystem,
			name:     "Clone repository into workspace (error)",
			commands: []string{fmt.Sprintf("echo 'Failed to get clone info: %s' && exit 1", err.Error())},
		}
	}

	var objectFormat string
	switch len(commitSHA) {
	case 40:
		objectFormat = "sha1"
	case 64:
		objectFormat = "sha256"
	default:
		return CloneStep{
			kind:     StepKindSystem,
			name:     "Clone repository into workspace (error)",
			commands: []string{"echo 'Failed to determine repo object format' && exit 1"},
		}
	}

	repoURL := BuildRepoURL(tr.Repo)

	var cloneOpts workflow.CloneSpec
	if twf.Clone != nil {
		cloneOpts = *twf.Clone
	}
	fetchArgs := buildFetchArgs(cloneOpts, commitSHA)

	// In dev mode we point at Caddy via host-gateway with a self-signed cert,
	// so skip the TLS check for the fetch call.
	fetchCmd := "git fetch"
	if dev {
		fetchCmd = "git -c http.sslVerify=false fetch"
	}

	return CloneStep{
		kind: StepKindSystem,
		name: "Clone repository into workspace",
		commands: []string{
			fmt.Sprintf("git init --object-format=%s", objectFormat),
			fmt.Sprintf("git remote add origin %s", repoURL),
			fmt.Sprintf("%s %s", fetchCmd, strings.Join(fetchArgs, " ")),
			"git checkout FETCH_HEAD",
		},
	}
}

func ExtractCommitSHA(tr workflow.TriggerMetadata) (string, error) {
	switch workflow.TriggerKind(tr.Kind) {
	case workflow.TriggerKindPush:
		if tr.Push == nil {
			return "", fmt.Errorf("push trigger metadata is nil")
		}
		return tr.Push.NewSha, nil

	case workflow.TriggerKindPullRequest:
		if tr.PullRequest == nil {
			return "", fmt.Errorf("pull request trigger metadata is nil")
		}
		return tr.PullRequest.SourceSha, nil

	case workflow.TriggerKindManual:
		if tr.Manual == nil {
			return "", fmt.Errorf("manual trigger metadata is nil")
		}
		return tr.Manual.Sha, nil

	case workflow.TriggerKindSchedule:
		if tr.Schedule == nil {
			return "", fmt.Errorf("schedule trigger metadata is nil")
		}
		return tr.Schedule.Sha, nil

	default:
		return "", fmt.Errorf("unknown trigger kind: %s", tr.Kind)
	}
}

// BuildRepoURL constructs the repository URL from repo metadata.
func BuildRepoURL(repo *workflow.TriggerRepo) string {
	if repo == nil || repo.RepoDid == nil {
		return ""
	}
	host, noSSL, _ := hostutil.ParseHostname(repo.Knot)
	scheme := "https"
	if noSSL {
		scheme = "http"
	}
	return fmt.Sprintf("%s://%s/%s", scheme, host, *repo.RepoDid)
}

// buildFetchArgs constructs the arguments for git fetch based on clone options
func buildFetchArgs(clone workflow.CloneSpec, sha string) []string {
	args := []string{}

	// Set fetch depth (default to 1 for shallow clone)
	depth := clone.Depth
	if depth == 0 {
		depth = 1
	}
	args = append(args, fmt.Sprintf("--depth=%d", depth))

	// Add submodules if requested
	if clone.Submodules {
		args = append(args, "--recurse-submodules=yes")
	}

	// Add tags if requested
	if clone.Tags {
		args = append(args, "--tags")
	}

	// Add remote and SHA
	args = append(args, "origin")
	if sha != "" {
		args = append(args, sha)
	}

	return args
}