add pre-receive hook command
message: add pre-receive hook command change_hash: ACKwl15k0EUU9zt2guC/yV0qA13f+YQbri/CRMENSNjc credentials: - type: pgp_signature pub_key_id: 95C46FA6A41148AC body: iQIzBAABAgAdFiEEJ6tQKp6olvZKJ0lwlcRvpqQRSKwFAl5It3gACgkQlcRvpqQRSKxdXw/9EeT/XYkI8XSJlyR1iXi070v0+dfOTuMOPJ3IGUP6BiOxfzy8J2kDdZHeQe1V1ajMb34TWvaQC/YyRN0CzThmykzhsUWAxnF4vRdOVdpPczAtNbhuRwSVVS1vGZmUViFB+xMa1GvrcU9rA5n6sKO1NwTAW05bwqBNgXAXDqDOJDtW033dx26ZbAx+ePENjw2JZDUmZg/3jCf6os8wIv29lSKEUU1cpvgP+ieN9w7L5B3h1R+wXFjJYgTEtFwz+ZvK4dld48asJXpd3b/nmXGFA4RLAlMKpTMRtVKHYgSNwn74jxUfmcIn9nQ+QJNvY8qsKKvbByRowxJ9Edf/t8wjy1lf734v0g0xNhCAziuRv+0vb1o/jNc0OiquqHC61T5bYngD/dx5kleBu7wiAYpiS3fuQeMHOptkXY3qLhekzqxLe7zwoysuwpBUgHWpm+UXsi5S1VkNwJle2lRhWToI85mGtWJ6ELfhav9Pxf+Dk8YEe7i480kvk+LUb/UmJXM6D3RbpAbw2Ci4hVgMV7brOkm484qQxYtlhe2gJ/XLwHCZhj7F6vm4IU4Ew3s1bApx6g1Kw/3r+esef9khVB19VLRLST2s2ue3I8oKuOhGK38OulGNr0PGGu8F/RV1zaSYA0afbdveUhwksEG2nmQrrPLhS6eK/5mHGV6f9z5oY24= account: mediocregopher
This commit is contained in:
parent
3f4d48ff89
commit
f0310bda75
@ -1,39 +1,57 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"dehub"
|
"dehub"
|
||||||
"errors"
|
"errors"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"gopkg.in/src-d/go-git.v4/plumbing"
|
"gopkg.in/src-d/go-git.v4/plumbing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func exitErr(err error) {
|
||||||
|
fmt.Fprintf(os.Stderr, "exiting: %v\n", err)
|
||||||
|
os.Stderr.Sync()
|
||||||
|
os.Stdout.Sync()
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
type subCmdCtx struct {
|
type subCmdCtx struct {
|
||||||
repo *dehub.Repo
|
repo func() *dehub.Repo
|
||||||
|
flag *flag.FlagSet
|
||||||
args []string
|
args []string
|
||||||
}
|
}
|
||||||
|
|
||||||
var subCmds = []struct {
|
func (sctx subCmdCtx) flagParse() {
|
||||||
|
if err := sctx.flag.Parse(sctx.args); err != nil {
|
||||||
|
exitErr(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type subCmd struct {
|
||||||
name, descr string
|
name, descr string
|
||||||
body func(sctx subCmdCtx) error
|
body func(sctx subCmdCtx) error
|
||||||
}{
|
}
|
||||||
|
|
||||||
|
var subCmds = []subCmd{
|
||||||
{
|
{
|
||||||
name: "commit",
|
name: "commit",
|
||||||
descr: "commits staged changes to the head of the current branch",
|
descr: "commits staged changes to the head of the current branch",
|
||||||
body: func(sctx subCmdCtx) error {
|
body: func(sctx subCmdCtx) error {
|
||||||
flag := flag.NewFlagSet("commit", flag.ExitOnError)
|
msg := sctx.flag.String("msg", "", "Commit message to use")
|
||||||
msg := flag.String("msg", "", "Commit message to use")
|
accountID := sctx.flag.String("account-id", "", "Account to sign commit as")
|
||||||
accountID := flag.String("account-id", "", "Account to sign commit as")
|
sctx.flagParse()
|
||||||
flag.Parse(sctx.args)
|
|
||||||
|
|
||||||
if *msg == "" || *accountID == "" {
|
if *msg == "" || *accountID == "" {
|
||||||
|
flag.PrintDefaults()
|
||||||
return errors.New("-msg and -account-id are both required")
|
return errors.New("-msg and -account-id are both required")
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg, err := sctx.repo.LoadConfig()
|
cfg, err := sctx.repo().LoadConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -58,7 +76,7 @@ var subCmds = []struct {
|
|||||||
return fmt.Errorf("could not cast %+v to SignifierInterface: %w", sig, err)
|
return fmt.Errorf("could not cast %+v to SignifierInterface: %w", sig, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, hash, err := sctx.repo.CommitMaster(*msg, *accountID, sigInt)
|
_, hash, err := sctx.repo().CommitMaster(*msg, *accountID, sigInt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -70,16 +88,15 @@ var subCmds = []struct {
|
|||||||
name: "verify",
|
name: "verify",
|
||||||
descr: "verifies one or more commits as having the proper credentials",
|
descr: "verifies one or more commits as having the proper credentials",
|
||||||
body: func(sctx subCmdCtx) error {
|
body: func(sctx subCmdCtx) error {
|
||||||
flag := flag.NewFlagSet("verify", flag.ExitOnError)
|
rev := sctx.flag.String("rev", "HEAD", "Revision of commit to verify")
|
||||||
rev := flag.String("rev", "HEAD", "Revision of commit to verify")
|
sctx.flagParse()
|
||||||
flag.Parse(sctx.args)
|
|
||||||
|
|
||||||
h, err := sctx.repo.GitRepo.ResolveRevision(plumbing.Revision(*rev))
|
h, err := sctx.repo().GitRepo.ResolveRevision(plumbing.Revision(*rev))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
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.VerifyMasterCommit(*h); err != nil {
|
if err := sctx.repo().VerifyMasterCommit(*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)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,50 +104,173 @@ var subCmds = []struct {
|
|||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "hook",
|
||||||
|
descr: "use dehub as a git hook",
|
||||||
|
body: func(sctx subCmdCtx) error {
|
||||||
|
preRcv := sctx.flag.Bool("pre-receive", false, "Use dehub as a server-side pre-receive hook")
|
||||||
|
sctx.flagParse()
|
||||||
|
|
||||||
|
if !*preRcv {
|
||||||
|
flag.PrintDefaults()
|
||||||
|
return errors.New("must set the hook type")
|
||||||
}
|
}
|
||||||
|
|
||||||
func printHelp() {
|
br := bufio.NewReader(os.Stdin)
|
||||||
fmt.Printf("USAGE: %s <command> [-h]\n\n", os.Args[0])
|
for {
|
||||||
|
line, err := br.ReadString('\n')
|
||||||
|
if errors.Is(err, io.EOF) {
|
||||||
|
return nil
|
||||||
|
} else if err != nil {
|
||||||
|
return fmt.Errorf("error reading next line from stdin: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
lineParts := strings.Fields(line)
|
||||||
|
if len(lineParts) < 3 {
|
||||||
|
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])
|
||||||
|
}
|
||||||
|
|
||||||
|
// the zeroRevision gets sent on the very first push
|
||||||
|
const zeroRevision plumbing.Revision = "0000000000000000000000000000000000000000"
|
||||||
|
|
||||||
|
fromRev := plumbing.Revision(lineParts[0])
|
||||||
|
var fromHash *plumbing.Hash
|
||||||
|
if fromRev != zeroRevision {
|
||||||
|
fromHash, err = sctx.repo().GitRepo.ResolveRevision(fromRev)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to resolve revision %q: %w", fromRev, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
toRev := plumbing.Revision(lineParts[1])
|
||||||
|
toHash, err := sctx.repo().GitRepo.ResolveRevision(toRev)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to resolve revision %q: %w", toRev, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
toCommit, err := sctx.repo().GitRepo.CommitObject(*toHash)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to find commit %q: %w", *toHash, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var hashesToCheck []plumbing.Hash
|
||||||
|
var found bool
|
||||||
|
for currCommit := toCommit; ; {
|
||||||
|
hashesToCheck = append(hashesToCheck, currCommit.Hash)
|
||||||
|
if currCommit.NumParents() == 0 {
|
||||||
|
break
|
||||||
|
} else if currCommit.NumParents() > 1 {
|
||||||
|
return fmt.Errorf("commit %q has more than one parent: %+v",
|
||||||
|
currCommit.Hash, currCommit.ParentHashes)
|
||||||
|
}
|
||||||
|
|
||||||
|
parentCommit, err := currCommit.Parent(0)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to get parent of commit %q: %w", currCommit.Hash, err)
|
||||||
|
} else if fromHash != nil && parentCommit.Hash == *fromHash {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
currCommit = parentCommit
|
||||||
|
}
|
||||||
|
if !found && fromHash != nil {
|
||||||
|
return fmt.Errorf("unable to find commit %q as an ancestor of %q", *fromHash, *toHash)
|
||||||
|
}
|
||||||
|
|
||||||
|
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.Println("All pushed commits have been verified, well done.")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var pickedSubCmd bool
|
||||||
|
var subCmd subCmd
|
||||||
|
flagSet := flag.NewFlagSet("dehub", flag.ContinueOnError)
|
||||||
|
flagSet.Usage = func() {
|
||||||
|
cmd := "<command>"
|
||||||
|
if pickedSubCmd {
|
||||||
|
cmd = subCmd.name
|
||||||
|
}
|
||||||
|
fmt.Printf("USAGE: %s [global flags] %s [command flags]\n\n", os.Args[0], cmd)
|
||||||
|
|
||||||
|
if !pickedSubCmd {
|
||||||
fmt.Println("COMMANDS")
|
fmt.Println("COMMANDS")
|
||||||
for _, subCmd := range subCmds {
|
for _, subCmd := range subCmds {
|
||||||
fmt.Printf("\t%s : %s\n", subCmd.name, subCmd.descr)
|
fmt.Printf("\t%s : %s\n", subCmd.name, subCmd.descr)
|
||||||
}
|
}
|
||||||
|
fmt.Println("")
|
||||||
}
|
}
|
||||||
|
|
||||||
func exitErr(err error) {
|
fmt.Println("GLOBAL FLAGS")
|
||||||
fmt.Fprintf(os.Stderr, "exiting: %v\n", err)
|
flagSet.PrintDefaults()
|
||||||
os.Stderr.Sync()
|
fmt.Println("")
|
||||||
os.Stdout.Sync()
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
bare := flagSet.Bool("bare", false, "If set then dehub will expect to be working with a bare repo")
|
||||||
if len(os.Args) < 2 {
|
if err := flagSet.Parse(os.Args[1:]); err != nil {
|
||||||
printHelp()
|
exitErr(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
subCmdName := strings.ToLower(os.Args[1])
|
args := flagSet.Args()
|
||||||
for _, subCmd := range subCmds {
|
if len(args) < 1 {
|
||||||
if subCmd.name != subCmdName {
|
flagSet.Usage()
|
||||||
continue
|
exitErr(errors.New("no command selected"))
|
||||||
}
|
}
|
||||||
|
|
||||||
r, err := dehub.OpenRepo(".")
|
subCmdName, args := strings.ToLower(args[0]), args[1:]
|
||||||
if err != nil {
|
for _, subCmd = range subCmds {
|
||||||
|
if pickedSubCmd = subCmd.name == subCmdName; pickedSubCmd {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !pickedSubCmd {
|
||||||
|
flagSet.Usage()
|
||||||
|
exitErr(fmt.Errorf("unknown command %q", subCmdName))
|
||||||
|
}
|
||||||
|
|
||||||
|
subFlagSet := flag.NewFlagSet(subCmd.name, flag.ExitOnError)
|
||||||
|
subFlagSet.Usage = func() {
|
||||||
|
flagSet.Usage()
|
||||||
|
fmt.Println("COMMAND FLAGS")
|
||||||
|
subFlagSet.PrintDefaults()
|
||||||
|
fmt.Println("")
|
||||||
|
}
|
||||||
|
|
||||||
|
var r *dehub.Repo
|
||||||
|
repoFn := func() *dehub.Repo {
|
||||||
|
if r != nil {
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
if r, err = dehub.OpenRepo(".", dehub.OpenBare(*bare)); err != nil {
|
||||||
|
wd, _ := os.Getwd()
|
||||||
|
err = fmt.Errorf("failed to OpenRepo at %q: %w", wd, err)
|
||||||
exitErr(err)
|
exitErr(err)
|
||||||
}
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
err = subCmd.body(subCmdCtx{
|
err := subCmd.body(subCmdCtx{
|
||||||
repo: r,
|
repo: repoFn,
|
||||||
args: os.Args[2:],
|
flag: subFlagSet,
|
||||||
|
args: args,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
exitErr(err)
|
exitErr(err)
|
||||||
}
|
}
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf("unknown command %q\n\n", subCmdName)
|
|
||||||
printHelp()
|
|
||||||
}
|
}
|
||||||
|
25
repo.go
25
repo.go
@ -26,6 +26,22 @@ var (
|
|||||||
ConfigPath = filepath.Join(DehubDir, "config.yml")
|
ConfigPath = filepath.Join(DehubDir, "config.yml")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type repoOpts struct {
|
||||||
|
bare bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpenOption is an option which can be passed to the OpenRepo function to
|
||||||
|
// affect the Repo's behavior.
|
||||||
|
type OpenOption func(*repoOpts)
|
||||||
|
|
||||||
|
// OpenBare returns an OpenOption which, if true is given, causes the OpenRepo
|
||||||
|
// function to expect to open a bare repo.
|
||||||
|
func OpenBare(bare bool) OpenOption {
|
||||||
|
return func(o *repoOpts) {
|
||||||
|
o.bare = bare
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Repo is an object which allows accessing and modifying the dehub repo.
|
// Repo is an object which allows accessing and modifying the dehub repo.
|
||||||
type Repo struct {
|
type Repo struct {
|
||||||
GitRepo *git.Repository
|
GitRepo *git.Repository
|
||||||
@ -36,11 +52,16 @@ type Repo struct {
|
|||||||
//
|
//
|
||||||
// The given path is expected to have a git repo and .dehub folder already
|
// The given path is expected to have a git repo and .dehub folder already
|
||||||
// initialized.
|
// initialized.
|
||||||
func OpenRepo(path string) (*Repo, error) {
|
func OpenRepo(path string, options ...OpenOption) (*Repo, error) {
|
||||||
|
var opts repoOpts
|
||||||
|
for _, opt := range options {
|
||||||
|
opt(&opts)
|
||||||
|
}
|
||||||
|
|
||||||
r := Repo{}
|
r := Repo{}
|
||||||
var err error
|
var err error
|
||||||
openOpts := &git.PlainOpenOptions{
|
openOpts := &git.PlainOpenOptions{
|
||||||
DetectDotGit: true,
|
DetectDotGit: !opts.bare,
|
||||||
}
|
}
|
||||||
if r.GitRepo, err = git.PlainOpenWithOptions(path, openOpts); err != nil {
|
if r.GitRepo, err = git.PlainOpenWithOptions(path, openOpts); err != nil {
|
||||||
return nil, fmt.Errorf("could not open git repo: %w", err)
|
return nil, fmt.Errorf("could not open git repo: %w", err)
|
||||||
|
Loading…
Reference in New Issue
Block a user