From 9a98ae3fb64f0aeac0336263590ddcff6c581024 Mon Sep 17 00:00:00 2001 From: Dmitry Vyukov Date: Thu, 16 Nov 2017 10:12:17 +0100 Subject: pkg/git: provide more helper functions Add Patch, Checkout, CheckRepoAddress and CheckBranch. Will be needed for patch testing. --- pkg/git/git.go | 75 +++++++++++++++++++++++++++++++++++++++++++++++++++++ pkg/git/git_test.go | 51 ++++++++++++++++++++++++++++++++++++ 2 files changed, 126 insertions(+) (limited to 'pkg/git') 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) + } + } +} -- cgit mrf-deployment