184 lines
4.2 KiB
Go
184 lines
4.2 KiB
Go
|
// Copyright 2019 Sorint.lab
|
||
|
//
|
||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
// you may not use this file except in compliance with the License.
|
||
|
// You may obtain a copy of the License at
|
||
|
//
|
||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||
|
//
|
||
|
// Unless required by applicable law or agreed to in writing, software
|
||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied
|
||
|
// See the License for the specific language governing permissions and
|
||
|
// limitations under the License.
|
||
|
|
||
|
package util
|
||
|
|
||
|
import (
|
||
|
"bufio"
|
||
|
"bytes"
|
||
|
"context"
|
||
|
"fmt"
|
||
|
"io"
|
||
|
"net/url"
|
||
|
"os"
|
||
|
"os/exec"
|
||
|
"regexp"
|
||
|
"strings"
|
||
|
"syscall"
|
||
|
|
||
|
"github.com/pkg/errors"
|
||
|
)
|
||
|
|
||
|
// scpSyntaxRe matches the SCP-like addresses used by Git to access repositories
|
||
|
// by SSH.
|
||
|
var scpSyntaxRe = regexp.MustCompile(`^([a-zA-Z0-9_]+)@([a-zA-Z0-9._-]+):(.*)$`)
|
||
|
|
||
|
func ParseGitURL(u string) (*url.URL, error) {
|
||
|
if m := scpSyntaxRe.FindStringSubmatch(u); m != nil {
|
||
|
// Match SCP-like syntax and convert it to a URL.
|
||
|
// Eg, "git@github.com:user/repo" becomes
|
||
|
// "ssh://git@github.com/user/repo".
|
||
|
return &url.URL{
|
||
|
Scheme: "ssh",
|
||
|
User: url.User(m[1]),
|
||
|
Host: m[2],
|
||
|
Path: m[3],
|
||
|
}, nil
|
||
|
}
|
||
|
return url.Parse(u)
|
||
|
}
|
||
|
|
||
|
type Git struct {
|
||
|
cmd *exec.Cmd
|
||
|
GitDir string
|
||
|
Env []string
|
||
|
}
|
||
|
|
||
|
func (g *Git) gitCmd(ctx context.Context, args ...string) *exec.Cmd {
|
||
|
cmd := exec.CommandContext(ctx, "git", args...)
|
||
|
// only keep the PATH, HOME and other useful env vars
|
||
|
cmdEnv := []string{}
|
||
|
cmdEnv = append(cmdEnv, "PATH="+os.Getenv("PATH"))
|
||
|
cmdEnv = append(cmdEnv, "HOME="+os.Getenv("HOME"))
|
||
|
cmdEnv = append(cmdEnv, "USER="+os.Getenv("USER"))
|
||
|
if g.GitDir != "" {
|
||
|
cmdEnv = append(cmdEnv, "GIT_DIR="+g.GitDir)
|
||
|
}
|
||
|
cmdEnv = append(cmdEnv, g.Env...)
|
||
|
cmd.Env = cmdEnv
|
||
|
|
||
|
return cmd
|
||
|
}
|
||
|
|
||
|
func (g *Git) Output(ctx context.Context, stdin io.Reader, args ...string) ([]byte, error) {
|
||
|
cmd := g.gitCmd(ctx, args...)
|
||
|
|
||
|
if stdin != nil {
|
||
|
cmd.Stdin = stdin
|
||
|
}
|
||
|
|
||
|
stderr := &bytes.Buffer{}
|
||
|
cmd.Stderr = stderr
|
||
|
|
||
|
out, err := cmd.Output()
|
||
|
if err != nil {
|
||
|
gitErr := stderr.String()
|
||
|
if len(gitErr) > 0 {
|
||
|
return nil, errors.New(stderr.String())
|
||
|
} else {
|
||
|
return nil, err
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return out, err
|
||
|
}
|
||
|
|
||
|
func (g *Git) OutputLines(ctx context.Context, stdin io.Reader, args ...string) ([]string, error) {
|
||
|
out, err := g.Output(ctx, stdin, args...)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
scanner := bufio.NewScanner(bytes.NewReader(out))
|
||
|
lines := []string{}
|
||
|
for scanner.Scan() {
|
||
|
lines = append(lines, scanner.Text())
|
||
|
}
|
||
|
if err := scanner.Err(); err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
return lines, nil
|
||
|
}
|
||
|
|
||
|
func (g *Git) Pipe(ctx context.Context, w io.Writer, r io.Reader, args ...string) error {
|
||
|
cmd := g.gitCmd(ctx, args...)
|
||
|
|
||
|
cmd.Stdin = r
|
||
|
|
||
|
stdout, err := cmd.StdoutPipe()
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
stderr := &bytes.Buffer{}
|
||
|
cmd.Stderr = stderr
|
||
|
|
||
|
if err := cmd.Start(); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
if _, err := io.Copy(w, stdout); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
if err := cmd.Wait(); err != nil {
|
||
|
gitErr := stderr.String()
|
||
|
if len(gitErr) > 0 {
|
||
|
return errors.New(stderr.String())
|
||
|
} else {
|
||
|
return err
|
||
|
}
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
type ErrNotFound struct {
|
||
|
Key string
|
||
|
}
|
||
|
|
||
|
func (e *ErrNotFound) Error() string {
|
||
|
return fmt.Sprintf("key `%q` was not found", e.Key)
|
||
|
}
|
||
|
|
||
|
func (g *Git) ConfigGet(ctx context.Context, args ...string) (string, error) {
|
||
|
args = append([]string{"config", "--get", "--null"}, args...)
|
||
|
out, err := g.Output(ctx, nil, args...)
|
||
|
|
||
|
if exitError, ok := err.(*exec.ExitError); ok {
|
||
|
if waitStatus, ok := exitError.Sys().(syscall.WaitStatus); ok {
|
||
|
if waitStatus.ExitStatus() == 1 {
|
||
|
return "", &ErrNotFound{Key: args[len(args)-1]}
|
||
|
}
|
||
|
}
|
||
|
return "", err
|
||
|
}
|
||
|
|
||
|
return strings.TrimRight(string(out), "\000"), nil
|
||
|
}
|
||
|
|
||
|
func (g *Git) ConfigSet(ctx context.Context, args ...string) (string, error) {
|
||
|
args = append([]string{"config", "--null"}, args...)
|
||
|
out, err := g.Output(ctx, nil, args...)
|
||
|
|
||
|
if exitError, ok := err.(*exec.ExitError); ok {
|
||
|
if waitStatus, ok := exitError.Sys().(syscall.WaitStatus); ok {
|
||
|
if waitStatus.ExitStatus() == 1 {
|
||
|
return "", &ErrNotFound{Key: args[len(args)-1]}
|
||
|
}
|
||
|
}
|
||
|
return "", err
|
||
|
}
|
||
|
|
||
|
return strings.TrimRight(string(out), "\000"), nil
|
||
|
}
|