forked from openshift/origin
/
git.go
129 lines (110 loc) · 3.78 KB
/
git.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
package git
import (
"bufio"
"io"
"net/url"
"os"
"os/exec"
"regexp"
"strings"
"github.com/golang/glog"
"github.com/openshift/source-to-image/pkg/api"
"github.com/openshift/source-to-image/pkg/util"
)
// Git is an interface used by main STI code to extract/checkout git repositories
type Git interface {
ValidCloneSpec(source string) bool
Clone(source, target string) error
Checkout(repo, ref string) error
GetInfo(string) *api.SourceInfo
}
// New returns a new instance of the default implementation of the Git interface
func New() Git {
return &stiGit{
runner: util.NewCommandRunner(),
}
}
type stiGit struct {
runner util.CommandRunner
}
var gitSshURLExp = regexp.MustCompile(`\A([\w\d\-_\.+]+@[\w\d\-_\.+]+:[\w\d\-_\.+%/]+\.git)$`)
var allowedSchemes = []string{"git", "http", "https", "file", "ssh"}
func stringInSlice(s string, slice []string) bool {
for _, element := range slice {
if s == element {
return true
}
}
return false
}
// ValidCloneSpec determines if the given string reference points to a valid git
// repository
func (h *stiGit) ValidCloneSpec(source string) bool {
url, err := url.Parse(source)
if err != nil {
return false
}
if stringInSlice(url.Scheme, allowedSchemes) {
return true
}
// support 'git@' ssh urls and local protocol without 'file://' scheme
return url.Scheme == "" && (strings.HasSuffix(source, ".git") ||
(strings.HasPrefix(source, "git@") && gitSshURLExp.MatchString(source)))
}
// Clone clones a git repository to a specific target directory
func (h *stiGit) Clone(source, target string) error {
// NOTE, we don NOT pass in both stdout and stderr, because
// with running with --quiet, and no output heading to stdout, hangs were occurring with the coordination
// of underlying channel management in the Go layer when dealing with the Go Cmd wrapper around
// git, sending of stdout/stderr to the Pipes created here, and the glog routines sent to pipeToLog
//
// It was agreed that we wanted to keep --quiet and no stdout output ....leaving stderr only since
// --quiet does not surpress that anyway reduced the frequency of the hang, but it still occurred.
// the pipeToLog method has been left for now for historical purposes, but if this implemenetation
// of git clone holds, we'll want to delete that at some point.
opts := util.CommandOpts{}
return h.runner.RunWithOptions(opts, "git", "clone", "--quiet", "--recursive", source, target)
}
// Checkout checks out a specific branch reference of a given git repository
func (h *stiGit) Checkout(repo, ref string) error {
opts := util.CommandOpts{
Stdout: os.Stdout,
Stderr: os.Stderr,
Dir: repo,
}
return h.runner.RunWithOptions(opts, "git", "checkout", ref)
}
// GetInfo retrieves the informations about the source code and commit
func (h *stiGit) GetInfo(repo string) *api.SourceInfo {
git := func(arg ...string) string {
command := exec.Command("git", arg...)
command.Dir = repo
out, err := command.CombinedOutput()
if err != nil {
glog.V(1).Infof("Error executing 'git %#v': %s (%v)", arg, out, err)
return ""
}
return strings.TrimSpace(string(out))
}
return &api.SourceInfo{
Location: git("config", "--get", "remote.origin.url"),
Ref: git("rev-parse", "--abbrev-ref", "HEAD"),
CommitID: git("rev-parse", "--short", "--verify", "HEAD"),
Author: git("--no-pager", "show", "-s", "--format=%an <%ae>", "HEAD"),
Date: git("--no-pager", "show", "-s", "--format=%ad", "HEAD"),
Message: git("--no-pager", "show", "-s", "--format=%<(80,trunc)%s", "HEAD"),
}
}
func pipeToLog(reader io.Reader, log func(...interface{})) {
scanner := bufio.NewReader(reader)
for {
if text, err := scanner.ReadString('\n'); err != nil {
if err != io.ErrClosedPipe {
glog.Errorf("Error reading stdout, %v", err)
}
break
} else {
log(text)
}
}
}