mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2025-01-24 08:08:38 +00:00
fixed compile error in queue
This commit is contained in:
parent
da74ef5920
commit
3fe374559a
5 changed files with 444 additions and 55 deletions
309
client/main.go
309
client/main.go
|
@ -1,9 +1,308 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
var (
|
import (
|
||||||
// commit sha for the current build.
|
"bytes"
|
||||||
version string = "0.2-dev"
|
"flag"
|
||||||
revision string
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/drone/drone/shared/build"
|
||||||
|
"github.com/drone/drone/shared/build/docker"
|
||||||
|
"github.com/drone/drone/shared/build/log"
|
||||||
|
"github.com/drone/drone/shared/build/repo"
|
||||||
|
"github.com/drone/drone/shared/build/script"
|
||||||
|
|
||||||
|
"gopkg.in/yaml.v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {}
|
var (
|
||||||
|
// identity file (id_rsa) that will be injected
|
||||||
|
// into the container if specified
|
||||||
|
identity = flag.String("identity", "", "")
|
||||||
|
|
||||||
|
// runs Drone in parallel mode if True
|
||||||
|
parallel = flag.Bool("parallel", false, "")
|
||||||
|
|
||||||
|
// build will timeout after N milliseconds.
|
||||||
|
// this will default to 500 minutes (6 hours)
|
||||||
|
timeout = flag.Duration("timeout", 300*time.Minute, "")
|
||||||
|
|
||||||
|
// build will run in a privileged container
|
||||||
|
privileged = flag.Bool("privileged", false, "")
|
||||||
|
|
||||||
|
// runs Drone with verbose output if True
|
||||||
|
verbose = flag.Bool("v", false, "")
|
||||||
|
|
||||||
|
// displays the help / usage if True
|
||||||
|
help = flag.Bool("h", false, "")
|
||||||
|
|
||||||
|
// version number, currently deterined by the
|
||||||
|
// git revision number (sha)
|
||||||
|
version string
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// default logging
|
||||||
|
log.SetPrefix("\033[2m[DRONE] ")
|
||||||
|
log.SetSuffix("\033[0m\n")
|
||||||
|
log.SetOutput(os.Stdout)
|
||||||
|
log.SetPriority(log.LOG_NOTICE)
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Parse the input parameters
|
||||||
|
flag.Usage = usage
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
if *help {
|
||||||
|
flag.Usage()
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
if *verbose {
|
||||||
|
log.SetPriority(log.LOG_DEBUG)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must speicify a command
|
||||||
|
args := flag.Args()
|
||||||
|
if len(args) == 0 {
|
||||||
|
flag.Usage()
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
// run drone build assuming the current
|
||||||
|
// working directory contains the drone.yml
|
||||||
|
case args[0] == "build" && len(args) == 1:
|
||||||
|
path, _ := os.Getwd()
|
||||||
|
path = filepath.Join(path, ".drone.yml")
|
||||||
|
run(path)
|
||||||
|
|
||||||
|
// run drone build where the path to the
|
||||||
|
// source directory is provided
|
||||||
|
case args[0] == "build" && len(args) == 2:
|
||||||
|
path := args[1]
|
||||||
|
path = filepath.Clean(path)
|
||||||
|
path, _ = filepath.Abs(path)
|
||||||
|
path = filepath.Join(path, ".drone.yml")
|
||||||
|
run(path)
|
||||||
|
|
||||||
|
// run drone vet where the path to the
|
||||||
|
// source directory is provided
|
||||||
|
case args[0] == "vet" && len(args) == 2:
|
||||||
|
path := args[1]
|
||||||
|
path = filepath.Clean(path)
|
||||||
|
path, _ = filepath.Abs(path)
|
||||||
|
path = filepath.Join(path, ".drone.yml")
|
||||||
|
vet(path)
|
||||||
|
|
||||||
|
// run drone vet assuming the current
|
||||||
|
// working directory contains the drone.yml
|
||||||
|
case args[0] == "vet" && len(args) == 1:
|
||||||
|
path, _ := os.Getwd()
|
||||||
|
path = filepath.Join(path, ".drone.yml")
|
||||||
|
vet(path)
|
||||||
|
|
||||||
|
// print the version / revision number
|
||||||
|
case args[0] == "version" && len(args) == 1:
|
||||||
|
println(version)
|
||||||
|
|
||||||
|
// print the help message
|
||||||
|
case args[0] == "help" && len(args) == 1:
|
||||||
|
flag.Usage()
|
||||||
|
}
|
||||||
|
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func vet(path string) {
|
||||||
|
// parse the Drone yml file
|
||||||
|
script, err := script.ParseBuildFile(path)
|
||||||
|
if err != nil {
|
||||||
|
log.Err(err.Error())
|
||||||
|
os.Exit(1)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// print the Drone yml as parsed
|
||||||
|
out, _ := yaml.Marshal(script)
|
||||||
|
log.Noticef("parsed yaml:\n%s", string(out))
|
||||||
|
}
|
||||||
|
|
||||||
|
func run(path string) {
|
||||||
|
dockerClient := docker.New()
|
||||||
|
|
||||||
|
// parse the Drone yml file
|
||||||
|
s, err := script.ParseBuildFile(path)
|
||||||
|
if err != nil {
|
||||||
|
log.Err(err.Error())
|
||||||
|
os.Exit(1)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the repository root directory
|
||||||
|
dir := filepath.Dir(path)
|
||||||
|
code := repo.Repo{
|
||||||
|
Name: filepath.Base(dir),
|
||||||
|
Branch: "HEAD", // should we do this?
|
||||||
|
Path: dir,
|
||||||
|
}
|
||||||
|
|
||||||
|
// does the local repository match the
|
||||||
|
// $GOPATH/src/{package} pattern? This is
|
||||||
|
// important so we know the target location
|
||||||
|
// where the code should be copied inside
|
||||||
|
// the container.
|
||||||
|
if gopath, ok := getRepoPath(dir); ok {
|
||||||
|
code.Dir = gopath
|
||||||
|
|
||||||
|
} else if gopath, ok := getGoPath(dir); ok {
|
||||||
|
// in this case we found a GOPATH and
|
||||||
|
// reverse engineered the package path
|
||||||
|
code.Dir = gopath
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// otherwise just use directory name
|
||||||
|
code.Dir = filepath.Base(dir)
|
||||||
|
}
|
||||||
|
|
||||||
|
// this is where the code gets uploaded to the container
|
||||||
|
// TODO move this code to the build package
|
||||||
|
code.Dir = filepath.Join("/var/cache/drone/src", filepath.Clean(code.Dir))
|
||||||
|
|
||||||
|
// track all build results
|
||||||
|
var builders []*build.Builder
|
||||||
|
|
||||||
|
// ssh key to import into container
|
||||||
|
var key []byte
|
||||||
|
if len(*identity) != 0 {
|
||||||
|
key, err = ioutil.ReadFile(*identity)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("[Error] Could not find or read identity file %s\n", *identity)
|
||||||
|
os.Exit(1)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
builds := []*script.Build{s}
|
||||||
|
|
||||||
|
// loop through and create builders
|
||||||
|
for _, b := range builds { //script.Builds {
|
||||||
|
builder := build.New(dockerClient)
|
||||||
|
builder.Build = b
|
||||||
|
builder.Repo = &code
|
||||||
|
builder.Key = key
|
||||||
|
builder.Stdout = os.Stdout
|
||||||
|
builder.Timeout = *timeout
|
||||||
|
builder.Privileged = *privileged
|
||||||
|
|
||||||
|
if *parallel == true {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
builder.Stdout = &buf
|
||||||
|
}
|
||||||
|
|
||||||
|
builders = append(builders, builder)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch *parallel {
|
||||||
|
case false:
|
||||||
|
runSequential(builders)
|
||||||
|
case true:
|
||||||
|
runParallel(builders)
|
||||||
|
}
|
||||||
|
|
||||||
|
// if in parallel mode, print out the buffer
|
||||||
|
// if we had a failure
|
||||||
|
for _, builder := range builders {
|
||||||
|
if builder.BuildState.ExitCode == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if buf, ok := builder.Stdout.(*bytes.Buffer); ok {
|
||||||
|
log.Noticef("printing stdout for failed build %s", builder.Build.Name)
|
||||||
|
println(buf.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// this exit code is initially 0 and will
|
||||||
|
// be set to an error code if any of the
|
||||||
|
// builds fail.
|
||||||
|
var exit int
|
||||||
|
|
||||||
|
fmt.Printf("\nDrone Build Results \033[90m(%v)\033[0m\n", len(builders))
|
||||||
|
|
||||||
|
// loop through and print results
|
||||||
|
for _, builder := range builders {
|
||||||
|
build := builder.Build
|
||||||
|
res := builder.BuildState
|
||||||
|
duration := time.Duration(res.Finished - res.Started)
|
||||||
|
switch {
|
||||||
|
case builder.BuildState.ExitCode == 0:
|
||||||
|
fmt.Printf(" \033[32m\u2713\033[0m %v \033[90m(%v)\033[0m\n", build.Name, humanizeDuration(duration*time.Second))
|
||||||
|
case builder.BuildState.ExitCode != 0:
|
||||||
|
fmt.Printf(" \033[31m\u2717\033[0m %v \033[90m(%v)\033[0m\n", build.Name, humanizeDuration(duration*time.Second))
|
||||||
|
exit = builder.BuildState.ExitCode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
os.Exit(exit)
|
||||||
|
}
|
||||||
|
|
||||||
|
func runSequential(builders []*build.Builder) {
|
||||||
|
// loop through and execute each build
|
||||||
|
for _, builder := range builders {
|
||||||
|
if err := builder.Run(); err != nil {
|
||||||
|
log.Errf("Error executing build: %s", err.Error())
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func runParallel(builders []*build.Builder) {
|
||||||
|
// spawn four worker goroutines
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
for _, builder := range builders {
|
||||||
|
// Increment the WaitGroup counter
|
||||||
|
wg.Add(1)
|
||||||
|
// Launch a goroutine to run the build
|
||||||
|
go func(builder *build.Builder) {
|
||||||
|
defer wg.Done()
|
||||||
|
builder.Run()
|
||||||
|
}(builder)
|
||||||
|
time.Sleep(500 * time.Millisecond) // get weird iptables failures unless we sleep.
|
||||||
|
}
|
||||||
|
|
||||||
|
// wait for the workers to finish
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
var usage = func() {
|
||||||
|
fmt.Println(`Drone is a tool for building and testing code in Docker containers.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
|
||||||
|
drone command [arguments]
|
||||||
|
|
||||||
|
The commands are:
|
||||||
|
|
||||||
|
build build and test the repository
|
||||||
|
version print the version number
|
||||||
|
vet validate the yaml configuration file
|
||||||
|
|
||||||
|
-v runs drone with verbose output
|
||||||
|
-h display this help and exit
|
||||||
|
--parallel runs drone build tasks in parallel
|
||||||
|
--timeout=300ms timeout build after 300 milliseconds
|
||||||
|
--privileged runs drone build in a privileged container
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
drone build builds the source in the pwd
|
||||||
|
drone build /path/to/repo builds the source repository
|
||||||
|
|
||||||
|
Use "drone help [command]" for more information about a command.
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
|
90
client/util.go
Normal file
90
client/util.go
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// getGoPath checks the source codes absolute path
|
||||||
|
// in reference to the host operating system's GOPATH
|
||||||
|
// to correctly determine the code's package path. This
|
||||||
|
// is Go-specific, since Go code must exist in
|
||||||
|
// $GOPATH/src/github.com/{owner}/{name}
|
||||||
|
func getGoPath(dir string) (string, bool) {
|
||||||
|
path := os.Getenv("GOPATH")
|
||||||
|
if len(path) == 0 {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
// append src to the GOPATH, since
|
||||||
|
// the code will be stored in the src dir
|
||||||
|
path = filepath.Join(path, "src")
|
||||||
|
if !filepath.HasPrefix(dir, path) {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove the prefix from the directory
|
||||||
|
// this should leave us with the go package name
|
||||||
|
return dir[len(path):], true
|
||||||
|
}
|
||||||
|
|
||||||
|
var gopathExp = regexp.MustCompile("./src/(github.com/[^/]+/[^/]+|bitbucket.org/[^/]+/[^/]+|code.google.com/[^/]+/[^/]+)")
|
||||||
|
|
||||||
|
// getRepoPath checks the source codes absolute path
|
||||||
|
// on the host operating system in an attempt
|
||||||
|
// to correctly determine the code's package path. This
|
||||||
|
// is Go-specific, since Go code must exist in
|
||||||
|
// $GOPATH/src/github.com/{owner}/{name}
|
||||||
|
func getRepoPath(dir string) (path string, ok bool) {
|
||||||
|
// let's get the package directory based
|
||||||
|
// on the path in the host OS
|
||||||
|
indexes := gopathExp.FindStringIndex(dir)
|
||||||
|
if len(indexes) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
index := indexes[len(indexes)-1]
|
||||||
|
|
||||||
|
// if the dir is /home/ubuntu/go/src/github.com/foo/bar
|
||||||
|
// the index will start at /src/github.com/foo/bar.
|
||||||
|
// We'll need to strip "/src/" which is where the
|
||||||
|
// magic number 5 comes from.
|
||||||
|
index = strings.LastIndex(dir, "/src/")
|
||||||
|
return dir[index+5:], true
|
||||||
|
}
|
||||||
|
|
||||||
|
// getGitOrigin checks the .git origin in an attempt
|
||||||
|
// to correctly determine the code's package path. This
|
||||||
|
// is Go-specific, since Go code must exist in
|
||||||
|
// $GOPATH/src/github.com/{owner}/{name}
|
||||||
|
func getGitOrigin(dir string) (path string, ok bool) {
|
||||||
|
// TODO
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// prints the time as a human readable string
|
||||||
|
func humanizeDuration(d time.Duration) string {
|
||||||
|
if seconds := int(d.Seconds()); seconds < 1 {
|
||||||
|
return "Less than a second"
|
||||||
|
} else if seconds < 60 {
|
||||||
|
return fmt.Sprintf("%d seconds", seconds)
|
||||||
|
} else if minutes := int(d.Minutes()); minutes == 1 {
|
||||||
|
return "About a minute"
|
||||||
|
} else if minutes < 60 {
|
||||||
|
return fmt.Sprintf("%d minutes", minutes)
|
||||||
|
} else if hours := int(d.Hours()); hours == 1 {
|
||||||
|
return "About an hour"
|
||||||
|
} else if hours < 48 {
|
||||||
|
return fmt.Sprintf("%d hours", hours)
|
||||||
|
} else if hours < 24*7*2 {
|
||||||
|
return fmt.Sprintf("%d days", hours/24)
|
||||||
|
} else if hours < 24*30*3 {
|
||||||
|
return fmt.Sprintf("%d weeks", hours/24/7)
|
||||||
|
} else if hours < 24*365*2 {
|
||||||
|
return fmt.Sprintf("%d months", hours/24/30)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%f years", d.Hours()/24/365)
|
||||||
|
}
|
|
@ -4,10 +4,10 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/drone/drone/pkg/build"
|
"github.com/drone/drone/shared/build"
|
||||||
"github.com/drone/drone/pkg/build/docker"
|
"github.com/drone/drone/shared/build/docker"
|
||||||
"github.com/drone/drone/pkg/build/repo"
|
"github.com/drone/drone/shared/build/repo"
|
||||||
"github.com/drone/drone/pkg/build/script"
|
"github.com/drone/drone/shared/build/script"
|
||||||
)
|
)
|
||||||
|
|
||||||
type BuildRunner interface {
|
type BuildRunner interface {
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
package queue
|
package queue
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/drone/drone/pkg/resource/build"
|
"github.com/drone/drone/server/resource/build"
|
||||||
"github.com/drone/drone/pkg/resource/commit"
|
"github.com/drone/drone/server/resource/commit"
|
||||||
"github.com/drone/drone/pkg/resource/repo"
|
"github.com/drone/drone/server/resource/repo"
|
||||||
"github.com/drone/drone/pkg/resource/user"
|
"github.com/drone/drone/server/resource/user"
|
||||||
|
|
||||||
"github.com/drone/drone/pkg/build/script"
|
"github.com/drone/drone/shared/build/script"
|
||||||
)
|
)
|
||||||
|
|
||||||
// A Queue dispatches tasks to workers.
|
// A Queue dispatches tasks to workers.
|
||||||
|
|
|
@ -3,21 +3,17 @@ package queue
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/drone/drone/pkg/build/git"
|
"github.com/drone/drone/server/channel"
|
||||||
r "github.com/drone/drone/pkg/build/repo"
|
"github.com/drone/drone/shared/build/git"
|
||||||
"github.com/drone/drone/pkg/channel"
|
r "github.com/drone/drone/shared/build/repo"
|
||||||
"github.com/drone/drone/pkg/database"
|
|
||||||
. "github.com/drone/drone/pkg/model"
|
|
||||||
//"github.com/drone/drone/pkg/plugin/notify"
|
//"github.com/drone/drone/pkg/plugin/notify"
|
||||||
"github.com/drone/go-github/github"
|
|
||||||
"io"
|
"io"
|
||||||
//"log"
|
|
||||||
"net/url"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/drone/drone/pkg/resource/build"
|
"github.com/drone/drone/server/resource/build"
|
||||||
"github.com/drone/drone/pkg/resource/commit"
|
"github.com/drone/drone/server/resource/commit"
|
||||||
|
"github.com/drone/drone/server/resource/repo"
|
||||||
)
|
)
|
||||||
|
|
||||||
type worker struct {
|
type worker struct {
|
||||||
|
@ -206,48 +202,52 @@ func (w *worker) runBuild(task *BuildTask, buf io.Writer) (bool, error) {
|
||||||
// updateGitHubStatus is a helper function that will send
|
// updateGitHubStatus is a helper function that will send
|
||||||
// the build status to GitHub using the Status API.
|
// the build status to GitHub using the Status API.
|
||||||
// see https://github.com/blog/1227-commit-status-api
|
// see https://github.com/blog/1227-commit-status-api
|
||||||
func updateGitHubStatus(repo *Repo, commit *Commit) error {
|
func updateGitHubStatus(repo *repo.Repo, commit *commit.Commit) error {
|
||||||
|
/*
|
||||||
|
// convert from drone status to github status
|
||||||
|
var message, status string
|
||||||
|
switch commit.Status {
|
||||||
|
case "Success":
|
||||||
|
status = "success"
|
||||||
|
message = "The build succeeded on drone.io"
|
||||||
|
case "Failure":
|
||||||
|
status = "failure"
|
||||||
|
message = "The build failed on drone.io"
|
||||||
|
case "Started":
|
||||||
|
status = "pending"
|
||||||
|
message = "The build is pending on drone.io"
|
||||||
|
default:
|
||||||
|
status = "error"
|
||||||
|
message = "The build errored on drone.io"
|
||||||
|
}
|
||||||
|
|
||||||
// convert from drone status to github status
|
// get the system settings
|
||||||
var message, status string
|
settings, _ := database.GetSettings()
|
||||||
switch commit.Status {
|
|
||||||
case "Success":
|
|
||||||
status = "success"
|
|
||||||
message = "The build succeeded on drone.io"
|
|
||||||
case "Failure":
|
|
||||||
status = "failure"
|
|
||||||
message = "The build failed on drone.io"
|
|
||||||
case "Started":
|
|
||||||
status = "pending"
|
|
||||||
message = "The build is pending on drone.io"
|
|
||||||
default:
|
|
||||||
status = "error"
|
|
||||||
message = "The build errored on drone.io"
|
|
||||||
}
|
|
||||||
|
|
||||||
// get the system settings
|
// get the user from the database
|
||||||
settings, _ := database.GetSettings()
|
// since we need his / her GitHub token
|
||||||
|
user, err := database.GetUser(repo.UserID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// get the user from the database
|
client := github.New(user.GithubToken)
|
||||||
// since we need his / her GitHub token
|
client.ApiUrl = settings.GitHubApiUrl
|
||||||
user, err := database.GetUser(repo.UserID)
|
buildUrl := getBuildUrl(settings.URL().String(), repo, commit)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
client := github.New(user.GithubToken)
|
return client.Repos.CreateStatus(repo.Owner, repo.Name, status, buildUrl, message, commit.Hash)
|
||||||
client.ApiUrl = settings.GitHubApiUrl
|
*/
|
||||||
buildUrl := getBuildUrl(settings.URL().String(), repo, commit)
|
return nil
|
||||||
|
|
||||||
return client.Repos.CreateStatus(repo.Owner, repo.Name, status, buildUrl, message, commit.Hash)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func getBuildUrl(host string, repo *Repo, commit *Commit) string {
|
/*
|
||||||
|
func getBuildUrl(host string, repo *repo.Repo, commit *commit.Commit) string {
|
||||||
branchQuery := url.Values{}
|
branchQuery := url.Values{}
|
||||||
branchQuery.Set("branch", commit.Branch)
|
branchQuery.Set("branch", commit.Branch)
|
||||||
buildUrl := fmt.Sprintf("%s/%s/commit/%s?%s", host, repo.Slug, commit.Hash, branchQuery.Encode())
|
buildUrl := fmt.Sprintf("%s/%s/commit/%s?%s", host, repo.Slug, commit.Hash, branchQuery.Encode())
|
||||||
return buildUrl
|
return buildUrl
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
type bufferWrapper struct {
|
type bufferWrapper struct {
|
||||||
buf bytes.Buffer
|
buf bytes.Buffer
|
||||||
|
|
Loading…
Reference in a new issue