bidet

commit a2cf096eba70b87eb52429e3f17c014e386de87e

tree

parent:
2214fef21788685c958b498f3e632bbbfc863ea3

nmyk <nick@nmyk.io>

2026-02-23T08:41:09-05:00

add last update time to index page

sort repos by last update time in descending order

diff --git a/go.mod b/go.mod
index 3eb936d0087105b90116c36abdfd3d14facd05a8..c500eda0bc79434ce2d33a0d2046cd7bb9d818f9 100644
--- a/go.mod
+++ b/go.mod
@@ -3,6 +3,7 @@
 go 1.25.0
 
 require (
+	github.com/dustin/go-humanize v1.0.1
 	github.com/go-git/go-git/v6 v6.0.0-20260220113129-c02711164eb8
 	github.com/peterbourgon/ff/v4 v4.0.0-beta.1
 )
diff --git a/go.sum b/go.sum
index 6d7af3dd9640cd9a889a2e8ca20f74c91accb64b..27e40590f5a4ec1b031fe8d881173e738d291580 100644
--- a/go.sum
+++ b/go.sum
@@ -13,6 +13,8 @@ github.com/cyphar/filepath-securejoin v0.6.1/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
+github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
 github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
 github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
 github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
diff --git a/internal/core/repo.go b/internal/core/repo.go
index f1230751ad3e44e8384331987d51f494dee89330..a2befcad3b24d4eeed2f859df06c36a28a4f6430 100644
--- a/internal/core/repo.go
+++ b/internal/core/repo.go
@@ -13,19 +13,21 @@ )
 
 const dotGit = ".git"
 
-func isBareRepo(e os.DirEntry) bool {
-	return (e.IsDir() && strings.HasSuffix(e.Name(), dotGit) && e.Name() != dotGit)
-}
-
-func List(dir string) ([]string, error) {
+func List(dir string) ([]*Repo, error) {
 	entries, err := os.ReadDir(dir)
 	if err != nil {
 		return nil, err
 	}
-	var repos []string
+	var repos []*Repo
 	for _, e := range entries {
-		if isBareRepo(e) {
-			repos = append(repos, strings.TrimSuffix(e.Name(), dotGit))
+		if e.Name() == dotGit {
+			continue
+		}
+		repo, err := Open(strings.TrimSuffix(e.Name(), dotGit))
+		if err == nil {
+			if _, err = repo.Worktree(); errors.Is(err, git.ErrIsBareRepository) {
+				repos = append(repos, repo)
+			}
 		}
 	}
 	if len(repos) == 0 {
@@ -36,6 +38,7 @@ }
 
 type Repo struct {
 	*git.Repository
+	Name string
 }
 
 func Open(repoName string) (*Repo, error) {
@@ -46,7 +49,7 @@ 	repo, err := git.PlainOpen(repoName + dotGit)
 	if err != nil {
 		return nil, err
 	}
-	return &Repo{repo}, nil
+	return &Repo{repo, repoName}, nil
 }
 
 func (repo *Repo) Load(entry *object.TreeEntry) (string, error) {
diff --git a/internal/handlers/handlers.go b/internal/handlers/handlers.go
index e3eec2c72d657d479eca187d96bdaaae7257f6a1..8f04a45e151430b71265c951041141045ed8b3f5 100644
--- a/internal/handlers/handlers.go
+++ b/internal/handlers/handlers.go
@@ -13,6 +13,7 @@ 	"slices"
 	"strings"
 	"time"
 
+	"github.com/dustin/go-humanize"
 	"github.com/go-git/go-git/v6"
 	"github.com/go-git/go-git/v6/plumbing"
 	"github.com/go-git/go-git/v6/plumbing/filemode"
@@ -96,7 +97,13 @@
 type IndexData struct {
 	Title  string
 	Header string
-	Repos  []string
+	Repos  []RepoMeta
+}
+
+type RepoMeta struct {
+	Name       string
+	Updated    string
+	updateTime time.Time
 }
 
 func (s Server) ListRepos(w http.ResponseWriter, _ *http.Request) {
@@ -105,10 +112,33 @@ 	if err != nil {
 		s.error(w, err)
 		return
 	}
+	var repoList []RepoMeta
+	for _, repo := range repos {
+		ref, err := repo.Head()
+		if err != nil {
+			s.error(w, err)
+		}
+		commit, err := repo.CommitObject(ref.Hash())
+		if err != nil {
+			s.error(w, err)
+		}
+		lastUpdatedTime := commit.Committer.When
+		lastUpdated := humanize.Time(lastUpdatedTime)
+		meta := RepoMeta{
+			repo.Name,
+			lastUpdated,
+			lastUpdatedTime,
+		}
+		repoList = append(repoList, meta)
+	}
+	slices.SortFunc(repoList, func(a RepoMeta, b RepoMeta) int {
+		// order by updateTime descending
+		return -1 * a.updateTime.Compare(b.updateTime)
+	})
 	data := IndexData{
 		Title:  s.Hostname,
 		Header: s.Greeting,
-		Repos:  repos,
+		Repos:  repoList,
 	}
 	s.Serve(w, "repos", data)
 }
diff --git a/internal/handlers/static/style.css b/internal/handlers/static/style.css
index d444c08d5f2a27c0a080c6563935313a707a9ad0..cb240c30a65cea23274e756b55755adefb37234a 100644
--- a/internal/handlers/static/style.css
+++ b/internal/handlers/static/style.css
@@ -71,4 +71,8 @@
 pre {
     overflow-x: auto;
     padding-bottom: 10px;
+}
+
+table {
+    width: 100%;
 }
\ No newline at end of file
diff --git a/internal/handlers/templates/repos.tmpl b/internal/handlers/templates/repos.tmpl
index 28fb5e11c198177f67558eea8af2563c37090e0b..4dcbf3937e14b8b3b3fbc34733878f92aa396db4 100644
--- a/internal/handlers/templates/repos.tmpl
+++ b/internal/handlers/templates/repos.tmpl
@@ -2,11 +2,16 @@ {{define "title"}}{{.Title}}{{end}}
 {{define "content"}}
 <h1>{{.Title}}</h1>
 <h4>{{.Header}}</h4>
-<ul>
+<table>
+<tbody>
 {{range .Repos}}
-<li><a href="/{{.}}">{{.}}</a></li>
-{{end}}
-</ul>
+        <tr>
+            <td><a href="/{{.Name}}">{{.Name}}</a></td>
+            <td>{{.Updated}}</td>
+        </tr>
+    {{end}}
+</tbody>
+</table>
 {{end}}
 
 {{define "repos"}}