e0eb757c08c8623e888a98c5529cf96521f87225

Author
Toby Padilla <toby@charm.sh>
Committer
Toby Padilla <toby@charm.sh>
Date

Message

List latest commits in bubble

Diff

  1diff --git a/main.go b/main.go
  2index a2cc4c953ff6da2861835f430dd3e3acdce97553..4ee805c02ac114d61909c25aff150c047508b27e 100644
  3--- a/main.go
  4+++ b/main.go
  5@@ -27,7 +27,7 @@ func main() {
  6 	s, err := server.NewServer(
  7 		cfg.Port,
  8 		cfg.KeyPath,
  9-		bm.Middleware(tui.SessionHandler),
 10+		bm.Middleware(tui.SessionHandler(cfg.RepoPath)),
 11 		gm.Middleware(cfg.RepoPath, cfg.RepoAuthPath),
 12 		lm.Middleware(),
 13 	)
 14diff --git a/tui/commands.go b/tui/commands.go
 15index 2ca8f35995743e69a7bef225d931ac6bc990cfe2..cd24944ead3f03f615f3153414d455cb75bd07b1 100644
 16--- a/tui/commands.go
 17+++ b/tui/commands.go
 18@@ -12,3 +12,9 @@ func (m *Model) windowChangesCmd() tea.Msg {
 19 	m.height = w.Height
 20 	return windowMsg{}
 21 }
 22+
 23+func (m *Model) getCommitsCmd() tea.Msg {
 24+	m.commits = m.repos.getCommits(20)
 25+	m.state = commitsLoadedState
 26+	return nil
 27+}
 28diff --git a/tui/git.go b/tui/git.go
 29new file mode 100644
 30index 0000000000000000000000000000000000000000..631ef7ede5d9172ffee779b52411609064b609ef
 31--- /dev/null
 32+++ b/tui/git.go
 33@@ -0,0 +1,77 @@
 34+package tui
 35+
 36+import (
 37+	"log"
 38+	"os"
 39+	"sort"
 40+	"sync"
 41+	"time"
 42+
 43+	"github.com/go-git/go-git/v5"
 44+	"github.com/go-git/go-git/v5/plumbing/object"
 45+)
 46+
 47+type commitLog []*object.Commit
 48+
 49+func (cl commitLog) Len() int           { return len(cl) }
 50+func (cl commitLog) Swap(i, j int)      { cl[i], cl[j] = cl[j], cl[i] }
 51+func (cl commitLog) Less(i, j int) bool { return cl[i].Author.When.After(cl[j].Author.When) }
 52+
 53+type repoSource struct {
 54+	mtx     sync.Mutex
 55+	path    string
 56+	repos   []*git.Repository
 57+	commits commitLog
 58+}
 59+
 60+func newRepoSource(repoPath string) *repoSource {
 61+	rs := &repoSource{path: repoPath}
 62+	go func() {
 63+		for {
 64+			rs.loadRepos()
 65+			time.Sleep(time.Second * 10)
 66+		}
 67+	}()
 68+	return rs
 69+}
 70+
 71+func (rs *repoSource) allRepos() []*git.Repository {
 72+	rs.mtx.Lock()
 73+	defer rs.mtx.Unlock()
 74+	return rs.repos
 75+}
 76+
 77+func (rs *repoSource) getCommits(limit int) []*object.Commit {
 78+	rs.mtx.Lock()
 79+	defer rs.mtx.Unlock()
 80+	if limit > len(rs.commits) {
 81+		limit = len(rs.commits)
 82+	}
 83+	return rs.commits[:limit]
 84+}
 85+
 86+func (rs *repoSource) loadRepos() {
 87+	rs.mtx.Lock()
 88+	defer rs.mtx.Unlock()
 89+	rd, err := os.ReadDir(rs.path)
 90+	if err != nil {
 91+		return
 92+	}
 93+	rs.repos = make([]*git.Repository, 0)
 94+	for _, rd := range rd {
 95+		r, err := git.PlainOpen(rs.path + string(os.PathSeparator) + rd.Name())
 96+		if err != nil {
 97+			log.Fatal(err)
 98+		}
 99+		l, err := r.Log(&git.LogOptions{All: true})
100+		if err != nil {
101+			log.Fatal(err)
102+		}
103+		l.ForEach(func(c *object.Commit) error {
104+			rs.commits = append(rs.commits, c)
105+			return nil
106+		})
107+		sort.Sort(rs.commits)
108+		rs.repos = append(rs.repos, r)
109+	}
110+}
111diff --git a/tui/model.go b/tui/model.go
112index 9ed9028a608a7fa2fea953a65cd5839309e2081c..2c92291e67bf228ee0865be497de26271cc973b4 100644
113--- a/tui/model.go
114+++ b/tui/model.go
115@@ -5,6 +5,7 @@ import (
116 
117 	tea "github.com/charmbracelet/bubbletea"
118 	"github.com/gliderlabs/ssh"
119+	"github.com/go-git/go-git/v5/plumbing/object"
120 )
121 
122 type sessionState int
123@@ -12,6 +13,7 @@ type sessionState int
124 const (
125 	startState sessionState = iota
126 	errorState
127+	commitsLoadedState
128 	quittingState
129 	quitState
130 )
131@@ -24,15 +26,18 @@ func (e errMsg) Error() string {
132 	return e.err.Error()
133 }
134 
135-func SessionHandler(s ssh.Session) (tea.Model, []tea.ProgramOption) {
136-	if len(s.Command()) == 0 {
137-		pty, changes, active := s.Pty()
138-		if !active {
139-			return nil, nil
140+func SessionHandler(repoPath string) func(ssh.Session) (tea.Model, []tea.ProgramOption) {
141+	rs := newRepoSource(repoPath)
142+	return func(s ssh.Session) (tea.Model, []tea.ProgramOption) {
143+		if len(s.Command()) == 0 {
144+			pty, changes, active := s.Pty()
145+			if !active {
146+				return nil, nil
147+			}
148+			return NewModel(pty.Window.Width, pty.Window.Height, changes, rs), nil
149 		}
150-		return NewModel(pty.Window.Width, pty.Window.Height, changes), []tea.ProgramOption{tea.WithAltScreen()}
151+		return nil, nil
152 	}
153-	return nil, nil
154 }
155 
156 type Model struct {
157@@ -42,20 +47,24 @@ type Model struct {
158 	width         int
159 	height        int
160 	windowChanges <-chan ssh.Window
161+	repos         *repoSource
162+	commits       []*object.Commit
163 }
164 
165-func NewModel(width int, height int, windowChanges <-chan ssh.Window) *Model {
166+func NewModel(width int, height int, windowChanges <-chan ssh.Window, repos *repoSource) *Model {
167 	m := &Model{
168 		width:         width,
169 		height:        height,
170 		windowChanges: windowChanges,
171+		repos:         repos,
172+		commits:       make([]*object.Commit, 0),
173 	}
174 	m.state = startState
175 	return m
176 }
177 
178 func (m *Model) Init() tea.Cmd {
179-	return tea.Batch(m.windowChangesCmd, tea.HideCursor)
180+	return tea.Batch(m.windowChangesCmd, tea.HideCursor, m.getCommitsCmd)
181 }
182 
183 func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
184@@ -86,11 +95,18 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
185 
186 func (m *Model) View() string {
187 	pad := 6
188-	h := headerStyle.Width(m.width - pad).Render("Smoothie")
189+	h := headerStyle.Width(m.width - pad).Render("Charm Beta")
190 	f := footerStyle.Render(m.info)
191 	s := ""
192 	content := ""
193 	switch m.state {
194+	case startState:
195+		s += normalStyle.Render("Loading")
196+	case commitsLoadedState:
197+		for _, c := range m.commits {
198+			msg := fmt.Sprintf("%s %s %s %s", c.Author.When, c.Author.Name, c.Author.Email, c.Message)
199+			s += normalStyle.Render(msg) + "\n"
200+		}
201 	case errorState:
202 		s += errorStyle.Render(fmt.Sprintf("Bummer: %s", m.error))
203 	default:
204diff --git a/tui/style.go b/tui/style.go
205index 6c219b1117230fc47c63efd21d90d117151ca91a..dc0d2f4bd0efad5142f31dd2b30af385049aaf27 100644
206--- a/tui/style.go
207+++ b/tui/style.go
208@@ -18,12 +18,9 @@ var normalStyle = lipgloss.NewStyle().
209 	Foreground(lipgloss.Color("#FFFFFF"))
210 
211 var footerStyle = lipgloss.NewStyle().
212-	BorderStyle(lipgloss.Border{Left: ">"}).
213 	BorderForeground(lipgloss.Color("#6D6D6D")).
214 	BorderLeft(true).
215 	Foreground(lipgloss.Color("#373737")).
216-	PaddingLeft(1).
217-	MarginLeft(1).
218 	Bold(true)
219 
220 var errorStyle = lipgloss.NewStyle().