Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 4 additions & 109 deletions pkg/cli/compile_orchestrator.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"errors"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"

Expand All @@ -18,115 +17,11 @@ import (

var compileOrchestratorLog = logger.New("cli:compile_orchestrator")

// getRepositorySlug extracts the repository slug (owner/repo) from git config
func getRepositorySlug() string {
// Try to get from git remote URL
cmd := exec.Command("git", "config", "--get", "remote.origin.url")
output, err := cmd.Output()
if err != nil {
return ""
}

url := strings.TrimSpace(string(output))

// Parse GitHub URL patterns:
// - https://github.com/owner/repo.git
// - git@github.com:owner/repo.git
// - https://github.com/owner/repo

// Remove .git suffix
url = strings.TrimSuffix(url, ".git")

// Extract owner/repo from URL
if strings.HasPrefix(url, "https://github.com/") {
slug := strings.TrimPrefix(url, "https://github.com/")
return slug
} else if strings.HasPrefix(url, "git@github.com:") {
slug := strings.TrimPrefix(url, "git@github.com:")
return slug
}

return ""
}

// getRepositorySlugForPath extracts the repository slug (owner/repo) from the git config
// of the repository containing the specified file path
func getRepositorySlugForPath(path string) string {
// Get absolute path first
absPath, err := filepath.Abs(path)
if err != nil {
return ""
}

// Use the directory containing the file
dir := filepath.Dir(absPath)

// Try to get from git remote URL in the file's repository
cmd := exec.Command("git", "-C", dir, "config", "--get", "remote.origin.url")
output, err := cmd.Output()
if err != nil {
return ""
}

url := strings.TrimSpace(string(output))

// Parse GitHub URL patterns:
// - https://github.com/owner/repo.git
// - git@github.com:owner/repo.git
// - https://github.com/owner/repo

// Remove .git suffix
url = strings.TrimSuffix(url, ".git")

// Extract owner/repo from URL
if strings.HasPrefix(url, "https://github.com/") {
slug := strings.TrimPrefix(url, "https://github.com/")
return slug
} else if strings.HasPrefix(url, "git@github.com:") {
slug := strings.TrimPrefix(url, "git@github.com:")
return slug
}

return ""
}

// getRepositoryRoot returns the absolute path to the git repository root
// It looks for the git repository containing the current directory
func getRepositoryRoot() (string, error) {
cmd := exec.Command("git", "rev-parse", "--show-toplevel")
output, err := cmd.Output()
if err != nil {
return "", fmt.Errorf("failed to get repository root: %w", err)
}
return strings.TrimSpace(string(output)), nil
}

// getRepositoryRootForPath returns the absolute path to the git repository root
// containing the specified file path
func getRepositoryRootForPath(path string) (string, error) {
// Get absolute path first
absPath, err := filepath.Abs(path)
if err != nil {
return "", fmt.Errorf("failed to get absolute path: %w", err)
}

// Use the directory containing the file
dir := filepath.Dir(absPath)

// Run git command in the file's directory
cmd := exec.Command("git", "-C", dir, "rev-parse", "--show-toplevel")
output, err := cmd.Output()
if err != nil {
return "", fmt.Errorf("failed to get repository root for path %s: %w", path, err)
}
return strings.TrimSpace(string(output)), nil
}

// getRepositoryRelativePath converts an absolute file path to a repository-relative path
// This ensures stable workflow identifiers regardless of where the repository is cloned
func getRepositoryRelativePath(absPath string) (string, error) {
// Get the repository root for the specific file
repoRoot, err := getRepositoryRootForPath(absPath)
repoRoot, err := findGitRootForPath(absPath)
if err != nil {
// If we can't get the repo root, just use the basename as fallback
compileOrchestratorLog.Printf("Warning: could not get repository root for %s: %v, using basename", absPath, err)
Expand Down Expand Up @@ -324,7 +219,7 @@ func CompileWorkflows(config CompileConfig) ([]*workflow.WorkflowData, error) {
compileOrchestratorLog.Print("Created compiler instance")

// Set repository slug for schedule scattering
repoSlug := getRepositorySlug()
repoSlug := getRepositorySlugFromRemote()
if repoSlug != "" {
compiler.SetRepositorySlug(repoSlug)
compileOrchestratorLog.Printf("Repository slug set: %s", repoSlug)
Expand Down Expand Up @@ -532,7 +427,7 @@ func CompileWorkflows(config CompileConfig) ([]*workflow.WorkflowData, error) {
compiler.SetWorkflowIdentifier(relPath)

// Set repository slug for this specific file (may differ from CWD's repo)
fileRepoSlug := getRepositorySlugForPath(resolvedFile)
fileRepoSlug := getRepositorySlugFromRemoteForPath(resolvedFile)
if fileRepoSlug != "" {
compiler.SetRepositorySlug(fileRepoSlug)
compileOrchestratorLog.Printf("Repository slug for file set: %s", fileRepoSlug)
Expand Down Expand Up @@ -869,7 +764,7 @@ func CompileWorkflows(config CompileConfig) ([]*workflow.WorkflowData, error) {
compiler.SetWorkflowIdentifier(relPath)

// Set repository slug for this specific file (may differ from CWD's repo)
fileRepoSlug := getRepositorySlugForPath(file)
fileRepoSlug := getRepositorySlugFromRemoteForPath(file)
if fileRepoSlug != "" {
compiler.SetRepositorySlug(fileRepoSlug)
compileOrchestratorLog.Printf("Repository slug for file set: %s", fileRepoSlug)
Expand Down
6 changes: 3 additions & 3 deletions pkg/cli/compile_orchestrator_stability_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
// TestGetRepositoryRelativePath tests that paths are correctly converted to repository-relative paths
func TestGetRepositoryRelativePath(t *testing.T) {
// Get the actual repository root
repoRoot, err := getRepositoryRoot()
repoRoot, err := findGitRoot()
if err != nil {
t.Skipf("Skipping test: not in a git repository: %v", err)
}
Expand Down Expand Up @@ -54,7 +54,7 @@ func TestGetRepositoryRelativePath(t *testing.T) {
// produces the same relative path regardless of how it's constructed
func TestGetRepositoryRelativePathConsistency(t *testing.T) {
// Get the actual repository root
repoRoot, err := getRepositoryRoot()
repoRoot, err := findGitRoot()
if err != nil {
t.Skipf("Skipping test: not in a git repository: %v", err)
}
Expand Down Expand Up @@ -111,7 +111,7 @@ func TestGetRepositoryRelativePathConsistency(t *testing.T) {
// is normalized to forward slashes for cross-platform stability
func TestGetRepositoryRelativePathCrossPlatform(t *testing.T) {
// Get the actual repository root
repoRoot, err := getRepositoryRoot()
repoRoot, err := findGitRoot()
if err != nil {
t.Skipf("Skipping test: not in a git repository: %v", err)
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/cli/compile_validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ func CompileWorkflowWithValidation(compiler *workflow.Compiler, filePath string,
compiler.SetWorkflowIdentifier(relPath)

// Set repository slug for this specific file (may differ from CWD's repo)
fileRepoSlug := getRepositorySlugForPath(filePath)
fileRepoSlug := getRepositorySlugFromRemoteForPath(filePath)
if fileRepoSlug != "" {
compiler.SetRepositorySlug(fileRepoSlug)
compileValidationLog.Printf("Repository slug for file set: %s", fileRepoSlug)
Expand Down
56 changes: 13 additions & 43 deletions pkg/cli/devcontainer.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"encoding/json"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"

Expand Down Expand Up @@ -240,61 +239,32 @@ func ensureDevcontainerConfig(verbose bool, additionalRepos []string) error {

// getCurrentRepoName gets the current repository name from git remote in owner/repo format
func getCurrentRepoName() string {
// Try to get the repository name from git remote
cmd := exec.Command("git", "config", "--get", "remote.origin.url")
output, err := cmd.Output()
if err != nil {
// Fallback to directory name
gitRoot, err := findGitRoot()
if err != nil {
return ""
}
return filepath.Base(gitRoot)
// Try to get the repository name from git remote using centralized helper
slug := getRepositorySlugFromRemote()
if slug != "" {
return slug
}

remoteURL := strings.TrimSpace(string(output))
return parseGitHubRepoFromURL(remoteURL)
// Fallback to directory name
gitRoot, err := findGitRoot()
if err != nil {
return ""
}
return filepath.Base(gitRoot)
}

// getRepoOwner extracts the owner from the git remote URL
func getRepoOwner() string {
cmd := exec.Command("git", "config", "--get", "remote.origin.url")
output, err := cmd.Output()
if err != nil {
// Use centralized helper to get full repo slug
fullRepo := getRepositorySlugFromRemote()
if fullRepo == "" {
return ""
}

remoteURL := strings.TrimSpace(string(output))
fullRepo := parseGitHubRepoFromURL(remoteURL)

// Extract owner from "owner/repo" format
parts := strings.Split(fullRepo, "/")
if len(parts) >= 1 {
return parts[0]
}
return ""
}

// parseGitHubRepoFromURL extracts owner/repo from a GitHub URL
func parseGitHubRepoFromURL(url string) string {
// Remove .git suffix if present
url = strings.TrimSuffix(url, ".git")

// Handle HTTPS URLs: https://github.com/owner/repo
if strings.Contains(url, "github.com/") {
parts := strings.Split(url, "github.com/")
if len(parts) == 2 {
return parts[1]
}
}

// Handle SSH URLs: git@github.com:owner/repo
if strings.Contains(url, "git@github.com:") {
parts := strings.Split(url, "git@github.com:")
if len(parts) == 2 {
return parts[1]
}
}

return ""
}
105 changes: 105 additions & 0 deletions pkg/cli/git.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,111 @@ func findGitRoot() (string, error) {
return gitRoot, nil
}

// findGitRootForPath finds the root directory of the git repository containing the specified path
func findGitRootForPath(path string) (string, error) {
gitLog.Printf("Finding git root for path: %s", path)

// Get absolute path first
absPath, err := filepath.Abs(path)
if err != nil {
return "", fmt.Errorf("failed to get absolute path: %w", err)
}

// Use the directory containing the file
dir := filepath.Dir(absPath)

// Run git command in the file's directory
cmd := exec.Command("git", "-C", dir, "rev-parse", "--show-toplevel")
output, err := cmd.Output()
if err != nil {
return "", fmt.Errorf("failed to get repository root for path %s: %w", path, err)
}
gitRoot := strings.TrimSpace(string(output))
gitLog.Printf("Found git root for path: %s", gitRoot)
return gitRoot, nil
}

// parseGitHubRepoSlugFromURL extracts owner/repo from a GitHub URL
// Supports both HTTPS (https://github.com/owner/repo) and SSH (git@github.com:owner/repo) formats
func parseGitHubRepoSlugFromURL(url string) string {
gitLog.Printf("Parsing GitHub repo slug from URL: %s", url)

// Remove .git suffix if present
url = strings.TrimSuffix(url, ".git")

// Handle HTTPS URLs: https://github.com/owner/repo
if strings.HasPrefix(url, "https://github.com/") {
slug := strings.TrimPrefix(url, "https://github.com/")
gitLog.Printf("Extracted slug from HTTPS URL: %s", slug)
return slug
}

// Handle SSH URLs: git@github.com:owner/repo
if strings.HasPrefix(url, "git@github.com:") {
slug := strings.TrimPrefix(url, "git@github.com:")
gitLog.Printf("Extracted slug from SSH URL: %s", slug)
return slug
}

gitLog.Print("Could not extract slug from URL")
return ""
}

// getRepositorySlugFromRemote extracts the repository slug (owner/repo) from git remote URL
func getRepositorySlugFromRemote() string {
gitLog.Print("Getting repository slug from git remote")

// Try to get from git remote URL
cmd := exec.Command("git", "config", "--get", "remote.origin.url")
output, err := cmd.Output()
if err != nil {
gitLog.Printf("Failed to get remote URL: %v", err)
return ""
}

url := strings.TrimSpace(string(output))
slug := parseGitHubRepoSlugFromURL(url)

if slug != "" {
gitLog.Printf("Repository slug: %s", slug)
}

return slug
}

// getRepositorySlugFromRemoteForPath extracts the repository slug (owner/repo) from the git remote URL
// of the repository containing the specified file path
func getRepositorySlugFromRemoteForPath(path string) string {
gitLog.Printf("Getting repository slug for path: %s", path)

// Get absolute path first
absPath, err := filepath.Abs(path)
if err != nil {
gitLog.Printf("Failed to get absolute path: %v", err)
return ""
}

// Use the directory containing the file
dir := filepath.Dir(absPath)

// Try to get from git remote URL in the file's repository
cmd := exec.Command("git", "-C", dir, "config", "--get", "remote.origin.url")
output, err := cmd.Output()
if err != nil {
gitLog.Printf("Failed to get remote URL for path: %v", err)
return ""
}

url := strings.TrimSpace(string(output))
slug := parseGitHubRepoSlugFromURL(url)

if slug != "" {
gitLog.Printf("Repository slug for path: %s", slug)
}

return slug
}

func stageWorkflowChanges() {
// Find git root and add .github/workflows relative to it
if gitRoot, err := findGitRoot(); err == nil {
Expand Down
Loading
Loading