Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

πŸ“ Add docstrings to feature/new-hooks-for-remove #69

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
coderabbitai wants to merge 1 commit into main
base: main
Choose a base branch
Loading
from coderabbitai/docstrings/e9095d3
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions cmd/wtp/add.go
View file Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -332,8 +332,12 @@ Use the --track flag to specify which remote to use:
Original error: %v`, e.BranchName, e.BranchName, e.BranchName, e.BranchName, e.BranchName, e.GitError)
}

// executePostCreateHooks executes any post-create hooks configured in cfg for the
// new worktree at workTreePath and writes progress messages to w. If no post-
// create hooks are configured this is a no-op. Returns an error if writing to w
// fails or if hook execution fails.
func executePostCreateHooks(w io.Writer, cfg *config.Config, repoPath, workTreePath string) error {
if cfg.HasHooks() {
if cfg.HasPostCreateHooks() {
if _, err := fmt.Fprintln(w, "\nExecuting post-create hooks..."); err != nil {
return err
}
Expand Down Expand Up @@ -575,4 +579,4 @@ func resolveBranchTracking(
}

return "", nil
}
}
16 changes: 15 additions & 1 deletion cmd/wtp/init.go
View file Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ func NewInitCommand() *cli.Command {
}
}

// initCommand initializes a Worktree Plus configuration file in the repository root.
// It creates a default `.wtp.yml` with example defaults and hook groups (including pre_remove and post_remove).
// The command fails if the current directory is not a git repository, if a configuration file already exists,
// if repository or target directory access or permissions prevent creation, or if writing/printing the file fails.
func initCommand(_ context.Context, cmd *cli.Command) error {
// Get current working directory (should be a git repository)
cwd, err := osGetwd()
Expand Down Expand Up @@ -99,6 +103,16 @@ hooks:
# command: npm install
# - type: command
# command: echo "Created new worktree!"

# Hooks that run before removing a worktree
pre_remove:
# - type: command
# command: echo "Removing worktree..."

# Hooks that run after removing a worktree
post_remove:
# - type: command
# command: echo "Removed worktree!"
`

if err := ensureWritableDirectory(repo.Path()); err != nil {
Expand Down Expand Up @@ -138,4 +152,4 @@ func ensureWritableDirectory(path string) error {
}

return nil
}
}
88 changes: 87 additions & 1 deletion cmd/wtp/remove.go
View file Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"github.com/satococoa/wtp/v2/internal/config"
"github.com/satococoa/wtp/v2/internal/errors"
"github.com/satococoa/wtp/v2/internal/git"
"github.com/satococoa/wtp/v2/internal/hooks"
)

// Variable to allow mocking in tests
Expand Down Expand Up @@ -92,6 +93,13 @@ func removeCommand(_ context.Context, cmd *cli.Command) error {
return removeCommandWithCommandExecutor(cmd, w, executor, cwd, worktreeName, force, withBranch, forceBranch)
}

