all repos

mugit @ e2cbacb

馃惍 git server that your cow will love
3 files changed, 152 insertions(+), 64 deletions(-)
git: get last commit from git cli, improves performance

also thanks tangled.org for code, MIT you know
Author: Oleksandr Smirnov olexsmir@gmail.com
Committed at: 2026-03-09 16:24:32 +0200
Authored at: 2026-03-08 13:08:14 +0200
Change ID: pllsurrkqkpuqttuzrumoxkmkwywwlww
Parent: 4ca522b
M internal/git/external.go
路路路
        33
        33
         	cmd.Stderr = opts.Stderr

      
        34
        34
         	return cmd.Run()

      
        35
        35
         }

      
        
        36
        +

      
        
        37
        +func (g *Repo) streamingGitLog(ctx context.Context, extraArgs ...string) (io.ReadCloser, error) {

      
        
        38
        +	args := []string{"log", g.h.String()}

      
        
        39
        +	args = append(args, extraArgs...)

      
        
        40
        +

      
        
        41
        +	cmd := exec.CommandContext(ctx, "git", args...)

      
        
        42
        +	cmd.Dir = g.path

      
        
        43
        +

      
        
        44
        +	stdout, err := cmd.StdoutPipe()

      
        
        45
        +	if err != nil {

      
        
        46
        +		return nil, err

      
        
        47
        +	}

      
        
        48
        +

      
        
        49
        +	if err := cmd.Start(); err != nil {

      
        
        50
        +		return nil, err

      
        
        51
        +	}

      
        
        52
        +

      
        
        53
        +	return &processReader{

      
        
        54
        +		Reader: stdout,

      
        
        55
        +		cmd:    cmd,

      
        
        56
        +		stdout: stdout,

      
        
        57
        +	}, nil

      
        
        58
        +}

      
        
        59
        +

      
        
        60
        +// processReader wraps a reader and ensures the associated process is cleaned up

      
        
        61
        +type processReader struct {

      
        
        62
        +	io.Reader

      
        
        63
        +	cmd    *exec.Cmd

      
        
        64
        +	stdout io.ReadCloser

      
        
        65
        +}

      
        
        66
        +

      
        
        67
        +func (pr *processReader) Close() error {

      
        
        68
        +	if err := pr.stdout.Close(); err != nil {

      
        
        69
        +		return err

      
        
        70
        +	}

      
        
        71
        +	return pr.cmd.Wait()

      
        
        72
        +}

      
