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
2 changes: 1 addition & 1 deletion .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,4 @@ slides/*.pdf filter=lfs diff=lfs merge=lfs -text
slides/*.pptx filter=lfs diff=lfs merge=lfs -text
docs/slides/** filter=lfs diff=lfs merge=lfs -text

.github/workflows/*.lock.yml linguist-generated=true merge=ours
.github/workflows/*.lock.yml linguist-generated=true
2 changes: 1 addition & 1 deletion create.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ If creating a workflow, the actual files you created will be under `.github/work
If creating a workflow, check the .gitattributes file and make sure it exists and contains at least the following line:

```text
.github/workflows/*.lock.yml linguist-generated=true merge=ours
.github/workflows/*.lock.yml linguist-generated=true
```

You do not need to run `gh aw init` as part of your workflow creation. However if you did run this you may also see:
Expand Down
4 changes: 2 additions & 2 deletions pkg/cli/add_gitattributes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,8 +109,8 @@ This is a test workflow.`
t.Fatalf("Failed to read .gitattributes: %v", err)
}

if !strings.Contains(string(content), ".github/workflows/*.lock.yml linguist-generated=true merge=ours") {
t.Errorf("Expected .gitattributes to contain '.github/workflows/*.lock.yml linguist-generated=true merge=ours'")
if !strings.Contains(string(content), ".github/workflows/*.lock.yml linguist-generated=true") {
t.Errorf("Expected .gitattributes to contain '.github/workflows/*.lock.yml linguist-generated=true'")
}
})

Expand Down
8 changes: 5 additions & 3 deletions pkg/cli/git.go
Original file line number Diff line number Diff line change
Expand Up @@ -375,9 +375,11 @@ func ensureGitAttributes() (bool, error) {
found = true
break
}
// Check for old format entries that need updating
if strings.HasPrefix(trimmedLine, constants.WorkflowsLockYmlGlob) && required == lockYmlEntry {
gitLog.Print("Updating old .gitattributes entry format")
// Only clean up the exact legacy gh-aw entry (with the ineffective
// "merge=ours" attribute); never rewrite other repository-owned lines
// that happen to start with the lock-yml glob.
if trimmedLine == constants.WorkflowsLockYmlGitAttributesEntryLegacy && required == lockYmlEntry {
gitLog.Print("Updating legacy .gitattributes entry format")
lines[i] = lockYmlEntry
found = true
modified = true
Expand Down
28 changes: 17 additions & 11 deletions pkg/cli/gitattributes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,31 +47,37 @@ func TestEnsureGitAttributes(t *testing.T) {
{
name: "creates new gitattributes file",
existingContent: "",
expectedContent: ".github/workflows/*.lock.yml linguist-generated=true merge=ours",
expectedContent: ".github/workflows/*.lock.yml linguist-generated=true",
expectUpdated: true,
},
{
name: "adds entry to existing file",
existingContent: "*.generated linguist-generated=true\n",
expectedContent: "*.generated linguist-generated=true\n\n.github/workflows/*.lock.yml linguist-generated=true merge=ours",
expectedContent: "*.generated linguist-generated=true\n\n.github/workflows/*.lock.yml linguist-generated=true",
expectUpdated: true,
},
{
name: "does not duplicate existing entry",
existingContent: ".github/workflows/*.lock.yml linguist-generated=true merge=ours\n",
expectedContent: ".github/workflows/*.lock.yml linguist-generated=true merge=ours",
existingContent: ".github/workflows/*.lock.yml linguist-generated=true\n",
expectedContent: ".github/workflows/*.lock.yml linguist-generated=true",
expectUpdated: false,
},
{
name: "does not duplicate entry with different order",
existingContent: "*.md linguist-documentation=true\n.github/workflows/*.lock.yml linguist-generated=true merge=ours\n*.txt text=auto\n",
expectedContent: "*.md linguist-documentation=true\n.github/workflows/*.lock.yml linguist-generated=true merge=ours\n*.txt text=auto",
existingContent: "*.md linguist-documentation=true\n.github/workflows/*.lock.yml linguist-generated=true\n*.txt text=auto\n",
expectedContent: "*.md linguist-documentation=true\n.github/workflows/*.lock.yml linguist-generated=true\n*.txt text=auto",
expectUpdated: false,
},
{
name: "updates old format entry",
existingContent: "*.md linguist-documentation=true\n.github/workflows/*.lock.yml linguist-generated=true\n*.txt text=auto\n",
expectedContent: "*.md linguist-documentation=true\n.github/workflows/*.lock.yml linguist-generated=true merge=ours\n*.txt text=auto",
name: "migrates legacy merge=ours entry",
existingContent: "*.md linguist-documentation=true\n.github/workflows/*.lock.yml linguist-generated=true merge=ours\n*.txt text=auto\n",
expectedContent: "*.md linguist-documentation=true\n.github/workflows/*.lock.yml linguist-generated=true\n*.txt text=auto",
expectUpdated: true,
},
{
name: "does not rewrite repository-owned lock-yml policy",
existingContent: "*.md linguist-documentation=true\n.github/workflows/*.lock.yml linguist-generated=true merge=union\n*.txt text=auto\n",
expectedContent: "*.md linguist-documentation=true\n.github/workflows/*.lock.yml linguist-generated=true merge=union\n*.txt text=auto\n\n.github/workflows/*.lock.yml linguist-generated=true",
expectUpdated: true,
},
}
Expand Down Expand Up @@ -119,8 +125,8 @@ func TestEnsureGitAttributes(t *testing.T) {
}

// Verify the entry is actually present
if !strings.Contains(string(content), ".github/workflows/*.lock.yml linguist-generated=true merge=ours") {
t.Errorf("Expected .gitattributes to contain '.github/workflows/*.lock.yml linguist-generated=true merge=ours'")
if !strings.Contains(string(content), ".github/workflows/*.lock.yml linguist-generated=true") {
t.Errorf("Expected .gitattributes to contain '.github/workflows/*.lock.yml linguist-generated=true'")
}
// Verify campaign.g.md entry is NOT present (it's now in .gitignore)
if strings.Contains(string(content), ".github/workflows/*.campaign.g.md") {
Expand Down
6 changes: 3 additions & 3 deletions pkg/cli/init_command_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -357,7 +357,7 @@ func TestInitRepositoryBasic(t *testing.T) {
t.Fatalf("Failed to read .gitattributes: %v", err)
}

expectedEntry := ".github/workflows/*.lock.yml linguist-generated=true merge=ours"
expectedEntry := ".github/workflows/*.lock.yml linguist-generated=true"
if !strings.Contains(string(content), expectedEntry) {
t.Errorf("Expected .gitattributes to contain %q", expectedEntry)
}
Expand Down Expand Up @@ -797,7 +797,7 @@ func TestInitRepositoryIdempotent(t *testing.T) {
t.Fatalf("Failed to read .gitattributes: %v", err)
}

expectedEntry := ".github/workflows/*.lock.yml linguist-generated=true merge=ours"
expectedEntry := ".github/workflows/*.lock.yml linguist-generated=true"

// Count occurrences - should only appear once
count := strings.Count(string(content), expectedEntry)
Expand Down Expand Up @@ -997,7 +997,7 @@ func TestInitRepositoryWithExistingFiles(t *testing.T) {
t.Error("Expected existing content to be preserved")
}

expectedEntry := ".github/workflows/*.lock.yml linguist-generated=true merge=ours"
expectedEntry := ".github/workflows/*.lock.yml linguist-generated=true"
if !strings.Contains(contentStr, expectedEntry) {
t.Error("Expected new entry to be added")
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/cli/init_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ func TestInitCommandIntegration(t *testing.T) {
gitAttrPath := filepath.Join(setup.tempDir, ".gitattributes")
content, err := os.ReadFile(gitAttrPath)
require.NoError(t, err, ".gitattributes should be created")
assert.Contains(t, string(content), ".github/workflows/*.lock.yml linguist-generated=true merge=ours",
assert.Contains(t, string(content), ".github/workflows/*.lock.yml linguist-generated=true",
".gitattributes should mark lock.yml files as generated")

// The dispatcher skill is created for every engine.
Expand Down
4 changes: 2 additions & 2 deletions pkg/cli/init_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,8 @@ func TestInitRepository(t *testing.T) {
if err != nil {
t.Fatalf("Failed to read .gitattributes: %v", err)
}
if !strings.Contains(string(content), ".github/workflows/*.lock.yml linguist-generated=true merge=ours") {
t.Errorf("Expected .gitattributes to contain '.github/workflows/*.lock.yml linguist-generated=true merge=ours'")
if !strings.Contains(string(content), ".github/workflows/*.lock.yml linguist-generated=true") {
t.Errorf("Expected .gitattributes to contain '.github/workflows/*.lock.yml linguist-generated=true'")
}
})
}
Expand Down
7 changes: 4 additions & 3 deletions pkg/constants/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -545,10 +545,10 @@ This appendix is generated from the current non-test Go source files in this pac
| Category | Count |
|----------|------:|
| Types | 14 |
| Constants | 305 |
| Constants | 306 |
| Variables | 23 |
| Functions and methods | 14 |
| Additional symbols documented in this appendix | 99 |
| Additional symbols documented in this appendix | 100 |

### Additional constants and variables

Expand Down Expand Up @@ -620,7 +620,8 @@ This appendix is generated from the current non-test Go source files in this pac
| `constants.go` | `const` | `UsrLocalPrefix` | `const UsrLocalPrefix = "/usr/local"` | UsrLocalPrefix is the standard /usr/local installation prefix. |
| `constants.go` | `const` | `WorkflowsDir` | `const WorkflowsDir = ".github/workflows"` | WorkflowsDir is the GitHub Actions workflow directory path (without trailing slash). |
| `constants.go` | `const` | `WorkflowsDirSlash` | `const WorkflowsDirSlash = WorkflowsDir + "/"` | WorkflowsDirSlash is WorkflowsDir with a trailing slash. |
| `constants.go` | `const` | `WorkflowsLockYmlGitAttributesEntry` | `const WorkflowsLockYmlGitAttributesEntry = WorkflowsLockYmlGlob + " linguist-generated=true merge=ours"` | WorkflowsLockYmlGitAttributesEntry is the . |
| `constants.go` | `const` | `WorkflowsLockYmlGitAttributesEntry` | `const WorkflowsLockYmlGitAttributesEntry = WorkflowsLockYmlGlob + " linguist-generated=true"` | WorkflowsLockYmlGitAttributesEntry is the .gitattributes entry that marks lock YAML files as generated. |
| `constants.go` | `const` | `WorkflowsLockYmlGitAttributesEntryLegacy` | `const WorkflowsLockYmlGitAttributesEntryLegacy = WorkflowsLockYmlGlob + " linguist-generated=true merge=ours"` | WorkflowsLockYmlGitAttributesEntryLegacy is the previous .gitattributes entry format that included an ineffective "merge=ours" attribute. It is only used to detect and clean up entries that gh-aw itself previously wrote, so we do not overwrite repository-owned policy. |
| `constants.go` | `const` | `WorkflowsLockYmlGlob` | `const WorkflowsLockYmlGlob = WorkflowsDirSlash + "*.lock.yml"` | WorkflowsLockYmlGlob is the glob pattern for compiled workflow lock YAML files. |
| `engine_constants.go` | `const` | `AnthropicAPIKey` | `const AnthropicAPIKey = "ANTHROPIC_API_KEY"` | AnthropicAPIKey is the API key secret name required by the Claude engine. |
| `engine_constants.go` | `const` | `CodexAPIKey` | `const CodexAPIKey = "CODEX_API_KEY"` | CodexAPIKey is the API key secret name used by the Codex engine. |
Expand Down
9 changes: 7 additions & 2 deletions pkg/constants/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -427,8 +427,13 @@ const AgentsDir = ".github/agents/"
const WorkflowsLockYmlGlob = WorkflowsDirSlash + "*.lock.yml"

// WorkflowsLockYmlGitAttributesEntry is the .gitattributes entry that marks lock YAML
// files as generated and sets the merge strategy.
const WorkflowsLockYmlGitAttributesEntry = WorkflowsLockYmlGlob + " linguist-generated=true merge=ours"
// files as generated.
const WorkflowsLockYmlGitAttributesEntry = WorkflowsLockYmlGlob + " linguist-generated=true"

// WorkflowsLockYmlGitAttributesEntryLegacy is the previous .gitattributes entry format that
// included an ineffective "merge=ours" attribute. It is only used to detect and clean up
// entries that gh-aw itself previously wrote, so we do not overwrite repository-owned policy.
const WorkflowsLockYmlGitAttributesEntryLegacy = WorkflowsLockYmlGlob + " linguist-generated=true merge=ours"

// Temporary runtime directory constants (/tmp/gh-aw tree)
//
Expand Down
Loading