Skip to content

Commit 6c6b171

Browse files
authored
impl(sidekick): compute changed files (#2143)
1 parent 2e21c79 commit 6c6b171

File tree

6 files changed

+293
-41
lines changed

6 files changed

+293
-41
lines changed

internal/sidekick/internal/rust_release/bump_versions.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,11 @@ func BumpVersions(config *config.Release) error {
2222
if err := PreFlight(config); err != nil {
2323
return err
2424
}
25-
_, err := getLastTag(config)
25+
lastTag, err := getLastTag(config)
26+
if err != nil {
27+
return err
28+
}
29+
_, err = filesChangedSince(config, lastTag)
2630
if err != nil {
2731
return err
2832
}

internal/sidekick/internal/rust_release/bump_versions_test.go

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,26 @@
1515
package rustrelease
1616

1717
import (
18+
"fmt"
19+
"os"
20+
"os/exec"
21+
"path"
1822
"testing"
1923

2024
"github.com/googleapis/librarian/internal/sidekick/internal/config"
2125
"github.com/googleapis/librarian/internal/sidekick/internal/external"
2226
)
2327

28+
const (
29+
initialCargoContents = `# Example Cargo file
30+
[package]
31+
name = "%s"
32+
version = "1.0.0"
33+
`
34+
35+
initialLibRsContents = `pub fn test() -> &'static str { "Hello World" }`
36+
)
37+
2438
func TestBumpVersionsSuccess(t *testing.T) {
2539
requireCommand(t, "git")
2640
config := &config.Release{
@@ -77,4 +91,65 @@ func setupForVersionBump(t *testing.T, wantTag string) {
7791
if err := external.Run("git", "clone", remoteDir, "."); err != nil {
7892
t.Fatal(err)
7993
}
94+
configNewGitRepository(t)
95+
}
96+
97+
func continueInNewGitRepository(t *testing.T, tmpDir string) {
98+
t.Helper()
99+
requireCommand(t, "git")
100+
t.Chdir(tmpDir)
101+
if err := external.Run("git", "init", "-b", "main"); err != nil {
102+
t.Fatal(err)
103+
}
104+
configNewGitRepository(t)
105+
}
106+
107+
func requireCommand(t *testing.T, command string) {
108+
t.Helper()
109+
if _, err := exec.LookPath(command); err != nil {
110+
t.Skipf("skipping test because %s is not installed", command)
111+
}
112+
}
113+
114+
func configNewGitRepository(t *testing.T) {
115+
if err := external.Run("git", "config", "user.email", "[email protected]"); err != nil {
116+
t.Fatal(err)
117+
}
118+
if err := external.Run("git", "config", "user.name", "Test Account"); err != nil {
119+
t.Fatal(err)
120+
}
121+
}
122+
123+
func initRepositoryContents(t *testing.T) {
124+
t.Helper()
125+
requireCommand(t, "git")
126+
if err := os.WriteFile("README.md", []byte("# Empty Repo"), 0644); err != nil {
127+
t.Fatal(err)
128+
}
129+
addCrate(t, path.Join("src", "storage"), "google-cloud-storage")
130+
addCrate(t, path.Join("src", "generated", "cloud", "secretmanager", "v1"), "google-cloud-secretmanager-v1")
131+
if err := external.Run("git", "add", "."); err != nil {
132+
t.Fatal(err)
133+
}
134+
if err := external.Run("git", "commit", "-m", "initial version"); err != nil {
135+
t.Fatal(err)
136+
}
137+
}
138+
139+
func addCrate(t *testing.T, location, name string) {
140+
t.Helper()
141+
_ = os.MkdirAll(path.Join(location, "src"), 0755)
142+
contents := []byte(fmt.Sprintf(initialCargoContents, name))
143+
if err := os.WriteFile(path.Join(location, "Cargo.toml"), contents, 0644); err != nil {
144+
t.Fatal(err)
145+
}
146+
if err := os.WriteFile(path.Join(location, "src", "lib.rs"), []byte(initialLibRsContents), 0644); err != nil {
147+
t.Fatal(err)
148+
}
149+
if err := os.WriteFile(path.Join(location, ".sidekick.toml"), []byte("# initial version"), 0644); err != nil {
150+
t.Fatal(err)
151+
}
152+
if err := os.WriteFile(path.Join(location, ".repo-metadata.json"), []byte("{}"), 0644); err != nil {
153+
t.Fatal(err)
154+
}
80155
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package rustrelease
16+
17+
import (
18+
"os/exec"
19+
"slices"
20+
"strings"
21+
22+
"github.com/go-git/go-git/v5/plumbing/format/gitignore"
23+
"github.com/googleapis/librarian/internal/sidekick/internal/config"
24+
)
25+
26+
func filesChangedSince(config *config.Release, ref string) ([]string, error) {
27+
cmd := exec.Command(gitExe(config), "diff", "--name-only", ref)
28+
cmd.Dir = "."
29+
output, err := cmd.CombinedOutput()
30+
if err != nil {
31+
return nil, err
32+
}
33+
return filesFilter(config, strings.Split(string(output), "\n")), nil
34+
}
35+
36+
func filesFilter(config *config.Release, files []string) []string {
37+
var patterns []gitignore.Pattern
38+
for _, p := range config.IgnoredChanges {
39+
patterns = append(patterns, gitignore.ParsePattern(p, nil))
40+
}
41+
matcher := gitignore.NewMatcher(patterns)
42+
43+
files = slices.DeleteFunc(files, func(a string) bool {
44+
if a == "" {
45+
return true
46+
}
47+
return matcher.Match(strings.Split(a, "/"), false)
48+
})
49+
return files
50+
}
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package rustrelease
16+
17+
import (
18+
"os"
19+
"path"
20+
"testing"
21+
22+
"github.com/google/go-cmp/cmp"
23+
"github.com/googleapis/librarian/internal/sidekick/internal/config"
24+
"github.com/googleapis/librarian/internal/sidekick/internal/external"
25+
)
26+
27+
const (
28+
newLibRsContents = `pub fn hello() -> &'static str { "Hello World" }`
29+
)
30+
31+
func TestFilesChangedSuccess(t *testing.T) {
32+
const wantTag = "release-2001-02-03"
33+
34+
const echo = "/bin/echo"
35+
requireCommand(t, echo)
36+
release := config.Release{
37+
Remote: "origin",
38+
Branch: "main",
39+
Preinstalled: map[string]string{
40+
"cargo": echo,
41+
},
42+
}
43+
setupForVersionBump(t, wantTag)
44+
name := path.Join("src", "storage", "src", "lib.rs")
45+
if err := os.WriteFile(name, []byte(newLibRsContents), 0644); err != nil {
46+
t.Fatal(err)
47+
}
48+
if err := external.Run("git", "commit", "-m", "feat: changed storage", "."); err != nil {
49+
t.Fatal(err)
50+
}
51+
52+
got, err := filesChangedSince(&release, wantTag)
53+
if err != nil {
54+
t.Fatal(err)
55+
}
56+
want := []string{name}
57+
if diff := cmp.Diff(want, got); diff != "" {
58+
t.Errorf("mismatch (-want, +got):\n%s", diff)
59+
}
60+
}
61+
62+
func TestFilesBadRef(t *testing.T) {
63+
const wantTag = "release-2002-03-04"
64+
65+
const echo = "/bin/echo"
66+
requireCommand(t, echo)
67+
release := config.Release{
68+
Remote: "origin",
69+
Branch: "main",
70+
Preinstalled: map[string]string{
71+
"cargo": echo,
72+
},
73+
}
74+
setupForVersionBump(t, wantTag)
75+
if got, err := filesChangedSince(&release, "--invalid--"); err == nil {
76+
t.Errorf("expected an error with invalid tag, got=%v", got)
77+
}
78+
}
79+
80+
func TestFilterNoFilter(t *testing.T) {
81+
input := []string{
82+
"src/storage/src/lib.rs",
83+
"src/storage/Cargo.toml",
84+
"src/storage/.repo-metadata.json",
85+
"src/generated/cloud/secretmanager/v1/.sidekick.toml",
86+
"src/generated/cloud/secretmanager/v1/Cargo.toml",
87+
"src/generated/cloud/secretmanager/v1/src/model.rs",
88+
}
89+
90+
config := &config.Release{}
91+
got := filesFilter(config, input)
92+
want := input
93+
if diff := cmp.Diff(want, got); diff != "" {
94+
t.Errorf("mismatch (-want, +got):\n%s", diff)
95+
}
96+
}
97+
98+
func TestFilterBasic(t *testing.T) {
99+
input := []string{
100+
"src/storage/src/lib.rs",
101+
"src/storage/Cargo.toml",
102+
"src/storage/.repo-metadata.json",
103+
"src/generated/cloud/secretmanager/v1/.sidekick.toml",
104+
"src/generated/cloud/secretmanager/v1/Cargo.toml",
105+
"src/generated/cloud/secretmanager/v1/src/model.rs",
106+
}
107+
108+
config := &config.Release{
109+
IgnoredChanges: []string{
110+
".sidekick.toml",
111+
".repo-metadata.json",
112+
},
113+
}
114+
got := filesFilter(config, input)
115+
want := []string{
116+
"src/storage/src/lib.rs",
117+
"src/storage/Cargo.toml",
118+
"src/generated/cloud/secretmanager/v1/Cargo.toml",
119+
"src/generated/cloud/secretmanager/v1/src/model.rs",
120+
}
121+
if diff := cmp.Diff(want, got); diff != "" {
122+
t.Errorf("mismatch (-want, +got):\n%s", diff)
123+
}
124+
}
125+
126+
func TestFilterSomeGlobs(t *testing.T) {
127+
input := []string{
128+
"doc/howto-1.md",
129+
"doc/howto-2.md",
130+
}
131+
132+
config := &config.Release{
133+
IgnoredChanges: []string{
134+
".sidekick.toml",
135+
".repo-metadata.json",
136+
"doc/**",
137+
},
138+
}
139+
got := filesFilter(config, input)
140+
want := []string{}
141+
if diff := cmp.Diff(want, got); diff != "" {
142+
t.Errorf("mismatch (-want, +got):\n%s", diff)
143+
}
144+
}
145+
146+
func TestFilterEmpty(t *testing.T) {
147+
input := []string{
148+
"",
149+
}
150+
151+
config := &config.Release{
152+
IgnoredChanges: []string{
153+
".sidekick.toml",
154+
".repo-metadata.json",
155+
"doc/**",
156+
},
157+
}
158+
got := filesFilter(config, input)
159+
want := []string{}
160+
if diff := cmp.Diff(want, got); diff != "" {
161+
t.Errorf("mismatch (-want, +got):\n%s", diff)
162+
}
163+
}

internal/sidekick/internal/rust_release/last_tag_test.go

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,9 @@
1515
package rustrelease
1616

1717
import (
18-
"os"
1918
"testing"
2019

2120
"github.com/googleapis/librarian/internal/sidekick/internal/config"
22-
"github.com/googleapis/librarian/internal/sidekick/internal/external"
2321
)
2422

2523
func TestLastTagSuccess(t *testing.T) {
@@ -60,17 +58,3 @@ func TestLastTagGitError(t *testing.T) {
6058
t.Fatalf("expected an error, got=%s", got)
6159
}
6260
}
63-
64-
func initRepositoryContents(t *testing.T) {
65-
t.Helper()
66-
requireCommand(t, "git")
67-
if err := os.WriteFile("README.md", []byte("# Empty Repo"), 0644); err != nil {
68-
t.Fatal(err)
69-
}
70-
if err := external.Run("git", "add", "."); err != nil {
71-
t.Fatal(err)
72-
}
73-
if err := external.Run("git", "commit", "-m", "initial version"); err != nil {
74-
t.Fatal(err)
75-
}
76-
}

internal/sidekick/internal/rust_release/preflight_test.go

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,9 @@
1515
package rustrelease
1616

1717
import (
18-
"os/exec"
1918
"testing"
2019

2120
"github.com/googleapis/librarian/internal/sidekick/internal/config"
22-
"github.com/googleapis/librarian/internal/sidekick/internal/external"
2321
)
2422

2523
func TestPreflightMissingGit(t *testing.T) {
@@ -89,25 +87,3 @@ func TestCargoExe(t *testing.T) {
8987
t.Errorf("mismatch in cargoExe(), want=alternative, got=%s", got)
9088
}
9189
}
92-
93-
func requireCommand(t *testing.T, command string) {
94-
t.Helper()
95-
if _, err := exec.LookPath(command); err != nil {
96-
t.Skipf("skipping test because %s is not installed", command)
97-
}
98-
}
99-
100-
func continueInNewGitRepository(t *testing.T, tmpDir string) {
101-
t.Helper()
102-
requireCommand(t, "git")
103-
t.Chdir(tmpDir)
104-
if err := external.Run("git", "init", "-b", "main"); err != nil {
105-
t.Fatal(err)
106-
}
107-
if err := external.Run("git", "config", "user.email", "[email protected]"); err != nil {
108-
t.Fatal(err)
109-
}
110-
if err := external.Run("git", "config", "user.name", "Test Account"); err != nil {
111-
t.Fatal(err)
112-
}
113-
}

0 commit comments

Comments
 (0)