Lindenii Project Forge
Login

server

Lindenii Forge’s main backend daemon

Warning: Due to various recent migrations, viewing non-HEAD refs may be broken.

/forged/internal/ipc/git2c/extra.go (raw)

// SPDX-License-Identifier: AGPL-3.0-only
// SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org>

package git2c

import (
	"encoding/hex"
	"fmt"
	"time"
)

type DiffChunk struct {
	Op      uint64
	Content string
}

type FileDiff struct {
	FromMode uint64
	ToMode   uint64
	FromPath string
	ToPath   string
	Chunks   []DiffChunk
}

type CommitInfo struct {
	Hash           string
	AuthorName     string
	AuthorEmail    string
	AuthorWhen     int64 // unix secs
	AuthorTZMin    int64 // minutes ofs
	CommitterName  string
	CommitterEmail string
	CommitterWhen  int64
	CommitterTZMin int64
	Message        string
	Parents        []string // hex
	Files          []FileDiff
}

func (c *Client) ResolveRef(repoPath, refType, refName string) (string, error) {
	if err := c.writer.WriteData([]byte(repoPath)); err != nil {
		return "", fmt.Errorf("sending repo path failed: %w", err)
	}
	if err := c.writer.WriteUint(3); err != nil {
		return "", fmt.Errorf("sending command failed: %w", err)
	}
	if err := c.writer.WriteData([]byte(refType)); err != nil {
		return "", fmt.Errorf("sending ref type failed: %w", err)
	}
	if err := c.writer.WriteData([]byte(refName)); err != nil {
		return "", fmt.Errorf("sending ref name failed: %w", err)
	}

	status, err := c.reader.ReadUint()
	if err != nil {
		return "", fmt.Errorf("reading status failed: %w", err)
	}
	if status != 0 {
		return "", Perror(status)
	}
	id, err := c.reader.ReadData()
	if err != nil {
		return "", fmt.Errorf("reading oid failed: %w", err)
	}
	return hex.EncodeToString(id), nil
}

func (c *Client) ListBranches(repoPath string) ([]string, error) {
	if err := c.writer.WriteData([]byte(repoPath)); err != nil {
		return nil, fmt.Errorf("sending repo path failed: %w", err)
	}
	if err := c.writer.WriteUint(4); err != nil {
		return nil, fmt.Errorf("sending command failed: %w", err)
	}
	status, err := c.reader.ReadUint()
	if err != nil {
		return nil, fmt.Errorf("reading status failed: %w", err)
	}
	if status != 0 {
		return nil, Perror(status)
	}
	count, err := c.reader.ReadUint()
	if err != nil {
		return nil, fmt.Errorf("reading count failed: %w", err)
	}
	branches := make([]string, 0, count)
	for range count {
		name, err := c.reader.ReadData()
		if err != nil {
			return nil, fmt.Errorf("reading branch name failed: %w", err)
		}
		branches = append(branches, string(name))
	}
	return branches, nil
}

func (c *Client) FormatPatch(repoPath, commitHex string) (string, error) {
	if err := c.writer.WriteData([]byte(repoPath)); err != nil {
		return "", fmt.Errorf("sending repo path failed: %w", err)
	}
	if err := c.writer.WriteUint(5); err != nil {
		return "", fmt.Errorf("sending command failed: %w", err)
	}
	if err := c.writer.WriteData([]byte(commitHex)); err != nil {
		return "", fmt.Errorf("sending commit failed: %w", err)
	}
	status, err := c.reader.ReadUint()
	if err != nil {
		return "", fmt.Errorf("reading status failed: %w", err)
	}
	if status != 0 {
		return "", Perror(status)
	}
	buf, err := c.reader.ReadData()
	if err != nil {
		return "", fmt.Errorf("reading patch failed: %w", err)
	}
	return string(buf), nil
}

func (c *Client) MergeBase(repoPath, hexA, hexB string) (string, error) {
	if err := c.writer.WriteData([]byte(repoPath)); err != nil {
		return "", fmt.Errorf("sending repo path failed: %w", err)
	}
	if err := c.writer.WriteUint(7); err != nil {
		return "", fmt.Errorf("sending command failed: %w", err)
	}
	if err := c.writer.WriteData([]byte(hexA)); err != nil {
		return "", fmt.Errorf("sending oid A failed: %w", err)
	}
	if err := c.writer.WriteData([]byte(hexB)); err != nil {
		return "", fmt.Errorf("sending oid B failed: %w", err)
	}
	status, err := c.reader.ReadUint()
	if err != nil {
		return "", fmt.Errorf("reading status failed: %w", err)
	}
	if status != 0 {
		return "", Perror(status)
	}
	base, err := c.reader.ReadData()
	if err != nil {
		return "", fmt.Errorf("reading base oid failed: %w", err)
	}
	return hex.EncodeToString(base), nil
}

