Skip to content
Closed
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
3 changes: 2 additions & 1 deletion pkg/cli/add_interactive_engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,8 @@ func (c *AddInteractiveConfig) collectCopilotPAT() error {
fmt.Fprintln(os.Stderr, console.FormatWarningMessage("Classic PATs (ghp_...) are not supported. You must use a fine-grained PAT (github_pat_...)."))
fmt.Fprintln(os.Stderr, "")
fmt.Fprintln(os.Stderr, "Please create a token at:")
fmt.Fprintln(os.Stderr, console.FormatCommandMessage(" https://github.com/settings/personal-access-tokens/new"))
githubHost := getGitHubHost()
fmt.Fprintln(os.Stderr, console.FormatCommandMessage(fmt.Sprintf(" %s/settings/personal-access-tokens/new", githubHost)))
fmt.Fprintln(os.Stderr, "")
fmt.Fprintln(os.Stderr, "Configure the token with:")
fmt.Fprintln(os.Stderr, " • Token name: Agentic Workflows Copilot")
Expand Down
3 changes: 2 additions & 1 deletion pkg/cli/add_interactive_workflow.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,8 @@ func (c *AddInteractiveConfig) checkStatusAndOfferRun(ctx context.Context) error
addInteractiveLog.Print("Running in Codespaces, skipping run offer and showing Actions link")
fmt.Fprintln(os.Stderr, "")
fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Running in GitHub Codespaces - please trigger the workflow manually from the Actions page"))
fmt.Fprintf(os.Stderr, "🔗 https://github.com/%s/actions\n", c.RepoOverride)
githubHost := getGitHubHost()
fmt.Fprintf(os.Stderr, "🔗 %s/%s/actions\n", githubHost, c.RepoOverride)
c.showFinalInstructions()
return nil
}
Expand Down
18 changes: 12 additions & 6 deletions pkg/cli/download_workflow.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ func resolveLatestReleaseViaGit(repo, currentRef string, allowMajor, verbose boo
fmt.Fprintln(os.Stderr, console.FormatVerboseMessage(fmt.Sprintf("Fetching latest release for %s via git ls-remote (current: %s, allow major: %v)", repo, currentRef, allowMajor)))
}

repoURL := fmt.Sprintf("https://github.com/%s.git", repo)
githubHost := getGitHubHost()
repoURL := fmt.Sprintf("%s/%s.git", githubHost, repo)

