rename TrunkCommit to ChangeCommit, in accordance with the new SPEC
message: rename TrunkCommit to ChangeCommit, in accordance with the new SPEC change_hash: ALR1AfczQ9gwz9WHk4G9U4pOdDLu8frv19TmrwtNx90W credentials: - type: pgp_signature pub_key_id: 95C46FA6A41148AC body: iQIzBAABAgAdFiEEJ6tQKp6olvZKJ0lwlcRvpqQRSKwFAl5PZT8ACgkQlcRvpqQRSKxiyA//T2UObXqMmKntv7ogT4Atel4J6HzzV1sq4HNdvuuRiq7I1Jjzlluh+U1MsODOYuoS8tWWW3YK5ND0D03vhPO/tahUxnAbJXNdhHPDmvu95CCTqiF5X6x47PIBg9M4Z3s8PAipcNVfyWsciLhIXSP9TgJ6mpxy13cqsV7qpoQi0SX1olClxvU5N1oNtTk3swvec0vZnH4nYt2iFt28yRRTyKJuzSPDHqdAxqSYfCn0kLfOyMaycWi3PkslL78j9aXWQ8bzSArmcYeAO6RYu7CFGUbTf4mNKj4F/DusV4CO87ld8xGyHwVXCuShBb8wW7UYyrlbNJxXFVSxrIARSxXwtw/CuO7kQvB+IplBnWgpUiqzASKjXrwzZ+LMMUzmKCWyQphkW2fUU8d9FIh2NIEUmFCJfnmnnULmFDfecP2crU3Fxjv5ATsOi/F4IiGcJb6N9TxQAWK8ezC/Kk4UUSfNDBD99Qq3yfzJcMNrkK1ZkQgQ/SOSFC1MSoGiatzYJAU9wmO4S6+W6SX9abP5cr2OrI2kwiQbQ+XoJd+ywSJk0f3nP/5d50u5d/ddT9udUwmPnn9cc99Ikt6gXgRgzK//ktn5A5rQwGO+cfxmZkHbxzGATo2LHC2lEKiQMyFzvrXXD8TKJ6HbwX2krYREe4hfDP8iHsuM6eRZblImlraV+Z8= account: mediocregopher
This commit is contained in:
parent
492e7242c6
commit
3344b8372d
@ -76,14 +76,14 @@ var subCmds = []subCmd{
|
|||||||
return fmt.Errorf("could not cast %+v to SignifierInterface: %w", sig, err)
|
return fmt.Errorf("could not cast %+v to SignifierInterface: %w", sig, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
tc, err := sctx.repo().NewTrunkCommit(*msg, *accountID, sigInt)
|
tc, err := sctx.repo().NewChangeCommit(*msg, *accountID, sigInt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could not construct trunk commit: %w", err)
|
return fmt.Errorf("could not construct change commit: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
hash, err := sctx.repo().Commit(tc, *accountID)
|
hash, err := sctx.repo().Commit(tc, *accountID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could not commit trunk commit: %w", err)
|
return fmt.Errorf("could not commit change commit: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("changes committed to HEAD as %s\n", hash)
|
fmt.Printf("changes committed to HEAD as %s\n", hash)
|
||||||
@ -102,7 +102,7 @@ var subCmds = []subCmd{
|
|||||||
return fmt.Errorf("could not resolve revision %q: %w", *rev, err)
|
return fmt.Errorf("could not resolve revision %q: %w", *rev, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := sctx.repo().VerifyTrunkCommit(*h); err != nil {
|
if err := sctx.repo().VerifyChangeCommit(*h); err != nil {
|
||||||
return fmt.Errorf("could not verify commit at %q (%s): %w", *rev, *h, err)
|
return fmt.Errorf("could not verify commit at %q (%s): %w", *rev, *h, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -189,9 +189,9 @@ var subCmds = []subCmd{
|
|||||||
|
|
||||||
for i := len(hashesToCheck) - 1; i >= 0; i-- {
|
for i := len(hashesToCheck) - 1; i >= 0; i-- {
|
||||||
hash := hashesToCheck[i]
|
hash := hashesToCheck[i]
|
||||||
fmt.Printf("Verifying trunk commit %q\n", hash)
|
fmt.Printf("Verifying change commit %q\n", hash)
|
||||||
if err := sctx.repo().VerifyTrunkCommit(hash); err != nil {
|
if err := sctx.repo().VerifyChangeCommit(hash); err != nil {
|
||||||
return fmt.Errorf("could not verify trunk commit %q", hash)
|
return fmt.Errorf("could not verify change commit %q", hash)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fmt.Println("All pushed commits have been verified, well done.")
|
fmt.Println("All pushed commits have been verified, well done.")
|
||||||
|
75
commit.go
75
commit.go
@ -19,16 +19,15 @@ import (
|
|||||||
yaml "gopkg.in/yaml.v2"
|
yaml "gopkg.in/yaml.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TrunkCommit describes the structure of the object encoded into the git
|
// ChangeCommit describes the structure of a change commit message.
|
||||||
// message of a commit in the repo trunk.
|
type ChangeCommit struct {
|
||||||
type TrunkCommit struct {
|
|
||||||
Message string `yaml:"message"`
|
Message string `yaml:"message"`
|
||||||
ChangeHash yamlutil.Blob `yaml:"change_hash"`
|
ChangeHash yamlutil.Blob `yaml:"change_hash"`
|
||||||
Credentials []sigcred.Credential `yaml:"credentials"`
|
Credentials []sigcred.Credential `yaml:"credentials"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type tcYAML struct {
|
type ccYAML struct {
|
||||||
Val TrunkCommit `yaml:",inline"`
|
Val ChangeCommit `yaml:",inline"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func msgHead(msg string) string {
|
func msgHead(msg string) string {
|
||||||
@ -40,34 +39,34 @@ func msgHead(msg string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// MarshalText implements the encoding.TextMarshaler interface by returning the
|
// MarshalText implements the encoding.TextMarshaler interface by returning the
|
||||||
// form the TrunkCommit object takes in the git commit message.
|
// form the ChangeCommit object takes in the git commit message.
|
||||||
func (tc TrunkCommit) MarshalText() ([]byte, error) {
|
func (cc ChangeCommit) MarshalText() ([]byte, error) {
|
||||||
trunkCommitEncoded, err := yaml.Marshal(tcYAML{tc})
|
changeCommitEncoded, err := yaml.Marshal(ccYAML{cc})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to encode TrunkCommit message: %w", err)
|
return nil, fmt.Errorf("failed to encode ChangeCommit message: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fullMsg := msgHead(tc.Message) + "\n\n" + string(trunkCommitEncoded)
|
fullMsg := msgHead(cc.Message) + "\n\n" + string(changeCommitEncoded)
|
||||||
return []byte(fullMsg), nil
|
return []byte(fullMsg), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalText implements the encoding.TextUnmarshaler interface by decoding a
|
// UnmarshalText implements the encoding.TextUnmarshaler interface by decoding a
|
||||||
// TrunkCommit object which has been encoded into a git commit message.
|
// ChangeCommit object which has been encoded into a git commit message.
|
||||||
func (tc *TrunkCommit) UnmarshalText(msg []byte) error {
|
func (cc *ChangeCommit) UnmarshalText(msg []byte) error {
|
||||||
i := bytes.Index(msg, []byte("\n"))
|
i := bytes.Index(msg, []byte("\n"))
|
||||||
if i < 0 {
|
if i < 0 {
|
||||||
return fmt.Errorf("commit message %q is malformed", msg)
|
return fmt.Errorf("commit message %q is malformed", msg)
|
||||||
}
|
}
|
||||||
msgHead, msg := msg[:i], msg[i:]
|
msgHead, msg := msg[:i], msg[i:]
|
||||||
|
|
||||||
var tcy tcYAML
|
var ccy ccYAML
|
||||||
if err := yaml.Unmarshal(msg, &tcy); err != nil {
|
if err := yaml.Unmarshal(msg, &ccy); err != nil {
|
||||||
return fmt.Errorf("could not unmarshal TrunkCommit message: %w", err)
|
return fmt.Errorf("could not unmarshal ChangeCommit message: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
*tc = tcy.Val
|
*cc = ccy.Val
|
||||||
if !strings.HasPrefix(tc.Message, string(msgHead)) {
|
if !strings.HasPrefix(cc.Message, string(msgHead)) {
|
||||||
return errors.New("encoded TrunkCommit is malformed, it might not be an encoded TrunkCommit")
|
return errors.New("encoded ChangeCommit is malformed, it might not be an encoded ChangeCommit")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -94,19 +93,19 @@ func (r *Repo) Commit(m encoding.TextMarshaler, accountID string) (plumbing.Hash
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewTrunkCommit constructs a TrunkCommit using the given SignifierInterface to
|
// NewChangeCommit constructs a ChangeCommit using the given SignifierInterface
|
||||||
// create a Credential for it.
|
// to create a Credential for it.
|
||||||
func (r *Repo) NewTrunkCommit(msg, accountID string, sig sigcred.SignifierInterface) (TrunkCommit, error) {
|
func (r *Repo) NewChangeCommit(msg, accountID string, sig sigcred.SignifierInterface) (ChangeCommit, error) {
|
||||||
_, headTree, err := r.head()
|
_, headTree, err := r.head()
|
||||||
if errors.Is(err, plumbing.ErrReferenceNotFound) {
|
if errors.Is(err, plumbing.ErrReferenceNotFound) {
|
||||||
headTree = &object.Tree{}
|
headTree = &object.Tree{}
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return TrunkCommit{}, err
|
return ChangeCommit{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, stagedTree, err := fs.FromStagedChangesTree(r.GitRepo)
|
_, stagedTree, err := fs.FromStagedChangesTree(r.GitRepo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return TrunkCommit{}, err
|
return ChangeCommit{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// this is necessarily different than headTree for the case of there being
|
// this is necessarily different than headTree for the case of there being
|
||||||
@ -116,18 +115,18 @@ func (r *Repo) NewTrunkCommit(msg, accountID string, sig sigcred.SignifierInterf
|
|||||||
// data might be).
|
// data might be).
|
||||||
sigFS, err := r.headOrRawFS()
|
sigFS, err := r.headOrRawFS()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return TrunkCommit{}, err
|
return ChangeCommit{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg, err := r.loadConfig(sigFS)
|
cfg, err := r.loadConfig(sigFS)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return TrunkCommit{}, fmt.Errorf("could not load config: %w", err)
|
return ChangeCommit{}, fmt.Errorf("could not load config: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
changeHash := genChangeHash(nil, msg, headTree, stagedTree)
|
changeHash := genChangeHash(nil, msg, headTree, stagedTree)
|
||||||
cred, err := sig.Sign(sigFS, changeHash)
|
cred, err := sig.Sign(sigFS, changeHash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return TrunkCommit{}, fmt.Errorf("failed to sign commit hash: %w", err)
|
return ChangeCommit{}, fmt.Errorf("failed to sign commit hash: %w", err)
|
||||||
}
|
}
|
||||||
cred.AccountID = accountID
|
cred.AccountID = accountID
|
||||||
|
|
||||||
@ -139,10 +138,10 @@ func (r *Repo) NewTrunkCommit(msg, accountID string, sig sigcred.SignifierInterf
|
|||||||
headTree, stagedTree,
|
headTree, stagedTree,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return TrunkCommit{}, fmt.Errorf("commit would not satisfy access controls: %w", err)
|
return ChangeCommit{}, fmt.Errorf("commit would not satisfy access controls: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return TrunkCommit{
|
return ChangeCommit{
|
||||||
Message: msg,
|
Message: msg,
|
||||||
ChangeHash: changeHash,
|
ChangeHash: changeHash,
|
||||||
Credentials: []sigcred.Credential{cred},
|
Credentials: []sigcred.Credential{cred},
|
||||||
@ -181,9 +180,9 @@ func (r *Repo) assertAccessControls(
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// VerifyTrunkCommit verifies that the commit at the given hash, which is
|
// VerifyChangeCommit verifies that the change commit at the given hash, which
|
||||||
// presumably on the repo trunk, is gucci.
|
// is presumably on the repo trunk, is gucci.
|
||||||
func (r *Repo) VerifyTrunkCommit(h plumbing.Hash) error {
|
func (r *Repo) VerifyChangeCommit(h plumbing.Hash) error {
|
||||||
commit, err := r.GitRepo.CommitObject(h)
|
commit, err := r.GitRepo.CommitObject(h)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could not retrieve commit object: %w", err)
|
return fmt.Errorf("could not retrieve commit object: %w", err)
|
||||||
@ -194,8 +193,8 @@ func (r *Repo) VerifyTrunkCommit(h plumbing.Hash) error {
|
|||||||
return fmt.Errorf("could not retrieve tree object: %w", err)
|
return fmt.Errorf("could not retrieve tree object: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var trunkCommit TrunkCommit
|
var changeCommit ChangeCommit
|
||||||
if err := trunkCommit.UnmarshalText([]byte(commit.Message)); err != nil {
|
if err := changeCommit.UnmarshalText([]byte(commit.Message)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -218,21 +217,21 @@ func (r *Repo) VerifyTrunkCommit(h plumbing.Hash) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
err = r.assertAccessControls(
|
err = r.assertAccessControls(
|
||||||
cfg.AccessControls, trunkCommit.Credentials,
|
cfg.AccessControls, changeCommit.Credentials,
|
||||||
parentTree, commitTree,
|
parentTree, commitTree,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to satisfy all access controls: %w", err)
|
return fmt.Errorf("failed to satisfy all access controls: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
expectedChangeHash := genChangeHash(nil, trunkCommit.Message, parentTree, commitTree)
|
expectedChangeHash := genChangeHash(nil, changeCommit.Message, parentTree, commitTree)
|
||||||
if !bytes.Equal(trunkCommit.ChangeHash, expectedChangeHash) {
|
if !bytes.Equal(changeCommit.ChangeHash, expectedChangeHash) {
|
||||||
return fmt.Errorf("malformed change_hash in commit body, is %s but should be %s",
|
return fmt.Errorf("malformed change_hash in commit body, is %s but should be %s",
|
||||||
base64.StdEncoding.EncodeToString(expectedChangeHash),
|
base64.StdEncoding.EncodeToString(expectedChangeHash),
|
||||||
base64.StdEncoding.EncodeToString(trunkCommit.ChangeHash))
|
base64.StdEncoding.EncodeToString(changeCommit.ChangeHash))
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, cred := range trunkCommit.Credentials {
|
for _, cred := range changeCommit.Credentials {
|
||||||
sig, err := r.signifierForCredential(sigFS, cred)
|
sig, err := r.signifierForCredential(sigFS, cred)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error finding signifier for credential %+v: %w", cred, err)
|
return fmt.Errorf("error finding signifier for credential %+v: %w", cred, err)
|
||||||
|
@ -13,7 +13,7 @@ import (
|
|||||||
yaml "gopkg.in/yaml.v2"
|
yaml "gopkg.in/yaml.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestTrunkCommitVerify(t *testing.T) {
|
func TestChangeCommitVerify(t *testing.T) {
|
||||||
type step struct {
|
type step struct {
|
||||||
msg string
|
msg string
|
||||||
msgHead string // defaults to msg
|
msgHead string // defaults to msg
|
||||||
@ -81,8 +81,8 @@ func TestTrunkCommitVerify(t *testing.T) {
|
|||||||
h.stage(step.tree)
|
h.stage(step.tree)
|
||||||
account := h.cfg.Accounts[0]
|
account := h.cfg.Accounts[0]
|
||||||
|
|
||||||
trunkCommit, hash := h.trunkCommit(step.msg, account.ID, h.sig)
|
changeCommit, hash := h.changeCommit(step.msg, account.ID, h.sig)
|
||||||
if err := h.repo.VerifyTrunkCommit(hash); err != nil {
|
if err := h.repo.VerifyChangeCommit(hash); err != nil {
|
||||||
t.Fatalf("could not verify hash %v: %v", hash, err)
|
t.Fatalf("could not verify hash %v: %v", hash, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -97,12 +97,12 @@ func TestTrunkCommitVerify(t *testing.T) {
|
|||||||
t.Fatalf("commit message %q does not start with expected head %q", commit.Message, step.msgHead)
|
t.Fatalf("commit message %q does not start with expected head %q", commit.Message, step.msgHead)
|
||||||
}
|
}
|
||||||
|
|
||||||
var actualTrunkCommit TrunkCommit
|
var actualChangeCommit ChangeCommit
|
||||||
if err := actualTrunkCommit.UnmarshalText([]byte(commit.Message)); err != nil {
|
if err := actualChangeCommit.UnmarshalText([]byte(commit.Message)); err != nil {
|
||||||
t.Fatalf("error unmarshaling commit body: %v", err)
|
t.Fatalf("error unmarshaling commit body: %v", err)
|
||||||
} else if !reflect.DeepEqual(actualTrunkCommit, trunkCommit) {
|
} else if !reflect.DeepEqual(actualChangeCommit, changeCommit) {
|
||||||
t.Fatalf("returned trunk commit:\n%s\ndoes not match actual one:\n%s",
|
t.Fatalf("returned change commit:\n%s\ndoes not match actual one:\n%s",
|
||||||
spew.Sdump(trunkCommit), spew.Sdump(actualTrunkCommit))
|
spew.Sdump(changeCommit), spew.Sdump(actualChangeCommit))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -116,7 +116,7 @@ func TestConfigChange(t *testing.T) {
|
|||||||
|
|
||||||
// commit the initial staged changes, which merely include the config and
|
// commit the initial staged changes, which merely include the config and
|
||||||
// public key
|
// public key
|
||||||
_, hash := h.trunkCommit("commit configuration", h.cfg.Accounts[0].ID, h.sig)
|
_, hash := h.changeCommit("commit configuration", h.cfg.Accounts[0].ID, h.sig)
|
||||||
hashes = append(hashes, hash)
|
hashes = append(hashes, hash)
|
||||||
|
|
||||||
// create a new account and add it to the configuration. It should not be
|
// create a new account and add it to the configuration. It should not be
|
||||||
@ -137,22 +137,22 @@ func TestConfigChange(t *testing.T) {
|
|||||||
}
|
}
|
||||||
h.stage(map[string]string{ConfigPath: string(cfgBody)})
|
h.stage(map[string]string{ConfigPath: string(cfgBody)})
|
||||||
|
|
||||||
_, err = h.repo.NewTrunkCommit("add toot user", h.cfg.Accounts[1].ID, newSig)
|
_, err = h.repo.NewChangeCommit("add toot user", h.cfg.Accounts[1].ID, newSig)
|
||||||
if aclErr := (accessctl.ErrConditionSignatureUnsatisfied{}); !errors.As(err, &aclErr) {
|
if aclErr := (accessctl.ErrConditionSignatureUnsatisfied{}); !errors.As(err, &aclErr) {
|
||||||
t.Fatalf("NewTrunkCommit should have returned an ErrConditionSignatureUnsatisfied, but returned %v", err)
|
t.Fatalf("NewChangeCommit should have returned an ErrConditionSignatureUnsatisfied, but returned %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// now add with the root user, this should work.
|
// now add with the root user, this should work.
|
||||||
_, hash = h.trunkCommit("add toot user", h.cfg.Accounts[0].ID, h.sig)
|
_, hash = h.changeCommit("add toot user", h.cfg.Accounts[0].ID, h.sig)
|
||||||
hashes = append(hashes, hash)
|
hashes = append(hashes, hash)
|
||||||
|
|
||||||
// _now_ the toot user should be able to do things.
|
// _now_ the toot user should be able to do things.
|
||||||
h.stage(map[string]string{"foo/bar": "what a cool file"})
|
h.stage(map[string]string{"foo/bar": "what a cool file"})
|
||||||
_, hash = h.trunkCommit("add a cool file", h.cfg.Accounts[1].ID, newSig)
|
_, hash = h.changeCommit("add a cool file", h.cfg.Accounts[1].ID, newSig)
|
||||||
hashes = append(hashes, hash)
|
hashes = append(hashes, hash)
|
||||||
|
|
||||||
for i, hash := range hashes {
|
for i, hash := range hashes {
|
||||||
if err := h.repo.VerifyTrunkCommit(hash); err != nil {
|
if err := h.repo.VerifyChangeCommit(hash); err != nil {
|
||||||
t.Fatalf("commit %d (%v) should have been verified but wasn't: %v", i, hash, err)
|
t.Fatalf("commit %d (%v) should have been verified but wasn't: %v", i, hash, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -100,15 +100,15 @@ func (h *harness) stage(tree map[string]string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *harness) trunkCommit(msg, accountID string, sig sigcred.SignifierInterface) (TrunkCommit, plumbing.Hash) {
|
func (h *harness) changeCommit(msg, accountID string, sig sigcred.SignifierInterface) (ChangeCommit, plumbing.Hash) {
|
||||||
tc, err := h.repo.NewTrunkCommit(msg, accountID, sig)
|
tc, err := h.repo.NewChangeCommit(msg, accountID, sig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.t.Fatalf("failed to make TrunkCommit: %v", err)
|
h.t.Fatalf("failed to make ChangeCommit: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
hash, err := h.repo.Commit(tc, accountID)
|
hash, err := h.repo.Commit(tc, accountID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.t.Fatalf("failed to commit TrunkCommit: %v", err)
|
h.t.Fatalf("failed to commit ChangeCommit: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return tc, hash
|
return tc, hash
|
||||||
|
Loading…
Reference in New Issue
Block a user