mirror of https://git.sr.ht/~rjarry/aerc
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
269 lines
5.1 KiB
Go
269 lines
5.1 KiB
Go
package patch
|
|
|
|
import (
|
|
"fmt"
|
|
"sort"
|
|
"strings"
|
|
"unicode"
|
|
|
|
"git.sr.ht/~rjarry/aerc/app"
|
|
"git.sr.ht/~rjarry/aerc/commands"
|
|
"git.sr.ht/~rjarry/aerc/commands/msg"
|
|
"git.sr.ht/~rjarry/aerc/lib/log"
|
|
"git.sr.ht/~rjarry/aerc/lib/pama"
|
|
"git.sr.ht/~rjarry/aerc/lib/pama/models"
|
|
)
|
|
|
|
type Apply struct {
|
|
Cmd string `opt:"-c" desc:"Apply patches with provided command."`
|
|
Worktree string `opt:"-w" desc:"Create linked worktree on this <commit-ish>."`
|
|
Tag string `opt:"tag" required:"true" complete:"CompleteTag" desc:"Identify patches with tag."`
|
|
}
|
|
|
|
func init() {
|
|
register(Apply{})
|
|
}
|
|
|
|
func (Apply) Description() string {
|
|
return "Apply the selected message(s) to the current project."
|
|
}
|
|
|
|
func (Apply) Context() commands.CommandContext {
|
|
return commands.MESSAGE_LIST | commands.MESSAGE_VIEWER
|
|
}
|
|
|
|
func (Apply) Aliases() []string {
|
|
return []string{"apply"}
|
|
}
|
|
|
|
func (*Apply) CompleteTag(arg string) []string {
|
|
patches, err := pama.New().CurrentPatches()
|
|
if err != nil {
|
|
log.Errorf("failed to current patches for completion: %v", err)
|
|
patches = nil
|
|
}
|
|
|
|
acct := app.SelectedAccount()
|
|
if acct == nil {
|
|
return nil
|
|
}
|
|
|
|
uids, err := acct.MarkedMessages()
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
if len(uids) == 0 {
|
|
msg, err := acct.SelectedMessage()
|
|
if err == nil {
|
|
uids = append(uids, msg.Uid)
|
|
}
|
|
}
|
|
|
|
store := acct.Store()
|
|
if store == nil {
|
|
return nil
|
|
}
|
|
|
|
var subjects []string
|
|
for _, uid := range uids {
|
|
if msg, ok := store.Messages[uid]; !ok || msg == nil || msg.Envelope == nil {
|
|
continue
|
|
} else {
|
|
subjects = append(subjects, msg.Envelope.Subject)
|
|
}
|
|
}
|
|
return proposePatchName(patches, subjects)
|
|
}
|
|
|
|
func (a Apply) Execute(args []string) error {
|
|
patch := a.Tag
|
|
worktree := a.Worktree
|
|
applyCmd := a.Cmd
|
|
|
|
m := pama.New()
|
|
p, err := m.CurrentProject()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
log.Tracef("Current project: %v", p)
|
|
|
|
if worktree != "" {
|
|
p, err = m.CreateWorktree(p, worktree, patch)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = m.SwitchProject(p.Name)
|
|
if err != nil {
|
|
log.Warnf("could not switch to worktree project: %v", err)
|
|
}
|
|
}
|
|
|
|
if models.Commits(p.Commits).HasTag(patch) {
|
|
return fmt.Errorf("Patch name '%s' already exists.", patch)
|
|
}
|
|
|
|
if !m.Clean(p) {
|
|
return fmt.Errorf("Aborting... There are unstaged changes in " +
|
|
"your repository.")
|
|
}
|
|
|
|
commit, err := m.Head(p)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
log.Tracef("HEAD commit before: %s", commit)
|
|
|
|
if applyCmd != "" {
|
|
rootFmt := "%r"
|
|
if strings.Contains(applyCmd, rootFmt) {
|
|
applyCmd = strings.ReplaceAll(applyCmd, rootFmt, p.Root)
|
|
}
|
|
log.Infof("use custom apply command: %s", applyCmd)
|
|
} else {
|
|
applyCmd, err = m.ApplyCmd(p)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
msgData := collectMessageData()
|
|
|
|
// apply patches with the pipe cmd
|
|
pipe := msg.Pipe{
|
|
Background: false,
|
|
Full: true,
|
|
Part: false,
|
|
Command: applyCmd,
|
|
}
|
|
return pipe.Run(func() {
|
|
p, err = m.ApplyUpdate(p, patch, commit, msgData)
|
|
if err != nil {
|
|
log.Errorf("Failed to save patch data: %v", err)
|
|
}
|
|
})
|
|
}
|
|
|
|
// collectMessageData returns a map where the key is the message id and the
|
|
// value the subject of the marked messages
|
|
func collectMessageData() map[string]string {
|
|
acct := app.SelectedAccount()
|
|
if acct == nil {
|
|
return nil
|
|
}
|
|
|
|
uids, err := commands.MarkedOrSelected(acct)
|
|
if err != nil {
|
|
log.Errorf("error occurred: %v", err)
|
|
return nil
|
|
}
|
|
|
|
store := acct.Store()
|
|
if store == nil {
|
|
return nil
|
|
}
|
|
|
|
kv := make(map[string]string)
|
|
for _, uid := range uids {
|
|
msginfo, ok := store.Messages[uid]
|
|
if !ok || msginfo == nil {
|
|
continue
|
|
}
|
|
id, err := msginfo.MsgId()
|
|
if err != nil {
|
|
continue
|
|
}
|
|
if msginfo.Envelope == nil {
|
|
continue
|
|
}
|
|
|
|
kv[id] = msginfo.Envelope.Subject
|
|
}
|
|
|
|
return kv
|
|
}
|
|
|
|
func proposePatchName(patches, subjects []string) []string {
|
|
parse := func(s string) (string, string, bool) {
|
|
var tag strings.Builder
|
|
var version string
|
|
var i, j int
|
|
|
|
i = strings.Index(s, "[")
|
|
if i < 0 {
|
|
goto noPatch
|
|
}
|
|
s = s[i+1:]
|
|
|
|
j = strings.Index(s, "]")
|
|
if j < 0 {
|
|
goto noPatch
|
|
}
|
|
for _, elem := range strings.Fields(s[:j]) {
|
|
vers := strings.ToLower(elem)
|
|
if !strings.HasPrefix(vers, "v") {
|
|
continue
|
|
}
|
|
isVersion := true
|
|
for _, r := range vers[1:] {
|
|
if !unicode.IsDigit(r) {
|
|
isVersion = false
|
|
break
|
|
}
|
|
}
|
|
if isVersion {
|
|
version = vers
|
|
break
|
|
}
|
|
}
|
|
s = strings.TrimSpace(s[j+1:])
|
|
|
|
for _, r := range s {
|
|
if unicode.IsSpace(r) || r == ':' {
|
|
break
|
|
}
|
|
_, err := tag.WriteRune(r)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
}
|
|
return tag.String(), version, true
|
|
noPatch:
|
|
return "", "", false
|
|
}
|
|
|
|
summary := make(map[string]struct{})
|
|
|
|
var results []string
|
|
for _, s := range subjects {
|
|
tag, version, isPatch := parse(s)
|
|
if tag == "" || !isPatch {
|
|
continue
|
|
}
|
|
if version == "" {
|
|
version = "v1"
|
|
}
|
|
result := fmt.Sprintf("%s_%s", tag, version)
|
|
result = strings.ReplaceAll(result, " ", "")
|
|
|
|
collision := false
|
|
for _, name := range patches {
|
|
if name == result {
|
|
collision = true
|
|
}
|
|
}
|
|
if collision {
|
|
continue
|
|
}
|
|
|
|
_, ok := summary[result]
|
|
if ok {
|
|
continue
|
|
}
|
|
results = append(results, result)
|
|
summary[result] = struct{}{}
|
|
}
|
|
|
|
sort.Strings(results)
|
|
return results
|
|
}
|