// List all tags
cmd := exec.Command("git", "ls-remote", "--tags", repoURL)
Expand Down Expand Up @@ -102,7 +103,8 @@ func resolveLatestReleaseViaGit(repo, currentRef string, allowMajor, verbose boo
func isBranchRefViaGit(repo, ref string) (bool, error) {
downloadLog.Printf("Attempting git ls-remote to check if ref is branch: %s@%s", repo, ref)

repoURL := fmt.Sprintf("https://github.com/%s.git", repo)
githubHost := getGitHubHost()
repoURL := fmt.Sprintf("%s/%s.git", githubHost, repo)

// List all branches and check if ref matches
cmd := exec.Command("git", "ls-remote", "--heads", repoURL)
Expand Down Expand Up @@ -167,7 +169,8 @@ func resolveBranchHeadViaGit(repo, branch string, verbose bool) (string, error)
fmt.Fprintln(os.Stderr, console.FormatVerboseMessage(fmt.Sprintf("Fetching latest commit for branch %s in %s via git ls-remote", branch, repo)))
}

repoURL := fmt.Sprintf("https://github.com/%s.git", repo)
githubHost := getGitHubHost()
repoURL := fmt.Sprintf("%s/%s.git", githubHost, repo)

// Get the SHA for the specific branch
cmd := exec.Command("git", "ls-remote", repoURL, fmt.Sprintf("refs/heads/%s", branch))
Expand Down Expand Up @@ -236,7 +239,8 @@ func resolveDefaultBranchHeadViaGit(repo string, verbose bool) (string, error) {
fmt.Fprintln(os.Stderr, console.FormatVerboseMessage(fmt.Sprintf("Fetching default branch for %s via git ls-remote", repo)))
}

repoURL := fmt.Sprintf("https://github.com/%s.git", repo)
githubHost := getGitHubHost()
repoURL := fmt.Sprintf("%s/%s.git", githubHost, repo)

// Get HEAD to find default branch
cmd := exec.Command("git", "ls-remote", "--symref", repoURL, "HEAD")
Expand Down Expand Up @@ -326,7 +330,8 @@ func downloadWorkflowContentViaGit(repo, path, ref string, verbose bool) ([]byte
downloadLog.Printf("Attempting git fallback for downloading workflow content: %s/%s@%s", repo, path, ref)

// Use git archive to get the file content without cloning
repoURL := fmt.Sprintf("https://github.com/%s.git", repo)
githubHost := getGitHubHost()
repoURL := fmt.Sprintf("%s/%s.git", githubHost, repo)

// git archive command: git archive --remote=<repo> <ref> <path>
cmd := exec.Command("git", "archive", "--remote="+repoURL, ref, path)
Expand Down Expand Up @@ -366,7 +371,8 @@ func downloadWorkflowContentViaGitClone(repo, path, ref string, verbose bool) ([
}
defer os.RemoveAll(tmpDir)

repoURL := fmt.Sprintf("https://github.com/%s.git", repo)
githubHost := getGitHubHost()
repoURL := fmt.Sprintf("%s/%s.git", githubHost, repo)

// Initialize git repository
initCmd := exec.Command("git", "-C", tmpDir, "init")
Expand Down
18 changes: 18 additions & 0 deletions pkg/cli/github.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,37 @@ var githubLog = logger.New("cli:github")
// getGitHubHost returns the GitHub host URL from environment variables.
// It checks GITHUB_SERVER_URL first (GitHub Actions standard),
// then falls back to GH_HOST (gh CLI standard),
// then derives from GITHUB_API_URL if available,
// and finally defaults to https://github.com
func getGitHubHost() string {
host := os.Getenv("GITHUB_SERVER_URL")
if host == "" {
host = os.Getenv("GH_HOST")
}
if host == "" {
// Try to derive from GITHUB_API_URL
if apiURL := os.Getenv("GITHUB_API_URL"); apiURL != "" {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot remove sniffing api url

// Convert API URL to server URL
// https://api.github.com -> https://github.com
// https://github.enterprise.com/api/v3 -> https://github.enterprise.com
host = strings.Replace(apiURL, "://api.", "://", 1)
host = strings.TrimSuffix(host, "/api/v3")
host = strings.TrimSuffix(host, "/api")
githubLog.Printf("Derived GitHub host from GITHUB_API_URL: %s", host)
}
}
if host == "" {
host = "https://github.com"
githubLog.Print("Using default GitHub host: https://github.com")
} else {
githubLog.Printf("Resolved GitHub host: %s", host)
}

// Ensure https:// prefix if only hostname is provided (from GH_HOST)
if !strings.HasPrefix(host, "http://") && !strings.HasPrefix(host, "https://") {
host = "https://" + host
}

// Remove trailing slash for consistency
return strings.TrimSuffix(host, "/")
}
50 changes: 50 additions & 0 deletions pkg/cli/github_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,51 +11,101 @@ func TestGetGitHubHost(t *testing.T) {
name string
serverURL string
ghHost string
apiURL string
expectedHost string
}{
{
name: "defaults to github.com",
serverURL: "",
ghHost: "",
apiURL: "",
expectedHost: "https://github.com",
},
{
name: "uses GITHUB_SERVER_URL when set",
serverURL: "https://github.enterprise.com",
ghHost: "",
apiURL: "",
expectedHost: "https://github.enterprise.com",
},
{
name: "uses GH_HOST when GITHUB_SERVER_URL not set",
serverURL: "",
ghHost: "https://github.saobby.my.eu.orgpany.com",
apiURL: "",
expectedHost: "https://github.saobby.my.eu.orgpany.com",
},
{
name: "adds https:// prefix to GH_HOST if missing",
serverURL: "",
ghHost: "github.company.com",
apiURL: "",
expectedHost: "https://github.saobby.my.eu.orgpany.com",
},
{
name: "GITHUB_SERVER_URL takes precedence over GH_HOST",
serverURL: "https://github.enterprise.com",
ghHost: "https://github.saobby.my.eu.orgpany.com",
apiURL: "",
expectedHost: "https://github.enterprise.com",
},
{
name: "removes trailing slash from GITHUB_SERVER_URL",
serverURL: "https://github.enterprise.com/",
ghHost: "",
apiURL: "",
expectedHost: "https://github.enterprise.com",
},
{
name: "removes trailing slash from GH_HOST",
serverURL: "",
ghHost: "https://github.saobby.my.eu.orgpany.com/",
apiURL: "",
expectedHost: "https://github.saobby.my.eu.orgpany.com",
},
{
name: "derives from GITHUB_API_URL when others not set (api subdomain)",
serverURL: "",
ghHost: "",
apiURL: "https://api.github.com",
expectedHost: "https://github.com",
},
{
name: "derives from GITHUB_API_URL (enterprise api subdomain)",
serverURL: "",
ghHost: "",
apiURL: "https://api.github.enterprise.com",
expectedHost: "https://github.enterprise.com",
},
{
name: "derives from GITHUB_API_URL (path-based API)",
serverURL: "",
ghHost: "",
apiURL: "https://github.enterprise.com/api/v3",
expectedHost: "https://github.enterprise.com",
},
{
name: "GITHUB_SERVER_URL takes precedence over GITHUB_API_URL",
serverURL: "https://github.primary.com",
ghHost: "",
apiURL: "https://api.github.secondary.com",
expectedHost: "https://github.primary.com",
},
{
name: "GH_HOST takes precedence over GITHUB_API_URL",
serverURL: "",
ghHost: "github.primary.com",
apiURL: "https://api.github.secondary.com",
expectedHost: "https://github.primary.com",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Set test env vars (always set to ensure clean state)
t.Setenv("GITHUB_SERVER_URL", tt.serverURL)
t.Setenv("GH_HOST", tt.ghHost)
t.Setenv("GITHUB_API_URL", tt.apiURL)

// Test
host := getGitHubHost()
Expand Down
5 changes: 3 additions & 2 deletions pkg/cli/pr_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -433,7 +433,8 @@ func createTransferPR(targetOwner, targetRepo string, prInfo *PRInfo, branchName

// Add fork as remote if not already present
remoteName := "fork"
forkRepoURL := fmt.Sprintf("https://github.com/%s/%s.git", forkOwner, forkRepo)
githubHost := getGitHubHost()
forkRepoURL := fmt.Sprintf("%s/%s/%s.git", githubHost, forkOwner, forkRepo)

// Check if fork remote exists
checkRemoteCmd := exec.Command("git", "remote", "get-url", remoteName)
Expand All @@ -450,7 +451,7 @@ func createTransferPR(targetOwner, targetRepo string, prInfo *PRInfo, branchName

// Also ensure target repository is set as upstream remote if not already present
upstreamRemote := "upstream"
targetRepoURL := fmt.Sprintf("https://github.com/%s/%s.git", targetOwner, targetRepo)
targetRepoURL := fmt.Sprintf("%s/%s/%s.git", githubHost, targetOwner, targetRepo)

// Check if upstream remote exists and points to the right repo
checkUpstreamCmd := exec.Command("git", "remote", "get-url", upstreamRemote)
Expand Down
3 changes: 2 additions & 1 deletion pkg/cli/secret_collection.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,8 @@ func promptForCopilotPAT() (string, error) {
fmt.Fprintln(os.Stderr, console.FormatWarningMessage("Classic PATs (ghp_...) are not supported. You must use a fine-grained PAT (github_pat_...)."))
fmt.Fprintln(os.Stderr, "")
fmt.Fprintln(os.Stderr, "Please create a token at:")
fmt.Fprintln(os.Stderr, console.FormatCommandMessage(" https://github.com/settings/personal-access-tokens/new"))
githubHost := getGitHubHost()
fmt.Fprintln(os.Stderr, console.FormatCommandMessage(fmt.Sprintf(" %s/settings/personal-access-tokens/new", githubHost)))
fmt.Fprintln(os.Stderr, "")
fmt.Fprintln(os.Stderr, "Configure the token with:")
fmt.Fprintln(os.Stderr, " • Token name: Agentic Workflows Copilot")
Expand Down
9 changes: 6 additions & 3 deletions pkg/cli/trial_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -465,7 +465,8 @@ func RunWorkflowTrials(ctx context.Context, workflowSpecs []string, opts TrialOp
}

// Generate workflow run URL
workflowRunURL := fmt.Sprintf("https://github.com/%s/actions/runs/%s", hostRepoSlug, runID)
githubHost := getGitHubHost()
workflowRunURL := fmt.Sprintf("%s/%s/actions/runs/%s", githubHost, hostRepoSlug, runID)
fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Workflow run started with ID: %s (%s)", runID, workflowRunURL)))

// Wait for workflow completion
Expand Down Expand Up @@ -571,7 +572,8 @@ func RunWorkflowTrials(ctx context.Context, workflowSpecs []string, opts TrialOp
if opts.DeleteHostRepo {
fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Host repository will be cleaned up"))
} else {
fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Host repository preserved: https://github.com/%s", hostRepoSlug)))
githubHost := getGitHubHost()
fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Host repository preserved: %s/%s", githubHost, hostRepoSlug)))
}
},
UseStderr: true,
Expand All @@ -596,7 +598,8 @@ func getCurrentGitHubUsername() (string, error) {

// showTrialConfirmation displays a confirmation prompt to the user using parsed workflow specs
func showTrialConfirmation(parsedSpecs []*WorkflowSpec, logicalRepoSlug, cloneRepoSlug, hostRepoSlug string, deleteHostRepo bool, forceDeleteHostRepo bool, autoMergePRs bool, repeatCount int, directTrialMode bool, engineOverride string) error {
hostRepoSlugURL := fmt.Sprintf("https://github.com/%s", hostRepoSlug)
githubHost := getGitHubHost()
hostRepoSlugURL := fmt.Sprintf("%s/%s", githubHost, hostRepoSlug)

var sections []string

Expand Down
24 changes: 15 additions & 9 deletions pkg/cli/trial_repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,8 @@ func ensureTrialRepository(repoSlug string, cloneRepoSlug string, forceDeleteHos
if dryRun {
prefix = "[DRY RUN] "
}
fmt.Fprintln(os.Stderr, console.FormatSuccessMessage(fmt.Sprintf("%sUsing existing host repository: https://github.com/%s", prefix, repoSlug)))
githubHost := getGitHubHost()
fmt.Fprintln(os.Stderr, console.FormatSuccessMessage(fmt.Sprintf("%sUsing existing host repository: %s/%s", prefix, githubHost, repoSlug)))
return nil
}
}
Expand All @@ -93,10 +94,11 @@ func ensureTrialRepository(repoSlug string, cloneRepoSlug string, forceDeleteHos
}

if dryRun {
githubHost := getGitHubHost()
fmt.Fprintln(os.Stderr, console.FormatInfoMessage("[DRY RUN] Would create repository with description: 'GitHub Agentic Workflows host repository'"))
fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("[DRY RUN] Would enable GitHub Actions permissions at: https://github.com/%s/settings/actions", repoSlug)))
fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("[DRY RUN] Would enable GitHub Actions permissions at: %s/%s/settings/actions", githubHost, repoSlug)))
fmt.Fprintln(os.Stderr, console.FormatInfoMessage("[DRY RUN] Would enable discussions"))
fmt.Fprintln(os.Stderr, console.FormatSuccessMessage(fmt.Sprintf("[DRY RUN] Would create host repository: https://github.com/%s", repoSlug)))
fmt.Fprintln(os.Stderr, console.FormatSuccessMessage(fmt.Sprintf("[DRY RUN] Would create host repository: %s/%s", githubHost, repoSlug)))
return nil
}

Expand All @@ -111,19 +113,21 @@ func ensureTrialRepository(repoSlug string, cloneRepoSlug string, forceDeleteHos
if verbose {
fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Repository already exists (detected via create error): %s", repoSlug)))
}
fmt.Fprintln(os.Stderr, console.FormatSuccessMessage(fmt.Sprintf("Using existing host repository: https://github.com/%s", repoSlug)))
githubHost := getGitHubHost()
fmt.Fprintln(os.Stderr, console.FormatSuccessMessage(fmt.Sprintf("Using existing host repository: %s/%s", githubHost, repoSlug)))
return nil
}
return fmt.Errorf("failed to create host repository: %w (output: %s)", err, string(output))
}

// Show host repository creation message with URL
fmt.Fprintln(os.Stderr, console.FormatSuccessMessage(fmt.Sprintf("Created host repository: https://github.com/%s", repoSlug)))
githubHost := getGitHubHost()
fmt.Fprintln(os.Stderr, console.FormatSuccessMessage(fmt.Sprintf("Created host repository: %s/%s", githubHost, repoSlug)))

// Prompt user to enable GitHub Actions permissions
fmt.Fprintln(os.Stderr, console.FormatInfoMessage(""))
fmt.Fprintln(os.Stderr, console.FormatInfoMessage("IMPORTANT: You must enable GitHub Actions permissions for the repository."))
fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("1. Go to: https://github.com/%s/settings/actions", repoSlug)))
fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("1. Go to: %s/%s/settings/actions", githubHost, repoSlug)))
fmt.Fprintln(os.Stderr, console.FormatInfoMessage("2. Under 'Workflow permissions', select 'Allow GitHub Actions to create and approve pull requests'"))
fmt.Fprintln(os.Stderr, console.FormatInfoMessage("3. Click 'Save'"))
fmt.Fprintln(os.Stderr, console.FormatInfoMessage(""))
Expand Down Expand Up @@ -182,7 +186,8 @@ func cloneTrialHostRepository(repoSlug string, verbose bool) (string, error) {
}

