Skip to content

Commit b905341

Browse files
authored
Merge pull request #40 from codecrafters-io/support-gitignore
CC-1511: Fix CLI + Visual Studio issue
2 parents 3a4ccd1 + fab322a commit b905341

File tree

6 files changed

+211
-10
lines changed

6 files changed

+211
-10
lines changed

.github/workflows/test.yml

+11-4
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,23 @@
1-
name: Test Install Script
1+
name: Test
22

33
on:
44
pull_request:
55
push:
66
branches: [main]
77

88
jobs:
9-
test:
9+
run-tests:
1010
runs-on: ubuntu-latest
11-
1211
steps:
13-
- uses: actions/checkout@v3
12+
- uses: actions/checkout@v4
13+
- uses: actions/setup-go@v5
14+
with:
15+
go-version: "1.23"
16+
- run: make test
1417

18+
install-cli:
19+
runs-on: ubuntu-latest
20+
steps:
21+
- uses: actions/checkout@v4
1522
- run: dash -x install.sh
1623
- run: codecrafters --version

go.mod

+3-2
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ require (
1111
github.com/mitchellh/go-wordwrap v1.0.1
1212
github.com/otiai10/copy v1.7.0
1313
github.com/rs/zerolog v1.28.0
14-
github.com/stretchr/testify v1.5.1
14+
github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06
15+
github.com/stretchr/testify v1.6.1
1516
)
1617

