diff --git a/.github/workflows/smoke-update-cross-repo-pr.lock.yml b/.github/workflows/smoke-update-cross-repo-pr.lock.yml index 66f6dc137d..a3a1a30633 100644 --- a/.github/workflows/smoke-update-cross-repo-pr.lock.yml +++ b/.github/workflows/smoke-update-cross-repo-pr.lock.yml @@ -23,7 +23,7 @@ # # Smoke test validating cross-repo pull request updates in githubnext/gh-aw-side-repo by adding lines from Homer's Odyssey to the README # -# gh-aw-metadata: {"schema_version":"v1","frontmatter_hash":"0dbcea46ce3435c40cb5ca9fcbd9feb04e8720f1b0b3b89993c94ae5d7bf3cd0"} +# gh-aw-metadata: {"schema_version":"v1","frontmatter_hash":"e7bb6f57e2a528ac46befd53d9f587e25a21ebc63c62d7d00795d8479082c8b0"} name: "Smoke Update Cross-Repo PR" "on": @@ -321,14 +321,14 @@ jobs: uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - - name: Checkout githubnext/gh-aw-side-repo + - name: Checkout side repo uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false repository: githubnext/gh-aw-side-repo token: ${{ secrets.GH_AW_SIDE_REPO_PAT }} fetch-depth: 0 - - name: Fetch additional refs for githubnext/gh-aw-side-repo + - name: Fetch additional refs for side repo env: GH_AW_FETCH_TOKEN: ${{ secrets.GH_AW_SIDE_REPO_PAT }} run: | diff --git a/.github/workflows/smoke-update-cross-repo-pr.md b/.github/workflows/smoke-update-cross-repo-pr.md index 3f8ee59c4a..8a7465ea08 100644 --- a/.github/workflows/smoke-update-cross-repo-pr.md +++ b/.github/workflows/smoke-update-cross-repo-pr.md @@ -22,6 +22,7 @@ network: checkout: - repository: githubnext/gh-aw-side-repo + name: side repo github-token: ${{ secrets.GH_AW_SIDE_REPO_PAT }} fetch: ["main", "refs/pulls/open/*"] # fetch all open PR refs after checkout fetch-depth: 0 # fetch full history to ensure we can see all commits and PR details diff --git a/pkg/parser/schemas/main_workflow_schema.json b/pkg/parser/schemas/main_workflow_schema.json index 85878a485b..759c30ea43 100644 --- a/pkg/parser/schemas/main_workflow_schema.json +++ b/pkg/parser/schemas/main_workflow_schema.json @@ -8007,6 +8007,11 @@ "description": "Configuration for a single actions/checkout step", "additionalProperties": false, "properties": { + "name": { + "type": "string", + "description": "Optional human-readable label used in generated step names. When set, overrides the default repository/path-derived label in 'Checkout ' and 'Fetch additional refs for ' step names.", + "examples": ["side repo", "upstream", "my-lib"] + }, "repository": { "type": "string", "description": "Repository to checkout in owner/repo format. Defaults to the current repository.", diff --git a/pkg/workflow/checkout_manager.go b/pkg/workflow/checkout_manager.go index 75f1c339ba..15e57a68a0 100644 --- a/pkg/workflow/checkout_manager.go +++ b/pkg/workflow/checkout_manager.go @@ -37,6 +37,11 @@ var checkoutManagerLog = logger.New("workflow:checkout_manager") // app-id: ${{ vars.APP_ID }} // private-key: ${{ secrets.APP_PRIVATE_KEY }} type CheckoutConfig struct { + // Name is an optional human-readable label used in generated step names. + // When set, it overrides the default repository/path-derived label in + // "Checkout " and "Fetch additional refs for " step names. + Name string `json:"name,omitempty"` + // Repository to checkout in owner/repo format. Defaults to the current repository. Repository string `json:"repository,omitempty"` @@ -107,6 +112,7 @@ type checkoutKey struct { // resolvedCheckout is an internal merged checkout entry used by CheckoutManager. type resolvedCheckout struct { key checkoutKey + name string // optional human-readable label overriding key-derived step name ref string // last non-empty ref wins token string // last non-empty github-token wins githubApp *GitHubAppConfig // GitHub App config (first non-nil wins) @@ -167,6 +173,9 @@ func (cm *CheckoutManager) add(cfg *CheckoutConfig) { // Merge into existing entry; first-seen wins for ref and token entry := cm.ordered[idx] entry.fetchDepth = deeperFetchDepth(entry.fetchDepth, cfg.FetchDepth) + if cfg.Name != "" && entry.name == "" { + entry.name = cfg.Name // first-seen name wins + } if cfg.Ref != "" && entry.ref == "" { entry.ref = cfg.Ref // first-seen ref wins } @@ -195,6 +204,7 @@ func (cm *CheckoutManager) add(cfg *CheckoutConfig) { } else { entry := &resolvedCheckout{ key: key, + name: cfg.Name, ref: cfg.Ref, token: cfg.GitHubToken, githubApp: cfg.GitHubApp, @@ -403,7 +413,7 @@ func (cm *CheckoutManager) GenerateDefaultCheckoutStep( // The index parameter identifies the checkout's position in the ordered list, used to // reference the correct app token minting step when app authentication is configured. func generateCheckoutStepLines(entry *resolvedCheckout, index int, getActionPin func(string) string) []string { - name := "Checkout " + checkoutStepName(entry.key) + name := "Checkout " + checkoutStepName(entry) var sb strings.Builder fmt.Fprintf(&sb, " - name: %s\n", name) fmt.Fprintf(&sb, " uses: %s\n", getActionPin("actions/checkout")) @@ -454,7 +464,12 @@ func generateCheckoutStepLines(entry *resolvedCheckout, index int, getActionPin } // checkoutStepName returns a human-readable description for a checkout step. -func checkoutStepName(key checkoutKey) string { +// When entry.name is set it takes precedence; otherwise a label is derived from the key. +func checkoutStepName(entry *resolvedCheckout) string { + if entry.name != "" { + return entry.name + } + key := entry.key if key.repository != "" && key.path != "" { return fmt.Sprintf("%s into %s", key.repository, key.path) } @@ -576,8 +591,8 @@ func generateFetchStepLines(entry *resolvedCheckout, index int) string { // Build step name name := "Fetch additional refs" - if entry.key.repository != "" { - name = "Fetch additional refs for " + entry.key.repository + if entry.name != "" || entry.key.repository != "" || entry.key.path != "" { + name = "Fetch additional refs for " + checkoutStepName(entry) } // Determine authentication token @@ -677,6 +692,14 @@ func ParseCheckoutConfigs(raw any) ([]*CheckoutConfig, error) { func checkoutConfigFromMap(m map[string]any) (*CheckoutConfig, error) { cfg := &CheckoutConfig{} + if v, ok := m["name"]; ok { + s, ok := v.(string) + if !ok { + return nil, errors.New("checkout.name must be a string") + } + cfg.Name = s + } + if v, ok := m["repository"]; ok { s, ok := v.(string) if !ok {