func (c *Client) Log(repoPath, refSpec string, n uint) ([]Commit, error) {
	if err := c.writer.WriteData([]byte(repoPath)); err != nil {
		return nil, fmt.Errorf("sending repo path failed: %w", err)
	}
	if err := c.writer.WriteUint(8); err != nil {
		return nil, fmt.Errorf("sending command failed: %w", err)
	}
	if err := c.writer.WriteData([]byte(refSpec)); err != nil {
		return nil, fmt.Errorf("sending refspec failed: %w", err)
	}
	if err := c.writer.WriteUint(uint64(n)); err != nil {
		return nil, fmt.Errorf("sending limit failed: %w", err)
	}
	status, err := c.reader.ReadUint()
	if err != nil {
		return nil, fmt.Errorf("reading status failed: %w", err)
	}
	if status != 0 {
		return nil, Perror(status)
	}
	var out []Commit
	for {
		id, err := c.reader.ReadData()
		if err != nil {
			break
		}
		title, _ := c.reader.ReadData()
		authorName, _ := c.reader.ReadData()
		authorEmail, _ := c.reader.ReadData()
		date, _ := c.reader.ReadData()
		out = append(out, Commit{
			Hash:    hex.EncodeToString(id),
			Author:  string(authorName),
			Email:   string(authorEmail),
			Date:    string(date),
			Message: string(title),
		})
	}
	return out, nil
}

func (c *Client) CommitTreeOID(repoPath, commitHex string) (string, error) {
	if err := c.writer.WriteData([]byte(repoPath)); err != nil {
		return "", fmt.Errorf("sending repo path failed: %w", err)
	}
	if err := c.writer.WriteUint(12); err != nil {
		return "", fmt.Errorf("sending command failed: %w", err)
	}
	if err := c.writer.WriteData([]byte(commitHex)); err != nil {
		return "", fmt.Errorf("sending oid failed: %w", err)
	}
	status, err := c.reader.ReadUint()
	if err != nil {
		return "", fmt.Errorf("reading status failed: %w", err)
	}
	if status != 0 {
		return "", Perror(status)
	}
	id, err := c.reader.ReadData()
	if err != nil {
		return "", fmt.Errorf("reading tree oid failed: %w", err)
	}
	return hex.EncodeToString(id), nil
}

func (c *Client) CommitCreate(repoPath, treeHex string, parents []string, authorName, authorEmail string, when time.Time, message string) (string, error) {
	if err := c.writer.WriteData([]byte(repoPath)); err != nil {
		return "", fmt.Errorf("sending repo path failed: %w", err)
	}
	if err := c.writer.WriteUint(13); err != nil {
		return "", fmt.Errorf("sending command failed: %w", err)
	}
	if err := c.writer.WriteData([]byte(treeHex)); err != nil {
		return "", fmt.Errorf("sending tree oid failed: %w", err)
	}
	if err := c.writer.WriteUint(uint64(len(parents))); err != nil {
		return "", fmt.Errorf("sending parents count failed: %w", err)
	}
	for _, p := range parents {
		if err := c.writer.WriteData([]byte(p)); err != nil {
			return "", fmt.Errorf("sending parent oid failed: %w", err)
		}
	}
	if err := c.writer.WriteData([]byte(authorName)); err != nil {
		return "", fmt.Errorf("sending author name failed: %w", err)
	}
	if err := c.writer.WriteData([]byte(authorEmail)); err != nil {
		return "", fmt.Errorf("sending author email failed: %w", err)
	}
	if err := c.writer.WriteInt(when.Unix()); err != nil {
		return "", fmt.Errorf("sending when failed: %w", err)
	}
	_, offset := when.Zone()
	if err := c.writer.WriteInt(int64(offset / 60)); err != nil {
		return "", fmt.Errorf("sending tz offset failed: %w", err)
	}
	if err := c.writer.WriteData([]byte(message)); err != nil {
		return "", fmt.Errorf("sending message failed: %w", err)
	}
	status, err := c.reader.ReadUint()
	if err != nil {
		return "", fmt.Errorf("reading status failed: %w", err)
	}
	if status != 0 {
		return "", Perror(status)
	}
	id, err := c.reader.ReadData()
	if err != nil {
		return "", fmt.Errorf("reading commit oid failed: %w", err)
	}
	return hex.EncodeToString(id), nil
}

func (c *Client) UpdateRef(repoPath, refName, commitHex string) error {
	if err := c.writer.WriteData([]byte(repoPath)); err != nil {
		return fmt.Errorf("sending repo path failed: %w", err)
	}
	if err := c.writer.WriteUint(14); err != nil {
		return fmt.Errorf("sending command failed: %w", err)
	}
	if err := c.writer.WriteData([]byte(refName)); err != nil {
		return fmt.Errorf("sending ref name failed: %w", err)
	}
	if err := c.writer.WriteData([]byte(commitHex)); err != nil {
		return fmt.Errorf("sending commit oid failed: %w", err)
	}
	status, err := c.reader.ReadUint()
	if err != nil {
		return fmt.Errorf("reading status failed: %w", err)
	}
	if status != 0 {
		return Perror(status)
	}
	return nil
}

