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