diff --git a/main.go b/main.go index a2cc4c953..4ee805c02 100644 --- a/main.go +++ b/main.go @@ -27,7 +27,7 @@ func main() { s, err := server.NewServer( cfg.Port, cfg.KeyPath, - bm.Middleware(tui.SessionHandler), + bm.Middleware(tui.SessionHandler(cfg.RepoPath)), gm.Middleware(cfg.RepoPath, cfg.RepoAuthPath), lm.Middleware(), ) diff --git a/tui/commands.go b/tui/commands.go index 2ca8f3599..cd24944ea 100644 --- a/tui/commands.go +++ b/tui/commands.go @@ -12,3 +12,9 @@ func (m *Model) windowChangesCmd() tea.Msg { m.height = w.Height return windowMsg{} } + +func (m *Model) getCommitsCmd() tea.Msg { + m.commits = m.repos.getCommits(20) + m.state = commitsLoadedState + return nil +} diff --git a/tui/git.go b/tui/git.go new file mode 100644 index 000000000..631ef7ede --- /dev/null +++ b/tui/git.go @@ -0,0 +1,77 @@ +package tui + +import ( + "log" + "os" + "sort" + "sync" + "time" + + "github.com/go-git/go-git/v5" + "github.com/go-git/go-git/v5/plumbing/object" +) + +type commitLog []*object.Commit + +func (cl commitLog) Len() int { return len(cl) } +func (cl commitLog) Swap(i, j int) { cl[i], cl[j] = cl[j], cl[i] } +func (cl commitLog) Less(i, j int) bool { return cl[i].Author.When.After(cl[j].Author.When) } + +type repoSource struct { + mtx sync.Mutex + path string + repos []*git.Repository + commits commitLog +} + +func newRepoSource(repoPath string) *repoSource { + rs := &repoSource{path: repoPath} + go func() { + for { + rs.loadRepos() + time.Sleep(time.Second * 10) + } + }() + return rs +} + +func (rs *repoSource) allRepos() []*git.Repository { + rs.mtx.Lock() + defer rs.mtx.Unlock() + return rs.repos +} + +func (rs *repoSource) getCommits(limit int) []*object.Commit { + rs.mtx.Lock() + defer rs.mtx.Unlock() + if limit > len(rs.commits) { + limit = len(rs.commits) + } + return rs.commits[:limit] +} + +func (rs *repoSource) loadRepos() { + rs.mtx.Lock() + defer rs.mtx.Unlock() + rd, err := os.ReadDir(rs.path) + if err != nil { + return + } + rs.repos = make([]*git.Repository, 0) + for _, rd := range rd { + r, err := git.PlainOpen(rs.path + string(os.PathSeparator) + rd.Name()) + if err != nil { + log.Fatal(err) + } + l, err := r.Log(&git.LogOptions{All: true}) + if err != nil { + log.Fatal(err) + } + l.ForEach(func(c *object.Commit) error { + rs.commits = append(rs.commits, c) + return nil + }) + sort.Sort(rs.commits) + rs.repos = append(rs.repos, r) + } +} diff --git a/tui/model.go b/tui/model.go index 9ed9028a6..2c92291e6 100644 --- a/tui/model.go +++ b/tui/model.go @@ -5,6 +5,7 @@ import ( tea "github.com/charmbracelet/bubbletea" "github.com/gliderlabs/ssh" + "github.com/go-git/go-git/v5/plumbing/object" ) type sessionState int @@ -12,6 +13,7 @@ type sessionState int const ( startState sessionState = iota errorState + commitsLoadedState quittingState quitState ) @@ -24,15 +26,18 @@ func (e errMsg) Error() string { return e.err.Error() } -func SessionHandler(s ssh.Session) (tea.Model, []tea.ProgramOption) { - if len(s.Command()) == 0 { - pty, changes, active := s.Pty() - if !active { - return nil, nil +func SessionHandler(repoPath string) func(ssh.Session) (tea.Model, []tea.ProgramOption) { + rs := newRepoSource(repoPath) + return func(s ssh.Session) (tea.Model, []tea.ProgramOption) { + if len(s.Command()) == 0 { + pty, changes, active := s.Pty() + if !active { + return nil, nil + } + return NewModel(pty.Window.Width, pty.Window.Height, changes, rs), nil } - return NewModel(pty.Window.Width, pty.Window.Height, changes), []tea.ProgramOption{tea.WithAltScreen()} + return nil, nil } - return nil, nil } type Model struct { @@ -42,20 +47,24 @@ type Model struct { width int height int windowChanges <-chan ssh.Window + repos *repoSource + commits []*object.Commit } -func NewModel(width int, height int, windowChanges <-chan ssh.Window) *Model { +func NewModel(width int, height int, windowChanges <-chan ssh.Window, repos *repoSource) *Model { m := &Model{ width: width, height: height, windowChanges: windowChanges, + repos: repos, + commits: make([]*object.Commit, 0), } m.state = startState return m } func (m *Model) Init() tea.Cmd { - return tea.Batch(m.windowChangesCmd, tea.HideCursor) + return tea.Batch(m.windowChangesCmd, tea.HideCursor, m.getCommitsCmd) } func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { @@ -86,11 +95,18 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { func (m *Model) View() string { pad := 6 - h := headerStyle.Width(m.width - pad).Render("Smoothie") + h := headerStyle.Width(m.width - pad).Render("Charm Beta") f := footerStyle.Render(m.info) s := "" content := "" switch m.state { + case startState: + s += normalStyle.Render("Loading") + case commitsLoadedState: + for _, c := range m.commits { + msg := fmt.Sprintf("%s %s %s %s", c.Author.When, c.Author.Name, c.Author.Email, c.Message) + s += normalStyle.Render(msg) + "\n" + } case errorState: s += errorStyle.Render(fmt.Sprintf("Bummer: %s", m.error)) default: diff --git a/tui/style.go b/tui/style.go index 6c219b111..dc0d2f4bd 100644 --- a/tui/style.go +++ b/tui/style.go @@ -18,12 +18,9 @@ var normalStyle = lipgloss.NewStyle(). Foreground(lipgloss.Color("#FFFFFF")) var footerStyle = lipgloss.NewStyle(). - BorderStyle(lipgloss.Border{Left: ">"}). BorderForeground(lipgloss.Color("#6D6D6D")). BorderLeft(true). Foreground(lipgloss.Color("#373737")). - PaddingLeft(1). - MarginLeft(1). Bold(true) var errorStyle = lipgloss.NewStyle().