From a5bee27892665b21d8ff2747cd2602426416f180 Mon Sep 17 00:00:00 2001 From: mediocregopher <> Date: Sun, 16 Feb 2020 10:28:59 -0700 Subject: [PATCH] Change all references to 'master' into 'trunk' message: Change all references to 'master' into 'trunk' change_hash: ABJwxLvHMmj63oJPIv/vNeRCIp1ZDZYuPLQT57x9K1lO credentials: - type: pgp_signature pub_key_id: 95C46FA6A41148AC body: iQIzBAABAgAdFiEEJ6tQKp6olvZKJ0lwlcRvpqQRSKwFAl5Je88ACgkQlcRvpqQRSKyxyw/7B51/vvtlxLan9Z6q6rh7uyGcFf6WpWGiRDIccAJHqzegGP4eAb8V4Jzi9H4JJ82TnAc+EUegs8ewRiOcWj6YkU463b5CgUSwnzYKm86K6SyHGW1WH9OxFIDEMzCaVsktMEc9iLMl0dJNzakhPzu+qy74pU5xlypjCBzRLFeuqmnf4M1fq4FAq6fCs7ZVB3LccyC0mhCWsS2eiuCE/mVQ7WROVpxj5tplp71jlX6ZtWU7qsgvQS2V8ggtTVpCT2WE4u6bnu1oYOpSs9g+sxKKOAHKvZfjAMLG9qM3pOl1J+44W2Ms/mtON0VUX7G1Q5XVcmM0hPopXsiWLiAslSOAOuL+LE5iLq4nz1RyIbVh0QakYr+4NJL6Yt0L2I6lNVnUS4SgmMr86n8ZCcCjDAs6g5d7Zchqp3S3EF3bbJuLi0ICoOCxTD2gNkjo2BGverI8APLTUpujQl+9W/sGmT2aEdTpruGYjIcwsRaGo8VMDFECdZply5Ng1FbBoohv+j3vdO05fRyNkvRu3CBxP2tUe39jLxUmpu62igGF2VjZptsK0bLdzDpQ5Hv6jSZjn+mPyFJ5w+EX2JCRYpjn2eaYYtI/IyULGTaftzcEZeZibEhd/wQJnsEJNqXpCilDKgZajZyYhgy7BSeT9xDf2d8qyFoqWpvFhshHcesbUfG2O+Y= account: mediocregopher --- .gitignore | 2 +- SPEC.md | 20 +++--- cmd/dehub/main.go | 22 ++++--- cmd/http-server/.gitignore | 1 + commit.go | 128 ++++++++++++++++++------------------- commit_test.go | 39 ++++------- repo.go | 3 + repo_test.go | 17 ++--- sigcred/credential.go | 4 +- 9 files changed, 113 insertions(+), 123 deletions(-) create mode 100644 cmd/http-server/.gitignore diff --git a/.gitignore b/.gitignore index bb70765..ef96859 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1 @@ -dehub +/dehub diff --git a/SPEC.md b/SPEC.md index 110a0f5..5d3f844 100644 --- a/SPEC.md +++ b/SPEC.md @@ -65,23 +65,23 @@ access_controls: count: 1 ``` -# Master commit +# Trunk Commit -All new commits being appended to the HEAD of the `master` branch are subject to +All new commits being appended to the HEAD of the `trunk` branch are subject to the following requirements: * Must conform to all requirements defined by the `access_controls` section of the `config.yml`, as found in the HEAD. If the commit is the initial commit of - the repo then it instead uses the `config.yml` found in itself. + the branch then it instead uses the `config.yml` found in itself. * Must not be a merge commit (this may be amended later, but at present it simplifies implementation). * The commit message must conform to the format and semantics defined below. -## Master Commit Message +## Trunk Commit Message -The commit message for a commit being appended to the HEAD of the `master` +The commit message for a commit being appended to the HEAD of the `trunk` branch must conform to the following format: a single line (the message head) giving a short description of the change, then two newlines, then a body which is a yaml formatted string: @@ -169,7 +169,7 @@ access control requirements. The message head of these are arbitrary, but the body must be formatted as such: ```yaml -# This object matches the one found in the `credentials` section of the master +# This object matches the one found in the `credentials` section of the trunk # commit message. type: pgp_signature account_id: some_user_id ``` @@ -184,10 +184,10 @@ new signature commit, if they agree with the new changes. ## Merging MRs -When an MR has accumulated enough meta commits to fulfuill access control -requirements it may be coalesced into a single commit destined for the master -branch. See the Master Commit Message sub-section for details on how commits in -the master branch must be formatted. +When an MR has accumulated enough meta commits to fulfill access control +requirements it may be coalesced into a single commit destined for the `trunk` +branch. See the Trunk Commit Message sub-section for details on how commit +messages in the `trunk` branch must be formatted. # TODO diff --git a/cmd/dehub/main.go b/cmd/dehub/main.go index cc56af3..56eb0da 100644 --- a/cmd/dehub/main.go +++ b/cmd/dehub/main.go @@ -76,10 +76,16 @@ var subCmds = []subCmd{ return fmt.Errorf("could not cast %+v to SignifierInterface: %w", sig, err) } - _, hash, err := sctx.repo().CommitMaster(*msg, *accountID, sigInt) + tc, err := sctx.repo().NewTrunkCommit(*msg, *accountID, sigInt) if err != nil { - return err + return fmt.Errorf("could not construct trunk commit: %w", err) } + + hash, err := sctx.repo().Commit(tc, *accountID) + if err != nil { + return fmt.Errorf("could not commit trunk commit: %w", err) + } + fmt.Printf("changes committed to HEAD as %s\n", hash) return nil }, @@ -96,7 +102,7 @@ var subCmds = []subCmd{ return fmt.Errorf("could not resolve revision %q: %w", *rev, err) } - if err := sctx.repo().VerifyMasterCommit(*h); err != nil { + if err := sctx.repo().VerifyTrunkCommit(*h); err != nil { return fmt.Errorf("could not verify commit at %q (%s): %w", *rev, *h, err) } @@ -130,8 +136,8 @@ var subCmds = []subCmd{ return fmt.Errorf("malformed pre-receive hook stdin line %q", line) } - if plumbing.ReferenceName(lineParts[2]) != plumbing.Master { - return fmt.Errorf("only commits to the master branch are allowed at the moment (tried to push to %q)", lineParts[2]) + if plumbing.ReferenceName(lineParts[2]) != dehub.Trunk { + return fmt.Errorf("only commits to the trunk branch are allowed at the moment (tried to push to %q)", lineParts[2]) } // the zeroRevision gets sent on the very first push @@ -183,9 +189,9 @@ var subCmds = []subCmd{ for i := len(hashesToCheck) - 1; i >= 0; i-- { hash := hashesToCheck[i] - fmt.Printf("Verifying master commit %q\n", hash) - if err := sctx.repo().VerifyMasterCommit(hash); err != nil { - return fmt.Errorf("could not verify master commit %q", hash) + fmt.Printf("Verifying trunk commit %q\n", hash) + if err := sctx.repo().VerifyTrunkCommit(hash); err != nil { + return fmt.Errorf("could not verify trunk commit %q", hash) } } fmt.Println("All pushed commits have been verified, well done.") diff --git a/cmd/http-server/.gitignore b/cmd/http-server/.gitignore new file mode 100644 index 0000000..bb70765 --- /dev/null +++ b/cmd/http-server/.gitignore @@ -0,0 +1 @@ +dehub diff --git a/commit.go b/commit.go index 10b4739..0ba922e 100644 --- a/commit.go +++ b/commit.go @@ -6,6 +6,7 @@ import ( "dehub/fs" "dehub/sigcred" "dehub/yamlutil" + "encoding" "encoding/base64" "errors" "fmt" @@ -18,16 +19,16 @@ import ( yaml "gopkg.in/yaml.v2" ) -// MasterCommit describes the structure of the object encoded into the git -// message of a commit in the master branch. -type MasterCommit struct { +// TrunkCommit describes the structure of the object encoded into the git +// message of a commit in the repo trunk. +type TrunkCommit struct { Message string `yaml:"message"` ChangeHash yamlutil.Blob `yaml:"change_hash"` Credentials []sigcred.Credential `yaml:"credentials"` } -type mcYAML struct { - Val MasterCommit `yaml:",inline"` +type tcYAML struct { + Val TrunkCommit `yaml:",inline"` } func msgHead(msg string) string { @@ -39,55 +40,73 @@ func msgHead(msg string) string { } // MarshalText implements the encoding.TextMarshaler interface by returning the -// form the MasterCommit object takes in the git commit message. -func (mc MasterCommit) MarshalText() ([]byte, error) { - masterCommitEncoded, err := yaml.Marshal(mcYAML{mc}) +// form the TrunkCommit object takes in the git commit message. +func (tc TrunkCommit) MarshalText() ([]byte, error) { + trunkCommitEncoded, err := yaml.Marshal(tcYAML{tc}) if err != nil { - return nil, fmt.Errorf("failed to encode MasterCommit message: %w", err) + return nil, fmt.Errorf("failed to encode TrunkCommit message: %w", err) } - fullMsg := msgHead(mc.Message) + "\n\n" + string(masterCommitEncoded) + fullMsg := msgHead(tc.Message) + "\n\n" + string(trunkCommitEncoded) return []byte(fullMsg), nil } // UnmarshalText implements the encoding.TextUnmarshaler interface by decoding a -// MasterCommit object which has been encoded into a git commit message. -func (mc *MasterCommit) UnmarshalText(msg []byte) error { +// TrunkCommit object which has been encoded into a git commit message. +func (tc *TrunkCommit) UnmarshalText(msg []byte) error { i := bytes.Index(msg, []byte("\n")) if i < 0 { return fmt.Errorf("commit message %q is malformed", msg) } msgHead, msg := msg[:i], msg[i:] - var mcy mcYAML - if err := yaml.Unmarshal(msg, &mcy); err != nil { - return fmt.Errorf("could not unmarshal MasterCommit message: %w", err) + var tcy tcYAML + if err := yaml.Unmarshal(msg, &tcy); err != nil { + return fmt.Errorf("could not unmarshal TrunkCommit message: %w", err) } - *mc = mcy.Val - if !strings.HasPrefix(mc.Message, string(msgHead)) { - return errors.New("encoded MasterCommit is malformed, it might not be an encoded MasterCommit") + *tc = tcy.Val + if !strings.HasPrefix(tc.Message, string(msgHead)) { + return errors.New("encoded TrunkCommit is malformed, it might not be an encoded TrunkCommit") } return nil } -// CommitMaster constructs a MasterCommit using the given SignifierInterface to -// create a Credential for it. It returns the commit's hash after having set it -// to HEAD. -// -// TODO this method is a prototype and does not reflect the method's final form. -func (r *Repo) CommitMaster(msg, accountID string, sig sigcred.SignifierInterface) (MasterCommit, plumbing.Hash, error) { +// Commit uses the given TextMarshaler to create a git commit object (with the +// specified accountID as the author) and commits it to the current HEAD, +// returning the hash of the commit. +func (r *Repo) Commit(m encoding.TextMarshaler, accountID string) (plumbing.Hash, error) { + msgB, err := m.MarshalText() + if err != nil { + return plumbing.ZeroHash, fmt.Errorf("error marshaling %T to string: %v", m, err) + } + + w, err := r.GitRepo.Worktree() + if err != nil { + return plumbing.ZeroHash, fmt.Errorf("could not get git worktree: %w", err) + } + return w.Commit(string(msgB), &git.CommitOptions{ + Author: &object.Signature{ + Name: accountID, + When: time.Now(), + }, + }) +} + +// NewTrunkCommit constructs a TrunkCommit using the given SignifierInterface to +// create a Credential for it. +func (r *Repo) NewTrunkCommit(msg, accountID string, sig sigcred.SignifierInterface) (TrunkCommit, error) { _, headTree, err := r.head() if errors.Is(err, plumbing.ErrReferenceNotFound) { headTree = &object.Tree{} } else if err != nil { - return MasterCommit{}, plumbing.ZeroHash, err + return TrunkCommit{}, err } _, stagedTree, err := fs.FromStagedChangesTree(r.GitRepo) if err != nil { - return MasterCommit{}, plumbing.ZeroHash, err + return TrunkCommit{}, err } // this is necessarily different than headTree for the case of there being @@ -97,18 +116,18 @@ func (r *Repo) CommitMaster(msg, accountID string, sig sigcred.SignifierInterfac // data might be). sigFS, err := r.headOrRawFS() if err != nil { - return MasterCommit{}, plumbing.ZeroHash, err + return TrunkCommit{}, err } cfg, err := r.loadConfig(sigFS) if err != nil { - return MasterCommit{}, plumbing.ZeroHash, fmt.Errorf("could not load config: %w", err) + return TrunkCommit{}, fmt.Errorf("could not load config: %w", err) } changeHash := genChangeHash(nil, msg, headTree, stagedTree) cred, err := sig.Sign(sigFS, changeHash) if err != nil { - return MasterCommit{}, plumbing.ZeroHash, fmt.Errorf("failed to sign commit hash: %w", err) + return TrunkCommit{}, fmt.Errorf("failed to sign commit hash: %w", err) } cred.AccountID = accountID @@ -120,37 +139,14 @@ func (r *Repo) CommitMaster(msg, accountID string, sig sigcred.SignifierInterfac headTree, stagedTree, ) if err != nil { - return MasterCommit{}, plumbing.ZeroHash, fmt.Errorf("commit would not satisfy access controls: %w", err) + return TrunkCommit{}, fmt.Errorf("commit would not satisfy access controls: %w", err) } - masterCommit := MasterCommit{ + return TrunkCommit{ Message: msg, ChangeHash: changeHash, Credentials: []sigcred.Credential{cred}, - } - - masterCommitB, err := masterCommit.MarshalText() - if err != nil { - return masterCommit, plumbing.ZeroHash, err - } - - w, err := r.GitRepo.Worktree() - if err != nil { - return masterCommit, plumbing.ZeroHash, fmt.Errorf("could not get git worktree: %w", err) - } - - hash, err := w.Commit(string(masterCommitB), &git.CommitOptions{ - Author: &object.Signature{ - Name: accountID, - When: time.Now(), - }, - }) - if err != nil { - return masterCommit, hash, fmt.Errorf("failed to commit changed: %w", err) - } - - return masterCommit, hash, nil - + }, nil } func (r *Repo) assertAccessControls( @@ -185,9 +181,9 @@ func (r *Repo) assertAccessControls( return nil } -// VerifyMasterCommit verifies that the commit at the given hash, which is -// presumably on the master branch, is gucci. -func (r *Repo) VerifyMasterCommit(h plumbing.Hash) error { +// VerifyTrunkCommit verifies that the commit at the given hash, which is +// presumably on the repo trunk, is gucci. +func (r *Repo) VerifyTrunkCommit(h plumbing.Hash) error { commit, err := r.GitRepo.CommitObject(h) if err != nil { return fmt.Errorf("could not retrieve commit object: %w", err) @@ -198,8 +194,8 @@ func (r *Repo) VerifyMasterCommit(h plumbing.Hash) error { return fmt.Errorf("could not retrieve tree object: %w", err) } - var masterCommit MasterCommit - if err := masterCommit.UnmarshalText([]byte(commit.Message)); err != nil { + var trunkCommit TrunkCommit + if err := trunkCommit.UnmarshalText([]byte(commit.Message)); err != nil { return err } @@ -222,21 +218,21 @@ func (r *Repo) VerifyMasterCommit(h plumbing.Hash) error { } err = r.assertAccessControls( - cfg.AccessControls, masterCommit.Credentials, + cfg.AccessControls, trunkCommit.Credentials, parentTree, commitTree, ) if err != nil { return fmt.Errorf("failed to satisfy all access controls: %w", err) } - expectedChangeHash := genChangeHash(nil, masterCommit.Message, parentTree, commitTree) - if !bytes.Equal(masterCommit.ChangeHash, expectedChangeHash) { + expectedChangeHash := genChangeHash(nil, trunkCommit.Message, parentTree, commitTree) + if !bytes.Equal(trunkCommit.ChangeHash, expectedChangeHash) { return fmt.Errorf("malformed change_hash in commit body, is %s but should be %s", base64.StdEncoding.EncodeToString(expectedChangeHash), - base64.StdEncoding.EncodeToString(masterCommit.ChangeHash)) + base64.StdEncoding.EncodeToString(trunkCommit.ChangeHash)) } - for _, cred := range masterCommit.Credentials { + for _, cred := range trunkCommit.Credentials { sig, err := r.signifierForCredential(sigFS, cred) if err != nil { return fmt.Errorf("error finding signifier for credential %+v: %w", cred, err) @@ -245,7 +241,5 @@ func (r *Repo) VerifyMasterCommit(h plumbing.Hash) error { } } - // TODO access controls - return nil } diff --git a/commit_test.go b/commit_test.go index 2f2549d..8265ba0 100644 --- a/commit_test.go +++ b/commit_test.go @@ -13,7 +13,7 @@ import ( yaml "gopkg.in/yaml.v2" ) -func TestMasterCommitVerify(t *testing.T) { +func TestTrunkCommitVerify(t *testing.T) { type step struct { msg string msgHead string // defaults to msg @@ -81,10 +81,8 @@ func TestMasterCommitVerify(t *testing.T) { h.stage(step.tree) account := h.cfg.Accounts[0] - masterCommit, hash, err := h.repo.CommitMaster(step.msg, account.ID, h.sig) - if err != nil { - t.Fatalf("failed to make MasterCommit: %v", err) - } else if err := h.repo.VerifyMasterCommit(hash); err != nil { + trunkCommit, hash := h.trunkCommit(step.msg, account.ID, h.sig) + if err := h.repo.VerifyTrunkCommit(hash); err != nil { t.Fatalf("could not verify hash %v: %v", hash, err) } @@ -99,12 +97,12 @@ func TestMasterCommitVerify(t *testing.T) { t.Fatalf("commit message %q does not start with expected head %q", commit.Message, step.msgHead) } - var actualMasterCommit MasterCommit - if err := actualMasterCommit.UnmarshalText([]byte(commit.Message)); err != nil { + var actualTrunkCommit TrunkCommit + if err := actualTrunkCommit.UnmarshalText([]byte(commit.Message)); err != nil { t.Fatalf("error unmarshaling commit body: %v", err) - } else if !reflect.DeepEqual(actualMasterCommit, masterCommit) { - t.Fatalf("returned master commit:\n%s\ndoes not match actual one:\n%s", - spew.Sdump(masterCommit), spew.Sdump(actualMasterCommit)) + } else if !reflect.DeepEqual(actualTrunkCommit, trunkCommit) { + t.Fatalf("returned trunk commit:\n%s\ndoes not match actual one:\n%s", + spew.Sdump(trunkCommit), spew.Sdump(actualTrunkCommit)) } } }) @@ -118,10 +116,7 @@ func TestConfigChange(t *testing.T) { // commit the initial staged changes, which merely include the config and // public key - _, hash, err := h.repo.CommitMaster("commit configuration", h.cfg.Accounts[0].ID, h.sig) - if err != nil { - t.Fatal(err) - } + _, hash := h.trunkCommit("commit configuration", h.cfg.Accounts[0].ID, h.sig) hashes = append(hashes, hash) // create a new account and add it to the configuration. It should not be @@ -142,28 +137,22 @@ func TestConfigChange(t *testing.T) { } h.stage(map[string]string{ConfigPath: string(cfgBody)}) - _, _, err = h.repo.CommitMaster("add toot user", h.cfg.Accounts[1].ID, newSig) + _, err = h.repo.NewTrunkCommit("add toot user", h.cfg.Accounts[1].ID, newSig) if aclErr := (accessctl.ErrConditionSignatureUnsatisfied{}); !errors.As(err, &aclErr) { - t.Fatalf("CommitMaster should have returned an ErrConditionSignatureUnsatisfied, but returned %v", err) + t.Fatalf("NewTrunkCommit should have returned an ErrConditionSignatureUnsatisfied, but returned %v", err) } // now add with the root user, this should work. - _, hash, err = h.repo.CommitMaster("add toot user", h.cfg.Accounts[0].ID, h.sig) - if err != nil { - t.Fatalf("got an unexpected error committing with root: %v", err) - } + _, hash = h.trunkCommit("add toot user", h.cfg.Accounts[0].ID, h.sig) hashes = append(hashes, hash) // _now_ the toot user should be able to do things. h.stage(map[string]string{"foo/bar": "what a cool file"}) - _, hash, err = h.repo.CommitMaster("add a cool file", h.cfg.Accounts[1].ID, newSig) - if err != nil { - t.Fatalf("got an unexpected error committing with toot: %v", err) - } + _, hash = h.trunkCommit("add a cool file", h.cfg.Accounts[1].ID, newSig) hashes = append(hashes, hash) for i, hash := range hashes { - if err := h.repo.VerifyMasterCommit(hash); err != nil { + if err := h.repo.VerifyTrunkCommit(hash); err != nil { t.Fatalf("commit %d (%v) should have been verified but wasn't: %v", i, hash, err) } } diff --git a/repo.go b/repo.go index 8fe03b3..c5f8e13 100644 --- a/repo.go +++ b/repo.go @@ -24,6 +24,9 @@ const ( var ( // ConfigPath defines the expected path to the Repo's configuration file. ConfigPath = filepath.Join(DehubDir, "config.yml") + + // Trunk defines the reference name of the trunk branch. + Trunk = plumbing.ReferenceName("refs/heads/trunk") ) type repoOpts struct { diff --git a/repo_test.go b/repo_test.go index a187b67..4fff991 100644 --- a/repo_test.go +++ b/repo_test.go @@ -9,9 +9,7 @@ import ( "path/filepath" "testing" - "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" ) @@ -102,17 +100,16 @@ func (h *harness) stage(tree map[string]string) { } } -func (h *harness) commit(msg string) plumbing.Hash { - w, err := h.repo.GitRepo.Worktree() +func (h *harness) trunkCommit(msg, accountID string, sig sigcred.SignifierInterface) (TrunkCommit, plumbing.Hash) { + tc, err := h.repo.NewTrunkCommit(msg, accountID, sig) if err != nil { - h.t.Fatal(err) + h.t.Fatalf("failed to make TrunkCommit: %v", err) } - hash, err := w.Commit(msg, &git.CommitOptions{ - Author: &object.Signature{Name: "god"}, - }) + + hash, err := h.repo.Commit(tc, accountID) if err != nil { - h.t.Fatal(err) + h.t.Fatalf("failed to commit TrunkCommit: %v", err) } - return hash + return tc, hash } diff --git a/sigcred/credential.go b/sigcred/credential.go index 8d1a22f..9495cca 100644 --- a/sigcred/credential.go +++ b/sigcred/credential.go @@ -3,8 +3,8 @@ package sigcred import "dehub/typeobj" // Credential represents a credential which has been attached to a commit which -// hopefully will allow it to be included in the master branch. Exactly one -// field tagged with "type" should be set. +// hopefully will allow it to be included in the trunk. Exactly one field tagged +// with "type" should be set. type Credential struct { PGPSignature *CredentialPGPSignature `type:"pgp_signature"`