aboutsummaryrefslogtreecommitdiffstats
path: root/lib/pama/revctrl/git.go
blob: f4b78dd5268d84a427c231b3f85b2d99ad707b45 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
package revctrl

import (
	"bytes"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"strings"

	"git.sr.ht/~rjarry/aerc/lib/pama/models"
	"git.sr.ht/~rjarry/aerc/log"
)

func init() {
	register("git", newGit)
}

func newGit(s string) models.RevisionController {
	return &git{path: strings.TrimSpace(s)}
}

type git struct {
	path string
}

func (g git) Support() bool {
	_, exitcode, err := g.do("rev-parse")
	return exitcode == 0 && err == nil
}

func (g git) Root() (string, error) {
	s, _, err := g.do("rev-parse", "--show-toplevel")
	return s, err
}

func (g git) Head() (string, error) {
	s, _, err := g.do("rev-list", "-n 1", "HEAD")
	return s, err
}

func (g git) History(commit string) ([]string, error) {
	s, _, err := g.do("rev-list", "--reverse", fmt.Sprintf("%s..HEAD", commit))
	return strings.Fields(s), err
}

func (g git) Subject(commit string) string {
	s, exitcode, err := g.do("log", "-1", "--pretty=%s", commit)
	if exitcode > 0 || err != nil {
		return ""
	}
	return s
}

func (g git) Author(commit string) string {
	s, exitcode, err := g.do("log", "-1", "--pretty=%an", commit)
	if exitcode > 0 || err != nil {
		return ""
	}
	return s
}

func (g git) Date(commit string) string {
	s, exitcode, err := g.do("log", "-1", "--pretty=%as", commit)
	if exitcode > 0 || err != nil {
		return ""
	}
	return s
}

func (g git) Remove(commit string) error {
	_, exitcode, err := g.do("rebase", "--onto", commit+"^", commit)
	if exitcode > 0 {
		return fmt.Errorf("failed to remove commit %s", commit)
	}
	return err
}

func (g git) Exists(commit string) bool {
	_, exitcode, err := g.do("merge-base", "--is-ancestor", commit, "HEAD")
	return exitcode == 0 && err == nil
}

func (g git) Clean() bool {
	// is a rebase in progress?
	dirs := []string{"rebase-merge", "rebase-apply"}
	for _, dir := range dirs {
		relPath, _, err := g.do("rev-parse", "--git-path", dir)
		if err == nil {
			if _, err := os.Stat(filepath.Join(g.path, relPath)); !os.IsNotExist(err) {
				log.Errorf("%s exists: another rebase in progress..", dir)
				return false
			}
		}
	}
	// are there unstaged changes?
	s, exitcode, err := g.do("diff-index", "HEAD")
	return len(s) == 0 && exitcode == 0 && err == nil
}

func (g git) ApplyCmd() string {
	// TODO: should we return a *exec.Cmd instead of a string?
	return fmt.Sprintf("git -C %s am -3 --empty drop", g.path)
}

func (g git) do(args ...string) (string, int, error) {
	proc := exec.Command("git", "-C", g.path)
	proc.Args = append(proc.Args, args...)
	proc.Env = os.Environ()
	result, err := proc.Output()
	return string(bytes.TrimSpace(result)), proc.ProcessState.ExitCode(), err
}