implement amend flag for change and comment commit types

---
type: change
message: |-
  implement amend flag for change and comment commit types

  This involved a custom implementation of soft reset, since go-git's doesn't
  correctly handle the behavior around the initial commit of a repo. There is also
  a small fix to HasStagedChanges, since it wasn't properly handling untracked
  files.
change_hash: AJxjg7EN6l2UBcqGXLAqS4HGCfMgD3xCmQ7uZMSOScBc
credentials:
- type: pgp_signature
  pub_key_id: 95C46FA6A41148AC
  body: iQIzBAABAgAdFiEEJ6tQKp6olvZKJ0lwlcRvpqQRSKwFAl6R9X0ACgkQlcRvpqQRSKwtGA/+Ow53J6A8al29hl4wQwpM5mLFI1krBrYcaZd6p/Sz4fhA2/YFhVAmmFCjFbI3QKQyb7DFijrT+8DSoUpgA83b8KIgHPXvUdBq12AFZeYKAfWB7USHWdmQPI4J7Z4rT7qP1o8T9fnWAAInKJPz3VC/RzEFvUdCFBgHHrYYX0S9twt4joEE7K/VlKJrjMQDhjUM+dFYEzQrcUHW5prSI5iHyJs/F+fpI2J7TSFPSCPCzPpAUyJ3Gt1aml9wBctzYOtuHslo5rw1nv3GweIrFBplekjEugxm0bvg95eVdsex/xltcY9KbKkl9iPVurmSRdkE2HDoQtc1c0Pl6XtssdA4k8rmjtO0jBHhKRA+b6ewVr5xRMDKRu2Jj5iPZlvbSMaldLhs/4QSfvQBvmyeVgX+R3zK7Y7JWdqxyOEgYihAI+/4jYxmHIu4QSTntJQw849do2tLqGgIjRcoQpjG35YUE+BZw1WjUVoT9GfPodPd75b5avwVYNuJ+3tKEPmwwz3HcMnwgPSaUEVIyfBlhdOEesSClIjk6rRJzB6WjpvooE9MJEF8AkaTxMGNcbkg/hSjXgt6vVW71YttdgRPoDuct6yU7aeoLwdDswCuqzEvaEdjB8elGcmwYlejnFt6xmLp0cUsPyRLSHQhpDFsdqJklmEXKVUFAgP6UEzxTxmGevY=
  account: mediocregopher
This commit is contained in:
mediocregopher 2020-04-11 10:51:09 -06:00
parent e78efcce74
commit 03459d4b20
6 changed files with 96 additions and 14 deletions

View File

@ -10,7 +10,6 @@ Must be able to feel good about showing the project publicly, as well as be able
to accept help from people asking to help.
* Fast-forward perms on branches (so they can be deleted)
* Ammending commits.
* Figure out commit range syntax, use that everywhere.
* Ability to specify a pgp key manually, even if it's not in the project.
* Ability to require _any_ signature on a commit, even if it's not in the
@ -65,6 +64,9 @@ to accept help from people asking to help.
These tasks aren't necessarily scheduled for any particular milestone, but they
are things that could use doing anyway.
* Config validation. Every interface used by the config should have a
`Validate() error` method, and Config itself should as well.
* Maybe coalesce the `accessctl`, `fs`, and `sigcred` packages back into the
root "dehub" package.

View File

