Monorepo for Tangled
tangled.org
1package models
2
3import (
4 "fmt"
5 "strings"
6
7 "tangled.org/core/api/tangled"
8 "tangled.org/core/hostutil"
9 "tangled.org/core/workflow"
10)
11
12type CloneStep struct {
13 name string
14 kind StepKind
15 commands []string
16}
17
18func (s CloneStep) Name() string {
19 return s.name
20}
21
22func (s CloneStep) Commands() []string {
23 return s.commands
24}
25
26func (s CloneStep) Command() string {
27 return strings.Join(s.commands, "\n")
28}
29
30func (s CloneStep) Kind() StepKind {
31 return s.kind
32}
33
34// BuildCloneStep generates git clone commands.
35// The caller must ensure the current working directory is set to the desired
36// workspace directory before executing these commands.
37//
38// The generated commands are:
39// - git init
40// - git remote add origin <url>
41// - git fetch --depth=<d> --recurse-submodules=<yes|no> <sha>
42// - git checkout FETCH_HEAD
43//
44// Supports all trigger types (push, PR, manual) and clone options.
45func BuildCloneStep(twf tangled.Pipeline_Workflow, tr tangled.Pipeline_TriggerMetadata, dev bool) CloneStep {
46 if twf.Clone != nil && twf.Clone.Skip {
47 return CloneStep{}
48 }
49
50 commitSHA, err := extractCommitSHA(tr)
51 if err != nil {
52 return CloneStep{
53 kind: StepKindSystem,
54 name: "Clone repository into workspace (error)",
55 commands: []string{fmt.Sprintf("echo 'Failed to get clone info: %s' && exit 1", err.Error())},
56 }
57 }
58
59 repoURL := BuildRepoURL(tr.Repo)
60
61 var cloneOpts tangled.Pipeline_CloneOpts
62 if twf.Clone != nil {
63 cloneOpts = *twf.Clone
64 }
65 fetchArgs := buildFetchArgs(cloneOpts, commitSHA)
66
67 // In dev mode we point at Caddy via host-gateway with a self-signed cert,
68 // so skip the TLS check for the fetch call.
69 fetchCmd := "git fetch"
70 if dev {
71 fetchCmd = "git -c http.sslVerify=false fetch"
72 }
73
74 return CloneStep{
75 kind: StepKindSystem,
76 name: "Clone repository into workspace",
77 commands: []string{
78 "git init",
79 fmt.Sprintf("git remote add origin %s", repoURL),
80 fmt.Sprintf("%s %s", fetchCmd, strings.Join(fetchArgs, " ")),
81 "git checkout FETCH_HEAD",
82 },
83 }
84}
85
86// extractCommitSHA extracts the commit SHA from trigger metadata based on trigger type
87func extractCommitSHA(tr tangled.Pipeline_TriggerMetadata) (string, error) {
88 switch workflow.TriggerKind(tr.Kind) {
89 case workflow.TriggerKindPush:
90 if tr.Push == nil {
91 return "", fmt.Errorf("push trigger metadata is nil")
92 }
93 return tr.Push.NewSha, nil
94
95 case workflow.TriggerKindPullRequest:
96 if tr.PullRequest == nil {
97 return "", fmt.Errorf("pull request trigger metadata is nil")
98 }
99 return tr.PullRequest.SourceSha, nil
100
101 case workflow.TriggerKindManual:
102 // Manual triggers don't have an explicit SHA in the metadata
103 // For now, return empty string - could be enhanced to fetch from default branch
104 // TODO: Implement manual trigger SHA resolution (fetch default branch HEAD)
105 return "", nil
106
107 default:
108 return "", fmt.Errorf("unknown trigger kind: %s", tr.Kind)
109 }
110}
111
112// BuildRepoURL constructs the repository URL from repo metadata.
113func BuildRepoURL(repo *tangled.Pipeline_TriggerRepo) string {
114 if repo == nil {
115 return ""
116 }
117
118 host, noSSL, _ := hostutil.ParseHostname(repo.Knot)
119 scheme := "https"
120 if noSSL {
121 scheme = "http"
122 }
123
124 return fmt.Sprintf("%s://%s/%s", scheme, host, *repo.RepoDid)
125}
126
127// buildFetchArgs constructs the arguments for git fetch based on clone options
128func buildFetchArgs(clone tangled.Pipeline_CloneOpts, sha string) []string {
129 args := []string{}
130
131 // Set fetch depth (default to 1 for shallow clone)
132 depth := clone.Depth
133 if depth == 0 {
134 depth = 1
135 }
136 args = append(args, fmt.Sprintf("--depth=%d", depth))
137
138 // Add submodules if requested
139 if clone.Submodules {
140 args = append(args, "--recurse-submodules=yes")
141 }
142
143 // Add tags if requested
144 if clone.Tags {
145 args = append(args, "--tags")
146 }
147
148 // Add remote and SHA
149 args = append(args, "origin")
150 if sha != "" {
151 args = append(args, sha)
152 }
153
154 return args
155}