package replica import ( "errors" "fmt" "io" "time" "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/filemode" "github.com/go-git/go-git/v5/plumbing/object" "github.com/go-git/go-git/v5/plumbing/storer" ) const ( FileName = "userbase.age" Message = "userbase update\n" AuthorName = "tug" AuthorEmail = "tug@userbase.invalid" ) var ( ErrParentMissing = errors.New("replica: parent commit is not in the local repository") ErrNotExport = errors.New("replica: commit does not hold exactly one userbase.age file") ) func Author(when time.Time) object.Signature { return object.Signature{Name: AuthorName, Email: AuthorEmail, When: when.UTC().Truncate(time.Second)} } func storeObject(s storer.EncodedObjectStorer, encode func(plumbing.EncodedObject) error) (plumbing.Hash, error) { obj := s.NewEncodedObject() if err := encode(obj); err != nil { return plumbing.ZeroHash, err } return s.SetEncodedObject(obj) } func CommitFile(repo *git.Repository, branch plumbing.ReferenceName, parent plumbing.Hash, content []byte, when time.Time, signer git.Signer) (plumbing.Hash, error) { s := repo.Storer commit := &object.Commit{ Author: Author(when), Committer: Author(when), Message: Message, } if !parent.IsZero() { if _, err := object.GetCommit(s, parent); err != nil { return plumbing.ZeroHash, fmt.Errorf("%w: %s: %v", ErrParentMissing, parent, err) } commit.ParentHashes = []plumbing.Hash{parent} } blob, err := storeObject(s, func(o plumbing.EncodedObject) error { o.SetType(plumbing.BlobObject) w, err := o.Writer() if err != nil { return err } if _, err := w.Write(content); err != nil { return err } return w.Close() }) if err != nil { return plumbing.ZeroHash, err } tree := &object.Tree{Entries: []object.TreeEntry{{Name: FileName, Mode: filemode.Regular, Hash: blob}}} if commit.TreeHash, err = storeObject(s, tree.Encode); err != nil { return plumbing.ZeroHash, err } payload := &plumbing.MemoryObject{} if err := commit.EncodeWithoutSignature(payload); err != nil { return plumbing.ZeroHash, err } r, err := payload.Reader() if err != nil { return plumbing.ZeroHash, err } sig, err := signer.Sign(r) if err != nil { return plumbing.ZeroHash, fmt.Errorf("replica: sign commit: %w", err) } commit.PGPSignature = string(sig) id, err := storeObject(s, commit.Encode) if err != nil { return plumbing.ZeroHash, err } if err := s.SetReference(plumbing.NewHashReference(branch, id)); err != nil { return plumbing.ZeroHash, err } return id, nil } func ReadFile(objects storer.EncodedObjectStorer, id plumbing.Hash) ([]byte, error) { c, err := object.GetCommit(objects, id) if err != nil { return nil, err } tree, err := c.Tree() if err != nil { return nil, err } if len(tree.Entries) != 1 || tree.Entries[0].Name != FileName || tree.Entries[0].Mode != filemode.Regular { return nil, ErrNotExport } blob, err := object.GetBlob(objects, tree.Entries[0].Hash) if err != nil { return nil, err } rd, err := blob.Reader() if err != nil { return nil, err } defer rd.Close() return io.ReadAll(rd) }