diff --git a/bug/bug.go b/bug/bug.go index 04bd599602375e0457edf4e96faf6e594002fddc..2ee890310814ab8e62e47ef7fab77a437eb3a0c6 100644 --- a/bug/bug.go +++ b/bug/bug.go @@ -109,8 +109,8 @@ func ReadLocalBug(repo repository.ClockedRepo, id entity.Id) (*Bug, error) { } // ReadRemoteBug will read a remote bug from its hash -func ReadRemoteBug(repo repository.ClockedRepo, remote string, id string) (*Bug, error) { - ref := fmt.Sprintf(bugsRemoteRefPattern, remote) + id +func ReadRemoteBug(repo repository.ClockedRepo, remote string, id entity.Id) (*Bug, error) { + ref := fmt.Sprintf(bugsRemoteRefPattern, remote) + id.String() return readBug(repo, ref) } @@ -242,9 +242,54 @@ func readBug(repo repository.ClockedRepo, ref string) (*Bug, error) { return &bug, nil } -func RemoveLocalBug(repo repository.ClockedRepo, id entity.Id) error { - ref := bugsRefPattern + id.String() - return repo.RemoveRef(ref) +// RemoveBug will remove a local bug from its entity.Id +func RemoveBug(repo repository.ClockedRepo, id entity.Id) error { + var fullMatches []string + + refs, err := repo.ListRefs(bugsRefPattern + id.String()) + if err != nil { + return err + } + if len(refs) > 1 { + return NewErrMultipleMatchBug(refsToIds(refs)) + } + if len(refs) == 1 { + // we have the bug locally + fullMatches = append(fullMatches, refs[0]) + } + + remotes, err := repo.GetRemotes() + if err != nil { + return err + } + + for remote := range remotes { + remotePrefix := fmt.Sprintf(bugsRemoteRefPattern+id.String(), remote) + remoteRefs, err := repo.ListRefs(remotePrefix) + if err != nil { + return err + } + if len(remoteRefs) > 1 { + return NewErrMultipleMatchBug(refsToIds(refs)) + } + if len(remoteRefs) == 1 { + // found the bug in a remote + fullMatches = append(fullMatches, remoteRefs[0]) + } + } + + if len(fullMatches) == 0 { + return ErrBugNotExist + } + + for _, ref := range fullMatches { + err = repo.RemoveRef(ref) + if err != nil { + return err + } + } + + return nil } type StreamedBug struct { @@ -305,13 +350,17 @@ func refsToIds(refs []string) []entity.Id { ids := make([]entity.Id, len(refs)) for i, ref := range refs { - split := strings.Split(ref, "/") - ids[i] = entity.Id(split[len(split)-1]) + ids[i] = refToId(ref) } return ids } +func refToId(ref string) entity.Id { + split := strings.Split(ref, "/") + return entity.Id(split[len(split)-1]) +} + // Validate check if the Bug data is valid func (bug *Bug) Validate() error { // non-empty diff --git a/bug/bug_test.go b/bug/bug_test.go index 43e760af47512614acd8292448338fee3ee31084..400e50f8711e2dbe182f7565932e9ce774045b78 100644 --- a/bug/bug_test.go +++ b/bug/bug_test.go @@ -1,10 +1,12 @@ package bug import ( + "fmt" "testing" "time" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/MichaelMure/git-bug/identity" "github.com/MichaelMure/git-bug/repository" @@ -64,7 +66,7 @@ func TestBugValidity(t *testing.T) { } } -func TestBugCommitLoadRemove(t *testing.T) { +func TestBugCommitLoad(t *testing.T) { bug1 := NewBug() rene := identity.NewIdentity("René Descartes", "rene@descartes.fr") @@ -100,16 +102,6 @@ func TestBugCommitLoadRemove(t *testing.T) { bug3, err := ReadLocalBug(repo, bug1.Id()) assert.NoError(t, err) equivalentBug(t, bug1, bug3) - - err = RemoveLocalBug(repo, bug1.Id()) - assert.NoError(t, err) - - streamedBugs := ReadAllLocalBugs(repo) - count := 0 - for range streamedBugs { - count++ - } - assert.Equal(t, 0, count) } func equivalentBug(t *testing.T, expected, actual *Bug) { @@ -123,3 +115,64 @@ func equivalentBug(t *testing.T, expected, actual *Bug) { assert.Equal(t, expected, actual) } + +func TestBugRemove(t *testing.T) { + repo := repository.CreateTestRepo(false) + remoteA := repository.CreateTestRepo(true) + remoteB := repository.CreateTestRepo(true) + defer repository.CleanupTestRepos(repo, remoteA, remoteB) + + err := repo.AddRemote("remoteA", "file://"+remoteA.GetPath()) + require.NoError(t, err) + + err = repo.AddRemote("remoteB", "file://"+remoteB.GetPath()) + require.NoError(t, err) + + // generate a bunch of bugs + rene := identity.NewIdentity("René Descartes", "rene@descartes.fr") + err = rene.Commit(repo) + require.NoError(t, err) + + for i := 0; i < 100; i++ { + b := NewBug() + createOp := NewCreateOp(rene, time.Now().Unix(), "title", fmt.Sprintf("message%v", i), nil) + b.Append(createOp) + err = b.Commit(repo) + require.NoError(t, err) + } + + // and one more for testing + b := NewBug() + createOp := NewCreateOp(rene, time.Now().Unix(), "title", "message", nil) + b.Append(createOp) + err = b.Commit(repo) + require.NoError(t, err) + + _, err = Push(repo, "remoteA") + require.NoError(t, err) + + _, err = Push(repo, "remoteB") + require.NoError(t, err) + + _, err = Fetch(repo, "remoteA") + require.NoError(t, err) + + _, err = Fetch(repo, "remoteB") + require.NoError(t, err) + + err = RemoveBug(repo, b.Id()) + require.NoError(t, err) + + _, err = ReadLocalBug(repo, b.Id()) + require.Error(t, ErrBugNotExist, err) + + _, err = ReadRemoteBug(repo, "remoteA", b.Id()) + require.Error(t, ErrBugNotExist, err) + + _, err = ReadRemoteBug(repo, "remoteB", b.Id()) + require.Error(t, ErrBugNotExist, err) + + ids, err := ListLocalIds(repo) + require.NoError(t, err) + require.Len(t, ids, 100) +} diff --git a/cache/repo_cache_bug.go b/cache/repo_cache_bug.go index 306923633a4e4f0772c651a15ced0ca4140cec16..bcbfcea3b542216cbc1561a902d0f4339ec3c1ce 100644 --- a/cache/repo_cache_bug.go +++ b/cache/repo_cache_bug.go @@ -359,3 +359,19 @@ func (c *RepoCache) NewBugRaw(author *IdentityCache, unixTime int64, title strin return cached, op, nil } + +// RemoveBug removes a bug from the cache and repo given a bug id prefix +func (c *RepoCache) RemoveBug(prefix string) error { + b, err := c.ResolveBugPrefix(prefix) + + if err != nil { + return err + } + + err = bug.RemoveBug(c.repo, b.Id()) + + delete(c.bugs, b.Id()) + delete(c.bugExcerpts, b.Id()) + + return c.writeBugCache() +} diff --git a/cache/repo_cache_test.go b/cache/repo_cache_test.go index 0a333c8f31bda8ce827595e270ba180b05468ee3..0deb155ed506a996a64688ac53bd2eaaa3649c64 100644 --- a/cache/repo_cache_test.go +++ b/cache/repo_cache_test.go @@ -1,10 +1,14 @@ package cache import ( + "fmt" "testing" + "time" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/MichaelMure/git-bug/bug" "github.com/MichaelMure/git-bug/query" "github.com/MichaelMure/git-bug/repository" ) @@ -157,3 +161,52 @@ func TestPushPull(t *testing.T) { require.Len(t, cacheA.AllBugsIds(), 2) } + +func TestRemove(t *testing.T) { + repo := repository.CreateTestRepo(false) + remoteA := repository.CreateTestRepo(true) + remoteB := repository.CreateTestRepo(true) + defer repository.CleanupTestRepos(repo, remoteA, remoteB) + + err := repo.AddRemote("remoteA", "file://"+remoteA.GetPath()) + require.NoError(t, err) + + err = repo.AddRemote("remoteB", "file://"+remoteB.GetPath()) + require.NoError(t, err) + + repoCache, err := NewRepoCache(repo) + require.NoError(t, err) + + // generate a bunch of bugs + rene, err := repoCache.NewIdentity("René Descartes", "rene@descartes.fr") + require.NoError(t, err) + + for i := 0; i < 100; i++ { + _, _, err := repoCache.NewBugRaw(rene, time.Now().Unix(), "title", fmt.Sprintf("message%v", i), nil, nil) + require.NoError(t, err) + } + + // and one more for testing + b1, _, err := repoCache.NewBugRaw(rene, time.Now().Unix(), "title", "message", nil, nil) + require.NoError(t, err) + + _, err = repoCache.Push("remoteA") + require.NoError(t, err) + + _, err = repoCache.Push("remoteB") + require.NoError(t, err) + + _, err = repoCache.Fetch("remoteA") + require.NoError(t, err) + + _, err = repoCache.Fetch("remoteB") + require.NoError(t, err) + + err = repoCache.RemoveBug(b1.Id().String()) + require.NoError(t, err) + assert.Equal(t, 100, len(repoCache.bugs)) + assert.Equal(t, 100, len(repoCache.bugExcerpts)) + + _, err = repoCache.ResolveBug(b1.Id()) + assert.Error(t, bug.ErrBugNotExist, err) +} diff --git a/commands/rm.go b/commands/rm.go new file mode 100644 index 0000000000000000000000000000000000000000..09f6a6ccf7b5b4379b1b146fc137f4fd1b1df28b --- /dev/null +++ b/commands/rm.go @@ -0,0 +1,43 @@ +package commands + +import ( + "errors" + + "github.com/spf13/cobra" +) + +func newRmCommand() *cobra.Command { + env := newEnv() + + cmd := &cobra.Command{ + Use: "rm ", + Short: "Remove an existing bug.", + Long: "Remove an existing bug in the local repository. Note removing bugs that were imported from bridges will not remove the bug on the remote, and will only remove the local copy of the bug.", + PreRunE: loadBackendEnsureUser(env), + PostRunE: closeBackend(env), + RunE: func(cmd *cobra.Command, args []string) error { + return runRm(env, args) + }, + } + + flags := cmd.Flags() + flags.SortFlags = false + + return cmd +} + +func runRm(env *Env, args []string) (err error) { + if len(args) == 0 { + return errors.New("you must provide a bug prefix to remove") + } + + err = env.backend.RemoveBug(args[0]) + + if err != nil { + return + } + + env.out.Printf("bug %s removed\n", args[0]) + + return +} diff --git a/commands/root.go b/commands/root.go index a67fec1a3f69c4323dd3fedcee3d273ea03d6f3b..e7848363ba4f80edc69079ff9e51900f670e509e 100644 --- a/commands/root.go +++ b/commands/root.go @@ -71,6 +71,7 @@ _git_bug() { cmd.AddCommand(newLsLabelCommand()) cmd.AddCommand(newPullCommand()) cmd.AddCommand(newPushCommand()) + cmd.AddCommand(newRmCommand()) cmd.AddCommand(newSelectCommand()) cmd.AddCommand(newShowCommand()) cmd.AddCommand(newStatusCommand())