M internal/git/repo.go
路路路
        192
        192
         	return newCommit(c), nil

      
        193
        193
         }

      
        194
        194
         

      
        195
        
        -// lastCommitForFilesInTree ...

      
        196
        
        -// TODO: at the moment it doesn't work well with merges, ideally i would "shell" out it,

      
        197
        
        -// `git log --pretty:format:%H,%ad,%s --date=iso --name-only -- g.path`

      
        198
        
        -func (g *Repo) lastCommitForFilesInTree(tree *object.Tree, dirPath string) (map[string]*Commit, error) {

      
        199
        
        -	log, err := g.r.Log(&git.LogOptions{From: g.h})

      
        200
        
        -	if err != nil {

      
        201
        
        -		return nil, err

      
        202
        
        -	}

      
        203
        
        -

      
        204
        
        -	result := make(map[string]*Commit)

      
        205
        
        -	err = log.ForEach(func(c *object.Commit) error {

      
        206
        
        -		if c.NumParents() == 0 {

      
        207
        
        -			for _, entry := range tree.Entries {

      
        208
        
        -				if _, seen := result[entry.Name]; !seen {

      
        209
        
        -					result[entry.Name] = newCommit(c)

      
        210
        
        -				}

      
        211
        
        -			}

      
        212
        
        -			return storer.ErrStop

      
        213
        
        -		}

      
        214
        
        -

      
        215
        
        -		// skip merge commits

      
        216
        
        -		if c.NumParents() > 1 {

      
        217
        
        -			return nil

      
        218
        
        -		}

      
        219
        
        -

      
        220
        
        -		parent, perr := c.Parent(0)

      
        221
        
        -		if perr != nil {

      
        222
        
        -			return perr

      
        223
        
        -		}

      
        224
        
        -

      
        225
        
        -		patch, perr := parent.Patch(c)

      
        226
        
        -		if perr != nil {

      
        227
        
        -			return perr

      
        228
        
        -		}

      
        229
        
        -

      
        230
        
        -		for _, fp := range patch.FilePatches() {

      
        231
        
        -			from, to := fp.Files()

      
        232
        
        -

      
        233
        
        -			var affectedPath string

      
        234
        
        -			if to != nil {

      
        235
        
        -				affectedPath = to.Path()

      
        236
        
        -			} else if from != nil {

      
        237
        
        -				affectedPath = from.Path()

      
        238
        
        -			}

      
        239
        
        -

      
        240
        
        -			name := topLevelEntry(affectedPath, dirPath)

      
        241
        
        -			if name == "" {

      
        242
        
        -				continue

      
        243
        
        -			}

      
        244
        
        -

      
        245
        
        -			if _, seen := result[name]; !seen {

      
        246
        
        -				result[name] = newCommit(c)

      
        247
        
        -			}

      
        248
        
        -		}

      
        249
        
        -		return nil

      
        250
        
        -	})

      
        251
        
        -	if err != nil && !errors.Is(err, storer.ErrStop) {

      
        252
        
        -		return nil, err

      
        253
        
        -	}

      
        254
        
        -	return result, nil

      
        255
        
        -}

      
        256
        
        -

      
        257
        195
         type Branch struct {

      
        258
        196
         	Name       string

      
        259
        197
         	LastUpdate time.Time

      
M internal/git/tree.go
路路路
        1
        1
         package git

      
        2
        2
         

      
        3
        3
         import (

      
        
        4
        +	"bufio"

      
        
        5
        +	"context"

      
        4
        6
         	"errors"

      
        5
        7
         	"fmt"

      
        6
        8
         	"io"

      
        7
        9
         	"mime"

      
        
        10
        +	"path"

      
        8
        11
         	"path/filepath"

      
        9
        12
         	"strings"

      
        
        13
        +	"time"

      
        10
        14
         

      
        
        15
        +	"github.com/go-git/go-git/v5/plumbing"

      
        11
        16
         	"github.com/go-git/go-git/v5/plumbing/object"

      
        12
        17
         )

      
        13
        18
         

      路路路
        22
        27
         func (g *Repo) makeNiceTree(t *object.Tree, parent string) []NiceTree {

      
        23
        28
         	var nts []NiceTree

      
        24
        29
         

      
        25
        
        -	cms, err := g.lastCommitForFilesInTree(t, parent)

      
        
        30
        +	ctx, cancel := context.WithTimeout(context.TODO(), 2*time.Second)

      
        
        31
        +	defer cancel()

      
        
        32
        +

      
        
        33
        +	cms, err := g.lastCommitForFilesInTree(ctx, t, parent)

      
        26
        34
         	if err != nil {

      
        27
        35
         		return nts

      
        28
        36
         	}

      
        29
        37
         

      
        30
        38
         	for _, e := range t.Entries {

      
        
        39
        +		fpath := path.Join(parent, e.Name)

      
        31
        40
         		mode, _ := e.Mode.ToOSFileMode()

      
        32
        41
         		sz, _ := t.Size(e.Name)

      
        33
        42
         		nts = append(nts, NiceTree{

      
        
        43
        +			Commit: cms[fpath],

      
        34
        44
         			Name:   e.Name,

      
        35
        45
         			Mode:   mode.String(),

      
        36
        46
         			IsFile: e.Mode.IsFile(),

      
        37
        
        -			Commit: cms[e.Name],

      
        38
        47
         			Size:   sz,

      
        39
        48
         		})

      
        40
        49
         	}

      路路路
        135
        144
         		Size:     file.Size,

      
        136
        145
         	}, nil

      
        137
        146
         }

      
        
        147
        +

      
        
        148
        +type logCommit struct {

      
        
        149
        +	Commit

      
        
        150
        +	hash  plumbing.Hash

      
        
        151
        +	files []string

      
        
        152
        +}

      
        
        153
        +

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

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

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

      
        
        157
        +	for _, e := range subtree.Entries {

      
        
        158
        +		fpath := path.Clean(path.Join(parent, e.Name))

      
        
        159
        +		filesToDo[fpath] = struct{}{}

      
        
        160
        +	}

      
        
        161
        +

      
        
        162
        +	if len(filesToDo) == 0 {

      
        
        163
        +		return filesDone, nil

      
        
        164
        +	}

      
        
        165
        +

      
        
        166
        +	ctx, cancel := context.WithCancel(ctx)

      
        
        167
        +	defer cancel()

      
        
        168
        +

      
        
        169
        +	pathSpec := "."

      
        
        170
        +	if parent != "" {

      
        
        171
        +		pathSpec = parent

      
        
        172
        +	}

      
        
        173
        +

      
        
        174
        +	output, err := g.streamingGitLog(ctx, "--pretty=format:%H,%ad,%ae,%an,%ce,%cn,%s", "--date=iso", "--name-only", "--", pathSpec)

      
        
        175
        +	if err != nil {

      
        
        176
        +		return nil, err

      
        
        177
        +	}

      
        
        178
        +	defer output.Close() // Ensure the git process is properly cleaned up

      
        
        179
        +

      
        
        180
        +	var current logCommit

      
        
        181
        +	reader := bufio.NewReader(output)

      
        
        182
        +	for {

      
        
        183
        +		line, err := reader.ReadString('\n')

      
        
        184
        +		if err != nil && err != io.EOF {

      
        
        185
        +			return nil, err

      
        
        186
        +		}

      
        
        187
        +

      
        
        188
        +		line = strings.TrimSpace(line)

      
        
        189
        +		fmt.Println("LINE", line)

      
        
        190
        +

      
        
        191
        +		if line == "" {

      
        
        192
        +			if !current.hash.IsZero() {

      
        
        193
        +				c := current.Commit

      
        
        194
        +				// we have a fully parsed commit

      
        
        195
        +				for _, f := range current.files {

      
        
        196
        +					if _, ok := filesToDo[f]; ok {

      
        
        197
        +						filesDone[f] = &c

      
        
        198
        +						delete(filesToDo, f)

      
        
        199
        +					}

      
        
        200
        +				}

      
        
        201
        +

      
        
        202
        +				if len(filesToDo) == 0 {

      
        
        203
        +					cancel()

      
        
        204
        +					break

      
        
        205
        +				}

      
        
        206
        +

      
        
        207
        +				current = logCommit{}

      
        
        208
        +			}

      
        
        209
        +		} else if current.hash.IsZero() {

      
        
        210
        +			parts := strings.SplitN(line, ",", 7)

      
        
        211
        +			if len(parts) == 7 {

      
        
        212
        +				current.hash = plumbing.NewHash(parts[0])

      
        
        213
        +

      
        
        214
        +				// NOTE: this is copy-paste of [newCommit]

      
        
        215
        +				current.Hash = parts[0]

      
        
        216
        +				current.HashShort = parts[0][:7]

      
        
        217
        +				current.Committed, _ = time.Parse("2006-01-02 15:04:05 -0700", parts[1])

      
        
        218
        +				current.AuthorEmail = parts[2]

      
        
        219
        +				current.AuthorName = parts[3]

      
        
        220
        +				current.CommitterEmail = parts[4]

      
        
        221
        +				current.CommitterName = parts[5]

      
        
        222
        +				current.Message = parts[6]

      
        
        223
        +			}

      
        
        224
        +		} else {

      
        
        225
        +			// all ancestors along this path should also be included

      
        
        226
        +			file := path.Clean(line)

      
        
        227
        +			ancestors := ancestors(file)

      
        
        228
        +			current.files = append(current.files, file)

      
        
        229
        +			current.files = append(current.files, ancestors...)

      
        
        230
        +		}

      
        
        231
        +

      
        
        232
        +		if err == io.EOF {

      
        
        233
        +			break

      
        
        234
        +		}

      
        
        235
        +	}

      
        
        236
        +

      
        
        237
        +	return filesDone, nil

      
        
        238
        +}

      
        
        239
        +

      
        
        240
        +func ancestors(p string) []string {

      
        
        241
        +	var ancestors []string

      
        
        242
        +	for {

      
        
        243
        +		p = path.Dir(p)

      
        
        244
        +		if p == "." || p == "/" {

      
        
        245
        +			break

      
        
        246
        +		}

      
        
        247
        +		ancestors = append(ancestors, p)

      
        
        248
        +	}

      
        
        249
        +	return ancestors

      
        
        250
        +}