--- type: change description: |- Completely refactor naming of everything, in light of new SPEC Writing the SPEC shed some light on just how weakly a lot of concepts, like "commit", had been defined, and prompted the delineation of a lot of things along specific lines (commit vs payload, repo vs project). This commit makes the code reflect the SPEC much better in quite a few ways: * Repo is now Project * Commit is now Payload * GitCommit is now just Commit * Hash is now Fingerprint * A lot of minor fields got renamed * All the XXXInterface types are now just XXX, and their old XXX type is now XXXUnion. More than likely there's still some comments and variable names that have slipped passed, but overall I feel like I got most of the changes. fingerprint: AKkDC5BKhKbfXzZQ/F4KquHeMgVvcNxgLmkZFz/nP/tY credentials: - type: pgp_signature pub_key_id: 95C46FA6A41148AC body: iQIzBAABAgAdFiEEJ6tQKp6olvZKJ0lwlcRvpqQRSKwFAl6l7aYACgkQlcRvpqQRSKxFrA//VQ+f8B6pwGS3ORB4VVBnHvvJTGZvAYTvB0fHuHJx2EreR4FwjhaNakk5ClkwbO7WFMq++2OV4xIkvzwswLdbXZF0IHx3wScQM59v4vIkR4V9Lj5p1aGGhQna52uIKugF2gTqKdU4tqYzmBjDND/c2XDwCN5CwTwwnAHXUSSsHxviiPUYPWV5wzFP7uyRW0ZeK8Isv7QECKRXlsDjcSJa+g+jc091FG/jG9Dkai8fbDbW8YXj7W3ALaXgXWEBJMrgQxZcJJRjgCvLY72FIIrUBquu3FepiyzMtZ0yaIvi4NmGCsYqIv00NcMvMtD7iwhOCZn10Sku4wvaKJ8YBMRduhqC99fnr/ZDW0/HvTNcL7GKx11GjwtmzkJgwsHFPy3zX+kMdF4m3WgtoeI0GwEsBXXZE2C49yAk3Mb/3puegl3a1PPMvOabTzo7Xm6xpWkI6gISChI7My71H3EuKZWhkb+IubPmMvJJXIdVxHnsHPz2dl/BZXLgpfVdEgQa2qWeXtYI4NNm37pLl3gv92V4kka+Kr4gfdoq8mJ7aqvc9was35baJbHg4+fEVJG2Wj+2AQU+ncx3nAFzgYyMxwo9K8VuC4QdfRF4ImyxTnWkuokEn9H6JRrbkBDKIELj6vzdPmsjOUEQ4nsYX66/zSibFD7UvhQmdXFs8Gp8/Qq6g4M= account: mediocregophermain
parent
351048e9aa
commit
b01fe1524a
@ -1,610 +1,222 @@ |
||||
package dehub |
||||
|
||||
import ( |
||||
"bytes" |
||||
"encoding/base64" |
||||
"encoding/hex" |
||||
"errors" |
||||
"fmt" |
||||
"reflect" |
||||
"sort" |
||||
"path/filepath" |
||||
"strings" |
||||
"time" |
||||
|
||||
"dehub.dev/src/dehub.git/accessctl" |
||||
"dehub.dev/src/dehub.git/fs" |
||||
"dehub.dev/src/dehub.git/sigcred" |
||||
"dehub.dev/src/dehub.git/typeobj" |
||||
|
||||
"gopkg.in/src-d/go-git.v4" |
||||
"gopkg.in/src-d/go-git.v4/plumbing" |
||||
"gopkg.in/src-d/go-git.v4/plumbing/object" |
||||
yaml "gopkg.in/yaml.v2" |
||||
) |
||||
|
||||
// CommitInterface describes the methods which must be implemented by the
|
||||
// different commit types. None of the methods should modify the underlying
|
||||
// object.
|
||||
type CommitInterface interface { |
||||
// MessageHead returns the head of the commit message (i.e. the first line).
|
||||
// The CommitCommon of the outer Commit is passed in for added context, if
|
||||
// necessary.
|
||||
MessageHead(CommitCommon) (string, error) |
||||
|
||||
// ExpectedHash returns the raw hash which Signifiers can sign to accredit
|
||||
// this commit. The ChangedFile objects given describe the file changes
|
||||
// between the parent commit and this commit.
|
||||
ExpectedHash([]ChangedFile) ([]byte, error) |
||||
|
||||
// StoredHash returns the signable Hash embedded in the commit, which should
|
||||
// hopefully correspond to the ExpectedHash.
|
||||
StoredHash() []byte |
||||
} |
||||
|
||||
// CommitCommon describes the fields common to all Commit objects.
|
||||
type CommitCommon struct { |
||||
// Credentials represent all created Credentials for this commit, and can be
|
||||
// set on all Commit objects regardless of other fields being set.
|
||||
Credentials []sigcred.Credential `yaml:"credentials"` |
||||
} |
||||
|
||||
func (cc CommitCommon) credIDs() []string { |
||||
m := map[string]struct{}{} |
||||
for _, cred := range cc.Credentials { |
||||
if cred.AccountID != "" { |
||||
m[cred.AccountID] = struct{}{} |
||||
} else if cred.AnonID != "" { |
||||
m[cred.AnonID] = struct{}{} |
||||
} |
||||
} |
||||
s := make([]string, 0, len(m)) |
||||
for id := range m { |
||||
s = append(s, id) |
||||
} |
||||
sort.Strings(s) |
||||
return s |
||||
} |
||||
|
||||
func abbrevCommitMessage(msg string) string { |
||||
i := strings.Index(msg, "\n") |
||||
if i > 0 { |
||||
msg = msg[:i] |
||||
} |
||||
if len(msg) > 80 { |
||||
msg = msg[:80] + "..." |
||||
} |
||||
return msg |
||||
} |
||||
|
||||
// Commit represents a single Commit which is being added to a branch. Only one
|
||||
// field should be set on a Commit, unless otherwise noted.
|
||||
// Commit wraps a single git commit object, and also contains various fields
|
||||
// which are parsed out of it, including the payload. It is used as a
|
||||
// convenience type, in place of having to manually retrieve and parse specific
|
||||
// information out of commit objects.
|
||||
type Commit struct { |
||||
Change *CommitChange `type:"change,default"` |
||||
Credential *CommitCredential `type:"credential"` |
||||
Comment *CommitComment `type:"comment"` |
||||
|
||||
Common CommitCommon `yaml:",inline"` |
||||
} |
||||
|
||||
// MarshalYAML implements the yaml.Marshaler interface.
|
||||
func (c Commit) MarshalYAML() (interface{}, error) { |
||||
return typeobj.MarshalYAML(c) |
||||
} |
||||
|
||||
// UnmarshalYAML implements the yaml.Unmarshaler interface.
|
||||
func (c *Commit) UnmarshalYAML(unmarshal func(interface{}) error) error { |
||||
return typeobj.UnmarshalYAML(c, unmarshal) |
||||
} |
||||
|
||||
// Interface returns the CommitInterface instance encapsulated by this Commit
|
||||
// object.
|
||||
func (c Commit) Interface() (CommitInterface, error) { |
||||
el, _, err := typeobj.Element(c) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
return el.(CommitInterface), nil |
||||
} |
||||
|
||||
// Type returns the Commit's type (as would be used in its YAML "type" field).
|
||||
func (c Commit) Type() (string, error) { |
||||
_, typeStr, err := typeobj.Element(c) |
||||
if err != nil { |
||||
return "", err |
||||
} |
||||
return typeStr, nil |
||||
} |
||||
|
||||
// MarshalText implements the encoding.TextMarshaler interface by returning the
|
||||
// form the Commit object takes in the git commit message.
|
||||
func (c Commit) MarshalText() ([]byte, error) { |
||||
commitInt, err := c.Interface() |
||||
if err != nil { |
||||
return nil, fmt.Errorf("could not cast Commit %+v to interface : %w", c, err) |
||||
} |
||||
|
||||
msgHead, err := commitInt.MessageHead(c.Common) |
||||
if err != nil { |
||||
return nil, fmt.Errorf("error constructing message head: %w", err) |
||||
} |
||||
|
||||
msgBodyB, err := yaml.Marshal(c) |
||||
if err != nil { |
||||
return nil, fmt.Errorf("error marshaling commit %+v as yaml: %w", c, err) |
||||
} |
||||
Payload PayloadUnion |
||||
|
||||
w := new(bytes.Buffer) |
||||
w.WriteString(msgHead) |
||||
w.WriteString("\n\n---\n") |
||||
w.Write(msgBodyB) |
||||
return w.Bytes(), nil |
||||
Hash plumbing.Hash |
||||
Object *object.Commit |
||||
TreeObject *object.Tree |
||||
} |
||||
|
||||
// UnmarshalText implements the encoding.TextUnmarshaler interface by decoding a
|
||||
// Commit object which has been encoded into a git commit message.
|
||||
func (c *Commit) UnmarshalText(msg []byte) error { |
||||
i := bytes.Index(msg, []byte("\n")) |
||||
if i < 0 { |
||||
return fmt.Errorf("commit message %q is malformed, it has no body", msg) |
||||
// GetCommit retrieves the Commit at the given hash, and all of its sub-data
|
||||
// which can be pulled out of it.
|
||||
func (proj *Project) GetCommit(h plumbing.Hash) (c Commit, err error) { |
||||
if c.Object, err = proj.GitRepo.CommitObject(h); err != nil { |
||||
return c, fmt.Errorf("getting git commit object: %w", err) |
||||
} else if c.TreeObject, err = proj.GitRepo.TreeObject(c.Object.TreeHash); err != nil { |
||||
return c, fmt.Errorf("getting git tree object %q: %w", |
||||
c.Object.TreeHash, err) |
||||
} else if c.Payload.UnmarshalText([]byte(c.Object.Message)); err != nil { |
||||
return c, fmt.Errorf("decoding commit message: %w", err) |
||||
} |
||||
msgBody := msg[i:] |
||||
|
||||
if err := yaml.Unmarshal(msgBody, c); err != nil { |
||||
return fmt.Errorf("could not unmarshal Commit message from yaml: %w", err) |
||||
|
||||
} else if reflect.DeepEqual(*c, Commit{}) { |
||||
// a basic check, but worthwhile
|
||||
return errors.New("commit message is malformed, could not unmarshal yaml object") |
||||
} |
||||
|
||||
return nil |
||||
c.Hash = c.Object.Hash |
||||
return |
||||
} |
||||
|
||||
// AccreditCommit returns the given Commit with an appended Credential provided
|
||||
// by the given SignifierInterface.
|
||||
func (r *Repo) AccreditCommit(commit Commit, sigInt sigcred.SignifierInterface) (Commit, error) { |
||||
commitInt, err := commit.Interface() |
||||
if err != nil { |
||||
return commit, fmt.Errorf("could not cast commit %+v to interface: %w", commit, err) |
||||
} |
||||
// ErrHeadIsZero is used to indicate that HEAD resolves to the zero hash. An
|
||||
// example of when this can happen is if the project was just initialized and
|
||||
// has no commits, or if an orphan branch is checked out.
|
||||
var ErrHeadIsZero = errors.New("HEAD resolves to the zero hash") |
||||
|
||||
headFS, err := r.headFS() |
||||
// GetHeadCommit returns the Commit which is currently referenced by HEAD.
|
||||
// This method may return ErrHeadIsZero if HEAD resolves to the zero hash.
|
||||
func (proj *Project) GetHeadCommit() (Commit, error) { |
||||
headHash, err := proj.ReferenceToHash(plumbing.HEAD) |
||||
if err != nil { |
||||
return commit, fmt.Errorf("could not grab snapshot of HEAD fs: %w", err) |
||||
return Commit{}, fmt.Errorf("resolving HEAD: %w", err) |
||||
} else if headHash == plumbing.ZeroHash { |
||||
return Commit{}, ErrHeadIsZero |
||||
} |
||||
|
||||
cred, err := sigInt.Sign(headFS, commitInt.StoredHash()) |
||||
c, err := proj.GetCommit(headHash) |
||||
if err != nil { |
||||
return commit, fmt.Errorf("could not accredit change commit: %w", err) |
||||
return Commit{}, fmt.Errorf("getting commit %q: %w", headHash, err) |
||||
} |
||||
commit.Common.Credentials = append(commit.Common.Credentials, cred) |
||||
return commit, nil |
||||
return c, nil |
||||
} |
||||
|
||||
// CommitBareParams are the parameters to the CommitBare method. All are
|
||||
// required, unless otherwise noted.
|
||||
type CommitBareParams struct { |
||||
Commit Commit |
||||
Author string |
||||
ParentHash plumbing.Hash // can be zero if the commit has no parents (Q_Q)
|
||||
GitTree *object.Tree |
||||
} |
||||
|
||||
// CommitBare constructs a git commit object and and stores it, returning the
|
||||
// resulting GitCommit. This method does not interact with HEAD at all.
|
||||
func (r *Repo) CommitBare(params CommitBareParams) (GitCommit, error) { |
||||
msgB, err := params.Commit.MarshalText() |
||||
if err != nil { |
||||
return GitCommit{}, fmt.Errorf("encoding %T to message string: %w", |
||||
params.Commit, err) |
||||
} |
||||
|
||||
author := object.Signature{ |
||||
Name: params.Author, |
||||
When: time.Now(), |
||||
} |
||||
commit := &object.Commit{ |
||||