package main import ( "bufio" "context" "dehub/cmd/dehub/dcmd" "errors" "fmt" "io" "os" "strings" "gopkg.in/src-d/go-git.v4/plumbing" ) func cmdHook(ctx context.Context, cmd *dcmd.Cmd) { flag := cmd.FlagSet() preRcv := flag.Bool("pre-receive", false, "Use dehub as a server-side pre-receive hook") cmd.Run(func() (context.Context, error) { if !*preRcv { return nil, errors.New("must set the hook type") } repo := ctxRepo(ctx) br := bufio.NewReader(os.Stdin) for { line, err := br.ReadString('\n') if errors.Is(err, io.EOF) { return nil, nil } else if err != nil { return nil, fmt.Errorf("error reading next line from stdin: %w", err) } fmt.Printf("Processing line %q\n", strings.TrimSpace(line)) lineParts := strings.Fields(line) if len(lineParts) < 3 { return nil, fmt.Errorf("malformed pre-receive hook stdin line %q", line) } branchName := plumbing.ReferenceName(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 = repo.GitRepo.ResolveRevision(fromRev) if err != nil { return nil, fmt.Errorf("unable to resolve revision %q: %w", fromRev, err) } } toRev := plumbing.Revision(lineParts[1]) toHash, err := repo.GitRepo.ResolveRevision(toRev) if err != nil { return nil, fmt.Errorf("unable to resolve revision %q: %w", toRev, err) } toCommit, err := repo.GitRepo.CommitObject(*toHash) if err != nil { return nil, 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 nil, fmt.Errorf("commit %q has more than one parent: %+v", currCommit.Hash, currCommit.ParentHashes) } parentCommit, err := currCommit.Parent(0) if err != nil { return nil, 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 nil, 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 change commit %q\n", hash) if err := repo.VerifyCommit(branchName, hash); err != nil { return nil, fmt.Errorf("could not verify change commit %q: %w", hash, err) } } fmt.Println("All pushed commits have been verified, well done.") return nil, nil } }) }