// removeCommandWithCommandExecutor locates the specified worktree, executes configured
// pre-remove hooks, removes the worktree, executes post-remove hooks, and optionally
// deletes the associated branch, writing status messages to w.
//
// It returns an error if worktree listing or parsing fails, if paths cannot be resolved,
// if the repository configuration cannot be loaded, if hook execution fails, if the git
// removal or branch deletion commands fail, or if writing output to w fails.
func removeCommandWithCommandExecutor(
_ *cli.Command,
w io.Writer,
Expand Down Expand Up @@ -130,6 +138,17 @@ func removeCommandWithCommandExecutor(
return errors.CannotRemoveCurrentWorktree(worktreeName, absTargetPath)
}

mainRepoPath := findMainWorktreePath(worktrees)
cfg, err := config.LoadConfig(mainRepoPath)
if err != nil {
configPath := mainRepoPath + "/" + config.ConfigFileName
return errors.ConfigLoadFailed(configPath, err)
}

if err := executePreRemoveHooks(w, cfg, mainRepoPath, absTargetPath); err != nil {
return err
}

// Remove worktree using CommandExecutor
removeCmd := command.GitWorktreeRemove(targetWorktree.Path, force)
result, err = executor.Execute([]command.Command{removeCmd})
Expand All @@ -148,6 +167,10 @@ func removeCommandWithCommandExecutor(
return err
}

if err := executePostRemoveHooks(w, cfg, mainRepoPath, absTargetPath); err != nil {
return err
}

// Remove branch if requested
if withBranch && targetWorktree.Branch != "" {
if err := removeBranchWithCommandExecutor(w, executor, targetWorktree.Branch, forceBranch); err != nil {
Expand All @@ -158,6 +181,69 @@ func removeCommandWithCommandExecutor(
return nil
}

// executePreRemoveHooks executes configured pre-remove hooks for the given worktree
// and writes progress messages to w. If no pre-remove hooks are configured, it does
// nothing. It returns any error encountered while writing to w or while executing the hooks.
func executePreRemoveHooks(w io.Writer, cfg *config.Config, repoPath, worktreePath string) error {
if !cfg.HasPreRemoveHooks() {
return nil
}

if _, err := fmt.Fprintln(w, "\nExecuting pre-remove hooks..."); err != nil {
return err
}

executor := hooks.NewExecutor(cfg, repoPath)
if err := executor.ExecutePreRemoveHooks(w, worktreePath); err != nil {
return err
}

if _, err := fmt.Fprintln(w, "βœ“ All hooks executed successfully"); err != nil {
return err
}

return nil
}

// executePostRemoveHooks runs the configured post-remove hooks for the given worktree.
// It normalizes each hook's WorkDir to an absolute path (defaulting to repoPath when empty),
// prints a header and a final success line to w, and returns any error from writing or from
// executing the hooks.
func executePostRemoveHooks(w io.Writer, cfg *config.Config, repoPath, worktreePath string) error {
if !cfg.HasPostRemoveHooks() {
return nil
}

if _, err := fmt.Fprintln(w, "\nExecuting post-remove hooks..."); err != nil {
return err
}

postRemoveCfg := *cfg
postRemoveHooks := make([]config.Hook, len(cfg.Hooks.PostRemove))
for i, hook := range cfg.Hooks.PostRemove {
if hook.WorkDir == "" {
hook.WorkDir = repoPath
} else if !filepath.IsAbs(hook.WorkDir) {
hook.WorkDir = filepath.Join(repoPath, hook.WorkDir)
}
postRemoveHooks[i] = hook
}
postRemoveCfg.Hooks.PostRemove = postRemoveHooks

executor := hooks.NewExecutor(&postRemoveCfg, repoPath)
if err := executor.ExecutePostRemoveHooks(w, worktreePath); err != nil {
return err
}

if _, err := fmt.Fprintln(w, "βœ“ All hooks executed successfully"); err != nil {
return err
}

return nil
}

// validateRemoveInput validates inputs for the remove command flags.
// It returns an error if worktreeName is empty or if --force-branch is provided without --with-branch.
func validateRemoveInput(worktreeName string, withBranch, forceBranch bool) error {
if worktreeName == "" {
return errors.WorktreeNameRequiredForRemove()
Expand Down Expand Up @@ -384,4 +470,4 @@ func completeWorktrees(_ context.Context, cmd *cli.Command) {
return
}
}
}
}
40 changes: 33 additions & 7 deletions internal/config/config.go
View file Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@ type Defaults struct {
BaseDir string `yaml:"base_dir,omitempty"`
}

// Hooks represents the post-create hooks configuration
// Hooks represents the hooks configuration
type Hooks struct {
PostCreate []Hook `yaml:"post_create,omitempty"`
PreRemove []Hook `yaml:"pre_remove,omitempty"`
PostRemove []Hook `yaml:"post_remove,omitempty"`
}

// Hook represents a single hook configuration
Expand Down Expand Up @@ -125,12 +127,26 @@ func (c *Config) Validate() error {
}

// Validate hooks
for i, hook := range c.Hooks.PostCreate {
if err := validateHooks("post_create", c.Hooks.PostCreate); err != nil {
return err
}
if err := validateHooks("pre_remove", c.Hooks.PreRemove); err != nil {
return err
}
if err := validateHooks("post_remove", c.Hooks.PostRemove); err != nil {
return err
}

return nil
}

// validateHooks checks each Hook in hooks and returns an error describing the first invalid hook, including the hook group name and a 1-based index.
func validateHooks(name string, hooks []Hook) error {
for i, hook := range hooks {
if err := hook.Validate(); err != nil {
return fmt.Errorf("invalid hook %d: %w", i+1, err)
return fmt.Errorf("invalid %s hook %d: %w", name, i+1, err)
}
}

return nil
}

Expand Down Expand Up @@ -158,16 +174,26 @@ func (h *Hook) Validate() error {
return nil
}

// HasHooks returns true if the configuration has any post-create hooks
func (c *Config) HasHooks() bool {
// HasPostCreateHooks returns true if the configuration has any post-create hooks
func (c *Config) HasPostCreateHooks() bool {
return len(c.Hooks.PostCreate) > 0
}

// HasPreRemoveHooks returns true if the configuration has any pre-remove hooks
func (c *Config) HasPreRemoveHooks() bool {
return len(c.Hooks.PreRemove) > 0
}

// HasPostRemoveHooks returns true if the configuration has any post-remove hooks
func (c *Config) HasPostRemoveHooks() bool {
return len(c.Hooks.PostRemove) > 0
}

// ResolveWorktreePath resolves the full path for a worktree given a name
func (c *Config) ResolveWorktreePath(repoRoot, worktreeName string) string {
baseDir := c.Defaults.BaseDir
if !filepath.IsAbs(baseDir) {
baseDir = filepath.Join(repoRoot, baseDir)
}
return filepath.Join(baseDir, worktreeName)
}
}
Loading
Loading

AltStyle γ«γ‚ˆγ£γ¦ε€‰ζ›γ•γ‚ŒγŸγƒšγƒΌγ‚Έ (->γ‚ͺγƒͺγ‚ΈγƒŠγƒ«) /