diff options
Diffstat (limited to 'lib/git')
| -rw-r--r-- | lib/git/git.go | 70 | ||||
| -rw-r--r-- | lib/git/git_example_test.go | 42 | ||||
| -rw-r--r-- | lib/git/git_test.go | 26 | ||||
| -rw-r--r-- | lib/git/testdata/IsIgnored/.gitignore | 8 | ||||
| -rw-r--r-- | lib/git/testdata/IsIgnored/foo/hello.go | 4 | ||||
| -rw-r--r-- | lib/git/testdata/IsIgnored/vendor/empty | 2 | ||||
| -rw-r--r-- | lib/git/testdata/New/fail_no_git/.gitignore | 2 |
7 files changed, 149 insertions, 5 deletions
diff --git a/lib/git/git.go b/lib/git/git.go index 71240221..02877281 100644 --- a/lib/git/git.go +++ b/lib/git/git.go @@ -1,6 +1,5 @@ -// SPDX-FileCopyrightText: 2018 M. Shulhan <ms@kilabit.info> -// // SPDX-License-Identifier: BSD-3-Clause +// SPDX-FileCopyrightText: 2018 M. Shulhan <ms@kilabit.info> // Package git provide a wrapper for git command line interface. package git @@ -13,6 +12,7 @@ import ( "os" "os/exec" "path/filepath" + "strings" "git.sr.ht/~shulhan/pakakeh.go/lib/ini" ) @@ -28,6 +28,39 @@ var ( _stderr io.Writer = os.Stderr ) +// Git is a type for working with single git repository. +type Git struct { + listIgnore map[string]*Gitignore + + // absDir define the absolute path to the repository. + absDir string +} + +// New start working on repository in directory `dir`. +// If the `dir` does not contains ".git" directory it will return an error, +// even if the parent directory may contains the ".git" directory. +func New(dir string) (git *Git, err error) { + var logp = `New` + var fi os.FileInfo + var pathDotGit = filepath.Join(dir, `.git`) + fi, err = os.Stat(pathDotGit) + if err != nil { + return nil, fmt.Errorf(`%s: %q is not a git repository`, logp, dir) + } + if !fi.IsDir() { + return nil, fmt.Errorf(`%s: %q is not a git repository`, logp, dir) + } + dir, err = filepath.Abs(dir) + if err != nil { + return nil, fmt.Errorf(`%s: %w`, logp, err) + } + git = &Git{ + absDir: dir, + listIgnore: map[string]*Gitignore{}, + } + return git, nil +} + // CheckoutRevision will set the HEAD to specific revision on specific branch. // Any untracked files and directories will be removed before checking out // existing branch or creating new branch. @@ -188,6 +221,39 @@ func GetTag(repoDir, revision string) (tag string, err error) { return tag, nil } +// IsIgnored return true if the `path` is ignored by git. +// This is processed by matching it with all of the patterns in the +// ".gitignore" file inside the path directory and its parent, until the root +// of Git repository. +func (git *Git) IsIgnored(path string) (b bool) { + path = strings.TrimSpace(path) + if path == `` { + return true + } + // Traverse each directory from bottom to the top of git directory to + // load ".gitignore" file and match it with path. + var absPath = filepath.Join(git.absDir, path) + var dirGitignore = filepath.Dir(absPath) + var name = strings.TrimPrefix(absPath, dirGitignore) + name = strings.TrimLeft(name, `/`) + for strings.HasPrefix(dirGitignore, git.absDir) { + var ign *Gitignore + var ok bool + ign, ok = git.listIgnore[dirGitignore] + if !ok { + ign, _ = LoadGitignore(dirGitignore) + git.listIgnore[dirGitignore] = ign + } + if ign != nil && ign.IsIgnored(name) { + return true + } + dirGitignore = filepath.Dir(dirGitignore) + name = strings.TrimPrefix(absPath, dirGitignore) + name = strings.TrimLeft(name, `/`) + } + return false +} + // LatestCommit get the latest commit hash in short format from "ref". // If ref is empty, its default to "origin/master". func LatestCommit(repoDir, ref string) (commit string, err error) { diff --git a/lib/git/git_example_test.go b/lib/git/git_example_test.go new file mode 100644 index 00000000..0b55a251 --- /dev/null +++ b/lib/git/git_example_test.go @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: BSD-3-Clause +// SPDX-FileCopyrightText: 2025 M. Shulhan <ms@kilabit.info> + +package git_test + +import ( + "fmt" + "log" + + "git.sr.ht/~shulhan/pakakeh.go/lib/git" +) + +func ExampleGit_IsIgnored() { + var agit *git.Git + var err error + agit, err = git.New(`testdata/IsIgnored`) + if err != nil { + log.Fatal(err) + } + + var listPath = []string{ + ``, + `vendor`, + `vendor/dummy`, + `hello.html`, + `hello.go`, + `foo/hello.go`, + } + var path string + var got bool + for _, path = range listPath { + got = agit.IsIgnored(path) + fmt.Printf("%q: %t\n", path, got) + } + // Output: + // "": true + // "vendor": true + // "vendor/dummy": true + // "hello.html": true + // "hello.go": false + // "foo/hello.go": false +} diff --git a/lib/git/git_test.go b/lib/git/git_test.go index bcc63a95..70a9ccca 100644 --- a/lib/git/git_test.go +++ b/lib/git/git_test.go @@ -1,6 +1,5 @@ -// Copyright 2018, Shulhan <ms@kilabit.info>. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. +// SPDX-License-Identifier: BSD-3-Clause +// SPDX-FileCopyrightText: 2018 M. Shulhan <ms@kilabit.info> package git @@ -44,6 +43,27 @@ func TestMain(m *testing.M) { os.Exit(s) } +func TestNew(t *testing.T) { + type testCase struct { + dir string + expError string + } + var listCase = []testCase{{ + dir: `testdata/New/fail_no_git`, + expError: `New: "testdata/New/fail_no_git" is not a git repository`, + }, { + dir: `testdata/New/fail_not_dir`, + expError: `New: "testdata/New/fail_not_dir" is not a git repository`, + }} + + var err error + var tcase testCase + for _, tcase = range listCase { + _, err = New(tcase.dir) + test.Assert(t, `error`, tcase.expError, err.Error()) + } +} + func TestClone(t *testing.T) { cases := []struct { desc, dest, expErr, expStderr, expStdout string diff --git a/lib/git/testdata/IsIgnored/.gitignore b/lib/git/testdata/IsIgnored/.gitignore new file mode 100644 index 00000000..15989de8 --- /dev/null +++ b/lib/git/testdata/IsIgnored/.gitignore @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: BSD-3-Clause +# SPDX-FileCopyrightText: 2025 M. Shulhan <ms@kilabit.info> + +# comment + # comment + vendor/ # ignore vendor directory, but not vendor file. +/hello.* # ignore hello at root, but not foo/hello.go. +!hello.go diff --git a/lib/git/testdata/IsIgnored/foo/hello.go b/lib/git/testdata/IsIgnored/foo/hello.go new file mode 100644 index 00000000..b61e7105 --- /dev/null +++ b/lib/git/testdata/IsIgnored/foo/hello.go @@ -0,0 +1,4 @@ +// SPDX-License-Identifier: BSD-3-Clause +// SPDX-FileCopyrightText: 2025 M. Shulhan <ms@kilabit.info> + +package foo diff --git a/lib/git/testdata/IsIgnored/vendor/empty b/lib/git/testdata/IsIgnored/vendor/empty new file mode 100644 index 00000000..62f13a9e --- /dev/null +++ b/lib/git/testdata/IsIgnored/vendor/empty @@ -0,0 +1,2 @@ +// SPDX-License-Identifier: BSD-3-Clause +// SPDX-FileCopyrightText: 2025 M. Shulhan <ms@kilabit.info> diff --git a/lib/git/testdata/New/fail_no_git/.gitignore b/lib/git/testdata/New/fail_no_git/.gitignore new file mode 100644 index 00000000..62f13a9e --- /dev/null +++ b/lib/git/testdata/New/fail_no_git/.gitignore @@ -0,0 +1,2 @@ +// SPDX-License-Identifier: BSD-3-Clause +// SPDX-FileCopyrightText: 2025 M. Shulhan <ms@kilabit.info> |