func (c *Client) CommitInfo(repoPath, commitHex string) (*CommitInfo, error) {
	if err := c.writer.WriteData([]byte(repoPath)); err != nil {
		return nil, fmt.Errorf("sending repo path failed: %w", err)
	}
	if err := c.writer.WriteUint(6); err != nil {
		return nil, fmt.Errorf("sending command failed: %w", err)
	}
	if err := c.writer.WriteData([]byte(commitHex)); err != nil {
		return nil, fmt.Errorf("sending commit failed: %w", err)
	}
	status, err := c.reader.ReadUint()
	if err != nil {
		return nil, fmt.Errorf("reading status failed: %w", err)
	}
	if status != 0 {
		return nil, Perror(status)
	}
	id, err := c.reader.ReadData()
	if err != nil {
		return nil, fmt.Errorf("reading id failed: %w", err)
	}
	aname, err := c.reader.ReadData()
	if err != nil {
		return nil, fmt.Errorf("reading author name failed: %w", err)
	}
	aemail, err := c.reader.ReadData()
	if err != nil {
		return nil, fmt.Errorf("reading author email failed: %w", err)
	}
	awhen, err := c.reader.ReadI64()
	if err != nil {
		return nil, fmt.Errorf("reading author time failed: %w", err)
	}
	aoff, err := c.reader.ReadI64()
	if err != nil {
		return nil, fmt.Errorf("reading author tz failed: %w", err)
	}
	cname, err := c.reader.ReadData()
	if err != nil {
		return nil, fmt.Errorf("reading committer name failed: %w", err)
	}
	cemail, err := c.reader.ReadData()
	if err != nil {
		return nil, fmt.Errorf("reading committer email failed: %w", err)
	}
	cwhen, err := c.reader.ReadI64()
	if err != nil {
		return nil, fmt.Errorf("reading committer time failed: %w", err)
	}
	coff, err := c.reader.ReadI64()
	if err != nil {
		return nil, fmt.Errorf("reading committer tz failed: %w", err)
	}
	msg, err := c.reader.ReadData()
	if err != nil {
		return nil, fmt.Errorf("reading message failed: %w", err)
	}
	pcnt, err := c.reader.ReadUint()
	if err != nil {
		return nil, fmt.Errorf("reading parents count failed: %w", err)
	}
	parents := make([]string, 0, pcnt)
	for i := uint64(0); i < pcnt; i++ {
		praw, perr := c.reader.ReadData()
		if perr != nil {
			return nil, fmt.Errorf("reading parent failed: %w", perr)
		}
		parents = append(parents, hex.EncodeToString(praw))
	}
	fcnt, err := c.reader.ReadUint()
	if err != nil {
		return nil, fmt.Errorf("reading file count failed: %w", err)
	}
	files := make([]FileDiff, 0, fcnt)
	for i := uint64(0); i < fcnt; i++ {
		fromMode, err := c.reader.ReadUint()
		if err != nil {
			return nil, fmt.Errorf("reading from mode failed: %w", err)
		}
		toMode, err := c.reader.ReadUint()
		if err != nil {
			return nil, fmt.Errorf("reading to mode failed: %w", err)
		}
		fromPath, err := c.reader.ReadData()
		if err != nil {
			return nil, fmt.Errorf("reading from path failed: %w", err)
		}
		toPath, err := c.reader.ReadData()
		if err != nil {
			return nil, fmt.Errorf("reading to path failed: %w", err)
		}
		ccnt, err := c.reader.ReadUint()
		if err != nil {
			return nil, fmt.Errorf("reading chunk count failed: %w", err)
		}
		chunks := make([]DiffChunk, 0, ccnt)
		for j := uint64(0); j < ccnt; j++ {
			op, err := c.reader.ReadUint()
			if err != nil {
				return nil, fmt.Errorf("reading chunk op failed: %w", err)
			}
			content, err := c.reader.ReadData()
			if err != nil {
				return nil, fmt.Errorf("reading chunk content failed: %w", err)
			}
			chunks = append(chunks, DiffChunk{Op: op, Content: string(content)})
		}
		files = append(files, FileDiff{
			FromMode: fromMode,
			ToMode:   toMode,
			FromPath: string(fromPath),
			ToPath:   string(toPath),
			Chunks:   chunks,
		})
	}
	return &CommitInfo{
		Hash:           hex.EncodeToString(id),
		AuthorName:     string(aname),
		AuthorEmail:    string(aemail),
		AuthorWhen:     awhen,
		AuthorTZMin:    aoff,
		CommitterName:  string(cname),
		CommitterEmail: string(cemail),
		CommitterWhen:  cwhen,
		CommitterTZMin: coff,
		Message:        string(msg),
		Parents:        parents,
		Files:          files,
	}, nil
}