all repos

mugit @ 1db697a

馃惍 git server that your cow will love
6 files changed, 91 insertions(+), 1 deletions(-)
ui: show last commit for file that's being viewed
Author: Oleksandr Smirnov olexsmir@gmail.com
Committed at: 2026-05-07 20:24:11 +0300
Authored at: 2026-05-07 19:26:35 +0300
Change ID: ktsykqsvznnrwzxxyllvntknrwzyssnk
Parent: 6bb47d3
M internal/git/repo.go
路路路
        4
        4
         	"context"

      
        5
        5
         	"errors"

      
        6
        6
         	"fmt"

      
        
        7
        +	"path"

      
        7
        8
         	"path/filepath"

      
        8
        9
         	"strings"

      
        9
        10
         	"time"

      路路路
        205
        206
         	c, err := g.r.CommitObject(g.h)

      
        206
        207
         	if err != nil {

      
        207
        208
         		return nil, fmt.Errorf("last commit: %w", err)

      
        
        209
        +	}

      
        
        210
        +

      
        
        211
        +	return newCommit(c), nil

      
        
        212
        +}

      
        
        213
        +

      
        
        214
        +func (g *Repo) LastFileCommit(ctx context.Context, fpath string) (*Commit, error) {

      
        
        215
        +	path := path.Clean(fpath)

      
        
        216
        +	hash, err := g.lastFileCommitHash(ctx, path)

      
        
        217
        +	if err != nil {

      
        
        218
        +		return nil, err

      
        
        219
        +	}

      
        
        220
        +

      
        
        221
        +	c, err := g.r.CommitObject(plumbing.NewHash(hash))

      
        
        222
        +	if err != nil {

      
        
        223
        +		return nil, fmt.Errorf("commit object %s: %w", hash, err)

      
        208
        224
         	}

      
        209
        225
         

      
        210
        226
         	return newCommit(c), nil

      
M internal/git/repo_test.go
路路路
        172
        172
         		r.commitFile("latest.txt", "latest", "latest commit")

      
        173
        173
         

      
        174
        174
         		commit, err := r.open().LastCommit()

      
        175
        
        -		is.Equal(t, commit.Message, "latest commit")

      
        176
        175
         		is.Err(t, err, nil)

      
        
        176
        +		is.Equal(t, commit.Message, "latest commit")

      
        177
        177
         	})

      
        178
        178
         

      
        179
        179
         	t.Run("empty repo returns empty commit", func(t *testing.T) {

      
        180
        180
         		commit, err := newTestRepo(t).open().LastCommit()

      
        181
        181
         		is.Err(t, err, nil)

      
        182
        182
         		is.Equal(t, commit.Message, "")

      
        
        183
        +	})

      
        
        184
        +}

      
        
        185
        +

      
        
        186
        +func TestRepo_LastFileCommit(t *testing.T) {

      
        
        187
        +	t.Run("returns last commit for root file", func(t *testing.T) {

      
        
        188
        +		r := newTestRepo(t)

      
        
        189
        +		r.commitFile("README.md", "v1\n", "init readme")

      
        
        190
        +		last := r.commitFile("README.md", "v2\n", "update readme")

      
        
        191
        +

      
        
        192
        +		c, err := r.open().LastFileCommit(t.Context(), "README.md")

      
        
        193
        +		is.Err(t, err, nil)

      
        
        194
        +		is.Equal(t, c.Hash, last.String())

      
        
        195
        +		is.Equal(t, c.Message, "update readme")

      
        
        196
        +	})

      
        
        197
        +

      
        
        198
        +	t.Run("returns last commit for nested file", func(t *testing.T) {

      
        
        199
        +		r := newTestRepo(t)

      
        
        200
        +		r.commitFile("docs/guide.md", "v1\n", "add guide")

      
        
        201
        +		last := r.commitFile("docs/guide.md", "v2\n", "update guide")

      
        
        202
        +		r.commitFile("README.md", "root\n", "touch root")

      
        
        203
        +

      
        
        204
        +		c, err := r.open().LastFileCommit(t.Context(), "docs/guide.md")

      
        
        205
        +		is.Err(t, err, nil)

      
        
        206
        +		is.Equal(t, c.Hash, last.String())

      
        
        207
        +		is.Equal(t, c.Message, "update guide")

      
        183
        208
         	})

      
        184
        209
         }

      
        185
        210
         

      
