aboutsummaryrefslogtreecommitdiff
path: root/lib/git
diff options
context:
space:
mode:
Diffstat (limited to 'lib/git')
-rw-r--r--lib/git/git.go70
-rw-r--r--lib/git/git_example_test.go42
-rw-r--r--lib/git/git_test.go26
-rw-r--r--lib/git/testdata/IsIgnored/.gitignore8
-rw-r--r--lib/git/testdata/IsIgnored/foo/hello.go4
-rw-r--r--lib/git/testdata/IsIgnored/vendor/empty2
-rw-r--r--lib/git/testdata/New/fail_no_git/.gitignore2
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>