aboutsummaryrefslogtreecommitdiffstats
path: root/lib/pama
diff options
context:
space:
mode:
authorKoni Marti <koni.marti@gmail.com>2023-11-24 16:02:57 +0100
committerRobin Jarry <robin@jarry.cc>2023-12-30 15:42:09 +0100
commit724d3a22cdaae3299df23195788ccfad7e332db8 (patch)
tree842a74f0b2a9b875b5c73b41cb03754c4b92b6ab /lib/pama
parent60c1c7347ad88ad00fc0809acd10ddd31243acd3 (diff)
downloadaerc-724d3a22cdaae3299df23195788ccfad7e332db8.tar.gz
pama: define the entity models
Define the entity models for the patch management. Add a Project and Commit struct and implement the Stringer interface for both. Signed-off-by: Koni Marti <koni.marti@gmail.com> Acked-by: Robin Jarry <robin@jarry.cc>
Diffstat (limited to 'lib/pama')
-rw-r--r--lib/pama/models/commit.go93
-rw-r--r--lib/pama/models/models.go86
-rw-r--r--lib/pama/models/view.go81
3 files changed, 260 insertions, 0 deletions
diff --git a/lib/pama/models/commit.go b/lib/pama/models/commit.go
new file mode 100644
index 00000000..4cd06b4b
--- /dev/null
+++ b/lib/pama/models/commit.go
@@ -0,0 +1,93 @@
+package models
+
+import (
+ "fmt"
+ "strings"
+)
+
+const (
+ Untracked = "untracked"
+)
+
+func NewCommit(r RevisionController, id, tag string) Commit {
+ return Commit{
+ ID: id,
+ Subject: r.Subject(id),
+ Author: r.Author(id),
+ Date: r.Date(id),
+ MessageId: "",
+ Tag: tag,
+ }
+}
+
+func (c Commit) Untracked() bool {
+ return c.Tag == Untracked
+}
+
+func (c Commit) Info() string {
+ s := []string{}
+ if c.Subject == "" {
+ s = append(s, "(no subject)")
+ } else {
+ s = append(s, c.Subject)
+ }
+ if c.Author != "" {
+ s = append(s, c.Author)
+ }
+ if c.Date != "" {
+ s = append(s, c.Date)
+ }
+ if c.MessageId != "" {
+ s = append(s, "<"+c.MessageId+">")
+ }
+ return strings.Join(s, ", ")
+}
+
+func (c Commit) String() string {
+ return fmt.Sprintf("%-6.6s %s", c.ID, c.Info())
+}
+
+type Commits []Commit
+
+func (h Commits) Tags() []string {
+ var tags []string
+ dedup := make(map[string]struct{})
+ for _, c := range h {
+ _, ok := dedup[c.Tag]
+ if ok {
+ continue
+ }
+ tags = append(tags, c.Tag)
+ dedup[c.Tag] = struct{}{}
+ }
+ return tags
+}
+
+func (h Commits) HasTag(t string) bool {
+ for _, c := range h {
+ if c.Tag == t {
+ return true
+ }
+ }
+ return false
+}
+
+func (h Commits) Lookup(id string) (Commit, bool) {
+ for _, c := range h {
+ if c.ID == id {
+ return c, true
+ }
+ }
+ return Commit{}, false
+}
+
+type CommitIDs []string
+
+func (c CommitIDs) Has(id string) bool {
+ for _, cid := range c {
+ if cid == id {
+ return true
+ }
+ }
+ return false
+}
diff --git a/lib/pama/models/models.go b/lib/pama/models/models.go
new file mode 100644
index 00000000..1c098885
--- /dev/null
+++ b/lib/pama/models/models.go
@@ -0,0 +1,86 @@
+package models
+
+// Commit represents a commit object in a revision control system.
+type Commit struct {
+ // ID is the commit hash.
+ ID string
+ // Subject is the subject line of the commit.
+ Subject string
+ // Author is the author's name.
+ Author string
+ // Date associated with the given commit.
+ Date string
+ // MessageId is the message id for the message that contains the commit
+ // diff. This field is only set when commits were applied via patch
+ // apply system.
+ MessageId string
+ // Tag is a user label that is assigned to one or multiple commits. It
+ // creates a logical connection between a group of commits to represent
+ // a patch set.
+ Tag string
+}
+
+// Project contains the data to access a revision control system and to store
+// the internal patch tracking data.
+type Project struct {
+ // Name is the project name and works as the project ID. Do not change
+ // it.
+ Name string
+ // Root represents the root directory of the revision control system.
+ Root string
+ // RevctrlID stores the ID for the revision control system.
+ RevctrlID string
+ // Base represents the reference (base) commit.
+ Base Commit
+ // Commits contains the commits that are being tracked. The slice can
+ // contain any commit between the Base commit and HEAD. These commits
+ // will be updated by an applying, removing or rebase operation.
+ Commits []Commit
+}
+
+// RevisionController is an interface to a revision control system.
+type RevisionController interface {
+ // Returns the commit hash of the HEAD commit.
+ Head() (string, error)
+ // History accepts a commit hash and returns a list of commit hashes
+ // between the provided hash and HEAD. The order of the returned slice
+ // is important. The commit hashes should be ordered from "earlier" to
+ // "later" where the last element must be HEAD.
+ History(string) ([]string, error)
+ // Clean returns true if there are no unstaged changes. If there are
+ // unstaged changes, applying and removing patches will not work.
+ Clean() bool
+ // Exists returns true if the commit hash exists in the commit history.
+ Exists(string) bool
+ // Subject returns the subject line for the provided commit hash.
+ Subject(string) string
+ // Author returns the author for the provided commit hash.
+ Author(string) string
+ // Date returns the date for the provided commit hash.
+ Date(string) string
+ // Remove removes the commit with the provided commit hash from the
+ // repository.
+ Remove(string) error
+ // ApplyCmd returns a string with an executable command that is used to
+ // apply patches with the :pipe command.
+ ApplyCmd() string
+}
+
+// PersistentStorer is an interface to a persistent storage for Project structs.
+type PersistentStorer interface {
+ // StoreProject saves the project data persistently. If overwrite is
+ // true, it will write over existing data.
+ StoreProject(Project, bool) error
+ // DeleteProject removes the project data from the store.
+ DeleteProject(string) error
+ // CurrentName returns the Project.Name for the active project.
+ CurrentName() (string, error)
+ // SetCurrent stores a Project.Name and make that project active.
+ SetCurrent(string) error
+ // Current returns the project data for the active project.
+ Current() (Project, error)
+ // Names returns a slice of Project.Name for all stored projects.
+ Names() ([]string, error)
+ // Projects returns all stored projects.
+ Projects() ([]Project, error)
+}
diff --git a/lib/pama/models/view.go b/lib/pama/models/view.go
new file mode 100644
index 00000000..093ca6f0
--- /dev/null
+++ b/lib/pama/models/view.go
@@ -0,0 +1,81 @@
+package models
+
+import (
+ "bytes"
+ "io"
+ "strings"
+ "text/template"
+
+ "git.sr.ht/~rjarry/aerc/log"
+)
+
+var templateText = `
+Project {{.Name}} {{if .IsActive}}[active]{{end}}
+Directory {{.Root}}
+Base {{with .Base.ID}}{{if ge (len .) 40}}{{printf "%-6.6s" .}}{{else}}{{.}}{{end}}{{end}}
+{{$notes := .Notes}}{{$commits := .Commits}}
+{{- range $index, $patch := .Patches}}
+ {{$patch}}:
+ {{- range (index $commits $patch)}}
+ {{with (index $notes .ID)}}[{{.}}] {{end}}{{. -}}
+ {{end}}
+{{end -}}
+`
+
+var viewRenderer = template.Must(template.New("ProjectToText").Parse(templateText))
+
+type view struct {
+ Name string
+ Root string
+ Base Commit
+ // Patches are the unique tag names.
+ Patches []string
+ // Commits is a map where the tag names are keys and the associated
+ // commits the values.
+ Commits map[string][]Commit
+ // Notes contain annotations of the commits where the commit hash is
+ // the key and the annotation is the value.
+ Notes map[string]string
+ // IsActive is true if the current project is selected.
+ IsActive bool
+}
+
+func newView(p Project, active bool, notes map[string]string) view {
+ v := view{
+ Name: p.Name,
+ Root: p.Root,
+ Base: p.Base,
+ Commits: make(map[string][]Commit),
+ Notes: notes,
+ IsActive: active,
+ }
+
+ for _, commit := range p.Commits {
+ patch := commit.Tag
+ commits, ok := v.Commits[patch]
+ if !ok {
+ v.Patches = append(v.Patches, patch)
+ }
+ commits = append(commits, commit)
+ v.Commits[patch] = commits
+ }
+
+ return v
+}
+
+func (v view) String() string {
+ var buf bytes.Buffer
+ err := viewRenderer.Execute(&buf, v)
+ if err != nil {
+ log.Errorf("failed to run template: %v", err)
+ }
+ return buf.String()
+}
+
+func (p Project) String() string {
+ return newView(p, false, nil).String()
+}
+
+func (p Project) NewReader(isActive bool, notes map[string]string) io.Reader {
+ return strings.NewReader(newView(p, isActive, notes).String())
+}