From 724d3a22cdaae3299df23195788ccfad7e332db8 Mon Sep 17 00:00:00 2001 From: Koni Marti Date: Fri, 24 Nov 2023 16:02:57 +0100 Subject: 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 Acked-by: Robin Jarry --- lib/pama/models/commit.go | 93 +++++++++++++++++++++++++++++++++++++++++++++++ lib/pama/models/models.go | 86 +++++++++++++++++++++++++++++++++++++++++++ lib/pama/models/view.go | 81 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 260 insertions(+) create mode 100644 lib/pama/models/commit.go create mode 100644 lib/pama/models/models.go create mode 100644 lib/pama/models/view.go (limited to 'lib/pama/models') 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()) +} -- cgit