From 273a0b3af9a978aae1bb2a80bdd7a49dd6fd2ac8 Mon Sep 17 00:00:00 2001 From: Timo Sand Date: Sun, 10 May 2026 21:21:00 +0300 Subject: [PATCH 01/11] Add failing regression test for files with colon in name Signed-off-by: Timo Sand --- .../resource_github_repository_file_test.go | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/github/resource_github_repository_file_test.go b/github/resource_github_repository_file_test.go index 57343b6dd8..ed778fcab2 100644 --- a/github/resource_github_repository_file_test.go +++ b/github/resource_github_repository_file_test.go @@ -8,6 +8,9 @@ import ( "github.com/hashicorp/terraform-plugin-testing/helper/acctest" "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/statecheck" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" ) func TestAccGithubRepositoryFile(t *testing.T) { @@ -455,4 +458,47 @@ func TestAccGithubRepositoryFile(t *testing.T) { }, }) }) + + t.Run("verify_that_id_can_contain_colon_in_file_path", func(t *testing.T) { + randomID := acctest.RandString(5) + repoName := fmt.Sprintf("%srepo-file-%s", testResourcePrefix, randomID) + filePathWithColon := "repro/example:one.yaml" + config := fmt.Sprintf(` + + resource "github_repository" "test" { + name = "%s" + auto_init = true + vulnerability_alerts = true + } + + resource "github_repository_file" "test" { + repository = github_repository.test.name + branch = "main" + file = "%s" + content = "bar" + commit_message = "Managed by Terraform" + commit_author = "Terraform User" + commit_email = "terraform@example.com" + } + `, repoName, filePathWithColon) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { skipUnauthenticated(t) }, + ProviderFactories: providerFactories, + Steps: []resource.TestStep{ + { + Config: config, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("github_repository_file.test", tfjsonpath.New("id"), knownvalue.StringFunc(func(v string) error { + if strings.Contains(v, escapeIDPart(filePathWithColon)) { + return nil + } + return fmt.Errorf("expected id to contain escaped file path, got: %s", v) + })), + statecheck.ExpectKnownValue("github_repository_file.test", tfjsonpath.New("file"), knownvalue.StringExact(filePathWithColon)), + }, + }, + }, + }) + }) } From 85118fd9a97d3818059a4268d351733ef737ac3b Mon Sep 17 00:00:00 2001 From: Timo Sand Date: Sun, 10 May 2026 21:51:29 +0300 Subject: [PATCH 02/11] Ensure that all file paths are escaped for resource ID Signed-off-by: Timo Sand --- github/resource_github_repository_file.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/github/resource_github_repository_file.go b/github/resource_github_repository_file.go index 870636c4ec..ad4a5b1e11 100644 --- a/github/resource_github_repository_file.go +++ b/github/resource_github_repository_file.go @@ -242,7 +242,7 @@ func resourceGithubRepositoryFileCreate(ctx context.Context, d *schema.ResourceD return diag.FromErr(err) } - newResourceID, err := buildID(repo, file, branch) + newResourceID, err := buildID(repo, escapeIDPart(file), branch) if err != nil { return diag.FromErr(err) } @@ -392,7 +392,7 @@ func resourceGithubRepositoryFileUpdate(ctx context.Context, d *schema.ResourceD } if d.HasChanges("repository", "file", "branch") { - newResourceID, err := buildID(repo, file, branch) + newResourceID, err := buildID(repo, escapeIDPart(file), branch) if err != nil { return diag.FromErr(err) } @@ -468,7 +468,7 @@ func resourceGithubRepositoryFileImport(ctx context.Context, d *schema.ResourceD return nil, err } - newResourceID, err := buildID(repo, filePath, branch) + newResourceID, err := buildID(repo, escapeIDPart(filePath), branch) if err != nil { return nil, err } From c536a44fd315ff1988850c3b170b4bad47d78c19 Mon Sep 17 00:00:00 2001 From: Timo Sand Date: Sun, 24 May 2026 15:30:55 +0300 Subject: [PATCH 03/11] Ensure that `github_repository_file` migration doesn't fail with unescapred ID parts Signed-off-by: Timo Sand --- github/resource_github_repository_file_migration.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/github/resource_github_repository_file_migration.go b/github/resource_github_repository_file_migration.go index 1c6739e58d..fd112d2f45 100644 --- a/github/resource_github_repository_file_migration.go +++ b/github/resource_github_repository_file_migration.go @@ -85,7 +85,7 @@ func resourceGithubRepositoryFileV0() *schema.Resource { } func resourceGithubRepositoryFileStateUpgradeV0(ctx context.Context, rawState map[string]any, m any) (map[string]any, error) { - tflog.Debug(ctx, "GitHub Repository File State before migration", map[string]any{ + tflog.Debug(ctx, "GitHub Repository File State before v0 migration", map[string]any{ "rawState": rawState, }) @@ -95,12 +95,12 @@ func resourceGithubRepositoryFileStateUpgradeV0(ctx context.Context, rawState ma repoName, ok := rawState["repository"].(string) if !ok { - return nil, fmt.Errorf("repository not found or is not a string") + return nil, fmt.Errorf("state upgrade v0: repository not found or is not a string") } repo, _, err := client.Repositories.Get(ctx, owner, repoName) if err != nil { - return nil, fmt.Errorf("failed to retrieve repository '%s': %w", repoName, err) + return nil, fmt.Errorf("state upgrade v0: failed to retrieve repository '%s': %w", repoName, err) } rawState["repository_id"] = int(repo.GetID()) @@ -110,13 +110,13 @@ func resourceGithubRepositoryFileStateUpgradeV0(ctx context.Context, rawState ma rawState["branch"] = repo.GetDefaultBranch() } - newResourceID, err := buildID(rawState["repository"].(string), rawState["file"].(string), rawState["branch"].(string)) + newResourceID, err := buildID(rawState["repository"].(string), escapeIDPart(rawState["file"].(string)), rawState["branch"].(string)) if err != nil { - return nil, fmt.Errorf("failed to build ID: %w", err) + return nil, fmt.Errorf("state upgrade v0: failed to build ID: %w", err) } rawState["id"] = newResourceID - tflog.Debug(ctx, "GitHub Repository File State after migration", map[string]any{ + tflog.Debug(ctx, "GitHub Repository File State after v0 migration", map[string]any{ "rawState": rawState, }) return rawState, nil From da6219b8a914635bc0216187bddceaacc88977f1 Mon Sep 17 00:00:00 2001 From: Timo Sand Date: Sun, 24 May 2026 15:32:25 +0300 Subject: [PATCH 04/11] Fix `example/dev.tfrc` - We want to ensure that users never accidentally get `hashicorp/github` - `~/` failed to expand on macOS Signed-off-by: Timo Sand --- examples/dev.tfrc | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/examples/dev.tfrc b/examples/dev.tfrc index 82a9812a63..d293f65e0c 100644 --- a/examples/dev.tfrc +++ b/examples/dev.tfrc @@ -1,7 +1,9 @@ provider_installation { dev_overrides { - "integrations/github" = "~/go/bin/" + "integrations/github" = "$HOME/go/bin/" } - direct {} + direct { + exclude = ["registry.terraform.io/hashicorp/github"] + } } \ No newline at end of file From fb5e6f7c8c13af320fdda4043537c2effdd0c4b9 Mon Sep 17 00:00:00 2001 From: Timo Sand Date: Sun, 24 May 2026 15:34:17 +0300 Subject: [PATCH 05/11] Fix deprecated attributes in `repository_file` example Signed-off-by: Timo Sand --- docs/resources/repository_file.md | 15 ++++++++++----- examples/resources/repository_file/example_2.tf | 16 ++++++++++------ 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/docs/resources/repository_file.md b/docs/resources/repository_file.md index 700b3c00fc..7971eed0ea 100644 --- a/docs/resources/repository_file.md +++ b/docs/resources/repository_file.md @@ -35,13 +35,19 @@ resource "github_repository_file" "foo" { ### Auto Created Branch ```terraform -resource "github_repository" "foo" { - name = "example" +resource "github_repository" "bar" { + name = "example2" auto_init = true } -resource "github_repository_file" "foo" { - repository = github_repository.foo.name +resource "github_branch" "bar" { + branch = "does/not/exist" + repository = github_repository.bar.name + +} + +resource "github_repository_file" "bar" { + repository = github_repository.bar.name branch = "does/not/exist" file = ".gitignore" content = "**/*.tfstate" @@ -49,7 +55,6 @@ resource "github_repository_file" "foo" { commit_author = "Terraform User" commit_email = "terraform@example.com" overwrite_on_create = true - autocreate_branch = true } ``` diff --git a/examples/resources/repository_file/example_2.tf b/examples/resources/repository_file/example_2.tf index 0cd472fba5..6e23752c8d 100644 --- a/examples/resources/repository_file/example_2.tf +++ b/examples/resources/repository_file/example_2.tf @@ -1,11 +1,17 @@ -resource "github_repository" "foo" { - name = "example" +resource "github_repository" "bar" { + name = "example2" auto_init = true } -resource "github_repository_file" "foo" { - repository = github_repository.foo.name +resource "github_branch" "bar" { + branch = "does/not/exist" + repository = github_repository.bar.name + +} + +resource "github_repository_file" "bar" { + repository = github_repository.bar.name branch = "does/not/exist" file = ".gitignore" content = "**/*.tfstate" @@ -13,6 +19,4 @@ resource "github_repository_file" "foo" { commit_author = "Terraform User" commit_email = "terraform@example.com" overwrite_on_create = true - autocreate_branch = true } - From 7cf1c09d5aa101dd2ebb0718edb8ec0e3c96028f Mon Sep 17 00:00:00 2001 From: Timo Sand Date: Sun, 24 May 2026 15:40:34 +0300 Subject: [PATCH 06/11] Make the linter happy Signed-off-by: Timo Sand --- github/resource_github_repository_file_migration.go | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/github/resource_github_repository_file_migration.go b/github/resource_github_repository_file_migration.go index fd112d2f45..55d3b8d9b8 100644 --- a/github/resource_github_repository_file_migration.go +++ b/github/resource_github_repository_file_migration.go @@ -105,12 +105,19 @@ func resourceGithubRepositoryFileStateUpgradeV0(ctx context.Context, rawState ma rawState["repository_id"] = int(repo.GetID()) + branch, ok := rawState["branch"].(string) // If branch is missing or empty, fetch the default branch from the repository - if branch, ok := rawState["branch"].(string); !ok || branch == "" { - rawState["branch"] = repo.GetDefaultBranch() + if !ok || branch == "" { + branch = repo.GetDefaultBranch() + rawState["branch"] = branch } - newResourceID, err := buildID(rawState["repository"].(string), escapeIDPart(rawState["file"].(string)), rawState["branch"].(string)) + filePath, ok := rawState["file"].(string) + if !ok { + return nil, fmt.Errorf("state upgrade v0: file path is not a string") + } + + newResourceID, err := buildID(repoName, escapeIDPart(filePath), branch) if err != nil { return nil, fmt.Errorf("state upgrade v0: failed to build ID: %w", err) } From 6ccb33cf16eebeb66e7ea36ff8bd924570ce14e4 Mon Sep 17 00:00:00 2001 From: Timo Sand Date: Tue, 26 May 2026 20:43:00 +0300 Subject: [PATCH 07/11] Fix missing newline Signed-off-by: Timo Sand --- examples/dev.tfrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/dev.tfrc b/examples/dev.tfrc index d293f65e0c..e89c638dda 100644 --- a/examples/dev.tfrc +++ b/examples/dev.tfrc @@ -6,4 +6,4 @@ provider_installation { direct { exclude = ["registry.terraform.io/hashicorp/github"] } -} \ No newline at end of file +} From a74a1ef57d21c6f2f5a82036c97172df0ecd0ea1 Mon Sep 17 00:00:00 2001 From: Timo Sand Date: Sat, 30 May 2026 14:27:17 +0300 Subject: [PATCH 08/11] Ensure we don't swallow any conversion errors Signed-off-by: Timo Sand --- ...source_github_repository_file_migration.go | 33 ++++++++++++++----- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/github/resource_github_repository_file_migration.go b/github/resource_github_repository_file_migration.go index 55d3b8d9b8..219d1f0eca 100644 --- a/github/resource_github_repository_file_migration.go +++ b/github/resource_github_repository_file_migration.go @@ -93,9 +93,14 @@ func resourceGithubRepositoryFileStateUpgradeV0(ctx context.Context, rawState ma client := meta.v3client owner := meta.name - repoName, ok := rawState["repository"].(string) - if !ok { - return nil, fmt.Errorf("state upgrade v0: repository not found or is not a string") + repoName := "" + if v, ok := rawState["repository"]; ok { + if s, ok := v.(string); ok && s != "" { + repoName = s + } + } + if repoName == "" { + return nil, fmt.Errorf("state upgrade v0: repository is not a string or not set") } repo, _, err := client.Repositories.Get(ctx, owner, repoName) @@ -105,15 +110,27 @@ func resourceGithubRepositoryFileStateUpgradeV0(ctx context.Context, rawState ma rawState["repository_id"] = int(repo.GetID()) - branch, ok := rawState["branch"].(string) - // If branch is missing or empty, fetch the default branch from the repository - if !ok || branch == "" { + branch := "" + if v, ok := rawState["branch"]; ok { + if s, ok := v.(string); ok && s != "" { + branch = s + } + } + + // If branch is empty, fetch the default branch from the repository + if branch == "" { branch = repo.GetDefaultBranch() rawState["branch"] = branch } - filePath, ok := rawState["file"].(string) - if !ok { + filePath := "" + if v, ok := rawState["file"]; ok { + if s, ok := v.(string); ok && s != "" { + filePath = s + } + } + + if filePath == "" { return nil, fmt.Errorf("state upgrade v0: file path is not a string") } From 4ea73fbac8db57766ce9d661a5610a66ee01b0d0 Mon Sep 17 00:00:00 2001 From: Timo Sand Date: Sat, 30 May 2026 14:28:42 +0300 Subject: [PATCH 09/11] Propose migration unit test with mock API Signed-off-by: Timo Sand --- ...e_github_repository_file_migration_test.go | 308 ++++++++++-------- 1 file changed, 167 insertions(+), 141 deletions(-) diff --git a/github/resource_github_repository_file_migration_test.go b/github/resource_github_repository_file_migration_test.go index f9bd8f866b..a9081639cc 100644 --- a/github/resource_github_repository_file_migration_test.go +++ b/github/resource_github_repository_file_migration_test.go @@ -2,154 +2,180 @@ package github // TODO: Enable this test once we have a way to mock the GitHub API -// import ( -// "context" -// "encoding/json" -// "fmt" -// "net/http" -// "net/url" -// "testing" +import ( + "encoding/json" + "fmt" + "net/http" + "testing" -// "github.com/google/go-cmp/cmp" -// "github.com/google/go-github/v88/github" -// ) + "github.com/google/go-cmp/cmp" + "github.com/google/go-github/v88/github" +) -// func buildMockResponsesForRepositoryFileMigrationV0toV1(mockOwner, mockRepo string, wantRepoID int) []*mockResponse { -// responseBodyJson, err := json.Marshal(github.Repository{ -// ID: github.Ptr(int64(wantRepoID)), -// DefaultBranch: github.Ptr("main"), -// Name: github.Ptr(mockRepo), -// }) -// if err != nil { -// panic(fmt.Sprintf("error marshalling repository response: %s", err)) -// } -// return []*mockResponse{{ -// ExpectedUri: fmt.Sprintf("/repos/%s/%s", mockOwner, mockRepo), -// ExpectedHeaders: map[string]string{ -// "Accept": "application/vnd.github.scarlet-witch-preview+json, application/vnd.github.mercy-preview+json, application/vnd.github.baptiste-preview+json, application/vnd.github.nebula-preview+json", -// }, -// ResponseBody: string(responseBodyJson), -// StatusCode: http.StatusOK, -// }} -// } +func buildMockResponsesForRepositoryFileMigrationV0toV1(mockOwner, mockRepo string, wantRepoID int) []*mockResponse { + responseBodyJson, err := json.Marshal(github.Repository{ + ID: new(int64(wantRepoID)), + DefaultBranch: new("main"), + Name: new(mockRepo), + }) + if err != nil { + panic(fmt.Sprintf("error marshalling repository response: %s", err)) + } + return []*mockResponse{{ + ExpectedUri: fmt.Sprintf("/repos/%s/%s", mockOwner, mockRepo), + ExpectedHeaders: map[string]string{ + "Accept": "application/vnd.github.scarlet-witch-preview+json, application/vnd.github.mercy-preview+json, application/vnd.github.baptiste-preview+json, application/vnd.github.nebula-preview+json", + }, + ResponseBody: string(responseBodyJson), + StatusCode: http.StatusOK, + }} +} -// func Test_resourceGithubRepositoryFileStateUpgradeV0toV1(t *testing.T) { -// t.Parallel() +func Test_resourceGithubRepositoryFileStateUpgradeV0toV1(t *testing.T) { + t.Parallel() -// for _, d := range []struct { -// testName string -// rawState map[string]any -// want map[string]any -// shouldError bool -// }{ -// { -// testName: "preserves_existing_branch", -// rawState: map[string]any{ -// "id": "test-repo/path/to/file.txt", -// "repository": "test-repo", -// "file": "path/to/file.txt", -// "content": "file content", -// "branch": "main", -// "commit_sha": "abc123", -// "sha": "def456", -// "overwrite_on_create": false, -// }, -// want: map[string]any{ -// "id": "test-repo:path/to/file.txt:main", -// "repository": "test-repo", -// "repository_id": 1234567890, -// "file": "path/to/file.txt", -// "content": "file content", -// "branch": "main", -// "commit_sha": "abc123", -// "sha": "def456", -// "overwrite_on_create": false, -// }, -// shouldError: false, -// }, -// { -// testName: "preserves_custom_branch", -// rawState: map[string]any{ -// "id": "test-repo/README.md", -// "repository": "test-repo", -// "file": "README.md", -// "content": "# README", -// "branch": "develop", -// }, -// want: map[string]any{ -// "id": "test-repo:README.md:develop", -// "repository": "test-repo", -// "repository_id": 1234567890, -// "file": "README.md", -// "content": "# README", -// "branch": "develop", -// }, -// shouldError: false, -// }, -// { -// testName: "migrates_with_missing_branch", -// rawState: map[string]any{ -// "id": "test-repo/path/to/file.txt", -// "repository": "test-repo", -// "file": "path/to/file.txt", -// "content": "file content", -// }, -// want: map[string]any{ -// "id": "test-repo:path/to/file.txt:main", -// "repository": "test-repo", -// "repository_id": 1234567890, -// "file": "path/to/file.txt", -// "content": "file content", -// "branch": "main", // fetched from API -// }, -// shouldError: false, -// }, -// { -// testName: "migrates_with_empty_branch", -// rawState: map[string]any{ -// "id": "test-repo/path/to/file.txt", -// "repository": "test-repo", -// "file": "path/to/file.txt", -// "content": "file content", -// "branch": "", -// }, -// want: map[string]any{ -// "id": "test-repo:path/to/file.txt:main", -// "repository": "test-repo", -// "repository_id": 1234567890, -// "file": "path/to/file.txt", -// "content": "file content", -// "branch": "main", // fetched from API -// }, -// shouldError: false, -// }, -// } { -// t.Run(d.testName, func(t *testing.T) { -// t.Parallel() + for _, d := range []struct { + testName string + rawState map[string]any + want map[string]any + shouldError bool + }{ + { + testName: "preserves_existing_branch", + rawState: map[string]any{ + "id": "test-repo/path/to/file.txt", + "repository": "test-repo", + "file": "path/to/file.txt", + "content": "file content", + "branch": "main", + "commit_sha": "abc123", + "sha": "def456", + "overwrite_on_create": false, + }, + want: map[string]any{ + "id": "test-repo:path/to/file.txt:main", + "repository": "test-repo", + "repository_id": 1234567890, + "file": "path/to/file.txt", + "content": "file content", + "branch": "main", + "commit_sha": "abc123", + "sha": "def456", + "overwrite_on_create": false, + }, + shouldError: false, + }, + { + testName: "preserves_custom_branch", + rawState: map[string]any{ + "id": "test-repo/README.md", + "repository": "test-repo", + "file": "README.md", + "content": "# README", + "branch": "develop", + }, + want: map[string]any{ + "id": "test-repo:README.md:develop", + "repository": "test-repo", + "repository_id": 1234567890, + "file": "README.md", + "content": "# README", + "branch": "develop", + }, + shouldError: false, + }, + { + testName: "migrates_with_missing_branch", + rawState: map[string]any{ + "id": "test-repo/path/to/file.txt", + "repository": "test-repo", + "file": "path/to/file.txt", + "content": "file content", + }, + want: map[string]any{ + "id": "test-repo:path/to/file.txt:main", + "repository": "test-repo", + "repository_id": 1234567890, + "file": "path/to/file.txt", + "content": "file content", + "branch": "main", // fetched from API + }, + shouldError: false, + }, + { + testName: "migrates_with_empty_branch", + rawState: map[string]any{ + "id": "test-repo/path/to/file.txt", + "repository": "test-repo", + "file": "path/to/file.txt", + "content": "file content", + "branch": "", + }, + want: map[string]any{ + "id": "test-repo:path/to/file.txt:main", + "repository": "test-repo", + "repository_id": 1234567890, + "file": "path/to/file.txt", + "content": "file content", + "branch": "main", // fetched from API + }, + shouldError: false, + }, + { + testName: "migrates_with_colon_in_file_path", + rawState: map[string]any{ + "id": "test-repo/path/to:file.txt", + "repository": "test-repo", + "file": "path/to:file.txt", + "content": "file content", + "branch": "main", + }, + want: map[string]any{ + "id": "test-repo:path/to??file.txt:main", + "repository": "test-repo", + "repository_id": 1234567890, + "file": "path/to:file.txt", + "content": "file content", + "branch": "main", + }, + shouldError: false, + }, + } { + t.Run(d.testName, func(t *testing.T) { + t.Parallel() -// meta := &Owner{ -// name: "test-org", -// } + meta := &Owner{ + name: "test-org", + } -// ts := githubApiMock(buildMockResponsesForRepositoryFileMigrationV0toV1(meta.name, d.want["repository"].(string), d.want["repository_id"].(int))) -// defer ts.Close() + wantRepositoryName := "" + if v, ok := d.want["repository"]; ok { + if s, ok := v.(string); ok { + wantRepositoryName = s + } + } -// httpCl := http.DefaultClient -// httpCl.Transport = http.DefaultTransport + wantRepositoryID := -1 + if v, ok := d.want["repository_id"]; ok { + if i, ok := v.(int); ok { + wantRepositoryID = i + } + } -// client := github.NewClient(httpCl) -// u, _ := url.Parse(ts.URL + "/") -// client.BaseURL = u -// meta.v3client = client + ts := githubApiMock(buildMockResponsesForRepositoryFileMigrationV0toV1(meta.name, wantRepositoryName, wantRepositoryID)) + defer ts.Close() -// got, err := resourceGithubRepositoryFileStateUpgradeV0(t.Context(), d.rawState, meta) -// if (err != nil) != d.shouldError { -// t.Fatalf("unexpected error state: got error %v, shouldError %v", err, d.shouldError) -// } + client := mustGitHubClient(t, ts.URL) + meta.v3client = client -// if diff := cmp.Diff(got, d.want); diff != "" && !d.shouldError { -// t.Fatalf("got %+v, want %+v, diff %s", got, d.want, diff) -// } -// }) -// } -// } + got, err := resourceGithubRepositoryFileStateUpgradeV0(t.Context(), d.rawState, meta) + if (err != nil) != d.shouldError { + t.Fatalf("unexpected error state: got error %v, shouldError %v", err, d.shouldError) + } + + if diff := cmp.Diff(got, d.want); diff != "" && !d.shouldError { + t.Fatalf("got %+v, want %+v, diff %s", got, d.want, diff) + } + }) + } +} From 510651d8fc99e66f96babf7bd87e9df6f4a6eef6 Mon Sep 17 00:00:00 2001 From: Timo Sand Date: Mon, 1 Jun 2026 21:27:06 +0300 Subject: [PATCH 10/11] Fix import issue (thanks copilot!) Signed-off-by: Timo Sand --- github/resource_github_repository_file.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/github/resource_github_repository_file.go b/github/resource_github_repository_file.go index ad4a5b1e11..8c4a45f9b0 100644 --- a/github/resource_github_repository_file.go +++ b/github/resource_github_repository_file.go @@ -433,11 +433,13 @@ func autoBranchDiffSuppressFunc(k, _, _ string, d *schema.ResourceData) bool { } func resourceGithubRepositoryFileImport(ctx context.Context, d *schema.ResourceData, meta any) ([]*schema.ResourceData, error) { - repo, filePath, branch, err := parseID3(d.Id()) + repo, filePathPart, branch, err := parseID3(d.Id()) if err != nil { return nil, fmt.Errorf("invalid ID specified. Supplied ID must be written as :: (when branch is default) or ::. %w", err) } + filePath := unescapeIDPart(filePathPart) + client := meta.(*Owner).v3client owner := meta.(*Owner).name From fb7b6f6b026ae57e6c4ed124d6d92b086d6a3ae6 Mon Sep 17 00:00:00 2001 From: Timo Sand Date: Mon, 1 Jun 2026 21:29:25 +0300 Subject: [PATCH 11/11] Update documentation to be clear about how to escape `:` Signed-off-by: Timo Sand --- docs/resources/actions_environment_secret.md | 2 +- docs/resources/repository_file.md | 2 +- templates/resources/actions_environment_secret.md.tmpl | 2 +- templates/resources/repository_file.md.tmpl | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/resources/actions_environment_secret.md b/docs/resources/actions_environment_secret.md index a8766dec48..67cd90719a 100644 --- a/docs/resources/actions_environment_secret.md +++ b/docs/resources/actions_environment_secret.md @@ -90,7 +90,7 @@ The following arguments are supported: ## Import -This resource can be imported using an ID made of the repository name, environment name (URL escaped), and secret name all separated by a `:`. +This resource can be imported using an ID made of the repository name, environment name (any `:` in the environment name need to be escaped as `??`), and secret name all separated by a `:`. ~> **Note**: When importing secrets, the `value`, `value_encrypted`, `encrypted_value`, or `plaintext_value` fields will not be populated in the state. You may need to ignore changes for these as a workaround if you're not planning on updating the secret through Terraform. diff --git a/docs/resources/repository_file.md b/docs/resources/repository_file.md index 7971eed0ea..aa6ca09cf4 100644 --- a/docs/resources/repository_file.md +++ b/docs/resources/repository_file.md @@ -98,7 +98,7 @@ The following additional attributes are exported: ## Import -Repository files can be imported using a combination of the `repo`, `file` and `branch` or empty branch for the default branch, e.g. +Repository files can be imported using a combination of the `repo`, `file path` (any `:` in the file path need to be escaped as `??`) and `branch` or empty branch for the default branch, e.g. ```sh terraform import github_repository_file.gitignore example:.gitignore:feature-branch diff --git a/templates/resources/actions_environment_secret.md.tmpl b/templates/resources/actions_environment_secret.md.tmpl index eda57c9fad..e6eee7316f 100644 --- a/templates/resources/actions_environment_secret.md.tmpl +++ b/templates/resources/actions_environment_secret.md.tmpl @@ -48,7 +48,7 @@ The following arguments are supported: ## Import -This resource can be imported using an ID made of the repository name, environment name (URL escaped), and secret name all separated by a `:`. +This resource can be imported using an ID made of the repository name, environment name (any `:` in the environment name need to be escaped as `??`), and secret name all separated by a `:`. ~> **Note**: When importing secrets, the `value`, `value_encrypted`, `encrypted_value`, or `plaintext_value` fields will not be populated in the state. You may need to ignore changes for these as a workaround if you're not planning on updating the secret through Terraform. diff --git a/templates/resources/repository_file.md.tmpl b/templates/resources/repository_file.md.tmpl index 25b9d6c198..54ce081ec0 100644 --- a/templates/resources/repository_file.md.tmpl +++ b/templates/resources/repository_file.md.tmpl @@ -60,7 +60,7 @@ The following additional attributes are exported: ## Import -Repository files can be imported using a combination of the `repo`, `file` and `branch` or empty branch for the default branch, e.g. +Repository files can be imported using a combination of the `repo`, `file path` (any `:` in the file path need to be escaped as `??`) and `branch` or empty branch for the default branch, e.g. ```sh terraform import github_repository_file.gitignore example:.gitignore:feature-branch