|
|
|
@ -1,39 +1,57 @@ |
|
|
|
|
package main |
|
|
|
|
|
|
|
|
|
import ( |
|
|
|
|
"bufio" |
|
|
|
|
"dehub" |
|
|
|
|
"errors" |
|
|
|
|
"flag" |
|
|
|
|
"fmt" |
|
|
|
|
"io" |
|
|
|
|
"os" |
|
|
|
|
"strings" |
|
|
|
|
|
|
|
|
|
"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 { |
|
|
|
|
repo *dehub.Repo |
|
|
|
|
repo func() *dehub.Repo |
|
|
|
|
flag *flag.FlagSet |
|
|
|
|
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 |
|
|
|
|
body func(sctx subCmdCtx) error |
|
|
|
|
}{ |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
var subCmds = []subCmd{ |
|
|
|
|
{ |
|
|
|
|
name: "commit", |
|
|
|
|
descr: "commits staged changes to the head of the current branch", |
|
|
|
|
body: func(sctx subCmdCtx) error { |
|
|
|
|
flag := flag.NewFlagSet("commit", flag.ExitOnError) |
|
|
|
|
msg := flag.String("msg", "", "Commit message to use") |
|
|
|
|
accountID := flag.String("account-id", "", "Account to sign commit as") |
|
|
|
|
flag.Parse(sctx.args) |
|
|
|
|
msg := sctx.flag.String("msg", "", "Commit message to use") |
|
|
|
|
accountID := sctx.flag.String("account-id", "", "Account to sign commit as") |
|
|
|
|
sctx.flagParse() |
|
|
|
|
|
|
|
|
|
if *msg == "" || *accountID == "" { |
|
|
|
|
flag.PrintDefaults() |
|
|
|
|
return errors.New("-msg and -account-id are both required") |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
cfg, err := sctx.repo.LoadConfig() |
|
|
|
|
cfg, err := sctx.repo().LoadConfig() |
|
|
|
|
if err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
@ -58,7 +76,7 @@ var subCmds = []struct { |
|
|
|
|
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 { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
@ -70,16 +88,15 @@ var subCmds = []struct { |
|
|
|
|
name: "verify", |
|
|
|
|
descr: "verifies one or more commits as having the proper credentials", |
|
|
|
|
body: func(sctx subCmdCtx) error { |
|
|
|
|
flag := flag.NewFlagSet("verify", flag.ExitOnError) |
|
|
|
|
rev := flag.String("rev", "HEAD", "Revision of commit to verify") |
|
|
|
|
flag.Parse(sctx.args) |
|
|
|
|
rev := sctx.flag.String("rev", "HEAD", "Revision of commit to verify") |
|
|
|
|
sctx.flagParse() |
|
|
|
|
|
|
|
|
|
h, err := sctx.repo.GitRepo.ResolveRevision(plumbing.Revision(*rev)) |
|
|
|
|
h, err := sctx.repo().GitRepo.ResolveRevision(plumbing.Revision(*rev)) |
|
|
|
|
if err != nil { |
|
|
|
|
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) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -87,50 +104,173 @@ var subCmds = []struct { |
|
|
|
|
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() |
|
|
|
|
|
|
|
|
|
func printHelp() { |
|
|
|
|
fmt.Printf("USAGE: %s <command> [-h]\n\n", os.Args[0]) |
|
|
|
|
fmt.Println("COMMANDS") |
|
|
|
|
for _, subCmd := range subCmds { |
|
|
|
|
fmt.Printf("\t%s : %s\n", subCmd.name, subCmd.descr) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
if !*preRcv { |
|
|
|
|
flag.PrintDefaults() |
|
|
|
|
return errors.New("must set the hook type") |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func exitErr(err error) { |
|
|
|
|
fmt.Fprintf(os.Stderr, "exiting: %v\n", err) |
|
|
|
|
os.Stderr.Sync() |
|
|
|
|
os.Stdout.Sync() |
|
|
|
|
os.Exit(1) |
|
|
|
|
br := bufio.NewReader(os.Stdin) |
|
|
|
|
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() { |
|
|
|
|
if len(os.Args) < 2 { |
|
|
|
|
printHelp() |
|
|
|
|
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") |
|
|
|
|
for _, subCmd := range subCmds { |
|
|
|
|
fmt.Printf("\t%s : %s\n", subCmd.name, subCmd.descr) |
|
|
|
|
} |
|
|
|
|
fmt.Println("") |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fmt.Println("GLOBAL FLAGS") |
|
|
|
|
flagSet.PrintDefaults() |
|
|
|
|
fmt.Println("") |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
bare := flagSet.Bool("bare", false, "If set then dehub will expect to be working with a bare repo") |
|
|
|
|
if err := flagSet.Parse(os.Args[1:]); err != nil { |
|
|
|
|
exitErr(err) |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
subCmdName := strings.ToLower(os.Args[1]) |
|
|
|
|
for _, subCmd := range subCmds { |
|
|
|
|
if subCmd.name != subCmdName { |
|
|
|
|
continue |
|
|
|
|
} |
|
|
|
|
args := flagSet.Args() |
|
|
|
|
if len(args) < 1 { |
|
|
|
|
flagSet.Usage() |
|
|
|
|
exitErr(errors.New("no command selected")) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
r, err := dehub.OpenRepo(".") |
|
|
|
|
if err != nil { |
|
|
|
|
exitErr(err) |
|
|
|
|
subCmdName, args := strings.ToLower(args[0]), args[1:] |
|
|
|
|
for _, subCmd = range subCmds { |
|
|
|
|
if pickedSubCmd = subCmd.name == subCmdName; pickedSubCmd { |
|
|
|
|
break |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
err = subCmd.body(subCmdCtx{ |
|
|
|
|
repo: r, |
|
|
|
|
args: os.Args[2:], |
|
|
|
|
}) |
|
|
|
|
if err != nil { |
|
|
|
|
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) |
|
|
|
|
} |
|
|
|
|
return |
|
|
|
|
return r |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fmt.Printf("unknown command %q\n\n", subCmdName) |
|
|
|
|
printHelp() |
|
|
|
|
err := subCmd.body(subCmdCtx{ |
|
|
|
|
repo: repoFn, |
|
|
|
|
flag: subFlagSet, |
|
|
|
|
args: args, |
|
|
|
|
}) |
|
|
|
|
if err != nil { |
|
|
|
|
exitErr(err) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|