Monorepo for Tangled — https://tangled.org

move the clone step out of nixery into a shared function for all spindle engines

Signed-off-by: Evan Jarrett <evan@evanjarrett.com>

evan.jarrett.net c2f71549 bc70e9cf

verified
+637 -58
+23 -58
spindle/engines/nixery/setup_steps.go
··· 2 2 3 3 import ( 4 4 "fmt" 5 - "path" 6 5 "strings" 7 6 8 7 "tangled.org/core/api/tangled" 9 - "tangled.org/core/workflow" 8 + "tangled.org/core/spindle/steps" 10 9 ) 11 10 12 11 func nixConfStep() Step { ··· 19 18 } 20 19 } 21 20 22 - // cloneOptsAsSteps processes clone options and adds corresponding steps 23 - // to the beginning of the workflow's step list if cloning is not skipped. 24 - // 25 - // the steps to do here are: 21 + // cloneStep uses the shared clone step builder to generate git clone commands. 22 + // The shared builder handles: 26 23 // - git init 27 24 // - git remote add origin <url> 28 25 // - git fetch --depth=<d> --recurse-submodules=<yes|no> <sha> 29 26 // - git checkout FETCH_HEAD 27 + // And supports all trigger types (push, PR, manual) and clone options. 30 28 func cloneStep(twf tangled.Pipeline_Workflow, tr tangled.Pipeline_TriggerMetadata, dev bool) Step { 31 - if twf.Clone.Skip { 32 - return Step{} 29 + // Use shared clone command builder 30 + cmds, err := steps.BuildCloneCommands(steps.CloneConfig{ 31 + Workflow: twf, 32 + TriggerMetadata: tr, 33 + DevMode: dev, 34 + WorkspaceDir: workspaceDir, 35 + }) 36 + if err != nil { 37 + // Return error step - this will cause the workflow to fail with a clear message 38 + return Step{ 39 + command: fmt.Sprintf("echo 'Failed to build clone commands: %s' && exit 1", err.Error()), 40 + name: "Clone repository into workspace (error)", 41 + } 33 42 } 34 43 35 - var commands []string 36 - 37 - // initialize git repo in workspace 38 - commands = append(commands, "git init") 39 - 40 - // add repo as git remote 41 - scheme := "https://" 42 - if dev { 43 - scheme = "http://" 44 - tr.Repo.Knot = strings.ReplaceAll(tr.Repo.Knot, "localhost", "host.docker.internal") 44 + // Check if cloning should be skipped 45 + if cmds.Skip { 46 + return Step{} 45 47 } 46 - url := scheme + path.Join(tr.Repo.Knot, tr.Repo.Did, tr.Repo.Repo) 47 - commands = append(commands, fmt.Sprintf("git remote add origin %s", url)) 48 48 49 - // run git fetch 50 - { 51 - var fetchArgs []string 52 - 53 - // default clone depth is 1 54 - depth := 1 55 - if twf.Clone.Depth > 1 { 56 - depth = int(twf.Clone.Depth) 57 - } 58 - fetchArgs = append(fetchArgs, fmt.Sprintf("--depth=%d", depth)) 59 - 60 - // optionally recurse submodules 61 - if twf.Clone.Submodules { 62 - fetchArgs = append(fetchArgs, "--recurse-submodules=yes") 63 - } 64 - 65 - // set remote to fetch from 66 - fetchArgs = append(fetchArgs, "origin") 67 - 68 - // set revision to checkout 69 - switch workflow.TriggerKind(tr.Kind) { 70 - case workflow.TriggerKindManual: 71 - // TODO: unimplemented 72 - case workflow.TriggerKindPush: 73 - fetchArgs = append(fetchArgs, tr.Push.NewSha) 74 - case workflow.TriggerKindPullRequest: 75 - fetchArgs = append(fetchArgs, tr.PullRequest.SourceSha) 76 - } 77 - 78 - commands = append(commands, fmt.Sprintf("git fetch %s", strings.Join(fetchArgs, " "))) 79 - } 80 - 81 - // run git checkout 82 - commands = append(commands, "git checkout FETCH_HEAD") 83 - 84 - cloneStep := Step{ 85 - command: strings.Join(commands, "\n"), 49 + // Nixery-specific: join commands with newlines for shell execution 50 + return Step{ 51 + command: strings.Join(cmds.All, "\n"), 86 52 name: "Clone repository into workspace", 87 53 } 88 - return cloneStep 89 54 } 90 55 91 56 // dependencyStep processes dependencies defined in the workflow.
+142
spindle/steps/clone.go
··· 1 + package steps 2 + 3 + import ( 4 + "fmt" 5 + "strings" 6 + 7 + "tangled.org/core/api/tangled" 8 + "tangled.org/core/workflow" 9 + ) 10 + 11 + // BuildCloneCommands generates git clone commands from pipeline trigger metadata 12 + // Returns engine-agnostic command strings that engines can execute in their own way 13 + func BuildCloneCommands(cfg CloneConfig) (*CloneCommands, error) { 14 + // Check if cloning should be skipped 15 + if cfg.Workflow.Clone != nil && cfg.Workflow.Clone.Skip { 16 + return &CloneCommands{Skip: true}, nil 17 + } 18 + 19 + // Extract commit SHA based on trigger type 20 + commitSHA, err := extractCommitSHA(cfg.TriggerMetadata) 21 + if err != nil { 22 + return nil, fmt.Errorf("failed to extract commit SHA: %w", err) 23 + } 24 + 25 + // Build repository URL 26 + repoURL := buildRepoURL(cfg.TriggerMetadata, cfg.DevMode) 27 + 28 + // Set default workspace if not provided 29 + workspaceDir := cfg.WorkspaceDir 30 + if workspaceDir == "" { 31 + workspaceDir = "/tangled/workspace" 32 + } 33 + 34 + // Build individual git commands 35 + initCmd := fmt.Sprintf("git init %s", workspaceDir) 36 + remoteCmd := fmt.Sprintf("git remote add origin %s", repoURL) 37 + 38 + // Build fetch command with options 39 + var cloneOpts tangled.Pipeline_CloneOpts 40 + if cfg.Workflow.Clone != nil { 41 + cloneOpts = *cfg.Workflow.Clone 42 + } 43 + fetchArgs := buildFetchArgs(cloneOpts, commitSHA) 44 + fetchCmd := fmt.Sprintf("git fetch %s", strings.Join(fetchArgs, " ")) 45 + 46 + checkoutCmd := "git checkout FETCH_HEAD" 47 + 48 + // Combine all commands 49 + allCmds := []string{ 50 + initCmd, 51 + fmt.Sprintf("cd %s", workspaceDir), 52 + remoteCmd, 53 + fetchCmd, 54 + checkoutCmd, 55 + } 56 + 57 + return &CloneCommands{ 58 + Init: initCmd, 59 + Remote: remoteCmd, 60 + Fetch: fetchCmd, 61 + Checkout: checkoutCmd, 62 + All: allCmds, 63 + RepoURL: repoURL, 64 + CommitSHA: commitSHA, 65 + Skip: false, 66 + }, nil 67 + } 68 + 69 + // extractCommitSHA extracts the commit SHA from trigger metadata based on trigger type 70 + func extractCommitSHA(tr tangled.Pipeline_TriggerMetadata) (string, error) { 71 + switch workflow.TriggerKind(tr.Kind) { 72 + case workflow.TriggerKindPush: 73 + if tr.Push == nil { 74 + return "", fmt.Errorf("push trigger metadata is nil") 75 + } 76 + return tr.Push.NewSha, nil 77 + 78 + case workflow.TriggerKindPullRequest: 79 + if tr.PullRequest == nil { 80 + return "", fmt.Errorf("pull request trigger metadata is nil") 81 + } 82 + return tr.PullRequest.SourceSha, nil 83 + 84 + case workflow.TriggerKindManual: 85 + // Manual triggers don't have an explicit SHA in the metadata 86 + // For now, return empty string - could be enhanced to fetch from default branch 87 + // TODO: Implement manual trigger SHA resolution (fetch default branch HEAD) 88 + return "", nil 89 + 90 + default: 91 + return "", fmt.Errorf("unknown trigger kind: %s", tr.Kind) 92 + } 93 + } 94 + 95 + // buildRepoURL constructs the repository URL from trigger metadata 96 + func buildRepoURL(tr tangled.Pipeline_TriggerMetadata, devMode bool) string { 97 + if tr.Repo == nil { 98 + return "" 99 + } 100 + 101 + // Determine protocol 102 + scheme := "https://" 103 + if devMode { 104 + scheme = "http://" 105 + } 106 + 107 + // Get host from knot 108 + host := tr.Repo.Knot 109 + 110 + // In dev mode, replace localhost with host.docker.internal for Docker networking 111 + if devMode && strings.Contains(host, "localhost") { 112 + host = strings.ReplaceAll(host, "localhost", "host.docker.internal") 113 + } 114 + 115 + // Build URL: {scheme}{knot}/{did}/{repo} 116 + return fmt.Sprintf("%s%s/%s/%s", scheme, host, tr.Repo.Did, tr.Repo.Repo) 117 + } 118 + 119 + // buildFetchArgs constructs the arguments for git fetch based on clone options 120 + func buildFetchArgs(clone tangled.Pipeline_CloneOpts, sha string) []string { 121 + args := []string{} 122 + 123 + // Set fetch depth (default to 1 for shallow clone) 124 + depth := clone.Depth 125 + if depth == 0 { 126 + depth = 1 127 + } 128 + args = append(args, fmt.Sprintf("--depth=%d", depth)) 129 + 130 + // Add submodules if requested 131 + if clone.Submodules { 132 + args = append(args, "--recurse-submodules=yes") 133 + } 134 + 135 + // Add remote and SHA 136 + args = append(args, "origin") 137 + if sha != "" { 138 + args = append(args, sha) 139 + } 140 + 141 + return args 142 + }
+421
spindle/steps/clone_test.go
··· 1 + package steps 2 + 3 + import ( 4 + "strings" 5 + "testing" 6 + 7 + "tangled.org/core/api/tangled" 8 + "tangled.org/core/workflow" 9 + ) 10 + 11 + func TestBuildCloneCommands_PushTrigger(t *testing.T) { 12 + cfg := CloneConfig{ 13 + Workflow: tangled.Pipeline_Workflow{ 14 + Clone: &tangled.Pipeline_CloneOpts{ 15 + Depth: 1, 16 + Submodules: false, 17 + Skip: false, 18 + }, 19 + }, 20 + TriggerMetadata: tangled.Pipeline_TriggerMetadata{ 21 + Kind: string(workflow.TriggerKindPush), 22 + Push: &tangled.Pipeline_PushTriggerData{ 23 + NewSha: "abc123", 24 + OldSha: "def456", 25 + Ref: "refs/heads/main", 26 + }, 27 + Repo: &tangled.Pipeline_TriggerRepo{ 28 + Knot: "example.com", 29 + Did: "did:plc:user123", 30 + Repo: "my-repo", 31 + }, 32 + }, 33 + DevMode: false, 34 + WorkspaceDir: "/tangled/workspace", 35 + } 36 + 37 + cmds, err := BuildCloneCommands(cfg) 38 + if err != nil { 39 + t.Fatalf("BuildCloneCommands failed: %v", err) 40 + } 41 + 42 + if cmds.Skip { 43 + t.Error("Expected Skip to be false") 44 + } 45 + 46 + if cmds.CommitSHA != "abc123" { 47 + t.Errorf("Expected CommitSHA 'abc123', got '%s'", cmds.CommitSHA) 48 + } 49 + 50 + expectedURL := "https://example.com/did:plc:user123/my-repo" 51 + if cmds.RepoURL != expectedURL { 52 + t.Errorf("Expected RepoURL '%s', got '%s'", expectedURL, cmds.RepoURL) 53 + } 54 + 55 + if !strings.Contains(cmds.Init, "git init") { 56 + t.Error("Init command should contain 'git init'") 57 + } 58 + 59 + if !strings.Contains(cmds.Remote, "git remote add origin") { 60 + t.Error("Remote command should contain 'git remote add origin'") 61 + } 62 + 63 + if !strings.Contains(cmds.Fetch, "git fetch") { 64 + t.Error("Fetch command should contain 'git fetch'") 65 + } 66 + 67 + if !strings.Contains(cmds.Fetch, "abc123") { 68 + t.Error("Fetch command should contain commit SHA") 69 + } 70 + 71 + if cmds.Checkout != "git checkout FETCH_HEAD" { 72 + t.Errorf("Expected Checkout 'git checkout FETCH_HEAD', got '%s'", cmds.Checkout) 73 + } 74 + 75 + if len(cmds.All) != 5 { 76 + t.Errorf("Expected 5 commands, got %d", len(cmds.All)) 77 + } 78 + } 79 + 80 + func TestBuildCloneCommands_PullRequestTrigger(t *testing.T) { 81 + cfg := CloneConfig{ 82 + Workflow: tangled.Pipeline_Workflow{ 83 + Clone: &tangled.Pipeline_CloneOpts{ 84 + Depth: 1, 85 + Skip: false, 86 + }, 87 + }, 88 + TriggerMetadata: tangled.Pipeline_TriggerMetadata{ 89 + Kind: string(workflow.TriggerKindPullRequest), 90 + PullRequest: &tangled.Pipeline_PullRequestTriggerData{ 91 + SourceSha: "pr-sha-789", 92 + SourceBranch: "feature-branch", 93 + TargetBranch: "main", 94 + Action: "opened", 95 + }, 96 + Repo: &tangled.Pipeline_TriggerRepo{ 97 + Knot: "example.com", 98 + Did: "did:plc:user123", 99 + Repo: "my-repo", 100 + }, 101 + }, 102 + DevMode: false, 103 + WorkspaceDir: "/tangled/workspace", 104 + } 105 + 106 + cmds, err := BuildCloneCommands(cfg) 107 + if err != nil { 108 + t.Fatalf("BuildCloneCommands failed: %v", err) 109 + } 110 + 111 + if cmds.CommitSHA != "pr-sha-789" { 112 + t.Errorf("Expected CommitSHA 'pr-sha-789', got '%s'", cmds.CommitSHA) 113 + } 114 + 115 + if !strings.Contains(cmds.Fetch, "pr-sha-789") { 116 + t.Error("Fetch command should contain PR commit SHA") 117 + } 118 + } 119 + 120 + func TestBuildCloneCommands_ManualTrigger(t *testing.T) { 121 + cfg := CloneConfig{ 122 + Workflow: tangled.Pipeline_Workflow{ 123 + Clone: &tangled.Pipeline_CloneOpts{ 124 + Depth: 1, 125 + Skip: false, 126 + }, 127 + }, 128 + TriggerMetadata: tangled.Pipeline_TriggerMetadata{ 129 + Kind: string(workflow.TriggerKindManual), 130 + Manual: &tangled.Pipeline_ManualTriggerData{ 131 + Inputs: nil, 132 + }, 133 + Repo: &tangled.Pipeline_TriggerRepo{ 134 + Knot: "example.com", 135 + Did: "did:plc:user123", 136 + Repo: "my-repo", 137 + }, 138 + }, 139 + DevMode: false, 140 + WorkspaceDir: "/tangled/workspace", 141 + } 142 + 143 + cmds, err := BuildCloneCommands(cfg) 144 + if err != nil { 145 + t.Fatalf("BuildCloneCommands failed: %v", err) 146 + } 147 + 148 + // Manual triggers don't have a SHA yet (TODO) 149 + if cmds.CommitSHA != "" { 150 + t.Errorf("Expected empty CommitSHA for manual trigger, got '%s'", cmds.CommitSHA) 151 + } 152 + } 153 + 154 + func TestBuildCloneCommands_SkipFlag(t *testing.T) { 155 + cfg := CloneConfig{ 156 + Workflow: tangled.Pipeline_Workflow{ 157 + Clone: &tangled.Pipeline_CloneOpts{ 158 + Skip: true, 159 + }, 160 + }, 161 + TriggerMetadata: tangled.Pipeline_TriggerMetadata{ 162 + Kind: string(workflow.TriggerKindPush), 163 + Push: &tangled.Pipeline_PushTriggerData{ 164 + NewSha: "abc123", 165 + }, 166 + Repo: &tangled.Pipeline_TriggerRepo{ 167 + Knot: "example.com", 168 + Did: "did:plc:user123", 169 + Repo: "my-repo", 170 + }, 171 + }, 172 + } 173 + 174 + cmds, err := BuildCloneCommands(cfg) 175 + if err != nil { 176 + t.Fatalf("BuildCloneCommands failed: %v", err) 177 + } 178 + 179 + if !cmds.Skip { 180 + t.Error("Expected Skip to be true") 181 + } 182 + 183 + if len(cmds.All) != 0 { 184 + t.Errorf("Expected no commands when Skip is true, got %d commands", len(cmds.All)) 185 + } 186 + } 187 + 188 + func TestBuildCloneCommands_DevMode(t *testing.T) { 189 + cfg := CloneConfig{ 190 + Workflow: tangled.Pipeline_Workflow{ 191 + Clone: &tangled.Pipeline_CloneOpts{ 192 + Depth: 1, 193 + Skip: false, 194 + }, 195 + }, 196 + TriggerMetadata: tangled.Pipeline_TriggerMetadata{ 197 + Kind: string(workflow.TriggerKindPush), 198 + Push: &tangled.Pipeline_PushTriggerData{ 199 + NewSha: "abc123", 200 + }, 201 + Repo: &tangled.Pipeline_TriggerRepo{ 202 + Knot: "localhost:3000", 203 + Did: "did:plc:user123", 204 + Repo: "my-repo", 205 + }, 206 + }, 207 + DevMode: true, 208 + WorkspaceDir: "/tangled/workspace", 209 + } 210 + 211 + cmds, err := BuildCloneCommands(cfg) 212 + if err != nil { 213 + t.Fatalf("BuildCloneCommands failed: %v", err) 214 + } 215 + 216 + // In dev mode, should use http:// and replace localhost with host.docker.internal 217 + expectedURL := "http://host.docker.internal:3000/did:plc:user123/my-repo" 218 + if cmds.RepoURL != expectedURL { 219 + t.Errorf("Expected dev mode URL '%s', got '%s'", expectedURL, cmds.RepoURL) 220 + } 221 + } 222 + 223 + func TestBuildCloneCommands_DepthAndSubmodules(t *testing.T) { 224 + cfg := CloneConfig{ 225 + Workflow: tangled.Pipeline_Workflow{ 226 + Clone: &tangled.Pipeline_CloneOpts{ 227 + Depth: 10, 228 + Submodules: true, 229 + Skip: false, 230 + }, 231 + }, 232 + TriggerMetadata: tangled.Pipeline_TriggerMetadata{ 233 + Kind: string(workflow.TriggerKindPush), 234 + Push: &tangled.Pipeline_PushTriggerData{ 235 + NewSha: "abc123", 236 + }, 237 + Repo: &tangled.Pipeline_TriggerRepo{ 238 + Knot: "example.com", 239 + Did: "did:plc:user123", 240 + Repo: "my-repo", 241 + }, 242 + }, 243 + DevMode: false, 244 + WorkspaceDir: "/tangled/workspace", 245 + } 246 + 247 + cmds, err := BuildCloneCommands(cfg) 248 + if err != nil { 249 + t.Fatalf("BuildCloneCommands failed: %v", err) 250 + } 251 + 252 + if !strings.Contains(cmds.Fetch, "--depth=10") { 253 + t.Error("Fetch command should contain '--depth=10'") 254 + } 255 + 256 + if !strings.Contains(cmds.Fetch, "--recurse-submodules=yes") { 257 + t.Error("Fetch command should contain '--recurse-submodules=yes'") 258 + } 259 + } 260 + 261 + func TestBuildCloneCommands_DefaultDepth(t *testing.T) { 262 + cfg := CloneConfig{ 263 + Workflow: tangled.Pipeline_Workflow{ 264 + Clone: &tangled.Pipeline_CloneOpts{ 265 + Depth: 0, // Default should be 1 266 + Skip: false, 267 + }, 268 + }, 269 + TriggerMetadata: tangled.Pipeline_TriggerMetadata{ 270 + Kind: string(workflow.TriggerKindPush), 271 + Push: &tangled.Pipeline_PushTriggerData{ 272 + NewSha: "abc123", 273 + }, 274 + Repo: &tangled.Pipeline_TriggerRepo{ 275 + Knot: "example.com", 276 + Did: "did:plc:user123", 277 + Repo: "my-repo", 278 + }, 279 + }, 280 + WorkspaceDir: "/tangled/workspace", 281 + } 282 + 283 + cmds, err := BuildCloneCommands(cfg) 284 + if err != nil { 285 + t.Fatalf("BuildCloneCommands failed: %v", err) 286 + } 287 + 288 + if !strings.Contains(cmds.Fetch, "--depth=1") { 289 + t.Error("Fetch command should default to '--depth=1'") 290 + } 291 + } 292 + 293 + func TestBuildCloneCommands_NilPushData(t *testing.T) { 294 + cfg := CloneConfig{ 295 + Workflow: tangled.Pipeline_Workflow{ 296 + Clone: &tangled.Pipeline_CloneOpts{ 297 + Depth: 1, 298 + Skip: false, 299 + }, 300 + }, 301 + TriggerMetadata: tangled.Pipeline_TriggerMetadata{ 302 + Kind: string(workflow.TriggerKindPush), 303 + Push: nil, // Nil push data should return error 304 + Repo: &tangled.Pipeline_TriggerRepo{ 305 + Knot: "example.com", 306 + Did: "did:plc:user123", 307 + Repo: "my-repo", 308 + }, 309 + }, 310 + WorkspaceDir: "/tangled/workspace", 311 + } 312 + 313 + _, err := BuildCloneCommands(cfg) 314 + if err == nil { 315 + t.Error("Expected error when push data is nil") 316 + } 317 + 318 + if !strings.Contains(err.Error(), "push trigger metadata is nil") { 319 + t.Errorf("Expected error about nil push metadata, got: %v", err) 320 + } 321 + } 322 + 323 + func TestBuildCloneCommands_NilPRData(t *testing.T) { 324 + cfg := CloneConfig{ 325 + Workflow: tangled.Pipeline_Workflow{ 326 + Clone: &tangled.Pipeline_CloneOpts{ 327 + Depth: 1, 328 + Skip: false, 329 + }, 330 + }, 331 + TriggerMetadata: tangled.Pipeline_TriggerMetadata{ 332 + Kind: string(workflow.TriggerKindPullRequest), 333 + PullRequest: nil, // Nil PR data should return error 334 + Repo: &tangled.Pipeline_TriggerRepo{ 335 + Knot: "example.com", 336 + Did: "did:plc:user123", 337 + Repo: "my-repo", 338 + }, 339 + }, 340 + WorkspaceDir: "/tangled/workspace", 341 + } 342 + 343 + _, err := BuildCloneCommands(cfg) 344 + if err == nil { 345 + t.Error("Expected error when pull request data is nil") 346 + } 347 + 348 + if !strings.Contains(err.Error(), "pull request trigger metadata is nil") { 349 + t.Errorf("Expected error about nil PR metadata, got: %v", err) 350 + } 351 + } 352 + 353 + func TestBuildCloneCommands_CustomWorkspace(t *testing.T) { 354 + cfg := CloneConfig{ 355 + Workflow: tangled.Pipeline_Workflow{ 356 + Clone: &tangled.Pipeline_CloneOpts{ 357 + Depth: 1, 358 + Skip: false, 359 + }, 360 + }, 361 + TriggerMetadata: tangled.Pipeline_TriggerMetadata{ 362 + Kind: string(workflow.TriggerKindPush), 363 + Push: &tangled.Pipeline_PushTriggerData{ 364 + NewSha: "abc123", 365 + }, 366 + Repo: &tangled.Pipeline_TriggerRepo{ 367 + Knot: "example.com", 368 + Did: "did:plc:user123", 369 + Repo: "my-repo", 370 + }, 371 + }, 372 + DevMode: false, 373 + WorkspaceDir: "/custom/path", 374 + } 375 + 376 + cmds, err := BuildCloneCommands(cfg) 377 + if err != nil { 378 + t.Fatalf("BuildCloneCommands failed: %v", err) 379 + } 380 + 381 + if !strings.Contains(cmds.Init, "/custom/path") { 382 + t.Error("Init command should use custom workspace directory") 383 + } 384 + 385 + if !strings.Contains(cmds.All[1], "/custom/path") { 386 + t.Error("cd command should use custom workspace directory") 387 + } 388 + } 389 + 390 + func TestBuildCloneCommands_DefaultWorkspace(t *testing.T) { 391 + cfg := CloneConfig{ 392 + Workflow: tangled.Pipeline_Workflow{ 393 + Clone: &tangled.Pipeline_CloneOpts{ 394 + Depth: 1, 395 + Skip: false, 396 + }, 397 + }, 398 + TriggerMetadata: tangled.Pipeline_TriggerMetadata{ 399 + Kind: string(workflow.TriggerKindPush), 400 + Push: &tangled.Pipeline_PushTriggerData{ 401 + NewSha: "abc123", 402 + }, 403 + Repo: &tangled.Pipeline_TriggerRepo{ 404 + Knot: "example.com", 405 + Did: "did:plc:user123", 406 + Repo: "my-repo", 407 + }, 408 + }, 409 + DevMode: false, 410 + WorkspaceDir: "", // Empty should default to /tangled/workspace 411 + } 412 + 413 + cmds, err := BuildCloneCommands(cfg) 414 + if err != nil { 415 + t.Fatalf("BuildCloneCommands failed: %v", err) 416 + } 417 + 418 + if !strings.Contains(cmds.Init, "/tangled/workspace") { 419 + t.Error("Init command should default to /tangled/workspace") 420 + } 421 + }
+51
spindle/steps/types.go
··· 1 + package steps 2 + 3 + import "tangled.org/core/api/tangled" 4 + 5 + // CloneConfig contains all configuration needed to generate git clone commands 6 + type CloneConfig struct { 7 + // Workflow contains the pipeline workflow definition with clone options 8 + Workflow tangled.Pipeline_Workflow 9 + 10 + // TriggerMetadata contains information about what triggered the pipeline 11 + // (push event, PR, manual trigger, etc.) 12 + TriggerMetadata tangled.Pipeline_TriggerMetadata 13 + 14 + // DevMode enables development mode URL handling 15 + // (e.g., replacing localhost with host.docker.internal) 16 + DevMode bool 17 + 18 + // WorkspaceDir is the engine-specific path where the repository 19 + // should be cloned (e.g., "/tangled/workspace") 20 + WorkspaceDir string 21 + } 22 + 23 + // CloneCommands represents the git commands needed to clone a repository 24 + // Engines can execute these commands in their own way (shell script, 25 + // init container, Docker exec, etc.) 26 + type CloneCommands struct { 27 + // Init is the git init command 28 + Init string 29 + 30 + // Remote is the git remote add command 31 + Remote string 32 + 33 + // Fetch is the git fetch command with all options 34 + Fetch string 35 + 36 + // Checkout is the git checkout command 37 + Checkout string 38 + 39 + // All contains all commands in execution order 40 + All []string 41 + 42 + // RepoURL is the constructed repository URL 43 + RepoURL string 44 + 45 + // CommitSHA is the extracted commit SHA for this trigger 46 + CommitSHA string 47 + 48 + // Skip indicates whether cloning should be skipped entirely 49 + // (based on workflow.Clone.Skip flag) 50 + Skip bool 51 + }