// Clone the repository using the full slug
repoURL := fmt.Sprintf("https://github.com/%s.git", repoSlug)
githubHost := getGitHubHost()
repoURL := fmt.Sprintf("%s/%s.git", githubHost, repoSlug)

output, err := workflow.RunGitCombined(fmt.Sprintf("Cloning %s...", repoSlug), "clone", repoURL, tempDir)
if err != nil {
Expand Down Expand Up @@ -526,7 +531,8 @@ func cloneRepoContentsIntoHost(cloneRepoSlug string, cloneRepoVersion string, ho
defer os.RemoveAll(tempCloneDir)

// Clone the source repository
cloneURL := fmt.Sprintf("https://github.com/%s.git", cloneRepoSlug)
githubHost := getGitHubHost()
cloneURL := fmt.Sprintf("%s/%s.git", githubHost, cloneRepoSlug)

output, err := workflow.RunGitCombined(fmt.Sprintf("Cloning %s...", cloneRepoSlug), "clone", cloneURL, tempCloneDir)
if err != nil {
Expand All @@ -547,7 +553,7 @@ func cloneRepoContentsIntoHost(cloneRepoSlug string, cloneRepoVersion string, ho
}

// Add the host repository as a new remote
hostURL := fmt.Sprintf("https://github.com/%s.git", hostRepoSlug)
hostURL := fmt.Sprintf("%s/%s.git", githubHost, hostRepoSlug)
remoteCmd := exec.Command("git", "remote", "add", "host", hostURL)
if output, err := remoteCmd.CombinedOutput(); err != nil {
return fmt.Errorf("failed to add host remote: %w (output: %s)", err, string(output))
Expand Down
3 changes: 2 additions & 1 deletion pkg/cli/update_actions.go
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,8 @@ func getLatestActionReleaseViaGit(repo, currentVersion string, allowMajor, verbo
baseRepo := extractBaseRepo(repo)
updateLog.Printf("Using base repository: %s for action: %s (git fallback)", baseRepo, repo)

repoURL := fmt.Sprintf("https://github.com/%s.git", baseRepo)
githubHost := getGitHubHost()
repoURL := fmt.Sprintf("%s/%s.git", githubHost, baseRepo)

// List all tags
cmd := exec.Command("git", "ls-remote", "--tags", repoURL)
Expand Down
Loading
Loading