M internal/git/tree.go
路路路
        151
        151
         	files []string

      
        152
        152
         }

      
        153
        153
         

      
        
        154
        +func (g *Repo) lastFileCommitHash(ctx context.Context, fpath string) (string, error) {

      
        
        155
        +	output, err := g.streamingGitLog(ctx, "-n", "1", "--format=%H", "--", fpath)

      
        
        156
        +	if err != nil {

      
        
        157
        +		return "", fmt.Errorf("last file commit for %q: %w", fpath, err)

      
        
        158
        +	}

      
        
        159
        +	defer output.Close()

      
        
        160
        +

      
        
        161
        +	raw, err := io.ReadAll(output)

      
        
        162
        +	if err != nil {

      
        
        163
        +		return "", fmt.Errorf("reading log output for %q: %w", fpath, err)

      
        
        164
        +	}

      
        
        165
        +

      
        
        166
        +	hash := string(raw)

      
        
        167
        +	if hash == "" {

      
        
        168
        +		return "", fmt.Errorf("no last commit found for %q", fpath)

      
        
        169
        +	}

      
        
        170
        +

      
        
        171
        +	return hash, nil

      
        
        172
        +}

      
        
        173
        +

      
        154
        174
         func (g *Repo) lastCommitForFilesInTree(ctx context.Context, subtree *object.Tree, parent string) (map[string]*Commit, error) {

      
        155
        175
         	filesToDo := make(map[string]struct{})

      
        156
        176
         	filesDone := make(map[string]*Commit)

      
M internal/handlers/repo.go
路路路
        169
        169
         	Ref         string

      
        170
        170
         	Desc        string

      
        171
        171
         	Lines       []string

      
        
        172
        +	LastCommit  *git.Commit

      
        172
        173
         	Breadcrumbs []Breadcrumb

      
        173
        174
         	Path        string

      
        174
        175
         	IsImage     bool

      路路路
        205
        206
         		IsBinary: fc.IsBinary,

      
        206
        207
         		Mime:     fc.Mime,

      
        207
        208
         		Size:     fc.Size,

      
        
        209
        +	}

      
        
        210
        +

      
        
        211
        +	p.LastCommit, err = repo.LastFileCommit(r.Context(), treePath)

      
        
        212
        +	if err != nil {

      
        
        213
        +		h.write500(w, err)

      
        
        214
        +		return

      
        208
        215
         	}

      
        209
        216
         

      
        210
        217
         	p.Desc, err = repo.Description()

      
M web/static/style.css
路路路
        88
        88
         

      
        89
        89
         /* utilities */

      
        90
        90
         .mono { font-family: var(--mono-font); }

      
        
        91
        +.bold { font-weight: 700; }

      
        91
        92
         .nowrap { white-space: nowrap; }

      
        92
        93
         .fill { width: 100%; }

      
        93
        94
         .muted { color: var(--gray); }

      
        94
        95
         .pl { padding-left: 0.3rem; }

      
        95
        96
         .pt { padding-top: 1rem; }

      
        96
        97
         .mb { margin-bottom: 1rem; }

      
        
        98
        +.msb {margin-bottom: 0.3rem; }

      
        97
        99
         .center {

      
        98
        100
           display: flex;

      
        99
        101
           justify-content: center;

      
M web/templates/repo_file.html
路路路
        18
        18
                   (<a class="muted" href="/{{ .RepoName }}/raw/{{ .P.Ref }}/{{ .P.Path }}">view raw</a>)

      
        19
        19
                 </span>

      
        20
        20
               </p>

      
        
        21
        +

      
        
        22
        +      <div class="box msb">

      
        
        23
        +        <span class="has-tip">

      
        
        24
        +          <span>

      
        
        25
        +           <a class="bold" href="mailto:{{ .P.LastCommit.AuthorEmail }}">{{ .P.LastCommit.AuthorName }}</a>

      
        
        26
        +          </span>

      
        
        27
        +          <span class="tooltip" role="tooltip">

      
        
        28
        +            <strong>{{ .P.LastCommit.AuthorName }}</strong><br>

      
        
        29
        +            <a href="mailto:{{ .P.LastCommit.AuthorEmail }}" class="commit-email">{{ .P.LastCommit.AuthorEmail }}</a>

      
        
        30
        +          </span>

      
        
        31
        +        </span>

      
        
        32
        +        <span class="pl">

      
        
        33
        +          <a class="link" href="/{{ .RepoName }}/commit/{{ .P.LastCommit.Hash }}">

      
        
        34
        +            {{- if .P.LastCommit.Message }}{{- commitSummary .P.LastCommit.Message -}}

      
        
        35
        +            {{- else -}}<span class="muted">Empty message</span>{{- end -}}

      
        
        36
        +          </a>,

      
        
        37
        +        </span>

      
        
        38
        +        <span class="muted">{{ humanizeRelTime .P.LastCommit.Committed }}</span>

      
        
        39
        +      </div>

      
        
        40
        +

      
        21
        41
               <div class="file-wrapper">

      
        22
        42
                 {{ if .P.IsImage }}

      
        23
        43
                 <div class="image-viewer">