From 9d322f323369ee28194fc77e7d1292f482315be8 Mon Sep 17 00:00:00 2001 From: Ross Light Date: Mon, 21 Feb 2022 20:11:00 -0800 Subject: [PATCH] git: add DeleteBranches method --- CHANGELOG.md | 15 ++++++++------- api.go | 30 ++++++++++++++++++++++++++++++ branch_test.go | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 84 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6969777..cba2724 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,16 +9,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- There are several new `git.RefMutation` constructors: `SetRef`, - `SetRefIfMatches`, and `CreateRef`. -- `git.RefMutation` has a new `IsNoop` method to make it easier to check for - the zero value. -- `git.CommitOptions` and `git.AmendOptions` have a new field: `SkipHooks`. +- There are several new `git.RefMutation` constructors: `SetRef`, + `SetRefIfMatches`, and `CreateRef`. +- `git.RefMutation` has a new `IsNoop` method to make it easier to check for + the zero value. +- `git.CommitOptions` and `git.AmendOptions` have a new field: `SkipHooks`. +- New method `Git.DeleteBranches`. ### Changed -- `*client.PullStream.ListRefs` and `*client.PushStream.Refs` now return a map - of refs instead of a slice. +- `*client.PullStream.ListRefs` and `*client.PushStream.Refs` now return a map + of refs instead of a slice. ## [0.9.0][] - 2021-01-26 diff --git a/api.go b/api.go index ae5b8ba..48c532d 100644 --- a/api.go +++ b/api.go @@ -1021,6 +1021,36 @@ func (g *Git) NewBranch(ctx context.Context, name string, opts BranchOptions) er return g.run(ctx, errPrefix, args) } +// DeleteBranchOptions specifies options for a new branch. +type DeleteBranchOptions struct { + // If Force is true, then the branch will be deleted + // even if the branch has not been merged + // or if the branch does not point to a valid commit. + Force bool +} + +// DeleteBranches deletes zero or more branches. +// If names is empty, then DeleteBranches returns nil without running Git. +func (g *Git) DeleteBranches(ctx context.Context, names []string, opts DeleteBranchOptions) error { + if len(names) == 0 { + return nil + } + errPrefix := "git branch --delete " + strings.Join(names, " ") + for _, name := range names { + if err := validateBranch(name); err != nil { + return fmt.Errorf("%s: %w", errPrefix, err) + } + } + args := make([]string, 0, len(names)+4) + args = append(args, "branch", "--delete") + if opts.Force { + args = append(args, "--force") + } + args = append(args, "--") + args = append(args, names...) + return g.run(ctx, errPrefix, args) +} + // NullTreeHash computes the hash of an empty tree and adds it to the // repository. This is sometimes useful as a diff comparison target. func (g *Git) NullTreeHash(ctx context.Context) (Hash, error) { diff --git a/branch_test.go b/branch_test.go index aa381a8..de772e4 100644 --- a/branch_test.go +++ b/branch_test.go @@ -295,3 +295,49 @@ func TestNewBranch(t *testing.T) { }) } } + +func TestDeleteBranches(t *testing.T) { + ctx := context.Background() + gitPath, err := findGit() + if err != nil { + t.Skip("git not found:", err) + } + env, err := newTestEnv(ctx, gitPath) + if err != nil { + t.Fatal(err) + } + defer env.cleanup() + + // Create a repository with a commit and a branch "foo". + if err := env.g.Init(ctx, "."); err != nil { + t.Fatal(err) + } + const content = "Hello, World!\n" + if err := env.root.Apply(filesystem.Write("file.txt", content)); err != nil { + t.Fatal(err) + } + if err := env.g.Add(ctx, []Pathspec{"file.txt"}, AddOptions{}); err != nil { + t.Fatal(err) + } + if err := env.g.Commit(ctx, dummyContent, CommitOptions{}); err != nil { + t.Fatal(err) + } + if err := env.g.NewBranch(ctx, "foo", BranchOptions{}); err != nil { + t.Fatal(err) + } + + // Delete the branch "foo". + if err := env.g.DeleteBranches(ctx, []string{"foo"}, DeleteBranchOptions{}); err != nil { + t.Error(err) + } + + // Verify that "foo" no longer exists. + refs, err := env.g.ListRefs(ctx) + if err != nil { + t.Fatal(err) + } + const fooRef = Ref("refs/heads/foo") + if hash, ok := refs[fooRef]; ok { + t.Errorf("%s = %v; want missing", fooRef, hash) + } +}