From 74a1c22a3a621a94d7340cdcb530781dd187a2dc Mon Sep 17 00:00:00 2001 From: Stephen Macke Date: Mon, 14 Oct 2024 16:47:42 -0700 Subject: [PATCH 1/5] support git worktrees for sync --- libs/git/repository.go | 46 ++++++++++++++++++++++++++++++++++++++++-- libs/git/view.go | 17 ++++++++++------ 2 files changed, 55 insertions(+), 8 deletions(-) diff --git a/libs/git/repository.go b/libs/git/repository.go index 6940ddac83..6d74391342 100644 --- a/libs/git/repository.go +++ b/libs/git/repository.go @@ -1,6 +1,7 @@ package git import ( + "bufio" "errors" "fmt" "io/fs" @@ -119,13 +120,54 @@ func (r *Repository) OriginUrl() string { return parsedUrl.String() } +func readGitDir(root vfs.Path) (string, error) { + file, err := root.Open(".git") + if err != nil { + return "", fmt.Errorf("error opening file: %w", err) + } + defer file.Close() + scanner := bufio.NewScanner(file) + for scanner.Scan() { + line := scanner.Text() + if strings.HasPrefix(line, "gitdir: ") { + // Extract the path after "gitdir: " + return strings.TrimSpace(strings.TrimPrefix(line, "gitdir: ")), nil + } + } + + if err := scanner.Err(); err != nil { + return "", fmt.Errorf("error reading file: %w", err) + } + + return "", errors.New("gitdir line not found") +} + +func (r *Repository) resolveGitRoot() vfs.Path { + fileInfo, err := r.root.Stat(".git") + if err != nil { + return r.root + } + if fileInfo.IsDir() { + return r.root + } + gitDir, err := readGitDir(r.root) + if err != nil { + return r.root + } + return vfs.MustNew(gitDir) +} + // loadConfig loads and combines user specific and repository specific configuration files. func (r *Repository) loadConfig() error { config, err := globalGitConfig() if err != nil { return fmt.Errorf("unable to load user specific gitconfig: %w", err) } - err = config.loadFile(r.root, ".git/config") + root := r.resolveGitRoot() + if root == nil { + return fmt.Errorf("unable to resolve git root") + } + err = config.loadFile(root, ".git/config") if err != nil { return fmt.Errorf("unable to load repository specific gitconfig: %w", err) } @@ -136,7 +178,7 @@ func (r *Repository) loadConfig() error { // newIgnoreFile constructs a new [ignoreRules] implementation backed by // a file using the specified path relative to the repository root. func (r *Repository) newIgnoreFile(relativeIgnoreFilePath string) ignoreRules { - return newIgnoreFile(r.root, relativeIgnoreFilePath) + return newIgnoreFile(r.resolveGitRoot(), relativeIgnoreFilePath) } // getIgnoreRules returns a slice of [ignoreRules] that apply diff --git a/libs/git/view.go b/libs/git/view.go index 90eed0bb8f..58fe94e7b2 100644 --- a/libs/git/view.go +++ b/libs/git/view.go @@ -107,6 +107,17 @@ func (v *View) EnsureValidGitIgnoreExists() error { return nil } + // Hard code .databricks ignore pattern so that we never sync it (irrespective) + // of .gitignore patterns + v.repo.addIgnoreRule(newStringIgnoreRules([]string{ + ".databricks", + })) + + // Bail if we are in a worktree + if v.repo.Root() != v.repo.resolveGitRoot().Native() { + return nil + } + // Create .gitignore with .databricks entry gitIgnorePath := filepath.Join(v.repo.Root(), v.targetPath, ".gitignore") file, err := os.OpenFile(gitIgnorePath, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644) @@ -115,12 +126,6 @@ func (v *View) EnsureValidGitIgnoreExists() error { } defer file.Close() - // Hard code .databricks ignore pattern so that we never sync it (irrespective) - // of .gitignore patterns - v.repo.addIgnoreRule(newStringIgnoreRules([]string{ - ".databricks", - })) - _, err = file.WriteString("\n.databricks\n") if err != nil { return err From c6fc519fc05391db1fecc7b2e0164ab75730beb1 Mon Sep 17 00:00:00 2001 From: Pieter Noordhuis Date: Fri, 18 Oct 2024 13:23:48 +0200 Subject: [PATCH 2/5] Work on worktree support --- libs/git/repository.go | 101 +++++++++++-------------------- libs/git/view.go | 19 +++--- libs/git/worktree.go | 122 ++++++++++++++++++++++++++++++++++++++ libs/git/worktree_test.go | 1 + 4 files changed, 165 insertions(+), 78 deletions(-) create mode 100644 libs/git/worktree.go create mode 100644 libs/git/worktree_test.go diff --git a/libs/git/repository.go b/libs/git/repository.go index 6d74391342..43ec79cb3b 100644 --- a/libs/git/repository.go +++ b/libs/git/repository.go @@ -1,7 +1,6 @@ package git import ( - "bufio" "errors" "fmt" "io/fs" @@ -24,8 +23,18 @@ type Repository struct { // directory where we process .gitignore files. real bool - // root is the absolute path to the repository root. - root vfs.Path + // rootDir is the path to the root of the repository checkout. + // This can be either the main repository checkout or a worktree. + rootDir vfs.Path + + // gitDir is the equivalent of $GIT_DIR and points to the + // `.git` directory of a repository or a worktree. + gitDir vfs.Path + + // gitCommonDir is the equivalent of $GIT_COMMON_DIR and points to the + // `.git` directory of the main working tree (common between worktrees). + // This is equivalent to [gitDir] if this is the main working tree. + gitCommonDir vfs.Path // ignore contains a list of ignore patterns indexed by the // path prefix relative to the repository root. @@ -45,12 +54,11 @@ type Repository struct { // Root returns the absolute path to the repository root. func (r *Repository) Root() string { - return r.root.Native() + return r.rootDir.Native() } func (r *Repository) CurrentBranch() (string, error) { - // load .git/HEAD - ref, err := LoadReferenceFile(r.root, path.Join(GitDirectoryName, "HEAD")) + ref, err := LoadReferenceFile(r.gitDir, "HEAD") if err != nil { return "", err } @@ -66,8 +74,7 @@ func (r *Repository) CurrentBranch() (string, error) { } func (r *Repository) LatestCommit() (string, error) { - // load .git/HEAD - ref, err := LoadReferenceFile(r.root, path.Join(GitDirectoryName, "HEAD")) + ref, err := LoadReferenceFile(r.gitDir, "HEAD") if err != nil { return "", err } @@ -81,12 +88,12 @@ func (r *Repository) LatestCommit() (string, error) { return ref.Content, nil } - // read reference from .git/HEAD + // Read reference from $GIT_DIR/HEAD branchHeadPath, err := ref.ResolvePath() if err != nil { return "", err } - branchHeadRef, err := LoadReferenceFile(r.root, path.Join(GitDirectoryName, branchHeadPath)) + branchHeadRef, err := LoadReferenceFile(r.gitCommonDir, branchHeadPath) if err != nil { return "", err } @@ -120,54 +127,13 @@ func (r *Repository) OriginUrl() string { return parsedUrl.String() } -func readGitDir(root vfs.Path) (string, error) { - file, err := root.Open(".git") - if err != nil { - return "", fmt.Errorf("error opening file: %w", err) - } - defer file.Close() - scanner := bufio.NewScanner(file) - for scanner.Scan() { - line := scanner.Text() - if strings.HasPrefix(line, "gitdir: ") { - // Extract the path after "gitdir: " - return strings.TrimSpace(strings.TrimPrefix(line, "gitdir: ")), nil - } - } - - if err := scanner.Err(); err != nil { - return "", fmt.Errorf("error reading file: %w", err) - } - - return "", errors.New("gitdir line not found") -} - -func (r *Repository) resolveGitRoot() vfs.Path { - fileInfo, err := r.root.Stat(".git") - if err != nil { - return r.root - } - if fileInfo.IsDir() { - return r.root - } - gitDir, err := readGitDir(r.root) - if err != nil { - return r.root - } - return vfs.MustNew(gitDir) -} - // loadConfig loads and combines user specific and repository specific configuration files. func (r *Repository) loadConfig() error { config, err := globalGitConfig() if err != nil { return fmt.Errorf("unable to load user specific gitconfig: %w", err) } - root := r.resolveGitRoot() - if root == nil { - return fmt.Errorf("unable to resolve git root") - } - err = config.loadFile(root, ".git/config") + err = config.loadFile(r.gitCommonDir, "config") if err != nil { return fmt.Errorf("unable to load repository specific gitconfig: %w", err) } @@ -175,12 +141,6 @@ func (r *Repository) loadConfig() error { return nil } -// newIgnoreFile constructs a new [ignoreRules] implementation backed by -// a file using the specified path relative to the repository root. -func (r *Repository) newIgnoreFile(relativeIgnoreFilePath string) ignoreRules { - return newIgnoreFile(r.resolveGitRoot(), relativeIgnoreFilePath) -} - // getIgnoreRules returns a slice of [ignoreRules] that apply // for the specified prefix. The prefix must be cleaned by the caller. // It lazily initializes an entry for the specified prefix if it @@ -191,7 +151,7 @@ func (r *Repository) getIgnoreRules(prefix string) []ignoreRules { return fs } - r.ignore[prefix] = append(r.ignore[prefix], r.newIgnoreFile(path.Join(prefix, gitIgnoreFileName))) + r.ignore[prefix] = append(r.ignore[prefix], newIgnoreFile(r.rootDir, path.Join(prefix, gitIgnoreFileName))) return r.ignore[prefix] } @@ -247,7 +207,7 @@ func (r *Repository) Ignore(relPath string) (bool, error) { func NewRepository(path vfs.Path) (*Repository, error) { real := true - rootPath, err := vfs.FindLeafInTree(path, GitDirectoryName) + rootDir, err := vfs.FindLeafInTree(path, GitDirectoryName) if err != nil { if !errors.Is(err, fs.ErrNotExist) { return nil, err @@ -255,13 +215,22 @@ func NewRepository(path vfs.Path) (*Repository, error) { // Cannot find `.git` directory. // Treat the specified path as a potential repository root. real = false - rootPath = path + rootDir = path + } + + // Derive $GIT_DIR and $GIT_COMMON_DIR paths if this is a real repository. + // If it isn't a real repository, they'll point to the (non-existent) `.git` directory. + gitDir, gitCommonDir, err := resolveGitDirs(rootDir) + if err != nil { + return nil, err } repo := &Repository{ - real: real, - root: rootPath, - ignore: make(map[string][]ignoreRules), + real: real, + rootDir: rootDir, + gitDir: gitDir, + gitCommonDir: gitCommonDir, + ignore: make(map[string][]ignoreRules), } err = repo.loadConfig() @@ -295,9 +264,9 @@ func NewRepository(path vfs.Path) (*Repository, error) { ".git", }), // Load repository-wide excludes file. - repo.newIgnoreFile(".git/info/excludes"), + newIgnoreFile(repo.gitCommonDir, "info/excludes"), // Load root gitignore file. - repo.newIgnoreFile(".gitignore"), + newIgnoreFile(repo.rootDir, ".gitignore"), } return repo, nil diff --git a/libs/git/view.go b/libs/git/view.go index 58fe94e7b2..2d2e39a60b 100644 --- a/libs/git/view.go +++ b/libs/git/view.go @@ -80,7 +80,7 @@ func NewView(root vfs.Path) (*View, error) { // Target path must be relative to the repository root path. target := root.Native() - prefix := repo.root.Native() + prefix := repo.rootDir.Native() if !strings.HasPrefix(target, prefix) { return nil, fmt.Errorf("path %q is not within repository root %q", root.Native(), prefix) } @@ -107,17 +107,6 @@ func (v *View) EnsureValidGitIgnoreExists() error { return nil } - // Hard code .databricks ignore pattern so that we never sync it (irrespective) - // of .gitignore patterns - v.repo.addIgnoreRule(newStringIgnoreRules([]string{ - ".databricks", - })) - - // Bail if we are in a worktree - if v.repo.Root() != v.repo.resolveGitRoot().Native() { - return nil - } - // Create .gitignore with .databricks entry gitIgnorePath := filepath.Join(v.repo.Root(), v.targetPath, ".gitignore") file, err := os.OpenFile(gitIgnorePath, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644) @@ -126,6 +115,12 @@ func (v *View) EnsureValidGitIgnoreExists() error { } defer file.Close() + // Hard code .databricks ignore pattern so that we never sync it (irrespective) + // of .gitignore patterns + v.repo.addIgnoreRule(newStringIgnoreRules([]string{ + ".databricks", + })) + _, err = file.WriteString("\n.databricks\n") if err != nil { return err diff --git a/libs/git/worktree.go b/libs/git/worktree.go new file mode 100644 index 0000000000..fa0204e360 --- /dev/null +++ b/libs/git/worktree.go @@ -0,0 +1,122 @@ +package git + +import ( + "bufio" + "errors" + "io/fs" + "path/filepath" + "strings" + + "github.com/databricks/cli/libs/vfs" +) + +func readLines(root vfs.Path, name string) ([]string, error) { + file, err := root.Open(name) + if err != nil { + return nil, err + } + + defer file.Close() + + var lines []string + scanner := bufio.NewScanner(file) + for scanner.Scan() { + lines = append(lines, scanner.Text()) + } + + return lines, scanner.Err() +} + +// readGitDir reads the value of the `.git` file in a worktree. +func readGitDir(root vfs.Path) (string, error) { + lines, err := readLines(root, GitDirectoryName) + if err != nil { + return "", err + } + + var gitDir string + for _, line := range lines { + parts := strings.SplitN(line, ": ", 2) + if len(parts) != 2 { + continue + } + + if parts[0] == "gitdir" { + gitDir = strings.TrimSpace(parts[1]) + } + } + + if gitDir == "" { + return "", errors.New("gitdir line not found") + } + + return gitDir, nil +} + +// readGitCommonDir reads the value of the `commondir` file in the `.git` directory of a worktree. +// This file typically contains "../.." to point to $GIT_COMMON_DIR. +func readGitCommonDir(gitDir vfs.Path) (string, error) { + lines, err := readLines(gitDir, "commondir") + if err != nil { + return "", err + } + + if len(lines) == 0 { + return "", errors.New("commondir file not found") + } + + return lines[0], nil +} + +// resolveGitDirs resolves the paths for $GIT_DIR and $GIT_COMMON_DIR. +// The path argument is the root of the checkout where (supposedly) a `.git` file or directory exists. +func resolveGitDirs(root vfs.Path) (vfs.Path, vfs.Path, error) { + fileInfo, err := root.Stat(GitDirectoryName) + if err != nil { + // If the `.git` file or directory does not exist, then this is not a git repository. + // Return paths that we know don't exist, so we do not need to perform nil checks in the caller. + if errors.Is(err, fs.ErrNotExist) { + gitDir := vfs.MustNew(filepath.Join(root.Native(), GitDirectoryName)) + return gitDir, gitDir, nil + } + return nil, nil, err + } + + // If the path is a directory, then it is the main working tree. + // Both $GIT_DIR and $GIT_COMMON_DIR point to the same directory. + if fileInfo.IsDir() { + gitDir := vfs.MustNew(filepath.Join(root.Native(), GitDirectoryName)) + return gitDir, gitDir, nil + } + + // If the path is not a directory, then it is a worktree. + // Read value for $GIT_DIR. + gitDirValue, err := readGitDir(root) + if err != nil { + return nil, nil, err + } + + // Resolve $GIT_DIR. + var gitDir vfs.Path + if filepath.IsAbs(gitDirValue) { + gitDir = vfs.MustNew(gitDirValue) + } else { + gitDir = vfs.MustNew(filepath.Join(root.Native(), gitDirValue)) + } + + // Read value for $GIT_COMMON_DIR. + gitCommonDirValue, err := readGitCommonDir(gitDir) + if err != nil { + return nil, nil, err + } + + // Resolve $GIT_COMMON_DIR. + var gitCommonDir vfs.Path + if filepath.IsAbs(gitCommonDirValue) { + gitCommonDir = vfs.MustNew(gitCommonDirValue) + } else { + gitCommonDir = vfs.MustNew(filepath.Join(gitDir.Native(), gitCommonDirValue)) + } + + return gitDir, gitCommonDir, nil +} diff --git a/libs/git/worktree_test.go b/libs/git/worktree_test.go new file mode 100644 index 0000000000..cd99cdbeec --- /dev/null +++ b/libs/git/worktree_test.go @@ -0,0 +1 @@ +package git From 0f681146e049c4c958b74d0048e9ea8ba692bb56 Mon Sep 17 00:00:00 2001 From: Pieter Noordhuis Date: Fri, 18 Oct 2024 13:57:57 +0200 Subject: [PATCH 3/5] Add tests for worktree specific resolution --- libs/git/worktree.go | 9 ++-- libs/git/worktree_test.go | 107 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 112 insertions(+), 4 deletions(-) diff --git a/libs/git/worktree.go b/libs/git/worktree.go index fa0204e360..964c1c95bf 100644 --- a/libs/git/worktree.go +++ b/libs/git/worktree.go @@ -3,6 +3,7 @@ package git import ( "bufio" "errors" + "fmt" "io/fs" "path/filepath" "strings" @@ -47,7 +48,7 @@ func readGitDir(root vfs.Path) (string, error) { } if gitDir == "" { - return "", errors.New("gitdir line not found") + return "", fmt.Errorf(`expected %q to contain a line with "gitdir: [...]"`, filepath.Join(root.Native(), GitDirectoryName)) } return gitDir, nil @@ -62,10 +63,10 @@ func readGitCommonDir(gitDir vfs.Path) (string, error) { } if len(lines) == 0 { - return "", errors.New("commondir file not found") + return "", errors.New("file is empty") } - return lines[0], nil + return strings.TrimSpace(lines[0]), nil } // resolveGitDirs resolves the paths for $GIT_DIR and $GIT_COMMON_DIR. @@ -107,7 +108,7 @@ func resolveGitDirs(root vfs.Path) (vfs.Path, vfs.Path, error) { // Read value for $GIT_COMMON_DIR. gitCommonDirValue, err := readGitCommonDir(gitDir) if err != nil { - return nil, nil, err + return nil, nil, fmt.Errorf(`expected "commondir" file in worktree git folder at %q: %w`, gitDir.Native(), err) } // Resolve $GIT_COMMON_DIR. diff --git a/libs/git/worktree_test.go b/libs/git/worktree_test.go index cd99cdbeec..3d620c4834 100644 --- a/libs/git/worktree_test.go +++ b/libs/git/worktree_test.go @@ -1 +1,108 @@ package git + +import ( + "fmt" + "os" + "path/filepath" + "testing" + + "github.com/databricks/cli/libs/vfs" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func setupWorktree(t *testing.T) string { + var err error + + tmpDir := t.TempDir() + + // Checkout path + err = os.MkdirAll(filepath.Join(tmpDir, "my_worktree"), os.ModePerm) + require.NoError(t, err) + + // Main $GIT_COMMON_DIR + err = os.MkdirAll(filepath.Join(tmpDir, ".git"), os.ModePerm) + require.NoError(t, err) + + // Worktree $GIT_DIR + err = os.MkdirAll(filepath.Join(tmpDir, ".git/worktrees/my_worktree"), os.ModePerm) + require.NoError(t, err) + + return tmpDir +} + +func writeGitDir(t *testing.T, dir, content string) { + err := os.WriteFile(filepath.Join(dir, "my_worktree/.git"), []byte(content), os.ModePerm) + require.NoError(t, err) +} + +func writeGitCommonDir(t *testing.T, dir, content string) { + err := os.WriteFile(filepath.Join(dir, ".git/worktrees/my_worktree/commondir"), []byte(content), os.ModePerm) + require.NoError(t, err) +} + +func verifyCorrectDirs(t *testing.T, dir string) { + gitDir, gitCommonDir, err := resolveGitDirs(vfs.MustNew(filepath.Join(dir, "my_worktree"))) + require.NoError(t, err) + assert.Equal(t, filepath.Join(dir, ".git/worktrees/my_worktree"), gitDir.Native()) + assert.Equal(t, filepath.Join(dir, ".git"), gitCommonDir.Native()) +} + +func TestWorktreeResolveGitDir(t *testing.T) { + dir := setupWorktree(t) + writeGitCommonDir(t, dir, "../..") + + t.Run("relative", func(t *testing.T) { + writeGitDir(t, dir, fmt.Sprintf("gitdir: %s", "../.git/worktrees/my_worktree")) + verifyCorrectDirs(t, dir) + }) + + t.Run("absolute", func(t *testing.T) { + writeGitDir(t, dir, fmt.Sprintf("gitdir: %s", filepath.Join(dir, ".git/worktrees/my_worktree"))) + verifyCorrectDirs(t, dir) + }) + + t.Run("additional spaces", func(t *testing.T) { + writeGitDir(t, dir, fmt.Sprintf("gitdir: %s \n\n\n", "../.git/worktrees/my_worktree")) + verifyCorrectDirs(t, dir) + }) + + t.Run("empty", func(t *testing.T) { + writeGitDir(t, dir, "") + + _, _, err := resolveGitDirs(vfs.MustNew(filepath.Join(dir, "my_worktree"))) + assert.ErrorContains(t, err, ` to contain a line with "gitdir: [...]"`) + }) +} + +func TestWorktreeResolveCommonDir(t *testing.T) { + dir := setupWorktree(t) + writeGitDir(t, dir, fmt.Sprintf("gitdir: %s", "../.git/worktrees/my_worktree")) + + t.Run("relative", func(t *testing.T) { + writeGitCommonDir(t, dir, "../..") + verifyCorrectDirs(t, dir) + }) + + t.Run("absolute", func(t *testing.T) { + writeGitCommonDir(t, dir, filepath.Join(dir, ".git")) + verifyCorrectDirs(t, dir) + }) + + t.Run("additional spaces", func(t *testing.T) { + writeGitCommonDir(t, dir, " ../.. \n\n\n") + verifyCorrectDirs(t, dir) + }) + + t.Run("empty", func(t *testing.T) { + writeGitCommonDir(t, dir, "") + + _, _, err := resolveGitDirs(vfs.MustNew(filepath.Join(dir, "my_worktree"))) + assert.ErrorContains(t, err, `expected "commondir" file in worktree git folder at `) + }) + + t.Run("missing", func(t *testing.T) { + _, _, err := resolveGitDirs(vfs.MustNew(filepath.Join(dir, "my_worktree"))) + assert.ErrorContains(t, err, `expected "commondir" file in worktree git folder at `) + }) +} From d2272fade58966d42f2df5b2c3586bb22f3c38cc Mon Sep 17 00:00:00 2001 From: Pieter Noordhuis Date: Fri, 18 Oct 2024 14:22:23 +0200 Subject: [PATCH 4/5] rootDir -> checkoutDir --- libs/git/repository.go | 20 ++++++++++---------- libs/git/view.go | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/libs/git/repository.go b/libs/git/repository.go index 43ec79cb3b..090310c2a1 100644 --- a/libs/git/repository.go +++ b/libs/git/repository.go @@ -23,9 +23,9 @@ type Repository struct { // directory where we process .gitignore files. real bool - // rootDir is the path to the root of the repository checkout. + // checkoutDir is the path to the root of the repository checkout. // This can be either the main repository checkout or a worktree. - rootDir vfs.Path + checkoutDir vfs.Path // gitDir is the equivalent of $GIT_DIR and points to the // `.git` directory of a repository or a worktree. @@ -54,7 +54,7 @@ type Repository struct { // Root returns the absolute path to the repository root. func (r *Repository) Root() string { - return r.rootDir.Native() + return r.checkoutDir.Native() } func (r *Repository) CurrentBranch() (string, error) { @@ -151,7 +151,7 @@ func (r *Repository) getIgnoreRules(prefix string) []ignoreRules { return fs } - r.ignore[prefix] = append(r.ignore[prefix], newIgnoreFile(r.rootDir, path.Join(prefix, gitIgnoreFileName))) + r.ignore[prefix] = append(r.ignore[prefix], newIgnoreFile(r.checkoutDir, path.Join(prefix, gitIgnoreFileName))) return r.ignore[prefix] } @@ -207,27 +207,27 @@ func (r *Repository) Ignore(relPath string) (bool, error) { func NewRepository(path vfs.Path) (*Repository, error) { real := true - rootDir, err := vfs.FindLeafInTree(path, GitDirectoryName) + checkoutDir, err := vfs.FindLeafInTree(path, GitDirectoryName) if err != nil { if !errors.Is(err, fs.ErrNotExist) { return nil, err } // Cannot find `.git` directory. - // Treat the specified path as a potential repository root. + // Treat the specified path as a potential repository root checkout. real = false - rootDir = path + checkoutDir = path } // Derive $GIT_DIR and $GIT_COMMON_DIR paths if this is a real repository. // If it isn't a real repository, they'll point to the (non-existent) `.git` directory. - gitDir, gitCommonDir, err := resolveGitDirs(rootDir) + gitDir, gitCommonDir, err := resolveGitDirs(checkoutDir) if err != nil { return nil, err } repo := &Repository{ real: real, - rootDir: rootDir, + checkoutDir: checkoutDir, gitDir: gitDir, gitCommonDir: gitCommonDir, ignore: make(map[string][]ignoreRules), @@ -266,7 +266,7 @@ func NewRepository(path vfs.Path) (*Repository, error) { // Load repository-wide excludes file. newIgnoreFile(repo.gitCommonDir, "info/excludes"), // Load root gitignore file. - newIgnoreFile(repo.rootDir, ".gitignore"), + newIgnoreFile(repo.checkoutDir, ".gitignore"), } return repo, nil diff --git a/libs/git/view.go b/libs/git/view.go index 2d2e39a60b..8c409dec01 100644 --- a/libs/git/view.go +++ b/libs/git/view.go @@ -80,7 +80,7 @@ func NewView(root vfs.Path) (*View, error) { // Target path must be relative to the repository root path. target := root.Native() - prefix := repo.rootDir.Native() + prefix := repo.checkoutDir.Native() if !strings.HasPrefix(target, prefix) { return nil, fmt.Errorf("path %q is not within repository root %q", root.Native(), prefix) } From e53e7489a2deacc121cc0f8d2f6c9a57348250b2 Mon Sep 17 00:00:00 2001 From: Pieter Noordhuis Date: Mon, 21 Oct 2024 09:29:29 +0200 Subject: [PATCH 5/5] Address comments --- libs/git/repository.go | 25 ++++++++++++++----------- libs/git/view.go | 2 +- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/libs/git/repository.go b/libs/git/repository.go index 090310c2a1..c521bbcd02 100644 --- a/libs/git/repository.go +++ b/libs/git/repository.go @@ -23,17 +23,20 @@ type Repository struct { // directory where we process .gitignore files. real bool - // checkoutDir is the path to the root of the repository checkout. - // This can be either the main repository checkout or a worktree. - checkoutDir vfs.Path + // rootDir is the path to the root of the repository checkout. + // This can be either the main repository checkout or a worktree checkout. + // For more information about worktrees, see: https://git-scm.com/docs/git-worktree#_description. + rootDir vfs.Path // gitDir is the equivalent of $GIT_DIR and points to the - // `.git` directory of a repository or a worktree. + // `.git` directory of a repository or a worktree directory. + // See https://git-scm.com/docs/git-worktree#_details for more information. gitDir vfs.Path // gitCommonDir is the equivalent of $GIT_COMMON_DIR and points to the // `.git` directory of the main working tree (common between worktrees). // This is equivalent to [gitDir] if this is the main working tree. + // See https://git-scm.com/docs/git-worktree#_details for more information. gitCommonDir vfs.Path // ignore contains a list of ignore patterns indexed by the @@ -54,7 +57,7 @@ type Repository struct { // Root returns the absolute path to the repository root. func (r *Repository) Root() string { - return r.checkoutDir.Native() + return r.rootDir.Native() } func (r *Repository) CurrentBranch() (string, error) { @@ -151,7 +154,7 @@ func (r *Repository) getIgnoreRules(prefix string) []ignoreRules { return fs } - r.ignore[prefix] = append(r.ignore[prefix], newIgnoreFile(r.checkoutDir, path.Join(prefix, gitIgnoreFileName))) + r.ignore[prefix] = append(r.ignore[prefix], newIgnoreFile(r.rootDir, path.Join(prefix, gitIgnoreFileName))) return r.ignore[prefix] } @@ -207,7 +210,7 @@ func (r *Repository) Ignore(relPath string) (bool, error) { func NewRepository(path vfs.Path) (*Repository, error) { real := true - checkoutDir, err := vfs.FindLeafInTree(path, GitDirectoryName) + rootDir, err := vfs.FindLeafInTree(path, GitDirectoryName) if err != nil { if !errors.Is(err, fs.ErrNotExist) { return nil, err @@ -215,19 +218,19 @@ func NewRepository(path vfs.Path) (*Repository, error) { // Cannot find `.git` directory. // Treat the specified path as a potential repository root checkout. real = false - checkoutDir = path + rootDir = path } // Derive $GIT_DIR and $GIT_COMMON_DIR paths if this is a real repository. // If it isn't a real repository, they'll point to the (non-existent) `.git` directory. - gitDir, gitCommonDir, err := resolveGitDirs(checkoutDir) + gitDir, gitCommonDir, err := resolveGitDirs(rootDir) if err != nil { return nil, err } repo := &Repository{ real: real, - checkoutDir: checkoutDir, + rootDir: rootDir, gitDir: gitDir, gitCommonDir: gitCommonDir, ignore: make(map[string][]ignoreRules), @@ -266,7 +269,7 @@ func NewRepository(path vfs.Path) (*Repository, error) { // Load repository-wide excludes file. newIgnoreFile(repo.gitCommonDir, "info/excludes"), // Load root gitignore file. - newIgnoreFile(repo.checkoutDir, ".gitignore"), + newIgnoreFile(repo.rootDir, ".gitignore"), } return repo, nil diff --git a/libs/git/view.go b/libs/git/view.go index 8c409dec01..2d2e39a60b 100644 --- a/libs/git/view.go +++ b/libs/git/view.go @@ -80,7 +80,7 @@ func NewView(root vfs.Path) (*View, error) { // Target path must be relative to the repository root path. target := root.Native() - prefix := repo.checkoutDir.Native() + prefix := repo.rootDir.Native() if !strings.HasPrefix(target, prefix) { return nil, fmt.Errorf("path %q is not within repository root %q", root.Native(), prefix) }