aboutsummaryrefslogtreecommitdiffstats
path: root/lib/pama/revctrl/git.go
blob: 2a35754eb360a0e35e083a2c1b26d24c3b133eef (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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
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) Drop(commit string) error {
	_, exitcode, err := g.do("rebase", "--onto", commit+"^", commit)
	if exitcode > 0 {
		return fmt.Errorf("failed to drop 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) CreateWorktree(target, commit string) error {
	_, exitcode, err := g.do("worktree", "add", target, commit)
	if exitcode > 0 {
		return fmt.Errorf("failed to create worktree in %s: %w", target, err)
	}
	return err
}

func (g git) DeleteWorktree(target string) error {
	_, exitcode, err := g.do("worktree", "remove", target)
	if exitcode > 0 {
		return fmt.Errorf("failed to delete worktree in %s: %w", target, err)
	}
	return err
}

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
}