aboutsummaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/pama/pama_test.go166
-rw-r--r--lib/pama/remove.go94
-rw-r--r--lib/pama/remove_test.go85
3 files changed, 345 insertions, 0 deletions
diff --git a/lib/pama/pama_test.go b/lib/pama/pama_test.go
new file mode 100644
index 00000000..7a4d1961
--- /dev/null
+++ b/lib/pama/pama_test.go
@@ -0,0 +1,166 @@
+package pama_test
+
+import (
+ "errors"
+
+ "git.sr.ht/~rjarry/aerc/lib/pama"
+ "git.sr.ht/~rjarry/aerc/lib/pama/models"
+)
+
+var errNotFound = errors.New("not found")
+
+func newCommit(id, subj, tag string) models.Commit {
+ return models.Commit{ID: id, Subject: subj, Tag: tag}
+}
+
+func newTestManager(
+ commits []string,
+ subjects []string,
+ data map[string]models.Project,
+ current string,
+) (pama.PatchManager, models.RevisionController, models.PersistentStorer) {
+ rc := mockRevctrl{
+ commitIDs: commits,
+ titles: subjects,
+ }
+ store := mockStore{
+ data: data,
+ current: current,
+ }
+ return pama.FromFunc(
+ nil,
+ func(_ string, _ string) (models.RevisionController, error) {
+ return &rc, nil
+ },
+ func() models.PersistentStorer {
+ return &store
+ },
+ ), &rc, &store
+}
+
+type mockRevctrl struct {
+ commitIDs []string
+ titles []string
+}
+
+func (c *mockRevctrl) Support() bool {
+ return true
+}
+
+func (c *mockRevctrl) Clean() bool {
+ return true
+}
+
+func (c *mockRevctrl) Root() (string, error) {
+ return "", nil
+}
+
+func (c *mockRevctrl) Head() (string, error) {
+ return c.commitIDs[len(c.commitIDs)-1], nil
+}
+
+func (c *mockRevctrl) History(commit string) ([]string, error) {
+ for i, s := range c.commitIDs {
+ if s == commit {
+ cp := make([]string, len(c.commitIDs[i+1:]))
+ copy(cp, c.commitIDs[i+1:])
+ return cp, nil
+ }
+ }
+ return nil, errNotFound
+}
+
+func (c *mockRevctrl) Exists(commit string) bool {
+ for _, s := range c.commitIDs {
+ if s == commit {
+ return true
+ }
+ }
+ return false
+}
+
+func (c *mockRevctrl) Subject(commit string) string {
+ for i, s := range c.commitIDs {
+ if s == commit {
+ return c.titles[i]
+ }
+ }
+ return ""
+}
+
+func (c *mockRevctrl) Author(commit string) string {
+ return ""
+}
+
+func (c *mockRevctrl) Date(commit string) string {
+ return ""
+}
+
+func (c *mockRevctrl) Remove(commit string) error {
+ for i, s := range c.commitIDs {
+ if s == commit {
+ c.commitIDs = append(c.commitIDs[:i], c.commitIDs[i+1:]...)
+ c.titles = append(c.titles[:i], c.titles[i+1:]...)
+ // modify commitIDs to simulate a "real" change in
+ // commit history that will also change all subsequent
+ // commitIDs
+ for j := i; j < len(c.commitIDs); j++ {
+ c.commitIDs[j] += "_new"
+ }
+ return nil
+ }
+ }
+ return errNotFound
+}
+
+func (c *mockRevctrl) ApplyCmd() string {
+ return ""
+}
+
+type mockStore struct {
+ data map[string]models.Project
+ current string
+}
+
+func (s *mockStore) StoreProject(p models.Project, ow bool) error {
+ _, ok := s.data[p.Name]
+ if ok && !ow {
+ return errors.New("alreay there")
+ }
+ s.data[p.Name] = p
+ return nil
+}
+
+func (s *mockStore) DeleteProject(name string) error {
+ delete(s.data, name)
+ return nil
+}
+
+func (s *mockStore) CurrentName() (string, error) {
+ return s.current, nil
+}
+
+func (s *mockStore) SetCurrent(c string) error {
+ s.current = c
+ return nil
+}
+
+func (s *mockStore) Current() (models.Project, error) {
+ return s.data[s.current], nil
+}
+
+func (s *mockStore) Names() ([]string, error) {
+ var names []string
+ for name := range s.data {
+ names = append(names, name)
+ }
+ return names, nil
+}
+
+func (s *mockStore) Projects() ([]models.Project, error) {
+ var ps []models.Project
+ for _, p := range s.data {
+ ps = append(ps, p)
+ }
+ return ps, nil
+}
diff --git a/lib/pama/remove.go b/lib/pama/remove.go
new file mode 100644
index 00000000..d74874a1
--- /dev/null
+++ b/lib/pama/remove.go
@@ -0,0 +1,94 @@
+package pama
+
+import (
+ "fmt"
+
+ "git.sr.ht/~rjarry/aerc/lib/pama/models"
+ "git.sr.ht/~rjarry/aerc/log"
+)
+
+func (m PatchManager) RemovePatch(patch string) error {
+ p, err := m.CurrentProject()
+ if err != nil {
+ return err
+ }
+
+ if !models.Commits(p.Commits).HasTag(patch) {
+ return fmt.Errorf("Patch '%s' not found in project '%s'", patch, p.Name)
+ }
+
+ rc, err := m.rc(p.RevctrlID, p.Root)
+ if err != nil {
+ return revErr(err)
+ }
+
+ if !rc.Clean() {
+ return fmt.Errorf("Aborting... There are unstaged changes " +
+ "or a rebase in progress")
+ }
+
+ toRemove := make([]models.Commit, 0)
+ for _, c := range p.Commits {
+ if !rc.Exists(c.ID) {
+ log.Errorf("failed to find commit. %v", c)
+ return fmt.Errorf("Cannot remove patch. " +
+ "Please rebase first with ':patch rebase'")
+ }
+ if c.Tag == patch {
+ toRemove = append(toRemove, c)
+ }
+ }
+
+ removed := make(map[string]struct{})
+ for i := len(toRemove) - 1; i >= 0; i-- {
+ commitID := toRemove[i].ID
+ beforeIDs, err := rc.History(commitID)
+ if err != nil {
+ log.Errorf("failed to remove %v (commits before): %v", toRemove[i], err)
+ continue
+ }
+ err = rc.Remove(commitID)
+ if err != nil {
+ log.Errorf("failed to remove %v (remove): %v", toRemove[i], err)
+ continue
+ }
+ removed[commitID] = struct{}{}
+ afterIDs, err := rc.History(p.Base.ID)
+ if err != nil {
+ log.Errorf("failed to remove %v (commits after): %v", toRemove[i], err)
+ continue
+ }
+ afterIDs = afterIDs[len(afterIDs)-len(beforeIDs):]
+ transform := make(map[string]string)
+ for j := 0; j < len(beforeIDs); j++ {
+ transform[beforeIDs[j]] = afterIDs[j]
+ }
+ for j, c := range p.Commits {
+ if newId, ok := transform[c.ID]; ok {
+ msgid := p.Commits[j].MessageId
+ p.Commits[j] = models.NewCommit(
+ rc,
+ newId,
+ p.Commits[j].Tag,
+ )
+ p.Commits[j].MessageId = msgid
+ }
+ }
+ }
+
+ if len(removed) < len(toRemove) {
+ return fmt.Errorf("Failed to remove commits. Removed %d of %d.",
+ len(removed), len(toRemove))
+ }
+
+ commits := make([]models.Commit, 0, len(p.Commits))
+ for _, c := range p.Commits {
+ if _, ok := removed[c.ID]; ok {
+ continue
+ }
+ commits = append(commits, c)
+ }
+ p.Commits = commits
+
+ return storeErr(m.store().StoreProject(p, true))
+}
diff --git a/lib/pama/remove_test.go b/lib/pama/remove_test.go
new file mode 100644
index 00000000..c9ce6c65
--- /dev/null
+++ b/lib/pama/remove_test.go
@@ -0,0 +1,85 @@
+package pama_test
+
+import (
+ "reflect"
+ "testing"
+
+ "git.sr.ht/~rjarry/aerc/lib/pama"
+ "git.sr.ht/~rjarry/aerc/lib/pama/models"
+)
+
+func TestPatchmgmt_Remove(t *testing.T) {
+ setup := func(p models.Project) (pama.PatchManager, models.RevisionController, models.PersistentStorer) {
+ return newTestManager(
+ []string{"0", "1", "2", "3", "4", "5"},
+ []string{"0", "a", "b", "c", "d", "f"},
+ map[string]models.Project{p.Name: p}, p.Name,
+ )
+ }
+
+ tests := []struct {
+ name string
+ remove string
+ commits []models.Commit
+ want []models.Commit
+ }{
+ {
+ name: "remove only patch",
+ remove: "patch1",
+ commits: []models.Commit{
+ newCommit("1", "a", "patch1"),
+ },
+ want: []models.Commit{},
+ },
+ {
+ name: "remove second one of two patch",
+ remove: "patch2",
+ commits: []models.Commit{
+ newCommit("1", "a", "patch1"),
+ newCommit("2", "b", "patch2"),
+ },
+ want: []models.Commit{
+ newCommit("1", "a", "patch1"),
+ },
+ },
+ {
+ name: "remove first one of two patch",
+ remove: "patch1",
+ commits: []models.Commit{
+ newCommit("1", "a", "patch1"),
+ newCommit("2", "b", "patch2"),
+ },
+ want: []models.Commit{
+ newCommit("2_new", "b", "patch2"),
+ },
+ },
+ }
+
+ for _, test := range tests {
+ p := models.Project{
+ Name: "project1",
+ Commits: test.commits,
+ Base: newCommit("0", "0", ""),
+ }
+ mgr, rc, _ := setup(p)
+
+ err := mgr.RemovePatch(test.remove)
+ if err != nil {
+ t.Errorf("test '%s' failed. %v", test.name, err)
+ }
+
+ q, _ := mgr.CurrentProject()
+ if !reflect.DeepEqual(q.Commits, test.want) {
+ t.Errorf("test '%s' failed. Commits don't match: "+
+ "got %v, but wanted %v", test.name, q.Commits,
+ test.want)
+ }
+
+ if len(test.want) > 0 {
+ last := test.want[len(test.want)-1]
+ if !rc.Exists(last.ID) {
+ t.Errorf("test '%s' failed. Could not find last commits: %v", test.name, last)
+ }
+ }
+ }
+}