SandpointsGitHook/vendor/github.com/bep/gitmap/gitmap.go

154 lines
3.9 KiB
Go

// Copyright © 2016-present Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>.
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.
package gitmap
import (
"bytes"
"errors"
"fmt"
"os/exec"
"path/filepath"
"strings"
"time"
)
var (
// will be modified during tests
gitExec string
GitNotFound = errors.New("Git executable not found in $PATH")
)
type GitRepo struct {
// TopLevelAbsPath contains the absolute path of the top-level directory.
// This is similar to the answer from "git rev-parse --show-toplevel"
// except symbolic link is not followed on non-Windows platforms.
// Note that this follows Git's way of handling paths, so expect to get forward slashes,
// even on Windows.
TopLevelAbsPath string
// The files in this Git repository.
Files GitMap
}
// GitMap maps filenames to Git revision information.
type GitMap map[string]*GitInfo
// GitInfo holds information about a Git commit.
type GitInfo struct {
Hash string `json:"hash"` // Commit hash
AbbreviatedHash string `json:"abbreviatedHash"` // Abbreviated commit hash
Subject string `json:"subject"` // The commit message's subject/title line
AuthorName string `json:"authorName"` // The author name, respecting .mailmap
AuthorEmail string `json:"authorEmail"` // The author email address, respecting .mailmap
AuthorDate time.Time `json:"authorDate"` // The author date
CommitDate time.Time `json:"commitDate"` // The commit date
}
// Map creates a GitRepo with a file map from the given repository path and revision.
// Use blank or HEAD as revision for the currently active revision.
func Map(repository, revision string) (*GitRepo, error) {
m := make(GitMap)
// First get the top level repo path
absRepoPath, err := filepath.Abs(repository)
if err != nil {
return nil, err
}
out, err := git("-C", repository, "rev-parse", "--show-cdup")
if err != nil {
return nil, err
}
cdUp := strings.TrimSpace(string(out))
topLevelPath := filepath.ToSlash(filepath.Join(absRepoPath, cdUp))
gitLogArgs := strings.Fields(fmt.Sprintf(
`--name-only --no-merges --format=format:%%x1e%%H%%x1f%%h%%x1f%%s%%x1f%%aN%%x1f%%aE%%x1f%%ai%%x1f%%ci %s`,
revision,
))
gitLogArgs = append([]string{"-c", "diff.renames=0", "-c", "log.showSignature=0", "-C", repository, "log"}, gitLogArgs...)
out, err = git(gitLogArgs...)
if err != nil {
return nil, err
}
entriesStr := string(out)
entriesStr = strings.Trim(entriesStr, "\n\x1e'")
entries := strings.Split(entriesStr, "\x1e")
for _, e := range entries {
lines := strings.Split(e, "\n")
gitInfo, err := toGitInfo(lines[0])
if err != nil {
return nil, err
}
for _, filename := range lines[1:] {
filename := strings.TrimSpace(filename)
if filename == "" {
continue
}
if _, ok := m[filename]; !ok {
m[filename] = gitInfo
}
}
}
return &GitRepo{Files: m, TopLevelAbsPath: topLevelPath}, nil
}
func git(args ...string) ([]byte, error) {
out, err := exec.Command(gitExec, args...).CombinedOutput()
if err != nil {
if ee, ok := err.(*exec.Error); ok {
if ee.Err == exec.ErrNotFound {
return nil, GitNotFound
}
}
return nil, errors.New(string(bytes.TrimSpace(out)))
}
return out, nil
}
func toGitInfo(entry string) (*GitInfo, error) {
items := strings.Split(entry, "\x1f")
authorDate, err := time.Parse("2006-01-02 15:04:05 -0700", items[5])
if err != nil {
return nil, err
}
commitDate, err := time.Parse("2006-01-02 15:04:05 -0700", items[6])
if err != nil {
return nil, err
}
return &GitInfo{
Hash: items[0],
AbbreviatedHash: items[1],
Subject: items[2],
AuthorName: items[3],
AuthorEmail: items[4],
AuthorDate: authorDate,
CommitDate: commitDate,
}, nil
}
func init() {
initDefaults()
}
func initDefaults() {
gitExec = "git"
}