1718
require (
@@ -26,5 +27,5 @@ require (
2627
golang.org/x/net v0.0.0-20221002022538-bcab6841153b // indirect
2728
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec // indirect
2829
golang.org/x/text v0.3.7 // indirect
29-
gopkg.in/yaml.v2 v2.4.0 // indirect
30+
gopkg.in/yaml.v3 v3.0.1 // indirect
3031
)

go.sum

+7-1
Original file line numberDiff line numberDiff line change
@@ -90,9 +90,12 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
9090
github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
9191
github.com/rs/zerolog v1.28.0 h1:MirSo27VyNi7RJYP3078AA1+Cyzd2GB66qy3aUHvsWY=
9292
github.com/rs/zerolog v1.28.0/go.mod h1:NILgTygv/Uej1ra5XxGf82ZFSLk58MFGAUS2o6usyD0=
93+
github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 h1:OkMGxebDjyw0ULyrTYWeN0UNCCkmCWfjPnIA2W6oviI=
94+
github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06/go.mod h1:+ePHsJ1keEjQtpvf9HHw0f4ZeJ0TLRsxhunSI2hYJSs=
9395
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
94-
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
9596
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
97+
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
98+
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
9699
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
97100
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
98101
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
@@ -162,3 +165,6 @@ gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
162165
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
163166
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
164167
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
168+
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
169+
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
170+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

internal/commands/test.go

+7-3
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import (
44
"context"
55
"errors"
66
"fmt"
7-
"io/ioutil"
87
"os"
98
"os/exec"
109
"strconv"
@@ -122,12 +121,17 @@ func TestCommand(ctx context.Context, shouldTestPrevious bool) (err error) {
122121
}
123122

124123
func copyRepositoryDirToTempDir(repoDir string) (string, error) {
125-
tmpDir, err := ioutil.TempDir("", "codecrafters")
124+
tmpDir, err := os.MkdirTemp("", "codecrafters")
125+
126126
if err != nil {
127127
return "", fmt.Errorf("create temp dir: %w", err)
128128
}
129129

130-
err = cp.Copy(repoDir, tmpDir)
130+
gitIgnore := utils.NewGitIgnore(repoDir)
131+
132+
err = cp.Copy(repoDir, tmpDir, cp.Options{
133+
Skip: gitIgnore.SkipFile,
134+
})
131135
if err != nil {
132136
return "", fmt.Errorf("copy files: %w", err)
133137
}

internal/utils/git_ignore.go

+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// Package utils
2+
package utils
3+
4+
import (
5+
"os"
6+
"os/exec"
7+
"path/filepath"
8+
"strings"
9+
10+
ignore "github.com/sabhiram/go-gitignore"
11+
)
12+
13+
type GitIgnore struct {
14+
baseDir string
15+
localGitIgnore *ignore.GitIgnore
16+
globalGitIgnore *ignore.GitIgnore
17+
gitInfoExclude *ignore.GitIgnore
18+
}
19+
20+
func NewGitIgnore(baseDir string) GitIgnore {
21+
return GitIgnore{
22+
baseDir: baseDir,
23+
localGitIgnore: compileIgnorer(filepath.Join(baseDir, ".gitignore")),
24+
globalGitIgnore: compileIgnorer(getGlobalGitIgnorePath()),
25+
gitInfoExclude: compileIgnorer(filepath.Join(baseDir, ".git", "info", "exclude")),
26+
}
27+
}
28+
29+
func (i GitIgnore) SkipFile(path string) (bool, error) {
30+
for _, ignorer := range []*ignore.GitIgnore{i.localGitIgnore, i.globalGitIgnore, i.gitInfoExclude} {
31+
if ignorer != nil && ignorer.MatchesPath(path) {
32+
return true, nil
33+
}
34+
}
35+
36+
return false, nil
37+
}
38+
39+
func compileIgnorer(path string) *ignore.GitIgnore {
40+
ignorer, err := ignore.CompileIgnoreFile(path)
41+
if err != nil {
42+
return nil
43+
}
44+
45+
return ignorer
46+
}
47+
48+
func getGlobalGitIgnorePath() string {
49+
output, err := exec.Command("git", "config", "--get", "core.excludesfile").Output()
50+
if err != nil {
51+
return ""
52+
}
53+
54+
path := strings.TrimSpace(string(output))
55+
if strings.HasPrefix(path, "~") {
56+
homeDir, err := os.UserHomeDir()
57+
if err != nil {
58+
return ""
59+
}
60+
path = filepath.Join(homeDir, path[2:])
61+
}
62+
63+
return path
64+
}

internal/utils/git_ignore_test.go

+119
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
package utils
2+
3+
import (
4+
"os"
5+
"os/exec"
6+
"path/filepath"
7+
"testing"
8+
9+
"github.com/stretchr/testify/assert"
10+
)
11+
12+
func TestGitIgnore(t *testing.T) {
13+
t.Run("without gitignore files", func(t *testing.T) {
14+
gitIgnore := NewGitIgnore(t.TempDir())
15+
assertFileNotSkipped(t, &gitIgnore, "some/random/file.txt")
16+
})
17+
18+
t.Run("with local gitignore", func(t *testing.T) {
19+
tmpRepoDir := t.TempDir()
20+
writeFile(t, filepath.Join(tmpRepoDir, ".gitignore"), "ignore/this/file.txt")
21+
22+
gitIgnore := NewGitIgnore(tmpRepoDir)
23+
assertFileSkipped(t, &gitIgnore, "ignore/this/file.txt")
24+
assertFileNotSkipped(t, &gitIgnore, "some/other/file.txt")
25+
})
26+
27+
t.Run("with global gitignore", func(t *testing.T) {
28+
backup := setupGlobalGitIgnore(t, "ignore/this/file.txt")
29+
defer func() {
30+
if backup.originalPath == "" {
31+
unsetGlobalGitIgnoreConfig(t)
32+
}
33+
backup.Restore(t)
34+
}()
35+
36+
gitIgnore := NewGitIgnore(t.TempDir())
37+
assertFileSkipped(t, &gitIgnore, "ignore/this/file.txt")
38+
assertFileNotSkipped(t, &gitIgnore, "some/other/file.txt")
39+
})
40+
41+
t.Run("with git info exclude", func(t *testing.T) {
42+
tmpRepoDir := createEmptyRepository(t)
43+
backup := setupGitInfoExclude(t, tmpRepoDir, "ignore/this/file.txt")
44+
defer backup.Restore(t)
45+
46+
gitIgnore := NewGitIgnore(tmpRepoDir)
47+
assertFileSkipped(t, &gitIgnore, "ignore/this/file.txt")
48+
assertFileNotSkipped(t, &gitIgnore, "some/other/file.txt")
49+
})
50+
}
51+
52+
func assertFileSkipped(t *testing.T, gitIgnore *GitIgnore, path string) {
53+
skip, err := gitIgnore.SkipFile(path)
54+
assert.NoError(t, err)
55+
assert.True(t, skip)
56+
}
57+
58+
func assertFileNotSkipped(t *testing.T, gitIgnore *GitIgnore, path string) {
59+
skip, err := gitIgnore.SkipFile(path)
60+
assert.NoError(t, err)
61+
assert.False(t, skip)
62+
}
63+
64+
type FileBackup struct {
65+
originalPath string
66+
backupPath string
67+
}
68+
69+
func (b *FileBackup) Restore(t *testing.T) {
70+
if b.originalPath != "" {
71+
moveFile(t, b.backupPath, b.originalPath)
72+
}
73+
}
74+
75+
func setupGlobalGitIgnore(t *testing.T, content string) *FileBackup {
76+
globalGitIgnorePath := getGlobalGitIgnorePath()
77+
backupPath := filepath.Join(t.TempDir(), ".gitignore_global")
78+
79+
if globalGitIgnorePath == "" {
80+
writeFile(t, backupPath, content)
81+
setGlobalGitIgnoreConfig(t, backupPath)
82+
return &FileBackup{originalPath: "", backupPath: backupPath}
83+
}
84+
85+
moveFile(t, globalGitIgnorePath, backupPath)
86+
writeFile(t, globalGitIgnorePath, content)
87+
return &FileBackup{originalPath: globalGitIgnorePath, backupPath: backupPath}
88+
}
89+
90+
func setupGitInfoExclude(t *testing.T, baseDir string, content string) *FileBackup {
91+
gitInfoExcludePath := filepath.Join(baseDir, ".git", "info", "exclude")
92+
_, err := os.Stat(gitInfoExcludePath)
93+
assert.NoError(t, err)
94+
95+
backupPath := filepath.Join(t.TempDir(), ".git_info_exclude_backup")
96+
moveFile(t, gitInfoExcludePath, backupPath)
97+
writeFile(t, gitInfoExcludePath, content)
98+
return &FileBackup{originalPath: gitInfoExcludePath, backupPath: backupPath}
99+
}
100+
101+
func setGlobalGitIgnoreConfig(t *testing.T, path string) {
102+
_, err := exec.Command("git", "config", "--global", "core.excludesfile", path).Output()
103+
assert.NoError(t, err)
104+
}
105+
106+
func unsetGlobalGitIgnoreConfig(t *testing.T) {
107+
_, err := exec.Command("git", "config", "--global", "--unset", "core.excludesfile").Output()
108+
assert.NoError(t, err)
109+
}
110+
111+
func moveFile(t *testing.T, srcPath string, dstPath string) {
112+
err := os.Rename(srcPath, dstPath)
113+
assert.NoError(t, err)
114+
}
115+
116+
func writeFile(t *testing.T, path string, content string) {
117+
err := os.WriteFile(path, []byte(content), 0644)
118+
assert.NoError(t, err)
119+
}

0 commit comments

Comments
 (0)