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.
|
to accept help from people asking to help.
|
||||||
|
|
||||||
* Fast-forward perms on branches (so they can be deleted)
|
* Fast-forward perms on branches (so they can be deleted)
|
||||||
* Ammending commits.
|
|
||||||
* Figure out commit range syntax, use that everywhere.
|
* 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 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
|
* 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
|
These tasks aren't necessarily scheduled for any particular milestone, but they
|
||||||
are things that could use doing anyway.
|
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
|
* Maybe coalesce the `accessctl`, `fs`, and `sigcred` packages back into the
|
||||||
root "dehub" package.
|
root "dehub" package.
|
||||||
|
|
||||||
|
@ -78,14 +78,24 @@ func cmdCommit(ctx context.Context, cmd *dcmd.Cmd) {
|
|||||||
func(ctx context.Context, cmd *dcmd.Cmd) {
|
func(ctx context.Context, cmd *dcmd.Cmd) {
|
||||||
flag := cmd.FlagSet()
|
flag := cmd.FlagSet()
|
||||||
msg := flag.String("msg", "", "Commit message")
|
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) {
|
cmd.Run(func() (context.Context, error) {
|
||||||
if !hasStaged {
|
if !hasStaged && !*amend {
|
||||||
return nil, errors.New("no changes have been staged for commit")
|
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 == "" {
|
if *msg == "" {
|
||||||
var err error
|
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)
|
return nil, fmt.Errorf("error collecting commit message from user: %w", err)
|
||||||
|
|
||||||
} else if *msg == "" {
|
} else if *msg == "" {
|
||||||
@ -115,7 +125,7 @@ func cmdCommit(ctx context.Context, cmd *dcmd.Cmd) {
|
|||||||
if *rev == "" && *startRev == "" {
|
if *rev == "" && *startRev == "" {
|
||||||
return nil, errors.New("-rev or -start is required")
|
return nil, errors.New("-rev or -start is required")
|
||||||
} else if hasStaged {
|
} 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
|
var credCommit dehub.Commit
|
||||||
@ -154,10 +164,24 @@ func cmdCommit(ctx context.Context, cmd *dcmd.Cmd) {
|
|||||||
func(ctx context.Context, cmd *dcmd.Cmd) {
|
func(ctx context.Context, cmd *dcmd.Cmd) {
|
||||||
flag := cmd.FlagSet()
|
flag := cmd.FlagSet()
|
||||||
msg := flag.String("msg", "", "Comment message")
|
msg := flag.String("msg", "", "Comment message")
|
||||||
|
amend := flag.Bool("amend", false, "Amend the comment message currently in HEAD")
|
||||||
cmd.Run(func() (context.Context, error) {
|
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 == "" {
|
if *msg == "" {
|
||||||
var err error
|
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)
|
return nil, fmt.Errorf("collecting comment message from user: %w", err)
|
||||||
|
|
||||||
} else if *msg == "" {
|
} else if *msg == "" {
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"dehub.dev/src/dehub.git"
|
"dehub.dev/src/dehub.git"
|
||||||
|
"gopkg.in/src-d/go-git.v4/plumbing"
|
||||||
)
|
)
|
||||||
|
|
||||||
type repo struct {
|
type repo struct {
|
||||||
@ -26,3 +28,50 @@ func (r *repo) openRepo() error {
|
|||||||
}
|
}
|
||||||
return nil
|
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"
|
"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")
|
editor := os.Getenv("EDITOR")
|
||||||
if editor == "" {
|
if editor == "" {
|
||||||
return "", errors.New("EDITOR not set, please set it or use -msg in order to create your commit message")
|
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()
|
tmpfName := tmpf.Name()
|
||||||
defer os.Remove(tmpfName)
|
defer os.Remove(tmpfName)
|
||||||
|
|
||||||
tmpBody := bytes.NewBufferString(`
|
tmpBody := bytes.NewBufferString(fmt.Sprintf(tpl, args...))
|
||||||
|
|
||||||
# Please enter the commit message for your commit. Lines starting
|
|
||||||
# with '#' will be ignored, and an empty message aborts the commit.`)
|
|
||||||
|
|
||||||
_, err = io.Copy(tmpf, tmpBody)
|
_, err = io.Copy(tmpf, tmpBody)
|
||||||
tmpf.Close()
|
tmpf.Close()
|
||||||
|
@ -281,7 +281,8 @@ func (r *Repo) HasStagedChanges() (bool, error) {
|
|||||||
|
|
||||||
var any bool
|
var any bool
|
||||||
for _, fileStatus := range status {
|
for _, fileStatus := range status {
|
||||||
if fileStatus.Staging != git.Unmodified {
|
if fileStatus.Staging != git.Unmodified &&
|
||||||
|
fileStatus.Staging != git.Untracked {
|
||||||
any = true
|
any = true
|
||||||
break
|
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
|
// ReferenceToBranchName traverses a chain of references looking for the first
|
||||||
// branch reference, and returns that name, or returns an error if no branch
|
// branch reference, and returns that name, or returns ErrNoBranchReference if
|
||||||
// reference is part of the chain.
|
// no branch reference is part of the chain.
|
||||||
func (r *Repo) ReferenceToBranchName(refName plumbing.ReferenceName) (plumbing.ReferenceName, error) {
|
func (r *Repo) ReferenceToBranchName(refName plumbing.ReferenceName) (plumbing.ReferenceName, error) {
|
||||||
// first check if the given refName is a branch, if so just return that.
|
// first check if the given refName is a branch, if so just return that.
|
||||||
if refName.IsBranch() {
|
if refName.IsBranch() {
|
||||||
@ -280,7 +284,7 @@ func (r *Repo) ReferenceToBranchName(refName plumbing.ReferenceName) (plumbing.R
|
|||||||
return ref.Target().IsBranch()
|
return ref.Target().IsBranch()
|
||||||
})
|
})
|
||||||
if errors.Is(err, errTraverseRefNoMatch) {
|
if errors.Is(err, errTraverseRefNoMatch) {
|
||||||
return "", errors.New("no branch in reference chain")
|
return "", ErrNoBranchReference
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return "", fmt.Errorf("traversing reference chain: %w", err)
|
return "", fmt.Errorf("traversing reference chain: %w", err)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user