all repos

mugit @ e5b0a7c

馃惍 git server that your cow will love
3 files changed, 86 insertions(+), 32 deletions(-)
ui: show file name, last commit message, and when it was updated
Author: Oleksandr Smirnov olexsmir@gmail.com
Committed at: 2026-03-03 19:29:41 +0200
Authored at: 2026-03-02 23:38:56 +0200
Change ID: vvswoqyoytpyyluymkykwzqsvnvvkrzs
Parent: 3d3163c
M internal/git/repo.go
路路路
        186
        186
         	return newCommit(c), nil

      
        187
        187
         }

      
        188
        188
         

      
        
        189
        +func (g *Repo) lastCommitForFile(filepath string) (*Commit, error) {

      
        
        190
        +	iter, err := g.r.Log(&git.LogOptions{

      
        
        191
        +		From:  g.h,

      
        
        192
        +		Order: git.LogOrderCommitterTime,

      
        
        193
        +	})

      
        
        194
        +	if err != nil {

      
        
        195
        +		return nil, fmt.Errorf("failed to log: %w", err)

      
        
        196
        +	}

      
        
        197
        +	defer iter.Close()

      
        
        198
        +

      
        
        199
        +	var prevHash plumbing.Hash

      
        
        200
        +	var result *object.Commit

      
        
        201
        +	err = iter.ForEach(func(com *object.Commit) error {

      
        
        202
        +		tree, terr := com.Tree()

      
        
        203
        +		if terr != nil {

      
        
        204
        +			return terr

      
        
        205
        +		}

      
        
        206
        +

      
        
        207
        +		var hash plumbing.Hash

      
        
        208
        +		entry, eerr := tree.FindEntry(filepath)

      
        
        209
        +		if eerr == nil {

      
        
        210
        +			hash = entry.Hash

      
        
        211
        +		} else {

      
        
        212
        +			file, ferr := tree.File(filepath)

      
        
        213
        +			if ferr != nil {

      
        
        214
        +				return storer.ErrStop

      
        
        215
        +			}

      
        
        216
        +			hash = file.Hash

      
        
        217
        +		}

      
        
        218
        +

      
        
        219
        +		if hash != prevHash {

      
        
        220
        +			result = com

      
        
        221
        +			prevHash = hash

      
        
        222
        +		}

      
        
        223
        +		return nil

      
        
        224
        +	})

      
        
        225
        +

      
        
        226
        +	if !errors.Is(err, storer.ErrStop) && err != nil {

      
        
        227
        +		return nil, fmt.Errorf("failed to walk commits: %w", err)

      
        
        228
        +	}

      
        
        229
        +	if result == nil {

      
        
        230
        +		return nil, fmt.Errorf("no commits found for path: %s", filepath)

      
        
        231
        +	}

      
        
        232
        +	return newCommit(result), nil

      
        
        233
        +}

      
        
        234
        +

      
        189
        235
         type Branch struct {

      
        190
        236
         	Name       string

      
        191
        237
         	LastUpdate time.Time

      
M internal/git/tree.go
路路路
        5
        5
         	"fmt"

      
        6
        6
         	"io"

      
        7
        7
         	"mime"

      
        
        8
        +	"path"

      
        8
        9
         	"path/filepath"

      
        9
        10
         	"strings"

      
        10
        11
         

      路路路
        12
        13
         )

      
        13
        14
         

      
        14
        15
         type NiceTree struct {

      
        15
        
        -	Name      string

      
        16
        
        -	Mode      string

      
        17
        
        -	Size      int64

      
        18
        
        -	IsFile    bool

      
        19
        
        -	IsSubtree bool

      
        
        16
        +	IsFile bool

      
        
        17
        +	Name   string

      
        
        18
        +	Commit *Commit

      
        
        19
        +	Mode   string

      
        
        20
        +	Size   int64

      
        20
        21
         }

      
        21
        22
         

      
        22
        
        -func makeNiceTree(t *object.Tree) []NiceTree {

      
        23
        
        -	nts := []NiceTree{}

      
        
        23
        +func (g *Repo) makeNiceTree(t *object.Tree, parent string) []NiceTree {

      
        
        24
        +	var nts []NiceTree

      
        24
        25
         	for _, e := range t.Entries {

      
        25
        26
         		mode, _ := e.Mode.ToOSFileMode()

      
        26
        27
         		sz, _ := t.Size(e.Name)

      
        
        28
        +

      
        
        29
        +		// TODO: this should be cached, its pretty expensive

      
        
        30
        +		lc, _ := g.lastCommitForFile(path.Join(parent, e.Name))

      
        27
        31
         		nts = append(nts, NiceTree{

      
        28
        32
         			Name:   e.Name,

      
        29
        33
         			Mode:   mode.String(),

      
        30
        34
         			IsFile: e.Mode.IsFile(),

      
        
        35
        +			Commit: lc,

      
        31
        36
         			Size:   sz,

      
        32
        37
         		})

      
        33
        38
         	}

      路路路
        40
        45
         		return nil, fmt.Errorf("commit object: %w", err)

      
        41
        46
         	}

      
        42
        47
         

      
        43
        
        -	files := []NiceTree{}

      
        44
        48
         	tree, err := c.Tree()

      
        45
        49
         	if err != nil {

      
        46
        50
         		return nil, fmt.Errorf("file tree: %w", err)

      
        47
        51
         	}

      
        48
        52
         

      
        
        53
        +	var files []NiceTree

      
        49
        54
         	if path == "" {

      
        50
        
        -		files = makeNiceTree(tree)

      
        
        55
        +		files = g.makeNiceTree(tree, path)

      
        51
        56
         	} else {

      
        52
        57
         		o, err := tree.FindEntry(path)

      
        53
        58
         		if err != nil {

      路路路
        59
        64
         			if err != nil {

      
        60
        65
         				return nil, err

      
        61
        66
         			}

      
        62
        
        -			files = makeNiceTree(subtree)

      
        
        67
        +			files = g.makeNiceTree(subtree, path)

      
        63
        68
         		}

      
        64
        69
         	}

      
        65
        70
         

      路路路
        117
        122
         	isBin, _ := file.IsBinary()

      
        118
        123
         	mimeType := mime.TypeByExtension(filepath.Ext(path))

      
        119
        124
         	if mimeType == "" {

      
        
        125
        +		mimeType = "text/plain"

      
        120
        126
         		if isBin {

      
        121
        127
         			mimeType = "application/octet-stream"

      
        122
        
        -		} else {

      
        123
        
        -			mimeType = "text/plain"

      
        124
        128
         		}

      
        125
        129
         	}

      
        126
        130
         

      
M web/templates/repo_tree.html
路路路
        14
        14
               <table class="table tree">

      
        15
        15
                 <thead>

      
        16
        16
                   <tr class="nohover">

      
        17
        
        -            <th class="mode nowrap">mode</th>

      
        18
        
        -            <th class="size nowrap">size</th>

      
        19
        
        -            <th class="fill">name</th>

      
        
        17
        +            <th class="nowrap">name</th>

      
        
        18
        +            <th class="fill">last commit</th>

      
        
        19
        +            <th class="nowrap">last update</th>

      
        20
        20
                   </tr>

      
        21
        21
                 </thead>

      
        22
        22
                 <tbody>

      
        23
        23
                   {{ if $parent }}

      
        24
        24
                   <tr>

      
        25
        
        -            <td class="mode nowrap"></td>

      
        26
        
        -            <td class="size nowrap"></td>

      
        27
        
        -            <td class="fill"><a href="/{{ $name }}/tree/{{ urlencode $ref }}/{{ .P.DotDot }}">..</a></td>

      
        
        25
        +            <td class="nowrap"><a href="/{{ $name }}/tree/{{ urlencode $ref }}/{{ .P.DotDot }}">..</a></td>

      
        
        26
        +            <th class="fill"></th>

      
        
        27
        +            <th class="nowrap"></th>

      
        28
        28
                   </tr>

      
        29
        29
                   {{ end }}

      
        30
        30
         

      
        31
        31
                   {{ range .P.Tree }}

      
        32
        32
                   {{ if not .IsFile }}

      
        33
        33
                   <tr>

      
        34
        
        -            <td class="mode nowrap">{{ .Mode }}</td>

      
        35
        
        -            <td class="size nowrap">{{ .Size }}</td>

      
        
        34
        +            <td class="nowrap">

      
        
        35
        +              {{ if $parent }}<a href="/{{ $name}}/tree/{{ urlencode $ref }}/{{ $parent }}/{{ .Name }}">{{ .Name }}/</a>

      
        
        36
        +              {{ else }}<a href="/{{ $name }}/tree/{{ urlencode $ref }}/{{ .Name }}">{{ .Name }}/</a>{{ end }}

      
        
        37
        +            </td>

      
        36
        38
                     <td class="fill">

      
        37
        
        -              {{ if $parent }}

      
        38
        
        -              <a href="/{{ $name}}/tree/{{ urlencode $ref }}/{{ $parent }}/{{ .Name }}">{{ .Name }}/</a>

      
        39
        
        -              {{ else }}

      
        40
        
        -              <a href="/{{ $name }}/tree/{{ urlencode $ref }}/{{ .Name }}">{{ .Name }}/</a>

      
        41
        
        -              {{ end }}

      
        
        39
        +              <a href="/{{ $name }}/commit/{{ .Commit.Hash }}">{{ commitSummary .Commit.Message }}</a>

      
        
        40
        +            </td>

      
        
        41
        +            <td class="age nowrap">

      
        
        42
        +              <a href="/{{ $.RepoName }}/commit/{{ .Commit.Hash }}">{{ humanizeRelTime .Commit.Committed }}</a>

      
        
        43
        +              <span class="tooltip" role="tooltip">{{ humanizeTime .Commit.Committed }}</span>

      
        42
        44
                     </td>

      
        43
        45
                   </tr>

      
        44
        46
                   {{ end }}

      路路路
        47
        49
                   {{ range .P.Tree }}

      
        48
        50
                   {{ if .IsFile }}

      
        49
        51
                   <tr>

      
        50
        
        -            <td class="mode nowrap">{{ .Mode }}</td>

      
        51
        
        -            <td class="size nowrap">{{ .Size }}</td>

      
        
        52
        +            <td class="nowrap">

      
        
        53
        +              {{ if $parent }}<a href="/{{ $name }}/blob/{{ urlencode $ref }}/{{ $parent }}/{{ .Name }}">{{ .Name }}</a>

      
        
        54
        +              {{ else }}<a href="/{{ $name }}/blob/{{ urlencode $ref }}/{{ .Name }}">{{ .Name }}</a>{{ end }}

      
        
        55
        +            </td>

      
        52
        56
                     <td class="fill">

      
        53
        
        -              {{ if $parent }}

      
        54
        
        -              <a href="/{{ $name }}/blob/{{ urlencode $ref }}/{{ $parent }}/{{ .Name }}">{{ .Name }}</a>

      
        55
        
        -              {{ else }}

      
        56
        
        -              <a href="/{{ $name }}/blob/{{ urlencode $ref }}/{{ .Name }}">{{ .Name }}</a>

      
        57
        
        -              {{ end }}

      
        
        57
        +              <a href="/{{ $.RepoName }}/commit/{{ .Commit.Hash }}">{{ commitSummary .Commit.Message }}</a>

      
        
        58
        +            </td>

      
        
        59
        +            <td class="age nowrap">

      
        
        60
        +              <a href="/{{ $.RepoName }}/commit/{{ .Commit.Hash }}">{{ humanizeRelTime .Commit.Committed }}</a>

      
        
        61
        +              <span class="tooltip" role="tooltip">{{ humanizeTime .Commit.Committed }}</span>

      
        58
        62
                     </td>

      
        59
        63
                   </tr>

      
        60
        64
                   {{ end }}