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:
parent
e78efcce74
commit
03459d4b20
@ -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.
|
||||
|
||||
|
@ -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 == "" {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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
10
repo.go
@ -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)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user