aboutsummaryrefslogtreecommitdiffstats
path: root/pkg/git
diff options
context:
space:
mode:
authorDmitry Vyukov <dvyukov@google.com>2017-11-16 10:12:17 +0100
committerDmitry Vyukov <dvyukov@google.com>2017-11-16 10:12:17 +0100
commit9a98ae3fb64f0aeac0336263590ddcff6c581024 (patch)
tree31110be8d0cd9a5450099896c197175e5d5591e1 /pkg/git
parent95cf3e724785cf8d46beec31c4a009b5a4c6af91 (diff)
pkg/git: provide more helper functions
Add Patch, Checkout, CheckRepoAddress and CheckBranch. Will be needed for patch testing.
Diffstat (limited to 'pkg/git')
-rw-r--r--pkg/git/git.go75
-rw-r--r--pkg/git/git_test.go51
2 files changed, 126 insertions, 0 deletions
diff --git a/pkg/git/git.go b/pkg/git/git.go
index 1bb961405..0178a3004 100644
--- a/pkg/git/git.go
+++ b/pkg/git/git.go
@@ -5,8 +5,11 @@
package git
import (
+ "bytes"
"fmt"
"os"
+ "os/exec"
+ "regexp"
"strings"
"time"
@@ -48,6 +51,24 @@ func Poll(dir, repo, branch string) (string, error) {
return HeadCommit(dir)
}
+// Checkout checkouts the specified repository/branch in dir.
+// It does not fetch history and efficiently supports checkouts of different repos in the same dir.
+func Checkout(dir, repo, branch string) (string, error) {
+ if _, err := osutil.RunCmd(timeout, dir, "git", "reset", "--hard"); err != nil {
+ if err := initRepo(dir); err != nil {
+ return "", err
+ }
+ }
+ output, err := osutil.RunCmd(timeout, dir, "git", "fetch", "--no-tags", "--depth=1", repo, branch)
+ if err != nil {
+ return "", fmt.Errorf("git fetch %v %v failed: %v\n%s", repo, branch, err, output)
+ }
+ if output, err := osutil.RunCmd(timeout, dir, "git", "checkout", "FETCH_HEAD"); err != nil {
+ return "", fmt.Errorf("git checkout FETCH_HEAD failed: %v\n%s", err, output)
+ }
+ return HeadCommit(dir)
+}
+
func clone(dir, repo, branch string) error {
if err := os.RemoveAll(dir); err != nil {
return fmt.Errorf("failed to remove repo dir: %v", err)
@@ -66,6 +87,20 @@ func clone(dir, repo, branch string) error {
return err
}
+func initRepo(dir string) error {
+ if err := os.RemoveAll(dir); err != nil {
+ return fmt.Errorf("failed to remove repo dir: %v", err)
+ }
+ if err := osutil.MkdirAll(dir); err != nil {
+ return fmt.Errorf("failed to create repo dir: %v", err)
+ }
+ output, err := osutil.RunCmd(timeout, dir, "git", "init")
+ if err != nil {
+ return fmt.Errorf("failed to init git repo: %v\n%s", err, output)
+ }
+ return nil
+}
+
// HeadCommit returns hash of the HEAD commit of the current branch of git repository in dir.
func HeadCommit(dir string) (string, error) {
output, err := osutil.RunCmd(timeout, dir, "git", "log", "--pretty=format:%H", "-n", "1")
@@ -116,3 +151,43 @@ var commitPrefixes = []string{
"FROMGIT:",
"net-backports:",
}
+
+func Patch(dir string, patch []byte) error {
+ // Do --dry-run first to not mess with partially consistent state.
+ cmd := exec.Command("patch", "-p1", "--force", "--ignore-whitespace", "--dry-run")
+ cmd.Stdin = bytes.NewReader(patch)
+ cmd.Dir = dir
+ if output, err := cmd.CombinedOutput(); err != nil {
+ // If it reverses clean, then it's already applied
+ // (seems to be the easiest way to detect it).
+ cmd = exec.Command("patch", "-p1", "--force", "--ignore-whitespace", "--reverse", "--dry-run")
+ cmd.Stdin = bytes.NewReader(patch)
+ cmd.Dir = dir
+ if _, err := cmd.CombinedOutput(); err == nil {
+ return fmt.Errorf("patch is already applied")
+ }
+ return fmt.Errorf("failed to apply patch:\n%s", output)
+ }
+ // Now apply for real.
+ cmd = exec.Command("patch", "-p1", "--force", "--ignore-whitespace")
+ cmd.Stdin = bytes.NewReader(patch)
+ cmd.Dir = dir
+ if output, err := cmd.CombinedOutput(); err != nil {
+ return fmt.Errorf("failed to apply patch after dry run:\n%s", output)
+ }
+ return nil
+}
+
+// CheckRepoAddress does a best-effort approximate check of a git repo address.
+func CheckRepoAddress(repo string) bool {
+ return gitRepoRe.MatchString(repo)
+}
+
+var gitRepoRe = regexp.MustCompile("^(git|ssh|http|https|ftp|ftps)://[a-zA-Z0-9-_]+(\\.[a-zA-Z0-9-_]+)+(:[0-9]+)?/[a-zA-Z0-9-_./]+\\.git(/)?$")
+
+// CheckBranch does a best-effort approximate check of a git branch name.
+func CheckBranch(branch string) bool {
+ return gitBranchRe.MatchString(branch)
+}
+
+var gitBranchRe = regexp.MustCompile("^[a-zA-Z0-9-_/.]{2,200}$")
diff --git a/pkg/git/git_test.go b/pkg/git/git_test.go
index 42d2d3a07..3cc1300dd 100644
--- a/pkg/git/git_test.go
+++ b/pkg/git/git_test.go
@@ -21,3 +21,54 @@ func TestCanonicalizeCommit(t *testing.T) {
}
}
}
+
+func TestCheckRepoAddress(t *testing.T) {
+ var tests = []struct {
+ repo string
+ result bool
+ }{
+ {"git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git", true},
+ {"https://github.com/torvalds/linux.git", true},
+ {"git://git.kernel.org/pub/scm/linux/kernel/git/stable/linux-stable.git", true},
+ {"git://git.cmpxchg.org/linux-mmots.git", true},
+ {"https://anonscm.debian.org/git/kernel/linux.git", true},
+ {"git://kernel.ubuntu.com/ubuntu/ubuntu-zesty.git", true},
+ {"http://host.xz:123/path/to/repo.git/", true},
+ {"", false},
+ {"foobar", false},
+ {"linux-next", false},
+ {"foo://kernel.ubuntu.com/ubuntu/ubuntu-zesty.git", false},
+ {"git://kernel/ubuntu.git", false},
+ {"git://kernel.com/ubuntu", false},
+ {"gitgit://kernel.ubuntu.com/ubuntu/ubuntu-zesty.git", false},
+ }
+ for _, test := range tests {
+ res := CheckRepoAddress(test.repo)
+ if res != test.result {
+ t.Errorf("%v: got %v, want %v", test.repo, res, test.result)
+ }
+ }
+}
+
+func TestCheckBranch(t *testing.T) {
+ var tests = []struct {
+ branch string
+ result bool
+ }{
+ {"master", true},
+ {"core/core", true},
+ {"irq-irqdomain-for-linus", true},
+ {"timers/2038", true},
+ {"ubuntu-zesty/v4.9.4", true},
+ {"WIP.locking/atomics", true},
+ {"linux-4.9.y", true},
+ {"abi_spec", true},
+ {"@", false},
+ }
+ for _, test := range tests {
+ res := CheckBranch(test.branch)
+ if res != test.result {
+ t.Errorf("%v: got %v, want %v", test.branch, res, test.result)
+ }
+ }
+}