@ -78,14 +78,24 @@ func cmdCommit(ctx context.Context, cmd *dcmd.Cmd) {
func(ctx context.Context, cmd *dcmd.Cmd) {
flag := cmd.FlagSet()
msg := flag.String("msg", "", "Commit message")
amend := flag.Bool("amend", false, "Add changes to HEAD commit, amend its message, and re-accredit it")
cmd.Run(func() (context.Context, error) {
if !hasStaged {
if !hasStaged && !*amend {
return nil, errors.New("no changes have been staged for commit")
}
var prevMsg string
if *amend {
oldHead, err := repo.softReset("change")
if err != nil {
return nil, err
}
prevMsg = oldHead.Commit.Change.Message
}
if *msg == "" {
var err error
if *msg, err = tmpFileMsg(); err != nil {
if *msg, err = tmpFileMsg(defaultCommitFileMsgTpl, prevMsg); err != nil {
return nil, fmt.Errorf("error collecting commit message from user: %w", err)
} else if *msg == "" {
@ -115,7 +125,7 @@ func cmdCommit(ctx context.Context, cmd *dcmd.Cmd) {
if *rev == "" && *startRev == "" {
return nil, errors.New("-rev or -start is required")
} else if hasStaged {
return nil, errors.New("credential commit cannot have any files changed")
return nil, errors.New("credential commit cannot have staged changes")
}
var credCommit dehub.Commit
@ -154,10 +164,24 @@ func cmdCommit(ctx context.Context, cmd *dcmd.Cmd) {
func(ctx context.Context, cmd *dcmd.Cmd) {
flag := cmd.FlagSet()
msg := flag.String("msg", "", "Comment message")
amend := flag.Bool("amend", false, "Amend the comment message currently in HEAD")
cmd.Run(func() (context.Context, error) {
if hasStaged {
return nil, errors.New("comment commit cannot have staged changes")
}
var prevMsg string
if *amend {
oldHead, err := repo.softReset("comment")
if err != nil {
return nil, err
}
prevMsg = oldHead.Commit.Comment.Message
}
if *msg == "" {
var err error
if *msg, err = tmpFileMsg(); err != nil {
if *msg, err = tmpFileMsg(defaultCommitFileMsgTpl, prevMsg); err != nil {
return nil, fmt.Errorf("collecting comment message from user: %w", err)
} else if *msg == "" {

View File

@ -1,11 +1,13 @@
package main
import (
"errors"
"flag"
"fmt"
"os"
"dehub.dev/src/dehub.git"
"gopkg.in/src-d/go-git.v4/plumbing"
)
type repo struct {
@ -26,3 +28,50 @@ func (r *repo) openRepo() error {
}
return nil
}
// softReset resets to HEAD^ (or to an orphaned index, if HEAD has no parents),
// returning the old HEAD.
func (r *repo) softReset(expType string) (dehub.GitCommit, error) {
head, err := r.GetGitHead()
if err != nil {
return head, fmt.Errorf("getting HEAD commit: %w", err)
} else if typ, err := head.Commit.Type(); err != nil {
return head, fmt.Errorf("determining commit type of HEAD:% w", err)
} else if expType != "" && typ != expType {
return head, fmt.Errorf("expected HEAD to be a %q commit, but found %q",
expType, typ)
}
branchName, branchErr := r.ReferenceToBranchName(plumbing.HEAD)
numParents := head.GitCommit.NumParents()
if numParents > 1 {
return head, errors.New("cannot reset to parent of a commit with multiple parents")
} else if numParents == 0 {
// if there are no parents then HEAD is the only commit in the branch.
// Don't handle ErrNoBranchReference because there's not really anything
// which can be done for that; we can't set head to "no commit".
// Otherwise, just remove the branch reference, HEAD will still point to
// it and all of HEAD's changes will be in the index.
if branchErr != nil {
return head, branchErr
} else if err := r.GitRepo.Storer.RemoveReference(branchName); err != nil {
return head, fmt.Errorf("removing reference %q: %w", branchName, err)
}
return head, nil
}
refName := branchName
if errors.Is(branchErr, dehub.ErrNoBranchReference) {
refName = plumbing.HEAD
} else if err != nil {
return head, fmt.Errorf("resolving HEAD: %w", err)
}
parentHash := head.GitCommit.ParentHashes[0]
newHeadRef := plumbing.NewHashReference(refName, parentHash)
if err := r.GitRepo.Storer.SetReference(newHeadRef); err != nil {
return head, fmt.Errorf("storing reference %q: %w", newHeadRef, err)
}
return head, nil
}

View File

@ -12,7 +12,12 @@ import (
"strings"
)
func tmpFileMsg() (string, error) {
const defaultCommitFileMsgTpl = `%s
# Please enter the commit message for your commit. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.`
func tmpFileMsg(tpl string, args ...interface{}) (string, error) {
editor := os.Getenv("EDITOR")
if editor == "" {
return "", errors.New("EDITOR not set, please set it or use -msg in order to create your commit message")
@ -27,10 +32,7 @@ func tmpFileMsg() (string, error) {
tmpfName := tmpf.Name()
defer os.Remove(tmpfName)
tmpBody := bytes.NewBufferString(`
# Please enter the commit message for your commit. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.`)
tmpBody := bytes.NewBufferString(fmt.Sprintf(tpl, args...))
_, err = io.Copy(tmpf, tmpBody)
tmpf.Close()

View File

@ -281,7 +281,8 @@ func (r *Repo) HasStagedChanges() (bool, error) {
var any bool
for _, fileStatus := range status {
if fileStatus.Staging != git.Unmodified {
if fileStatus.Staging != git.Unmodified &&
fileStatus.Staging != git.Untracked {
any = true
break
}

10
repo.go
View File

@ -267,9 +267,13 @@ func (r *Repo) TraverseReferenceChain(refName plumbing.ReferenceName, pred func(
}
}
// ErrNoBranchReference is returned from ReferenceToBranchName if no reference
// in the reference chain is for a branch.
var ErrNoBranchReference = errors.New("no branch reference found")
// ReferenceToBranchName traverses a chain of references looking for the first
// branch reference, and returns that name, or returns an error if no branch
// reference is part of the chain.
// branch reference, and returns that name, or returns ErrNoBranchReference if
// no branch reference is part of the chain.
func (r *Repo) ReferenceToBranchName(refName plumbing.ReferenceName) (plumbing.ReferenceName, error) {
// first check if the given refName is a branch, if so just return that.
if refName.IsBranch() {
@ -280,7 +284,7 @@ func (r *Repo) ReferenceToBranchName(refName plumbing.ReferenceName) (plumbing.R
return ref.Target().IsBranch()
})
if errors.Is(err, errTraverseRefNoMatch) {
return "", errors.New("no branch in reference chain")
return "", ErrNoBranchReference
} else if err != nil {
return "", fmt.Errorf("traversing reference chain: %w", err)
}