From 7a07a2900763e2256f19e086e1aeadec0dfa2071 Mon Sep 17 00:00:00 2001 From: Kent Rancourt Date: Mon, 16 Sep 2024 07:18:32 -0400 Subject: [PATCH] feat(controller): more git pkg improvements (#2531) Signed-off-by: Kent Rancourt --- internal/controller/git/bare_repo.go | 34 +++++++++++++++++----- internal/controller/git/bare_repo_test.go | 10 +++++-- internal/controller/git/base_repo.go | 25 ++++++++++++++++ internal/controller/git/work_tree.go | 35 +++++++---------------- internal/controller/git/work_tree_test.go | 9 ++++-- 5 files changed, 76 insertions(+), 37 deletions(-) diff --git a/internal/controller/git/bare_repo.go b/internal/controller/git/bare_repo.go index 994e4df29..dffb545d4 100644 --- a/internal/controller/git/bare_repo.go +++ b/internal/controller/git/bare_repo.go @@ -14,9 +14,8 @@ import ( // BareRepo is an interface for interacting with a bare Git repository. type BareRepo interface { - // AddWorkTree adds a working tree to the repository. The working tree will be - // created at the specified path and will be checked out to the specified ref. - AddWorkTree(path, ref string) (WorkTree, error) + // AddWorkTree adds a working tree to the repository. + AddWorkTree(path string, opts *AddWorkTreeOptions) (WorkTree, error) // Close cleans up file system resources used by this repository. This should // always be called before a repository goes out of scope. Close() error @@ -25,6 +24,9 @@ type BareRepo interface { // HomeDir returns an absolute path to the home directory of the system user // who has cloned this repo. HomeDir() string + // RemoteBranchExists returns a bool indicating if the specified branch exists + // in the remote repository. + RemoteBranchExists(branch string) (bool, error) // RemoveWorkTree removes a working tree from the repository. The working tree // will be removed from the file system. RemoveWorkTree(path string) error @@ -135,7 +137,21 @@ func LoadBareRepo(path string, opts *LoadBareRepoOptions) (BareRepo, error) { return b, nil } -func (b *bareRepo) AddWorkTree(path, ref string) (WorkTree, error) { +// AddWorkTreeOptions represents options for adding a working tree to a bare +// repository. +type AddWorkTreeOptions struct { + // Orphan specifies whether the working tree should be created from a new, + // orphaned branch. If true, the Ref field will be ignored. + Orphan bool + // Ref specifies the branch or commit to check out in the working tree. Will + // be ignored if Orphan is true. + Ref string +} + +func (b *bareRepo) AddWorkTree(path string, opts *AddWorkTreeOptions) (WorkTree, error) { + if opts == nil { + opts = &AddWorkTreeOptions{} + } path, err := filepath.Abs(path) if err != nil { return nil, fmt.Errorf("error resolving absolute path for %s: %w", path, err) @@ -147,9 +163,13 @@ func (b *bareRepo) AddWorkTree(path, ref string) (WorkTree, error) { if slices.Contains(workTreePaths, path) { return nil, fmt.Errorf("working tree already exists at %q", path) } - if _, err = libExec.Exec( - b.buildGitCommand("worktree", "add", path, ref), - ); err != nil { + args := []string{"worktree", "add", path} + if opts.Orphan { + args = append(args, "--orphan") + } else { + args = append(args, opts.Ref) + } + if _, err = libExec.Exec(b.buildGitCommand(args...)); err != nil { return nil, fmt.Errorf("error adding working tree at %q: %w", path, err) } if path, err = filepath.EvalSymlinks(path); err != nil { diff --git a/internal/controller/git/bare_repo_test.go b/internal/controller/git/bare_repo_test.go index cb91cb3ab..9f6ae1d40 100644 --- a/internal/controller/git/bare_repo_test.go +++ b/internal/controller/git/bare_repo_test.go @@ -107,9 +107,13 @@ func TestBareRepo(t *testing.T) { }) workingTreePath := filepath.Join(rep.HomeDir(), "working-tree") - // "master" is still the default branch name for a new repository unless - // you configure it otherwise. - workTree, err := rep.AddWorkTree(workingTreePath, "master") + workTree, err := rep.AddWorkTree( + workingTreePath, + // "master" is still the default branch name for a new repository unless + // you configure it otherwise. + &AddWorkTreeOptions{Ref: "master"}, + ) + require.NoError(t, err) defer workTree.Close() diff --git a/internal/controller/git/base_repo.go b/internal/controller/git/base_repo.go index f615d4665..a1405e64b 100644 --- a/internal/controller/git/base_repo.go +++ b/internal/controller/git/base_repo.go @@ -1,6 +1,7 @@ package git import ( + "errors" "fmt" "net/url" "os" @@ -244,6 +245,30 @@ func (b *baseRepo) HomeDir() string { return b.homeDir } +func (b *baseRepo) RemoteBranchExists(branch string) (bool, error) { + _, err := libExec.Exec(b.buildGitCommand( + "ls-remote", + "--heads", + "--exit-code", // Return 2 if not found + b.url, + branch, + )) + var exitErr *libExec.ExitError + if errors.As(err, &exitErr) && exitErr.ExitCode == 2 { + // Branch does not exist + return false, nil + } + if err != nil { + return false, fmt.Errorf( + "error checking for existence of branch %q in remote repo %q: %w", + branch, + b.url, + err, + ) + } + return true, nil +} + func (b *baseRepo) URL() string { return b.url } diff --git a/internal/controller/git/work_tree.go b/internal/controller/git/work_tree.go index b021b56ac..279f2fad8 100644 --- a/internal/controller/git/work_tree.go +++ b/internal/controller/git/work_tree.go @@ -23,6 +23,8 @@ type WorkTree interface { AddAllAndCommit(message string) error // Clean cleans the working tree. Clean() error + // Clear executes `git rm -rf .` to remove all files from the working tree. + Clear() error // Close cleans up file system resources used by this working tree. This // should always be called before a WorkTree goes out of scope. Close() error @@ -150,6 +152,15 @@ func (w *workTree) Clean() error { return nil } +func (w *workTree) Clear() error { + if _, err := libExec.Exec( + w.buildGitCommand("rm", "-rf", "--ignore-unmatch", "."), + ); err != nil { + return fmt.Errorf("error clearing worktree: %w", err) + } + return nil +} + func (w *workTree) Close() error { if w.bareRepo != nil { return w.bareRepo.RemoveWorkTree(w.dir) @@ -496,30 +507,6 @@ func (w *workTree) RefsHaveDiffs(commit1 string, commit2 string) (bool, error) { return false, fmt.Errorf("error diffing commits %s..%s: %w", commit1, commit2, err) } -func (w *workTree) RemoteBranchExists(branch string) (bool, error) { - _, err := libExec.Exec(w.buildGitCommand( - "ls-remote", - "--heads", - "--exit-code", // Return 2 if not found - w.url, - branch, - )) - var exitErr *libExec.ExitError - if errors.As(err, &exitErr) && exitErr.ExitCode == 2 { - // Branch does not exist - return false, nil - } - if err != nil { - return false, fmt.Errorf( - "error checking for existence of branch %q in remote repo %q: %w", - branch, - w.url, - err, - ) - } - return true, nil -} - func (w *workTree) ResetHard() error { if _, err := libExec.Exec(w.buildGitCommand("reset", "--hard")); err != nil { return fmt.Errorf("error resetting branch working tree: %w", err) diff --git a/internal/controller/git/work_tree_test.go b/internal/controller/git/work_tree_test.go index a82e73d88..d65ed6c0d 100644 --- a/internal/controller/git/work_tree_test.go +++ b/internal/controller/git/work_tree_test.go @@ -75,9 +75,12 @@ func TestWorkTree(t *testing.T) { defer rep.Close() workingTreePath := filepath.Join(rep.HomeDir(), "working-tree") - // "master" is still the default branch name for a new repository unless - // you configure it otherwise. - workTree, err := rep.AddWorkTree(workingTreePath, "master") + workTree, err := rep.AddWorkTree( + workingTreePath, + // "master" is still the default branch name for a new repository unless + // you configure it otherwise. + &AddWorkTreeOptions{Ref: "master"}, + ) require.NoError(t, err) defer workTree.Close()